Compare commits

..

770 Commits

Author SHA1 Message Date
Max Brunsfeld
3fb1e6846d Don't scroll List on out-of-bounds mouse wheel events 2023-12-09 12:00:33 -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
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
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
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
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
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
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
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
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
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
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
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
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
Kirill Bulatov
92953fb53d If enabled, search in ignored files during project search 2023-11-22 16:41:02 +02:00
Joseph T. Lyons
a4a1e6ba98 WIP
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-11-21 22:32:07 -05: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
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
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
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
418 changed files with 61729 additions and 41904 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

@@ -1,146 +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: Set up default .cargo/config.toml
run: printf "[build]\nrustflags = [\"-D\", \"warnings\"]" > $HOME/.cargo/config.toml
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: Run rustfmt
uses: ./.github/actions/check_formatting
- 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"
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
- name: Run tests
uses: ./.github/actions/run_tests
- name: Build collab
run: cargo build -p collab
- name: Build collab
run: cargo build -p collab
- name: Build other binaries
run: cargo build --workspace --bins --all-features
- 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 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
# TODO kb seems that zed.dev relies on GitHub releases for release version tracking.
# Find alternatives for `nightly` or just go on with more releases?
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
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 }}

View File

@@ -1,98 +1,98 @@
name: Release Nightly
on:
schedule:
# Fire every night at 1:00am
- cron: "0 1 * * *"
push:
tags:
- "nightly"
schedule:
# Fire every night at 1:00am
- cron: "0 1 * * *"
push:
tags:
- "nightly"
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: Checkout repo
uses: actions/checkout@v3
with:
clean: false
submodules: "recursive"
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
- 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"
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
- 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
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: Install Node
uses: actions/setup-node@v3
with:
node-version: "18"
- 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: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- 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: 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: Generate license file
run: script/generate-licenses
- name: Create app bundle
run: script/bundle -2
- name: Create app bundle
run: script/bundle -2
- name: Upload Zed Nightly
run: script/upload-nightly
- name: Upload Zed Nightly
run: script/upload-nightly

407
Cargo.lock generated
View File

@@ -19,6 +19,25 @@ dependencies = [
"workspace",
]
[[package]]
name = "activity_indicator2"
version = "0.1.0"
dependencies = [
"anyhow",
"auto_update2",
"editor2",
"futures 0.3.28",
"gpui2",
"language2",
"project2",
"settings2",
"smallvec",
"theme2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "addr2line"
version = "0.17.0"
@@ -293,6 +312,15 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "arrayref"
version = "0.3.7"
@@ -357,6 +385,47 @@ dependencies = [
"workspace",
]
[[package]]
name = "assistant2"
version = "0.1.0"
dependencies = [
"ai2",
"anyhow",
"chrono",
"client2",
"collections",
"ctor",
"editor2",
"env_logger 0.9.3",
"fs2",
"futures 0.3.28",
"gpui2",
"indoc",
"isahc",
"language2",
"log",
"menu2",
"multi_buffer2",
"ordered-float 2.10.0",
"parking_lot 0.11.2",
"project2",
"rand 0.8.5",
"regex",
"schemars",
"search2",
"semantic_index2",
"serde",
"serde_json",
"settings2",
"smol",
"theme2",
"tiktoken-rs",
"ui2",
"util",
"uuid 1.4.1",
"workspace2",
]
[[package]]
name = "async-broadcast"
version = "0.4.1"
@@ -394,9 +463,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.3.15"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
dependencies = [
"flate2",
"futures-core",
@@ -1076,6 +1145,24 @@ dependencies = [
"workspace",
]
[[package]]
name = "breadcrumbs2"
version = "0.1.0"
dependencies = [
"collections",
"editor2",
"gpui2",
"itertools 0.10.5",
"language2",
"outline2",
"project2",
"search2",
"settings2",
"theme2",
"ui2",
"workspace2",
]
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
@@ -1192,6 +1279,7 @@ dependencies = [
"fs2",
"futures 0.3.28",
"gpui2",
"image",
"language2",
"live_kit_client2",
"log",
@@ -1203,6 +1291,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings2",
"smallvec",
"util",
]
@@ -1299,10 +1388,11 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.84"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"jobserver",
"libc",
]
@@ -1664,7 +1754,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.28.0"
version = "0.30.1"
dependencies = [
"anyhow",
"async-trait",
@@ -1751,7 +1841,7 @@ dependencies = [
"clap 3.2.25",
"client2",
"clock",
"collab_ui",
"collab_ui2",
"collections",
"ctor",
"dashmap",
@@ -1880,6 +1970,7 @@ dependencies = [
"postage",
"pretty_assertions",
"project2",
"recent_projects2",
"rich_text2",
"rpc2",
"schemars",
@@ -2074,12 +2165,13 @@ dependencies = [
"lsp2",
"node_runtime",
"parking_lot 0.11.2",
"rpc",
"rpc2",
"serde",
"serde_derive",
"settings2",
"smol",
"theme2",
"ui2",
"util",
]
@@ -2102,6 +2194,25 @@ dependencies = [
"workspace",
]
[[package]]
name = "copilot_button2"
version = "0.1.0"
dependencies = [
"anyhow",
"copilot2",
"editor2",
"fs2",
"futures 0.3.28",
"gpui2",
"language2",
"settings2",
"smol",
"theme2",
"util",
"workspace2",
"zed_actions2",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -3062,6 +3173,12 @@ dependencies = [
"regex",
]
[[package]]
name = "fast-srgb8"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
[[package]]
name = "fastrand"
version = "1.9.0"
@@ -3123,6 +3240,39 @@ dependencies = [
"workspace",
]
[[package]]
name = "feedback2"
version = "0.1.0"
dependencies = [
"anyhow",
"client2",
"db2",
"editor2",
"futures 0.3.28",
"gpui2",
"human_bytes",
"isahc",
"language2",
"lazy_static",
"log",
"menu2",
"postage",
"project2",
"regex",
"search2",
"serde",
"serde_derive",
"settings2",
"smallvec",
"sysinfo",
"theme2",
"tree-sitter-markdown",
"ui2",
"urlencoding",
"util",
"workspace2",
]
[[package]]
name = "file-per-thread-logger"
version = "0.1.6"
@@ -4521,6 +4671,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
[[package]]
name = "journal"
version = "0.1.0"
@@ -4750,6 +4909,24 @@ dependencies = [
"workspace",
]
[[package]]
name = "language_selector2"
version = "0.1.0"
dependencies = [
"anyhow",
"editor2",
"fuzzy2",
"gpui2",
"language2",
"picker2",
"project2",
"settings2",
"theme2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "language_tools"
version = "0.1.0"
@@ -4878,18 +5055,18 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linkme"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ed2ee9464ff9707af8e9ad834cffa4802f072caad90639c583dd3c62e6e608"
checksum = "b1e6b0bb9ca88d3c5ae88240beb9683821f903b824ee8381ef9ab4e8522fbfa9"
dependencies = [
"linkme-impl",
]
[[package]]
name = "linkme-impl"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396"
checksum = "b3b3f61e557a617ec6ba36c79431e1f3b5e100d67cfbdb61ed6ef384298af016"
dependencies = [
"proc-macro2",
"quote",
@@ -6086,12 +6263,54 @@ dependencies = [
"workspace",
]
[[package]]
name = "outline2"
version = "0.1.0"
dependencies = [
"editor2",
"fuzzy2",
"gpui2",
"language2",
"ordered-float 2.10.0",
"picker2",
"postage",
"settings2",
"smol",
"text2",
"theme2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "palette"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e2f34147767aa758aa649415b50a69eeb46a67f9dc7db8011eeb3d84b351dc"
dependencies = [
"approx",
"fast-srgb8",
"palette_derive",
]
[[package]]
name = "palette_derive"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7db010ec5ff3d4385e4f133916faacd9dad0f6a09394c92d825b3aed310fa0a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]]
name = "parity-tokio-ipc"
version = "0.9.0"
@@ -6976,6 +7195,18 @@ dependencies = [
"workspace",
]
[[package]]
name = "quick_action_bar2"
version = "0.1.0"
dependencies = [
"assistant2",
"editor2",
"gpui2",
"search2",
"ui2",
"workspace2",
]
[[package]]
name = "quote"
version = "1.0.33"
@@ -7158,6 +7389,28 @@ dependencies = [
"workspace",
]
[[package]]
name = "recent_projects2"
version = "0.1.0"
dependencies = [
"db",
"editor2",
"futures 0.3.28",
"fuzzy2",
"gpui2",
"language2",
"ordered-float 2.10.0",
"picker2",
"postage",
"settings2",
"smol",
"text2",
"theme2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -8137,6 +8390,57 @@ dependencies = [
"workspace",
]
[[package]]
name = "semantic_index2"
version = "0.1.0"
dependencies = [
"ai2",
"anyhow",
"async-trait",
"client2",
"collections",
"ctor",
"env_logger 0.9.3",
"futures 0.3.28",
"globset",
"gpui2",
"language2",
"lazy_static",
"log",
"ndarray",
"node_runtime",
"ordered-float 2.10.0",
"parking_lot 0.11.2",
"postage",
"pretty_assertions",
"project2",
"rand 0.8.5",
"rpc2",
"rusqlite",
"rust-embed",
"schemars",
"serde",
"serde_json",
"settings2",
"sha1",
"smol",
"tempdir",
"tiktoken-rs",
"tree-sitter",
"tree-sitter-cpp",
"tree-sitter-elixir",
"tree-sitter-json 0.20.0",
"tree-sitter-lua",
"tree-sitter-php",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-toml",
"tree-sitter-typescript",
"unindent",
"util",
"workspace2",
]
[[package]]
name = "semver"
version = "1.0.18"
@@ -9332,6 +9636,7 @@ dependencies = [
"terminal2",
"theme2",
"thiserror",
"ui2",
"util",
"workspace2",
]
@@ -9438,6 +9743,7 @@ dependencies = [
"indexmap 1.9.3",
"json_comments",
"log",
"palette",
"rust-embed",
"serde",
"simplelog",
@@ -9466,6 +9772,27 @@ dependencies = [
"workspace",
]
[[package]]
name = "theme_selector2"
version = "0.1.0"
dependencies = [
"editor2",
"feature_flags2",
"fs2",
"fuzzy2",
"gpui2",
"log",
"parking_lot 0.11.2",
"picker2",
"postage",
"settings2",
"smol",
"theme2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "thiserror"
version = "1.0.48"
@@ -9925,7 +10252,7 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.20.10"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=35a6052fbcafc5e5fc0f9415b8652be7dcaf7222#35a6052fbcafc5e5fc0f9415b8652be7dcaf7222"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=b5f461a69bf3df7298b1903574d506179e6390b0#b5f461a69bf3df7298b1903574d506179e6390b0"
dependencies = [
"cc",
"regex",
@@ -10173,10 +10500,19 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-uiua"
version = "0.3.3"
source = "git+https://github.com/shnarazk/tree-sitter-uiua?rev=9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2#9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-vue"
version = "0.0.1"
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58#9b6cb221ccb8d0b956fcb17e9a1efac2feefeb58"
source = "git+https://github.com/zed-industries/tree-sitter-vue?rev=6608d9d60c386f19d80af7d8132322fa11199c42#6608d9d60c386f19d80af7d8132322fa11199c42"
dependencies = [
"cc",
"tree-sitter",
@@ -11040,6 +11376,31 @@ dependencies = [
"workspace",
]
[[package]]
name = "welcome2"
version = "0.1.0"
dependencies = [
"anyhow",
"client2",
"db2",
"editor2",
"fs2",
"fuzzy2",
"gpui2",
"install_cli2",
"log",
"picker2",
"project2",
"schemars",
"serde",
"settings2",
"theme2",
"theme_selector2",
"ui2",
"util",
"workspace2",
]
[[package]]
name = "which"
version = "4.4.2"
@@ -11493,7 +11854,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.114.0"
version = "0.117.0"
dependencies = [
"activity_indicator",
"ai",
@@ -11610,6 +11971,7 @@ dependencies = [
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue",
"tree-sitter-yaml",
"unindent",
@@ -11633,17 +11995,22 @@ dependencies = [
[[package]]
name = "zed2"
version = "0.109.0"
version = "2.0.0"
dependencies = [
"activity_indicator2",
"ai2",
"anyhow",
"assistant2",
"async-compression",
"async-recursion 0.3.2",
"async-tar",
"async-trait",
"audio2",
"auto_update2",
"backtrace",
"breadcrumbs2",
"call2",
"channel2",
"chrono",
"cli",
"client2",
@@ -11651,12 +12018,14 @@ dependencies = [
"collections",
"command_palette2",
"copilot2",
"copilot_button2",
"ctor",
"db2",
"diagnostics2",
"editor2",
"env_logger 0.9.3",
"feature_flags2",
"feedback2",
"file_finder2",
"fs2",
"fsevent",
@@ -11670,6 +12039,7 @@ dependencies = [
"isahc",
"journal2",
"language2",
"language_selector2",
"lazy_static",
"libc",
"log",
@@ -11677,11 +12047,14 @@ dependencies = [
"menu2",
"node_runtime",
"num_cpus",
"outline2",
"parking_lot 0.11.2",
"postage",
"project2",
"project_panel2",
"quick_action_bar2",
"rand 0.8.5",
"recent_projects2",
"regex",
"rope2",
"rpc2",
@@ -11689,6 +12062,7 @@ dependencies = [
"rust-embed",
"schemars",
"search2",
"semantic_index2",
"serde",
"serde_derive",
"serde_json",
@@ -11702,6 +12076,7 @@ dependencies = [
"terminal_view2",
"text2",
"theme2",
"theme_selector2",
"thiserror",
"tiny_http",
"toml 0.5.11",
@@ -11731,6 +12106,7 @@ dependencies = [
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue",
"tree-sitter-yaml",
"unindent",
@@ -11738,6 +12114,7 @@ dependencies = [
"urlencoding",
"util",
"uuid 1.4.1",
"welcome2",
"workspace2",
"zed_actions2",
]

View File

@@ -1,13 +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",
@@ -59,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",
@@ -73,6 +77,7 @@ members = [
"crates/notifications",
"crates/notifications2",
"crates/outline",
"crates/outline2",
"crates/picker",
"crates/picker2",
"crates/plugin",
@@ -85,12 +90,16 @@ members = [
"crates/project_panel",
"crates/project_panel2",
"crates/project_symbols",
"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",
@@ -107,14 +116,15 @@ members = [
"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/workspace2",
"crates/welcome",
"crates/welcome2",
"crates/xtask",
"crates/zed",
"crates/zed2",
@@ -127,6 +137,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).
@@ -194,9 +205,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
@@ -208,11 +221,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

@@ -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": {

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!(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

@@ -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()

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,126 @@
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!(
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

@@ -84,20 +84,17 @@ impl Settings for AutoUpdateSetting {
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {
wokrspace
.register_action(|_, action: &Check, cx| check(action, cx))
.register_action(|_, _action: &CheckThatAutoUpdaterWorks, cx| {
let prompt = cx.prompt(gpui::PromptLevel::Info, "It does!", &["Ok"]);
cx.spawn(|_, _cx| async move {
prompt.await.ok();
})
.detach();
});
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|_, action: &Check, cx| check(action, cx));
// @nate - code to trigger update notification on launch
// 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 {
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);
@@ -125,13 +122,19 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
}
}
pub fn check(_: &Check, cx: &mut AppContext) {
pub fn check(_: &Check, cx: &mut ViewContext<Workspace>) {
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"],
));
}
}
fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
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;

View File

@@ -1,87 +1,56 @@
use gpui::{div, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext};
use menu::Cancel;
use workspace::notifications::NotificationEvent;
use gpui::{
div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render,
SemanticVersion, StatefulInteractiveElement, Styled, ViewContext,
};
use util::channel::ReleaseChannel;
use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt};
pub struct UpdateNotification {
_version: SemanticVersion,
version: SemanticVersion,
}
impl EventEmitter<NotificationEvent> for UpdateNotification {}
impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
type Output = Div;
type Element = Div;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Output {
div().child("Updated zed!")
// let theme = theme::current(cx).clone();
// let theme = &theme.update_notification;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let app_name = cx.global::<ReleaseChannel>().display_name();
// let app_name = cx.global::<ReleaseChannel>().display_name();
// MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
// Flex::column()
// .with_child(
// Flex::row()
// .with_child(
// Text::new(
// format!("Updated to {app_name} {}", self.version),
// theme.message.text.clone(),
// )
// .contained()
// .with_style(theme.message.container)
// .aligned()
// .top()
// .left()
// .flex(1., true),
// )
// .with_child(
// MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
// let style = theme.dismiss_button.style_for(state);
// Svg::new("icons/x.svg")
// .with_color(style.color)
// .constrained()
// .with_width(style.icon_width)
// .aligned()
// .contained()
// .with_style(style.container)
// .constrained()
// .with_width(style.button_width)
// .with_height(style.button_width)
// })
// .with_padding(Padding::uniform(5.))
// .on_click(MouseButton::Left, move |_, this, cx| {
// this.dismiss(&Default::default(), cx)
// })
// .aligned()
// .constrained()
// .with_height(cx.font_cache().line_height(theme.message.text.font_size))
// .aligned()
// .top()
// .flex_float(),
// ),
// )
// .with_child({
// let style = theme.action_message.style_for(state);
// Text::new("View the release notes", style.text.clone())
// .contained()
// .with_style(style.container)
// })
// .contained()
// })
// .with_cursor_style(CursorStyle::PointingHand)
// .on_click(MouseButton::Left, |_, _, cx| {
// crate::view_release_notes(&Default::default(), cx)
// })
// .into_any_named("update notification")
v_stack()
.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(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: version }
Self { version }
}
pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
cx.emit(NotificationEvent::Dismiss);
pub fn dismiss(&mut self, 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,140 @@
use gpui::{
Div, Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
ViewContext, WeakView,
};
use itertools::Itertools;
use theme::ActiveTheme;
use ui::{prelude::*, ButtonLike, ButtonStyle, Label, Tooltip};
use workspace::{
item::{ItemEvent, ItemHandle},
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
pub enum Event {
UpdateLocation,
}
pub struct Breadcrumbs {
pane_focused: bool,
active_item: Option<Box<dyn ItemHandle>>,
subscription: Option<Subscription>,
workspace: WeakView<Workspace>,
}
impl Breadcrumbs {
pub fn new(workspace: &Workspace) -> Self {
Self {
pane_focused: false,
active_item: Default::default(),
subscription: Default::default(),
workspace: workspace.weak_handle(),
}
}
}
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()
});
element.child(
ButtonLike::new("toggle outline view")
.style(ButtonStyle::Subtle)
.child(h_stack().gap_1().children(breadcrumbs))
// We disable the button when the containing pane is not focused:
// Because right now all the breadcrumb does is open the outline view, which is an
// action which operates on the active editor, clicking the breadcrumbs of another
// editor could cause weirdness. I remember that at one point it actually caused a
// panic weirdly.
//
// It might be possible that with changes around how focus is managed that we
// might be able to update the active editor to the one with the breadcrumbs
// clicked on? That or we could just add a code path for being able to open the
// outline for a specific editor. Long term we'd like for it to be an actual
// breadcrumb bar so that problem goes away
//
// — Julia (https://github.com/zed-industries/zed/pull/3505#pullrequestreview-1766198050)
.disabled(!self.pane_focused)
.on_click(cx.listener(|breadcrumbs, _, cx| {
if let Some(workspace) = breadcrumbs.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
outline::toggle(workspace, &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

@@ -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

@@ -464,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);
@@ -477,7 +477,7 @@ 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);

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

@@ -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

@@ -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,

View File

@@ -109,6 +109,10 @@ pub enum ClickhouseEvent {
virtual_memory_in_bytes: u64,
milliseconds_since_first_event: i64,
},
App {
operation: &'static str,
milliseconds_since_first_event: i64,
},
}
#[cfg(debug_assertions)]
@@ -168,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
@@ -256,7 +255,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_copilot_event(
@@ -273,7 +272,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_assistant_event(
@@ -290,7 +289,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_call_event(
@@ -307,7 +306,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_cpu_event(
@@ -322,7 +321,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_memory_event(
@@ -337,7 +336,21 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
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 {
@@ -358,6 +371,7 @@ impl Telemetry {
self: &Arc<Self>,
event: ClickhouseEvent,
telemetry_settings: TelemetrySettings,
immediate_flush: bool,
) {
if !telemetry_settings.metrics {
return;
@@ -370,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 {

View File

@@ -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() {
@@ -1325,7 +1324,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,5 +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;
@@ -107,6 +108,10 @@ pub enum ClickhouseEvent {
virtual_memory_in_bytes: u64,
milliseconds_since_first_event: i64,
},
App {
operation: &'static str,
milliseconds_since_first_event: i64,
},
}
#[cfg(debug_assertions)]
@@ -122,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,
@@ -147,9 +153,30 @@ impl Telemetry {
}),
});
// 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())
}
@@ -163,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
@@ -257,7 +279,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_copilot_event(
@@ -274,7 +296,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_assistant_event(
@@ -291,7 +313,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_call_event(
@@ -308,7 +330,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_cpu_event(
@@ -323,7 +345,7 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
self.report_clickhouse_event(event, telemetry_settings, false)
}
pub fn report_memory_event(
@@ -338,7 +360,21 @@ impl Telemetry {
milliseconds_since_first_event: self.milliseconds_since_first_event(),
};
self.report_clickhouse_event(event, telemetry_settings)
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 {
@@ -359,6 +395,7 @@ impl Telemetry {
self: &Arc<Self>,
event: ClickhouseEvent,
telemetry_settings: TelemetrySettings,
immediate_flush: bool,
) {
if !telemetry_settings.metrics {
return;
@@ -371,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 {

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.30.1"
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<_>>(),
[]
)
});
}

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?;
}

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

@@ -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<_>>(),
[]
)
});
}

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?;
}

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();
@@ -602,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

@@ -41,7 +41,7 @@ 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 = { path = "../recent_projects" }
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"}

View File

@@ -1,454 +1,444 @@
// use anyhow::{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};
// use gpui::{
// actions,
// elements::{ChildView, Label},
// geometry::vector::Vector2F,
// AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Subscription, Task, View,
// ViewContext, ViewHandle,
// };
// use project::Project;
// use smallvec::SmallVec;
// use std::{
// any::{Any, TypeId},
// sync::Arc,
// };
// use util::ResultExt;
// use workspace::{
// item::{FollowableItem, Item, ItemEvent, ItemHandle},
// register_followable_item,
// searchable::SearchableItemHandle,
// ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
// };
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::Label;
use util::ResultExt;
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle},
register_followable_item,
searchable::SearchableItemHandle,
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
};
// actions!(channel_view, [Deploy]);
actions!(Deploy);
// pub fn init(cx: &mut AppContext) {
// register_followable_item::<ChannelView>(cx)
// }
pub fn init(cx: &mut AppContext) {
register_followable_item::<ChannelView>(cx)
}
// pub struct ChannelView {
// pub editor: ViewHandle<Editor>,
// project: ModelHandle<Project>,
// channel_store: ModelHandle<ChannelStore>,
// channel_buffer: ModelHandle<ChannelBuffer>,
// remote_id: Option<ViewId>,
// _editor_event_subscription: Subscription,
// }
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: ViewHandle<Workspace>,
// cx: &mut AppContext,
// ) -> Task<Result<ViewHandle<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)
// })
// }
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: ViewHandle<Pane>,
// workspace: ViewHandle<Workspace>,
// cx: &mut AppContext,
// ) -> Task<Result<ViewHandle<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));
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();
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);
// }
// })
// });
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);
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);
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;
// }
// }
// 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.add_view(|cx| {
// let mut this = Self::new(project, channel_store, channel_buffer, cx);
// this.acknowledge_buffer_version(cx);
// this
// });
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.id(), SaveIntent::Skip, cx)
// .detach();
// pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
// }
// }
// 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
// })
// .ok_or_else(|| anyhow!("pane was dropped"))
// })
// }
view
})
})
}
// pub fn new(
// project: ModelHandle<Project>,
// channel_store: ModelHandle<ChannelStore>,
// channel_buffer: ModelHandle<ChannelBuffer>,
// cx: &mut ViewContext<Self>,
// ) -> Self {
// let buffer = channel_buffer.read(cx).buffer();
// let editor = cx.add_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, cx| cx.emit(e.clone()));
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();
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
.detach();
// Self {
// editor,
// project,
// channel_store,
// channel_buffer,
// remote_id: None,
// _editor_event_subscription,
// }
// }
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)
// }
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
self.channel_buffer.read(cx).channel(cx)
}
// fn handle_channel_buffer_event(
// &mut self,
// _: ModelHandle<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::Event::TitleChanged);
// cx.notify()
// });
// }
// ChannelBufferEvent::BufferEdited => {
// if cx.is_self_focused() || self.editor.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 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);
// });
// }
// }
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 Entity for ChannelView {
// type Event = editor::Event;
// }
impl EventEmitter<EditorEvent> for ChannelView {}
// impl View for ChannelView {
// fn ui_name() -> &'static str {
// "ChannelView"
// }
impl Render for ChannelView {
type Element = AnyView;
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
// ChildView::new(self.editor.as_any(), cx).into_any()
// }
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
self.editor.clone().into()
}
}
// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
// if cx.is_self_focused() {
// self.acknowledge_buffer_version(cx);
// cx.focus(self.editor.as_any())
// }
// }
// }
impl FocusableView for ChannelView {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.read(cx).focus_handle(cx)
}
}
// impl Item for ChannelView {
// fn act_as_type<'a>(
// &'a self,
// type_id: TypeId,
// self_handle: &'a ViewHandle<Self>,
// _: &'a AppContext,
// ) -> Option<&'a AnyViewHandle> {
// if type_id == TypeId::of::<Self>() {
// Some(self_handle)
// } else if type_id == TypeId::of::<Editor>() {
// Some(&self.editor)
// } else {
// None
// }
// }
impl Item for ChannelView {
type Event = EditorEvent;
// fn tab_content<V: 'static>(
// &self,
// _: Option<usize>,
// style: &theme::Tab,
// cx: &gpui::AppContext,
// ) -> AnyElement<V> {
// 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, style.label.to_owned()).into_any()
// }
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 clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> {
// Some(Self::new(
// self.project.clone(),
// self.channel_store.clone(),
// self.channel_buffer.clone(),
// cx,
// ))
// }
fn tab_content(&self, _: Option<usize>, 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).into_any_element()
}
// fn is_singleton(&self, _cx: &AppContext) -> bool {
// false
// }
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 navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
// self.editor
// .update(cx, |editor, cx| editor.navigate(data, cx))
// }
fn is_singleton(&self, _cx: &AppContext) -> bool {
false
}
// fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
// self.editor
// .update(cx, |editor, cx| Item::deactivated(editor, cx))
// }
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, 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 deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| Item::deactivated(editor, cx))
}
// fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
// Some(Box::new(self.editor.clone()))
// }
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 show_toolbar(&self) -> bool {
// true
// }
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
// fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
// self.editor.read(cx).pixel_position_of_cursor(cx)
// }
fn show_toolbar(&self) -> bool {
true
}
// fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
// editor::Editor::to_item_events(event)
// }
// }
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
self.editor.read(cx).pixel_position_of_cursor(cx)
}
// impl FollowableItem for ChannelView {
// fn remote_id(&self) -> Option<workspace::ViewId> {
// self.remote_id
// }
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
Editor::to_item_events(event, f)
}
}
// fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
// let channel_buffer = self.channel_buffer.read(cx);
// if !channel_buffer.is_connected() {
// return None;
// }
impl FollowableItem for ChannelView {
fn remote_id(&self) -> Option<workspace::ViewId> {
self.remote_id
}
// 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 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;
}
// fn from_state_proto(
// pane: ViewHandle<workspace::Pane>,
// workspace: ViewHandle<workspace::Workspace>,
// remote_id: workspace::ViewId,
// state: &mut Option<proto::view::Variant>,
// cx: &mut AppContext,
// ) -> Option<gpui::Task<anyhow::Result<ViewHandle<Self>>>> {
// let Some(proto::view::Variant::ChannelView(_)) = state else {
// return None;
// };
// let Some(proto::view::Variant::ChannelView(state)) = state.take() else {
// unreachable!()
// };
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
},
},
))
}
// let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
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!()
};
// Some(cx.spawn(|mut cx| async move {
// let this = open.await?;
let open = ChannelView::open_in_pane(state.channel_id, pane, workspace, cx);
// let task = this
// .update(&mut cx, |this, cx| {
// this.remote_id = Some(remote_id);
Some(cx.spawn(|mut cx| async move {
let this = open.await?;
// 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
// }
// })
// .ok_or_else(|| anyhow!("window was closed"))?;
let task = this.update(&mut cx, |this, cx| {
this.remote_id = Some(remote_id);
// if let Some(task) = task {
// task.await?;
// }
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
}
})?;
// Ok(this)
// }))
// }
if let Some(task) = task {
task.await?;
}
// fn add_event_to_update_proto(
// &self,
// event: &Self::Event,
// update: &mut Option<proto::update_view::Variant>,
// cx: &AppContext,
// ) -> bool {
// self.editor
// .read(cx)
// .add_event_to_update_proto(event, update, cx)
// }
Ok(this)
}))
}
// fn apply_update_proto(
// &mut self,
// project: &ModelHandle<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 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 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 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 should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
// Editor::should_unfollow_on_event(event, 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: &AppContext) -> bool {
// false
// }
// }
fn is_project_item(&self, _cx: &WindowContext) -> bool {
false
}
// struct ChannelBufferCollaborationHub(ModelHandle<ChannelBuffer>);
fn to_follow_event(event: &Self::Event) -> Option<workspace::item::FollowEvent> {
Editor::to_follow_event(event)
}
}
// impl CollaborationHub for ChannelBufferCollaborationHub {
// fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
// self.0.read(cx).collaborators()
// }
struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);
// fn user_participant_indices<'a>(
// &self,
// cx: &'a AppContext,
// ) -> &'a HashMap<u64, ParticipantIndex> {
// self.0.read(cx).user_store().read(cx).participant_indices()
// }
// }
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()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,14 @@ use client::UserId;
use collections::HashMap;
use editor::{AnchorRangeExt, Editor};
use gpui::{
elements::ChildView, AnyElement, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
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);
@@ -19,8 +20,8 @@ lazy_static! {
}
pub struct MessageEditor {
pub editor: ViewHandle<Editor>,
channel_store: ModelHandle<ChannelStore>,
pub editor: View<Editor>,
channel_store: Model<ChannelStore>,
users: HashMap<String, UserId>,
mentions: Vec<UserId>,
mentions_task: Option<Task<()>>,
@@ -30,8 +31,8 @@ pub struct MessageEditor {
impl MessageEditor {
pub fn new(
language_registry: Arc<LanguageRegistry>,
channel_store: ModelHandle<ChannelStore>,
editor: ViewHandle<Editor>,
channel_store: Model<ChannelStore>,
editor: View<Editor>,
cx: &mut ViewContext<Self>,
) -> Self {
editor.update(cx, |editor, cx| {
@@ -48,15 +49,13 @@ impl MessageEditor {
cx.subscribe(&buffer, Self::on_buffer_event).detach();
let markdown = language_registry.language_for_name("Markdown");
cx.app_context()
.spawn(|mut cx| async move {
let markdown = markdown.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(Some(markdown), cx)
});
anyhow::Ok(())
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);
})
.detach_and_log_err(cx);
Self {
editor,
@@ -71,7 +70,7 @@ impl MessageEditor {
pub fn set_channel(
&mut self,
channel_id: u64,
channel_name: Option<String>,
channel_name: Option<SharedString>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
@@ -132,26 +131,28 @@ impl MessageEditor {
fn on_buffer_event(
&mut self,
buffer: ModelHandle<Buffer>,
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().timer(MENTIONS_DEBOUNCE_INTERVAL).await;
cx.background_executor()
.timer(MENTIONS_DEBOUNCE_INTERVAL)
.await;
Self::find_mentions(this, buffer, cx).await;
}));
}
}
async fn find_mentions(
this: WeakViewHandle<MessageEditor>,
this: WeakView<MessageEditor>,
buffer: BufferSnapshot,
mut cx: AsyncAppContext,
mut cx: AsyncWindowContext,
) {
let (buffer, ranges) = cx
.background()
.background_executor()
.spawn(async move {
let ranges = MENTIONS_SEARCH.search(&buffer, None).await;
(buffer, ranges)
@@ -180,11 +181,7 @@ impl MessageEditor {
}
editor.clear_highlights::<Self>(cx);
editor.highlight_text::<Self>(
anchor_ranges,
theme::current(cx).chat_panel.rich_text.mention_highlight,
cx,
)
editor.highlight_text::<Self>(anchor_ranges, gpui::red().into(), cx)
});
this.mentions = mentioned_user_ids;
@@ -192,21 +189,17 @@ impl MessageEditor {
})
.ok();
}
}
impl Entity for MessageEditor {
type Event = ();
}
impl View for MessageEditor {
fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
ChildView::new(&self.editor, cx).into_any()
pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
self.editor.read(cx).focus_handle(cx)
}
}
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
cx.focus(&self.editor);
}
impl Render for MessageEditor {
type Element = AnyView;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
self.editor.to_any()
}
}
@@ -214,7 +207,7 @@ impl View for MessageEditor {
mod tests {
use super::*;
use client::{Client, User, UserStore};
use gpui::{TestAppContext, WindowHandle};
use gpui::{Context as _, TestAppContext, VisualContext as _};
use language::{Language, LanguageConfig};
use rpc::proto;
use settings::SettingsStore;
@@ -222,8 +215,17 @@ mod tests {
#[gpui::test]
async fn test_message_editor(cx: &mut TestAppContext) {
let editor = init_test(cx);
let editor = editor.root(cx);
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(
@@ -255,7 +257,7 @@ mod tests {
});
});
cx.foreground().advance_clock(MENTIONS_DEBOUNCE_INTERVAL);
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);
@@ -269,15 +271,14 @@ mod tests {
});
}
fn init_test(cx: &mut TestAppContext) -> WindowHandle<MessageEditor> {
cx.foreground().forbid_parking();
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.add_model(|cx| UserStore::new(client.clone(), http, cx));
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, 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);
@@ -292,16 +293,6 @@ mod tests {
},
Some(tree_sitter_markdown::language()),
)));
let editor = cx.add_window(|cx| {
MessageEditor::new(
language_registry,
ChannelStore::global(cx),
cx.add_view(|cx| Editor::auto_height(4, None, cx)),
cx,
)
});
cx.foreground().run_until_parked();
editor
language_registry
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,58 +3,55 @@ use client::{
proto::{self, ChannelRole, ChannelVisibility},
User, UserId, UserStore,
};
use context_menu::{ContextMenu, ContextMenuItem};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions,
elements::*,
platform::{CursorStyle, MouseButton},
AppContext, ClipboardItem, Entity, ModelHandle, MouseState, Task, View, ViewContext,
ViewHandle,
actions, div, AppContext, ClipboardItem, DismissEvent, Div, Entity, EventEmitter,
FocusableView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext,
WeakView,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::prelude::*;
use util::TryFutureExt;
use workspace::Modal;
use workspace::ModalView;
actions!(
channel_modal,
[
SelectNextControl,
ToggleMode,
ToggleMemberAdmin,
RemoveMember
]
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 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: ViewHandle<Picker<ChannelModalDelegate>>,
channel_store: ModelHandle<ChannelStore>,
picker: View<Picker<ChannelModalDelegate>>,
channel_store: Model<ChannelStore>,
channel_id: ChannelId,
has_focus: bool,
}
impl ChannelModal {
pub fn new(
user_store: ModelHandle<UserStore>,
channel_store: ModelHandle<ChannelStore>,
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 picker = cx.add_view(|cx| {
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,
@@ -64,20 +61,17 @@ impl ChannelModal {
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
}),
// context_menu: cx.add_view(|cx| {
// let mut menu = ContextMenu::new(cx.view_id(), cx);
// menu.set_position_mode(OverlayPositionMode::Local);
// menu
// }),
},
cx,
)
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
let has_focus = picker.read(cx).has_focus();
let has_focus = picker.focus_handle(cx).contains_focused(cx);
Self {
picker,
@@ -88,7 +82,7 @@ impl ChannelModal {
}
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
let mode = match self.picker.read(cx).delegate().mode {
let mode = match self.picker.read(cx).delegate.mode {
Mode::ManageMembers => Mode::InviteMembers,
Mode::InviteMembers => Mode::ManageMembers,
};
@@ -103,20 +97,20 @@ impl ChannelModal {
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_mut().members = members);
.update(cx, |picker, _| picker.delegate.members = members);
})?;
}
this.update(&mut cx, |this, cx| {
this.picker.update(cx, |picker, cx| {
let delegate = picker.delegate_mut();
let delegate = &mut picker.delegate;
delegate.mode = mode;
delegate.selected_index = 0;
picker.set_query("", cx);
@@ -131,203 +125,195 @@ impl ChannelModal {
fn toggle_member_admin(&mut self, _: &ToggleMemberAdmin, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.delegate_mut().toggle_selected_member_admin(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_mut().remove_selected_member(cx);
picker.delegate.remove_selected_member(cx);
});
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(PickerEvent::Dismiss);
cx.emit(DismissEvent);
}
}
impl Entity for ChannelModal {
type Event = PickerEvent;
}
impl EventEmitter<DismissEvent> for ChannelModal {}
impl ModalView for ChannelModal {}
impl View for ChannelModal {
fn ui_name() -> &'static str {
"ChannelModal"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
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;
impl FocusableView for ChannelModal {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Modal for ChannelModal {
fn has_focus(&self) -> bool {
self.has_focus
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 dismiss_on_event(event: &Self::Event) -> bool {
match event {
PickerEvent::Dismiss => true,
}
}
// 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)]
@@ -337,19 +323,22 @@ pub enum Mode {
}
pub struct ChannelModalDelegate {
channel_modal: WeakView<ChannelModal>,
matching_users: Vec<Arc<User>>,
matching_member_indices: Vec<usize>,
user_store: ModelHandle<UserStore>,
channel_store: ModelHandle<ChannelStore>,
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>,
// context_menu: ViewHandle<ContextMenu>,
}
impl PickerDelegate for ChannelModalDelegate {
type ListItem = Div;
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
@@ -382,19 +371,19 @@ impl PickerDelegate for ChannelModalDelegate {
}
}));
let matches = cx.background().block(match_strings(
let matches = cx.background_executor().block(match_strings(
&self.match_candidates,
&query,
true,
usize::MAX,
&Default::default(),
cx.background().clone(),
cx.background_executor().clone(),
));
cx.spawn(|picker, mut cx| async move {
picker
.update(&mut cx, |picker, cx| {
let delegate = picker.delegate_mut();
let delegate = &mut picker.delegate;
delegate.matching_member_indices.clear();
delegate
.matching_member_indices
@@ -412,8 +401,7 @@ impl PickerDelegate for ChannelModalDelegate {
async {
let users = search_users.await?;
picker.update(&mut cx, |picker, cx| {
let delegate = picker.delegate_mut();
delegate.matching_users = users;
picker.delegate.matching_users = users;
cx.notify();
})?;
anyhow::Ok(())
@@ -445,138 +433,142 @@ impl PickerDelegate for ChannelModalDelegate {
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
cx.emit(PickerEvent::Dismiss);
self.channel_modal
.update(cx, |_, cx| {
cx.emit(DismissEvent);
})
.ok();
}
fn render_match(
&self,
ix: usize,
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
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);
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 style = tabbed_modal
// .picker
// .item
// .in_state(selected)
// .style_for(mouse_state);
let in_manage = matches!(self.mode, Mode::ManageMembers);
// 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,
},
};
// 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();
// 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();
}
// if selected {
// result = Stack::new()
// .with_child(result)
// .with_child(
// ChildView::new(&self.context_menu, cx)
// .aligned()
// .top()
// .right(),
// )
// .into_any();
// }
result
// result
}
}
@@ -623,7 +615,7 @@ impl ChannelModalDelegate {
cx.spawn(|picker, mut cx| async move {
update.await?;
picker.update(&mut cx, |picker, cx| {
let this = picker.delegate_mut();
let this = &mut picker.delegate;
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
member.role = new_role;
}
@@ -644,7 +636,7 @@ impl ChannelModalDelegate {
cx.spawn(|picker, mut cx| async move {
update.await?;
picker.update(&mut cx, |picker, cx| {
let this = picker.delegate_mut();
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| {
@@ -683,7 +675,7 @@ impl ChannelModalDelegate {
kind: proto::channel_member::Kind::Invitee,
role: ChannelRole::Member,
};
let members = &mut this.delegate_mut().members;
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),
}
@@ -695,23 +687,23 @@ impl ChannelModalDelegate {
}
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,
)
})
// 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

@@ -1,37 +1,35 @@
use client::{ContactRequestStatus, User, UserStore};
use gpui::{
elements::*, AppContext, Entity, ModelHandle, MouseState, Task, View, ViewContext, ViewHandle,
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, PickerEvent};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use util::TryFutureExt;
use workspace::Modal;
use theme::ActiveTheme as _;
use ui::prelude::*;
use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView;
pub fn init(cx: &mut AppContext) {
Picker::<ContactFinderDelegate>::init(cx);
cx.add_action(ContactFinder::dismiss)
//Picker::<ContactFinderDelegate>::init(cx);
//cx.add_action(ContactFinder::dismiss)
}
pub struct ContactFinder {
picker: ViewHandle<Picker<ContactFinderDelegate>>,
picker: View<Picker<ContactFinderDelegate>>,
has_focus: bool,
}
impl ContactFinder {
pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.add_view(|cx| {
Picker::new(
ContactFinderDelegate {
user_store,
potential_contacts: Arc::from([]),
selected_index: 0,
},
cx,
)
.with_theme(|theme| theme.collab_panel.tabbed_modal.picker.clone())
});
cx.subscribe(&picker, |_, _, e, cx| cx.emit(*e)).detach();
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,
@@ -41,105 +39,73 @@ impl ContactFinder {
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
self.picker.update(cx, |picker, cx| {
picker.set_query(query, cx);
// todo!()
// picker.set_query(query, cx);
});
}
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(PickerEvent::Dismiss);
}
}
impl Entity for ContactFinder {
type Event = PickerEvent;
}
impl View for ContactFinder {
fn ui_name() -> &'static str {
"ContactFinder"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.tabbed_modal;
fn render_mode_button(
text: &'static str,
theme: &theme::TabbedModal,
_cx: &mut ViewContext<ContactFinder>,
) -> AnyElement<ContactFinder> {
let contained_text = &theme.tab_button.active_state().default;
Label::new(text, contained_text.text.clone())
.contained()
.with_style(contained_text.container.clone())
.into_any()
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()
}
Flex::column()
.with_child(
Flex::column()
.with_child(
Label::new("Contacts", theme.title.text.clone())
.contained()
.with_style(theme.title.container.clone()),
)
.with_child(Flex::row().with_children([render_mode_button(
"Invite new contacts",
&theme,
cx,
)]))
.expanded()
.contained()
.with_style(theme.header),
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),
)
.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()
.child(self.picker.clone())
.w(rems(34.))
}
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_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;
}
// fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
// self.has_focus = false;
// }
type Element = Div;
}
impl Modal for ContactFinder {
fn has_focus(&self) -> bool {
self.has_focus
}
// impl Modal for ContactFinder {
// fn has_focus(&self) -> bool {
// self.has_focus
// }
fn dismiss_on_event(event: &Self::Event) -> bool {
match event {
PickerEvent::Dismiss => true,
}
}
}
// fn dismiss_on_event(event: &Self::Event) -> bool {
// match event {
// PickerEvent::Dismiss => true,
// }
// }
// }
pub struct ContactFinderDelegate {
parent: WeakView<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
user_store: ModelHandle<UserStore>,
user_store: Model<UserStore>,
selected_index: usize,
}
impl PickerDelegate for ContactFinderDelegate {
fn placeholder_text(&self) -> Arc<str> {
"Search collaborator by username...".into()
}
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()
}
@@ -152,6 +118,10 @@ impl PickerDelegate for ContactFinderDelegate {
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
@@ -161,7 +131,7 @@ impl PickerDelegate for ContactFinderDelegate {
async {
let potential_contacts = search_users.await?;
picker.update(&mut cx, |picker, cx| {
picker.delegate_mut().potential_contacts = potential_contacts.into();
picker.delegate.potential_contacts = potential_contacts.into();
cx.notify();
})?;
anyhow::Ok(())
@@ -191,19 +161,18 @@ impl PickerDelegate for ContactFinderDelegate {
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
cx.emit(PickerEvent::Dismiss);
//cx.emit(PickerEvent::Dismiss);
self.parent
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
mouse_state: &mut MouseState,
selected: bool,
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
let full_theme = &theme::current(cx);
let theme = &full_theme.collab_panel.contact_finder;
let tabbed_modal = &full_theme.collab_panel.tabbed_modal;
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);
@@ -214,48 +183,46 @@ impl PickerDelegate for ContactFinderDelegate {
ContactRequestStatus::RequestSent => Some("icons/x.svg"),
ContactRequestStatus::RequestAccepted => None,
};
let button_style = if self.user_store.read(cx).is_contact_request_pending(user) {
&theme.disabled_contact_button
} else {
&theme.contact_button
};
let style = tabbed_modal
.picker
.item
.in_state(selected)
.style_for(mouse_state);
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()
Some(
div()
.flex_1()
.justify_between()
.children(user.avatar.clone().map(|avatar| img(avatar)))
.child(Label::new(user.github_login.clone()))
.children(icon_path.map(|icon_path| svg().path(icon_path))),
)
// 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()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,23 +7,26 @@ pub mod notification_panel;
pub mod notifications;
mod panel_settings;
use std::sync::Arc;
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 gpui::AppContext;
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]
// );
actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx);
ChatPanelSettings::register(cx);
NotificationPanelSettings::register(cx);
@@ -31,95 +34,101 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
// vcs_menu::init(cx);
collab_titlebar_item::init(cx);
collab_panel::init(cx);
// chat_panel::init(cx);
// notifications::init(&app_state, cx);
channel_view::init(cx);
chat_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_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);
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();
// }
// }
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();
// }
// }
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 Screen>,
// window_size: Vector2F,
// ) -> WindowOptions<'static> {
// const NOTIFICATION_PADDING: f32 = 16.;
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.content_bounds();
// WindowOptions {
// bounds: WindowBounds::Fixed(RectF::new(
// screen_bounds.upper_right()
// + vec2f(
// -NOTIFICATION_PADDING - window_size.x(),
// NOTIFICATION_PADDING,
// ),
// window_size,
// )),
// titlebar: None,
// center: false,
// focus: false,
// show: true,
// kind: WindowKind::PopUp,
// is_movable: false,
// screen: Some(screen),
// }
// }
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>>,
@@ -149,6 +158,6 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
// .into_any()
// }
// fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
// cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
// }
fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
}

View File

@@ -1,113 +1,32 @@
// use std::ops::Range;
use gpui::{
div, AnyElement, Div, ElementId, IntoElement, ParentElement as _, RenderOnce, Styled,
WindowContext,
};
// use gpui::{
// geometry::{
// rect::RectF,
// vector::{vec2f, Vector2F},
// },
// json::ToJson,
// serde_json::{self, json},
// AnyElement, Axis, Element, View, ViewContext,
// };
#[derive(Default, IntoElement)]
pub struct FacePile {
pub faces: Vec<AnyElement>,
}
// pub(crate) struct FacePile<V: View> {
// overlap: f32,
// faces: Vec<AnyElement<V>>,
// }
impl RenderOnce for FacePile {
type Rendered = Div;
// impl<V: View> FacePile<V> {
// pub fn new(overlap: f32) -> Self {
// Self {
// overlap,
// faces: Vec::new(),
// }
// }
// }
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;
// impl<V: View> Element<V> for FacePile<V> {
// type LayoutState = ();
// type PaintState = ();
div()
.z_index((player_count - ix) as u32)
.when(isnt_last, |div| div.neg_mr_1())
.child(player)
});
div().p_1().flex().items_center().children(player_list)
}
}
// fn layout(
// &mut self,
// constraint: gpui::SizeConstraint,
// view: &mut V,
// cx: &mut ViewContext<V>,
// ) -> (Vector2F, Self::LayoutState) {
// debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
// let mut width = 0.;
// let mut max_height = 0.;
// for face in &mut self.faces {
// let layout = face.layout(constraint, view, cx);
// width += layout.x();
// max_height = f32::max(max_height, layout.y());
// }
// width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
// (
// Vector2F::new(width, max_height.clamp(1., constraint.max.y())),
// (),
// )
// }
// fn paint(
// &mut self,
// bounds: RectF,
// visible_bounds: RectF,
// _layout: &mut Self::LayoutState,
// view: &mut V,
// cx: &mut ViewContext<V>,
// ) -> Self::PaintState {
// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
// let origin_y = bounds.upper_right().y();
// let mut origin_x = bounds.upper_right().x();
// for face in self.faces.iter_mut().rev() {
// let size = face.size();
// origin_x -= size.x();
// let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
// cx.scene().push_layer(None);
// face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
// cx.scene().pop_layer();
// origin_x += self.overlap;
// }
// ()
// }
// fn rect_for_text_range(
// &self,
// _: Range<usize>,
// _: RectF,
// _: RectF,
// _: &Self::LayoutState,
// _: &Self::PaintState,
// _: &V,
// _: &ViewContext<V>,
// ) -> Option<RectF> {
// None
// }
// fn debug(
// &self,
// bounds: RectF,
// _: &Self::LayoutState,
// _: &Self::PaintState,
// _: &V,
// _: &ViewContext<V>,
// ) -> serde_json::Value {
// json!({
// "type": "FacePile",
// "bounds": bounds.to_json()
// })
// }
// }
// impl<V: View> Extend<AnyElement<V>> for FacePile<V> {
// fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
// self.faces.extend(children);
// }
// }
impl Extend<AnyElement> for FacePile {
fn extend<T: IntoIterator<Item = AnyElement>>(&mut self, children: T) {
self.faces.extend(children);
}
}

View File

@@ -1,11 +1,11 @@
// use gpui::AppContext;
// use std::sync::Arc;
// use workspace::AppState;
use gpui::AppContext;
use std::sync::Arc;
use workspace::AppState;
// pub mod incoming_call_notification;
// pub mod project_shared_notification;
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);
// }
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

@@ -1,14 +1,13 @@
use crate::notification_window_options;
use call::{ActiveCall, IncomingCall};
use client::proto;
use futures::StreamExt;
use gpui::{
elements::*,
geometry::vector::vec2f,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
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;
@@ -19,21 +18,33 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
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.remove(&mut cx);
window
.update(&mut cx, |_, cx| {
// todo!()
cx.remove_window();
})
.log_err();
}
if let Some(incoming_call) = incoming_call {
let window_size = cx.read(|cx| {
let theme = &theme::current(cx).incoming_call_notification;
vec2f(theme.window_width, theme.window_height)
});
let unique_screens = cx.update(|cx| cx.displays()).unwrap();
let window_size = gpui::Size {
width: px(380.),
height: px(64.),
};
for screen in cx.platform().screens() {
for screen in unique_screens {
let options = notification_window_options(screen, window_size);
let window = cx
.add_window(notification_window_options(screen, window_size), |_| {
IncomingCallNotification::new(incoming_call.clone(), app_state.clone())
});
.open_window(options, |cx| {
cx.build_view(|_| {
IncomingCallNotification::new(
incoming_call.clone(),
app_state.clone(),
)
})
})
.unwrap();
notification_windows.push(window);
}
}
@@ -47,167 +58,97 @@ struct RespondToCall {
accept: bool,
}
pub struct IncomingCallNotification {
struct IncomingCallNotificationState {
call: IncomingCall,
app_state: Weak<AppState>,
}
impl IncomingCallNotification {
pub struct IncomingCallNotification {
state: Arc<IncomingCallNotificationState>,
}
impl IncomingCallNotificationState {
pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
Self { call, app_state }
}
fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
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();
cx.app_context()
.spawn(|mut 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);
}
});
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
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();
});
}
}
}
fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &theme::current(cx).incoming_call_notification;
let default_project = proto::ParticipantProject::default();
let initial_project = self
.call
.initial_project
.as_ref()
.unwrap_or(&default_project);
Flex::row()
.with_children(self.call.calling_user.avatar.clone().map(|avatar| {
Image::from_data(avatar)
.with_style(theme.caller_avatar)
.aligned()
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()
.children(
self.state
.call
.calling_user
.avatar
.as_ref()
.map(|avatar| Avatar::data(avatar.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)
}))
.with_child(
Flex::column()
.with_child(
Label::new(
self.call.calling_user.github_login.clone(),
theme.caller_username.text.clone(),
)
.contained()
.with_style(theme.caller_username.container),
)
.with_child(
Label::new(
format!(
"is sharing a project in Zed{}",
if initial_project.worktree_root_names.is_empty() {
""
} else {
":"
}
),
theme.caller_message.text.clone(),
)
.contained()
.with_style(theme.caller_message.container),
)
.with_children(if initial_project.worktree_root_names.is_empty() {
None
} else {
Some(
Label::new(
initial_project.worktree_root_names.join(", "),
theme.worktree_roots.text.clone(),
)
.contained()
.with_style(theme.worktree_roots.container),
)
})
.contained()
.with_style(theme.caller_metadata)
.aligned(),
)
.contained()
.with_style(theme.caller_container)
.flex(1., true)
.into_any()
}
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum Accept {}
enum Decline {}
let theme = theme::current(cx);
Flex::column()
.with_child(
MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
let theme = &theme.incoming_call_notification;
Label::new("Accept", theme.accept_button.text.clone())
.aligned()
.contained()
.with_style(theme.accept_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, this, cx| {
this.respond(true, cx);
})
.flex(1., true),
)
.with_child(
MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
let theme = &theme.incoming_call_notification;
Label::new("Decline", theme.decline_button.text.clone())
.aligned()
.contained()
.with_style(theme.decline_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, this, cx| {
this.respond(false, cx);
})
.flex(1., true),
)
.constrained()
.with_width(theme.incoming_call_notification.button_width)
.into_any()
}
}
impl Entity for IncomingCallNotification {
type Event = ();
}
impl Render for IncomingCallNotification {
type Element = Div;
impl View for IncomingCallNotification {
fn ui_name() -> &'static str {
"IncomingCallNotification"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let background = theme::current(cx).incoming_call_notification.background;
Flex::row()
.with_child(self.render_caller(cx))
.with_child(self.render_buttons(cx))
.contained()
.with_background_color(background)
.expanded()
.into_any()
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div().bg(red()).flex_none().child(self.render_caller(cx))
}
}

View File

@@ -3,12 +3,11 @@ use call::{room, ActiveCall};
use client::User;
use collections::HashMap;
use gpui::{
elements::*,
geometry::vector::vec2f,
platform::{CursorStyle, MouseButton},
AppContext, Entity, View, ViewContext,
px, AppContext, Div, Element, ParentElement, Render, RenderOnce, Size, Styled, ViewContext,
VisualContext,
};
use std::sync::{Arc, Weak};
use ui::{h_stack, v_stack, Avatar, Button, Clickable, Label};
use workspace::AppState;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -21,38 +20,54 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
project_id,
worktree_root_names,
} => {
let theme = &theme::current(cx).project_shared_notification;
let window_size = vec2f(theme.window_width, theme.window_height);
let window_size = Size {
width: px(380.),
height: px(64.),
};
for screen in cx.platform().screens() {
let window =
cx.add_window(notification_window_options(screen, window_size), |_| {
for screen in cx.displays() {
let options = notification_window_options(screen, window_size);
let window = cx.open_window(options, |cx| {
cx.build_view(|_| {
ProjectSharedNotification::new(
owner.clone(),
*project_id,
worktree_root_names.clone(),
app_state.clone(),
)
});
})
});
notification_windows
.entry(*project_id)
.or_insert(Vec::new())
.push(window);
}
}
room::Event::RemoteProjectUnshared { project_id }
| room::Event::RemoteProjectJoined { project_id }
| room::Event::RemoteProjectInvitationDiscarded { project_id } => {
if let Some(windows) = notification_windows.remove(&project_id) {
for window in windows {
window.remove(cx);
window
.update(cx, |_, cx| {
// todo!()
cx.remove_window();
})
.ok();
}
}
}
room::Event::Left => {
for (_, windows) in notification_windows.drain() {
for window in windows {
window.remove(cx);
window
.update(cx, |_, cx| {
// todo!()
cx.remove_window();
})
.ok();
}
}
}
@@ -102,116 +117,60 @@ impl ProjectSharedNotification {
}
}
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &theme::current(cx).project_shared_notification;
Flex::row()
.with_children(self.owner.avatar.clone().map(|avatar| {
Image::from_data(avatar)
.with_style(theme.owner_avatar)
.aligned()
}))
.with_child(
Flex::column()
.with_child(
Label::new(
self.owner.github_login.clone(),
theme.owner_username.text.clone(),
)
.contained()
.with_style(theme.owner_username.container),
)
.with_child(
Label::new(
format!(
"is sharing a project in Zed{}",
if self.worktree_root_names.is_empty() {
""
} else {
":"
}
),
theme.message.text.clone(),
)
.contained()
.with_style(theme.message.container),
)
.with_children(if self.worktree_root_names.is_empty() {
fn render_owner(&self) -> impl Element {
h_stack()
.children(
self.owner
.avatar
.clone()
.map(|avatar| Avatar::data(avatar.clone())),
)
.child(
v_stack()
.child(Label::new(self.owner.github_login.clone()))
.child(Label::new(format!(
"is sharing a project in Zed{}",
if self.worktree_root_names.is_empty() {
""
} else {
":"
}
)))
.children(if self.worktree_root_names.is_empty() {
None
} else {
Some(
Label::new(
self.worktree_root_names.join(", "),
theme.worktree_roots.text.clone(),
)
.contained()
.with_style(theme.worktree_roots.container),
)
})
.contained()
.with_style(theme.owner_metadata)
.aligned(),
Some(Label::new(self.worktree_root_names.join(", ")))
}),
)
.contained()
.with_style(theme.owner_container)
.flex(1., true)
.into_any()
}
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum Open {}
enum Dismiss {}
let theme = theme::current(cx);
Flex::column()
.with_child(
MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
let theme = &theme.project_shared_notification;
Label::new("Open", theme.open_button.text.clone())
.aligned()
.contained()
.with_style(theme.open_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
.flex(1., true),
fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
let this = cx.view().clone();
v_stack()
.child(Button::new("open", "Open").render(cx).on_click({
let this = this.clone();
move |_, cx| {
this.update(cx, |this, cx| this.join(cx));
}
}))
.child(
Button::new("dismiss", "Dismiss")
.render(cx)
.on_click(move |_, cx| {
this.update(cx, |this, cx| this.dismiss(cx));
}),
)
.with_child(
MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
let theme = &theme.project_shared_notification;
Label::new("Dismiss", theme.dismiss_button.text.clone())
.aligned()
.contained()
.with_style(theme.dismiss_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, this, cx| {
this.dismiss(cx);
})
.flex(1., true),
)
.constrained()
.with_width(theme.project_shared_notification.button_width)
.into_any()
}
}
impl Entity for ProjectSharedNotification {
type Event = ();
}
impl Render for ProjectSharedNotification {
type Element = Div;
impl View for ProjectSharedNotification {
fn ui_name() -> &'static str {
"ProjectSharedNotification"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
let background = theme::current(cx).project_shared_notification.background;
Flex::row()
.with_child(self.render_owner(cx))
.with_child(self.render_buttons(cx))
.contained()
.with_background_color(background)
.expanded()
.into_any()
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
h_stack()
.size_full()
.bg(gpui::red())
.child(self.render_owner())
.child(self.render_buttons(cx))
}
}

View File

@@ -23,11 +23,13 @@ pub type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "test-support"))]
pub type HashSet<T> = std::collections::HashSet<T>;
use std::any::TypeId;
pub use std::collections::*;
// NEW TYPES
#[derive(Default)]
pub struct CommandPaletteFilter {
pub filtered_namespaces: HashSet<&'static str>,
pub hidden_namespaces: HashSet<&'static str>,
pub hidden_action_types: HashSet<TypeId>,
}

View File

@@ -109,7 +109,7 @@ impl PickerDelegate for CommandPaletteDelegate {
let filtered = cx.read(|cx| {
if cx.has_global::<CommandPaletteFilter>() {
let filter = cx.global::<CommandPaletteFilter>();
filter.filtered_namespaces.contains(action.namespace())
filter.hidden_namespaces.contains(action.namespace())
} else {
false
}
@@ -430,7 +430,7 @@ mod tests {
// Add namespace filter, and redeploy the palette
cx.update(|cx| {
cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
filter.filtered_namespaces.insert("editor");
filter.hidden_namespaces.insert("editor");
})
});

View File

@@ -1,21 +1,22 @@
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, div, prelude::*, Action, AppContext, Div, EventEmitter, FocusHandle, FocusableView,
Keystroke, Manager, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use std::{
cmp::{self, Reverse},
sync::Arc,
};
use theme::ActiveTheme;
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
};
use workspace::Workspace;
use workspace::{ModalView, Workspace};
use zed_actions::OpenZedURL;
actions!(Toggle);
@@ -25,6 +26,8 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(CommandPalette::register).detach();
}
impl ModalView for CommandPalette {}
pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>,
}
@@ -48,14 +51,16 @@ impl CommandPalette {
.filter_map(|action| {
let name = gpui::remove_the_2(action.name());
let namespace = name.split("::").next().unwrap_or("malformed action name");
if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) {
if filter.is_some_and(|f| {
f.hidden_namespaces.contains(namespace)
|| f.hidden_action_types.contains(&action.type_id())
}) {
return None;
}
Some(Command {
name: humanize_action_name(&name),
action,
keystrokes: vec![], // todo!()
})
})
.collect();
@@ -68,7 +73,7 @@ impl CommandPalette {
}
}
impl EventEmitter<Manager> for CommandPalette {}
impl EventEmitter<DismissEvent> for CommandPalette {}
impl FocusableView for CommandPalette {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
@@ -77,10 +82,10 @@ impl FocusableView for CommandPalette {
}
impl Render for CommandPalette {
type Output = Div;
type Element = Div;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Output {
v_stack().w_96().child(self.picker.clone())
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
v_stack().w(rems(34.)).child(self.picker.clone())
}
}
@@ -104,7 +109,6 @@ pub struct CommandPaletteDelegate {
struct Command {
name: String,
action: Box<dyn Action>,
keystrokes: Vec<Keystroke>,
}
impl Clone for Command {
@@ -112,7 +116,6 @@ impl Clone for Command {
Self {
name: self.name.clone(),
action: self.action.boxed_clone(),
keystrokes: self.keystrokes.clone(),
}
}
}
@@ -140,7 +143,7 @@ impl CommandPaletteDelegate {
}
impl PickerDelegate for CommandPaletteDelegate {
type ListItem = Div;
type ListItem = ListItem;
fn placeholder_text(&self) -> Arc<str> {
"Execute a command...".into()
@@ -223,6 +226,7 @@ impl PickerDelegate for CommandPaletteDelegate {
})
}
}
if let Some(CommandInterceptResult {
action,
string,
@@ -238,7 +242,6 @@ impl PickerDelegate for CommandPaletteDelegate {
commands.push(Command {
name: string.clone(),
action,
keystrokes: vec![],
});
matches.insert(
0,
@@ -250,6 +253,7 @@ impl PickerDelegate for CommandPaletteDelegate {
},
)
}
picker
.update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate;
@@ -268,7 +272,7 @@ impl PickerDelegate for CommandPaletteDelegate {
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.command_palette
.update(cx, |_, cx| cx.emit(Manager::Dismiss))
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
@@ -279,6 +283,8 @@ impl PickerDelegate for CommandPaletteDelegate {
}
let action_ix = self.matches[self.selected_ix].candidate_id;
let command = self.commands.swap_remove(action_ix);
self.matches.clear();
self.commands.clear();
cx.update_global(|hit_counts: &mut HitCounts, _| {
*hit_counts.0.entry(command.name).or_default() += 1;
});
@@ -293,32 +299,25 @@ impl PickerDelegate for CommandPaletteDelegate {
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
let Some(r#match) = self.matches.get(ix) else {
return div();
};
let Some(command) = self.commands.get(r#match.candidate_id) else {
return div();
};
div()
.px_1()
.text_color(colors.text)
.text_ui()
.bg(colors.ghost_element_background)
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
.child(
) -> Option<Self::ListItem> {
let r#match = self.matches.get(ix)?;
let command = self.commands.get(r#match.candidate_id)?;
Some(
ListItem::new(ix).inset(true).selected(selected).child(
h_stack()
.w_full()
.justify_between()
.child(HighlightedLabel::new(
command.name.clone(),
r#match.positions.clone(),
))
.children(KeyBinding::for_action(&*command.action, cx)),
)
.children(KeyBinding::for_action_in(
&*command.action,
&self.previous_focus_handle,
cx,
)),
),
)
}
}
@@ -350,8 +349,7 @@ impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Command")
.field("name", &self.name)
.field("keystrokes", &self.keystrokes)
.finish()
.finish_non_exhaustive()
}
}
@@ -434,7 +432,7 @@ mod tests {
cx.update(|cx| {
cx.set_global(CommandPaletteFilter::default());
cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
filter.filtered_namespaces.insert("editor");
filter.hidden_namespaces.insert("editor");
})
});

View File

@@ -28,7 +28,7 @@ theme = { path = "../theme" }
lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime"}
util = { path = "../util" }
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
async-compression.workspace = true
async-tar = "0.4.2"
anyhow.workspace = true
log.workspace = true

View File

@@ -58,16 +58,16 @@ pub fn init(
cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
match status {
Status::Disabled => {
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
filter.hidden_namespaces.insert(COPILOT_AUTH_NAMESPACE);
}
Status::Authorized => {
filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
filter.hidden_namespaces.remove(COPILOT_NAMESPACE);
filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
}
_ => {
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
filter.hidden_namespaces.insert(COPILOT_NAMESPACE);
filter.hidden_namespaces.remove(COPILOT_AUTH_NAMESPACE);
}
}
});

View File

@@ -28,7 +28,8 @@ theme = { package = "theme2", path = "../theme2" }
lsp = { package = "lsp2", path = "../lsp2" }
node_runtime = { path = "../node_runtime"}
util = { path = "../util" }
async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
ui = { package = "ui2", path = "../ui2" }
async-compression.workspace = true
async-tar = "0.4.2"
anyhow.workspace = true
log.workspace = true
@@ -45,6 +46,6 @@ fs = { path = "../fs", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
rpc = { path = "../rpc", 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"] }

View File

@@ -22,6 +22,7 @@ use request::StatusNotification;
use settings::SettingsStore;
use smol::{fs, io::BufReader, stream::StreamExt};
use std::{
any::TypeId,
ffi::OsString,
mem,
ops::Range,
@@ -32,13 +33,14 @@ use util::{
fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
};
// todo!()
// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
actions!(SignIn, SignOut);
// todo!()
// const COPILOT_NAMESPACE: &'static str = "copilot";
actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall);
actions!(
Suggest,
NextSuggestion,
PreviousSuggestion,
Reinstall,
SignIn,
SignOut
);
pub fn init(
new_server_id: LanguageServerId,
@@ -51,52 +53,70 @@ pub fn init(
move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
});
cx.set_global(copilot.clone());
cx.observe(&copilot, |handle, cx| {
let copilot_action_types = [
TypeId::of::<Suggest>(),
TypeId::of::<NextSuggestion>(),
TypeId::of::<PreviousSuggestion>(),
TypeId::of::<Reinstall>(),
];
let copilot_auth_action_types = [TypeId::of::<SignOut>()];
let copilot_no_auth_action_types = [TypeId::of::<SignIn>()];
let status = handle.read(cx).status();
let filter = cx.default_global::<collections::CommandPaletteFilter>();
// TODO
// cx.observe(&copilot, |handle, cx| {
// let status = handle.read(cx).status();
// cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
// match status {
// Status::Disabled => {
// filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
// filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
// }
// Status::Authorized => {
// filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
// filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
// }
// _ => {
// filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
// filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
// }
// }
// });
// })
// .detach();
match status {
Status::Disabled => {
filter.hidden_action_types.extend(copilot_action_types);
filter.hidden_action_types.extend(copilot_auth_action_types);
filter
.hidden_action_types
.extend(copilot_no_auth_action_types);
}
Status::Authorized => {
filter
.hidden_action_types
.extend(copilot_no_auth_action_types);
for type_id in copilot_action_types
.iter()
.chain(&copilot_auth_action_types)
{
filter.hidden_action_types.remove(type_id);
}
}
_ => {
filter.hidden_action_types.extend(copilot_action_types);
filter.hidden_action_types.extend(copilot_auth_action_types);
for type_id in &copilot_no_auth_action_types {
filter.hidden_action_types.remove(type_id);
}
}
}
})
.detach();
// sign_in::init(cx);
// cx.add_global_action(|_: &SignIn, cx| {
// if let Some(copilot) = Copilot::global(cx) {
// copilot
// .update(cx, |copilot, cx| copilot.sign_in(cx))
// .detach_and_log_err(cx);
// }
// });
// cx.add_global_action(|_: &SignOut, cx| {
// if let Some(copilot) = Copilot::global(cx) {
// copilot
// .update(cx, |copilot, cx| copilot.sign_out(cx))
// .detach_and_log_err(cx);
// }
// });
// cx.add_global_action(|_: &Reinstall, cx| {
// if let Some(copilot) = Copilot::global(cx) {
// copilot
// .update(cx, |copilot, cx| copilot.reinstall(cx))
// .detach();
// }
// });
sign_in::init(cx);
cx.on_action(|_: &SignIn, cx| {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
.detach_and_log_err(cx);
}
});
cx.on_action(|_: &SignOut, cx| {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| copilot.sign_out(cx))
.detach_and_log_err(cx);
}
});
cx.on_action(|_: &Reinstall, cx| {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| copilot.reinstall(cx))
.detach();
}
});
}
enum CopilotServer {
@@ -1002,229 +1022,231 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
}
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use gpui::{executor::Deterministic, TestAppContext};
#[cfg(test)]
mod tests {
use super::*;
use gpui::TestAppContext;
// #[gpui::test(iterations = 10)]
// async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
// deterministic.forbid_parking();
// let (copilot, mut lsp) = Copilot::fake(cx);
#[gpui::test(iterations = 10)]
async fn test_buffer_management(cx: &mut TestAppContext) {
let (copilot, mut lsp) = Copilot::fake(cx);
// let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Hello"));
// let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
// .await,
// lsp::DidOpenTextDocumentParams {
// text_document: lsp::TextDocumentItem::new(
// buffer_1_uri.clone(),
// "plaintext".into(),
// 0,
// "Hello".into()
// ),
// }
// );
let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Hello"));
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
.parse()
.unwrap();
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,
lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
buffer_1_uri.clone(),
"plaintext".into(),
0,
"Hello".into()
),
}
);
// let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Goodbye"));
// let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
// .await,
// lsp::DidOpenTextDocumentParams {
// text_document: lsp::TextDocumentItem::new(
// buffer_2_uri.clone(),
// "plaintext".into(),
// 0,
// "Goodbye".into()
// ),
// }
// );
let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
.parse()
.unwrap();
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,
lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
buffer_2_uri.clone(),
"plaintext".into(),
0,
"Goodbye".into()
),
}
);
// buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
// .await,
// lsp::DidChangeTextDocumentParams {
// text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
// content_changes: vec![lsp::TextDocumentContentChangeEvent {
// range: Some(lsp::Range::new(
// lsp::Position::new(0, 5),
// lsp::Position::new(0, 5)
// )),
// range_length: None,
// text: " world".into(),
// }],
// }
// );
buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
.await,
lsp::DidChangeTextDocumentParams {
text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
content_changes: vec![lsp::TextDocumentContentChangeEvent {
range: Some(lsp::Range::new(
lsp::Position::new(0, 5),
lsp::Position::new(0, 5)
)),
range_length: None,
text: " world".into(),
}],
}
);
// // Ensure updates to the file are reflected in the LSP.
// buffer_1
// .update(cx, |buffer, cx| {
// buffer.file_updated(
// Arc::new(File {
// abs_path: "/root/child/buffer-1".into(),
// path: Path::new("child/buffer-1").into(),
// }),
// cx,
// )
// })
// .await;
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
// .await,
// lsp::DidCloseTextDocumentParams {
// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
// }
// );
// let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
// .await,
// lsp::DidOpenTextDocumentParams {
// text_document: lsp::TextDocumentItem::new(
// buffer_1_uri.clone(),
// "plaintext".into(),
// 1,
// "Hello world".into()
// ),
// }
// );
// Ensure updates to the file are reflected in the LSP.
buffer_1.update(cx, |buffer, cx| {
buffer.file_updated(
Arc::new(File {
abs_path: "/root/child/buffer-1".into(),
path: Path::new("child/buffer-1").into(),
}),
cx,
)
});
assert_eq!(
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
.await,
lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
}
);
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,
lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
buffer_1_uri.clone(),
"plaintext".into(),
1,
"Hello world".into()
),
}
);
// // Ensure all previously-registered buffers are closed when signing out.
// lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
// Ok(request::SignOutResult {})
// });
// copilot
// .update(cx, |copilot, cx| copilot.sign_out(cx))
// .await
// .unwrap();
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
// .await,
// lsp::DidCloseTextDocumentParams {
// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
// }
// );
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
// .await,
// lsp::DidCloseTextDocumentParams {
// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
// }
// );
// Ensure all previously-registered buffers are closed when signing out.
lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
Ok(request::SignOutResult {})
});
copilot
.update(cx, |copilot, cx| copilot.sign_out(cx))
.await
.unwrap();
// todo!() po: these notifications now happen in reverse order?
assert_eq!(
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
.await,
lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
}
);
assert_eq!(
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
.await,
lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
}
);
// // Ensure all previously-registered buffers are re-opened when signing in.
// lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
// Ok(request::SignInInitiateResult::AlreadySignedIn {
// user: "user-1".into(),
// })
// });
// copilot
// .update(cx, |copilot, cx| copilot.sign_in(cx))
// .await
// .unwrap();
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
// .await,
// lsp::DidOpenTextDocumentParams {
// text_document: lsp::TextDocumentItem::new(
// buffer_2_uri.clone(),
// "plaintext".into(),
// 0,
// "Goodbye".into()
// ),
// }
// );
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
// .await,
// lsp::DidOpenTextDocumentParams {
// text_document: lsp::TextDocumentItem::new(
// buffer_1_uri.clone(),
// "plaintext".into(),
// 0,
// "Hello world".into()
// ),
// }
// );
// Ensure all previously-registered buffers are re-opened when signing in.
lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
Ok(request::SignInInitiateResult::AlreadySignedIn {
user: "user-1".into(),
})
});
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
.await
.unwrap();
// // Dropping a buffer causes it to be closed on the LSP side as well.
// cx.update(|_| drop(buffer_2));
// assert_eq!(
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
// .await,
// lsp::DidCloseTextDocumentParams {
// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
// }
// );
// }
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,
lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
buffer_1_uri.clone(),
"plaintext".into(),
0,
"Hello world".into()
),
}
);
assert_eq!(
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await,
lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
buffer_2_uri.clone(),
"plaintext".into(),
0,
"Goodbye".into()
),
}
);
// Dropping a buffer causes it to be closed on the LSP side as well.
cx.update(|_| drop(buffer_2));
assert_eq!(
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
.await,
lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
}
);
}
// struct File {
// abs_path: PathBuf,
// path: Arc<Path>,
// }
struct File {
abs_path: PathBuf,
path: Arc<Path>,
}
// impl language2::File for File {
// fn as_local(&self) -> Option<&dyn language2::LocalFile> {
// Some(self)
// }
impl language::File for File {
fn as_local(&self) -> Option<&dyn language::LocalFile> {
Some(self)
}
// fn mtime(&self) -> std::time::SystemTime {
// unimplemented!()
// }
fn mtime(&self) -> std::time::SystemTime {
unimplemented!()
}
// fn path(&self) -> &Arc<Path> {
// &self.path
// }
fn path(&self) -> &Arc<Path> {
&self.path
}
// fn full_path(&self, _: &AppContext) -> PathBuf {
// unimplemented!()
// }
fn full_path(&self, _: &AppContext) -> PathBuf {
unimplemented!()
}
// fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
// unimplemented!()
// }
fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
unimplemented!()
}
// fn is_deleted(&self) -> bool {
// unimplemented!()
// }
fn is_deleted(&self) -> bool {
unimplemented!()
}
// fn as_any(&self) -> &dyn std::any::Any {
// unimplemented!()
// }
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!()
}
// fn to_proto(&self) -> rpc::proto::File {
// unimplemented!()
// }
fn to_proto(&self) -> rpc::proto::File {
unimplemented!()
}
// fn worktree_id(&self) -> usize {
// 0
// }
// }
fn worktree_id(&self) -> usize {
0
}
}
// impl language::LocalFile for File {
// fn abs_path(&self, _: &AppContext) -> PathBuf {
// self.abs_path.clone()
// }
impl language::LocalFile for File {
fn abs_path(&self, _: &AppContext) -> PathBuf {
self.abs_path.clone()
}
// fn load(&self, _: &AppContext) -> Task<Result<String>> {
// unimplemented!()
// }
fn load(&self, _: &AppContext) -> Task<Result<String>> {
unimplemented!()
}
// fn buffer_reloaded(
// &self,
// _: u64,
// _: &clock::Global,
// _: language::RopeFingerprint,
// _: language::LineEnding,
// _: std::time::SystemTime,
// _: &mut AppContext,
// ) {
// unimplemented!()
// }
// }
// }
fn buffer_reloaded(
&self,
_: u64,
_: &clock::Global,
_: language::RopeFingerprint,
_: language::LineEnding,
_: std::time::SystemTime,
_: &mut AppContext,
) {
unimplemented!()
}
}
}

View File

@@ -1,376 +1,213 @@
// TODO add logging in
// use crate::{request::PromptUserDeviceFlow, Copilot, Status};
// use gpui::{
// elements::*,
// geometry::rect::RectF,
// platform::{WindowBounds, WindowKind, WindowOptions},
// AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
// WindowHandle,
// };
// use theme::ui::modal;
use crate::{request::PromptUserDeviceFlow, Copilot, Status};
use gpui::{
div, size, AppContext, Bounds, ClipboardItem, Div, Element, GlobalPixels, InteractiveElement,
IntoElement, ParentElement, Point, Render, Stateful, Styled, ViewContext, VisualContext,
WindowBounds, WindowHandle, WindowKind, WindowOptions,
};
use theme::ActiveTheme;
use ui::{prelude::*, Button, Icon, IconElement, Label};
// #[derive(PartialEq, Eq, Debug, Clone)]
// struct CopyUserCode;
const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
// #[derive(PartialEq, Eq, Debug, Clone)]
// struct OpenGithub;
pub fn init(cx: &mut AppContext) {
if let Some(copilot) = Copilot::global(cx) {
let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
cx.observe(&copilot, move |copilot, cx| {
let status = copilot.read(cx).status();
// const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
match &status {
crate::Status::SigningIn { prompt } => {
if let Some(window) = verification_window.as_mut() {
let updated = window
.update(cx, |verification, cx| {
verification.set_status(status.clone(), cx);
cx.activate_window();
})
.is_ok();
if !updated {
verification_window = Some(create_copilot_auth_window(cx, &status));
}
} else if let Some(_prompt) = prompt {
verification_window = Some(create_copilot_auth_window(cx, &status));
}
}
Status::Authorized | Status::Unauthorized => {
if let Some(window) = verification_window.as_ref() {
window
.update(cx, |verification, cx| {
verification.set_status(status, cx);
cx.activate(true);
cx.activate_window();
})
.ok();
}
}
_ => {
if let Some(code_verification) = verification_window.take() {
code_verification
.update(cx, |_, cx| cx.remove_window())
.ok();
}
}
}
})
.detach();
}
}
// pub fn init(cx: &mut AppContext) {
// if let Some(copilot) = Copilot::global(cx) {
// let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
// cx.observe(&copilot, move |copilot, cx| {
// let status = copilot.read(cx).status();
fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
) -> WindowHandle<CopilotCodeVerification> {
let window_size = size(GlobalPixels::from(280.), GlobalPixels::from(280.));
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(Bounds::new(Point::default(), window_size)),
titlebar: None,
center: true,
focus: true,
show: true,
kind: WindowKind::PopUp,
is_movable: true,
display_id: None,
};
let window = cx.open_window(window_options, |cx| {
cx.build_view(|_| CopilotCodeVerification::new(status.clone()))
});
window
}
// match &status {
// crate::Status::SigningIn { prompt } => {
// if let Some(window) = verification_window.as_mut() {
// let updated = window
// .root(cx)
// .map(|root| {
// root.update(cx, |verification, cx| {
// verification.set_status(status.clone(), cx);
// cx.activate_window();
// })
// })
// .is_some();
// if !updated {
// verification_window = Some(create_copilot_auth_window(cx, &status));
// }
// } else if let Some(_prompt) = prompt {
// verification_window = Some(create_copilot_auth_window(cx, &status));
// }
// }
// Status::Authorized | Status::Unauthorized => {
// if let Some(window) = verification_window.as_ref() {
// if let Some(verification) = window.root(cx) {
// verification.update(cx, |verification, cx| {
// verification.set_status(status, cx);
// cx.platform().activate(true);
// cx.activate_window();
// });
// }
// }
// }
// _ => {
// if let Some(code_verification) = verification_window.take() {
// code_verification.update(cx, |cx| cx.remove_window());
// }
// }
// }
// })
// .detach();
// }
// }
pub struct CopilotCodeVerification {
status: Status,
connect_clicked: bool,
}
// fn create_copilot_auth_window(
// cx: &mut AppContext,
// status: &Status,
// ) -> WindowHandle<CopilotCodeVerification> {
// let window_size = theme::current(cx).copilot.modal.dimensions();
// let window_options = WindowOptions {
// bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
// titlebar: None,
// center: true,
// focus: true,
// show: true,
// kind: WindowKind::Normal,
// is_movable: true,
// screen: None,
// };
// cx.add_window(window_options, |_cx| {
// CopilotCodeVerification::new(status.clone())
// })
// }
impl CopilotCodeVerification {
pub fn new(status: Status) -> Self {
Self {
status,
connect_clicked: false,
}
}
// pub struct CopilotCodeVerification {
// status: Status,
// connect_clicked: bool,
// }
pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
self.status = status;
cx.notify();
}
// impl CopilotCodeVerification {
// pub fn new(status: Status) -> Self {
// Self {
// status,
// connect_clicked: false,
// }
// }
fn render_device_code(
data: &PromptUserDeviceFlow,
cx: &mut ViewContext<Self>,
) -> impl IntoElement {
let copied = cx
.read_from_clipboard()
.map(|item| item.text() == &data.user_code)
.unwrap_or(false);
h_stack()
.cursor_pointer()
.justify_between()
.on_mouse_down(gpui::MouseButton::Left, {
let user_code = data.user_code.clone();
move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
cx.notify();
}
})
.child(Label::new(data.user_code.clone()))
.child(div())
.child(Label::new(if copied { "Copied!" } else { "Copy" }))
}
// pub fn set_status(&mut self, status: Status, cx: &mut ViewContext<Self>) {
// self.status = status;
// cx.notify();
// }
fn render_prompting_modal(
connect_clicked: bool,
data: &PromptUserDeviceFlow,
cx: &mut ViewContext<Self>,
) -> impl Element {
let connect_button_label = if connect_clicked {
"Waiting for connection..."
} else {
"Connect to Github"
};
v_stack()
.flex_1()
.items_center()
.justify_between()
.w_full()
.child(Label::new(
"Enable Copilot by connecting your existing license",
))
.child(Self::render_device_code(data, cx))
.child(
Label::new("Paste this code into GitHub after clicking the button below.")
.size(ui::LabelSize::Small),
)
.child(
Button::new("connect-button", connect_button_label).on_click({
let verification_uri = data.verification_uri.clone();
cx.listener(move |this, _, cx| {
cx.open_url(&verification_uri);
this.connect_clicked = true;
})
}),
)
}
fn render_enabled_modal() -> impl Element {
v_stack()
.child(Label::new("Copilot Enabled!"))
.child(Label::new(
"You can update your settings or sign out from the Copilot menu in the status bar.",
))
.child(
Button::new("copilot-enabled-done-button", "Done")
.on_click(|_, cx| cx.remove_window()),
)
}
// fn render_device_code(
// data: &PromptUserDeviceFlow,
// style: &theme::Copilot,
// cx: &mut ViewContext<Self>,
// ) -> impl IntoAnyElement<Self> {
// let copied = cx
// .read_from_clipboard()
// .map(|item| item.text() == &data.user_code)
// .unwrap_or(false);
fn render_unauthorized_modal() -> impl Element {
v_stack()
.child(Label::new(
"Enable Copilot by connecting your existing license.",
))
.child(
Label::new("You must have an active Copilot license to use it in Zed.")
.color(Color::Warning),
)
.child(
Button::new("copilot-subscribe-button", "Subscibe on Github").on_click(|_, cx| {
cx.remove_window();
cx.open_url(COPILOT_SIGN_UP_URL)
}),
)
}
}
// let device_code_style = &style.auth.prompting.device_code;
impl Render for CopilotCodeVerification {
type Element = Stateful<Div>;
// MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
// Flex::row()
// .with_child(
// Label::new(data.user_code.clone(), device_code_style.text.clone())
// .aligned()
// .contained()
// .with_style(device_code_style.left_container)
// .constrained()
// .with_width(device_code_style.left),
// )
// .with_child(
// Label::new(
// if copied { "Copied!" } else { "Copy" },
// device_code_style.cta.style_for(state).text.clone(),
// )
// .aligned()
// .contained()
// .with_style(*device_code_style.right_container.style_for(state))
// .constrained()
// .with_width(device_code_style.right),
// )
// .contained()
// .with_style(device_code_style.cta.style_for(state).container)
// })
// .on_click(gpui::platform::MouseButton::Left, {
// let user_code = data.user_code.clone();
// move |_, _, cx| {
// cx.platform()
// .write_to_clipboard(ClipboardItem::new(user_code.clone()));
// cx.notify();
// }
// })
// .with_cursor_style(gpui::platform::CursorStyle::PointingHand)
// }
// fn render_prompting_modal(
// connect_clicked: bool,
// data: &PromptUserDeviceFlow,
// style: &theme::Copilot,
// cx: &mut ViewContext<Self>,
// ) -> AnyElement<Self> {
// enum ConnectButton {}
// Flex::column()
// .with_child(
// Flex::column()
// .with_children([
// Label::new(
// "Enable Copilot by connecting",
// style.auth.prompting.subheading.text.clone(),
// )
// .aligned(),
// Label::new(
// "your existing license.",
// style.auth.prompting.subheading.text.clone(),
// )
// .aligned(),
// ])
// .align_children_center()
// .contained()
// .with_style(style.auth.prompting.subheading.container),
// )
// .with_child(Self::render_device_code(data, &style, cx))
// .with_child(
// Flex::column()
// .with_children([
// Label::new(
// "Paste this code into GitHub after",
// style.auth.prompting.hint.text.clone(),
// )
// .aligned(),
// Label::new(
// "clicking the button below.",
// style.auth.prompting.hint.text.clone(),
// )
// .aligned(),
// ])
// .align_children_center()
// .contained()
// .with_style(style.auth.prompting.hint.container.clone()),
// )
// .with_child(theme::ui::cta_button::<ConnectButton, _, _, _>(
// if connect_clicked {
// "Waiting for connection..."
// } else {
// "Connect to GitHub"
// },
// style.auth.content_width,
// &style.auth.cta_button,
// cx,
// {
// let verification_uri = data.verification_uri.clone();
// move |_, verification, cx| {
// cx.platform().open_url(&verification_uri);
// verification.connect_clicked = true;
// }
// },
// ))
// .align_children_center()
// .into_any()
// }
// fn render_enabled_modal(
// style: &theme::Copilot,
// cx: &mut ViewContext<Self>,
// ) -> AnyElement<Self> {
// enum DoneButton {}
// let enabled_style = &style.auth.authorized;
// Flex::column()
// .with_child(
// Label::new("Copilot Enabled!", enabled_style.subheading.text.clone())
// .contained()
// .with_style(enabled_style.subheading.container)
// .aligned(),
// )
// .with_child(
// Flex::column()
// .with_children([
// Label::new(
// "You can update your settings or",
// enabled_style.hint.text.clone(),
// )
// .aligned(),
// Label::new(
// "sign out from the Copilot menu in",
// enabled_style.hint.text.clone(),
// )
// .aligned(),
// Label::new("the status bar.", enabled_style.hint.text.clone()).aligned(),
// ])
// .align_children_center()
// .contained()
// .with_style(enabled_style.hint.container),
// )
// .with_child(theme::ui::cta_button::<DoneButton, _, _, _>(
// "Done",
// style.auth.content_width,
// &style.auth.cta_button,
// cx,
// |_, _, cx| cx.remove_window(),
// ))
// .align_children_center()
// .into_any()
// }
// fn render_unauthorized_modal(
// style: &theme::Copilot,
// cx: &mut ViewContext<Self>,
// ) -> AnyElement<Self> {
// let unauthorized_style = &style.auth.not_authorized;
// Flex::column()
// .with_child(
// Flex::column()
// .with_children([
// Label::new(
// "Enable Copilot by connecting",
// unauthorized_style.subheading.text.clone(),
// )
// .aligned(),
// Label::new(
// "your existing license.",
// unauthorized_style.subheading.text.clone(),
// )
// .aligned(),
// ])
// .align_children_center()
// .contained()
// .with_style(unauthorized_style.subheading.container),
// )
// .with_child(
// Flex::column()
// .with_children([
// Label::new(
// "You must have an active copilot",
// unauthorized_style.warning.text.clone(),
// )
// .aligned(),
// Label::new(
// "license to use it in Zed.",
// unauthorized_style.warning.text.clone(),
// )
// .aligned(),
// ])
// .align_children_center()
// .contained()
// .with_style(unauthorized_style.warning.container),
// )
// .with_child(theme::ui::cta_button::<Self, _, _, _>(
// "Subscribe on GitHub",
// style.auth.content_width,
// &style.auth.cta_button,
// cx,
// |_, _, cx| {
// cx.remove_window();
// cx.platform().open_url(COPILOT_SIGN_UP_URL)
// },
// ))
// .align_children_center()
// .into_any()
// }
// }
// impl Entity for CopilotCodeVerification {
// type Event = ();
// }
// impl View for CopilotCodeVerification {
// fn ui_name() -> &'static str {
// "CopilotCodeVerification"
// }
// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
// cx.notify()
// }
// fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
// cx.notify()
// }
// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
// enum ConnectModal {}
// let style = theme::current(cx).clone();
// modal::<ConnectModal, _, _, _, _>(
// "Connect Copilot to Zed",
// &style.copilot.modal,
// cx,
// |cx| {
// Flex::column()
// .with_children([
// theme::ui::icon(&style.copilot.auth.header).into_any(),
// match &self.status {
// Status::SigningIn {
// prompt: Some(prompt),
// } => Self::render_prompting_modal(
// self.connect_clicked,
// &prompt,
// &style.copilot,
// cx,
// ),
// Status::Unauthorized => {
// self.connect_clicked = false;
// Self::render_unauthorized_modal(&style.copilot, cx)
// }
// Status::Authorized => {
// self.connect_clicked = false;
// Self::render_enabled_modal(&style.copilot, cx)
// }
// _ => Empty::new().into_any(),
// },
// ])
// .align_children_center()
// },
// )
// .into_any()
// }
// }
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let prompt = match &self.status {
Status::SigningIn {
prompt: Some(prompt),
} => Self::render_prompting_modal(self.connect_clicked, &prompt, cx).into_any_element(),
Status::Unauthorized => {
self.connect_clicked = false;
Self::render_unauthorized_modal().into_any_element()
}
Status::Authorized => {
self.connect_clicked = false;
Self::render_enabled_modal().into_any_element()
}
_ => div().into_any_element(),
};
div()
.id("copilot code verification")
.flex()
.flex_col()
.size_full()
.items_center()
.p_10()
.bg(cx.theme().colors().element_background)
.child(ui::Label::new("Connect Copilot to Zed"))
.child(IconElement::new(Icon::ZedXCopilot))
.child(prompt)
}
}

View File

@@ -0,0 +1,27 @@
[package]
name = "copilot_button2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/copilot_button.rs"
doctest = false
[dependencies]
copilot = { package = "copilot2", path = "../copilot2" }
editor = { package = "editor2", path = "../editor2" }
fs = { package = "fs2", path = "../fs2" }
zed-actions = { package="zed_actions2", path = "../zed_actions2"}
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }
workspace = { package = "workspace2", path = "../workspace2" }
anyhow.workspace = true
smol.workspace = true
futures.workspace = true
[dev-dependencies]
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }

View File

@@ -0,0 +1,370 @@
#![allow(unused)]
use anyhow::Result;
use copilot::{Copilot, SignOut, Status};
use editor::{scroll::autoscroll::Autoscroll, Editor};
use fs::Fs;
use gpui::{
div, Action, AnchorCorner, AppContext, AsyncAppContext, AsyncWindowContext, Div, Entity,
ParentElement, Render, Subscription, View, ViewContext, WeakView, WindowContext,
};
use language::{
language_settings::{self, all_language_settings, AllLanguageSettings},
File, Language,
};
use settings::{update_settings_file, Settings, SettingsStore};
use std::{path::Path, sync::Arc};
use util::{paths, ResultExt};
use workspace::{
create_and_open_local_file,
item::ItemHandle,
ui::{
popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, PopoverMenu, Tooltip,
},
StatusItemView, Toast, Workspace,
};
use zed_actions::OpenBrowser;
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
const COPILOT_STARTING_TOAST_ID: usize = 1337;
const COPILOT_ERROR_TOAST_ID: usize = 1338;
pub struct CopilotButton {
editor_subscription: Option<(Subscription, usize)>,
editor_enabled: Option<bool>,
language: Option<Arc<Language>>,
file: Option<Arc<dyn File>>,
fs: Arc<dyn Fs>,
}
impl Render for CopilotButton {
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let all_language_settings = all_language_settings(None, cx);
if !all_language_settings.copilot.feature_enabled {
return div();
}
let Some(copilot) = Copilot::global(cx) else {
return div();
};
let status = copilot.read(cx).status();
let enabled = self
.editor_enabled
.unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
let icon = match status {
Status::Error(_) => Icon::CopilotError,
Status::Authorized => {
if enabled {
Icon::Copilot
} else {
Icon::CopilotDisabled
}
}
_ => Icon::CopilotInit,
};
if let Status::Error(e) = status {
return div().child(
IconButton::new("copilot-error", icon)
.on_click(cx.listener(move |this, _, cx| {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(
COPILOT_ERROR_TOAST_ID,
format!("Copilot can't be started: {}", e),
)
.on_click(
"Reinstall Copilot",
|cx| {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| copilot.reinstall(cx))
.detach();
}
},
),
cx,
);
});
}
}))
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
);
}
let this = cx.view().clone();
div().child(
popover_menu("copilot")
.menu(move |cx| match status {
Status::Authorized => this.update(cx, |this, cx| this.build_copilot_menu(cx)),
_ => this.update(cx, |this, cx| this.build_copilot_start_menu(cx)),
})
.anchor(AnchorCorner::BottomRight)
.trigger(
IconButton::new("copilot-icon", icon)
.tooltip(|cx| Tooltip::text("GitHub Copilot", cx)),
),
)
}
}
impl CopilotButton {
pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
.detach();
Self {
editor_subscription: None,
editor_enabled: None,
language: None,
file: None,
fs,
}
}
pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
let fs = self.fs.clone();
ContextMenu::build(cx, |menu, cx| {
menu.entry("Sign In", initiate_sign_in)
.entry("Disable Copilot", move |cx| hide_copilot(fs.clone(), cx))
})
}
pub fn build_copilot_menu(&mut self, cx: &mut ViewContext<Self>) -> View<ContextMenu> {
let fs = self.fs.clone();
return ContextMenu::build(cx, move |mut menu, cx| {
if let Some(language) = self.language.clone() {
let fs = fs.clone();
let language_enabled =
language_settings::language_settings(Some(&language), None, cx)
.show_copilot_suggestions;
menu = menu.entry(
format!(
"{} Suggestions for {}",
if language_enabled { "Hide" } else { "Show" },
language.name()
),
move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
);
}
let settings = AllLanguageSettings::get_global(cx);
if let Some(file) = &self.file {
let path = file.path().clone();
let path_enabled = settings.copilot_enabled_for_path(&path);
menu = menu.entry(
format!(
"{} Suggestions for This Path",
if path_enabled { "Hide" } else { "Show" }
),
move |cx| {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
if let Ok(workspace) = workspace.root_view(cx) {
let workspace = workspace.downgrade();
cx.spawn(|cx| {
configure_disabled_globs(
workspace,
path_enabled.then_some(path.clone()),
cx,
)
})
.detach_and_log_err(cx);
}
}
},
);
}
let globally_enabled = settings.copilot_enabled(None, None);
menu.entry(
if globally_enabled {
"Hide Suggestions for All Files"
} else {
"Show Suggestions for All Files"
},
move |cx| toggle_copilot_globally(fs.clone(), cx),
)
.separator()
.link(
"Copilot Settings",
OpenBrowser {
url: COPILOT_SETTINGS_URL.to_string(),
}
.boxed_clone(),
)
.action("Sign Out", SignOut.boxed_clone())
});
}
pub fn update_enabled(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let suggestion_anchor = editor.selections.newest_anchor().start;
let language = snapshot.language_at(suggestion_anchor);
let file = snapshot.file_at(suggestion_anchor).cloned();
self.editor_enabled = Some(
all_language_settings(self.file.as_ref(), cx)
.copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())),
);
self.language = language.cloned();
self.file = file;
cx.notify()
}
}
impl StatusItemView for CopilotButton {
fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
if let Some(editor) = item.map(|item| item.act_as::<Editor>(cx)).flatten() {
self.editor_subscription = Some((
cx.observe(&editor, Self::update_enabled),
editor.entity_id().as_u64() as usize,
));
self.update_enabled(editor, cx);
} else {
self.language = None;
self.editor_subscription = None;
self.editor_enabled = None;
}
cx.notify();
}
}
async fn configure_disabled_globs(
workspace: WeakView<Workspace>,
path_to_disable: Option<Arc<Path>>,
mut cx: AsyncWindowContext,
) -> Result<()> {
let settings_editor = workspace
.update(&mut cx, |_, cx| {
create_and_open_local_file(&paths::SETTINGS, cx, || {
settings::initial_user_settings_content().as_ref().into()
})
})?
.await?
.downcast::<Editor>()
.unwrap();
settings_editor.downgrade().update(&mut cx, |item, cx| {
let text = item.buffer().read(cx).snapshot(cx).text();
let settings = cx.global::<SettingsStore>();
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
let copilot = file.copilot.get_or_insert_with(Default::default);
let globs = copilot.disabled_globs.get_or_insert_with(|| {
settings
.get::<AllLanguageSettings>(None)
.copilot
.disabled_globs
.iter()
.map(|glob| glob.glob().to_string())
.collect()
});
if let Some(path_to_disable) = &path_to_disable {
globs.push(path_to_disable.to_string_lossy().into_owned());
} else {
globs.clear();
}
});
if !edits.is_empty() {
item.change_selections(Some(Autoscroll::newest()), cx, |selections| {
selections.select_ranges(edits.iter().map(|e| e.0.clone()));
});
// When *enabling* a path, don't actually perform an edit, just select the range.
if path_to_disable.is_some() {
item.edit(edits.iter().cloned(), cx);
}
}
})?;
anyhow::Ok(())
}
fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
});
}
fn toggle_copilot_for_language(language: Arc<Language>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_copilot_suggestions =
all_language_settings(None, cx).copilot_enabled(Some(&language), None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.languages
.entry(language.name())
.or_default()
.show_copilot_suggestions = Some(!show_copilot_suggestions);
});
}
fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.features.get_or_insert(Default::default()).copilot = Some(false);
});
}
fn initiate_sign_in(cx: &mut WindowContext) {
let Some(copilot) = Copilot::global(cx) else {
return;
};
let status = copilot.read(cx).status();
match status {
Status::Starting { task } => {
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
cx,
);
workspace.weak_handle()
}) else {
return;
};
cx.spawn(|mut cx| async move {
task.await;
if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
workspace
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
Status::Authorized => workspace.show_toast(
Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot has started!"),
cx,
),
_ => {
workspace.dismiss_toast(COPILOT_STARTING_TOAST_ID, cx);
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
.detach_and_log_err(cx);
}
})
.log_err();
}
})
.detach();
}
_ => {
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
.detach_and_log_err(cx);
}
}
}

View File

@@ -126,7 +126,7 @@ impl View for ProjectDiagnosticsEditor {
json!({
"project": json!({
"language_servers": project.language_server_statuses().collect::<Vec<_>>(),
"summary": project.diagnostic_summary(cx),
"summary": project.diagnostic_summary(false, cx),
}),
"summary": self.summary,
"paths_to_update": self.paths_to_update.iter().map(|(server_id, paths)|
@@ -195,7 +195,7 @@ impl ProjectDiagnosticsEditor {
});
let project = project_handle.read(cx);
let summary = project.diagnostic_summary(cx);
let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
summary,
@@ -241,7 +241,7 @@ impl ProjectDiagnosticsEditor {
let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
.project
.read(cx)
.diagnostic_summaries(cx)
.diagnostic_summaries(false, cx)
.fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
summaries.entry(server_id).or_default().insert(path);
summaries
@@ -320,7 +320,7 @@ impl ProjectDiagnosticsEditor {
.context("rechecking diagnostics for paths")?;
this.update(&mut cx, |this, cx| {
this.summary = this.project.read(cx).diagnostic_summary(cx);
this.summary = this.project.read(cx).diagnostic_summary(false, cx);
cx.emit(Event::TitleChanged);
})?;
anyhow::Ok(())

View File

@@ -34,19 +34,19 @@ impl DiagnosticIndicator {
}
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
this.summary = project.read(cx).diagnostic_summary(cx);
this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
this.summary = project.read(cx).diagnostic_summary(cx);
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
_ => {}
})
.detach();
Self {
summary: project.read(cx).diagnostic_summary(cx),
summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
.read(cx)
.language_servers_running_disk_based_diagnostics()

View File

@@ -36,7 +36,7 @@ use std::{
};
use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
use ui::{h_stack, Color, HighlightedLabel, Icon, IconElement, Label};
use ui::{h_stack, prelude::*, HighlightedLabel, Icon, IconElement, Label};
use util::TryFutureExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -88,12 +88,12 @@ struct DiagnosticGroupState {
block_count: usize,
}
impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
impl Render for ProjectDiagnosticsEditor {
type Output = Focusable<Div>;
type Element = Focusable<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Output {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let child = if self.path_states.is_empty() {
div()
.bg(cx.theme().colors().editor_background)
@@ -158,14 +158,14 @@ impl ProjectDiagnosticsEditor {
});
let editor_event_subscription =
cx.subscribe(&editor, |this, _editor, event: &EditorEvent, cx| {
Self::emit_item_event_for_editor_event(event, cx);
cx.emit(event.clone());
if event == &EditorEvent::Focused && this.path_states.is_empty() {
cx.focus(&this.focus_handle);
}
});
let project = project_handle.read(cx);
let summary = project.diagnostic_summary(cx);
let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
summary,
@@ -183,40 +183,6 @@ impl ProjectDiagnosticsEditor {
this
}
fn emit_item_event_for_editor_event(event: &EditorEvent, cx: &mut ViewContext<Self>) {
match event {
EditorEvent::Closed => cx.emit(ItemEvent::CloseItem),
EditorEvent::Saved | EditorEvent::TitleChanged => {
cx.emit(ItemEvent::UpdateTab);
cx.emit(ItemEvent::UpdateBreadcrumbs);
}
EditorEvent::Reparsed => {
cx.emit(ItemEvent::UpdateBreadcrumbs);
}
EditorEvent::SelectionsChanged { local } if *local => {
cx.emit(ItemEvent::UpdateBreadcrumbs);
}
EditorEvent::DirtyChanged => {
cx.emit(ItemEvent::UpdateTab);
}
EditorEvent::BufferEdited => {
cx.emit(ItemEvent::Edit);
cx.emit(ItemEvent::UpdateBreadcrumbs);
}
EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
cx.emit(ItemEvent::Edit);
}
_ => {}
}
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, cx);
@@ -252,7 +218,7 @@ impl ProjectDiagnosticsEditor {
let mut new_summaries: HashMap<LanguageServerId, HashSet<ProjectPath>> = self
.project
.read(cx)
.diagnostic_summaries(cx)
.diagnostic_summaries(false, cx)
.fold(HashMap::default(), |mut summaries, (path, server_id, _)| {
summaries.entry(server_id).or_default().insert(path);
summaries
@@ -332,9 +298,8 @@ impl ProjectDiagnosticsEditor {
.context("rechecking diagnostics for paths")?;
this.update(&mut cx, |this, cx| {
this.summary = this.project.read(cx).diagnostic_summary(cx);
cx.emit(ItemEvent::UpdateTab);
cx.emit(ItemEvent::UpdateBreadcrumbs);
this.summary = this.project.read(cx).diagnostic_summary(false, cx);
cx.emit(EditorEvent::TitleChanged);
})?;
anyhow::Ok(())
}
@@ -649,6 +614,12 @@ impl FocusableView for ProjectDiagnosticsEditor {
}
impl Item for ProjectDiagnosticsEditor {
type Event = EditorEvent;
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
Editor::to_item_events(event, f)
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, cx| editor.deactivated(cx));
}
@@ -774,24 +745,39 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
Arc::new(move |_| {
h_stack()
.id("diagnostic header")
.gap_3()
.bg(gpui::red())
.map(|stack| {
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
IconElement::new(Icon::XCircle).color(Color::Error)
} else {
IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
};
stack.child(div().pl_8().child(icon))
})
.when_some(diagnostic.source.as_ref(), |stack, source| {
stack.child(Label::new(format!("{source}:")).color(Color::Accent))
})
.child(HighlightedLabel::new(message.clone(), highlights.clone()))
.when_some(diagnostic.code.as_ref(), |stack, code| {
stack.child(Label::new(code.clone()))
})
.py_2()
.pl_10()
.pr_5()
.w_full()
.justify_between()
.gap_2()
.child(
h_stack()
.gap_3()
.map(|stack| {
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
IconElement::new(Icon::XCircle).color(Color::Error)
} else {
IconElement::new(Icon::ExclamationTriangle).color(Color::Warning)
};
stack.child(icon)
})
.child(
h_stack()
.gap_1()
.child(HighlightedLabel::new(message.clone(), highlights.clone()))
.when_some(diagnostic.code.as_ref(), |stack, code| {
stack.child(Label::new(format!("({code})")).color(Color::Muted))
}),
),
)
.child(
h_stack()
.gap_1()
.when_some(diagnostic.source.as_ref(), |stack, source| {
stack.child(Label::new(format!("{source}")).color(Color::Muted))
}),
)
.into_any_element()
})
}
@@ -802,11 +788,22 @@ pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement {
label.into_any_element()
} else {
h_stack()
.bg(gpui::red())
.child(IconElement::new(Icon::XCircle))
.child(Label::new(summary.error_count.to_string()))
.child(IconElement::new(Icon::ExclamationTriangle))
.child(Label::new(summary.warning_count.to_string()))
.gap_1()
.when(summary.error_count > 0, |then| {
then.child(
h_stack()
.gap_1()
.child(IconElement::new(Icon::XCircle).color(Color::Error))
.child(Label::new(summary.error_count.to_string())),
)
})
.when(summary.warning_count > 0, |then| {
then.child(
h_stack()
.child(IconElement::new(Icon::ExclamationTriangle).color(Color::Warning))
.child(Label::new(summary.warning_count.to_string())),
)
})
.into_any_element()
}
}
@@ -1550,7 +1547,7 @@ mod tests {
block_id: ix,
editor_style: &editor::EditorStyle::default(),
})
.element_id()?
.inner_id()?
.try_into()
.ok()?,

View File

@@ -22,9 +22,9 @@ pub struct DiagnosticIndicator {
}
impl Render for DiagnosticIndicator {
type Output = Stateful<Div>;
type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Output {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) {
(0, 0) => h_stack().child(IconElement::new(Icon::Check).color(Color::Success)),
(0, warning_count) => h_stack()
@@ -44,7 +44,7 @@ impl Render for DiagnosticIndicator {
};
h_stack()
.id(cx.entity_id())
.id("diagnostic-indicator")
.on_action(cx.listener(Self::go_to_next_diagnostic))
.rounded_md()
.flex_none()
@@ -77,13 +77,13 @@ impl DiagnosticIndicator {
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
this.summary = project.read(cx).diagnostic_summary(cx);
this.summary = project.read(cx).diagnostic_summary(false, cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
this.summary = project.read(cx).diagnostic_summary(cx);
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.notify();
}
@@ -92,7 +92,7 @@ impl DiagnosticIndicator {
.detach();
Self {
summary: project.read(cx).diagnostic_summary(cx),
summary: project.read(cx).diagnostic_summary(false, cx),
in_progress_checks: project
.read(cx)
.language_servers_running_disk_based_diagnostics()

View File

@@ -1,5 +1,6 @@
use crate::ProjectDiagnosticsEditor;
use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
use ui::prelude::*;
use ui::{Icon, IconButton, Tooltip};
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
@@ -8,9 +9,9 @@ pub struct ToolbarControls {
}
impl Render for ToolbarControls {
type Output = Div;
type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Output {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let include_warnings = self
.editor
.as_ref()

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