Compare commits

..

938 Commits

Author SHA1 Message Date
Piotr Osiewicz
62949d20a7 chore: fmt 2023-08-31 15:43:50 +02:00
Piotr Osiewicz
110bb84261 Merge branch 'main' into gpui_extract_platform 2023-08-31 15:43:32 +02:00
Kirill Bulatov
fad595dca6 Use ctrl-: instead of ctrl-shift-: for inlay hints toggling (#2921)
The latter is not possible to press in Zed, since `:` is typed as
`shift-;` with typical US keyboard layouts.

In the end, it's the same buttons you have to press to toggle the inlay
hints, but working this time.

Release Notes:

- N/A
2023-08-31 12:10:45 +03:00
Kirill Bulatov
897adb67c5 Log language server stderr output in server logs (#2918)
<img width="1728" alt="Screenshot 2023-08-31 at 01 07 11"
src="https://github.com/zed-industries/zed/assets/2690773/537a18d6-59bf-4a77-896f-fc2cb6dc7fe8">

Line by line, we print stderr to help with debugging and servers that
log into stderr.

Release Notes:

- N/A
2023-08-31 11:13:45 +03:00
Kirill Bulatov
18efc0d5e5 Fix the tests, by not requiring stderr for fake servers 2023-08-31 11:07:37 +03:00
Kirill Bulatov
af665cc3d2 Use ctrl-: instead of ctrl-shift-: for inlay hints toggling
The latter is not posible to press in Zed, since `:` is typed as
`shift-;` with typical US keyboard layouts.

In the end, it's the same buttons you have to press to toggle the inlay
hints, but working this time.
2023-08-31 10:43:29 +03:00
Kirill Bulatov
0f619e0b67 Do not write TRACE logs into file for Lua 2023-08-31 01:14:37 +03:00
Kirill Bulatov
54e7e2f59d Capture language servers' stderr into server logs 2023-08-31 01:14:37 +03:00
Max Brunsfeld
b94955910c Disable save as prompt for channel notes (#2917) 2023-08-30 15:09:53 -07:00
Max Brunsfeld
4b5948e004 Disable save as prompt for channel notes 2023-08-30 15:05:47 -07:00
Kirill Bulatov
d9a72aa1e0 Rewrite inlay hint collab tests to remove races (#2916)
Release Notes:

- N/A
2023-08-31 00:49:50 +03:00
Max Brunsfeld
6d60960fd2 Fix collab panel regressions (#2915)
Release Notes:

- Fixed a bug where collaborators' cursors were rendered with the wrong
color when not following them (preview only).
- Fixed an issue where icons were inconsistent sizes in the
collaboration panel (preview only).
2023-08-30 14:41:37 -07:00
Kirill Bulatov
ab49f8c592 Rewrite inlay hint collab tests to remove races 2023-08-31 00:37:00 +03:00
Max Brunsfeld
f9dffc1734 Remove unnecessary ConstrainedBoxes in collab panel
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-30 14:35:02 -07:00
Max Brunsfeld
123bc85a8e collab panel: Make screen share row line up with shared projects
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-30 14:23:12 -07:00
Max Brunsfeld
e0bdd857f1 Fix cursor colors of non-followed collaborators
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-30 14:14:18 -07:00
Max Brunsfeld
166585a2a8 Avoid accidental gpui transitive dependency in collab (#2914)
Release Notes:

- N/A
2023-08-30 13:22:10 -07:00
Max Brunsfeld
46429426ef Avoid accidental gpui transitive dependency in collab
* Make Fs depend on Text, not vise versa

Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-30 13:16:01 -07:00
Joseph T. Lyons
feb7a8a0f4 collab 0.19.0 2023-08-30 14:37:55 -04:00
Joseph T. Lyons
7204c245ea v0.103.x dev 2023-08-30 13:41:09 -04:00
Nate Butler
e808386765 WIP UI Tweaks (#2911)
- Tighten up toolbar
- Reduce intensity of active tools
- Remove divider between project + branch
- Add a styletree for toolbar + move breadcrumb into it
- Some ts theme tidying

[[PR Description]]

Release Notes:

- Improved density and contrast of a number of UI elements.
2023-08-30 11:19:05 -04:00
Nate Butler
1da3be0646 Align diagnostic icons with other statusbar icons 2023-08-30 11:14:50 -04:00
Nate Butler
256949bee0 fmt 2023-08-30 10:56:08 -04:00
Antonio Scandurra
ea17d1638e Introduce code generation (#2901)
![CleanShot 2023-08-28 at 12 24
36@2x](https://github.com/zed-industries/zed/assets/482957/f97cb399-1ac2-4fa9-94a7-137d1eec711c)


Release Notes:

- Added a new "Inline Assist" feature that lets you transform a
selection or generate new code at the cursor location by hitting
`ctrl-enter`.
2023-08-30 14:58:22 +02:00
Antonio Scandurra
bf67d3710a Remove trailing backticks when assistant ends with a trailing newline 2023-08-30 12:08:14 +02:00
Antonio Scandurra
5f6562c214 Detect indentation from GPT output 2023-08-30 12:07:58 +02:00
Antonio Scandurra
c6f4390511 Retain search history for inline assistants
This only works in-memory for now.
2023-08-30 11:30:51 +02:00
Antonio Scandurra
5c498c8610 Show inline assistant errors 2023-08-30 11:04:48 +02:00
Kirill Bulatov
7a1b300b25 Do not blink the cursor if Zed app is focused away (#2907)
Deals with https://github.com/zed-industries/community/issues/1116
Part of https://github.com/zed-industries/community/issues/1098

Use Mac platform callbacks to send a global event and enable/disable
caret blinking in editors when the app is not focused.

Release Notes:

- Stop blinking caret if Zed app is not focused
2023-08-30 10:53:09 +03:00
Nate Butler
6e964acd92 Fix extra theme entry 2023-08-29 20:04:15 -04:00
Nate Butler
97d187bba7 Remove project divider in titlebar 2023-08-29 19:50:27 -04:00
Nate Butler
2af5fc5030 Move breadcrumb style to toolbar 2023-08-29 16:37:48 -04:00
Nate Butler
c9b1237079 Update titlebar size 2023-08-29 16:20:19 -04:00
Nate Butler
93cf52a719 Update toolbar active state style 2023-08-29 16:10:40 -04:00
Nate Butler
d91a9615b5 Format 2023-08-29 16:02:04 -04:00
Nate Butler
33c9f14852 Don't require passing theme to toggleable_icon_button 2023-08-29 16:01:08 -04:00
Nate Butler
0a14e33dba Pull toolbar into it's own styletree 2023-08-29 15:59:35 -04:00
Max Brunsfeld
e7ba5a1edb Search UI polish (#2904)
This PR polishes the search bar UI, making the layout more dense, and
the spacing more consistent with the rest of the app. I've also
re-ordered the toolbar items to reflect some of @iamnbutler's original
search designs. The items related to the search query are on the left,
and the actions that navigate the buffer (next, prev, select all, result
count) are on the right.
2023-08-29 12:53:32 -07:00
Kirill Bulatov
100870aa9c Do not blink the cursor if Zed window is focused away
co-authored-by: Max <max@zed.dev>
2023-08-29 21:33:03 +03:00
Conrad Irwin
dd577074f2 vim: Fix relative motions (#2888)
This changes vim motions to be relative to fold lines, not display
lines, to match the behaviour of vim.

This is necessary for relative line numbers to make sense (as the most
important thing is you can do `3j` to get th e line that is numbered 3).


Release Notes:

- vim: Fix handling of motions when `soft_wrap` is enabled in zed. Like
in vim `j,k,up,down,$,^,0,home,end` will all now navigate in file
coordinates not display coordinates.
- vim: Add `g {j,k,up,down,$,^,0,home,end}` to navigate in display
coordinates.
- vim: Add `z o` and `z c` to open and close folds.
- vim: Add `z f` in visual mode to fold selection.

Note: this may be a jarring change if you're grown used to the current
behaviour of `j` and `k`. You can make the issue less acute by setting
`"soft_wrap":"none"` in your settings; or you can manually copy the
bindings for `g j` to the binding for `j` (etc.) in your keymap.json to
preserve the existing behaviour.
2023-08-29 11:19:37 -07:00
Kirill Bulatov
859ac1e97f Add a default binding for toggling inlay hints (#2910)
Release Notes:

- Add a `"ctrl-shift-:": "editor::ToggleInlayHints"` default binding
2023-08-29 21:09:46 +03:00
Nate Butler
e89ccf2e26 Remove unused label_button 2023-08-29 14:09:31 -04:00
Nate Butler
f0ab27a83d Reorder "Select All" button 2023-08-29 14:04:17 -04:00
Kirill Bulatov
bbb222b6fc Add a default binding for toggling inlay hints 2023-08-29 20:56:26 +03:00
Nate Butler
f626c61b1e Update action_button style 2023-08-29 13:40:58 -04:00
Nate Butler
f6faeea720 Add disabled as an option on text_button 2023-08-29 13:40:46 -04:00
Nate Butler
05da4b740a Update spacing, button heights 2023-08-29 13:28:06 -04:00
Nate Butler
a5b12d535f Add margin and padding functions 2023-08-29 13:06:13 -04:00
Nate Butler
53558bc603 Remove baseurl to prevent theme import issue 2023-08-29 13:05:59 -04:00
Max Brunsfeld
7ae5ee699f Debounce code action and document highlight requests (#2905)
Lately, I've been finding Rust-analyzer unusably slow when editing large
files (like `editor_tests.rs`, or `integration_tests.rs`). When I
profile the Rust-analyzer process, I see that it sometimes saturates up
to 10 cores processing a queue of code actions requests.

Additionally, sometimes when collaborating on large files like these, we
see long delays in propagating buffer operations. I'm still not sure why
this is happening, but whenever I look at the server logs in Datadog, I
see that there are remote `CodeActions` and `DocumentHighlights`
messages being processed that take upwards of 30 seconds. I think what
may be happening is that many such requests are resolving at once, and
the responses are taking up too much of the host's bandwidth.

I think that both of these problems are caused by us sending way too
many code action and document highlight requests to rust-analyzer. This
PR adds a simple debounce between changing selections and making these
requests.

From my local testing, this debounce makes Rust-analyzer *much* more
responsive when moving the cursor around a large file like
`editor_tests.rs`.
2023-08-29 09:30:36 -07:00
Antonio Scandurra
87e25c8c23 Use model from conversation when available 2023-08-29 18:25:02 +02:00
Antonio Scandurra
16422a06ad Remember whether include conversation was toggled 2023-08-29 18:25:02 +02:00
Nathan Sobo
84b9c22170 Merge branch 'main' into ai-refactoring 2023-08-29 10:17:01 -06:00
Antonio Scandurra
df377d5195 Use Inline Assist across the board 2023-08-29 17:32:23 +02:00
Piotr Osiewicz
ea0e5e880e chore: Use IsTerminal trait instead of relying on libc to detect stdout being a terminal (#2908)
IsTerminal was added in 1.70.

Release Notes:
- N/A
2023-08-29 15:56:50 +02:00
Nathan Sobo
396558755e Testing: Update build script to make local testing easier (#2903)
* `script/bundle -l` will only build for the current architecture and
skip DMG creation. It will also copy to `/Applications`.
* `script/bundle -l "My Bundle"` will name the bundle after your
provided name.
* Passing `-f` will overwrite. Passing `-o` will also open the
application.
2023-08-29 07:07:35 -06:00
Antonio Scandurra
72413dbaf2 Remove the ability to reply to specific message in assistant 2023-08-29 14:51:00 +02:00
Antonio Scandurra
2332f82442 More polish 2023-08-29 14:41:02 +02:00
Antonio Scandurra
08df24412a Delete less aggressively 2023-08-29 14:31:58 +02:00
Antonio Scandurra
c2b60df5af Allow including conversation when triggering inline assist 2023-08-29 14:08:16 +02:00
Kirill Bulatov
1c6964ee96 Trim off surrounding [] when parsing terminal hover links (#2906)
Follow-up of https://github.com/zed-industries/zed/pull/2899

Terminal has to accept `[` and `]` as valid word parts, due to
`[slug].tsx` being a valid file name.
Yet, terminal has to exclude these to match paths in strings like
`[/some/path/[slug].tsx]`.

Release Notes:

- N/A
2023-08-29 10:49:33 +03:00
Kirill Bulatov
7c498feb85 Trim off surrounding [] when parsing terminal hover links
Terminal has to accept `[` and `]` as valid word parts, due to
`[slug].tsx` being a valid file name.
Yet, terminal has to exclude these to match paths in strings like
`[/some/path/[slug].tsx]`.
2023-08-29 10:40:20 +03:00
Max Brunsfeld
791f6cf9e7 Update some tests to reflect code action debouncing 2023-08-28 17:45:32 -07:00
Max Brunsfeld
5142049515 Demote some Peer logging to trace level 2023-08-28 17:45:19 -07:00
Max Brunsfeld
89eab78cf7 Debounce document highlight and code actions requests 2023-08-28 15:48:55 -07:00
Max Brunsfeld
70bea75897 Change cycle mode action to reflect new mode button order 2023-08-28 15:15:54 -07:00
Max Brunsfeld
3eee282a6b Overhaul search bar layout
* Use a single row, instead of centering the search bar within a double-row toolbar.
* Search query controls on the left, navigation on the right
* Semantic is the final mode, for greater stability between buffer and project search.
* Prevent query editor from moving when toggling path filters
2023-08-28 14:20:09 -07:00
Max Brunsfeld
78f9a1f280 Remove padding from workspace toolbar, increase its content height to compensate
The padding makes it difficult to layout toolbar items correctly when they are more than one row tall.
2023-08-28 14:18:30 -07:00
Max Brunsfeld
bb448b91d5 Don't add a quick actions toolbar item for non-editor views
Rather than adding primary toolbar item that renders as empty,
don't add an item at all. This prevents spurious spacing from
being added after other primary toolbar items.
2023-08-28 14:16:45 -07:00
Max Brunsfeld
9521f6da42 Simplify implementation of flex with spacing 2023-08-28 14:16:21 -07:00
Nathan Sobo
a1d2ae3095 Add -l option to build script
When you pass -l, we build for the local architecture only and copy the
resulting app bundle to /Applications. You can provide a bundle name as
an optional argument.
2023-08-28 13:32:30 -06:00
Max Brunsfeld
04354675ca Remove search dismiss button 2023-08-28 10:35:23 -07:00
Piotr Osiewicz
9aad602af7 chore: Bump memchr to 2.6.0 (#2902)
Fresh off the press, memchr 2.6.0 adds vector search routines for
aarch64. That directly improves our search performance for both text and
regex searches. Per BurntSushi's claims, the simple string searches in
ripgrep got ~2 times faster (more details available in
https://github.com/BurntSushi/memchr/pull/129).

Release Notes:

- N/A
2023-08-28 18:20:10 +02:00
Antonio Scandurra
ccec59337a 📝 2023-08-28 14:46:05 +02:00
Antonio Scandurra
52e1e014ad Allow redoing edits performed by inline assistant after cancelling it 2023-08-28 14:42:52 +02:00
Antonio Scandurra
b9df85e01f Remove todo.md 2023-08-28 14:25:01 +02:00
Antonio Scandurra
8c4d2ccf80 Close inline assist when the associated transaction is undone 2023-08-28 14:23:42 +02:00
Antonio Scandurra
c587cf66ce Remove ellipsis from tooltip 2023-08-28 12:24:01 +02:00
Antonio Scandurra
44f554f489 Merge remote-tracking branch 'origin/main' into ai-refactoring 2023-08-28 12:16:24 +02:00
Antonio Scandurra
1fb7ce0f4a Show icon to toggle inline assist 2023-08-28 12:13:44 +02:00
Antonio Scandurra
d804afcfa9 Don't auto-indent when the assistant starts responding with indentation 2023-08-28 11:57:02 +02:00
Piotr Osiewicz
07b9c6c302 language: Make Buffer::new take an explicit ID (#2900)
See Linear description for the full explanation of the issue. This PR is
mostly a mechanical change, except for the one case where we do pass in
an explicit `next_id` instead of `model_id` in project.rs.

Release Notes:
- Fixed a bug where some results were not reported in project search in
presence of unnamed buffers.
2023-08-28 11:51:50 +02:00
Antonio Scandurra
937aabfdfd Extract a strip_markdown_codeblock function 2023-08-28 11:24:55 +02:00
Kirill Bulatov
81a5043b61 Allow [ and ] symbols in terminal links (#2899)
Deals with https://github.com/zed-industries/community/issues/1865

` ./src/pages/[[...slug]].tsx` is a valid file path in macOs and Linux,
and should be available for cmd-hover-click in terminal.

Release Notes:

- Allow `[` and `]` symbols in terminal links
2023-08-28 11:40:52 +03:00
Kirill Bulatov
506ec01df3 Allow [ and ] symbols in terminal links
` ./src/pages/[[...slug]].tsx` is a valid file path in macOs and Linux,
and should be available for cmd-hover-click in terminal.
2023-08-28 11:19:57 +03:00
Kirill Bulatov
3bfe78b1df Use proper property names for inlay hint resolve capabilities 2023-08-28 00:27:59 +03:00
Kirill Bulatov
805ae1be80 More inlay hover fixes (#2898)
Better handle edge cases around cmd+hover around inlays:
* distinguish between same text anchors' trigger: inlay and text
triggers can have the same anchor, but are different
* forbid cmd+click on inlay that has no label part with location
selected
* properly omit throttled inlays that are outside of the visible range

Release Notes:

- N/A
2023-08-27 19:57:31 +03:00
Kirill Bulatov
38da2a587a Fix the tests 2023-08-27 19:41:15 +03:00
Kirill Bulatov
81e70905bb Do not allow cmd+click in invalid inlay context 2023-08-27 19:12:32 +03:00
Kirill Bulatov
693e91f335 Properly compare previous hover trigger point when hover changes 2023-08-27 18:23:40 +03:00
Kirill Bulatov
dad64edde1 Better highlight hint ranges 2023-08-27 15:14:45 +03:00
Piotr Osiewicz
576849c98a WIP. Fix up some more test scenarios 2023-08-26 23:16:58 +02:00
Kirill Bulatov
5cf51211b6 Use better names, simplify 2023-08-27 00:01:31 +03:00
Piotr Osiewicz
f16105f391 fix up platform namespace in test macro 2023-08-26 23:00:13 +02:00
Piotr Osiewicz
ac329e40dc Allow dead code for is_empty fn 2023-08-26 22:42:55 +02:00
Piotr Osiewicz
a9db466e67 chore: fmt 2023-08-26 22:40:11 +02:00
Piotr Osiewicz
6f32431d33 Merge branch 'main' into gpui_extract_platform 2023-08-26 22:39:57 +02:00
Kirill Bulatov
73937876b6 Properly omit throttled hint queries 2023-08-26 21:12:04 +03:00
Kirill Bulatov
851d74d5e1 Improve inlay hints management (#2897)
Apply a rate limiter to hints scrolling, properly invalidate hint task
cached ranges, properly react to multibuffer events, properly highlight
hints and introduce basic debug logging for hints.

Release Notes:

- N/A
2023-08-26 16:50:12 +03:00
Kirill Bulatov
f8a8b998ce Properly react on excerpts drop 2023-08-26 15:21:45 +03:00
Kirill Bulatov
84284099e2 Properly handle padding when highlighting inlay hints 2023-08-26 15:04:48 +03:00
Kirill Bulatov
2a42a08f46 Invalidate skipped throttled hint fetch tasks' ranges 2023-08-26 14:47:42 +03:00
Kirill Bulatov
9bdf76f445 Properly handle hover-less areas hover 2023-08-26 14:42:20 +03:00
Kirill Bulatov
48659d3b3c Treat multibuffer edit events properly
Miltibuffer emits edit events even if it only got an excerpt
added/removed/etc.
Separate buffer edits and trigger hint invalidation refresh for them
only, also trigger hint new lines refresh on excerpt addition events.
2023-08-26 14:13:24 +03:00
Kirill Bulatov
3fc48fc277 Log LSP inlay hint path 2023-08-26 13:46:55 +03:00
Antonio Scandurra
55bf45d265 Add disabled style for prompt editor after confirming 2023-08-26 12:07:03 +02:00
Kirill Bulatov
e6fb909d89 Limit LSP non-invalidating queries 2023-08-26 13:06:50 +03:00
Antonio Scandurra
658d616b96 Allow multiple inline assistant highlights at once 2023-08-26 11:55:47 +02:00
Kirill Bulatov
2b007930a9 Remove query ranges for failed inlay hint requests 2023-08-26 12:09:12 +03:00
Antonio Scandurra
c8e5c3963b Clear selection when deploying inline assistant 2023-08-26 09:52:01 +02:00
Mikayla Maki
8ddee0e58d Server-sent feature flags (#2894)
This PR adds the wiring to both the server and the client for sending
and receiving individual feature flags, as well as a client side API for
convenient access to these feature flags.

Release Notes:

- N/A
2023-08-25 17:11:33 -07:00
Kirill Bulatov
afdc5012cb Fix inlay hint bugs (#2895)
* https://github.com/zed-industries/zed/pull/2891

Fixes ranges pointing at incorrect positions inside multi-codepoint
characters

* https://github.com/zed-industries/zed/pull/2890

Defers hint links' document URL resolution into buffer up until the
hover & cmd-click is made by the user.

Release Notes:

- N/A
2023-08-26 03:09:19 +03:00
Mikayla
74565ed0b8 Add feature flags handling to the client, rewrite staff mode to a trait extension style 2023-08-25 17:00:53 -07:00
Kirill Bulatov
81c64647e8 Fix the test 2023-08-26 03:00:53 +03:00
Kirill Bulatov
e6c4802488 Properly clip request offsets 2023-08-26 02:54:50 +03:00
Kirill Bulatov
b2b0918790 Consider padding during hint highlight range mapping 2023-08-26 02:45:08 +03:00
Kirill Bulatov
665d86ea73 Defer navigation target buffer opening 2023-08-26 02:45:08 +03:00
Kirill Bulatov
ddd7ab116f Do not convert lsp::Location of hint labels before resolve 2023-08-26 02:45:08 +03:00
Max Brunsfeld
d6ffb68305 Fix bugs in autoscroll with 'fit' strategy (#2893)
This fixes a bug where text moved up and down by one pixel in the buffer
search query editor, while typing.

Release  notes:
* Fixed a bug where editors didn't auto-scroll when typing if all
cursors could not fit within the viewport.
2023-08-25 16:36:33 -07:00
Piotr Osiewicz
2495d6581e Un serialize project search (#2857)
This is the first batch of improvements to current project search. There
are few things we can do better still, but I want to get this out in
next Preview.
Most of the slowness at this point seems to stem from updating UI too
often.

Release Notes:
- Improved project search by making it report results sooner.

---------

Co-authored-by: Julia Risley <julia@zed.dev>
2023-08-26 01:31:52 +02:00
Max Brunsfeld
a3b2c03b17 Fix bugs in autoscroll with 'fit' strategy
* Scroll to the newest cursor if all cursors can't fit in the viewport.
* Refuse to layout an editor less tall than one line height.

Co-authored-by: Nathan <nathan@zed.dev>
2023-08-25 16:13:12 -07:00
Piotr Osiewicz
8342803ba5 WIP test migration 2023-08-26 00:13:57 +02:00
Piotr Osiewicz
d033475565 Fix playground build 2023-08-25 23:43:45 +02:00
Mikayla
6fdf101745 Update database and RPC to provide configured feature flags 2023-08-25 14:34:32 -07:00
Piotr Osiewicz
2a350e91b2 chore: fmt 2023-08-25 23:29:28 +02:00
Piotr Osiewicz
fe17505100 Use gpui_platform in Zed 2023-08-25 23:29:12 +02:00
Piotr Osiewicz
79179e8fff Merge branch 'main' into gpui_extract_platform 2023-08-25 23:28:27 +02:00
Piotr Osiewicz
95b4bd467b chore: fmt 2023-08-25 23:24:23 +02:00
Conrad Irwin
dee1a433dd A few more fixes for wrapped line motions 2023-08-25 14:40:04 -06:00
Conrad Irwin
20aa2a4c54 vim: Fix relative line motion
Before this change up and down were in display co-ordinates, after this
change they are in fold coordinates (which matches the vim behaviour).

To make this work without causing usabliity problems, a bunch of extra
keyboard shortcuts now work:

- vim: `z {o,c}` to open,close a fold
- vim: `z f` to fold current visual selection
- vim: `g {j,k,up,down}` to move up/down a display line
- vim: `g {0,^,$,home,end}` to get to start/end of a display line

Fixes: zed-industries/community#1562
2023-08-25 14:40:04 -06:00
Conrad Irwin
0280d5d010 vim change for wrapped lines 2023-08-25 14:40:03 -06:00
Conrad Irwin
104f5ae9cd relative line numbers (#2887)
- Add relative_line_mode
- vim change for wrapped lines

Release Notes:

- Add a `relative_line_numbers` setting
([#988](https://github.com/zed-industries/community/issues/998)).
2023-08-25 14:17:41 -06:00
Joseph T. Lyons
c1fd648390 Add setting to automatically enable virtual environment (#2882)
This isn't ready to go - I'm opening a PR to ask for some advice. When
activating a python virtual environment, the typical command used is
`source path_to_venv/bin/activate`. The problem is, the activatate
script isn't portable to all shells, so some additional scripts are
bundled in the env, for example, `activate.fish`. We don't have a good
way of knowing what shell we are in, in order to know what script to
run.

Julia gave the alternative of simply activating the virtual environment
while in the zsh context, before the user's custom shell is launched,
which I think does work, but because we activate the virtual environment
before we launch the custom shell, the shell isn't really aware that we
are in the virtual environment and it fails to display the information
in the prompt that is typically shown after activating.

Is there a clean way for us to know for a fact what shell is being ran,
so we know what script to run?

Check out the code comments below for more context.

---


https://github.com/zed-industries/zed/assets/19867440/ddb76aaa-152b-4c93-a513-3cd580b7c40f

I've used Zed to write Python scripts, but working on an actual project
has really magnified where Python dev is falling short. A huge
quality-of-life thing we can do is provide a setting to automaticaly
search for and activate virtual environments when found, when terminals
are created. Manually starting these up in every terminal instance is
such a drag.

A few quirks:
- We don't have a way of knowing if the prompt is ready before we try
run the command, which means we see the text inserted at the top of the
terminal and on the prompt - I dont think this should be a blocker
though.
- If a user has multiple python projects with mutliple virtual
environments, we only detect and activate the first one, since can't
really make any assumptions about which one to activate. I dont think
this should be a blocker either, as I think most users will have a
single project open in Zed.

Release Notes:

- Added a `detect_venv` setting for the terminal. When configured, the
Zed terminal will automatically activate Python virtual environments on
terminal creation.
2023-08-25 15:14:24 -04:00
Joseph T. Lyons
507a5db09c WIP
Co-Authored-By: Mikayla Maki <mikayla.c.maki@gmail.com>
2023-08-25 15:06:31 -04:00
Conrad Irwin
bde67b2b9c Fix merge-conflict 2023-08-25 11:59:16 -06:00
Conrad Irwin
1c945a7521 Fix zed-industries/community#1950 (#2892)
Release Notes:

- vim: fix goal preservation of visual block selections
([#1950](https://github.com/zed-industries/community/issues/1950)).
2023-08-25 11:55:48 -06:00
Conrad Irwin
f18cdcba54 Fix relative line numbers in vim visual mode
In visual mode when your selection ends with a newline we show the
cursor at the end of the previous line (not the start of the current
line). We had only been accounting for this if the cursor was on-screen.
2023-08-25 11:50:41 -06:00
Conrad Irwin
8d5dc266a3 Fix relative line numbers when newest cursor offscreen 2023-08-25 11:50:41 -06:00
Conrad Irwin
790aa5d476 Add relative_line_mode
Co-Authored-By: joseph@zed.dev
2023-08-25 11:50:41 -06:00
Conrad Irwin
1f3e009b32 Fix zed-industries/community#1950 2023-08-25 11:34:07 -06:00
Max Brunsfeld
f798be6e27 Fix rust 1.72 warnings about shadowed glob re-exports 2023-08-25 10:25:21 -07:00
Max Brunsfeld
404f76739c Format let-else statements 2023-08-25 10:11:32 -07:00
Max Brunsfeld
732af201dc Upgrade to rust 1.72 2023-08-25 09:59:16 -07:00
Antonio Scandurra
7c5200e757 More styling 2023-08-25 17:51:13 +02:00
Antonio Scandurra
c4966ff57a Remove warning 2023-08-25 17:35:14 +02:00
Antonio Scandurra
75a6a94e96 Add placeholder text for inline assistant prompts 2023-08-25 17:24:26 +02:00
Kirill Bulatov
44c340b5f2 Properly invalidate the hint cache 2023-08-25 17:33:17 +03:00
Antonio Scandurra
b101a7edff Cancel last inline assist when escaping from the editor 2023-08-25 15:54:52 +02:00
Antonio Scandurra
fdbf4680bb Ensure the inline assistant works with gpt-3.5 2023-08-25 15:39:10 +02:00
Kirill Bulatov
205e101dd0 Query certain editor ranges for inlays with a delay (#2891)
Part of
https://linear.app/zed-industries/issue/Z-2750/investigate-performance-of-collaborating-on-large-files-with-inlay
Fixes
https://linear.app/zed-industries/issue/Z-2824/inlay-hints-affect-code-layout-in-multibuffer

We query hints for visible part of the screen, and two parts above and
below the visible range, of the same range (if applicable, we can be on
the edge of the document).

When rapidly typing, we do not care about the invisible range updates,
yet still query a lot of them + rust-analyzer sends /refresh hint
requests shortly after every modification too, forcing us to re-query.

Instead querying both visible and invisible ranges altogether, wait for
visible range query first and wait add a `400ms` delay afterwards before
querying the invisible ranges.
This allows any /refresh requests or rapid typing to avoid 2 extra
requests, cancelling them before they start.
Visible part of the screen is still queried after every change, without
any debouncing.

Release Notes:

- Delay certain inlay hint requests to reduce general LSP server load
2023-08-25 16:33:21 +03:00
Kirill Bulatov
2b95f0580e Fix the tests 2023-08-25 16:25:43 +03:00
Kirill Bulatov
c10c3e2b54 Only invalidate when doing first, visible range query 2023-08-25 15:16:55 +03:00
Kirill Bulatov
a63e1571dc Defer querying inlay hints for invisible editor ranges
This way, only the visible part gets frequently queried on typing (and
hint /refresh requests that follow), with queries for invisible ranges
cancelled eagerly.
2023-08-25 15:16:55 +03:00
Kirill Bulatov
b50762c821 Handle inlay hints resolve, support dynamic hints (#2890)
Resolves inlay hints on hover, shows hint label parts' tooltips, allows
cmd+click to navigate to the hints' parts with locations,
correspondingly highlight the hints.

Release Notes:

- Support dynamic inlay hints
2023-08-25 15:16:13 +03:00
Kirill Bulatov
0a18aa694f Use stricter inlay range checks to avoid stuck highlights
Often, hint ranges are separated by a single '<` char as in
`Option<Vec<u32>>`. When moving the caret from left to right, avoid
inclusive ranges to faster update the matching hint underline.
2023-08-25 14:46:39 +03:00
Antonio Scandurra
27c90f12f6 Merge remote-tracking branch 'origin/main' into ai-refactoring 2023-08-25 13:37:32 +02:00
Antonio Scandurra
0444b5a775 💄 2023-08-25 13:36:52 +02:00
Kirill Bulatov
8ed280a029 Rebase fixes 2023-08-25 14:30:07 +03:00
Kirill Bulatov
e44516cc6c Add hover tests 2023-08-25 14:26:17 +03:00
Kirill Bulatov
f19c659ed6 Add link_go_to_definition test for inlays 2023-08-25 14:26:17 +03:00
Kirill Bulatov
abd2d012b1 Properly binary search cached inlay hints 2023-08-25 14:26:17 +03:00
Kirill Bulatov
3c55c933d4 Be more lenient with hint resolution, always return some hint 2023-08-25 14:26:17 +03:00
Kirill Bulatov
852427e87b Use inlay highlights in randomized tests 2023-08-25 14:26:17 +03:00
Kirill Bulatov
7cd60d6afb Simplify and restore client resolve capabilities 2023-08-25 14:26:17 +03:00
Kirill Bulatov
dcf570bb03 Fix resolve status conversion 2023-08-25 14:26:17 +03:00
Kirill Bulatov
bcaff0a18a Propagate inlay background highlights to data storage 2023-08-25 14:26:17 +03:00
Kirill Bulatov
4b78678923 Prepare background highlights for inlay highlights 2023-08-25 14:26:17 +03:00
Kirill Bulatov
12ffbe54fb Unify text and inlay highlights 2023-08-25 14:26:17 +03:00
Kirill Bulatov
420f8b7b15 Prepare for inlay and text highlight unification 2023-08-25 14:26:17 +03:00
Kirill Bulatov
4cc9f2f525 Highlight inlay hint parts on cmd-hover
Co-Authored-By: Antonio <antonio@zed.dev>
2023-08-25 14:26:17 +03:00
Kirill Bulatov
f8874a726c Attempt to highlight inlays 2023-08-25 14:26:17 +03:00
Kirill Bulatov
6c5761d05b Pass inlay highlight information 2023-08-25 14:26:17 +03:00
Kirill Bulatov
477fc865f5 Properly resolve inlay label parts' locations and buffers 2023-08-25 14:26:17 +03:00
Kirill Bulatov
7eab18ec89 Pass inlay go to definition data 2023-08-25 14:26:17 +03:00
Kirill Bulatov
ac86bbac75 Prepare for hover functionality refactoring 2023-08-25 14:26:17 +03:00
Kirill Bulatov
80e8714241 Send inlay hint resolve requests 2023-08-25 14:26:17 +03:00
Kirill Bulatov
3434990b70 Store inlay hint resolve data 2023-08-25 14:26:17 +03:00
Kirill Bulatov
e4b78e322e Revert "Strip off inlay hints data that should be resolved"
Without holding all hints in host's cache, this is impossile.
Currenly, we keep hint caches separate and isolated, so this will not
work when we actually resolve.
2023-08-25 14:26:17 +03:00
Kirill Bulatov
d1cb0b3c27 Properly detect hovered inlay hint label part 2023-08-25 14:26:17 +03:00
Kirill Bulatov
d34491e822 Draft inlay hint part hover detection 2023-08-25 14:26:17 +03:00
Antonio Scandurra
971c833e80 Improve background highlighting of inline assists 2023-08-25 12:35:36 +02:00
Antonio Scandurra
144f5c5d41 Use a left bias for the prompt editor 2023-08-25 12:25:43 +02:00
Antonio Scandurra
66a496edd7 Allow generating code without editing it 2023-08-25 12:16:28 +02:00
Antonio Scandurra
c1bd035875 Rework inline assistant 2023-08-25 11:39:27 +02:00
Kyle Caverly
bc7e9088fe Eager Semantic Indexing Queue (#2886)
Optimization to the Semantic Indexing Engine.

We've transitioned from a framework in which the entire project tree is
walked at each index command, to an eager queuing method, in which an
initial queue of outstanding indexing work is initialized upon workspace
creation, and then subscriptions are leveraged for file change events to
continually keep an updated view on outstanding work.

This optimization contributes towards quicker user feedback, when
initializing or using Semantic Search functionality. It also opens the
doors towards better transparency across the system on outstanding
indexing work.

Release Notes:

- Refactored index operation queue to an eager queuing framework.
- Moved semantic search initialization to workspace creation.
- Adjusted rate limiting strategy on api delays to reduce time spent
waiting for rate limits.
2023-08-25 11:03:05 +02:00
KCaverly
ee97bc54cf cleaned up warnings 2023-08-25 10:38:01 +02:00
Joseph T. Lyons
f1586c9923 Automatically enable project search filters when using Search Inside (#2889)
Now that the filters are hidden behind a toggle-able setting, running
the `Search Inside` action from the project panel feels a bit weird,
since the filter being used is hidden. This PR automatically opens that
filter section after running a `Search Inside` action.

Release Notes:

- N/A
2023-08-25 02:39:44 -04:00
Joseph T. Lyons
8288e5591d Automatically enable project search filters when using Search Inside 2023-08-25 02:21:07 -04:00
Joseph T. Lyons
0801e5e437 Merge branch 'main' into add-setting-to-automatically-enable-virtual-environment 2023-08-25 01:50:57 -04:00
Joseph T. Lyons
9fe580acb6 WIP 2023-08-25 01:50:54 -04:00
Mikayla Maki
fbc7b37b2f Introduce channel notes (#2880)
![Screen Shot 2023-08-24 at 5 26 53
PM](https://github.com/zed-industries/zed/assets/326587/3e84c5a6-1aaf-4335-a880-4c32eb83332d)

### Todo

* [x] Snapshot channel buffers when everyone closes the buffer
* [x] Ensure that users who are in both a project and a channel note
have the same color in both places
* [x] Allow following project collaborators into channel notes
* [x] Expose notes for the current channel under "Current Call" section
of the collaboration panel
* [x] Offline state for the channel notes view
* [x] Make the channel context menu accessible to all members (to expose
the notes)
* [x] Wire in view and Item method overrides

Release Notes:

- N/A
2023-08-24 18:01:28 -07:00
Max Brunsfeld
a95dcfa8bc Make channel notes view searchable and navigable via pane history
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 17:18:18 -07:00
Max Brunsfeld
7b6c0c539c Show non-admin context menu items for all channel members
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 17:17:20 -07:00
Max Brunsfeld
c7c220309d Avoid creating redundant snapshots of channel notes buffers
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 16:55:45 -07:00
Max Brunsfeld
358a20494c Make channel notes read-only when disconnected
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 16:50:13 -07:00
Max Brunsfeld
a327320f7d Show channel notes in current call section of collab panel
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 15:00:54 -07:00
Max Brunsfeld
1ae54ca620 Dedup channel views
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 14:29:04 -07:00
Max Brunsfeld
5888e7b214 Dedup channel buffers 2023-08-24 13:40:44 -07:00
Max Brunsfeld
90f22cb0d2 Replicate editor state when following into channel notes
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 12:36:01 -07:00
Max Brunsfeld
24141c2f16 Ensure collaborators cursor colors are the same in channel buffers as in projects
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 11:31:41 -07:00
Max Brunsfeld
3268cce41a Fix error in update_channel_buffer when there are no operations to store
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-24 11:26:22 -07:00
Mikayla
199be8241c Add following into channel notes
co-authored-by: max <max@zed.dev>
2023-08-24 11:25:20 -07:00
Antonio Scandurra
b6035ee6a6 WIP 2023-08-24 20:00:25 +02:00
Conrad Irwin
3b6794fe36 vim: Fix linewise copy of last line with no trailing newline (#2885)
Along the way, delete the VimBindingTestContext by updating the
visual tests to no-longer need it.


Release Notes:

- vim: Fix `y` when on the last line of a file with no trailing newline.
2023-08-24 10:45:46 -06:00
KCaverly
131950f670 add handling for Added file events to semantic index 2023-08-24 18:40:08 +02:00
Antonio Scandurra
cb4b816d0e Add todo for modal assistant 2023-08-24 17:33:59 +02:00
Antonio Scandurra
805e44915c WIP 2023-08-24 17:23:12 +02:00
Antonio Scandurra
cbf7160054 Improve scoring 2023-08-24 16:32:55 +02:00
Antonio Scandurra
c1d9b37dbc Move to an inline refactoring prompt 2023-08-24 15:46:18 +02:00
Antonio Scandurra
2468506189 Always clear refactoring text highlights, even if an error occurs 2023-08-24 14:29:05 +02:00
Antonio Scandurra
71a5964c18 Rename merge_transaction_into to merge_transactions 2023-08-24 14:26:42 +02:00
KCaverly
a892a51ec3 update initialize project call to accomodate for test scenarios
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-24 13:46:43 +02:00
KCaverly
0b204bfdc8 reindex semantic index when search project pane is reactivated in semantic mode
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-24 13:40:04 +02:00
KCaverly
a1519e4c38 move semantic search project intialization to a subscribe event for workspace created
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-24 13:14:19 +02:00
KCaverly
e8e7b294d8 add delete files operation for remaining files in database not included in current worktree
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-24 12:49:20 +02:00
Antonio Scandurra
9674b03855 Make scoring more precise by using floats when diffing AI refactors 2023-08-24 12:45:44 +02:00
Antonio Scandurra
481bcbf204 Normalize indentation when refactoring 2023-08-24 12:45:21 +02:00
KCaverly
afe0e74868 remove worktree_file_mtimes in state as it is no longer used
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-24 12:42:41 +02:00
KCaverly
aa07872a24 accomodate for duplicate entries in indexing queue
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-24 12:36:43 +02:00
KCaverly
3f9f742530 update rate limiting embeddings strategy to delay less 2023-08-24 11:45:52 +02:00
Antonio Scandurra
985397b55c 📝 2023-08-24 09:52:07 +02:00
Conrad Irwin
e4794e3134 vim: Fix linewise copy of last line with no trailing newline
Along the way, delete the VimBindingTestContext by updating the
visual tests to no-longer need it.
2023-08-24 00:00:12 -06:00
Max Brunsfeld
7e83138805 Start work on showing consistent replica ids for channel buffers
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-23 18:37:01 -07:00
Max Brunsfeld
11ef5e2740 Simplify buffer_operations schema
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-23 18:37:01 -07:00
Max Brunsfeld
1d08f44e70 Snapshot channel notes buffers when everyone leaves
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-23 18:37:01 -07:00
Mikayla
4eff8ad186 Add channel notes view
co-authored-by: Max <max@zed.dev>
2023-08-23 18:37:00 -07:00
Mikayla
5a0315c4d5 Achieve end to end channel buffer synchronization
co-authored-by: max <max@zed.dev>
2023-08-23 18:34:43 -07:00
Max Brunsfeld
95ea664725 WIP 2023-08-23 18:34:43 -07:00
Max Brunsfeld
71611ee7a2 Get join_buffer_for_channel compiling
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-23 18:34:43 -07:00
Mikayla
364ed1f840 WIP: pass synchronize channel buffers integration test 2023-08-23 18:34:43 -07:00
Mikayla
a7a4e2e369 Add buffer integration test
Rearrange channel crate structure
Get channel buffer from database

co-authored-by: Max <max@zed.dev>
2023-08-23 18:34:43 -07:00
Max Brunsfeld
ff5035ea37 Start work on storing channel buffers 2023-08-23 18:34:43 -07:00
Max Brunsfeld
29e43384f0 Simplify macro for running a test with both databases 2023-08-23 18:34:43 -07:00
Mikayla Maki
26c3312049 Fix cursor and hover styles changing when dragging the mouse (#2884)
Since the resizing rework on docks, I noticed a lot of cursor flickering
when dragging, that drags trigger hover states in underlying elements
instead of being ignored, and that clicking and dragging off a button
can leave that button's click style active. This PR fixes all three
bugs.

Release Notes:

- Fixed several styling bugs related to dragging the mouse cursor
2023-08-23 18:29:29 -07:00
Mikayla
ff75d1663b Fix stuck click styling when dragging off of a button 2023-08-23 18:22:48 -07:00
Mikayla
4d2f5a8e04 Fix cursor and hover styles changing when dragging the mouse 2023-08-23 18:08:33 -07:00
Mikayla Maki
2ab8ebaaf7 Add disclosable component (#2868)
This PR adds a disclosable component, related wiring, and uses it to
implement the collaboration panel's disclosure of subchannels. It also
adds a component test page to make style development easier, and
refactors components into v0.2, safe styles (as described in [TWAZ
#16](https://zed.dev/blog/this-week-at-zed-16))

Release Notes:

- N/A
2023-08-23 16:37:34 -07:00
Mikayla
707ca34f19 Merge branch 'main' into disclosable-component 2023-08-23 16:30:27 -07:00
Mikayla
2a182b6a7b Tune styles and disclosable elements 2023-08-23 16:25:40 -07:00
Piotr Osiewicz
1320fadc30 Bump rust embed (#2883)
This is a follow-up to a recent patch I've submitted to this crate to
improve compile time and runtime (in older versions file lookup was
essentially O(n) with respect to path count, now it's O(log n))

Release Notes:

- N/A
2023-08-24 00:16:35 +02:00
KCaverly
b72c4c576b catchup with main 2023-08-23 22:31:39 +02:00
KCaverly
e42b9e910e fix async calls on project updated entries to ensure that all files are updating appropriately 2023-08-23 22:28:30 +02:00
Joseph T. Lyons
6c45be2dc4 Add docker system prune command
This will hopefully keep the system drive cleaned up so we don't run issues with not enough disk space.
2023-08-23 14:54:39 -04:00
Joseph T. Lyons
af21546a43 collab 0.18.0 2023-08-23 14:19:24 -04:00
Antonio Scandurra
f22acb602e Apply a score boost when consecutive triplets of characters match 2023-08-23 19:21:44 +02:00
Joseph T. Lyons
72f0efb7b7 v0.102.x dev 2023-08-23 12:49:13 -04:00
Antonio Scandurra
301a12923f Merge transactions into the original assistant transaction
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Kyle Caverly <kyle@zed.dev>
2023-08-23 18:22:17 +02:00
Antonio Scandurra
a69461dba2 Don't score whitespace matches
Co-Authored-By: Kyle Caverly <kyle@zed.dev>
2023-08-23 17:19:07 +02:00
Antonio Scandurra
e4f49746e1 Group modal assistant edits into the same transaction
Co-Authored-By: Kyle Caverly <kyle@zed.dev>
2023-08-23 17:09:15 +02:00
Nathan Sobo
1bc4f22373 Rework element system, phase 1 (#2881)
This is a deep cut. There's still more work to do until we start
building UI with this. I've approached this as additively as possible,
but I've made a few changes to the rest of the code that I think would
be good to upstream before proceeding too much further.

Most of the interesting pieces are in gpui/playground, which is a
standalone binary that opens a single window and renders a new kind of
element. The layout of these new elements is provided by the taffy
layout engine crate, which conforms to web conventions. The idea is that
playground is relatively cheap to build and work on. As concepts
coalesce in playground, we can drop them into gpui and start
transitioning.
2023-08-23 08:26:38 -06:00
Antonio Scandurra
d3238441ce 🎨 2023-08-23 16:13:37 +02:00
KCaverly
09fd99b1e3 moved semantic_index project intialization to queue and channel method 2023-08-23 15:09:15 +02:00
Antonio Scandurra
2e1a4b2591 Adjust scoring 2023-08-23 13:26:30 +02:00
Antonio Scandurra
aa6d6582fd Add basic styling 2023-08-23 13:09:02 +02:00
Antonio Scandurra
a2671a29a0 Highlight text when the diff is the same 2023-08-23 10:28:43 +02:00
Joseph T. Lyons
7b170304df Shorten setting name 2023-08-23 04:07:10 -04:00
Joseph T. Lyons
711f156308 WIP 2023-08-23 04:04:36 -04:00
Antonio Scandurra
a93583065b Delete unused imports 2023-08-23 09:59:07 +02:00
Antonio Scandurra
c2935056e8 Support multi-byte characters in diff 2023-08-23 09:46:33 +02:00
Antonio Scandurra
a9871a7a70 Add randomized tests for incremental diff 2023-08-23 09:09:01 +02:00
Joseph T. Lyons
b77e661c9f Update textmate.json 2023-08-23 01:31:01 -04:00
Nathan Sobo
cd72a42bd4 Mark new crates as publish = false 2023-08-22 20:47:05 -06:00
Conrad Irwin
b0815bd13e vim: Rewrite paste (#2878)
A complete overhaul of the way vim did paste. This ended up being more
involved than I expected because of the variety of different behaviors
that vim exhibits when copying/pasting between various modes.

Release Notes:

- vim: support P for paste before
([#1869](https://github.com/zed-industries/community/issues/1869)).
- vim: support P in visual modes for paste without overriding clipboard
- vim: fix position when using `p` on text copied outside zed
([#469](https://github.com/zed-industries/community/issues/469)).
- vim: fix indentation when using `p` on text copied from zed
([#1015](https://github.com/zed-industries/community/issues/1015)).
- all: Separate copied multi-selections by `\n`
2023-08-22 20:21:58 -06:00
Piotr Osiewicz
978d074b1d WIP 2023-08-23 01:33:19 +02:00
Piotr Osiewicz
d0f287772b chore: fmt 2023-08-23 01:13:04 +02:00
Nathan Sobo
d375f7992d Merge branch 'main' into divs 2023-08-22 16:35:56 -06:00
Conrad Irwin
27ba77b16f Ensure editor clipboard contains \n too 2023-08-22 13:52:04 -06:00
Conrad Irwin
215a922680 vim: Dismiss menu in insert mode with escape (#2879)
Release Notes:

- vim: Fix escape to dismiss suggestions in insert mode.
2023-08-22 13:38:12 -06:00
Joseph T. Lyons
471810a3c2 WIP
Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com>
2023-08-22 15:29:25 -04:00
Conrad Irwin
ef077796f8 vim: Dismiss menu in insert mode with escape 2023-08-22 13:28:50 -06:00
Conrad Irwin
33d7fe02ee Rewrite paste
- vim: support P for paste before
- vim: support P in visual mode for paste without overriding clipboard
- vim: fix position when using `p` on text copied outside zed
- vim: fix indentation when using `p` on text copied from zed
2023-08-22 13:27:57 -06:00
Conrad Irwin
31db5e4f62 Vim z 2819 (#2875)
Add `$` and `#` and a smoke test for vim-mode respecting the
configuration.
2023-08-22 11:38:06 -06:00
Nathan Sobo
733df38f9b Checkpoint
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-08-22 11:34:06 -06:00
Antonio Scandurra
3a511db5c9 🎨 2023-08-22 18:41:22 +02:00
Piotr Osiewicz
ccb3f6748c chore: fmt 2023-08-22 18:14:07 +02:00
Piotr Osiewicz
ccb9b5d278 Query char_kind for completion triggers.
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2023-08-22 18:12:05 +02:00
Joseph T. Lyons
d3c7f03fc4 Add default bindings for new file and directory (#2877)
Release Notes:

- Added default key bindings for `NewFile` and `NewDirectory` in the
project panel (bound to `cmd-n` and `alt-cmd-n`, respectively).
2023-08-22 11:48:02 -04:00
Joseph T. Lyons
ebcb395f1f Add default bindings for new file and directory 2023-08-22 11:33:40 -04:00
Nathan Sobo
3921278319 Add more style helpers 2023-08-22 09:16:05 -06:00
Nathan Sobo
53679ce045 Checkpoint 2023-08-22 09:07:45 -06:00
KCaverly
328b7e523c reorganized to stop the race 2023-08-22 15:01:21 +02:00
KCaverly
aabdfa210f working on initialization + index breakup 2023-08-22 14:45:27 +02:00
Antonio Scandurra
69b6967838 Integrate the new diff algorithm into the modal assistant 2023-08-22 13:59:45 +02:00
Antonio Scandurra
1ae5a909cd Start on a custom diff implementation 2023-08-22 12:07:41 +02:00
KCaverly
ced2b2aec3 reworked ProjectState to include additional context 2023-08-22 11:58:48 +02:00
Piotr Osiewicz
d27cebd977 Z 2819 (#2872)
This PR adds new config option to language config called
`word_boundaries` that controls which characters should be recognised as
word boundary for a given language. This will improve our UX for
languages such as PHP and Tailwind.

Release Notes:

- Improved completions for PHP
[#1820](https://github.com/zed-industries/community/issues/1820)

---------

Co-authored-by: Julia Risley <julia@zed.dev>
2023-08-22 10:35:20 +02:00
Joseph T. Lyons
a836f9c23d Add a default_open_ai_model setting for the assistant (#2876)
[This PR has been sitting around for a
bit](https://github.com/zed-industries/zed/pull/2845). I received a bit
of mixed opinions from the team on how this setting should work, if it
should use the full model names or some simpler form of it, etc. I went
ahead and made the decision to do the following:

- Use the full model names in settings - ex: `gpt-4-0613`
- Default to `gpt-4-0613` when no setting is present
- Save the full model names in the conversation history files (this is
how it was prior) - ex: `gpt-4-0613`
- Display the shortened model names in the assistant - ex: `gpt-4`
- Not worry about adding an option to add custom models (can add in a
follow-up PR)
- Not query what models are available to the user via their api key (can
add in a follow-up PR)

Release Notes:

- Added a `default_open_ai_model` setting for the assistant (defaults to
`gpt-4-0613`).

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-22 02:55:27 -04:00
Antonio Scandurra
5453553cfa WIP 2023-08-22 08:16:22 +02:00
Conrad Irwin
168a213a44 Add test for word characters in vim 2023-08-21 22:37:14 -06:00
Conrad Irwin
14fa996cdc Add # and $ for js 2023-08-21 22:36:58 -06:00
Piotr Osiewicz
cfecbc5522 Start moving tests 2023-08-22 01:06:06 +02:00
Piotr Osiewicz
42b0c5dfdd Remove comment;for real now 2023-08-22 01:04:55 +02:00
Piotr Osiewicz
bca2d02a61 Revert "Remove dead comment"
This reverts commit 046759a366.
2023-08-22 01:04:15 +02:00
Piotr Osiewicz
344a09a4f8 Rename word_boundaries to word_characters 2023-08-22 01:02:25 +02:00
Piotr Osiewicz
046759a366 Remove dead comment 2023-08-22 01:01:27 +02:00
Piotr Osiewicz
94cf1c3336 Use gpui_platform in test_support 2023-08-22 00:50:05 +02:00
Piotr Osiewicz
f3b76e0571 Add gpui_platform crate for a simple dispatch to implementation for current platform 2023-08-22 00:37:19 +02:00
Piotr Osiewicz
b4cba64fd6 Start fixing up platform tests 2023-08-22 00:30:52 +02:00
Nathan Sobo
ff7b25c538 Checkpoint 2023-08-21 16:14:59 -06:00
Piotr Osiewicz
7516e91a56 Extract gpui_mac out of gpui 2023-08-21 22:29:49 +02:00
Joseph T. Lyons
3a13795021 Add channel id to call events (#2873)
Release Notes:

- N/A
2023-08-21 14:11:35 -04:00
Joseph T. Lyons
f66e6863fa Add channel id to call events
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
2023-08-21 13:50:04 -04:00
Piotr Osiewicz
6137d88a54 Merge branch 'main' into Z-2819 2023-08-21 17:41:27 +02:00
Piotr Osiewicz
00caad2f17 ..and use it in PHP language config 2023-08-21 17:41:05 +02:00
Piotr Osiewicz
ab5bd0ac5a Use new char_kind (parameterized by language) 2023-08-21 17:39:38 +02:00
Piotr Osiewicz
3e8522b5f2 WIP: Saved state from Friday.
Co-authored-by: Julia Risley <julia@zed.dev>
2023-08-21 17:09:03 +02:00
KCaverly
3d89cd10a4 added sha1 encoding for each document 2023-08-21 16:35:57 +02:00
Antonio Scandurra
5b9d48d723 Avoid diffing when the length is too small 2023-08-21 15:53:43 +02:00
Antonio Scandurra
42f02eb4e7 Incrementally diff input coming from GPT 2023-08-21 15:13:26 +02:00
Kyle Caverly
bbe6d3b261 Semantic index rough edges (#2871)
Small PR aimed at improving a few edge cases for semantic indexing large
projects.

Release Notes (Preview-only).

- Batched large files with a number of documents greater than
EMBEDDINGS_BATCH_SIZE.
- Ensured that the job handle counting mechanism is consistent with
inner file batching.
- Updated tab content names for semantic search, to match text/regex
searches.
2023-08-21 13:43:08 +02:00
Piotr Osiewicz
c68b518aec chore: fix compiler warning
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-21 13:31:45 +02:00
Piotr Osiewicz
67a48ec106 project_search: use search history's current entry as a tab name.
Previously the tab name for Semantic Search was not updated, as we didn't have an active query to go off of

Co-authored-by: Kyle <kyle@zed.dev>
2023-08-21 13:30:32 +02:00
Piotr Osiewicz
61041b0cd1 Do not attempt to reindex a file if previous attempts have failed.
Add doc comment to JobHandle

Co-authored-by: Kyle <kyle@zed.dev>
2023-08-21 13:23:11 +02:00
Piotr Osiewicz
1a88444f2f Increment job counter on JobClient::new
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-21 13:00:56 +02:00
KCaverly
def215af9f update job handle to ensure file count is consistent
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-21 12:47:43 +02:00
Piotr Osiewicz
267c0b9a3f chore: Update Cargo.lock following rodio PR (#2870)
That's merely a follow-up to a previous PR.
Release Notes:
- N/A
2023-08-21 11:33:06 +02:00
KCaverly
1cae4758cc manage for edge case in which file documents are larger than the allowable limit 2023-08-21 11:29:45 +02:00
Piotr Osiewicz
9ea0ed3e95 chore: remove unused features from rodio crate. (#2869)
This reduces our dep count by 1% at the expense of not supporting
playback of .flac, .mp3 and .vorbis formats. We only use .wav anyways.


Release Notes:
- N/A
2023-08-21 11:07:31 +02:00
Nathan Sobo
f4d8763d2b WIP 2023-08-19 23:16:20 -06:00
Nathan Sobo
6663d3f8eb Checkpoint 2023-08-19 22:33:28 -06:00
Nathan Sobo
afff46b335 Checkpoint 2023-08-19 21:24:28 -06:00
Nathan Sobo
2c6f692c56 Checkpoint 2023-08-19 20:29:55 -06:00
Nathan Sobo
0747131bd4 Compiling checkpoint 2023-08-19 19:51:22 -06:00
Mikayla
bfd3e53dcd Implement component test page 2023-08-19 16:29:24 -07:00
Mikayla
e946b0a2ec Finish building out adapters and names
Document core traits
Add start for a component storybook
2023-08-19 14:40:05 -07:00
Mikayla
bd3ab82dac Add disclosable components into channels
Rename components to more closely match their purpose
2023-08-19 05:18:53 -07:00
Mikayla Maki
b7e03507c2 Update components.rs 2023-08-19 04:30:49 -07:00
Nathan Sobo
76993f6b57 WIP: Trying to find a composable approach to styling that plays nice with layout engine 2023-08-18 20:08:29 -06:00
Mikayla
2d37128693 Actually get it compiling, omg 2023-08-18 19:02:27 -07:00
Mikayla
3178adefde WIP: Add disclosable channels 2023-08-18 18:11:05 -07:00
Mikayla Maki
29c339e3b4 Revert "Remove semantic search UI" (#2865)
This reverts commit c0f042b39a, where I
deleted the semantic-search related UI code.

Apologies to @KCaverly for the misunderstanding

Release Notes:

- N/A
2023-08-18 18:05:19 -07:00
Mikayla Maki
15f91f38f6 Remove potential osascript hijacking attack (#2867)
Fixes
https://linear.app/zed-industries/issue/Z-2818/security-vulnerability-dylib-injection

Release Notes:

- Fixed a potential local code-injection if a user installs the Zed CLI
for the first time with a hijacked `osascript` in their path.
2023-08-18 18:00:39 -07:00
Mikayla
d22caf5a19 Fix erorr in revert 2023-08-18 17:59:06 -07:00
Mikayla
5a356a4710 Remove potential osascript hijacking 2023-08-18 17:40:08 -07:00
Max Brunsfeld
ef9686c988 Reorganize the structure of the collab crate's db module (#2866)
This PR just moves some code around, with the goal of making it easier
to find things in the `collab::db` module. That has become a large
module. Previously, most of the logic lived in one giant `impl Database`
item in `db.rs`.

I broke up this `impl` into several different `impl` blocks, grouped by
topic, each in a different file in a folder called `queries`.

I also pulled out the macro-generated id types into their own file,
moved the `TestDb` struct into its own file, and moved the `sea_orm`
entity declarations into a folder called `tables`.

New folder structure:

```
db
├── db_tests.rs
├── ids.rs
├── queries
│   ├── access_tokens.rs
│   ├── channels.rs
│   ├── contacts.rs
│   ├── projects.rs
│   ├── rooms.rs
│   ├── servers.rs
│   ├── signups.rs
│   └── users.rs
├── queries.rs
├── tables
│   ├── access_token.rs
│   ├── channel.rs
│   ├── channel_member.rs
│   ├── channel_path.rs
│   ├── contact.rs
│   ├── follower.rs
│   ├── language_server.rs
│   ├── project.rs
│   ├── project_collaborator.rs
│   ├── room.rs
│   ├── room_participant.rs
│   ├── server.rs
│   ├── signup.rs
│   ├── user.rs
│   ├── worktree.rs
│   ├── worktree_diagnostic_summary.rs
│   ├── worktree_entry.rs
│   ├── worktree_repository.rs
│   ├── worktree_repository_statuses.rs
│   └── worktree_settings_file.rs
├── tables.rs
└── test_db.rs
```

Release Notes:

- N/A
2023-08-18 17:29:15 -07:00
Max Brunsfeld
b35aaf144b Break up Database impl into multiple files, organized by topic 2023-08-18 17:03:34 -07:00
Max Brunsfeld
e964137d79 Reorganize source files of collab::db
* Move all sea_orm tables into a 'tables' module
* Move TestDb into its own file
* Move id types into their own module
2023-08-18 16:23:33 -07:00
Mikayla
48553d7c8f Revert "Remove semantic search UI"
This reverts commit c0f042b39a.
2023-08-18 16:18:28 -07:00
Nathan Sobo
eec39dc23c WIP 2023-08-18 17:14:29 -06:00
Mikayla Maki
cb55204e22 Move the collab panel to the left by default (#2864)
Increase the indent size on channels
Switch font UI mono

Release Notes:

- Switch the collaboration panel font to Zed's sans-mono (preview only)
- Switch the default dock side to the left (preview-only)
- Increase the indent size on the channels panel (preview-only)
2023-08-18 16:06:10 -07:00
Mikayla Maki
64cbfe088a Update bump-version.sh 2023-08-18 15:52:21 -07:00
Mikayla Maki
24fbea1557 Update collab_panel.rs 2023-08-18 15:34:35 -07:00
Mikayla
4c15f26eba Finish rename correctly 2023-08-18 15:26:36 -07:00
Mikayla
4c3227ce2a Fix example compile erorr 2023-08-18 15:17:24 -07:00
Mikayla
84f35d4e6b Add pub 2023-08-18 15:12:56 -07:00
Mikayla
8ef671d7a1 Move the collab panel to the left by default
Increase the indent size on channels
Switch font UI mono
2023-08-18 15:04:22 -07:00
Conrad Irwin
f0ebbd01e6 Don't show vim mode when disabled (#2863)
Fixes vim's mode indicator showing up when vim is disabled.
2023-08-18 16:00:46 -06:00
Conrad Irwin
3ab3042e95 Don't show vim mode when disabled 2023-08-18 15:50:34 -06:00
Max Brunsfeld
e48bb1853c Fix failure to detect string scope, and disable quote autoclose in Rust (#2862)
This regressed in https://github.com/zed-industries/zed/pull/2723
Fixes https://github.com/zed-industries/community/issues/684
Fixes
https://linear.app/zed-industries/issue/Z-1516/avoid-double-quotes-when-closing-a-string

Release Notes:

- Fixed a bug where auto-closing quotation marks were inserted
incorrectly in Rust.
2023-08-18 14:39:44 -07:00
Max Brunsfeld
1c46749ad7 Fix regression in Buffer::language_scope_at
Co-authored-by: Julia <julia@zed.dev>
2023-08-18 12:58:09 -07:00
Joseph T. Lyons
98186fc016 Add more file icons (#2861)
Release Notes:

- Added file icons for Python and Elixir
2023-08-18 15:27:04 -04:00
Max Brunsfeld
08429169e2 Upgrade Tree-sitter for error recovery bug fix (#2860)
Bumps Tree-sitter for
https://github.com/tree-sitter/tree-sitter/pull/2526.

Release Notes:

- Fixed a bug where small syntax errors would mess up syntax
highlighting more than necessary when editing certain languages, like
Scheme and PHP.
2023-08-18 12:01:53 -07:00
Joseph T. Lyons
e26d6d7a49 Add elixir icons 2023-08-18 14:54:53 -04:00
Conrad Irwin
9dab6b6a20 vim: Fix cursor adjustment on scroll (#2859)
Fixes: zed-industries/community#1929

Also preserves visual modes correctly.

[[PR Description]]

Release Notes:

- vim: Fix scroll offset on `ctrl-{e,y,u,b}`
([#1929](https://github.com/zed-industries/community/issues/1929)).
2023-08-18 12:32:47 -06:00
Max Brunsfeld
09fcacdfd1 Upgrade Tree-sitter for error recovery bug fix 2023-08-18 11:31:40 -07:00
Conrad Irwin
93461d366c Fix <Enter> to go to non-whitespace character
Fixes: zed-industries/community#831
2023-08-18 12:24:39 -06:00
Joseph T. Lyons
ab73375a61 Alphabetize list 2023-08-18 14:07:55 -04:00
Joseph T. Lyons
ffdca77124 Add python file icon 2023-08-18 14:07:17 -04:00
Conrad Irwin
5029587e3b Add ctrl-q as an alias for ctrl-v
Fixes: zed-industries/community#871
2023-08-18 11:39:48 -06:00
Conrad Irwin
e3c0e93e46 Fix cursor adjustment on scroll
Fixes: zed-industries/community#1929

Also preserves visual modes correctly.
2023-08-18 11:23:43 -06:00
Antonio Scandurra
3ad7f528cb Start on a refactoring assistant 2023-08-18 17:58:22 +02:00
Conrad Irwin
d1aa82bb48 vim visual block (#2849)
Release notes:

- vim: add Visual Block mode
([#984](https://github.com/zed-industries/community/issues/984)),
([#1415](https://github.com/zed-industries/community/issues/1415)).
- vim: add support for `a<object>` and `i<object>` in visual modes
- vim: fix scroll shortcuts (`ctrl-{f,b,d,u,e,y}`) in visual modes
- allow `shift-enter` to type a newline.
2023-08-18 09:55:40 -06:00
Piotr Osiewicz
273a8b4368 editor: Recognize '$' as a Word character.
This fixes PHP variable completion. When we were querying for completions, PHP LS returned proper matches for variables which we filtered out as our query did not include a `$` character.

Z-2819
2023-08-18 17:25:20 +02:00
Kirill Bulatov
760c4918cb Respect completion resolve server capabilities (#2858)
Only query for additional edits if the server supports it

Release Notes:

- N/A
2023-08-18 17:50:37 +03:00
Kirill Bulatov
269dad5a9c Respect completion resolve server capabilities
Only query for additional edits if the server supports it

Co-Authored-By: Julia Risley <julia@zed.dev>
2023-08-18 16:55:47 +03:00
Kyle Caverly
8451e7eb7e Project search design (#2834)
TODO before merging: 
- [x] Re-run project search when options (case, word, regex) change

/cc @PixelJanitor 
Release Notes:
- Revamped project & buffer search UI.
- Added "Cycle Mode" command for search
2023-08-18 14:38:01 +02:00
Nathan Sobo
b910c85f7f Still need to wire up MouseMove with the new regions 2023-08-18 02:23:06 -06:00
Nathan Sobo
3709eff34b Compiling 2023-08-18 01:59:21 -06:00
Nathan Sobo
9b74dc196e Introduce Refinement trait and derive macro 2023-08-18 01:03:46 -06:00
Mikayla
66e94aa199 Make search re-query eagerly when changing modes
Fix a bug where focus could be lost when clearing the search results
2023-08-17 17:53:58 -07:00
Nathan Sobo
19ccb19c96 Compiling 2023-08-17 18:37:54 -06:00
Mikayla
c0f042b39a Remove semantic search UI 2023-08-17 17:28:09 -07:00
Mikayla
21fa6090b8 Add action button component for rendering the search options 2023-08-17 17:28:09 -07:00
Max Brunsfeld
802911d742 Fix AppKit screen coordinate conversion leading to wrong window bounds (#2856)
Fixes
https://linear.app/zed-industries/issue/Z-1510/join-project-notification-takes-up-full-screen-on-a-second-monitor

There were multiple mistakes in the positioning of Zed's notification
windows, one of which lead to the notifications taking up the full
screen on secondary displays 😱 .
* Wrong sign for the vertical padding (moving the window *upward*
instead of downward)
* Using the screen's full frame instead of its "visible frame" (which
accounts for app menu bar)
* Wrong coordinate translation between our coordinates and AppKit's
coordinates. Regardless of which display a given window appears on, the
coordinate translation needs to use the height of the *main* display.

Release Notes:

- Fixed a bug where call notifications were accidentally full-screen on
all displays except the main display.
2023-08-17 16:24:46 -07:00
Conrad Irwin
b0ba0f8851 Fix visual objects
Adds 'a'/'i' in visual mode
2023-08-17 17:13:23 -06:00
Max Brunsfeld
cd2ef784ea Translate coordinates using the primary screen not the main screen 2023-08-17 16:12:52 -07:00
Max Brunsfeld
6eba0ef630 Return to master branch of alacritty 2023-08-17 15:31:27 -07:00
Mikayla
8630557ece Add action button component for rendering the search options 2023-08-17 15:30:40 -07:00
Max Brunsfeld
d9ef987b04 Fix AppKit screen coordinate conversion leading to wrong window bounds 2023-08-17 15:23:28 -07:00
Conrad Irwin
d4276acab8 Give up on monospace indicator
Changing mode almost always introduces the (1 selected) text in
the status bar, so we may as well also keep the --'s for block and line
mode.
2023-08-17 16:04:55 -06:00
Conrad Irwin
243d1664e5 shift-enter should also give a newline
(reported as vim feedback, but really true of the editor too)
2023-08-17 16:01:19 -06:00
Conrad Irwin
3c483d85f7 Scrolling should work in visual mode 2023-08-17 15:58:10 -06:00
Conrad Irwin
eb0b2e60bb Preserve line mode selection on undo
This diverges from vim's behaviour (which collapses the
cursor to a single point on undo).
2023-08-17 15:40:27 -06:00
Conrad Irwin
59d1a5632f Fix edge-cases in visual block insert 2023-08-17 15:16:26 -06:00
Conrad Irwin
3514816ece Store some vim state per-editor
This fixes a bug where opening and closing command would reset your
selection incorrectly.
2023-08-17 14:09:47 -06:00
Nathan Sobo
24d19deb0b Compiling checkpoint 2023-08-17 14:05:18 -06:00
Nathan Sobo
f3a275d339 Revert "Add a proc macro for deriving override structs with optional fields"
This reverts commit ab9356e9d8.
2023-08-17 13:48:46 -06:00
Conrad Irwin
d308c91020 Add I and A in visual block mode 2023-08-17 11:21:58 -06:00
Nathan Sobo
ab9356e9d8 Add a proc macro for deriving override structs with optional fields 2023-08-17 09:39:23 -06:00
Nate Butler
f451e3423d Fix missing border on tab bar navigation arrows 2023-08-17 11:00:22 -04:00
Piotr Osiewicz
1bd7d7077a Move nav buttons to the left hand side of a tab bar.
Co-authored-by: Nate <nate@zed.dev>
2023-08-17 16:45:11 +02:00
Nathan Sobo
b95b2af3e0 Store generic mouse regions on window that contain their event type id 2023-08-17 08:37:55 -06:00
KCaverly
b7dd12e53e ensured search results are cleared appropriately while cycling modes 2023-08-17 11:11:09 +01:00
Mikayla
afebe3faf8 Merge branch 'main' into project_search_design 2023-08-17 01:56:05 -07:00
Mikayla Maki
6d3518cb50 Collab panel touch ups (#2855)
This will also fix the bug that @JosephTLyons observed where accepting a
channel invite would not show sub channels.

Release Notes:

- Offline section is now collapsed by default
- Manage members now shows full list
- Dragging of docks now follows the mouse exactly, and double clicks
reset size. (https://github.com/zed-industries/community/issues/1816)
2023-08-17 01:09:01 -07:00
Mikayla
75679291a9 Add fix for lost channel update bug 2023-08-17 00:56:21 -07:00
Conrad Irwin
7598030102 Tidy-up 2023-08-16 22:29:28 -06:00
Nathan Sobo
812d3f6af6 Get basic mouse_down and mouse_up working 2023-08-16 22:21:27 -06:00
Mikayla
5bc481112e Add test for lost channel update 2023-08-16 20:05:21 -07:00
Nate Butler
e0cafffbc8 Fix collab indicator colors (#2854)
[[PR Description]]

Release Notes:

- N/A

or

- (Added|Fixed|Improved) ...
([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).

If the release notes are only intended for a specific release channel
only, add `(<release_channel>-only)` to the end of the release note
line.
These will be removed by the person making the release.
2023-08-16 22:59:55 -04:00
Mikayla
05becc75d1 Collapse offline section by default 2023-08-16 19:51:41 -07:00
Nate Butler
8b1322745d Fix collab indicators 2023-08-16 22:50:02 -04:00
Mikayla
2f16147055 Fix dock resizing 2023-08-16 19:47:54 -07:00
Mikayla
3074455386 WIP 2023-08-16 16:56:00 -07:00
Max Brunsfeld
fa0ddfa15d Use our fork of alacritty to avoid winit dependency (#2853)
Bumps `alacritty_terminal` for
https://github.com/alacritty/alacritty/pull/7149
2023-08-16 14:54:55 -07:00
Max Brunsfeld
a5a212e1da Use our fork of alacritty to avoid winit dependency 2023-08-16 14:31:38 -07:00
Kirill Bulatov
988ea3c16b Add editor::ToggleInlayHints command and a bar to toggle editor-related items (#2839)
Closes
https://linear.app/zed-industries/issue/Z-2735/make-inlay-hints-discoverable
Deals with https://github.com/zed-industries/community/issues/1764

Adds `editor::ToggleInlayHints` command and a new panel with two
buttons for 
* toggling hints on/off for every editor separately (overrides settings)

Would benefit from a new icon.

* toggling buffer search on/off 

Does not have a keybinding shown in its tooltip, that is a separate
issue with the way `KeystrokeLabel` gets these for panels not in the
view directly.

Release Notes:

- Adds `editor::ToggleInlayHints` command and a bar to toggle
editor-related items
2023-08-16 23:50:54 +03:00
Kirill Bulatov
7334bdccbf Better multibuffer tests 2023-08-16 23:38:11 +03:00
Nate Butler
ef86c08174 Use the ghost variant for a flat button style in the toolbar 2023-08-16 23:38:11 +03:00
Nate Butler
5bb6a14d42 Update inlay_hint icon 2023-08-16 23:38:11 +03:00
Kirill Bulatov
7fcf9022b4 Fix rebase issues 2023-08-16 23:38:11 +03:00
Nate Butler
3ed50708ac Add inlay_hint icon, update search icon, update tooltips 2023-08-16 23:38:11 +03:00
Kirill Bulatov
f9131f657e Use InlayHint instead of Inlay where appropriate 2023-08-16 23:38:11 +03:00
Kirill Bulatov
1938fd85e8 Do not leak pane handles 2023-08-16 23:38:11 +03:00
Kirill Bulatov
9c6135f47a Test hints toggle 2023-08-16 23:38:11 +03:00
Kirill Bulatov
8926c23bdb Extract quick_action_bar into its own crate 2023-08-16 23:38:11 +03:00
Kirill Bulatov
0f650acc23 Repaint inlays icon on inlays cache disabling/enabling
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-08-16 23:38:11 +03:00
Kirill Bulatov
0b93e490a5 Improve toggle UI, fix inlays update speed 2023-08-16 23:38:11 +03:00
Kirill Bulatov
6a326c1bd8 Toggle buffer search via quick actions 2023-08-16 23:38:11 +03:00
Kirill Bulatov
5c3d563f0f Draft quick actions bar 2023-08-16 23:38:11 +03:00
Nathan Sobo
187d78011c WIP 2023-08-16 13:52:42 -06:00
Nathan Sobo
f1aafab61d Get text rendering
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-08-16 12:50:35 -06:00
Joseph T. Lyons
07675e3c55 v0.101.x dev 2023-08-16 14:22:54 -04:00
Max Brunsfeld
442ec606d0 collab 0.17.0 2023-08-16 11:05:08 -07:00
Max Brunsfeld
4ea8b8292c Introduce channels and move collab popover contents to a collaboration panel (#2828)
### Summary

This PR introduces channels: a new way of starting collaboration
sessions. You can create channels and invite others to join them. You
can then hold a call in a channel, where any member of the channel is
free to join the call without needing to be invited.

Channels are displayed in a new panel called the collaboration panel,
which now also contains the contacts list, and the current call. The
collaboration popover has been removed from the titlebar.

![Screen Shot 2023-08-15 at 9 25 37
AM](https://github.com/zed-industries/zed/assets/326587/0f989dea-7fb7-4d50-9acd-25c8f1c30cd1)


For now, the channels functionality will only be revealed to staff, so
the public-facing change is just the move from the popover to the panel.

### To-do

* User-facing UI
  * [x] signed-out state for collab panel
  * [x] new icon for collab panel
  * [x] for now, channels section only appears for zed staff
* [x] current call section styling
(https://zed-industries.slack.com/archives/C05CJUNF2BU/p1691189389988239?thread_ts=1691189120.403009&cid=C05CJUNF2BU)
* [x] Channel members
* Channels
  * [x] style channel name editor
* [x] decide on a special "empty state" for the panel, when user has no
contacts
* [x] ensure channels are sorted in a consistent way (expose channel id
paths to client)
  * [x] Figure out layered panels UX
  * [x] Change add contacts to be the same kind of tabbed modal
* [x] race condition between channel updates and user fetches
(`ChannelStore::handle_update_contacts`)
* [x] race condition between joining channels and channel update
messages `collab::rpc::channel_updated`)
* [x] don't display mic as muted when microphone share is pending upon
first joining call

Release Notes:

- Moved the collaboration dropdown into its own panel.
- Added settings for disabling the AI assistant panel button.
- Switch to lazily initializing audio output sources
(https://github.com/zed-industries/community/issues/1840,
https://github.com/zed-industries/community/issues/1919)
2023-08-16 11:03:53 -07:00
Nate Butler
925e09e012 Update collab panel empty state to match project panel
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
2023-08-16 13:56:11 -04:00
Nate Butler
43127384c6 Update modal icon styles
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
2023-08-16 13:48:12 -04:00
Conrad Irwin
7f06191c9f Disable autoindent in visual block insert mode 2023-08-16 10:44:59 -06:00
Nate Butler
6c15636ccc Style cleanup for channels panel 2023-08-16 12:38:44 -04:00
KCaverly
c99b530968 remove kill search, and restart search immediately upon cycle mode
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 16:13:21 +01:00
KCaverly
aeda5d9842 fix semantic search panic which is created via incompatible build_search_query path
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 15:50:54 +01:00
KCaverly
6e3e61ec95 removed commented out code 2023-08-16 14:01:53 +01:00
KCaverly
2a7df106e1 adjusted icon sizes downwards
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 14:01:06 +01:00
KCaverly
6f78a1633d fix editor height in buffer search, but the dancing is back
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 13:51:41 +01:00
Kirill Bulatov
1601892f35 Focus terminal view on mouse click in terminal (#2852)
Before, terminal view focused the parent (pane) instead and, if
terminal's search bar was open and focused, pane transferred the focus
back

Release Notes:

- Fixed terminal search focus not switching to terminal on mouse click
inside
2023-08-16 15:25:17 +03:00
Nathan Sobo
fea987b459 WIP 2023-08-16 06:22:47 -06:00
Kirill Bulatov
80c779b95e Focus terminal view on mouse click in terminal
Before, terminal view focused the parent (pane) instead and, if
terminal's search bar was open and focused, pane transferred the focus
back
2023-08-16 15:16:20 +03:00
KCaverly
a59535efa1 remove redundant and unneeded styling
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 12:55:32 +01:00
KCaverly
d727ba18f2 Merge branch 'main' of github.com:zed-industries/zed into project_search_design 2023-08-16 12:43:04 +01:00
KCaverly
9bf227b884 remove regex keymap, and made spacing consistent between search objects
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 12:17:59 +01:00
KCaverly
11ecb7b604 reorganize search bar, enable filters and disable select all during invalid states
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-16 11:35:09 +01:00
Kirill Bulatov
139cbbfd3a Move gpui derives tests into gpui crate to avoid dependency cycles (#2851)
`cargo run` on Zed project leads to rust-analyzer evantually emitting

`[ERROR project_model::workspace] cyclic deps:
gpui_macros(Idx::<CrateData>(269)) -> gpui(Idx::<CrateData>(264)),
alternative path: gpui(Idx::<CrateData>(264)) ->
gpui_macros(Idx::<CrateData>(269))`

error after loading the project.

The PR fixes this by moving away the test to the "root" project.

Release Notes:

- N/A
2023-08-16 10:41:12 +03:00
Kirill Bulatov
1c4be24fb7 Move gpui derives tests into gpui crate to avoid dependency cycles
`cargo run` on Zed project leads to rust-analyzer evantually emitting

`[ERROR project_model::workspace] cyclic deps:
gpui_macros(Idx::<CrateData>(269)) -> gpui(Idx::<CrateData>(264)),
alternative path: gpui(Idx::<CrateData>(264)) ->
gpui_macros(Idx::<CrateData>(269))`

error after loading the project.

The PR fixes this by moving away the test to the "root" project.
2023-08-16 10:19:20 +03:00
Mikayla
0524abf114 Lazily initialize and destroy the audio handle state on call initiation and end 2023-08-15 23:19:11 -07:00
Nathan Sobo
a8ecc1a643 Add support for a measure function to the layout engine facade 2023-08-15 22:02:56 -06:00
Nathan Sobo
4efc46c763 Add derive macro now new elements 2023-08-15 21:04:48 -06:00
Nathan Sobo
77bc682a69 Render a component 2023-08-15 19:46:07 -06:00
Nathan Sobo
65a5c54a2c Compiling checkpoint 2023-08-15 19:29:57 -06:00
Max Brunsfeld
706227701e Keep collab panel focused after deleting a channel 2023-08-15 16:14:24 -07:00
Mikayla Maki
facb942156 Add component traits to GPUI (#2850)
Release Notes:

- N/A
2023-08-15 15:53:12 -07:00
Mikayla
7d3ffae47d move component into gpui 2023-08-15 15:44:59 -07:00
Nate Butler
a56747af8c Update assistant status bar icon 2023-08-15 18:36:30 -04:00
Nate Butler
28649fb71d Update channel context menu 2023-08-15 18:36:23 -04:00
Max Brunsfeld
3623a9ca5e Simplify Component implementation
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-15 15:26:02 -07:00
KCaverly
32bec2e401 left align buffer search with new structure 2023-08-15 23:18:03 +01:00
Conrad Irwin
1b4dd49b1d Vim visual block mode
This isn't quite an exact emulation, as instead of using one selection
that is magically in "column mode", we emulate it with a bunch of zed
multi-selections (one per line).

I think this is better, as it requires fewer changes to the codebase,
and lets you see the impact of any changes immediately on all lines.

Fixes: zed-industries/community#984
2023-08-15 16:00:50 -06:00
Max Brunsfeld
1ffde7bddc Implement calling contacts into your current channel
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-15 14:56:54 -07:00
Kirill Bulatov
2670e2c9ec Support editor::SelectAll in Terminal (#2848)
![image](https://github.com/zed-industries/zed/assets/2690773/3aae1e6a-9993-4e65-8ed1-20f2f4b452df)

Allows to use `editor::SelectAll`(`cmd-a` by default) in Terminal to
select all text in it, for future copying.
Currently, does not try to be smart and trim the selected whitespaces
after the last prompt, and copies them too.

Release Notes:

- Support `editor::SelectAll` in Terminal
2023-08-15 23:59:26 +03:00
Joseph T. Lyons
88e094c6e2 Associate additional file extensions with known languages (#2847)
Going to do these in batches.  Here is the first one.

Release Notes:

- Associated additional file extensions with known languages
(([#633](https://github.com/zed-industries/community/issues/633)),
([#1822](https://github.com/zed-industries/community/issues/1822))).
    - C++: `cxx`, `hxx`, `inl`
    - JavaScript: `cjs`
    - Python: `mpy`
    - TypeScript: `cts`, `d.cts`, `d.mts`, `mts`
2023-08-15 16:46:42 -04:00
Kirill Bulatov
de69f08c10 Support editor::SelectAll in Terminal 2023-08-15 23:43:32 +03:00
Max Brunsfeld
943aeb8c09 Run until parked when setting editor's state via EditorTestContext
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-15 13:42:54 -07:00
Joseph T. Lyons
d6ca0a1f24 Associate extensions with language 2023-08-15 16:33:02 -04:00
Nate Butler
13cf3ada39 Update checked icon 2023-08-15 16:29:01 -04:00
Max Brunsfeld
ddf3642d47 Avoid flicker when moving between channels 2023-08-15 13:18:56 -07:00
Max Brunsfeld
46928fa871 Reword channel-creation tooltips 2023-08-15 13:08:44 -07:00
Nate Butler
9d60e550be Additional status bar styles 2023-08-15 15:32:14 -04:00
Mikayla
d13cedb248 seperate out channel styles in theme 2023-08-15 12:12:30 -07:00
KCaverly
1e8a7c7caa refactor buffer_search to reduce redundancy and simplying height management
Co-authored-by: maxbrunsfeld <max@zed.dev>
2023-08-15 20:04:14 +01:00
Mikayla
d05e8852d3 Add dismiss on escape 2023-08-15 11:02:18 -07:00
Mikayla
d95b036fde Fix cursor style
co-authored-by: Nate <nate@zed.dev>
2023-08-15 10:58:31 -07:00
Mikayla
e36dfa0946 Add active styling 2023-08-15 10:53:30 -07:00
Mikayla
9e99b74fce Add the channel name into the current call 2023-08-15 10:45:36 -07:00
Max Brunsfeld
fafc10d57c Merge branch 'main' into collab-panel 2023-08-15 09:09:50 -07:00
Conrad Irwin
1cc0798aea Add a VisualBlock mode
Instead of trying to extend the Mode::Visual special case, just split
out into three different modes.
2023-08-15 10:00:45 -06:00
Nathan Sobo
e7489bd4c9 Compiling checkpoint 2023-08-15 09:33:59 -06:00
Nathan Sobo
0fe457020b WIP 2023-08-15 09:26:16 -06:00
Conrad Irwin
404b1aa65a Fix vim selection to include entire range (#2787)
Update vim mode to have vim selection and editor selections match.
Before this we had to adjust between vim selections and real selections
when making changes; now we have to adjust when making selections.

Release Notes:

- vim: Ensure editor selection matches the vim selection
([#1796](https://github.com/zed-industries/community/issues/1796)).
- vim: Fix `s` in visual line mode
- vim: Add `o` and `shift-o` to toggle direction of visual selection
- vim: Fix `v` and `shift-v` to toggle back to normal mode
- vim: Fix block selections like `vi}` to contain correct whitespace
2023-08-15 08:36:17 -06:00
Conrad Irwin
1e3f468fc7 Fix vim escape in normal mode (#2844)
Fixes: zed-industries/community#1857

- vim: Fix escape in normal mode
([#1857](https://github.com/zed-industries/community/issues/1857)).
2023-08-15 08:35:49 -06:00
KCaverly
695e6d2f2e fix spacing on editor magnifying glass icon 2023-08-15 15:05:39 +01:00
Mikayla
111e17b220 Merge branch 'main' into collab-panel 2023-08-15 03:25:45 -07:00
Mikayla Maki
22da42fc69 Add components example (#2846)
This PR is a continuation of the components UI exploration I've been
doing. It adds an example to the GPUI examples page and totally
restructures the generics on our MouseEventHandler.

Release Note:
- N/A
2023-08-15 03:17:50 -07:00
Mikayla
e5eed29c72 Add components example
Re-arrange generics on mouse event handler
Add TypeTag struct for dynamically tagged components
2023-08-15 03:06:43 -07:00
Max Brunsfeld
cbf497bc12 Fix race condition when UpdateChannel message is received while fetching participants for previous update
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 17:36:35 -07:00
Max Brunsfeld
71454ba27c Limit number of participants shown in channel face piles
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 17:11:03 -07:00
Max Brunsfeld
13982fe2f4 Display intended mute status while still connecting to a room
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 16:47:26 -07:00
Max Brunsfeld
5af8ee71aa Fix clicking outside of modals to dismiss them
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 16:38:21 -07:00
Max Brunsfeld
d7f21a9155 Ensure channels are sorted alphabetically
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 16:27:35 -07:00
Conrad Irwin
1af7425059 Fix vim escape in normal mode
Fixes: zed-industries/community#1857
2023-08-14 16:05:41 -06:00
Conrad Irwin
fb90eada70 Merge branch 'main' into vim-visual-selection 2023-08-14 15:29:33 -06:00
Nate Butler
ef73e77d3d Update some status bar icons and states 2023-08-14 17:15:25 -04:00
Conrad Irwin
5b37cdcb04 Better tests 2023-08-14 15:03:16 -06:00
Nate Butler
b4b044ccbf Initial modal styles 2023-08-14 17:01:34 -04:00
Nathan Sobo
f9858445b8 Get a 50% colored box rendering
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-08-14 14:46:09 -06:00
Nate Butler
e0d73842d2 Continue panel styles 2023-08-14 16:12:39 -04:00
Kirill Bulatov
64c2043913 Query less inlay hints (#2842)
Part of
https://linear.app/zed-industries/issue/Z-2750/investigate-performance-of-collaborating-on-large-files-with-inlay

Instead of querying the entire file for hints, query visible editor(s)
range + the areas above and below, of the same height.
Non-invalidating future queries (e.g. scrolling) query only missing
parts of the ranges.

Release Notes:

- Improved LSP resource usage by querying less hints for big files
2023-08-14 23:06:30 +03:00
Kirill Bulatov
54bcef9420 Strip off inlay hint data that should be resolved (#2843)
Part of
https://linear.app/zed-industries/issue/Z-2750/investigate-performance-of-collaborating-on-large-files-with-inlay

* Declares client capabilities for hint resolution, marking both fields
available for resolution (`textEdits` and `tooltop`) as resolvable.
We do not use these fields anymore, hence can omit resolving them for
now, but LSP servers can omit them during general hint requests.

* Removes `tooltip` and replaces complex `label` with its simple string
counterpart for clients' hint responses from host: both should be
resolved through host later

Release Notes:

- Reduces collab mode clients' inlay hint footprint by enabling hint
data resolution
2023-08-14 23:06:19 +03:00
Nate Butler
f2d46e0ff9 Use new icons in channel panel 2023-08-14 15:57:31 -04:00
Kirill Bulatov
27bf01c3a8 Strip off inlay hints data that should be resolved 2023-08-14 22:50:55 +03:00
Nate Butler
a5534bb30f Add new icons 2023-08-14 15:50:42 -04:00
Nate Butler
8531cdaff7 Style channels panel items 2023-08-14 15:50:37 -04:00
Nathan Sobo
740b105330 Merge branch 'main' into taffy
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-08-14 13:50:21 -06:00
Nate Butler
4a5b2fa5dc Add ghost button variants 2023-08-14 15:13:57 -04:00
Max Brunsfeld
3b10ae9310 Add icon before the empty contacts text
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 11:57:19 -07:00
Max Brunsfeld
2bb9f7929d Structure the contact finder more similarly to the channel modal
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-14 11:37:05 -07:00
Mikayla
b6f3dd51a0 Move default collab panel to the right 2023-08-14 10:47:29 -07:00
Mikayla
b07555b6df Make empty state interactive 2023-08-14 10:34:00 -07:00
Mikayla
fde9653ad8 Add placeholder implementation 2023-08-14 10:24:00 -07:00
Nate Butler
3856137b6e Add list empty state style 2023-08-14 13:17:57 -04:00
Kirill Bulatov
e0d011e354 Better assert multibuffer edit test results 2023-08-14 20:12:35 +03:00
Kirill Bulatov
4b3273182a Do not filter out hints to be removed 2023-08-14 19:20:20 +03:00
Kirill Bulatov
336fbb3392 Clip offsets in inlay hint queries 2023-08-14 18:39:30 +03:00
Nathan Sobo
7756497933 Simplify adapter 2023-08-14 09:26:35 -06:00
Piotr Osiewicz
db36a5fe2d Refactor buffer search UI rendering in a quest to find the meaning of life.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 15:27:57 +02:00
Kirill Bulatov
558367dc8b Optimize query ranges tracking 2023-08-14 16:19:44 +03:00
Piotr Osiewicz
ff4370f88e buffer: Extract columns to separate objects.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 15:14:57 +02:00
Piotr Osiewicz
4aa5df4cda Extract columns into separate objects.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 15:13:17 +02:00
Kirill Bulatov
87e6651ecb Fix hint tests, add a char boundary bug test 2023-08-14 16:01:02 +03:00
Piotr Osiewicz
f4121b42da Add more padding to dismiss button.
Add a style for mode buttons group margin

Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 15:01:01 +02:00
Piotr Osiewicz
d17aba4fd3 Fix double borders in mode buttons.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 14:43:06 +02:00
Piotr Osiewicz
7314456685 nav_button: Fix double border between nav buttons.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 14:33:40 +02:00
Piotr Osiewicz
b72a42ec29 buffer: fix alignment of editor icon.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-14 14:31:14 +02:00
Piotr Osiewicz
9ae28f81c1 Update option buttons text color 2023-08-14 12:35:33 +02:00
Piotr Osiewicz
ae229d4c52 Align "Select all" to the center 2023-08-14 12:31:11 +02:00
Piotr Osiewicz
94ac84a908 Fix borders being cut off in buffer search 2023-08-14 12:28:35 +02:00
Piotr Osiewicz
8bb3bfa6a8 Style buffer modes like project modes 2023-08-14 12:25:59 +02:00
Piotr Osiewicz
1c35db7e97 project_search: style filters button like the rest of the buttons 2023-08-14 12:20:59 +02:00
Piotr Osiewicz
d37ebe7841 Add option_button_height style 2023-08-14 12:10:37 +02:00
Piotr Osiewicz
9121178ba0 project_search: Swap places of case-sens/whole-word 2023-08-14 11:59:42 +02:00
Kirill Bulatov
449c009639 Properly generate ranges to query 2023-08-14 11:25:02 +03:00
Kirill Bulatov
56f89739f8 Do not add duplicate hints to the cache 2023-08-14 11:25:02 +03:00
Kirill Bulatov
0e2a1fc149 Query inlay hints for parts of the file 2023-08-14 11:25:02 +03:00
Kirill Bulatov
708409e06d Query hints on every scroll 2023-08-14 11:25:02 +03:00
Kirill Bulatov
5d2750e0d4 Hide inlay cache fields 2023-08-14 11:25:02 +03:00
Nathan Sobo
85f35497b6 Compiling checkpoint 2023-08-13 22:39:06 -06:00
Nathan Sobo
7662210776 I think the adapter is ready. Close to pixels. 2023-08-13 22:06:35 -06:00
Nathan Sobo
625e4a1bd0 Introduce new PaintContext and LayoutContext 2023-08-13 21:57:38 -06:00
Nathan Sobo
2d17e9685f Compiling checkpoint 2023-08-13 21:20:47 -06:00
Nathan Sobo
be7a43c81c Get taffy style conversion working 2023-08-13 19:47:49 -06:00
Nathan Sobo
52ad48d50b WIP 2023-08-13 17:29:07 -06:00
Nathan Sobo
473ac5a842 WIP 2023-08-13 02:27:36 -06:00
Nathan Sobo
36b853ac05 WIP 2023-08-13 01:40:10 -06:00
Nathan Sobo
1ef486b227 WIP 2023-08-13 01:40:05 -06:00
Piotr Osiewicz
b586601cab Style nav buttons 2023-08-13 01:29:35 +02:00
Piotr Osiewicz
e151555542 Style dismiss button.
Fix clipping in nav buttons and mode buttons. Add missing borders to outskirts of mode buttons.
2023-08-13 01:06:35 +02:00
Piotr Osiewicz
c6425b36da chore: remove redundant mut 2023-08-12 22:20:22 +02:00
Piotr Osiewicz
94f1d8281a Remove buttonside, use new corner_radii instead 2023-08-12 22:18:27 +02:00
Piotr Osiewicz
6be73e46bf Merge branch 'main' into project_search_design 2023-08-12 21:57:21 +02:00
Mikayla
a90c0e0326 Merge branch 'main' into collab-panel 2023-08-12 12:44:22 -07:00
Mikayla Maki
5ce7ccac32 Allow individual corner radii on containers, images, and drop shadows (#2841)
Here's an example in `crates/gpui/examples/corner_radii.rs`

![CleanShot 2023-08-12 at 11 06
09@2x](https://github.com/zed-industries/zed/assets/1789/1b5992ac-f7ef-45d8-b8c2-f0e677b07dd9)

@iamnbutler, in the themes, anywhere we have a container style can now
take either a `corner_radius` or a `corner_radii` field, both of these
fields can either have 1 number (for all 4 corners) or a an object like:

```
{
  top_left?: number,
  top_right?: number,
  bottom_left?: number, 
  bottom_right?:number 
} 
```

Fields that are not included in this second representation default to 0
corner radius.
2023-08-12 12:36:05 -07:00
Mikayla
29a85635ea Make each setting optional 2023-08-12 12:23:46 -07:00
Mikayla
563b25f26f Add deserialization helper 2023-08-12 12:21:44 -07:00
Nathan Sobo
fa7ebd0825 Include drop shadows with different corner radii in the example 2023-08-12 11:08:58 -06:00
Nathan Sobo
65123e6eed Allow individual corner radii on drop shadows 2023-08-12 10:58:08 -06:00
Nathan Sobo
40f478937e Allow distinct corner radii for images 2023-08-12 10:50:04 -06:00
Nathan Sobo
84dc4090bd Wire up per corner radii for quad
Still need to expose this in the styling layer and allow images
to have per corner radii.
2023-08-12 10:40:23 -06:00
Nathan Sobo
0d31d6dac5 WIP 2023-08-12 10:00:08 -06:00
Nathan Sobo
4b4b949972 WIP 2023-08-12 01:11:12 -06:00
Nathan Sobo
5e36040533 Put a Taffy layout engine on window 2023-08-12 00:58:11 -06:00
Mikayla Maki
1911f537b4 Add a compile test for the element derive (#2840)
Tried to use this the new element derive on a branch and ran into some
bugs, this fixes those.

Release Notes:

- N/A
2023-08-11 18:08:13 -07:00
Mikayla
7970406694 Add a compile test for the element derive 2023-08-11 18:00:12 -07:00
Piotr Osiewicz
c0356fdf16 Decrease row count for buffer search bar 2023-08-11 23:47:16 +02:00
Mikayla
9b5551a079 split into body and header 2023-08-11 11:35:51 -07:00
Nate Butler
ff1261b300 WIP Restyle channel modal
Co-Authored-By: Mikayla Maki <mikayla.c.maki@gmail.com>
2023-08-11 13:32:46 -04:00
Piotr Osiewicz
0a0314eec9 Shrink project search + decrease padding 2023-08-11 18:19:45 +02:00
Piotr Osiewicz
e4c593c2fb chore: fix compiler warning 2023-08-11 17:09:26 +02:00
Piotr Osiewicz
799278e296 Add row_height 2023-08-11 17:02:25 +02:00
Piotr Osiewicz
100a8961b5 Add accent colors to search option buttons 2023-08-11 16:51:18 +02:00
Piotr Osiewicz
f6ecf83f33 Increase editor's left hand side padding to 10 2023-08-11 16:43:07 +02:00
Piotr Osiewicz
9e9b3093a9 Style mode/option buttons 2023-08-11 16:32:15 +02:00
Nathan Sobo
983641da2b WIP 2023-08-11 07:39:30 -06:00
Piotr Osiewicz
ffffbbea1f chore: use Cow instead of String for tooltips (#2838)
A QoL change to align `Tooltip` with other elements like `Label`
Release Notes:

- N/A
2023-08-11 15:29:55 +02:00
Piotr Osiewicz
3d103546bc Make ButtonSide respect corner_radius 2023-08-11 14:44:22 +02:00
Piotr Osiewicz
b8df26b194 mode_button: get rid of borders before creating a label 2023-08-11 13:26:22 +02:00
Piotr Osiewicz
a9a2d281c3 Make ButtonSide scale with corner_radius 2023-08-11 13:10:56 +02:00
Nathan Sobo
d6eaa3c6b8 Ditch the hot reloading approach 2023-08-11 00:26:58 -06:00
Mikayla
b21b17c120 Merge branch 'main' into collab-panel 2023-08-10 10:04:01 -07:00
Nathan Sobo
0bf607cd2d WIP 2023-08-10 10:26:48 -06:00
Nathan Sobo
dd6425e898 WIP 2023-08-10 09:24:16 -06:00
Piotr Osiewicz
096e293c17 Fix rounding artifacts of nav buttons 2023-08-10 15:26:05 +02:00
Piotr Osiewicz
9ad308f2f0 Fix padding inside search input.
Adjust borders for toggle buttons
2023-08-10 14:53:25 +02:00
Piotr Osiewicz
da3a4174ce Bump row_count for buffer search 2023-08-10 13:01:20 +02:00
Piotr Osiewicz
b2f773e91d Increase padding on search 2023-08-10 12:57:12 +02:00
Nathan Sobo
3b1e5e966a What if we base themes on Rose Pine 2023-08-09 20:54:30 -06:00
Max Brunsfeld
b3447ada27 Dial in the channel creating/renaming UI
* Ensure channel list is in a consistent state with no flicker while the
  channel creation / rename request is outstanding.
* Maintain selection properly when renaming and creating channels.
* Style the channel name editor more consistently with the non-editable
  channel names.

Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-09 17:11:57 -07:00
Max Brunsfeld
076b72cf2b Improve styling of collab panel 2023-08-09 15:11:30 -07:00
Max Brunsfeld
ac1b2b18aa Send user ids of channels of which they are admins on connecting
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-09 14:40:47 -07:00
Max Brunsfeld
60e25d780a Send channel permissions to clients when they fetch their channels 2023-08-09 13:56:03 -07:00
Max Brunsfeld
268f4b1939 Restore shutdown behavior (#2837)
Deals with https://github.com/zed-industries/community/issues/1898

Restores original close behavior from
https://github.com/zed-industries/zed/pull/2832/files#diff-89af0b4072205c53b518aa977d6be48997e1a51fa4dbf06c7ddd1fec99fc510eL444
(load diff for the last file, zed.rs)

and adds a better name for the variable.

Release Notes:

- Fixes `cmd-q` not working
2023-08-09 13:50:21 -07:00
Kirill Bulatov
704ab33f72 Restore shutdown behavior 2023-08-09 23:39:21 +03:00
Mikayla
a3623ec2b8 Add renames
co-authored-by: max <max@zed.dev>
2023-08-09 12:20:48 -07:00
Mikayla
eed49a88bd Fix bad merge 2023-08-09 11:04:09 -07:00
Mikayla
707e41ce1f Merge branch 'collab-panel' of github.com:zed-industries/zed into collab-panel 2023-08-09 10:44:50 -07:00
Mikayla
99daa73325 Merge branch 'main' into collab-panel 2023-08-09 10:37:22 -07:00
Max Brunsfeld
778fd6b0a9 Represent channel relationships using paths table
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-09 10:36:27 -07:00
Max Brunsfeld
498d043a0a Avoid leak of channel store
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-09 10:23:52 -07:00
Nate Butler
beffe6f6a9 WIP BROKEN 2023-08-09 12:44:34 -04:00
Joseph T. Lyons
230b894871 v0.100.x dev 2023-08-09 12:30:39 -04:00
Mikayla Maki
40030f32d9 Fix two mouse event bugs (#2835)
This PR fixes two bugs we discovered in Zed's mouse event handling while
investigating an interesting and mysterious bug we we were seeing, where
spurious `MouseMoved` events would continuously be dispatched after
control-clicking.

Release Notes:

- Fixed a rendering glitch that could occur after control-clicking
certain elements.
2023-08-09 09:04:32 -07:00
Piotr Osiewicz
c58cf396d3 Add cycle mode command for buffer search 2023-08-09 18:02:21 +02:00
Mikayla
a5cb4c6d52 Fix selections and enter-to-create-file 2023-08-09 08:54:24 -07:00
Nate Butler
6cc0b81e39 Add ui_sans as a font option in the theme (#2836)
This adds IBM Plex as a font option available to use as `ui_sans`

Note: This PR adds a static list of accepted font types in `/font`, as
LICENSE files were causing the app to crash when Zed was trying to load
them as fonts.

Release Notes:

- N/A (No user facing changes)

Thanks @ForLoveOfCats for getting me unstuck ❤️
2023-08-09 11:54:05 -04:00
Nate Butler
85af025d82 Add IBM Plex license 2023-08-09 11:39:15 -04:00
Nate Butler
af388e7f9c Only load TTF fonts for now, additional font types will need to be manually added
Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com>
2023-08-09 11:38:02 -04:00
Nate Butler
183c292a5c Remove license causing unwrap error
Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com>
2023-08-09 11:11:57 -04:00
Nate Butler
b23f1c809a WIP add IBM Plex Sans
(base) natebutler@Nate16 zed % cargo run
   Compiling zed v0.99.0 (/Users/natebutler/Code/zed/zed/crates/zed)
    Finished dev [unoptimized + debuginfo] target(s) in 9.15s
     Running `target/debug/Zed`
Thread "main" panicked with "called `Result::unwrap()` on an `Err` value: parse error" at crates/zed/src/main.rs:667:10
   0: backtrace::backtrace::libunwind::trace
             at /Users/natebutler/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.68/src/backtrace/libunwind.rs:93:5
      backtrace::backtrace::trace_unsynchronized
             at /Users/natebutler/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.68/src/backtrace/mod.rs:66:5
   1: backtrace::backtrace::trace
             at /Users/natebutler/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.68/src/backtrace/mod.rs:53:14
   2: backtrace::capture::Backtrace::create
             at /Users/natebutler/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.68/src/capture.rs:176:9
   3: backtrace::capture::Backtrace::new
             at /Users/natebutler/.cargo/registry/src/index.crates.io-6f17d22bba15001f/backtrace-0.3.68/src/capture.rs:140:22
   4: Zed::init_panic_hook::{{closure}}
             at crates/zed/src/main.rs:436:29
   5: std::panicking::rust_panic_with_hook
   6: std::panicking::begin_panic_handler::{{closure}}
   7: std::sys_common::backtrace::__rust_end_short_backtrace
   8: _rust_begin_unwind
   9: core::panicking::panic_fmt
  10: core::result::unwrap_failed
  11: core::result::Result<T,E>::unwrap
             at /private/tmp/rust-20230613-7622-103lepv/rustc-1.70.0-src/library/core/src/result.rs:1089:23
  12: Zed::load_embedded_fonts
             at crates/zed/src/main.rs:664:5
  13: Zed::main
             at crates/zed/src/main.rs:80:5
  14: core::ops::function::FnOnce::call_once
             at /private/tmp/rust-20230613-7622-103lepv/rustc-1.70.0-src/library/core/src/ops/function.rs:250:5
  15: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /private/tmp/rust-20230613-7622-103lepv/rustc-1.70.0-src/library/std/src/sys_common/backtrace.rs:134:18
  16: std::rt::lang_start::{{closure}}
             at /private/tmp/rust-20230613-7622-103lepv/rustc-1.70.0-src/library/std/src/rt.rs:166:18
  17: std::panicking::try
  18: std::rt::lang_start_internal
  19: std::rt::lang_start
             at /private/tmp/rust-20230613-7622-103lepv/rustc-1.70.0-src/library/std/src/rt.rs:165:17
  20: _mai
2023-08-09 11:01:20 -04:00
Piotr Osiewicz
17d505bb7b Reset search index on mode change 2023-08-09 14:25:56 +02:00
Piotr Osiewicz
28a57662af buffer: Add magnifying glass icon 2023-08-09 13:47:48 +02:00
Piotr Osiewicz
6fbef9f3c7 chore: update fn paint 2023-08-09 12:33:14 +02:00
Piotr Osiewicz
7ef5656f6a Merge branch 'main' into project_search_design 2023-08-09 12:30:48 +02:00
Piotr Osiewicz
8b033223fb Go back to previous mode on rejection of semantic indexing 2023-08-09 12:28:15 +02:00
Nathan Sobo
82c903de14 Rename node to frame 2023-08-08 21:28:52 -06:00
Nathan Sobo
c95aecdd53 Merge branch 'main' into cells 2023-08-08 21:23:57 -06:00
Nathan Sobo
8ed5e8f86d Pass PaintContext to Element::paint (#2788)
I want to use this on another branch, but it's a sweeping change, so
this prepares the ground for it. This can always be reverted if it
doesn't work out.
2023-08-08 21:16:57 -06:00
Joseph T. Lyons
bed0d1d529 Fix language detection when file name begins with a . (#2833)
I went to add in `zprofile` to the bash language config to get syntax
highlighting for it. After adding it in, Zed was still not highlighting
the file. I checked and saw that we are using `Path::extension()` in
`language_for_file()`, which [returns `None` when a file's name begins
with a
`.`](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.extension),
such as in the case of `.zprofile`. This PR adds a custom method, with
some tests, that just tries to grab the last component in the file name
if `Path::extension` returns `None`. Not sure if `ext` is the best name,
but I can't use `extension`.

Maybe this method should be called `extension_or_hidden_file_name()`?

Release Notes:

- Fixed a bug where language detection would fail for files starting
with `.` in their names.
- Added syntax highlighting for `.zprofile` files
2023-08-08 21:48:56 -04:00
Joseph T. Lyons
c523ccc4c7 Fix code that identifies language via extension 2023-08-08 21:35:11 -04:00
Max Brunsfeld
2605ae1ef5 Use Arc::make_mut in ChannelStore
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-08 17:49:29 -07:00
Max Brunsfeld
0b93a30821 Terminate synthetic drag state on mouse up w/ ctrl held
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-08 17:39:45 -07:00
Max Brunsfeld
e3bb5e5103 Fix failure to remove hovered region_ids on element removal
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-08 17:39:05 -07:00
Nathan Sobo
db96fb1307 Merge remote-tracking branch 'origin/main' into paint-context 2023-08-08 18:27:16 -06:00
Nathan Sobo
54ca5f1d44 Replace context methods that take a window id with methods on window handles (#2832)
With this PR, I've eliminated almost all references to window ids
outside of the internals of GPUI. All public methods taking these ids
are now defined on `AnyWindowHandle`, which provides a more coherent
narrative around windows as a concept.
2023-08-08 17:51:37 -06:00
Piotr Osiewicz
d34da2db69 fix dismiss tooltip for project search 2023-08-09 01:37:17 +02:00
Piotr Osiewicz
22f630e985 chore: remove unused function 2023-08-09 01:30:45 +02:00
Piotr Osiewicz
f978acdd34 buffer: use shared func for nav button rendering 2023-08-09 01:29:22 +02:00
Nathan Sobo
0dc70e6cbf Rename mac platform Window to MacWindow for clarity 2023-08-08 17:21:06 -06:00
Nathan Sobo
fc96676662 Use AppContext::update when updating windows so we handle effects 2023-08-08 17:20:46 -06:00
Piotr Osiewicz
e2b8e6ce63 chore: fix compiler warnings 2023-08-09 01:16:24 +02:00
Piotr Osiewicz
a583d1c6cc Do not display match count when query is empty 2023-08-09 01:15:17 +02:00
Piotr Osiewicz
b282bdfe5a buffer: finish up moving things around 2023-08-09 01:09:07 +02:00
Piotr Osiewicz
e1724daf35 buffer: WIP style sides of buffer search 2023-08-09 00:38:50 +02:00
Nathan Sobo
8e49d1419a Minimize window id usage 2023-08-08 16:38:46 -06:00
Piotr Osiewicz
a7bd05ec0a buffer: use icons for search options 2023-08-09 00:16:12 +02:00
Nathan Sobo
afd89b256a Store AnyWindowHandles instead of usizes 2023-08-08 16:06:53 -06:00
Mikayla
bbe4a9b388 Position and style the channel editor correctly
Fix a bug where some channel updates would be lost
Add channel name sanitization before storing in the database
2023-08-08 14:06:29 -07:00
Mikayla
b708824d37 Position and style the channel editor correctly
Fix a bug where some channel updates would be lost
Add channel name sanitization before storing in the database
2023-08-08 12:46:13 -07:00
Piotr Osiewicz
c96b03ae55 Piotr/optimize search selections with a limit (#2831)
/cc @nathansobo @maxbrunsfeld 

Release Notes:
- Fixed scrollbar selections causing noticeable slowdowns with large
quantities of selections.
2023-08-08 21:29:24 +02:00
Mikayla
d00f6a490c Fix a bug where channel invitations would show up in the channels section
Block non-members from reading channel information
WIP: Make sure Arc::make_mut() works
2023-08-08 11:47:13 -07:00
Mikayla
6a7245b92b Fix positioning on face piles, fix panic on member invite removal 2023-08-08 10:44:44 -07:00
Nathan Sobo
1e8a9ccdb5 Merge remote-tracking branch 'origin/main' into window-handles 2023-08-08 11:42:55 -06:00
Nathan Sobo
b77c336a3d Return window handles from WeakItemHandle 2023-08-08 11:39:56 -06:00
Nathan Sobo
b2d9ccc0a2 Move more window methods off AsyncAppContext 2023-08-08 11:38:07 -06:00
Nathan Sobo
95cd96e4be Move debug_elements to AnyWindowHandle 2023-08-08 11:27:19 -06:00
Nathan Sobo
4f10f0ee86 Remove window methods from AsyncAppContext 2023-08-08 11:23:49 -06:00
Nathan Sobo
1fd80ba8bd Remove AsyncAppContext::remove_window 2023-08-08 11:22:43 -06:00
Nathan Sobo
fe6a1886c1 Remove unused dock code 2023-08-08 11:20:42 -06:00
Nathan Sobo
0a4633f88f Remove more window id usage 2023-08-08 11:20:09 -06:00
Nathan Sobo
da7dc9c880 Work with window handles instead of ids in drag code 2023-08-08 11:14:02 -06:00
Nathan Sobo
d896d89842 Store an AnyWindowHandle in WindowContext 2023-08-08 11:08:37 -06:00
Mikayla
17c9b4ca96 Fix tests 2023-08-08 10:04:29 -07:00
Nate Butler
662e196267 Calculate the range for each color family in a theme (#2738)
Release Notes:
- N/A (Internal theme stuff)
2023-08-08 11:49:52 -04:00
Nathan Sobo
49f1f1c6c2 Remove window when closing workspace in test 2023-08-08 09:13:17 -06:00
Piotr Osiewicz
2c570fbb72 Move option button rendering to search_bar 2023-08-08 16:26:19 +02:00
Piotr Osiewicz
343e9d080e Finish migration to modes for buffer search 2023-08-08 15:57:07 +02:00
Piotr Osiewicz
fecea03c90 Add modes to buffer search 2023-08-08 15:25:32 +02:00
Piotr Osiewicz
0374fdfd06 Move mode cycling to mode module 2023-08-08 15:20:34 +02:00
Piotr Osiewicz
7547fa2679 Move mode rendering to a search_bar module 2023-08-08 15:11:32 +02:00
Piotr Osiewicz
31a56720d9 Move nav buttons generation to search_bar 2023-08-08 14:56:21 +02:00
Piotr Osiewicz
c53554ead3 Remove SearchOptions::REGEX.
A bit WIP as it awaits migration of buffer search to modes
2023-08-08 14:42:11 +02:00
Piotr Osiewicz
0ca29e56c2 Update keybinds to use new names 2023-08-08 14:33:51 +02:00
Piotr Osiewicz
d497f279f0 Move ButtonSide to new module 2023-08-08 14:24:26 +02:00
Piotr Osiewicz
95891d28d8 Move SearchMode and SearchHistory to separate modules 2023-08-08 14:14:39 +02:00
Piotr Osiewicz
1aff642981 Do not highlgiht selections at all over the threshold 2023-08-08 13:09:27 +02:00
Nathan Sobo
dba2facd23 Remove window via handles 2023-08-07 22:58:01 -06:00
Nathan Sobo
f0da6b05fd Remove TestAppContext::add_view
Instead, we now call this on window handles.
2023-08-07 22:46:48 -06:00
Nathan Sobo
0f332238b3 Remove unused method 2023-08-07 22:08:44 -06:00
Nathan Sobo
d687c3d81f Merge remote-tracking branch 'origin/main' into window-handles 2023-08-07 22:07:20 -06:00
Nathan Sobo
f2be3181a9 Move window-related methods from TestAppContext to AnyWindowHandle 2023-08-07 20:23:04 -06:00
Nathan Sobo
0197d49230 Move activation simulation to AnyWindowHandle 2023-08-07 19:45:43 -06:00
Nathan Sobo
486f5bc6ca Get compiling 2023-08-07 19:08:58 -06:00
Max Brunsfeld
299906346e Change collab panel icon 2023-08-07 18:04:41 -07:00
Piotr Osiewicz
371c669e00 Address review feedback.
Rename selected_rows to background_highlight_row_ranges.
Do not return any ranges if there are more than 50k results
2023-08-08 02:47:49 +02:00
Piotr Osiewicz
b0fc6da55b Use display maps 2023-08-08 02:37:27 +02:00
Piotr Osiewicz
241d3951b8 Remove redundant argument 2023-08-08 02:25:30 +02:00
Piotr Osiewicz
42e1221357 Add upper bound limit. Remove dbg! statements 2023-08-08 02:17:11 +02:00
Mikayla
fa71de8842 Tune UX for context menus
Co-authored-by: max <max@zed.dev>
2023-08-07 17:14:14 -07:00
Mikayla
bedf60b6b2 Improve local collaboration script to accept a zed impersonate
Gate channels UI behind a flag

co-authored-by: max <max@zed.dev>
2023-08-07 16:45:13 -07:00
Mikayla
8980a9f1c1 Add settings for removing the assistant and collaboration panel buttons
Add a not-logged-in state to the collaboration panel

co-authored-by: max <max@zed.dev>
2023-08-07 16:27:47 -07:00
Mikayla
e37e76fc0b Add context menu controls to the channel member management
co-authored-by: Max <max@zed.dev>
2023-08-07 15:29:30 -07:00
Piotr Osiewicz
fa16895976 Do not query start of range if it's end is the same as the previous hunk's 2023-08-08 00:27:38 +02:00
Piotr Osiewicz
ca21626064 Baseline: Improve selection rendering for large quantities from 270ms to 90ms 2023-08-07 23:32:27 +02:00
Mikayla
9913067e51 Remove admin and member button
Fix bug with invites in the member list
Fix bug when there are network errors in the member related RPC calls

co-authored-by: Max <max@zed.dev>
2023-08-07 14:32:13 -07:00
Max Brunsfeld
7288be4251 Make LspAdapter::process_diagnostics synchronous (#2829)
When editing rust code, the project diagnostics view sometimes fails to
update, so that you have to close the view and re-open it to see the
correct state.

This PR fixes one possible cause of that problem. There was an async
step in between *receiving* diagnostics from the language server and
updating the diagnostics, due to an async call to
`LspAdapter::process_diagnostics`. This could cause the following
sequence of events to happen:

1. Rust-analyzer sends us new diagnostics for a file `a.rs`
2. We call `process_diagnostics` with those diagnostics
3. Rust-analyzer sends us a `WorkDoneProgress` message, indicating that
the "flycheck" (aka `cargo check`) process has completed
4. We update the project diagnostics view due to this message.
5. The `process_diagnostics` call for `a.rs` completes
6. 💥 We have the new diagnostics for `a.rs`, but do not update the
project diagnostics view again.

This PR fixes this bug by simply making `process_diagnostics`
synchronous. There is no I/O or expensive computation happening in that
method. If we need to make it asynchronous in the future, we need to
introduce a queue that ensures that `publishDiagnostics` and
`workDoneProgress` messages are processed serially.

Release Notes:

- Fixed a bug where the project diagnostics view would sometimes fail to
update properly when using Rust-analyzer.
2023-08-07 14:31:49 -07:00
Joseph T. Lyons
d417993c9d Add syntax highlighting for Cargo.toml files (#2830)
Release Notes:

- Added syntax highlighting for `Cargo.toml` files
2023-08-07 17:28:21 -04:00
Joseph T. Lyons
dbf25ea2ff Add syntax highlighting for Cargo.toml files 2023-08-07 17:24:22 -04:00
Joseph T. Lyons
580c2ea8eb Fix test name 2023-08-07 17:07:01 -04:00
Max Brunsfeld
4e33654aba Make LspAdapter::process_diagnostics synchronous
Co-authored-by: Nathan <nathan@zed.dev>
2023-08-07 13:53:41 -07:00
Mikayla
90cdbe8bf3 Fix modal click throughs and adjust height for channel modal 2023-08-07 13:39:05 -07:00
Mikayla
f1957b1737 Push focus and fix keybindings 2023-08-07 13:31:58 -07:00
Nathan Sobo
3e0d0e5c01 WIP 2023-08-07 13:54:47 -06:00
Max Brunsfeld
c537cf2a57 Merge branch 'main' into collab-panel 2023-08-07 11:50:40 -07:00
Conrad Irwin
19eb280351 Fix selection background too
Refactor code to centralize the logic too
2023-08-07 19:01:04 +01:00
Piotr Osiewicz
7a1f40405a Add dismiss button to project search 2023-08-07 12:22:10 +02:00
Nathan Sobo
d4d32611fe WIP 2023-08-06 18:57:02 -06:00
Nathan Sobo
adc50469ff WIP 2023-08-06 12:45:31 -06:00
Joseph T. Lyons
e3a4d174de Fix bash path_suffixes and add cmd-/ line comment support (#2827)
<img width="1608" alt="SCR-20230806-cyrg"
src="https://github.com/zed-industries/zed/assets/19867440/2491c4bc-5797-4417-9633-08c136b4e8fe">

I noticed we weren't highlghting bash files if the shebang line didn't
exist. After checking, it looks like the `.` were accidentally added to
the `path_suffixes` list. This PR fixes that and adds in support for
`cmd-/` to trigger line comments.

<img width="1608" alt="SCR-20230806-czxh"
src="https://github.com/zed-industries/zed/assets/19867440/37dd0c8e-c4e7-49e2-9997-9dd8145f460e">


Release Notes:

- Fixed a bug where shell files weren't syntax highlighted if a shebang
didn't exist.
- Added support for `cmd-/` to add line comments to shell files.
2023-08-06 02:35:00 -04:00
Joseph T. Lyons
ef5b982ea5 Fix bash path_suffixes and add line_comment 2023-08-06 02:20:31 -04:00
Nathan Sobo
dcf8b00656 WIP 2023-08-05 18:00:44 -06:00
Joseph T. Lyons
7777d973cd Expand empty selections to cover full word when doing case conversions and fix bugs (#2826)
When doing case conversions, specifically in the case of an empty
selection, in both VS Code and Sublime, the cursor winds up being in a
different place relative to where it started.

In VS Code, the cursor maintains it position in the text, no matter if
the text expands or shrinks


https://github.com/zed-industries/zed/assets/19867440/b24f5d86-c315-4a72-9ed4-3732b490ea9a

In Sublime, I have no idea what is going on:


https://github.com/zed-industries/zed/assets/19867440/05f21303-6e42-47b2-b844-7accd0bf05d7

I thought it would be a better experience if, when doing an empty
selection transformation, we simply expand the selection and park the
cursor at the end of the newly-transformed text.


https://github.com/zed-industries/zed/assets/19867440/833619ef-04e2-47b6-ad4e-e2b43d54fb2b

This feels similar to us expanding the selection when doing line
manipulations:


https://github.com/zed-industries/zed/assets/19867440/c30c5332-787d-4cf0-a9ee-e66c3c159956

Selections are adjusted to match however each word expands and shrinks,
even when there are multiple:


https://github.com/zed-industries/zed/assets/19867440/d7073aac-8a59-4f2c-b0e5-1df37be1694c

Release Notes:

- Improved behavior of empty-selection case transformations by selecting
resulting word.
- Fixed some bugs with overflow
2023-08-05 12:16:46 -04:00
Joseph T. Lyons
1abb6a0176 Expand empty selections to cover full word and fix bugs 2023-08-05 11:31:21 -04:00
Joseph T. Lyons
d1048d03b0 Add more convert to case commands (#2825)
I'm using [convert_case](https://crates.io/crates/convert_case)
underneath the hood, which has over 35 million downloads and feels
solid.

Release Notes:

- Added commands to convert between variable name styles
([#1821](https://github.com/zed-industries/community/issues/1821)).
    - `convert to kebab case`
    - `convert to snake case`
    - `convert to upper camel case`
    - `convert to lower camel case`
    - `convert to title case`
2023-08-04 22:51:32 -04:00
Joseph T. Lyons
12e8f417e4 Add more convert to case commands
ConvertToTitleCase
ConvertToSnakeCase
ConvertToKebabCase
ConvertToUpperCamelCase
ConvertToLowerCamelCase
2023-08-04 22:45:26 -04:00
Max Brunsfeld
2ccd153233 Fix joining descendant channels, style channel invites
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-04 16:21:43 -07:00
Joseph T. Lyons
5c2f38a0bc Add convert to {upper,lower} case commands (#2824)
Release Notes:

- Added `convert to upper case` and `convert to lower case` commands
([#1011](https://github.com/zed-industries/community/issues/1011)).
2023-08-04 18:12:55 -04:00
Max Brunsfeld
87b2d599c1 Flesh out channel member management
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-04 14:12:08 -07:00
Joseph T. Lyons
8c98b02e45 Add convert to {upper,lower} case commands
Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com>
2023-08-04 15:10:33 -04:00
Max Brunsfeld
a2486de045 Don't expose channel admin actions in UI if user isn't admin 2023-08-04 10:24:46 -07:00
Piotr Osiewicz
9889449a81 Adjust row count for project search.
Instead of using the same row count as for the breadcrumbs, we double the height so that there's some space for padding.

Co-authored-by: Kyle <kyle@zed.dev>
2023-08-04 18:54:27 +02:00
Max Brunsfeld
1762d2c6d4 Add test assertion where user is not admin of channel 2023-08-04 09:51:37 -07:00
Piotr Osiewicz
de8e1852a8 Align match count
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-04 18:21:46 +02:00
Piotr Osiewicz
8fa082c28b Center the query editor (for real now)
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-04 17:49:54 +02:00
Piotr Osiewicz
82eb6d8bc3 Change styling of match nav buttons
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-04 17:35:59 +02:00
Nathan Sobo
b6110fed9e WIP 2023-08-04 07:56:20 -06:00
Conrad Irwin
22927fa1d7 Fix visual selection cursor in multibuffers 2023-08-04 14:39:16 +01:00
Piotr Osiewicz
2c0e3886a5 Align search bar in the middle 2023-08-04 15:38:53 +02:00
Piotr Osiewicz
fcefb37ca0 Prevent modes from being vertically centered when row_count changes 2023-08-04 13:54:50 +02:00
Piotr Osiewicz
792f29e288 Refresh windows on toggle_filters. 2023-08-04 12:25:18 +02:00
Piotr Osiewicz
50d03ffc8c Revert "Focus filters when enabled"
This reverts commit 13a6b65a35.
2023-08-04 12:24:57 +02:00
Nathan Sobo
196946cbb6 Squelch warnings for now 2023-08-03 21:21:45 -06:00
Nathan Sobo
379652f074 Merge branch 'main' into cells 2023-08-03 19:33:04 -06:00
Max Brunsfeld
7a04ee3b71 Start work on exposing which channels the user has admin rights to 2023-08-03 18:31:00 -07:00
Max Brunsfeld
95b1ab9574 Implement channel member removal, permission check for member retrieval 2023-08-03 18:03:40 -07:00
Nathan Sobo
d3c1966d96 WIP: Return WindowHandle<V: View> from AppContext::add_window (#2820)
Instead of returning a usize for the window id, I'm instead returning a
`WindowHandle<V: View>` where `V` is the type of the window's root view.
@as-cii helped me with a cool technique using generic associated types
where methods on `WindowHandle` can return either T or Option<T>
depending on the `BorrowWindowContext::Result` associated type.

Some example usage...

```rs
let window = cx.add_window(|cx| MyView::new(cx));
let my_view = window.root(cx); // If cx is TestAppContext, returns MyView. Otherwise returns Option<MyView>, because the window could be closed.
```


This isn't insanely beneficial on its own, but I think it will help
clean up our testing story. I'm planning on making `window` more useful
in tests for laying out elements, etc.

- [x] Rework tests that call `add_window` 😅 to expect only a window in
return.
- [x] Get tests passing
- [x] 🚬  test
2023-08-03 18:45:51 -06:00
Piotr Osiewicz
13a6b65a35 Focus filters when enabled 2023-08-04 02:33:48 +02:00
Nathan Sobo
2d96388be3 Use WindowHandles in a couple places 2023-08-03 17:46:34 -06:00
Max Brunsfeld
4a6c73c6fd Lay-out channel modal with picker beneath channel name and mode buttons
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-03 16:15:29 -07:00
Nathan Sobo
485c0a482e Don't refcount window handles 2023-08-03 17:11:47 -06:00
Nathan Sobo
afcc0d621b WIP 2023-08-03 17:03:39 -06:00
Joseph T. Lyons
ee1b4a52cc Add PathExt trait (#2823)
This PR adds a `PathExt` trait. It pulls in our existing `compact()`
function, as a method, and then adds a method, and testing, for
`icon_suffix()`. A test was added to fix:

- https://github.com/zed-industries/community/issues/1877

Release Notes:

- Fixed a bug where file icons would not be registered for files with
with `.` characters in their name
([#1877](https://github.com/zed-industries/community/issues/1877)).
2023-08-03 18:57:43 -04:00
Max Brunsfeld
a7e883d956 Implement basic channel member management UI
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-03 14:49:01 -07:00
KCaverly
c14a99d8fa updated project_search text 2023-08-03 17:02:46 -04:00
Mikayla Maki
129f2890c5 simplify server implementation 2023-08-03 13:27:00 -07:00
Max Brunsfeld
9a1dd0c6bc Fetch channel members before constructing channel mgmt modal 2023-08-03 12:10:53 -07:00
Mikayla Maki
6c4964f071 WIP: continue channel management modal and rename panel to collab_panel 2023-08-03 11:40:55 -07:00
Piotr Osiewicz
4658bc610c Update styling of nav buttons (width, corner_radius)
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-03 20:35:10 +02:00
Piotr Osiewicz
31fb503418 Rewrite permission queries (it no longer blocks)
Co-authored-by: Kyle <kyle@zed.dev>
Co-authored-by: Max <max@zed.dev>
2023-08-03 20:26:56 +02:00
Mikayla Maki
d450c4be9a WIP: add custom channel modal 2023-08-03 10:59:09 -07:00
Piotr Osiewicz
d157e3598d Query semantic_permissioned on demand.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-03 19:27:27 +02:00
Piotr Osiewicz
799adf6c25 Query Semantic Index Engine for permission state.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-03 19:18:14 +02:00
KCaverly
8dd330a160 add semantic search prompt for the indexing permission on first search
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-03 12:52:20 -04:00
KCaverly
358e4e5ccf update minor text when actively searching 2023-08-03 10:22:31 -04:00
Piotr Osiewicz
822b1ec002 Clean up compiler warnings
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-03 16:12:08 +02:00
Nathan Sobo
3c938a7377 WIP 2023-08-03 08:10:16 -06:00
Piotr Osiewicz
8831e03eba Remove reference to pane from a toolbar.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-03 16:09:26 +02:00
Piotr Osiewicz
7d83d15bf3 Move navigation buttons to the tab bar.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-03 16:05:24 +02:00
Mikayla Maki
30e1bfc872 Add the ability to jump between channels while in a channel 2023-08-02 17:13:09 -07:00
Max Brunsfeld
0ae1f29be8 wip 2023-08-02 15:52:56 -07:00
Max Brunsfeld
4d55110452 Restore seeding of random GH users in seed-db
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-02 15:45:22 -07:00
Julia
ad4fd7619b Use the same font size for hovered state of LSP status (#2821)
This element is used for the update state as well for some reason so
while we don't normally ever see this state, it is used when the status
is acting as the restart to update button

Release Notes:

- Fixed an inconsistency in the status bar update button font size.
2023-08-02 18:16:39 -04:00
Max Brunsfeld
fca8cdcb8e Start work on rendering channel participants in collab panel
Co-authored-by: mikayla <mikayla@zed.dev>
2023-08-02 15:09:37 -07:00
Julia
df4480ba52 Use the same font size for hovered state of LSP status
This element is used for the update state as well for some reason so
while we don't normally ever see this state, it is used when the status
is acting as the restart to update button
2023-08-02 17:33:56 -04:00
KCaverly
b4f6d6eadc update search text for no results 2023-08-02 17:14:15 -04:00
Nathan Sobo
8e36da1382 Get tests passing 2023-08-02 15:02:55 -06:00
KCaverly
a125e318fe catch up with main 2023-08-02 16:48:11 -04:00
Nathan Sobo
884cee6dfd Get tests compiling returning WindowHandle<V: View> from add_window 2023-08-02 14:05:03 -06:00
Max Brunsfeld
9e755bb855 Revert "Extract syntax highlighting properties from tree-sitter highlight queries (#2797)"
This reverts commit 45c635872b, reversing
changes made to f2b82369f2.
2023-08-02 12:15:39 -07:00
Max Brunsfeld
a9de73739a WIP 2023-08-02 12:15:06 -07:00
KCaverly
71bbd5f2f6 update keymaps for cycle search mode and toggle filters 2023-08-02 15:08:54 -04:00
KCaverly
acf78f5fb4 add kill_search function to stop searching on mode change 2023-08-02 14:18:28 -04:00
Nathan Sobo
60e190e500 WIP 2023-08-02 12:08:56 -06:00
Joseph T. Lyons
b0ec05a732 v0.99.x dev 2023-08-02 13:50:30 -04:00
KCaverly
7b43b0d4f1 refactored search mode to ensure state is consistent
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-02 12:29:19 -04:00
Mikayla Maki
61a6892b8c WIP: Broadcast room updates to channel members 2023-08-02 09:21:30 -07:00
Max Brunsfeld
7d97d1dd8d Merge branch 'main' into collab-panel 2023-08-02 09:20:53 -07:00
Max Brunsfeld
a127b0d3e6 Fix warnings surfaced in Rust 1.71 2023-08-02 09:19:23 -07:00
Max Brunsfeld
a555fa1ada Merge branch 'main' into collab-panel 2023-08-02 09:08:50 -07:00
Max Brunsfeld
4c7d60ed13 Upgrade to rust 1.71 2023-08-02 09:08:08 -07:00
Piotr Osiewicz
5b30caa333 Add borders to button sides 2023-08-02 11:27:59 -04:00
KCaverly
caaa4b1618 add theme for search mode option buttons, and adjust padding
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-02 11:23:55 -04:00
Piotr Osiewicz
e0eaf23c28 Add borders to button sides 2023-08-02 16:33:35 +02:00
Nathan Sobo
300ce61bd0 WIP 2023-08-02 08:25:40 -06:00
Conrad Irwin
5f6535e92b TEMP 2023-08-02 15:06:46 +01:00
Piotr Osiewicz
d39585b240 Fix query editor 'floating' when filters are enabled 2023-08-02 14:05:09 +02:00
Piotr Osiewicz
0253ff3043 Touch up rounding on the sides of mode switcher 2023-08-02 13:52:02 +02:00
Piotr Osiewicz
4ef59899d1 WIP: Add ButtonSide element 2023-08-02 13:05:29 +02:00
Nathan Sobo
b695c42e11 WIP: Return WindowHandle<V: View> from AppContext::add_window 2023-08-01 22:28:04 -06:00
Nathan Sobo
1dfde8eab5 WIP 2023-08-01 20:53:15 -06:00
Mikayla Maki
7145f47454 Fix a few bugs in how channels are moved around 2023-08-01 18:42:14 -07:00
Max Brunsfeld
6a404dfe31 Start work on adding sub-channels in the UI
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-08-01 18:20:25 -07:00
Mikayla Maki
b389dcc637 Add subchannel creation
co-authored-by: max <max@zed.dev>
2023-08-01 16:48:11 -07:00
Mikayla Maki
74437b3988 Add remove channel method
Move test client fields into appstate and fix tests

Co-authored-by: max <max@zed.dev>
2023-08-01 16:06:27 -07:00
Piotr Osiewicz
e4871afaf3 Improve styling of mglass icon & search switches 2023-08-02 00:36:46 +02:00
Mikayla Maki
56d4d5d1a8 Add root channel UI
co-authored-by: Max <max@zed.dev>
2023-08-01 13:33:31 -07:00
Mikayla Maki
7434d66fdd WIP: Add channel creation to panel UI 2023-08-01 13:22:06 -07:00
Julia
5e9f7f10c0 Improve panic message usefulness on local dev builds (#2819)
I got tired of having to hack in a panic hook bypass whenever I wanted a
backtrace with line numbers. Now a dev channel build will behave more
like the default panic hook, printing a pretty traditional backtrace
message and exit with an error code instead of aborting to avoid the
annoying "Zed crashed" dialog.

I have plans to modify our panic reporting to be able to have line
numbers reported without breaking the de-duping but I haven't done that
yet.

Additionally I slightly improved what we do in threads which panic as a
result of another thread's panic.

Release Notes:

- N/A
2023-08-01 15:43:33 -04:00
Julia
3cee181f99 Improve panic message usefulness on local dev builds 2023-08-01 14:30:20 -04:00
KCaverly
cf060f0011 added major and minor display text to project search, and fixed icon padding
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-01 13:28:21 -04:00
KCaverly
7d5ff60ff0 added svg right margin in search bar
Co-authored-by: Piotr <piotr@zed.dev>
2023-08-01 12:34:02 -04:00
Piotr Osiewicz
00a9672eca Scale down the case insensitive icon
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-01 18:02:19 +02:00
Piotr Osiewicz
444b98e32f Update case insensitive button.
Co-authored-by: Kyle <kyle@zed.dev>
2023-08-01 17:56:09 +02:00
Joseph T. Lyons
eb26fb2d45 Fix variable names 2023-08-01 11:52:53 -04:00
Kyle Caverly
ce258dfeb9 Expanded Semantic language support (#2818)
Expand Language Support within Semantic Search

Release Notes (Preview-only)

- Added semantic search support for lua, ruby, php, svelte, erb, heex,
html and markdown.
- Full details are included here:
https://linear.app/zed-industries/issue/Z-2611/allow-semantic-search-for-all-supported-languages
2023-08-01 11:51:51 -04:00
KCaverly
c28ba3a11a add cycle mode to project search
Co-authored-by: Piotrek <piotr@zed.dev>
2023-08-01 11:47:30 -04:00
Piotr Osiewicz
2e2d0a3884 Hide whole word/case sensitive options under semantic search 2023-08-01 17:07:21 +02:00
KCaverly
300c693d55 catchup with main 2023-08-01 10:40:38 -04:00
KCaverly
e221f23018 add support for markdown files to semantic search 2023-08-01 10:30:34 -04:00
Piotr Osiewicz
b29a535f04 Use icons instead of text in toggles 2023-08-01 11:20:23 +02:00
KCaverly
9a50b43eaa add templating languages html, erb, heex, svelte as entire parseable file types 2023-07-31 21:03:02 -04:00
Max Brunsfeld
7954b02819 Start work on displaying channels and invites in collab panel 2023-07-31 18:00:14 -07:00
Piotr Osiewicz
a33d8519f2 Move buttons inside of query editor (WIP) 2023-08-01 02:06:40 +02:00
Mikayla Maki
003a711dea Add room creation from channel join
co-authored-by: max <max@zed.dev>
2023-07-31 16:54:12 -07:00
Mikayla Maki
92fa879b0c Add ability to join a room from a channel ID
co-authored-by: max <max@zed.dev>
2023-07-31 16:53:57 -07:00
Kirill Bulatov
06a0c9f82b Track history items in the buffer and project searches (#2817)
![image](https://github.com/zed-industries/zed/assets/2690773/53ce324f-2243-4cd9-a8aa-11f361c36860)

Tracks last N (20 currently) items in buffer and project search, allows
cycling through between them with up/down keys by default.

Release Notes:

- Track history in buffer and project searches
2023-08-01 01:44:58 +03:00
Piotr Osiewicz
52a48de9ca Add WIP Normal button (resuses parts of semantic button, gotta wire it proper) 2023-08-01 00:44:52 +02:00
Kirill Bulatov
634baeedb4 Add project search history 2023-08-01 01:31:28 +03:00
Kirill Bulatov
646dabe113 Add buffer search history 2023-08-01 01:31:28 +03:00
KCaverly
599f674827 add php support for semantic search 2023-07-31 16:36:09 -04:00
Mikayla Maki
ef57d444d0 Halve opacity on wrap guides (#2815)
Wrap guides are a little too bright as is
2023-07-31 10:59:34 -07:00
Mikayla Maki
88474a6048 Clip wrap guides from under the scrollbar 2023-07-31 10:54:29 -07:00
Nate Butler
b530aabff3 Additional storage filetypes (#2816)
Been working with some db stuff and thought it would be nice to add a
few more associations

Release Notes:

- Added additional filetype associations in the project browser
2023-07-31 13:32:15 -04:00
Nate Butler
bb288eb941 Ensure json uses a tab size of 4 2023-07-31 13:08:40 -04:00
Nate Butler
c4709418d1 Format 2023-07-31 12:50:30 -04:00
Nate Butler
e07a81b225 Add additional storage filetypes 2023-07-31 12:49:55 -04:00
KCaverly
89edb3d1b5 fix templating bug for parseable entire files 2023-07-31 11:41:18 -04:00
Julia
49ef3e35e3 Put LiveKitBridge Swift build directory in target (#2812)
Helps it get caught in a cargo clean. Joseph was having trouble building
a specific version of the app and deleting the Swift build dir for this
package resolved it. He had run cargo clean which would have handled
that if the Swift build dir was in `target` which this patch does


Release Notes:

- N/A
2023-07-31 11:25:41 -04:00
KCaverly
ca4e21881e add ruby support for semantic search 2023-07-31 10:54:30 -04:00
KCaverly
a5dd8dd0a9 add lua embedding query for semantic search 2023-07-31 10:02:28 -04:00
Mikayla Maki
8926266952 Halve opacity on wrap guides 2023-07-29 23:53:16 -07:00
Mikayla Maki
6184c60278 disable wrap guides in the assitant panel (#2814)
Wrap guides do not look correct in the assistant due to it's current
header styling. Disable them in that context now.

Release Notes:

- Fix a visual bug displaying when enabling wrap guides in the
assistant.
2023-07-28 22:39:30 -07:00
Mikayla Maki
d58f031696 disable wrap guides in the assitant panel 2023-07-28 22:27:36 -07:00
Conrad Irwin
645c149344 Fix visual selection of trailing newline 2023-07-28 22:38:39 -06:00
Joseph T. Lyons
0bd6e7bac3 Fix comment 2023-07-28 23:13:36 -04:00
Joseph T. Lyons
b0e81c58dc Remove unused code in test 2023-07-28 23:06:40 -04:00
Joseph T. Lyons
525c8dacbc Add a command to collapse all entires (#2813)
Release Notes:

- Added a `project panel: collapse all entries` command
([#158](https://github.com/zed-industries/community/issues/158)).
2023-07-28 22:44:08 -04:00
Joseph T. Lyons
2c47efcce9 Add a command to collapse all entires 2023-07-28 22:36:15 -04:00
Max Brunsfeld
4b94bfa045 Set up basic RPC for managing channels
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-07-28 17:05:56 -07:00
Julia
fe43bacb6f Put LiveKitBridge Swift build directory in target
Helps it get caught in a cargo clean
2023-07-28 18:53:24 -04:00
Nathan Sobo
6f6096238d WIP 2023-07-28 16:44:15 -06:00
Mikayla Maki
4626981b25 Make wrap guides respect scroll position (#2810)
Release Notes:

- Fixed a visual bug when scrolling with wrap guides active
2023-07-28 15:08:52 -07:00
Mikayla Maki
d3b89e16f2 Make wrap guides respect scroll position 2023-07-28 14:56:13 -07:00
Max Brunsfeld
758e1f6e57 Get DB channels query working with postgres
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-07-28 14:56:02 -07:00
Conrad Irwin
9cb0ce7745 Review 2023-07-28 15:36:14 -06:00
Conrad Irwin
0c15ef7305 Merge VisualChange -> Substitute
They both are supposed to work the same.
2023-07-28 15:32:02 -06:00
Nathan Sobo
6d10328796 WIP 2023-07-28 15:29:29 -06:00
Conrad Irwin
236b755b1d Fix substitute in visual line mode 2023-07-28 15:26:40 -06:00
Conrad Irwin
e3788cc6e6 Add o/O for flipping selection 2023-07-28 15:26:40 -06:00
Conrad Irwin
3f2f3bb78d Fix crash when deleting a long line in visual line mode 2023-07-28 15:26:40 -06:00
Conrad Irwin
5edcb74760 Add support for visual ranges ending with a newline
These primarily happen when first entering visual mode, but can also
be created with objects like `vi{`.

Along the way fix the way ranges like `vi{` are selected to be more
similar to nvim.
2023-07-28 15:26:40 -06:00
Conrad Irwin
b53fb8633e Fix vim selection to include entire range
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
2023-07-28 15:26:40 -06:00
Nathan Sobo
4d66c3237d WIP 2023-07-28 15:01:45 -06:00
Mikayla Maki
0998440bdd implement recursive channel query 2023-07-28 13:24:43 -07:00
Mikayla Maki
15631a6fd5 Add channel_tests.rs 2023-07-28 13:24:43 -07:00
Mikayla Maki
26a94b5244 WIP: Channel CRUD 2023-07-28 13:24:43 -07:00
Max Brunsfeld
bb70901e71 WIP 2023-07-28 13:24:43 -07:00
Mikayla Maki
40c293e184 Add channel_modal file 2023-07-28 13:24:43 -07:00
Mikayla Maki
1549c2274f Create channel adding modal 2023-07-28 13:24:43 -07:00
Max Brunsfeld
4a088fc4ae Make major collab panel headers non-interactive 2023-07-28 13:24:43 -07:00
Max Brunsfeld
fc49194535 Restructure collab panel, make contact finder into a normal modal 2023-07-28 13:24:43 -07:00
Max Brunsfeld
14fdcadcfc Add seemingly-redundant export in theme src file to workaround theme build error 2023-07-28 13:24:43 -07:00
Max Brunsfeld
87dfce94ae Rename contact list theme to collab panel 2023-07-28 13:24:43 -07:00
Max Brunsfeld
969ecfcfa2 Reinstate all of the contacts popovers' functionality in the new collaboration panel 2023-07-28 13:24:43 -07:00
Mikayla Maki
7f9df6dd24 Move channels panel into collab and rename to collab panel
remove contacts popover and add to collab panel
2023-07-28 13:24:42 -07:00
Mikayla Maki
fe5db3035f move channels UI code to channels-rpc 2023-07-28 13:21:41 -07:00
Mikayla Maki
ac35dae66e Add channels panel with stubbed out information
co-authored-by: nate <nate@zed.dev>
2023-07-28 13:21:39 -07:00
Julia
e6f3e0ab9c Reattempt Node installation if the installation itself errors (#2808)
This also makes us a bit more aggressive about reinstalling Node

Fixes
https://linear.app/zed-industries/issue/Z-2697/language-server-error-html-failed-to-iterate-over-archive

Release Notes:
- Improved the Node runtime re-installation mechanism for language
servers.
2023-07-28 15:49:42 -04:00
Julia
46101bf110 Reattempt Node installation if the installation itself errors
This also makes us a bit more aggressive about reinstalling Node
2023-07-28 15:24:40 -04:00
Joseph T. Lyons
b8690ec1d1 Update release action to choose between preview and stable URL in Discord announcements
This is what ChatGPT told me, so we'll see.
2023-07-28 15:12:37 -04:00
Conrad Irwin
5d8370e2a1 Don't highlight project search matches either (#2807)
@JosephTLyons this is probably worth merging alongside #2803

- vim: Fix a bug where focusing project search results unexpectedly
entered visual mode
2023-07-28 12:25:32 -06:00
Conrad Irwin
fac0e2dd56 Don't highlight project search matches either 2023-07-28 12:17:32 -06:00
Kirill Bulatov
97042eb5aa Make project search includes and excludes more user-friendly (#2806)
Closes
https://linear.app/zed-industries/issue/Z-2707/make-inclusionexclusion-with-non-globs-more-intuitive

Allow search results that start with the include/exclude path part.

![image](https://github.com/zed-industries/zed/assets/2690773/ef48ca8e-f8fd-41b2-a656-c31dc7712a11)


Release Notes:

- Improved project search include/exclude filters' usability: allow path
entries along with the glob ones
2023-07-28 13:38:53 +03:00
Kirill Bulatov
cf6e524c9a Make project search includes and excludes more user-friendly
Allow search results that start with the include/exclude path part
2023-07-28 12:56:44 +03:00
Nathan Sobo
dc8e7acca0 WIP 2023-07-27 23:52:41 -06:00
Nathan Sobo
239024acd6 WIP 2023-07-27 23:26:12 -06:00
Nathan Sobo
33e49b4962 WIP 2023-07-27 23:00:52 -06:00
Mikayla Maki
1545128ec5 Add Nix language support (#2781)
This adds rudimentary language support for the Nix expression language,
through tree-sitter-nix.

I spent a little bit of time trying to add support for one of the Nix
language servers too, but wasn't able to get any of them running
reliably without crashing, and so I've opted to stick to just the
tree-sitter grammar for now.


![image](https://github.com/zed-industries/zed/assets/285821/c770f3d8-6fa0-4083-9bf3-239cc78ca307)
2023-07-27 18:35:52 -07:00
Mikayla Maki
e945b3c0e1 feat(workspace): add action for closing inactive editors on all panes (#2771)
using zed more and more to develop zed itself I'm finding some small qol
features missing, this is one of them
I'm very used to open two or three splits, and sometimes I want to close
everything except for the active editor, but that wasn't supported, as
the `pane::CloseInactiveItems` action only closes inactive items on the
active pane

so I've implemented it really quick, although I'm not sure it's the
right way to do this

note: I really don't like the default keybinding I've set it to, I have
this action bound to `cmd-shift-w` on all editors, but in zed is taken,
so I chose something that's free but without thinking too much about it

Release Notes:

- Added action for closing inactive editors from all panes
2023-07-27 18:11:11 -07:00
Conrad Irwin
ade8d4d167 Fix jumping to definition in a new file (#2803)
This is broken because vim currently sets settings only on the active
editor. Fix this by correcting the range on the currently active editor.

It would be nice (at some point) to refactor how vim sets settings, but
that's for another day.

Release Notes:

- vim: Fix bug when jumping to definition in new file accidentally
entered visual mode.
2023-07-27 19:10:01 -06:00
Mikayla Maki
dc5b23e9bb Update elixir depedency (#2805)
Fixes https://github.com/zed-industries/community/issues/1761

Redo of https://github.com/zed-industries/zed/pull/2734 

This PR increases the version further to one that does not have the
symbol collision issue we found with elm-tree-sitter

Release Notes:

- N/A
2023-07-27 18:01:16 -07:00
Mikayla Maki
4735b07088 Fix warning 2023-07-27 18:00:33 -07:00
Mikayla Maki
a0fc515cfc Rework close_inactive_items to await all tasks
Update action name to be more accurate
2023-07-27 17:58:48 -07:00
Mikayla Maki
45e5d81664 update to dependency without symbol conflict 2023-07-27 17:41:13 -07:00
Mikayla Maki
0dffb728db Update elixir depedency
co-authored-by: Alex <alexviscreanu@gmail.com>
2023-07-27 17:36:09 -07:00
Mikayla Maki
549769491e Underscore arguments in conditionally compiled code (#2804)
For some reason, our bundle script doesn't fail on warning, so this
hasn't been caught yet.

Release Notes:

- N/A
2023-07-27 17:32:00 -07:00
Nathan Sobo
480401d65d WIP 2023-07-27 18:23:23 -06:00
Mikayla Maki
f15a03816f underscore arguments 2023-07-27 17:19:32 -07:00
Conrad Irwin
1935307b4f Fix jumping to definition in a new file
This is broken because vim currently sets settings only on the active
editor. Fix this by correcting the range on the currently active editor.

It would be nice (at some point) to refactor how vim sets settings, but
that's for another day.
2023-07-27 18:10:17 -06:00
Mikayla Maki
ae765bbca3 Make mode indicator follow vim enabled state (#2802)
There was a minor visual bug introduced in
https://github.com/zed-industries/zed/pull/2801, this PR corrects it.

Release Notes:

- N/A
2023-07-27 16:30:49 -07:00
Mikayla Maki
03bc430bdd Make mode indicator follow vim enabled state 2023-07-27 16:14:56 -07:00
Mikayla Maki
17fa15d989 Avoid panic by accessing view handle by global in wrong window (#2801)
View handles are window specific but the Vim global will be doing things
in all windows, that would cause a panic when Vim attempted to update a
status bar mode indicator in a background window

Release Notes:

- N/A
2023-07-27 16:02:46 -07:00
Julia
fc9687d163 Avoid panic by accessing view handle by global in wrong window
View handles are window specific but this global will be doing things
in all windows, that would cause a panic when it attempted to update
a status bar mode indicator in a background window

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-07-27 18:53:05 -04:00
Joseph T. Lyons
3979454485 Follow naming convention for menu items 2023-07-27 17:13:37 -04:00
Joseph T. Lyons
e3201857ce Publish preview releases to discord (#2800)
Release Notes:

- N/A
2023-07-27 15:26:08 -04:00
Joseph T. Lyons
7a9af7c663 Publish preview releases to discord 2023-07-27 15:17:13 -04:00
Julia
6a0e1d5345 Update Alacritty (#2798)
This will potentially fix a number of Alacritty related panics, however
it is tricky to figure out which ones in particular will be impacted for
sure. We've said we were going to do this for a while but with the
constant trickle of panics it seemed reasonable to just go and do it

Release Notes:

- Fixed a few potential terminal related crashes.
2023-07-27 15:03:53 -04:00
Nathan Sobo
2ef19e48bc WIP 2023-07-27 13:00:28 -06:00
Nathan Sobo
70c9959ebc WIP 2023-07-27 12:35:54 -06:00
Mikayla Maki
0455311371 Downgrade our dependency on treesitter-cpp (#2799)
Our dependency on `tree-sitter-cpp` got upgraded to an incompatible
version despite semver 'guarantees'. This pins the dependency onto the
commit of version 0.20.0

Release Notes:

- Restored language detection for C++ (preview-only)
2023-07-27 11:20:29 -07:00
Mikayla Maki
a829b5be01 fmt 2023-07-27 11:14:21 -07:00
Mikayla Maki
d835274306 Downgrade our dependency on treesitter-cpp 2023-07-27 11:11:24 -07:00
Nate Butler
27931663d9 chore: add theme types docs (#2737)
Release Notes:

- Added documentation about theme types generation
2023-07-27 13:21:31 -04:00
Nate Butler
45c635872b Extract syntax highlighting properties from tree-sitter highlight queries (#2797)
This should be a purely internal change. Let me know if any visual
changes are observed from this!

### Syntax theme
- Update the theme to allow any syntax highlighting property used in any
`highlight.scm` to be styled
- Only define syntax styles that are baked into the default theme &
don't use the default text style
- Adds the `generate-syntax` command
- Removes a few unused properties that we were styling for some reason,
like `enum` and `variant`, neither of which exist in any `highlight.scm`
- Moves `@constructor` symbols to `@method.constructor` to prevent
issues with `constructor` being a reserved property in ts/js.

Syntax is now build as the theme is created rather than as part of the
styleTree. This means it no longer requires a compiled `Theme`, which
makes things a bit more straightforward if we need to access it in other
components that should be built before the styleTrees.

### Scheme

Also updates all uses of `#match` in our `highlights.scm` files, as
these break the scheme tree-sitter query. This fixes _most_ instances of
our scheme highlighting breaking.

For some reason something in here breaks the `highlights.scm` for PHP:

```scheme
((name) @constant.builtin
 (.match? @constant.builtin "^__[A-Z][A-Z\d_]+__$"))

((name) @method.constructor
(.match? @method.constructor "^[A-Z]"))

((name) @variable.builtin
 (.eq? @variable.builtin "this"))
```

Release Notes:

- No public facing changes
2023-07-27 13:14:01 -04:00
Nate Butler
b08a2770b8 Remove redundant syntax_highlights 2023-07-27 13:02:40 -04:00
Nate Butler
b9d5cc5828 Format 2023-07-27 12:56:54 -04:00
Nate Butler
0b7e75c25a Add the generate-syntax action 2023-07-27 12:55:32 -04:00
Nate Butler
86fa27eb54 Update uses of # to . in our scheme files where they are interchangeable.
uses of `#` cause ERRORs in our scheme highlighting
2023-07-27 12:41:19 -04:00
Nate Butler
85f193dd09 Extract syntax highlighting properties from tree-sitter highlight queries 2023-07-27 12:25:53 -04:00
Julia
8c9c8362ec Update Alacritty 2023-07-27 12:19:07 -04:00
Julia
f2b82369f2 Make LSP status message match new font size of diagnostic message (#2796)
The status bar diagnostic message font size was updated in
1f65effe57
but the LSP status message font size remained the same causing a font
size change when swapping between the two

![CleanShot 2023-07-26 at 18 32
13](https://github.com/zed-industries/zed/assets/30666851/8d357f46-3a48-4ed1-9832-d24b4b2be4a8)
![CleanShot 2023-07-26 at 18 30
56](https://github.com/zed-industries/zed/assets/30666851/e4ce9911-f66d-4bc3-b951-78196da44176)

Release Notes:

- Fixed an inconsistency in status bar font size.
2023-07-27 11:16:28 -04:00
Piotr Osiewicz
b9cdb851d2 Update results text 2023-07-27 16:31:24 +02:00
Julia
0ab1c6ac8e Make LSP status message match new font size of diagnostic message 2023-07-27 10:25:46 -04:00
Piotr Osiewicz
dff9bf7d7e Make row_count of toolbaritem dynamic (WIP).
Move result count to the left hand side.
2023-07-27 16:03:26 +02:00
Antonio Scandurra
46dd717857 Maintain cursor stack's position correctly when ascending the tree (#2795)
This fixes a bug that could cause the cursor to incorrectly report its
start when using `slice` or `seek_forward`, and then calling `prev`. We
didn't notice this because we were not testing those three methods
together.

I suppose this could explain some of the panics we've observed because
we do use `slice`/`seek_forward` followed by `prev` calls in production.
2023-07-27 13:51:09 +02:00
Piotr Osiewicz
8ca1e0b15b Add dummy filter icon 2023-07-27 13:09:19 +02:00
Piotr Osiewicz
4761197d62 Add filter button.
Move semantic & regex buttons to the right hand side.
Add default tab name for the new project search.
2023-07-27 13:08:31 +02:00
Antonio Scandurra
2e0d051a78 Maintain cursor stack's position correctly when ascending the tree
This fixes a bug that could cause the cursor to incorrectly report its
start when using `slice` or `seek_forward`, and then calling `prev`. We
didn't notice this because we were not testing those three methods
together.

I suppose this could explain some of the panics we've observed because
we do use `slice`/`seek_forward` followed by `prev` calls in production.
2023-07-27 12:34:03 +02:00
Piotr Osiewicz
f2a35a7e1d Use a three-way layout 2023-07-27 12:18:19 +02:00
Piotr Osiewicz
91c32ed307 WIP: project search redesign 2023-07-27 11:43:32 +02:00
Nathan Sobo
ab8906551d WIP 2023-07-26 22:37:59 -06:00
Mikayla Maki
129e711545 Downgrade tree sitter elm to 5.6.4 (#2794)
The tree sitter elm parser contains a c symbol which collides with other
linked symbols. This PR downgrades the tree sitter elm parser to a
version which doesn't have this problem.

Release Notes:
- Fixed crash when parsing elm files
2023-07-26 15:54:55 -07:00
Mikayla Maki
d5c30709b3 Downgrade tree sitter elm to 5.6.4 2023-07-26 15:44:06 -07:00
Mikayla Maki
b8a2a2ecd3 Block extra drag events in original drag handlers (#2793)
In https://github.com/zed-industries/zed/pull/2790 I added an extra drag
event on mouse_up which signaled the end of a drag event, as mouse_up
event themselves wouldn't reliably fire if users moved their mouse too
quickly. This broke the assumptions of the terminal element. This PR
adds filters to all current on_drag handlers which removes this new
event.

Release Notes:

- Fixed a bug causing terminal links to never open (preview only)
- Fixed a bug in terminal link detection causing it to miss files with a
`-` in it
2023-07-26 15:15:31 -07:00
Mikayla Maki
354c020612 Block extra drag events in original drag handlers 2023-07-26 14:57:46 -07:00
Kyle Caverly
ee66f99ce6 Parallel vector db (#2792)
Parallelize Vector Database calls for project semantic search.

Release Notes: (Preview-only)

- Parallelize Vector database calls for project semantic search. Cuts
query time by 2/3rds.
- Removed default keymap for old semantic search modal.
2023-07-26 17:17:59 -04:00
KCaverly
fbede4a5a3 removed old code 2023-07-26 17:11:30 -04:00
KCaverly
e2b38f7a31 remove unused imports 2023-07-26 17:01:44 -04:00
KCaverly
06167889c3 remove default keymap for semantic search modal 2023-07-26 16:59:19 -04:00
KCaverly
5c48729c7c managed for small batch size edge case in semantic search 2023-07-26 16:56:41 -04:00
KCaverly
89bbcdfa4f remove debug logging for project_search semantic search 2023-07-26 16:51:25 -04:00
KCaverly
98fde36834 batch search queries in the vector database 2023-07-26 16:36:39 -04:00
Nathan Sobo
c602d98680 WIP 2023-07-26 13:34:57 -06:00
Nathan Sobo
0374bc66ce WIP 2023-07-26 12:54:23 -06:00
Joseph T. Lyons
40fcec1495 Follow naming convention for menu items 2023-07-26 14:35:23 -04:00
Joseph T. Lyons
6cd10f3d5e v0.98.x dev 2023-07-26 13:27:55 -04:00
Nathan Sobo
c48d8af204 Merge branch 'paint-context' into cells 2023-07-25 17:36:05 -06:00
Nathan Sobo
1b03c5d69c Pass PaintContext to Element::paint
I want to use this on another branch, but it's a sweeping change,
so this prepares the ground for it. This can always be reverted if
it doesn't work out.
2023-07-25 17:32:31 -06:00
Nathan Sobo
df9c17176e WIP 2023-07-25 16:33:58 -06:00
Mikayla Maki
b5630eb901 Merge branch 'main' into quinn/nix 2023-07-25 11:34:57 -07:00
Nathan Sobo
54a7419fa2 WIP 2023-07-24 23:27:14 -06:00
Quinn Wilton
fe388ed71e Add tree-sitter-nix 2023-07-23 17:24:50 -07:00
Alex Viscreanu
1610e270d6 feat(workspace): add action for closing inactive editors on all panes 2023-07-21 13:16:00 +02:00
Nathan Sobo
19e4cad7a9 WIP 2023-07-18 13:32:07 -06:00
Nathan Sobo
18b0385d73 WIP 2023-07-17 16:48:31 -06:00
Nathan Sobo
f5682a3cb5 Paint basic children
Co-Authored-By: Derek Briggs <derek.briggs@me.com>
2023-07-17 16:39:58 -06:00
Sergey Onufrienko
cb8762d855 chore: add theme types docs 2023-07-17 17:54:37 +01:00
Nathan Sobo
248f5dfd4b Get a red box on screen
Co-Authored-By: Derek Briggs <derek.briggs@me.com>
2023-07-14 17:03:07 -06:00
Nathan Sobo
87bafb04e2 Get playground app launching
Not sure if it should be in the workspace, but it's easier for now.

Co-Authored-By: Derek Briggs <derek.briggs@me.com>
2023-07-14 15:34:31 -06:00
Nathan Sobo
4b2054215a Get cell compiling 2023-07-14 15:20:53 -06:00
Nathan Sobo
0db8f681ba WIP 2023-07-14 11:58:43 -06:00
Sergey Onufrienko
036d3e811a feat: add low, high, range and scaling 2023-07-13 22:09:31 +01:00
Sergey Onufrienko
fbf1552be9 Add color_family to theme 2023-07-10 20:41:39 +01:00
512 changed files with 53704 additions and 18770 deletions

View File

@@ -11,7 +11,7 @@ env:
jobs:
publish:
name: Publish collab server image
name: Publish collab server image
runs-on:
- self-hosted
- deploy
@@ -22,6 +22,9 @@ jobs:
- name: Sign into DigitalOcean docker registry
run: doctl registry login
- name: Prune Docker system
run: docker system prune
- name: Checkout repo
uses: actions/checkout@v3
with:
@@ -41,6 +44,6 @@ jobs:
- name: Build docker image
run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
- name: Publish docker image
run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}

View File

@@ -6,14 +6,23 @@ jobs:
discord_release:
runs-on: ubuntu-latest
steps:
- name: Get appropriate URL
id: get-appropriate-url
run: |
if [ "${{ github.event.release.prerelease }}" == "true" ]; then
URL="https://zed.dev/releases/preview/latest"
else
URL="https://zed.dev/releases/stable/latest"
fi
echo "::set-output name=URL::$URL"
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
if: ${{ ! github.event.release.prerelease }}
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: |
📣 Zed ${{ github.event.release.tag_name }} was just released!
Restart your Zed or head to https://zed.dev/releases/stable/latest to grab it.
Restart your Zed or head to ${{ steps.get-appropriate-url.outputs.URL }} to grab it.
${{ github.event.release.body }}

5
.zed/settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"JSON": {
"tab_size": 4
}
}

1098
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ members = [
"crates/auto_update",
"crates/breadcrumbs",
"crates/call",
"crates/channel",
"crates/cli",
"crates/client",
"crates/clock",
@@ -13,10 +14,13 @@ members = [
"crates/collab_ui",
"crates/collections",
"crates/command_palette",
"crates/component_test",
"crates/context_menu",
"crates/copilot",
"crates/copilot_button",
"crates/db",
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/diagnostics",
"crates/drag_and_drop",
"crates/editor",
@@ -28,6 +32,10 @@ members = [
"crates/git",
"crates/go_to_line",
"crates/gpui",
"crates/gpui_mac",
"crates/gpui_platform",
"crates/gpui/playground",
"crates/gpui/playground_macros",
"crates/gpui_macros",
"crates/install_cli",
"crates/journal",
@@ -56,7 +64,7 @@ members = [
"crates/snippet",
"crates/sqlez",
"crates/sqlez_macros",
"crates/staff_mode",
"crates/feature_flags",
"crates/sum_tree",
"crates/terminal",
"crates/text",
@@ -79,6 +87,7 @@ resolver = "2"
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
ctor = { version = "0.1" }
derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
globset = { version = "0.4" }
@@ -90,9 +99,11 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = { version = "2.1.1" }
parking_lot = { version = "0.11.1" }
postage = { version = "0.5", features = ["futures-traits"] }
prost = { version = "0.8" }
rand = { version = "0.8.5" }
refineable = { path = "./crates/refineable" }
regex = { version = "1.5" }
rust-embed = { version = "6.3", features = ["include-exclude"] }
rust-embed = { version = "8.0", features = ["include-exclude"] }
schemars = { version = "0.8" }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
@@ -109,9 +120,9 @@ pretty_assertions = "1.3.0"
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" }
tree-sitter-c = "0.20.1"
tree-sitter-cpp = "0.20.0"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
tree-sitter-embedded-template = "0.20.0"
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
@@ -131,9 +142,10 @@ tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", r
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"}
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1c65ca24bc9a734ab70115188f465e12eecf224e" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }
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

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.70-bullseye as builder
FROM rust:1.72-bullseye as builder
WORKDIR app
COPY . .

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,93 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

23
assets/icons/ai.svg Normal file
View File

@@ -0,0 +1,23 @@
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 8.94203V11C7.38649 11 6.61351 11 4 11V10.6812L10 5.31884V5H4V7.08696" stroke="black" stroke-width="1.25"/>
<circle cx="0.5" cy="8" r="0.5" fill="black"/>
<circle cx="1.49976" cy="5.82825" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="1.49976" cy="10.1719" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="13.5" cy="8.01581" r="0.5" fill="black"/>
<circle cx="12.5" cy="5.84387" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="12.5" cy="10.1877" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="6.99219" cy="1.48438" r="0.5" fill="black"/>
<circle cx="4.5" cy="2.5" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="0.5" cy="12.016" r="0.5" fill="black"/>
<circle cx="0.5" cy="3.98438" r="0.5" fill="black"/>
<circle cx="13.5" cy="12.016" r="0.5" fill="black"/>
<circle cx="13.5" cy="3.98438" r="0.5" fill="black"/>
<circle cx="2.49976" cy="14.516" r="0.5" fill="black"/>
<circle cx="2.48413" cy="1.48438" r="0.5" fill="black"/>
<circle cx="11.5" cy="14.516" r="0.5" fill="black"/>
<circle cx="11.5" cy="1.48438" r="0.5" fill="black"/>
<circle cx="9.49609" cy="2.48438" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="6.99219" cy="14.5" r="0.5" fill="black"/>
<circle cx="4.50391" cy="13.516" r="0.5" fill="black" fill-opacity="0.75"/>
<circle cx="9.49609" cy="13.5" r="0.5" fill="black" fill-opacity="0.75"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

After

Width:  |  Height:  |  Size: 275 B

View File

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

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12px" height="12px" viewBox="0 0 12 12" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 2.976562 2.746094 L 4.226562 2.746094 L 6.105469 9.296875 L 5.285156 9.296875 L 4.804688 7.640625 L 2.386719 7.640625 L 1.914062 9.296875 L 1.097656 9.296875 Z M 4.621094 6.917969 L 3.640625 3.449219 L 3.5625 3.449219 L 2.582031 6.917969 Z M 4.621094 6.917969 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 2.878906 2.617188 L 4.324219 2.617188 L 6.277344 9.425781 L 5.191406 9.425781 L 4.707031 7.769531 L 2.484375 7.769531 L 2.011719 9.425781 L 0.925781 9.425781 Z M 3.601562 3.785156 L 2.75 6.789062 L 4.453125 6.789062 Z M 3.601562 3.785156 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 7.285156 9.378906 L 7.28125 9.378906 C 7.03125 9.277344 6.851562 9.101562 6.738281 8.859375 L 6.738281 8.855469 C 6.636719 8.621094 6.59375 8.296875 6.59375 7.894531 C 6.59375 7.421875 6.660156 7.035156 6.8125 6.757812 C 6.964844 6.464844 7.21875 6.265625 7.5625 6.152344 C 7.90625 6.039062 8.375 5.980469 8.96875 5.980469 L 9.75 5.980469 L 9.75 5.867188 C 9.75 5.605469 9.71875 5.417969 9.65625 5.292969 L 9.65625 5.289062 C 9.605469 5.175781 9.519531 5.09375 9.378906 5.039062 C 9.238281 4.984375 9.023438 4.949219 8.726562 4.949219 C 8.535156 4.949219 8.378906 4.964844 8.253906 4.988281 C 8.128906 5.011719 8.046875 5.042969 8 5.082031 L 7.996094 5.082031 C 7.902344 5.144531 7.832031 5.285156 7.820312 5.554688 L 7.8125 5.675781 L 6.746094 5.675781 L 6.746094 5.546875 C 6.746094 5.164062 6.804688 4.851562 6.925781 4.625 C 7.054688 4.382812 7.273438 4.21875 7.5625 4.128906 C 7.847656 4.03125 8.238281 3.984375 8.726562 3.984375 C 9.238281 3.984375 9.640625 4.039062 9.933594 4.148438 C 10.242188 4.261719 10.464844 4.464844 10.589844 4.75 C 10.714844 5.027344 10.773438 5.402344 10.773438 5.867188 L 10.773438 9.429688 L 9.78125 9.429688 L 9.78125 8.964844 C 9.644531 9.140625 9.488281 9.269531 9.316406 9.355469 C 9.078125 9.472656 8.730469 9.523438 8.289062 9.523438 C 7.871094 9.523438 7.53125 9.480469 7.285156 9.378906 Z M 8.058594 7.039062 C 7.914062 7.085938 7.816406 7.167969 7.753906 7.277344 C 7.699219 7.386719 7.664062 7.558594 7.664062 7.808594 C 7.664062 8.132812 7.730469 8.3125 7.824219 8.398438 C 7.921875 8.480469 8.132812 8.542969 8.496094 8.542969 C 8.84375 8.542969 9.097656 8.488281 9.261719 8.394531 C 9.421875 8.296875 9.546875 8.136719 9.621094 7.894531 C 9.691406 7.671875 9.734375 7.351562 9.742188 6.929688 L 8.777344 6.9375 C 8.460938 6.945312 8.222656 6.980469 8.0625 7.035156 Z M 8.058594 7.039062 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="10.2795" y1="2.63847" x2="7.74785" y2="11.0142" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="6.26624" y1="2.99597" x2="3.7346" y2="11.3717" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="3.15982" y1="5.3799" x2="11.9098" y2="5.3799" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="2.0983" y1="8.62407" x2="10.8483" y2="8.62407" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 571 B

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

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.98438 7.85115L6.13569 9.44983L9.98438 4.08141" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 8L6.5 9L9 5.5" stroke="#11181C" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="7" cy="7" r="4.875" stroke="#11181C" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.63281 5.66406L6.99344 8.89844L10.3672 5.66406" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

View File

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

After

Width:  |  Height:  |  Size: 244 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.64062 3.64062L8.89062 7.00125L5.64062 10.375" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.63281 8.36719L6.99344 5.13281L10.3672 8.36719" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.46115 8.43419C7.30678 8.43419 8.92229 7.43411 8.92229 5.21171C8.92229 2.98933 7.30678 1.98926 5.46115 1.98926C3.61553 1.98926 2 2.98933 2 5.21171C2 6.028 2.21794 6.67935 2.58519 7.17685C2.7184 7.35732 2.69033 7.77795 2.58387 7.97539C2.32908 8.44793 2.81048 8.9657 3.33372 8.84571C3.72539 8.75597 4.13621 8.63447 4.49574 8.4715C4.62736 8.41181 4.7727 8.38777 4.91631 8.40402C5.09471 8.42416 5.27678 8.43419 5.46115 8.43419Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4661 10.6353C10.4697 10.7833 10.4914 10.9562 10.5423 11.1245C10.2394 11.0477 9.94714 10.9535 9.69757 10.8403C9.44717 10.7269 9.1668 10.6793 8.88556 10.7111C8.73612 10.728 8.58194 10.7365 8.42443 10.7365C7.68587 10.7365 7.04509 10.5503 6.58359 10.213C6.25127 9.97033 5.78501 10.0428 5.54218 10.3751C5.29939 10.7075 5.37193 11.1737 5.70427 11.4165C6.48017 11.9834 7.45185 12.2271 8.42443 12.2271C8.6356 12.2271 8.84564 12.2156 9.05296 12.1921C9.05904 12.1914 9.06942 12.1921 9.08212 12.1979C9.50348 12.3888 9.9667 12.5238 10.3854 12.6198C10.933 12.7453 11.4558 12.536 11.7761 12.1748C11.9716 11.9544 12.0298 11.6167 12.043 11.361C12.0564 11.1006 12.0238 10.8609 11.9375 10.6152C12.3875 9.98145 12.6308 9.18769 12.6308 8.2593C12.6308 7.23782 12.3361 6.3809 11.7994 5.72187C11.5395 5.4027 11.07 5.35466 10.7509 5.61459C10.4318 5.87448 10.3837 6.34387 10.6436 6.66305M10.4661 10.6353C10.4612 10.4326 10.4844 10.075 10.7008 9.78189C10.9613 9.42893 11.1403 8.93793 11.1403 8.2593C11.1403 7.53473 10.9364 7.0226 10.6436 6.66305" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

9
assets/icons/copilot.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.64063 7.67017C5.97718 7.67017 6.25 7.94437 6.25 8.28263V9.60963C6.25 9.94786 5.97718 10.2221 5.64063 10.2221C5.30408 10.2221 5.03125 9.94786 5.03125 9.60963V8.28263C5.03125 7.94437 5.30408 7.67017 5.64063 7.67017Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.37537 7.67017C8.71192 7.67017 8.98474 7.94437 8.98474 8.28263V9.60963C8.98474 9.94786 8.71192 10.2221 8.37537 10.2221C8.03882 10.2221 7.76599 9.94786 7.76599 9.60963V8.28263C7.76599 7.94437 8.03882 7.67017 8.37537 7.67017Z" fill="black"/>
<path d="M7 3.65625C7 5.84375 5.10754 6.3718 3.76562 6.3718C2.42371 6.3718 2.1405 5.3854 2.1405 4.16861C2.1405 2.95182 3.22834 1.96542 4.57025 1.96542C5.91216 1.96542 7 2.43946 7 3.65625Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.25"/>
<path d="M7 3.65625C7 5.84375 8.89246 6.3718 10.2344 6.3718C11.5763 6.3718 11.8595 5.3854 11.8595 4.16861C11.8595 2.95182 10.7717 1.96542 9.42975 1.96542C8.08784 1.96542 7 2.43946 7 3.65625Z" fill="black" fill-opacity="0.5" stroke="black" stroke-width="1.25"/>
<path d="M11.0156 6.01562C11.0156 6.01562 11.6735 6.43636 12 7.07348C12.3265 7.7106 12.3281 9.18621 12 9.7181C11.6719 10.25 11.2813 10.625 10.2931 11.16C9.30501 11.695 8 12.0156 8 12.0156H6C6 12.0156 4.70312 11.7344 3.70687 11.16C2.71061 10.5856 2.23437 10.2188 2 9.7181C1.76562 9.21746 1.6875 7.75 2 7.07348C2.31249 6.39695 3 6.01562 3 6.01562" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M10.4454 11.0264V6.41934L12.1671 6.99323V9.5598L10.4454 11.0264Z" fill="black" fill-opacity="0.75"/>
<path d="M3.51556 11.0264V6.41934L1.79388 6.99323V9.5598L3.51556 11.0264Z" fill="black" fill-opacity="0.75"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

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

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="5.64062" width="6.35938" height="6.35938" rx="0.5" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M8.01562 3.75H5.625V2.03125H11.9375V8.39062H10.2656V6C10.2656 4.75736 9.25827 3.75 8.01562 3.75Z" fill="black" fill-opacity="0.5"/>
<path d="M5.625 3.125V2.5C5.625 2.22386 5.84886 2 6.125 2H11.5C11.7761 2 12 2.22386 12 2.5V7.875C12 8.15114 11.7761 8.375 11.5 8.375H10.8906" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7" cy="7" r="1" fill="black"/>
<circle cx="11" cy="7" r="1" fill="black"/>
<circle cx="3" cy="7" r="1" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

4
assets/icons/error.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.86396 2C8.99657 2 9.12375 2.05268 9.21751 2.14645L11.8536 4.78249C11.9473 4.87625 12 5.00343 12 5.13604L12 8.86396C12 8.99657 11.9473 9.12375 11.8536 9.21751L9.21751 11.8536C9.12375 11.9473 8.99657 12 8.86396 12L5.13604 12C5.00343 12 4.87625 11.9473 4.78249 11.8536L2.14645 9.21751C2.05268 9.12375 2 8.99657 2 8.86396L2 5.13604C2 5.00343 2.05268 4.87625 2.14645 4.78249L4.78249 2.14645C4.87625 2.05268 5.00343 2 5.13604 2L8.86396 2Z" fill="#001A33" fill-opacity="0.157" stroke="#11181C" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M8.89063 5.10938L5.10937 8.89063M8.89063 8.89063L5.10937 5.10938" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 785 B

4
assets/icons/exit.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.3594 7.00127L9.86062 4.5025M12.3594 7.00127L9.86062 9.50002M12.3594 7.00127L5 7.00127" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 2H2.5C2.22386 2 2 2.22386 2 2.5V11.5C2 11.7761 2.22386 12 2.5 12H6" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 427 B

View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 3.5C2 3.22386 2.22386 3 2.5 3H11.5C11.7761 3 12 3.22386 12 3.5V10.5C12 10.7761 11.7761 11 11.5 11H2.5C2.22386 11 2 10.7761 2 10.5V3.5Z" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M3 4L6.95312 7L11 4" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 9L5 8" stroke="black" stroke-opacity="0.5" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 9L9 8" stroke="black" stroke-opacity="0.5" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 675 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2L8.6165 2.10275C8.65805 1.8534 8.54532 1.60357 8.33085 1.46975C8.11639 1.33594 7.84243 1.34449 7.63673 1.49142L8 2ZM9.88714 8.62257C10.1098 9.73604 9.86526 10.3554 9.4569 10.7229C9.00367 11.1308 8.19498 11.375 7 11.375V12.625C8.30502 12.625 9.49633 12.3692 10.2931 11.6521C11.1347 10.8946 11.3902 9.76396 11.1129 8.37743L9.88714 8.62257ZM7 11.375C5.87824 11.375 5.17563 11.0417 4.75444 10.6206C4.32847 10.1946 4.125 9.61372 4.125 9H2.875C2.875 9.88628 3.17153 10.8054 3.87056 11.5044C4.57437 12.2083 5.62176 12.625 7 12.625V11.375ZM4.125 9C4.125 7.72699 5.00594 4.90668 8.36327 2.50858L7.63673 1.49142C3.99406 4.09332 2.875 7.27301 2.875 9H4.125ZM7.3835 1.89725C7.09577 3.62363 7.69108 4.78835 8.35497 5.78419C9.03189 6.79957 9.66859 7.52983 9.88714 8.62257L11.1129 8.37743C10.8314 6.97017 9.96811 5.95043 9.39503 5.09081C8.80892 4.21165 8.40423 3.37637 8.6165 2.10275L7.3835 1.89725Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1017 B

View File

@@ -1,159 +1,192 @@
{
"suffixes": {
"aac": "audio",
"bash": "terminal",
"bmp": "image",
"c": "code",
"conf": "settings",
"cpp": "code",
"cc": "code",
"css": "code",
"doc": "document",
"docx": "document",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"flac": "audio",
"fish": "terminal",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"gif": "image",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"htm": "template",
"html": "template",
"svelte": "template",
"hpp": "code",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"lock": "lock",
"log": "log",
"md": "document",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"ods": "document",
"odp": "document",
"odt": "document",
"ogg": "video",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierrc": "prettier",
"prettierignore": "prettier",
"ps1": "terminal",
"psd": "image",
"py": "code",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"scm": "code",
"sh": "terminal",
"bashrc": "terminal",
"bash_profile": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"profile": "terminal",
"zshrc": "terminal",
"zshenv": "terminal",
"zsh_profile": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zlogin": "terminal",
"sql": "code",
"svg": "image",
"swift": "code",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsx": "code",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zsh": "terminal"
},
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
"suffixes": {
"aac": "audio",
"accdb": "storage",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"conf": "settings",
"cpp": "code",
"css": "code",
"csv": "storage",
"dat": "storage",
"db": "storage",
"dbf": "storage",
"dll": "storage",
"doc": "document",
"docx": "document",
"eex": "elixir",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"ex": "elixir",
"exs": "elixir",
"fish": "terminal",
"flac": "audio",
"fmp": "storage",
"fp7": "storage",
"frm": "storage",
"gdb": "storage",
"gif": "image",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"ldf": "storage",
"lock": "lock",
"log": "log",
"md": "document",
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"myd": "storage",
"myi": "storage",
"odp": "document",
"ods": "document",
"odt": "document",
"ogg": "video",
"pdb": "storage",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
"prettierrc": "prettier",
"profile": "terminal",
"ps1": "terminal",
"psd": "image",
"py": "python",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"sav": "storage",
"scm": "code",
"sdf": "storage",
"sh": "terminal",
"sqlite": "storage",
"svelte": "template",
"svg": "image",
"swift": "code",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsv": "storage",
"tsx": "code",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zsh_profile": "terminal",
"zshenv": "terminal",
"zshrc": "terminal"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"elixir": {
"icon": "icons/file_icons/elixir.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"python": {
"icon": "icons/file_icons/python.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
}
}
}
}

View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 8C12 7.32138 11.9375 6.5 11.7188 5.75C11.0625 6.53125 9.875 7.1875 9 7.5C9.75 4.90625 8.5625 2.1875 7 2C7 3.96875 6.625 4.90625 5.5 6.5C4 4 2.5 5.5 2 6C2.5 6.5 3.21832 7.24064 3.34375 8.3125C3.6875 11.25 5.75 12 7.5 12C9.25 12 9.5 10 11.5 11" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4.03125" cy="6.625" r="1.53125" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 502 B

View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.18452 1.9164C5.01625 1.9164 3.98489 2.77625 3.91991 3.9468H3.72024C2.81569 3.9468 2 4.63733 2 5.587V7.1098C2 8.05947 2.81569 8.75 3.72024 8.75H4.33631C4.67376 8.75 5.02976 8.48561 5.02976 8.06155C5.02976 7.46058 5.51694 6.9734 6.11791 6.9734H7.27976C8.18431 6.9734 9 6.28288 9 5.3332V4.0642C9 2.83419 7.93913 1.9164 6.73214 1.9164H6.18452Z" stroke="black" stroke-width="1.25"/>
<path d="M7.79613 12.0836C8.97889 12.0836 10.0103 11.2025 10.0702 10.0191H10.2738C11.1885 10.0191 12 9.31459 12 8.36187V6.8135C12 5.86077 11.1885 5.15625 10.2738 5.15625H9.65439C9.30991 5.15625 8.96057 5.42749 8.96057 5.84577C8.96057 6.46262 8.46051 6.96268 7.84365 6.96268H6.69494C5.78027 6.96268 4.96875 7.6672 4.96875 8.61993V9.91023C4.96875 11.148 6.02678 12.0836 7.24554 12.0836H7.79613Z" stroke="black" stroke-width="1.25"/>
<circle cx="6.03975" cy="3.9167" r="0.633501" fill="black"/>
<circle cx="7.92285" cy="10.0793" r="0.670898" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

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

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6749 2.40608C11.8058 2.24239 11.6893 1.99991 11.4796 1.99991H2.51996C2.31033 1.99991 2.19379 2.24239 2.32474 2.40608L5.14583 5.93246C5.34148 6.17701 5.44808 6.48087 5.44808 6.79412C5.44808 7.46881 5.44808 10.334 5.44808 11.5016C5.44808 11.7778 5.67194 11.9999 5.94808 11.9999H8.05153C8.32767 11.9999 8.55153 11.7778 8.55153 11.5016C8.55153 10.334 8.55153 7.46881 8.55153 6.79412C8.55153 6.48087 8.65815 6.17701 8.8538 5.93246L11.6749 2.40608Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6748 1.40617C10.8058 1.24248 10.6892 1 10.4796 1H1.51991C1.31028 1 1.19374 1.24248 1.32469 1.40617L4.14578 4.93255C4.34144 5.1771 4.44803 5.48097 4.44803 5.79421C4.44803 6.4689 4.44803 9.33412 4.44803 10.5017C4.44803 10.7779 4.67189 11 4.94803 11H7.05148C7.32762 11 7.55148 10.7779 7.55148 10.5017C7.55148 9.33412 7.55148 6.4689 7.55148 5.79421C7.55148 5.48097 7.6581 5.1771 7.85376 4.93255L10.6748 1.40617Z" stroke="#787D87" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 611 B

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style="fill:none;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(47.058824%,49.019608%,52.941176%);stroke-opacity:1;stroke-miterlimit:4;" d="M 10.674107 1.40625 C 10.804688 1.242188 10.690848 1.001116 10.479911 1.001116 L 1.520089 1.001116 C 1.309152 1.001116 1.195312 1.242188 1.325893 1.40625 L 4.145089 4.93192 C 4.342634 5.176339 4.446429 5.481027 4.446429 5.795759 C 4.446429 6.46875 4.446429 9.334821 4.446429 10.503348 C 4.446429 10.777902 4.670759 10.998884 4.948661 10.998884 L 7.051339 10.998884 C 7.329241 10.998884 7.550223 10.777902 7.550223 10.503348 C 7.550223 9.334821 7.550223 6.46875 7.550223 5.795759 C 7.550223 5.481027 7.657366 5.176339 7.854911 4.93192 Z M 10.674107 1.40625 " transform="matrix(1.166667,0,0,1.166667,0,0)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 991 B

6
assets/icons/hash.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="10.2795" y1="2.63847" x2="7.74786" y2="11.0142" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="6.26625" y1="2.99597" x2="3.73461" y2="11.3717" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="3.15979" y1="5.3799" x2="11.9098" y2="5.3799" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<line x1="2.09833" y1="8.62407" x2="10.8483" y2="8.62407" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

5
assets/icons/html.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.15735 3.17108L5.84271 10.8289" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
<path d="M4 5L2 7L4 9" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 9L12 7L10 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 423 B

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="3" cy="9" r="1" fill="black"/>
<circle cx="3" cy="5" r="1" fill="black"/>
<path d="M7 3H10M13 3H10M10 3C10 3 10 11 10 11.5" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 276 B

5
assets/icons/kebab.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7" cy="7" r="1" fill="black"/>
<circle cx="11" cy="7" r="1" fill="black"/>
<circle cx="3" cy="7" r="1" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 233 B

6
assets/icons/lock.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="5" width="8" height="7" rx="0.5" stroke="black" stroke-width="1.25"/>
<path d="M4 4C4 2.89543 4.89543 2 6 2H8C9.10457 2 10 2.89543 10 4V5H4V4Z" stroke="black" stroke-opacity="0.75" stroke-width="1.25"/>
<circle cx="7" cy="8" r="1" fill="black"/>
<path d="M7 8V9.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 445 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12L9.41379 9.41379M2 6.31034C2 3.92981 3.92981 2 6.31034 2C8.6909 2 10.6207 3.92981 10.6207 6.31034C10.6207 8.6909 8.6909 10.6207 6.31034 10.6207C3.92981 10.6207 2 8.6909 2 6.31034Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 383 B

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.47087 3.20502H4.93146L7.12233 10.845H6.16733L5.60557 8.91252H2.78552L2.235 10.845H1.28L3.47087 3.20502ZM5.3921 8.06988L4.24611 4.02519H4.15622L3.01023 8.06988H5.3921Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.35784 3.05502H5.04449L7.32139 10.995H6.05473L5.49297 9.06253H2.89876L2.34823 10.995H1.08094L3.35784 3.05502ZM4.20117 4.41683L3.20863 7.91989H5.1937L4.20117 4.41683Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.49755 10.9439L8.49614 10.9433C8.20513 10.8235 7.99172 10.6192 7.86261 10.3352L7.86103 10.3317C7.74397 10.0558 7.69085 9.67797 7.69085 9.21014C7.69085 8.65676 7.77089 8.20993 7.94588 7.88453C8.12486 7.54406 8.4223 7.31025 8.82246 7.17939C9.22218 7.04366 9.77245 6.97946 10.4643 6.97946H11.3773V6.84676C11.3773 6.53978 11.3365 6.32064 11.2676 6.17645L11.2652 6.17158C11.2077 6.03931 11.105 5.94128 10.942 5.87857L10.9401 5.87785C10.7779 5.81296 10.5289 5.77548 10.1816 5.77548C9.95742 5.77548 9.77444 5.79025 9.63048 5.818C9.4849 5.84607 9.38928 5.88554 9.33128 5.92772L9.32759 5.9304C9.22055 6.00339 9.13583 6.16518 9.1215 6.4804L9.11499 6.62359H7.87178V6.47359C7.87178 6.02598 7.93666 5.66152 8.08202 5.39592C8.23181 5.11455 8.48509 4.9233 8.82297 4.81582C9.15491 4.7028 9.61083 4.64999 10.1816 4.64999C10.7762 4.64999 11.2497 4.71047 11.5915 4.84054C11.9497 4.97397 12.2081 5.20795 12.3539 5.54148C12.5023 5.86386 12.5706 6.30304 12.5706 6.84676V10.9998H11.4112V10.4612C11.2513 10.6622 11.0717 10.8156 10.8706 10.9161L10.869 10.917C10.5893 11.0526 10.1848 11.1129 9.67276 11.1129C9.18264 11.1129 8.78731 11.0598 8.49755 10.9439ZM9.40357 8.21033C9.23125 8.26777 9.11727 8.36187 9.04741 8.4893C8.98131 8.61621 8.94073 8.81734 8.94073 9.10837C8.94073 9.48881 9.01919 9.69954 9.12735 9.79866C9.24209 9.89577 9.48642 9.96479 9.91023 9.96479C10.3198 9.96479 10.6134 9.90216 10.8072 9.79296C10.9944 9.68003 11.1366 9.4918 11.226 9.21004C11.3088 8.94889 11.3567 8.57563 11.3648 8.08368L10.2403 8.09363C9.87055 8.10107 9.59539 8.14186 9.40658 8.20929L9.40357 8.21033Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.74677 9.48683L4.07035 6.03229L3.38589 9.48683H2.17618L1.00285 4.00778H2.27563L2.81571 7.41751L3.48443 4.01749H4.65869L5.31824 7.41173L5.8574 4.00778H7.13018L5.95684 9.48683H4.74677Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.64902 9.324C8.31295 9.13305 8.08208 8.81972 7.9472 8.40798C7.81465 8.00336 7.75312 7.44225 7.75312 6.73695C7.75312 6.03863 7.81136 5.48473 7.93685 5.08734L7.9375 5.0853C8.07226 4.67391 8.30335 4.36276 8.64083 4.17813C8.96801 3.99275 9.41114 3.91059 9.94955 3.91059C10.3406 3.91059 10.6631 3.95604 10.8967 4.06503C11.0079 4.11693 11.1098 4.18862 11.2033 4.27763V2.03046H12.4076V9.48579H11.2033V9.19001C11.0944 9.29092 10.9799 9.37114 10.8591 9.4277C10.6327 9.53666 10.3334 9.5827 9.97862 9.5827C9.43385 9.5827 8.98587 9.50374 8.6537 9.32658L8.64902 9.324ZM11.1139 7.85526C11.1841 7.60329 11.2226 7.23372 11.2226 6.73695C11.2226 6.2462 11.184 5.88349 11.114 5.63862C11.0456 5.39921 10.94 5.25882 10.8149 5.18284L10.8077 5.17844C10.6804 5.09361 10.4713 5.03744 10.1531 5.03744C9.8078 5.03744 9.57185 5.09378 9.42251 5.18338L9.41824 5.18594C9.27997 5.2643 9.16717 5.40621 9.09394 5.64281C9.01872 5.88584 8.97689 6.24686 8.97689 6.73695C8.97689 7.23381 9.01877 7.59792 9.09394 7.84078C9.16725 8.07763 9.28092 8.22495 9.42251 8.3099C9.57185 8.39951 9.8078 8.45585 10.1531 8.45585C10.4721 8.45585 10.683 8.40283 10.8113 8.32234C10.9395 8.23962 11.0456 8.09391 11.1139 7.85526Z" fill="black"/>
<rect x="1.14084" y="10.7188" width="11.7183" height="1.26565" rx="0.632824" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 8.5V12M2 12H5.5M2 12L6.01562 7.98437" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 5.5V2M12 2L8.5 2M12 2L8.01562 5.98437" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 8.5C10.5 8.5 9.375 10 7 10C4.625 10 3.5 8.5 3.5 8.5" stroke="black" stroke-width="1.25"/>
<rect x="5" y="2" width="4" height="5.40625" rx="2" fill="black" fill-opacity="0.25" stroke="black" stroke-width="1.25"/>
<path d="M7 10V12M7 12H9M7 12H5" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 423 B

View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.01563 11.4844L6.01563 7.98438M6.01563 7.98438L2.51563 7.98437M6.01563 7.98438L2 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.01562 2.48438V5.98438M8.01562 5.98438H11.5156M8.01562 5.98438L12 2" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 447 B

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

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3V11M11 7H3" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 188 B

5
assets/icons/project.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.03125 2V2.03125M2.03125 8C2.03125 10 5 10 5 10M2.03125 8V2.03125M2.03125 8L2.03125 11M2.03125 2.03125C2.03125 4 5 4 5 4" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="7.375" y="2.375" width="4.25" height="3.25" rx="1.125" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
<rect x="7.375" y="8.375" width="4.25" height="3.25" rx="1.125" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 588 B

11
assets/icons/replace.svg Normal file
View File

@@ -0,0 +1,11 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 12C4.97279 12 3.22735 10.7936 2.4425 9.0595M7 2C9.11228 2 10.9186 3.30981 11.6512 5.16152" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="1.65625" cy="1.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="3.71094" cy="1.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="4.96094" cy="3.36719" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="3.71094" cy="4.79688" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="4.60156" cy="6.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="1.65625" cy="4.17188" r="0.625" fill="black" fill-opacity="0.75"/>
<circle cx="1.65625" cy="6.67188" r="0.625" fill="black" fill-opacity="0.75"/>
<path d="M10.7802 10.8195C10.838 10.8195 10.8906 10.8527 10.9155 10.9048L11.7174 12.5811C11.8088 12.7721 12.0017 12.8938 12.2135 12.8938H12.3394C12.7483 12.8938 13.0142 12.4635 12.8314 12.0978L12.1619 10.7589C12.1232 10.6816 12.1582 10.5823 12.241 10.5349C12.7565 10.2397 13.0695 9.66858 13.0695 9.00391C13.0695 8.43361 12.8777 7.97006 12.5248 7.64951C12.1725 7.3295 11.6652 7.15703 11.043 7.15703H9.49609C9.19234 7.15703 8.94609 7.40327 8.94609 7.70703V12.3438C8.94609 12.6475 9.19234 12.8938 9.49609 12.8938H9.60156C9.90532 12.8938 10.1516 12.6475 10.1516 12.3438V10.9695C10.1516 10.8867 10.2187 10.8195 10.3016 10.8195H10.7802ZM10.1516 8.31328C10.1516 8.23044 10.2187 8.16328 10.3016 8.16328H10.8984C11.2023 8.16328 11.4371 8.2449 11.5954 8.38814C11.7529 8.5308 11.8406 8.73993 11.8406 9.00781C11.8406 9.28155 11.751 9.49461 11.5909 9.63971C11.4302 9.7854 11.1925 9.86797 10.8867 9.86797H10.3016C10.2187 9.86797 10.1516 9.80081 10.1516 9.71797V8.31328Z" fill="black" stroke="black" stroke-width="0.1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.10517 5.8012C4.07193 5.73172 4.00176 5.6875 3.92475 5.6875H3.44609C3.33564 5.6875 3.24609 5.77704 3.24609 5.8875V7.26172C3.24609 7.53786 3.02224 7.76172 2.74609 7.76172H2.64062C2.36448 7.76172 2.14062 7.53786 2.14062 7.26172V2.625C2.14062 2.34886 2.36448 2.125 2.64062 2.125H4.1875C5.41406 2.125 6.16406 2.80469 6.16406 3.92188C6.16406 4.57081 5.85885 5.12418 5.36073 5.40943C5.25888 5.46775 5.20921 5.59421 5.2617 5.69918L5.93117 7.03811C6.09739 7.37056 5.85564 7.76172 5.48395 7.76172H5.35806C5.16552 7.76172 4.99009 7.65117 4.907 7.47748L4.10517 5.8012ZM3.44609 3.03125C3.33564 3.03125 3.24609 3.12079 3.24609 3.23125V4.63594C3.24609 4.74639 3.33564 4.83594 3.44609 4.83594H4.03125C4.66016 4.83594 5.03516 4.49609 5.03516 3.92578C5.03516 3.36719 4.66797 3.03125 4.04297 3.03125H3.44609Z" fill="black" fill-opacity="0.75"/>
<path d="M3.92475 5.7375C3.98251 5.7375 4.03514 5.77067 4.06006 5.82277L4.8619 7.49905C4.95329 7.69011 5.14627 7.81172 5.35806 7.81172H5.48395C5.89281 7.81172 6.15873 7.38145 5.97589 7.01575L5.30642 5.67682C5.26778 5.59953 5.30269 5.50028 5.38557 5.45282C5.90107 5.15762 6.21406 4.58655 6.21406 3.92188C6.21406 3.35158 6.02226 2.88803 5.66936 2.56748C5.31705 2.24747 4.80973 2.075 4.1875 2.075H2.64062C2.33687 2.075 2.09062 2.32124 2.09062 2.625V7.26172C2.09062 7.56548 2.33687 7.81172 2.64062 7.81172H2.74609C3.04985 7.81172 3.29609 7.56548 3.29609 7.26172V5.8875C3.29609 5.80466 3.36325 5.7375 3.44609 5.7375H3.92475ZM3.29609 3.23125C3.29609 3.14841 3.36325 3.08125 3.44609 3.08125H4.04297C4.34688 3.08125 4.58164 3.16287 4.73988 3.30611C4.89748 3.44876 4.98516 3.6579 4.98516 3.92578C4.98516 4.19952 4.89553 4.41258 4.73546 4.55768C4.57475 4.70337 4.33706 4.78594 4.03125 4.78594H3.44609C3.36325 4.78594 3.29609 4.71878 3.29609 4.63594V3.23125Z" stroke="black" stroke-opacity="0.75" stroke-width="0.1"/>
<path d="M9.5 7V9.5M9.5 12V9.5M12 9.5H9.5M7 9.5H9.5M9.5 9.5L11.1667 7.83333M9.5 9.5L7.83333 11.1667M9.5 9.5L11.1667 11.1667M9.5 9.5L7.83333 7.83333" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.96454 5.6762C3.93131 5.60672 3.86114 5.5625 3.78412 5.5625H3.30547C3.19501 5.5625 3.10547 5.65204 3.10547 5.7625V7.13672C3.10547 7.41286 2.88161 7.63672 2.60547 7.63672H2.5C2.22386 7.63672 2 7.41286 2 7.13672V2.5C2 2.22386 2.22386 2 2.5 2H4.04688C5.27344 2 6.02344 2.67969 6.02344 3.79688C6.02344 4.44581 5.71823 4.99918 5.2201 5.28443C5.11826 5.34275 5.06859 5.46921 5.12107 5.57418L5.79054 6.91311C5.95677 7.24556 5.71502 7.63672 5.34333 7.63672H5.21743C5.02489 7.63672 4.84946 7.52617 4.76638 7.35248L3.96454 5.6762ZM3.30547 2.90625C3.19501 2.90625 3.10547 2.99579 3.10547 3.10625V4.51094C3.10547 4.62139 3.19501 4.71094 3.30547 4.71094H3.89062C4.51953 4.71094 4.89453 4.37109 4.89453 3.80078C4.89453 3.24219 4.52734 2.90625 3.90234 2.90625H3.30547Z" fill="black" fill-opacity="0.75"/>
<path d="M3.78412 5.6125C3.84188 5.6125 3.89451 5.64567 3.91944 5.69777L4.72127 7.37405C4.81266 7.56511 5.00564 7.68672 5.21743 7.68672H5.34333C5.75219 7.68672 6.01811 7.25645 5.83526 6.89075L5.1658 5.55182C5.12715 5.47453 5.16207 5.37528 5.24495 5.32782C5.76044 5.03262 6.07344 4.46155 6.07344 3.79688C6.07344 3.22658 5.88164 2.76303 5.52873 2.44248C5.17642 2.12247 4.6691 1.95 4.04688 1.95H2.5C2.19624 1.95 1.95 2.19624 1.95 2.5V7.13672C1.95 7.44048 2.19624 7.68672 2.5 7.68672H2.60547C2.90923 7.68672 3.15547 7.44048 3.15547 7.13672V5.7625C3.15547 5.67966 3.22263 5.6125 3.30547 5.6125H3.78412ZM3.15547 3.10625C3.15547 3.02341 3.22263 2.95625 3.30547 2.95625H3.90234C4.20626 2.95625 4.44101 3.03787 4.59926 3.18111C4.75686 3.32376 4.84453 3.5329 4.84453 3.80078C4.84453 4.07452 4.75491 4.28758 4.59484 4.43268C4.43413 4.57837 4.19643 4.66094 3.89062 4.66094H3.30547C3.22263 4.66094 3.15547 4.59378 3.15547 4.51094V3.10625Z" stroke="black" stroke-opacity="0.75" stroke-width="0.1"/>
<path d="M7.5 5.88672C9.433 5.88672 11 7.45372 11 9.38672V12M11 12L13 10M11 12L9 10" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

4
assets/icons/screen.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="10" height="7" rx="0.5" fill="black" fill-opacity="0.25" stroke="black" stroke-width="1.25"/>
<path d="M7 9V12M7 12H9M7 12H5" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

5
assets/icons/split.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 2H10C11.1046 2 12 2.89543 12 4V10C12 11.1046 11.1046 12 10 12H7V2Z" fill="black" fill-opacity="0.25"/>
<rect x="2" y="2" width="10" height="10" rx="0.5" stroke="black" stroke-width="1.25"/>
<line x1="7" y1="2" x2="7" y2="12" stroke="black" stroke-width="1.25"/>
</svg>

After

Width:  |  Height:  |  Size: 377 B

4
assets/icons/success.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 2.5C2 2.22386 2.22386 2 2.5 2H11.5C11.7761 2 12 2.22386 12 2.5V11.5C12 11.7761 11.7761 12 11.5 12H2.5C2.22386 12 2 11.7761 2 11.5V2.5Z" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linejoin="round"/>
<path d="M4.60938 7.625L6.3125 8.89062L9.35938 4.64062" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 474 B

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.65625 2.5C1.65625 2.22386 1.88011 2 2.15625 2H11.8437C12.1199 2 12.3438 2.22386 12.3438 2.5V11.5C12.3438 11.7761 12.1199 12 11.8437 12H2.15625C1.88011 12 1.65625 11.7761 1.65625 11.5V2.5Z" stroke="black" stroke-width="1.25"/>
<path d="M4.375 9L6.375 7L4.375 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.625 9L9.90625 9" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

6
assets/icons/warning.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 835 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12px" height="12px" viewBox="0 0 12 12" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 4.070312 8.132812 L 3.488281 5.171875 L 2.902344 8.132812 L 1.867188 8.132812 L 0.859375 3.433594 L 1.949219 3.433594 L 2.414062 6.359375 L 2.988281 3.445312 L 3.992188 3.445312 L 4.558594 6.351562 L 5.019531 3.433594 L 6.113281 3.433594 L 5.105469 8.132812 Z M 4.070312 8.132812 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 7.414062 7.992188 C 7.125 7.828125 6.925781 7.558594 6.8125 7.207031 C 6.699219 6.859375 6.644531 6.378906 6.644531 5.773438 C 6.644531 5.175781 6.695312 4.699219 6.804688 4.359375 C 6.917969 4.007812 7.117188 3.738281 7.40625 3.582031 C 7.6875 3.421875 8.066406 3.351562 8.527344 3.351562 C 8.863281 3.351562 9.140625 3.390625 9.339844 3.484375 C 9.433594 3.527344 9.523438 3.589844 9.601562 3.667969 L 9.601562 1.738281 L 10.636719 1.738281 L 10.636719 8.128906 L 9.601562 8.128906 L 9.601562 7.878906 C 9.507812 7.964844 9.410156 8.03125 9.308594 8.082031 C 9.113281 8.175781 8.855469 8.214844 8.554688 8.214844 C 8.085938 8.214844 7.703125 8.144531 7.417969 7.992188 Z M 9.527344 6.734375 C 9.585938 6.515625 9.621094 6.199219 9.621094 5.773438 C 9.621094 5.355469 9.585938 5.042969 9.527344 4.832031 C 9.46875 4.628906 9.378906 4.507812 9.269531 4.441406 L 9.265625 4.4375 C 9.15625 4.367188 8.976562 4.316406 8.703125 4.316406 C 8.40625 4.316406 8.203125 4.367188 8.078125 4.441406 L 8.074219 4.445312 C 7.953125 4.511719 7.859375 4.632812 7.792969 4.835938 C 7.730469 5.042969 7.695312 5.355469 7.695312 5.773438 C 7.695312 6.199219 7.730469 6.511719 7.792969 6.71875 C 7.859375 6.921875 7.957031 7.050781 8.078125 7.121094 C 8.203125 7.199219 8.40625 7.246094 8.703125 7.246094 C 8.976562 7.246094 9.15625 7.203125 9.265625 7.132812 C 9.375 7.0625 9.46875 6.9375 9.527344 6.734375 Z M 9.527344 6.734375 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(47.058824%,49.019608%,52.941176%);fill-opacity:1;" d="M 1.519531 9.1875 L 10.480469 9.1875 C 10.777344 9.1875 11.023438 9.429688 11.023438 9.730469 C 11.023438 10.03125 10.777344 10.273438 10.480469 10.273438 L 1.519531 10.273438 C 1.222656 10.273438 0.976562 10.03125 0.976562 9.730469 C 0.976562 9.429688 1.222656 9.1875 1.519531 9.1875 Z M 1.519531 9.1875 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.74672 9.48686L4.07031 6.03232L3.38584 9.48686H2.17614L1.00281 4.00781H2.27559L2.81566 7.41754L3.48439 4.01752H4.65865L5.31819 7.41176L5.85736 4.00781H7.13014L5.9568 9.48686H4.74672Z" fill="#787D87"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.64907 9.32382C8.313 9.13287 8.08213 8.81954 7.94725 8.4078C7.8147 8.00318 7.75317 7.44207 7.75317 6.73677C7.75317 6.03845 7.81141 5.48454 7.9369 5.08716L7.93755 5.08512C8.07231 4.67373 8.3034 4.36258 8.64088 4.17794C8.96806 3.99257 9.41119 3.9104 9.9496 3.9104C10.3406 3.9104 10.6632 3.95585 10.8967 4.06485C11.0079 4.11675 11.1099 4.18844 11.2033 4.27745V2.03027H12.4077V9.4856H11.2033V9.18983C11.0945 9.29074 10.98 9.37096 10.8591 9.42752C10.6327 9.53648 10.3335 9.58252 9.97867 9.58252C9.4339 9.58252 8.98592 9.50355 8.65375 9.3264L8.64907 9.32382ZM11.1139 7.85508C11.1841 7.60311 11.2227 7.23354 11.2227 6.73677C11.2227 6.24602 11.1841 5.88331 11.1141 5.63844C11.0457 5.39902 10.9401 5.25863 10.8149 5.18266L10.8077 5.17826C10.6804 5.09342 10.4713 5.03726 10.1531 5.03726C9.80785 5.03726 9.5719 5.09359 9.42256 5.1832L9.41829 5.18576C9.28002 5.26412 9.16722 5.40602 9.09399 5.64263C9.01876 5.88566 8.97694 6.24668 8.97694 6.73677C8.97694 7.23363 9.01882 7.59774 9.09399 7.8406C9.1673 8.07745 9.28097 8.22477 9.42256 8.30972C9.5719 8.39933 9.80785 8.45566 10.1531 8.45566C10.4721 8.45566 10.683 8.40265 10.8114 8.32216C10.9396 8.23944 11.0456 8.09373 11.1139 7.85508Z" fill="#787D87"/>
<rect x="1.14087" y="10.7188" width="11.7183" height="1.26565" rx="0.632824" fill="#787D87"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

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

@@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.82843 4.17157L4.17157 9.82842M9.82843 9.82842L4.17157 4.17157" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 238 B

View File

@@ -13,6 +13,7 @@
"cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast",
"enter": "menu::Confirm",
"ctrl-enter": "menu::ShowContextMenu",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
@@ -22,6 +23,7 @@
"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",
@@ -171,6 +173,7 @@
"context": "Editor && mode == full",
"bindings": {
"enter": "editor::Newline",
"shift-enter": "editor::Newline",
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-enter": "editor::NewlineBelow",
"alt-z": "editor::ToggleSoftWrap",
@@ -223,19 +226,36 @@
"tab": "buffer_search::FocusEditor",
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches"
"alt-enter": "search::SelectAllMatches",
"alt-tab": "search::CycleMode"
}
},
{
"context": "BufferSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus"
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode"
}
},
{
"context": "ProjectSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{
"context": "ProjectSearchView",
"bindings": {
"escape": "project_search::ToggleFocus"
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode"
}
},
{
@@ -247,7 +267,8 @@
"alt-enter": "search::SelectAllMatches",
"alt-cmd-c": "search::ToggleCaseSensitive",
"alt-cmd-w": "search::ToggleWholeWord",
"alt-cmd-r": "search::ToggleRegex"
"alt-tab": "search::CycleMode",
"alt-cmd-f": "project_search::ToggleFilters"
}
},
// Bindings from VS Code
@@ -411,7 +432,6 @@
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-t": "project_symbols::Toggle",
"cmd-ctrl-t": "semantic_search::Toggle",
"cmd-p": "file_finder::Toggle",
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
@@ -499,8 +519,10 @@
{
"bindings": {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
"cmd-shift-c": "collab::ToggleContactsMenu",
"cmd-alt-i": "zed::DebugElements"
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
"cmd-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints",
}
},
{
@@ -508,7 +530,8 @@
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk"
"cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"
}
},
{
@@ -522,6 +545,8 @@
"bindings": {
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"cmd-n": "project_panel::NewFile",
"alt-cmd-n": "project_panel::NewDirectory",
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste",
@@ -535,6 +560,25 @@
"alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
{
"context": "CollabPanel",
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm"
}
},
{
"context": "ChannelModal",
"bindings": {
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "ChannelModal > Picker > Editor",
"bindings": {
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "Terminal",
"bindings": {

View File

@@ -2,7 +2,6 @@
{
"bindings": {
"cmd-shift-o": "projects::OpenRecent",
"cmd-shift-b": "branches::OpenRecent",
"cmd-alt-tab": "project_panel::ToggleFocus"
}
},
@@ -12,8 +11,9 @@
"cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLine",
"cmd-b": "editor::GoToDefinition",
"alt-cmd-b": "editor::GoToDefinition",
"cmd-j": "editor::ScrollCursorCenter",
"cmd-enter": "editor::NewlineBelow",
"cmd-alt-enter": "editor::NewLineAbove",
"cmd-shift-l": "editor::SelectLine",
"cmd-shift-t": "outline::Toggle",
"alt-backspace": "editor::DeleteToPreviousWordStart",
@@ -51,14 +51,17 @@
}
],
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart",
"ctrl-shift-right": "editor::SelectToNextSubwordEnd"
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
"ctrl-w": "editor::SelectNext",
"ctrl-u": "editor::ConvertToUpperCase",
"ctrl-shift-u": "editor::ConvertToLowerCase",
"ctrl-alt-u": "editor::ConvertToUpperCamelCase",
"ctrl-_": "editor::ConvertToSnakeCase"
}
},
{
"context": "Editor && mode == full",
"bindings": {
"cmd-alt-enter": "editor::NewlineAbove"
}
"bindings": {}
},
{
"context": "BufferSearchBar",
@@ -85,5 +88,9 @@
{
"context": "ProjectPanel",
"bindings": {}
},
{
"context": "Dock",
"bindings": {}
}
]

View File

@@ -101,9 +101,21 @@
"vim::SwitchMode",
"Normal"
],
"v": "vim::ToggleVisual",
"shift-v": "vim::ToggleVisualLine",
"ctrl-v": "vim::ToggleVisualBlock",
"ctrl-q": "vim::ToggleVisualBlock",
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
"ctrl-b": "vim::PageUp",
"pageup": "vim::PageUp",
"ctrl-d": "vim::ScrollDown",
"ctrl-u": "vim::ScrollUp",
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
// "g" commands
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
@@ -125,10 +137,67 @@
"partialWord": true
}
],
"g j": [
"vim::Down",
{
"displayLines": true
}
],
"g down": [
"vim::Down",
{
"displayLines": true
}
],
"g k": [
"vim::Up",
{
"displayLines": true
}
],
"g up": [
"vim::Up",
{
"displayLines": true
}
],
"g $": [
"vim::EndOfLine",
{
"displayLines": true
}
],
"g end": [
"vim::EndOfLine",
{
"displayLines": true
}
],
"g 0": [
"vim::StartOfLine",
{
"displayLines": true
}
],
"g home": [
"vim::StartOfLine",
{
"displayLines": true
}
],
"g ^": [
"vim::FirstNonWhitespace",
{
"displayLines": true
}
],
// z commands
"z t": "editor::ScrollCursorTop",
"z z": "editor::ScrollCursorCenter",
"z b": "editor::ScrollCursorBottom",
"z c": "editor::Fold",
"z o": "editor::UnfoldLines",
"z f": "editor::FoldSelectedRanges",
// Count support
"1": [
"vim::Number",
@@ -236,6 +305,14 @@
"ctrl-w ctrl-q": "pane::CloseAllItems"
}
},
{
// escape is in its own section so that it cancels a pending count.
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
"escape": "editor::Cancel",
"ctrl+[": "editor::Cancel"
}
},
{
"context": "Editor && vim_mode == normal && (vim_operator == none || vim_operator == n) && !VimWaiting",
"bindings": {
@@ -266,23 +343,13 @@
"o": "vim::InsertLineBelow",
"shift-o": "vim::InsertLineAbove",
"~": "vim::ChangeCase",
"v": [
"vim::SwitchMode",
{
"Visual": {
"line": false
}
}
],
"shift-v": [
"vim::SwitchMode",
{
"Visual": {
"line": true
}
}
],
"p": "vim::Paste",
"shift-p": [
"vim::Paste",
{
"before": true
}
],
"u": "editor::Undo",
"ctrl-r": "editor::Redo",
"/": "vim::Search",
@@ -299,14 +366,6 @@
"backwards": true
}
],
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
"ctrl-b": "vim::PageUp",
"pageup": "vim::PageUp",
"ctrl-d": "vim::ScrollDown",
"ctrl-u": "vim::ScrollUp",
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
"r": [
"vim::PushOperator",
"Replace"
@@ -371,16 +430,29 @@
}
},
{
"context": "Editor && vim_mode == visual && !VimWaiting",
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
"bindings": {
"u": "editor::Undo",
"c": "vim::VisualChange",
"o": "vim::OtherEnd",
"shift-o": "vim::OtherEnd",
"d": "vim::VisualDelete",
"x": "vim::VisualDelete",
"y": "vim::VisualYank",
"p": "vim::VisualPaste",
"p": "vim::Paste",
"shift-p": [
"vim::Paste",
{
"preserveClipboard": true
}
],
"s": "vim::Substitute",
"c": "vim::Substitute",
"~": "vim::ChangeCase",
"shift-i": [
"vim::SwitchMode",
"Insert"
],
"shift-a": "vim::InsertAfter",
"r": [
"vim::PushOperator",
"Replace"
@@ -389,12 +461,36 @@
"vim::SwitchMode",
"Normal"
],
"escape": [
"vim::SwitchMode",
"Normal"
],
"ctrl+[": [
"vim::SwitchMode",
"Normal"
],
">": "editor::Indent",
"<": "editor::Outdent"
"<": "editor::Outdent",
"i": [
"vim::PushOperator",
{
"Object": {
"around": false
}
}
],
"a": [
"vim::PushOperator",
{
"Object": {
"around": true
}
}
],
}
},
{
"context": "Editor && vim_mode == insert",
"context": "Editor && vim_mode == insert && !menu",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",

View File

@@ -98,6 +98,7 @@
// Whether to show selections in the scrollbar.
"selections": true
},
"relative_line_numbers": false,
// Inlay hint related settings
"inlay_hints": {
// Global switch to toggle hints on and off, switched off by default.
@@ -122,13 +123,29 @@
// Amount of indentation for nested items.
"indent_size": 20
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
"button": true,
// Where to dock channels panel. Can be 'left' or 'right'.
"dock": "left",
// Default width of the channels panel.
"default_width": 240
},
"assistant": {
// Whether to show the assistant panel button in the status bar.
"button": true,
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
"dock": "right",
// Default width when the assistant is docked to the left or right.
"default_width": 640,
// Default height when the assistant is docked to the bottom.
"default_height": 320
"default_height": 320,
// The default OpenAI model to use when starting new conversations. This
// setting can take two values:
//
// 1. "gpt-3.5-turbo-0613""
// 2. "gpt-4-0613""
"default_open_ai_model": "gpt-4-0613"
},
// Whether the screen sharing icon is shown in the os status bar.
"show_call_status_icon": true,
@@ -214,7 +231,9 @@
"copilot": {
// The set of glob patterns for which copilot should be disabled
// in any matching file.
"disabled_globs": [".env"]
"disabled_globs": [
".env"
]
},
// Settings specific to journaling
"journal": {
@@ -266,8 +285,6 @@
// "directory": "~/zed/projects/"
// }
// }
//
//
"working_directory": "current_project_directory",
// Set the cursor blinking behavior in the terminal.
// May take 4 values:
@@ -316,13 +333,32 @@
// "line_height": {
// "custom": 2
// },
"line_height": "comfortable"
"line_height": "comfortable",
// Activate the python virtual environment, if one is found, in the
// terminal's working directory (as resolved by the working_directory
// setting). Set this to "off" to disable this behavior.
"detect_venv": {
"on": {
// Default directories to search for virtual environments, relative
// to the current working directory. We recommend overriding this
// in your project's settings, rather than globally.
"directories": [
".env",
"env",
".venv",
"venv"
],
// Can also be 'csh' and 'fish'
"activate_script": "default"
}
}
// Set the terminal's font size. If this option is not included,
// the terminal will default to matching the buffer's font size.
// "font_size": "15"
// "font_size": "15",
// Set the terminal's font family. If this option is not included,
// the terminal will default to matching the buffer's font family.
// "font_family": "Zed Mono"
// "font_family": "Zed Mono",
// ---
},
// Difference settings for semantic_index
"semantic_index": {

View File

@@ -318,7 +318,7 @@ impl View for ActivityIndicator {
on_click,
} = self.content_to_render(cx);
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
let mut element = MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
let style = if state.hovered() && on_click.is_some() {
theme.hovered.as_ref().unwrap_or(&theme.default)

View File

@@ -24,7 +24,9 @@ workspace = { path = "../workspace" }
anyhow.workspace = true
chrono = { version = "0.4", features = ["serde"] }
futures.workspace = true
indoc.workspace = true
isahc.workspace = true
ordered-float.workspace = true
regex.workspace = true
schemars.workspace = true
serde.workspace = true
@@ -35,3 +37,8 @@ tiktoken-rs = "0.4"
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
log.workspace = true
rand.workspace = true

View File

@@ -1,27 +1,33 @@
pub mod assistant;
mod assistant_settings;
mod streaming_diff;
use anyhow::Result;
use anyhow::{anyhow, Result};
pub use assistant::AssistantPanel;
use assistant_settings::OpenAIModel;
use chrono::{DateTime, Local};
use collections::HashMap;
use fs::Fs;
use futures::StreamExt;
use gpui::AppContext;
use futures::{io::BufReader, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use gpui::{executor::Background, AppContext};
use isahc::{http::StatusCode, Request, RequestExt};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::{
cmp::Reverse,
ffi::OsStr,
fmt::{self, Display},
io,
path::PathBuf,
sync::Arc,
};
use util::paths::CONVERSATIONS_DIR;
const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
// Data types for chat completion requests
#[derive(Debug, Serialize)]
struct OpenAIRequest {
pub struct OpenAIRequest {
model: String,
messages: Vec<RequestMessage>,
stream: bool,
@@ -60,7 +66,7 @@ struct SavedConversation {
messages: Vec<SavedMessage>,
message_metadata: HashMap<MessageId, MessageMetadata>,
summary: String,
model: String,
model: OpenAIModel,
}
impl SavedConversation {
@@ -115,7 +121,7 @@ struct RequestMessage {
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct ResponseMessage {
pub struct ResponseMessage {
role: Option<Role>,
content: Option<String>,
}
@@ -149,7 +155,7 @@ impl Display for Role {
}
#[derive(Deserialize, Debug)]
struct OpenAIResponseStreamEvent {
pub struct OpenAIResponseStreamEvent {
pub id: Option<String>,
pub object: String,
pub created: u32,
@@ -159,14 +165,14 @@ struct OpenAIResponseStreamEvent {
}
#[derive(Deserialize, Debug)]
struct Usage {
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Deserialize, Debug)]
struct ChatChoiceDelta {
pub struct ChatChoiceDelta {
pub index: u32,
pub delta: ResponseMessage,
pub finish_reason: Option<String>,
@@ -190,3 +196,97 @@ struct OpenAIChoice {
pub fn init(cx: &mut AppContext) {
assistant::init(cx);
}
pub async fn stream_completion(
api_key: String,
executor: Arc<Background>,
mut request: OpenAIRequest,
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
request.stream = true;
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
let json_data = serde_json::to_string(&request)?;
let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.body(json_data)?
.send_async()
.await?;
let status = response.status();
if status == StatusCode::OK {
executor
.spawn(async move {
let mut lines = BufReader::new(response.body_mut()).lines();
fn parse_line(
line: Result<String, io::Error>,
) -> Result<Option<OpenAIResponseStreamEvent>> {
if let Some(data) = line?.strip_prefix("data: ") {
let event = serde_json::from_str(&data)?;
Ok(Some(event))
} else {
Ok(None)
}
}
while let Some(line) = lines.next().await {
if let Some(event) = parse_line(line).transpose() {
let done = event.as_ref().map_or(false, |event| {
event
.choices
.last()
.map_or(false, |choice| choice.finish_reason.is_some())
});
if tx.unbounded_send(event).is_err() {
break;
}
if done {
break;
}
}
}
anyhow::Ok(())
})
.detach();
Ok(rx)
} else {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
#[derive(Deserialize)]
struct OpenAIResponse {
error: OpenAIError,
}
#[derive(Deserialize)]
struct OpenAIError {
message: String,
}
match serde_json::from_str::<OpenAIResponse>(&body) {
Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
"Failed to connect to OpenAI API: {}",
response.error.message,
)),
_ => Err(anyhow!(
"Failed to connect to OpenAI API: {} {}",
response.status(),
body,
)),
}
}
}
#[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

@@ -3,6 +3,37 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Setting;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub enum OpenAIModel {
#[serde(rename = "gpt-3.5-turbo-0613")]
ThreePointFiveTurbo,
#[serde(rename = "gpt-4-0613")]
Four,
}
impl OpenAIModel {
pub fn full_name(&self) -> &'static str {
match self {
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo-0613",
OpenAIModel::Four => "gpt-4-0613",
}
}
pub fn short_name(&self) -> &'static str {
match self {
OpenAIModel::ThreePointFiveTurbo => "gpt-3.5-turbo",
OpenAIModel::Four => "gpt-4",
}
}
pub fn cycle(&self) -> Self {
match self {
OpenAIModel::ThreePointFiveTurbo => OpenAIModel::Four,
OpenAIModel::Four => OpenAIModel::ThreePointFiveTurbo,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AssistantDockPosition {
@@ -13,16 +44,20 @@ pub enum AssistantDockPosition {
#[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 Setting for AssistantSettings {

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

@@ -13,7 +13,7 @@ gpui = { path = "../gpui" }
collections = { path = "../collections" }
util = { path = "../util" }
rodio = "0.17.1"
rodio ={version = "0.17.1", default-features=false, features = ["wav"]}
log.workspace = true

View File

@@ -39,29 +39,43 @@ pub struct Audio {
impl Audio {
pub fn new() -> Self {
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
Self {
_output_stream,
output_handle,
_output_stream: None,
output_handle: None,
}
}
pub fn play_sound(sound: Sound, cx: &AppContext) {
fn ensure_output_exists(&mut self) -> Option<&OutputStreamHandle> {
if self.output_handle.is_none() {
let (_output_stream, output_handle) = OutputStream::try_default().log_err().unzip();
self.output_handle = output_handle;
self._output_stream = _output_stream;
}
self.output_handle.as_ref()
}
pub fn play_sound(sound: Sound, cx: &mut AppContext) {
if !cx.has_global::<Self>() {
return;
}
let this = cx.global::<Self>();
cx.update_global::<Self, _, _>(|this, cx| {
let output_handle = this.ensure_output_exists()?;
let source = SoundRegistry::global(cx).get(sound.file()).log_err()?;
output_handle.play_raw(source).log_err()?;
Some(())
});
}
let Some(output_handle) = this.output_handle.as_ref() else {
pub fn end_call(cx: &mut AppContext) {
if !cx.has_global::<Self>() {
return;
};
}
let Some(source) = SoundRegistry::global(cx).get(sound.file()).log_err() else {
return;
};
output_handle.play_raw(source).log_err();
cx.update_global::<Self, _, _>(|this, _| {
this._output_stream.take();
this.output_handle.take();
});
}
}

View File

@@ -31,7 +31,7 @@ impl View for UpdateNotification {
let app_name = cx.global::<ReleaseChannel>().display_name();
MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
Flex::column()
.with_child(
Flex::row()
@@ -48,7 +48,7 @@ impl View for UpdateNotification {
.flex(1., true),
)
.with_child(
MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
let style = theme.dismiss_button.style_for(state);
Svg::new("icons/x_mark_8.svg")
.with_color(style.color)

View File

@@ -50,7 +50,7 @@ impl View for Breadcrumbs {
let not_editor = active_item.downcast::<editor::Editor>().is_none();
let theme = theme::current(cx).clone();
let style = &theme.workspace.breadcrumbs;
let style = &theme.workspace.toolbar.breadcrumbs;
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
Some(breadcrumbs) => breadcrumbs,
@@ -60,7 +60,7 @@ impl View for Breadcrumbs {
.map(|breadcrumb| {
Text::new(
breadcrumb.text,
theme.workspace.breadcrumbs.default.text.clone(),
theme.workspace.toolbar.breadcrumbs.default.text.clone(),
)
.with_highlights(breadcrumb.highlights.unwrap_or_default())
.into_any()
@@ -68,10 +68,10 @@ impl View for Breadcrumbs {
let crumbs = Flex::row()
.with_children(Itertools::intersperse_with(breadcrumbs, || {
Label::new(" ", style.default.text.clone()).into_any()
Label::new(" ", style.default.text.clone()).into_any()
}))
.constrained()
.with_height(theme.workspace.breadcrumb_height)
.with_height(theme.workspace.toolbar.breadcrumb_height)
.contained();
if not_editor || !self.pane_focused {
@@ -82,7 +82,7 @@ impl View for Breadcrumbs {
.into_any();
}
MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
let style = style.style_for(state);
crumbs.with_style(style.container)
})

View File

@@ -13,6 +13,7 @@ test-support = [
"client/test-support",
"collections/test-support",
"gpui/test-support",
"gpui_platform",
"live_kit_client/test-support",
"project/test-support",
"util/test-support"
@@ -20,9 +21,11 @@ test-support = [
[dependencies]
audio = { path = "../audio" }
channel = { path = "../channel" }
client = { path = "../client" }
collections = { path = "../collections" }
gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
log.workspace = true
live_kit_client = { path = "../live_kit_client" }
fs = { path = "../fs" }

View File

@@ -5,7 +5,9 @@ pub mod room;
use std::sync::Arc;
use anyhow::{anyhow, Result};
use audio::Audio;
use call_settings::CallSettings;
use channel::ChannelId;
use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore};
use collections::HashSet;
use futures::{future::Shared, FutureExt};
@@ -75,6 +77,10 @@ impl ActiveCall {
}
}
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
self.room()?.read(cx).channel_id()
}
async fn handle_incoming_call(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::IncomingCall>,
@@ -267,16 +273,43 @@ impl ActiveCall {
.borrow_mut()
.take()
.ok_or_else(|| anyhow!("no incoming call"))?;
Self::report_call_event_for_room("decline incoming", call.room_id, &self.client, cx);
Self::report_call_event_for_room("decline incoming", call.room_id, None, &self.client, cx);
self.client.send(proto::DeclineCall {
room_id: call.room_id,
})?;
Ok(())
}
pub fn join_channel(
&mut self,
channel_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if let Some(room) = self.room().cloned() {
if room.read(cx).channel_id() == Some(channel_id) {
return Task::ready(Ok(()));
} else {
room.update(cx, |room, cx| room.clear_state(cx));
}
}
let join = Room::join_channel(channel_id, self.client.clone(), self.user_store.clone(), cx);
cx.spawn(|this, mut cx| async move {
let room = join.await?;
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("join channel", cx)
});
Ok(())
})
}
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
self.report_call_event("hang up", cx);
Audio::end_call(cx);
if let Some((room, _)) = self.room.take() {
room.update(cx, |room, cx| room.leave(cx))
} else {
@@ -372,19 +405,31 @@ impl ActiveCall {
fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
if let Some(room) = self.room() {
Self::report_call_event_for_room(operation, room.read(cx).id(), &self.client, cx)
let room = room.read(cx);
Self::report_call_event_for_room(
operation,
room.id(),
room.channel_id(),
&self.client,
cx,
)
}
}
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<u64>,
client: &Arc<Client>,
cx: &AppContext,
) {
let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call { operation, room_id };
let event = ClickhouseEvent::Call {
operation,
room_id,
channel_id,
};
telemetry.report_clickhouse_event(event, telemetry_settings);
}
}

View File

@@ -49,6 +49,7 @@ pub enum Event {
pub struct Room {
id: u64,
channel_id: Option<u64>,
live_kit: Option<LiveKitRoom>,
status: RoomStatus,
shared_projects: HashSet<WeakModelHandle<Project>>,
@@ -93,8 +94,25 @@ impl Entity for Room {
}
impl Room {
pub fn channel_id(&self) -> Option<u64> {
self.channel_id
}
#[cfg(any(test, feature = "test-support"))]
pub fn is_connected(&self) -> bool {
if let Some(live_kit) = self.live_kit.as_ref() {
matches!(
*live_kit.room.status().borrow(),
live_kit_client::ConnectionState::Connected { .. }
)
} else {
false
}
}
fn new(
id: u64,
channel_id: Option<u64>,
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
@@ -185,6 +203,7 @@ impl Room {
Self {
id,
channel_id,
live_kit: live_kit_room,
status: RoomStatus::Online,
shared_projects: Default::default(),
@@ -217,6 +236,7 @@ impl Room {
let room = cx.add_model(|cx| {
Self::new(
room_proto.id,
None,
response.live_kit_connection_info,
client,
user_store,
@@ -248,35 +268,64 @@ impl Room {
})
}
pub(crate) fn join_channel(
channel_id: u64,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
cx: &mut AppContext,
) -> Task<Result<ModelHandle<Self>>> {
cx.spawn(|cx| async move {
Self::from_join_response(
client.request(proto::JoinChannel { channel_id }).await?,
client,
user_store,
cx,
)
})
}
pub(crate) fn join(
call: &IncomingCall,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
cx: &mut AppContext,
) -> Task<Result<ModelHandle<Self>>> {
let room_id = call.room_id;
cx.spawn(|mut cx| async move {
let response = client.request(proto::JoinRoom { id: room_id }).await?;
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.add_model(|cx| {
Self::new(
room_id,
response.live_kit_connection_info,
client,
user_store,
cx,
)
});
room.update(&mut cx, |room, cx| {
room.leave_when_empty = true;
room.apply_room_update(room_proto, cx)?;
anyhow::Ok(())
})?;
Ok(room)
let id = call.room_id;
cx.spawn(|cx| async move {
Self::from_join_response(
client.request(proto::JoinRoom { id }).await?,
client,
user_store,
cx,
)
})
}
fn from_join_response(
response: proto::JoinRoomResponse,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
mut cx: AsyncAppContext,
) -> Result<ModelHandle<Self>> {
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
let room = cx.add_model(|cx| {
Self::new(
room_proto.id,
response.channel_id,
response.live_kit_connection_info,
client,
user_store,
cx,
)
});
room.update(&mut cx, |room, cx| {
room.leave_when_empty = room.channel_id.is_none();
room.apply_room_update(room_proto, cx)?;
anyhow::Ok(())
})?;
Ok(room)
}
fn should_leave(&self) -> bool {
self.leave_when_empty
&& self.pending_room_update.is_none()
@@ -297,7 +346,18 @@ impl Room {
}
log::info!("leaving room");
Audio::play_sound(Sound::Leave, cx);
self.clear_state(cx);
let leave_room = self.client.request(proto::LeaveRoom {});
cx.background().spawn(async move {
leave_room.await?;
anyhow::Ok(())
})
}
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
for project in self.shared_projects.drain() {
if let Some(project) = project.upgrade(cx) {
project.update(cx, |project, cx| {
@@ -314,8 +374,6 @@ impl Room {
}
}
Audio::play_sound(Sound::Leave, cx);
self.status = RoomStatus::Offline;
self.remote_participants.clear();
self.pending_participants.clear();
@@ -324,12 +382,6 @@ impl Room {
self.live_kit.take();
self.pending_room_update.take();
self.maintain_connection.take();
let leave_room = self.client.request(proto::LeaveRoom {});
cx.background().spawn(async move {
leave_room.await?;
anyhow::Ok(())
})
}
async fn maintain_connection(
@@ -592,7 +644,9 @@ impl Room {
if let Some(participants) = remote_participants.log_err() {
for (participant, user) in room.participants.into_iter().zip(participants) {
let Some(peer_id) = participant.peer_id else { continue };
let Some(peer_id) = participant.peer_id else {
continue;
};
this.participant_user_ids.insert(participant.user_id);
let old_projects = this
@@ -1066,11 +1120,11 @@ impl Room {
})
}
pub fn is_muted(&self) -> bool {
pub fn is_muted(&self, cx: &AppContext) -> bool {
self.live_kit
.as_ref()
.and_then(|live_kit| match &live_kit.microphone_track {
LocalTrack::None => Some(true),
LocalTrack::None => Some(settings::get::<CallSettings>(cx).mute_on_join),
LocalTrack::Pending { muted, .. } => Some(*muted),
LocalTrack::Published { muted, .. } => Some(*muted),
})
@@ -1260,7 +1314,7 @@ impl Room {
}
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
let should_mute = !self.is_muted();
let should_mute = !self.is_muted(cx);
if let Some(live_kit) = self.live_kit.as_mut() {
if matches!(live_kit.microphone_track, LocalTrack::None) {
return Ok(self.share_microphone(cx));

51
crates/channel/Cargo.toml Normal file
View File

@@ -0,0 +1,51 @@
[package]
name = "channel"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/channel.rs"
doctest = false
[features]
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies]
client = { path = "../client" }
collections = { path = "../collections" }
db = { path = "../db" }
gpui = { path = "../gpui" }
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
language = { path = "../language" }
settings = { path = "../settings" }
feature_flags = { path = "../feature_flags" }
sum_tree = { path = "../sum_tree" }
anyhow.workspace = true
futures.workspace = true
image = "0.23"
lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
schemars.workspace = true
smol.workspace = true
thiserror.workspace = true
time.workspace = true
tiny_http = "0.8"
uuid = { version = "1.1.2", features = ["v4"] }
url = "2.2"
serde.workspace = true
serde_derive.workspace = true
tempfile = "3"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }

View File

@@ -0,0 +1,14 @@
mod channel_store;
pub mod channel_buffer;
use std::sync::Arc;
pub use channel_store::*;
use client::Client;
#[cfg(test)]
mod channel_store_tests;
pub fn init(client: &Arc<Client>) {
channel_buffer::init(client);
}

View File

@@ -0,0 +1,197 @@
use crate::Channel;
use anyhow::Result;
use client::Client;
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle};
use rpc::{proto, TypedEnvelope};
use std::sync::Arc;
use util::ResultExt;
pub(crate) fn init(client: &Arc<Client>) {
client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
client.add_model_message_handler(ChannelBuffer::handle_add_channel_buffer_collaborator);
client.add_model_message_handler(ChannelBuffer::handle_remove_channel_buffer_collaborator);
}
pub struct ChannelBuffer {
pub(crate) channel: Arc<Channel>,
connected: bool,
collaborators: Vec<proto::Collaborator>,
buffer: ModelHandle<language::Buffer>,
client: Arc<Client>,
subscription: Option<client::Subscription>,
}
pub enum Event {
CollaboratorsChanged,
Disconnected,
}
impl Entity for ChannelBuffer {
type Event = Event;
fn release(&mut self, _: &mut AppContext) {
if self.connected {
self.client
.send(proto::LeaveChannelBuffer {
channel_id: self.channel.id,
})
.log_err();
}
}
}
impl ChannelBuffer {
pub(crate) async fn new(
channel: Arc<Channel>,
client: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<ModelHandle<Self>> {
let response = client
.request(proto::JoinChannelBuffer {
channel_id: channel.id,
})
.await?;
let base_text = response.base_text;
let operations = response
.operations
.into_iter()
.map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?;
let collaborators = response.collaborators;
let buffer = cx.add_model(|_| {
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
});
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
let subscription = client.subscribe_to_entity(channel.id)?;
anyhow::Ok(cx.add_model(|cx| {
cx.subscribe(&buffer, Self::on_buffer_update).detach();
Self {
buffer,
client,
connected: true,
collaborators,
channel,
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
}
}))
}
async fn handle_update_channel_buffer(
this: ModelHandle<Self>,
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let ops = update_channel_buffer
.payload
.operations
.into_iter()
.map(language::proto::deserialize_operation)
.collect::<Result<Vec<_>, _>>()?;
this.update(&mut cx, |this, cx| {
cx.notify();
this.buffer
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
})?;
Ok(())
}
async fn handle_add_channel_buffer_collaborator(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::AddChannelBufferCollaborator>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
let collaborator = envelope.payload.collaborator.ok_or_else(|| {
anyhow::anyhow!(
"Should have gotten a collaborator in the AddChannelBufferCollaborator message"
)
})?;
this.update(&mut cx, |this, cx| {
this.collaborators.push(collaborator);
cx.emit(Event::CollaboratorsChanged);
cx.notify();
});
Ok(())
}
async fn handle_remove_channel_buffer_collaborator(
this: ModelHandle<Self>,
message: TypedEnvelope<proto::RemoveChannelBufferCollaborator>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.collaborators.retain(|collaborator| {
if collaborator.peer_id == message.payload.peer_id {
this.buffer.update(cx, |buffer, cx| {
buffer.remove_peer(collaborator.replica_id as u16, cx)
});
false
} else {
true
}
});
cx.emit(Event::CollaboratorsChanged);
cx.notify();
});
Ok(())
}
fn on_buffer_update(
&mut self,
_: ModelHandle<language::Buffer>,
event: &language::Event,
_: &mut ModelContext<Self>,
) {
if let language::Event::Operation(operation) = event {
let operation = language::proto::serialize_operation(operation);
self.client
.send(proto::UpdateChannelBuffer {
channel_id: self.channel.id,
operations: vec![operation],
})
.log_err();
}
}
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
self.buffer.clone()
}
pub fn collaborators(&self) -> &[proto::Collaborator] {
&self.collaborators
}
pub fn channel(&self) -> Arc<Channel> {
self.channel.clone()
}
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
if self.connected {
self.connected = false;
self.subscription.take();
cx.emit(Event::Disconnected);
cx.notify()
}
}
pub fn is_connected(&self) -> bool {
self.connected
}
pub fn replica_id(&self, cx: &AppContext) -> u16 {
self.buffer.read(cx).replica_id()
}
}

View File

@@ -0,0 +1,656 @@
use crate::channel_buffer::ChannelBuffer;
use anyhow::{anyhow, Result};
use client::{Client, Status, Subscription, User, UserId, UserStore};
use collections::{hash_map, HashMap, HashSet};
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
use rpc::{proto, TypedEnvelope};
use std::sync::Arc;
use util::ResultExt;
pub type ChannelId = u64;
pub struct ChannelStore {
channels_by_id: HashMap<ChannelId, Arc<Channel>>,
channel_paths: Vec<Vec<ChannelId>>,
channel_invitations: Vec<Arc<Channel>>,
channel_participants: HashMap<ChannelId, Vec<Arc<User>>>,
channels_with_admin_privileges: HashSet<ChannelId>,
outgoing_invites: HashSet<(ChannelId, UserId)>,
update_channels_tx: mpsc::UnboundedSender<proto::UpdateChannels>,
opened_buffers: HashMap<ChannelId, OpenedChannelBuffer>,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
_rpc_subscription: Subscription,
_watch_connection_status: Task<()>,
_update_channels: Task<()>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Channel {
pub id: ChannelId,
pub name: String,
}
pub struct ChannelMembership {
pub user: Arc<User>,
pub kind: proto::channel_member::Kind,
pub admin: bool,
}
pub enum ChannelEvent {
ChannelCreated(ChannelId),
ChannelRenamed(ChannelId),
}
impl Entity for ChannelStore {
type Event = ChannelEvent;
}
pub enum ChannelMemberStatus {
Invited,
Member,
NotMember,
}
enum OpenedChannelBuffer {
Open(WeakModelHandle<ChannelBuffer>),
Loading(Shared<Task<Result<ModelHandle<ChannelBuffer>, Arc<anyhow::Error>>>>),
}
impl ChannelStore {
pub fn new(
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
cx: &mut ModelContext<Self>,
) -> Self {
let rpc_subscription =
client.add_message_handler(cx.handle(), Self::handle_update_channels);
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
let mut connection_status = client.status();
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
while let Some(status) = connection_status.next().await {
if !status.is_connected() {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
if matches!(status, Status::ConnectionLost | Status::SignedOut) {
this.handle_disconnect(cx);
} else {
this.disconnect_buffers(cx);
}
});
} else {
break;
}
}
}
});
Self {
channels_by_id: HashMap::default(),
channel_invitations: Vec::default(),
channel_paths: Vec::default(),
channel_participants: Default::default(),
channels_with_admin_privileges: Default::default(),
outgoing_invites: Default::default(),
opened_buffers: Default::default(),
update_channels_tx,
client,
user_store,
_rpc_subscription: rpc_subscription,
_watch_connection_status: watch_connection_status,
_update_channels: cx.spawn_weak(|this, mut cx| async move {
while let Some(update_channels) = update_channels_rx.next().await {
if let Some(this) = this.upgrade(&cx) {
let update_task = this.update(&mut cx, |this, cx| {
this.update_channels(update_channels, cx)
});
if let Some(update_task) = update_task {
update_task.await.log_err();
}
}
}
}),
}
}
pub fn has_children(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
path.len() > ix + 1
} else {
false
}
})
}
pub fn channel_count(&self) -> usize {
self.channel_paths.len()
}
pub fn channels(&self) -> impl '_ + Iterator<Item = (usize, &Arc<Channel>)> {
self.channel_paths.iter().map(move |path| {
let id = path.last().unwrap();
let channel = self.channel_for_id(*id).unwrap();
(path.len() - 1, channel)
})
}
pub fn channel_at_index(&self, ix: usize) -> Option<(usize, &Arc<Channel>)> {
let path = self.channel_paths.get(ix)?;
let id = path.last().unwrap();
let channel = self.channel_for_id(*id).unwrap();
Some((path.len() - 1, channel))
}
pub fn channel_invitations(&self) -> &[Arc<Channel>] {
&self.channel_invitations
}
pub fn channel_for_id(&self, channel_id: ChannelId) -> Option<&Arc<Channel>> {
self.channels_by_id.get(&channel_id)
}
pub fn open_channel_buffer(
&mut self,
channel_id: ChannelId,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<ChannelBuffer>>> {
// Make sure that a given channel buffer is only opened once per
// app instance, even if this method is called multiple times
// with the same channel id while the first task is still running.
let task = loop {
match self.opened_buffers.entry(channel_id) {
hash_map::Entry::Occupied(e) => match e.get() {
OpenedChannelBuffer::Open(buffer) => {
if let Some(buffer) = buffer.upgrade(cx) {
break Task::ready(Ok(buffer)).shared();
} else {
self.opened_buffers.remove(&channel_id);
continue;
}
}
OpenedChannelBuffer::Loading(task) => break task.clone(),
},
hash_map::Entry::Vacant(e) => {
let client = self.client.clone();
let task = cx
.spawn(|this, cx| async move {
let channel = this.read_with(&cx, |this, _| {
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
Arc::new(anyhow!("no channel for id: {}", channel_id))
})
})?;
ChannelBuffer::new(channel, client, cx)
.await
.map_err(Arc::new)
})
.shared();
e.insert(OpenedChannelBuffer::Loading(task.clone()));
cx.spawn({
let task = task.clone();
|this, mut cx| async move {
let result = task.await;
this.update(&mut cx, |this, cx| match result {
Ok(buffer) => {
cx.observe_release(&buffer, move |this, _, _| {
this.opened_buffers.remove(&channel_id);
})
.detach();
this.opened_buffers.insert(
channel_id,
OpenedChannelBuffer::Open(buffer.downgrade()),
);
}
Err(error) => {
log::error!("failed to open channel buffer {error:?}");
this.opened_buffers.remove(&channel_id);
}
});
}
})
.detach();
break task;
}
}
};
cx.foreground()
.spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
}
pub fn is_user_admin(&self, channel_id: ChannelId) -> bool {
self.channel_paths.iter().any(|path| {
if let Some(ix) = path.iter().position(|id| *id == channel_id) {
path[..=ix]
.iter()
.any(|id| self.channels_with_admin_privileges.contains(id))
} else {
false
}
})
}
pub fn channel_participants(&self, channel_id: ChannelId) -> &[Arc<User>] {
self.channel_participants
.get(&channel_id)
.map_or(&[], |v| v.as_slice())
}
pub fn create_channel(
&self,
name: &str,
parent_id: Option<ChannelId>,
cx: &mut ModelContext<Self>,
) -> Task<Result<ChannelId>> {
let client = self.client.clone();
let name = name.trim_start_matches("#").to_owned();
cx.spawn(|this, mut cx| async move {
let channel = client
.request(proto::CreateChannel { name, parent_id })
.await?
.channel
.ok_or_else(|| anyhow!("missing channel in response"))?;
let channel_id = channel.id;
this.update(&mut cx, |this, cx| {
let task = this.update_channels(
proto::UpdateChannels {
channels: vec![channel],
..Default::default()
},
cx,
);
assert!(task.is_none());
// This event is emitted because the collab panel wants to clear the pending edit state
// before this frame is rendered. But we can't guarantee that the collab panel's future
// will resolve before this flush_effects finishes. Synchronously emitting this event
// ensures that the collab panel will observe this creation before the frame completes
cx.emit(ChannelEvent::ChannelCreated(channel_id));
});
Ok(channel_id)
})
}
pub fn invite_member(
&mut self,
channel_id: ChannelId,
user_id: UserId,
admin: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("invite request already in progress")));
}
cx.notify();
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let result = client
.request(proto::InviteChannelMember {
channel_id,
user_id,
admin,
})
.await;
this.update(&mut cx, |this, cx| {
this.outgoing_invites.remove(&(channel_id, user_id));
cx.notify();
});
result?;
Ok(())
})
}
pub fn remove_member(
&mut self,
channel_id: ChannelId,
user_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("invite request already in progress")));
}
cx.notify();
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let result = client
.request(proto::RemoveChannelMember {
channel_id,
user_id,
})
.await;
this.update(&mut cx, |this, cx| {
this.outgoing_invites.remove(&(channel_id, user_id));
cx.notify();
});
result?;
Ok(())
})
}
pub fn set_member_admin(
&mut self,
channel_id: ChannelId,
user_id: UserId,
admin: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if !self.outgoing_invites.insert((channel_id, user_id)) {
return Task::ready(Err(anyhow!("member request already in progress")));
}
cx.notify();
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let result = client
.request(proto::SetChannelMemberAdmin {
channel_id,
user_id,
admin,
})
.await;
this.update(&mut cx, |this, cx| {
this.outgoing_invites.remove(&(channel_id, user_id));
cx.notify();
});
result?;
Ok(())
})
}
pub fn rename(
&mut self,
channel_id: ChannelId,
new_name: &str,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let client = self.client.clone();
let name = new_name.to_string();
cx.spawn(|this, mut cx| async move {
let channel = client
.request(proto::RenameChannel { channel_id, name })
.await?
.channel
.ok_or_else(|| anyhow!("missing channel in response"))?;
this.update(&mut cx, |this, cx| {
let task = this.update_channels(
proto::UpdateChannels {
channels: vec![channel],
..Default::default()
},
cx,
);
assert!(task.is_none());
// This event is emitted because the collab panel wants to clear the pending edit state
// before this frame is rendered. But we can't guarantee that the collab panel's future
// will resolve before this flush_effects finishes. Synchronously emitting this event
// ensures that the collab panel will observe this creation before the frame complete
cx.emit(ChannelEvent::ChannelRenamed(channel_id))
});
Ok(())
})
}
pub fn respond_to_channel_invite(
&mut self,
channel_id: ChannelId,
accept: bool,
) -> impl Future<Output = Result<()>> {
let client = self.client.clone();
async move {
client
.request(proto::RespondToChannelInvite { channel_id, accept })
.await?;
Ok(())
}
}
pub fn get_channel_member_details(
&self,
channel_id: ChannelId,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<ChannelMembership>>> {
let client = self.client.clone();
let user_store = self.user_store.downgrade();
cx.spawn(|_, mut cx| async move {
let response = client
.request(proto::GetChannelMembers { channel_id })
.await?;
let user_ids = response.members.iter().map(|m| m.user_id).collect();
let user_store = user_store
.upgrade(&cx)
.ok_or_else(|| anyhow!("user store dropped"))?;
let users = user_store
.update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
.await?;
Ok(users
.into_iter()
.zip(response.members)
.filter_map(|(user, member)| {
Some(ChannelMembership {
user,
admin: member.admin,
kind: proto::channel_member::Kind::from_i32(member.kind)?,
})
})
.collect())
})
}
pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future<Output = Result<()>> {
let client = self.client.clone();
async move {
client.request(proto::RemoveChannel { channel_id }).await?;
Ok(())
}
}
pub fn has_pending_channel_invite_response(&self, _: &Arc<Channel>) -> bool {
false
}
pub fn has_pending_channel_invite(&self, channel_id: ChannelId, user_id: UserId) -> bool {
self.outgoing_invites.contains(&(channel_id, user_id))
}
async fn handle_update_channels(
this: ModelHandle<Self>,
message: TypedEnvelope<proto::UpdateChannels>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<()> {
this.update(&mut cx, |this, _| {
this.update_channels_tx
.unbounded_send(message.payload)
.unwrap();
});
Ok(())
}
fn handle_disconnect(&mut self, cx: &mut ModelContext<'_, ChannelStore>) {
self.disconnect_buffers(cx);
self.channels_by_id.clear();
self.channel_invitations.clear();
self.channel_participants.clear();
self.channels_with_admin_privileges.clear();
self.channel_paths.clear();
self.outgoing_invites.clear();
cx.notify();
}
fn disconnect_buffers(&mut self, cx: &mut ModelContext<ChannelStore>) {
for (_, buffer) in self.opened_buffers.drain() {
if let OpenedChannelBuffer::Open(buffer) = buffer {
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
}
}
}
}
pub(crate) fn update_channels(
&mut self,
payload: proto::UpdateChannels,
cx: &mut ModelContext<ChannelStore>,
) -> Option<Task<Result<()>>> {
if !payload.remove_channel_invitations.is_empty() {
self.channel_invitations
.retain(|channel| !payload.remove_channel_invitations.contains(&channel.id));
}
for channel in payload.channel_invitations {
match self
.channel_invitations
.binary_search_by_key(&channel.id, |c| c.id)
{
Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
Err(ix) => self.channel_invitations.insert(
ix,
Arc::new(Channel {
id: channel.id,
name: channel.name,
}),
),
}
}
let channels_changed = !payload.channels.is_empty() || !payload.remove_channels.is_empty();
if channels_changed {
if !payload.remove_channels.is_empty() {
self.channels_by_id
.retain(|channel_id, _| !payload.remove_channels.contains(channel_id));
self.channel_participants
.retain(|channel_id, _| !payload.remove_channels.contains(channel_id));
self.channels_with_admin_privileges
.retain(|channel_id| !payload.remove_channels.contains(channel_id));
for channel_id in &payload.remove_channels {
let channel_id = *channel_id;
if let Some(OpenedChannelBuffer::Open(buffer)) =
self.opened_buffers.remove(&channel_id)
{
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, ChannelBuffer::disconnect);
}
}
}
}
for channel_proto in payload.channels {
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
Arc::make_mut(existing_channel).name = channel_proto.name;
} else {
let channel = Arc::new(Channel {
id: channel_proto.id,
name: channel_proto.name,
});
self.channels_by_id.insert(channel.id, channel.clone());
if let Some(parent_id) = channel_proto.parent_id {
let mut ix = 0;
while ix < self.channel_paths.len() {
let path = &self.channel_paths[ix];
if path.ends_with(&[parent_id]) {
let mut new_path = path.clone();
new_path.push(channel.id);
self.channel_paths.insert(ix + 1, new_path);
ix += 1;
}
ix += 1;
}
} else {
self.channel_paths.push(vec![channel.id]);
}
}
}
self.channel_paths.sort_by(|a, b| {
let a = Self::channel_path_sorting_key(a, &self.channels_by_id);
let b = Self::channel_path_sorting_key(b, &self.channels_by_id);
a.cmp(b)
});
self.channel_paths.dedup();
self.channel_paths.retain(|path| {
path.iter()
.all(|channel_id| self.channels_by_id.contains_key(channel_id))
});
}
for permission in payload.channel_permissions {
if permission.is_admin {
self.channels_with_admin_privileges
.insert(permission.channel_id);
} else {
self.channels_with_admin_privileges
.remove(&permission.channel_id);
}
}
cx.notify();
if payload.channel_participants.is_empty() {
return None;
}
let mut all_user_ids = Vec::new();
let channel_participants = payload.channel_participants;
for entry in &channel_participants {
for user_id in entry.participant_user_ids.iter() {
if let Err(ix) = all_user_ids.binary_search(user_id) {
all_user_ids.insert(ix, *user_id);
}
}
}
let users = self
.user_store
.update(cx, |user_store, cx| user_store.get_users(all_user_ids, cx));
Some(cx.spawn(|this, mut cx| async move {
let users = users.await?;
this.update(&mut cx, |this, cx| {
for entry in &channel_participants {
let mut participants: Vec<_> = entry
.participant_user_ids
.iter()
.filter_map(|user_id| {
users
.binary_search_by_key(&user_id, |user| &user.id)
.ok()
.map(|ix| users[ix].clone())
})
.collect();
participants.sort_by_key(|u| u.id);
this.channel_participants
.insert(entry.channel_id, participants);
}
cx.notify();
});
anyhow::Ok(())
}))
}
fn channel_path_sorting_key<'a>(
path: &'a [ChannelId],
channels_by_id: &'a HashMap<ChannelId, Arc<Channel>>,
) -> impl 'a + Iterator<Item = Option<&'a str>> {
path.iter()
.map(|id| Some(channels_by_id.get(id)?.name.as_str()))
}
}

View File

@@ -0,0 +1,168 @@
use super::*;
use client::{Client, UserStore};
use gpui::{AppContext, ModelHandle};
use rpc::proto;
use util::http::FakeHttpClient;
#[gpui::test]
fn test_update_channels(cx: &mut AppContext) {
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));
let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
update_channels(
&channel_store,
proto::UpdateChannels {
channels: vec![
proto::Channel {
id: 1,
name: "b".to_string(),
parent_id: None,
},
proto::Channel {
id: 2,
name: "a".to_string(),
parent_id: None,
},
],
channel_permissions: vec![proto::ChannelPermission {
channel_id: 1,
is_admin: true,
}],
..Default::default()
},
cx,
);
assert_channels(
&channel_store,
&[
//
(0, "a".to_string(), false),
(0, "b".to_string(), true),
],
cx,
);
update_channels(
&channel_store,
proto::UpdateChannels {
channels: vec![
proto::Channel {
id: 3,
name: "x".to_string(),
parent_id: Some(1),
},
proto::Channel {
id: 4,
name: "y".to_string(),
parent_id: Some(2),
},
],
..Default::default()
},
cx,
);
assert_channels(
&channel_store,
&[
(0, "a".to_string(), false),
(1, "y".to_string(), false),
(0, "b".to_string(), true),
(1, "x".to_string(), true),
],
cx,
);
}
#[gpui::test]
fn test_dangling_channel_paths(cx: &mut AppContext) {
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));
let channel_store = cx.add_model(|cx| ChannelStore::new(client, user_store, cx));
update_channels(
&channel_store,
proto::UpdateChannels {
channels: vec![
proto::Channel {
id: 0,
name: "a".to_string(),
parent_id: None,
},
proto::Channel {
id: 1,
name: "b".to_string(),
parent_id: Some(0),
},
proto::Channel {
id: 2,
name: "c".to_string(),
parent_id: Some(1),
},
],
channel_permissions: vec![proto::ChannelPermission {
channel_id: 0,
is_admin: true,
}],
..Default::default()
},
cx,
);
// Sanity check
assert_channels(
&channel_store,
&[
//
(0, "a".to_string(), true),
(1, "b".to_string(), true),
(2, "c".to_string(), true),
],
cx,
);
update_channels(
&channel_store,
proto::UpdateChannels {
remove_channels: vec![1, 2],
..Default::default()
},
cx,
);
// Make sure that the 1/2/3 path is gone
assert_channels(&channel_store, &[(0, "a".to_string(), true)], cx);
}
fn update_channels(
channel_store: &ModelHandle<ChannelStore>,
message: proto::UpdateChannels,
cx: &mut AppContext,
) {
let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
assert!(task.is_none());
}
#[track_caller]
fn assert_channels(
channel_store: &ModelHandle<ChannelStore>,
expected_channels: &[(usize, String, bool)],
cx: &AppContext,
) {
let actual = channel_store.read_with(cx, |store, _| {
store
.channels()
.map(|(depth, channel)| {
(
depth,
channel.name.to_string(),
store.is_user_admin(channel.id),
)
})
.collect::<Vec<_>>()
});
assert_eq!(actual, expected_channels);
}

View File

@@ -9,16 +9,18 @@ path = "src/client.rs"
doctest = false
[features]
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
test-support = ["collections/test-support", "gpui/test-support", "gpui_platform", "rpc/test-support"]
[dependencies]
collections = { path = "../collections" }
db = { path = "../db" }
gpui = { path = "../gpui" }
gpui_platform = {path = "../gpui_platform", optional = true}
util = { path = "../util" }
rpc = { path = "../rpc" }
text = { path = "../text" }
settings = { path = "../settings" }
staff_mode = { path = "../staff_mode" }
feature_flags = { path = "../feature_flags" }
sum_tree = { path = "../sum_tree" }
anyhow.workspace = true

View File

@@ -535,6 +535,7 @@ impl Client {
}
}
#[track_caller]
pub fn add_message_handler<M, E, H, F>(
self: &Arc<Self>,
model: ModelHandle<E>,
@@ -570,7 +571,13 @@ impl Client {
}),
);
if prev_handler.is_some() {
panic!("registered handler for the same message twice");
let location = std::panic::Location::caller();
panic!(
"{}:{} registered handler for the same message {} twice",
location.file(),
location.line(),
std::any::type_name::<M>()
);
}
Subscription::Message {

View File

@@ -74,6 +74,7 @@ pub enum ClickhouseEvent {
Call {
operation: &'static str,
room_id: u64,
channel_id: Option<u64>,
},
}
@@ -134,8 +135,6 @@ impl Telemetry {
}
}
/// This method takes the entire TelemetrySettings struct in order to force client code
/// to pull the struct out of the settings global. Do not remove!
pub fn set_authenticated_user_info(
self: &Arc<Self>,
metrics_id: Option<String>,

View File

@@ -168,6 +168,7 @@ impl FakeServer {
GetPrivateUserInfoResponse {
metrics_id: "the-metrics-id".into(),
staff: false,
flags: Default::default(),
},
)
.await;

View File

@@ -1,18 +1,20 @@
use super::{proto, Client, Status, TypedEnvelope};
use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use staff_mode::StaffMode;
use std::sync::{Arc, Weak};
use util::http::HttpClient;
use util::TryFutureExt as _;
pub type UserId = u64;
#[derive(Default, Debug)]
pub struct User {
pub id: u64,
pub id: UserId,
pub github_login: String,
pub avatar: Option<Arc<ImageData>>,
}
@@ -143,39 +145,48 @@ impl UserStore {
let fetch_metrics_id =
client.request(proto::GetPrivateUserInfo {}).log_err();
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
cx.read(|cx| {
client.telemetry.set_authenticated_user_info(
info.as_ref().map(|info| info.metrics_id.clone()),
info.as_ref().map(|info| info.staff).unwrap_or(false),
cx,
)
});
cx.update(|cx| {
cx.update_default_global(|staff_mode: &mut StaffMode, _| {
if !staff_mode.0 {
*staff_mode = StaffMode(
info.as_ref()
.map(|info| info.staff)
.unwrap_or_default(),
)
}
()
if let Some(info) = info {
cx.update(|cx| {
cx.update_flags(info.staff, info.flags);
client.telemetry.set_authenticated_user_info(
Some(info.metrics_id.clone()),
info.staff,
cx,
)
});
});
} else {
cx.read(|cx| {
client
.telemetry
.set_authenticated_user_info(None, false, cx)
});
}
current_user_tx.send(user).await.ok();
this.update(&mut cx, |_, cx| {
cx.notify();
});
}
}
Status::SignedOut => {
current_user_tx.send(None).await.ok();
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, _| this.clear_contacts()).await;
this.update(&mut cx, |this, cx| {
cx.notify();
this.clear_contacts()
})
.await;
}
}
Status::ConnectionLost => {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, _| this.clear_contacts()).await;
this.update(&mut cx, |this, cx| {
cx.notify();
this.clear_contacts()
})
.await;
}
}
_ => {}

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.16.0"
version = "0.19.0"
publish = false
[[bin]]
@@ -14,8 +14,10 @@ name = "seed"
required-features = ["seed-support"]
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
live_kit_server = { path = "../live_kit_server" }
text = { path = "../text" }
rpc = { path = "../rpc" }
util = { path = "../util" }
@@ -35,6 +37,7 @@ log.workspace = true
nanoid = "0.4"
parking_lot.workspace = true
prometheus = "0.13"
prost.workspace = true
rand.workspace = true
reqwest = { version = "0.11", features = ["json"], optional = true }
scrypt = "0.7"
@@ -62,6 +65,7 @@ collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
call = { path = "../call", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
channel = { path = "../channel" }
editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
@@ -74,6 +78,7 @@ rpc = { path = "../rpc", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
theme = { path = "../theme" }
workspace = { path = "../workspace", features = ["test-support"] }
collab_ui = { path = "../collab_ui", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true

View File

@@ -36,7 +36,8 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b");
CREATE TABLE "rooms" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"live_kit_room" VARCHAR NOT NULL
"live_kit_room" VARCHAR NOT NULL,
"channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE
);
CREATE TABLE "projects" (
@@ -184,3 +185,86 @@ CREATE UNIQUE INDEX
"index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
CREATE TABLE "channels" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT now
);
CREATE TABLE "channel_paths" (
"id_path" TEXT NOT NULL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE
);
CREATE INDEX "index_channel_paths_on_channel_id" ON "channel_paths" ("channel_id");
CREATE TABLE "channel_members" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"admin" BOOLEAN NOT NULL DEFAULT false,
"accepted" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP NOT NULL DEFAULT now
);
CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
CREATE TABLE "buffers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
CREATE TABLE "buffer_operations" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"replica_id" INTEGER NOT NULL,
"lamport_timestamp" INTEGER NOT NULL,
"value" BLOB NOT NULL,
PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
);
CREATE TABLE "buffer_snapshots" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"operation_serialization_version" INTEGER NOT NULL,
PRIMARY KEY(buffer_id, epoch)
);
CREATE TABLE "channel_buffer_collaborators" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"connection_id" INTEGER NOT NULL,
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
"connection_lost" BOOLEAN NOT NULL DEFAULT false,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"replica_id" INTEGER NOT NULL
);
CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");
CREATE TABLE "feature_flags" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"flag" TEXT NOT NULL UNIQUE
);
CREATE INDEX "index_feature_flags" ON "feature_flags" ("id");
CREATE TABLE "user_features" (
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"feature_id" INTEGER NOT NULL REFERENCES feature_flags (id) ON DELETE CASCADE,
PRIMARY KEY (user_id, feature_id)
);
CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");

View File

@@ -0,0 +1,30 @@
DROP TABLE "channel_messages";
DROP TABLE "channel_memberships";
DROP TABLE "org_memberships";
DROP TABLE "orgs";
DROP TABLE "channels";
CREATE TABLE "channels" (
"id" SERIAL PRIMARY KEY,
"name" VARCHAR NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT now()
);
CREATE TABLE "channel_paths" (
"id_path" VARCHAR NOT NULL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE
);
CREATE INDEX "index_channel_paths_on_channel_id" ON "channel_paths" ("channel_id");
CREATE TABLE "channel_members" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"admin" BOOLEAN NOT NULL DEFAULT false,
"accepted" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX "index_channel_members_on_channel_id_and_user_id" ON "channel_members" ("channel_id", "user_id");
ALTER TABLE rooms ADD COLUMN "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE;

View File

@@ -0,0 +1,40 @@
CREATE TABLE "buffers" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX "index_buffers_on_channel_id" ON "buffers" ("channel_id");
CREATE TABLE "buffer_operations" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"replica_id" INTEGER NOT NULL,
"lamport_timestamp" INTEGER NOT NULL,
"value" BYTEA NOT NULL,
PRIMARY KEY(buffer_id, epoch, lamport_timestamp, replica_id)
);
CREATE TABLE "buffer_snapshots" (
"buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"text" TEXT NOT NULL,
"operation_serialization_version" INTEGER NOT NULL,
PRIMARY KEY(buffer_id, epoch)
);
CREATE TABLE "channel_buffer_collaborators" (
"id" SERIAL PRIMARY KEY,
"channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
"connection_id" INTEGER NOT NULL,
"connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
"connection_lost" BOOLEAN NOT NULL DEFAULT FALSE,
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"replica_id" INTEGER NOT NULL
);
CREATE INDEX "index_channel_buffer_collaborators_on_channel_id" ON "channel_buffer_collaborators" ("channel_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_and_replica_id" ON "channel_buffer_collaborators" ("channel_id", "replica_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_server_id" ON "channel_buffer_collaborators" ("connection_server_id");
CREATE INDEX "index_channel_buffer_collaborators_on_connection_id" ON "channel_buffer_collaborators" ("connection_id");
CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection_id_and_server_id" ON "channel_buffer_collaborators" ("channel_id", "connection_id", "connection_server_id");

View File

@@ -0,0 +1,16 @@
CREATE TABLE "feature_flags" (
"id" SERIAL PRIMARY KEY,
"flag" VARCHAR(255) NOT NULL UNIQUE
);
CREATE UNIQUE INDEX "index_feature_flags" ON "feature_flags" ("id");
CREATE TABLE "user_features" (
"user_id" INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
"feature_id" INTEGER NOT NULL REFERENCES feature_flags(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, feature_id)
);
CREATE UNIQUE INDEX "index_user_features_user_id_and_feature_id" ON "user_features" ("user_id", "feature_id");
CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");

View File

@@ -64,9 +64,9 @@ async fn main() {
.expect("failed to fetch user")
.is_none()
{
if let Some(email) = &github_user.email {
if admin {
db.create_user(
email,
&format!("{}@zed.dev", github_user.login),
admin,
db::NewUserParams {
github_login: github_user.login,
@@ -76,15 +76,11 @@ async fn main() {
)
.await
.expect("failed to insert user");
} else if admin {
db.create_user(
&format!("{}@zed.dev", github_user.login),
admin,
db::NewUserParams {
github_login: github_user.login,
github_user_id: github_user.id,
invite_count: 5,
},
} else {
db.get_or_create_user_by_github_account(
&github_user.login,
Some(github_user.id),
github_user.email.as_deref(),
)
.await
.expect("failed to insert user");

File diff suppressed because it is too large Load Diff

128
crates/collab/src/db/ids.rs Normal file
View File

@@ -0,0 +1,128 @@
use crate::Result;
use sea_orm::DbErr;
use sea_query::{Value, ValueTypeErr};
use serde::{Deserialize, Serialize};
macro_rules! id_type {
($name:ident) => {
#[derive(
Clone,
Copy,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
)]
#[serde(transparent)]
pub struct $name(pub i32);
impl $name {
#[allow(unused)]
pub const MAX: Self = Self(i32::MAX);
#[allow(unused)]
pub fn from_proto(value: u64) -> Self {
Self(value as i32)
}
#[allow(unused)]
pub fn to_proto(self) -> u64 {
self.0 as u64
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<$name> for sea_query::Value {
fn from(value: $name) -> Self {
sea_query::Value::Int(Some(value.0))
}
}
impl sea_orm::TryGetable for $name {
fn try_get(
res: &sea_orm::QueryResult,
pre: &str,
col: &str,
) -> Result<Self, sea_orm::TryGetError> {
Ok(Self(i32::try_get(res, pre, col)?))
}
}
impl sea_query::ValueType for $name {
fn try_from(v: Value) -> Result<Self, sea_query::ValueTypeErr> {
Ok(Self(value_to_integer(v)?))
}
fn type_name() -> String {
stringify!($name).into()
}
fn array_type() -> sea_query::ArrayType {
sea_query::ArrayType::Int
}
fn column_type() -> sea_query::ColumnType {
sea_query::ColumnType::Integer(None)
}
}
impl sea_orm::TryFromU64 for $name {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
Ok(Self(n.try_into().map_err(|_| {
DbErr::ConvertFromU64(concat!(
"error converting ",
stringify!($name),
" to u64"
))
})?))
}
}
impl sea_query::Nullable for $name {
fn null() -> Value {
Value::Int(None)
}
}
};
}
fn value_to_integer(v: Value) -> Result<i32, ValueTypeErr> {
match v {
Value::TinyInt(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::SmallInt(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::Int(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::BigInt(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::TinyUnsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::SmallUnsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::Unsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
Value::BigUnsigned(Some(int)) => int.try_into().map_err(|_| ValueTypeErr),
_ => Err(ValueTypeErr),
}
}
id_type!(BufferId);
id_type!(AccessTokenId);
id_type!(ChannelId);
id_type!(ChannelMemberId);
id_type!(ContactId);
id_type!(FollowerId);
id_type!(RoomId);
id_type!(RoomParticipantId);
id_type!(ProjectId);
id_type!(ProjectCollaboratorId);
id_type!(ReplicaId);
id_type!(ServerId);
id_type!(SignupId);
id_type!(UserId);
id_type!(ChannelBufferCollaboratorId);
id_type!(FlagId);

View File

@@ -0,0 +1,11 @@
use super::*;
pub mod access_tokens;
pub mod buffers;
pub mod channels;
pub mod contacts;
pub mod projects;
pub mod rooms;
pub mod servers;
pub mod signups;
pub mod users;

View File

@@ -0,0 +1,53 @@
use super::*;
impl Database {
pub async fn create_access_token(
&self,
user_id: UserId,
access_token_hash: &str,
max_access_token_count: usize,
) -> Result<AccessTokenId> {
self.transaction(|tx| async {
let tx = tx;
let token = access_token::ActiveModel {
user_id: ActiveValue::set(user_id),
hash: ActiveValue::set(access_token_hash.into()),
..Default::default()
}
.insert(&*tx)
.await?;
access_token::Entity::delete_many()
.filter(
access_token::Column::Id.in_subquery(
Query::select()
.column(access_token::Column::Id)
.from(access_token::Entity)
.and_where(access_token::Column::UserId.eq(user_id))
.order_by(access_token::Column::Id, sea_orm::Order::Desc)
.limit(10000)
.offset(max_access_token_count as u64)
.to_owned(),
),
)
.exec(&*tx)
.await?;
Ok(token.id)
})
.await
}
pub async fn get_access_token(
&self,
access_token_id: AccessTokenId,
) -> Result<access_token::Model> {
self.transaction(|tx| async move {
Ok(access_token::Entity::find_by_id(access_token_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such access token"))?)
})
.await
}
}

View File

@@ -0,0 +1,588 @@
use super::*;
use prost::Message;
use text::{EditOperation, InsertionTimestamp, UndoOperation};
impl Database {
pub async fn join_channel_buffer(
&self,
channel_id: ChannelId,
user_id: UserId,
connection: ConnectionId,
) -> Result<proto::JoinChannelBufferResponse> {
self.transaction(|tx| async move {
let tx = tx;
self.check_user_is_channel_member(channel_id, user_id, &tx)
.await?;
let buffer = channel::Model {
id: channel_id,
..Default::default()
}
.find_related(buffer::Entity)
.one(&*tx)
.await?;
let buffer = if let Some(buffer) = buffer {
buffer
} else {
let buffer = buffer::ActiveModel {
channel_id: ActiveValue::Set(channel_id),
..Default::default()
}
.insert(&*tx)
.await?;
buffer_snapshot::ActiveModel {
buffer_id: ActiveValue::Set(buffer.id),
epoch: ActiveValue::Set(0),
text: ActiveValue::Set(String::new()),
operation_serialization_version: ActiveValue::Set(
storage::SERIALIZATION_VERSION,
),
}
.insert(&*tx)
.await?;
buffer
};
// Join the collaborators
let mut collaborators = channel_buffer_collaborator::Entity::find()
.filter(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
.all(&*tx)
.await?;
let replica_ids = collaborators
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(0);
while replica_ids.contains(&replica_id) {
replica_id.0 += 1;
}
let collaborator = channel_buffer_collaborator::ActiveModel {
channel_id: ActiveValue::Set(channel_id),
connection_id: ActiveValue::Set(connection.id as i32),
connection_server_id: ActiveValue::Set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::Set(user_id),
replica_id: ActiveValue::Set(replica_id),
..Default::default()
}
.insert(&*tx)
.await?;
collaborators.push(collaborator);
// Assemble the buffer state
let (base_text, operations) = self.get_buffer_state(&buffer, &tx).await?;
Ok(proto::JoinChannelBufferResponse {
buffer_id: buffer.id.to_proto(),
replica_id: replica_id.to_proto() as u32,
base_text,
operations,
collaborators: collaborators
.into_iter()
.map(|collaborator| proto::Collaborator {
peer_id: Some(collaborator.connection().into()),
user_id: collaborator.user_id.to_proto(),
replica_id: collaborator.replica_id.0 as u32,
})
.collect(),
})
})
.await
}
pub async fn leave_channel_buffer(
&self,
channel_id: ChannelId,
connection: ConnectionId,
) -> Result<Vec<ConnectionId>> {
self.transaction(|tx| async move {
self.leave_channel_buffer_internal(channel_id, connection, &*tx)
.await
})
.await
}
pub async fn leave_channel_buffer_internal(
&self,
channel_id: ChannelId,
connection: ConnectionId,
tx: &DatabaseTransaction,
) -> Result<Vec<ConnectionId>> {
let result = channel_buffer_collaborator::Entity::delete_many()
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
.add(channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
channel_buffer_collaborator::Column::ConnectionServerId
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("not a collaborator on this project"))?;
}
let mut connections = Vec::new();
let mut rows = channel_buffer_collaborator::Entity::find()
.filter(
Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
connections.push(ConnectionId {
id: row.connection_id as u32,
owner_id: row.connection_server_id.0 as u32,
});
}
drop(rows);
if connections.is_empty() {
self.snapshot_buffer(channel_id, &tx).await?;
}
Ok(connections)
}
pub async fn leave_channel_buffers(
&self,
connection: ConnectionId,
) -> Result<Vec<(ChannelId, Vec<ConnectionId>)>> {
self.transaction(|tx| async move {
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
enum QueryChannelIds {
ChannelId,
}
let channel_ids: Vec<ChannelId> = channel_buffer_collaborator::Entity::find()
.select_only()
.column(channel_buffer_collaborator::Column::ChannelId)
.filter(Condition::all().add(
channel_buffer_collaborator::Column::ConnectionId.eq(connection.id as i32),
))
.into_values::<_, QueryChannelIds>()
.all(&*tx)
.await?;
let mut result = Vec::new();
for channel_id in channel_ids {
let collaborators = self
.leave_channel_buffer_internal(channel_id, connection, &*tx)
.await?;
result.push((channel_id, collaborators));
}
Ok(result)
})
.await
}
#[cfg(debug_assertions)]
pub async fn get_channel_buffer_collaborators(
&self,
channel_id: ChannelId,
) -> Result<Vec<UserId>> {
self.transaction(|tx| async move {
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
enum QueryUserIds {
UserId,
}
let users: Vec<UserId> = channel_buffer_collaborator::Entity::find()
.select_only()
.column(channel_buffer_collaborator::Column::UserId)
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.into_values::<_, QueryUserIds>()
.all(&*tx)
.await?;
Ok(users)
})
.await
}
pub async fn update_channel_buffer(
&self,
channel_id: ChannelId,
user: UserId,
operations: &[proto::Operation],
) -> Result<Vec<ConnectionId>> {
self.transaction(move |tx| async move {
self.check_user_is_channel_member(channel_id, user, &*tx)
.await?;
let buffer = buffer::Entity::find()
.filter(buffer::Column::ChannelId.eq(channel_id))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such buffer"))?;
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
enum QueryVersion {
OperationSerializationVersion,
}
let serialization_version: i32 = buffer
.find_related(buffer_snapshot::Entity)
.select_only()
.column(buffer_snapshot::Column::OperationSerializationVersion)
.filter(buffer_snapshot::Column::Epoch.eq(buffer.epoch))
.into_values::<_, QueryVersion>()
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("missing buffer snapshot"))?;
let operations = operations
.iter()
.filter_map(|op| operation_to_storage(op, &buffer, serialization_version))
.collect::<Vec<_>>();
if !operations.is_empty() {
buffer_operation::Entity::insert_many(operations)
.exec(&*tx)
.await?;
}
let mut connections = Vec::new();
let mut rows = channel_buffer_collaborator::Entity::find()
.filter(
Condition::all()
.add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)),
)
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
connections.push(ConnectionId {
id: row.connection_id as u32,
owner_id: row.connection_server_id.0 as u32,
});
}
Ok(connections)
})
.await
}
async fn get_buffer_state(
&self,
buffer: &buffer::Model,
tx: &DatabaseTransaction,
) -> Result<(String, Vec<proto::Operation>)> {
let id = buffer.id;
let (base_text, version) = if buffer.epoch > 0 {
let snapshot = buffer_snapshot::Entity::find()
.filter(
buffer_snapshot::Column::BufferId
.eq(id)
.and(buffer_snapshot::Column::Epoch.eq(buffer.epoch)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such snapshot"))?;
let version = snapshot.operation_serialization_version;
(snapshot.text, version)
} else {
(String::new(), storage::SERIALIZATION_VERSION)
};
let mut rows = buffer_operation::Entity::find()
.filter(
buffer_operation::Column::BufferId
.eq(id)
.and(buffer_operation::Column::Epoch.eq(buffer.epoch)),
)
.stream(&*tx)
.await?;
let mut operations = Vec::new();
while let Some(row) = rows.next().await {
let row = row?;
let operation = operation_from_storage(row, version)?;
operations.push(proto::Operation {
variant: Some(operation),
})
}
Ok((base_text, operations))
}
async fn snapshot_buffer(&self, channel_id: ChannelId, tx: &DatabaseTransaction) -> Result<()> {
let buffer = channel::Model {
id: channel_id,
..Default::default()
}
.find_related(buffer::Entity)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such buffer"))?;
let (base_text, operations) = self.get_buffer_state(&buffer, tx).await?;
if operations.is_empty() {
return Ok(());
}
let mut text_buffer = text::Buffer::new(0, 0, base_text);
text_buffer
.apply_ops(operations.into_iter().filter_map(operation_from_wire))
.unwrap();
let base_text = text_buffer.text();
let epoch = buffer.epoch + 1;
buffer_snapshot::Model {
buffer_id: buffer.id,
epoch,
text: base_text,
operation_serialization_version: storage::SERIALIZATION_VERSION,
}
.into_active_model()
.insert(tx)
.await?;
buffer::ActiveModel {
id: ActiveValue::Unchanged(buffer.id),
epoch: ActiveValue::Set(epoch),
..Default::default()
}
.save(tx)
.await?;
Ok(())
}
}
fn operation_to_storage(
operation: &proto::Operation,
buffer: &buffer::Model,
_format: i32,
) -> Option<buffer_operation::ActiveModel> {
let (replica_id, lamport_timestamp, value) = match operation.variant.as_ref()? {
proto::operation::Variant::Edit(operation) => (
operation.replica_id,
operation.lamport_timestamp,
storage::Operation {
local_timestamp: operation.local_timestamp,
version: version_to_storage(&operation.version),
is_undo: false,
edit_ranges: operation
.ranges
.iter()
.map(|range| storage::Range {
start: range.start,
end: range.end,
})
.collect(),
edit_texts: operation.new_text.clone(),
undo_counts: Vec::new(),
},
),
proto::operation::Variant::Undo(operation) => (
operation.replica_id,
operation.lamport_timestamp,
storage::Operation {
local_timestamp: operation.local_timestamp,
version: version_to_storage(&operation.version),
is_undo: true,
edit_ranges: Vec::new(),
edit_texts: Vec::new(),
undo_counts: operation
.counts
.iter()
.map(|entry| storage::UndoCount {
replica_id: entry.replica_id,
local_timestamp: entry.local_timestamp,
count: entry.count,
})
.collect(),
},
),
_ => None?,
};
Some(buffer_operation::ActiveModel {
buffer_id: ActiveValue::Set(buffer.id),
epoch: ActiveValue::Set(buffer.epoch),
replica_id: ActiveValue::Set(replica_id as i32),
lamport_timestamp: ActiveValue::Set(lamport_timestamp as i32),
value: ActiveValue::Set(value.encode_to_vec()),
})
}
fn operation_from_storage(
row: buffer_operation::Model,
_format_version: i32,
) -> Result<proto::operation::Variant, Error> {
let operation =
storage::Operation::decode(row.value.as_slice()).map_err(|error| anyhow!("{}", error))?;
let version = version_from_storage(&operation.version);
Ok(if operation.is_undo {
proto::operation::Variant::Undo(proto::operation::Undo {
replica_id: row.replica_id as u32,
local_timestamp: operation.local_timestamp as u32,
lamport_timestamp: row.lamport_timestamp as u32,
version,
counts: operation
.undo_counts
.iter()
.map(|entry| proto::UndoCount {
replica_id: entry.replica_id,
local_timestamp: entry.local_timestamp,
count: entry.count,
})
.collect(),
})
} else {
proto::operation::Variant::Edit(proto::operation::Edit {
replica_id: row.replica_id as u32,
local_timestamp: operation.local_timestamp as u32,
lamport_timestamp: row.lamport_timestamp as u32,
version,
ranges: operation
.edit_ranges
.into_iter()
.map(|range| proto::Range {
start: range.start,
end: range.end,
})
.collect(),
new_text: operation.edit_texts,
})
})
}
fn version_to_storage(version: &Vec<proto::VectorClockEntry>) -> Vec<storage::VectorClockEntry> {
version
.iter()
.map(|entry| storage::VectorClockEntry {
replica_id: entry.replica_id,
timestamp: entry.timestamp,
})
.collect()
}
fn version_from_storage(version: &Vec<storage::VectorClockEntry>) -> Vec<proto::VectorClockEntry> {
version
.iter()
.map(|entry| proto::VectorClockEntry {
replica_id: entry.replica_id,
timestamp: entry.timestamp,
})
.collect()
}
// This is currently a manual copy of the deserialization code in the client's langauge crate
pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operation> {
match operation.variant? {
proto::operation::Variant::Edit(edit) => Some(text::Operation::Edit(EditOperation {
timestamp: InsertionTimestamp {
replica_id: edit.replica_id as text::ReplicaId,
local: edit.local_timestamp,
lamport: edit.lamport_timestamp,
},
version: version_from_wire(&edit.version),
ranges: edit
.ranges
.into_iter()
.map(|range| {
text::FullOffset(range.start as usize)..text::FullOffset(range.end as usize)
})
.collect(),
new_text: edit.new_text.into_iter().map(Arc::from).collect(),
})),
proto::operation::Variant::Undo(undo) => Some(text::Operation::Undo {
lamport_timestamp: clock::Lamport {
replica_id: undo.replica_id as text::ReplicaId,
value: undo.lamport_timestamp,
},
undo: UndoOperation {
id: clock::Local {
replica_id: undo.replica_id as text::ReplicaId,
value: undo.local_timestamp,
},
version: version_from_wire(&undo.version),
counts: undo
.counts
.into_iter()
.map(|c| {
(
clock::Local {
replica_id: c.replica_id as text::ReplicaId,
value: c.local_timestamp,
},
c.count,
)
})
.collect(),
},
}),
_ => None,
}
}
fn version_from_wire(message: &[proto::VectorClockEntry]) -> clock::Global {
let mut version = clock::Global::new();
for entry in message {
version.observe(clock::Local {
replica_id: entry.replica_id as text::ReplicaId,
value: entry.timestamp,
});
}
version
}
mod storage {
#![allow(non_snake_case)]
use prost::Message;
pub const SERIALIZATION_VERSION: i32 = 1;
#[derive(Message)]
pub struct Operation {
#[prost(uint32, tag = "1")]
pub local_timestamp: u32,
#[prost(message, repeated, tag = "2")]
pub version: Vec<VectorClockEntry>,
#[prost(bool, tag = "3")]
pub is_undo: bool,
#[prost(message, repeated, tag = "4")]
pub edit_ranges: Vec<Range>,
#[prost(string, repeated, tag = "5")]
pub edit_texts: Vec<String>,
#[prost(message, repeated, tag = "6")]
pub undo_counts: Vec<UndoCount>,
}
#[derive(Message)]
pub struct VectorClockEntry {
#[prost(uint32, tag = "1")]
pub replica_id: u32,
#[prost(uint32, tag = "2")]
pub timestamp: u32,
}
#[derive(Message)]
pub struct Range {
#[prost(uint64, tag = "1")]
pub start: u64,
#[prost(uint64, tag = "2")]
pub end: u64,
}
#[derive(Message)]
pub struct UndoCount {
#[prost(uint32, tag = "1")]
pub replica_id: u32,
#[prost(uint32, tag = "2")]
pub local_timestamp: u32,
#[prost(uint32, tag = "3")]
pub count: u32,
}
}

View File

@@ -0,0 +1,697 @@
use super::*;
impl Database {
pub async fn create_root_channel(
&self,
name: &str,
live_kit_room: &str,
creator_id: UserId,
) -> Result<ChannelId> {
self.create_channel(name, None, live_kit_room, creator_id)
.await
}
pub async fn create_channel(
&self,
name: &str,
parent: Option<ChannelId>,
live_kit_room: &str,
creator_id: UserId,
) -> Result<ChannelId> {
let name = Self::sanitize_channel_name(name)?;
self.transaction(move |tx| async move {
if let Some(parent) = parent {
self.check_user_is_channel_admin(parent, creator_id, &*tx)
.await?;
}
let channel = channel::ActiveModel {
name: ActiveValue::Set(name.to_string()),
..Default::default()
}
.insert(&*tx)
.await?;
let channel_paths_stmt;
if let Some(parent) = parent {
let sql = r#"
INSERT INTO channel_paths
(id_path, channel_id)
SELECT
id_path || $1 || '/', $2
FROM
channel_paths
WHERE
channel_id = $3
"#;
channel_paths_stmt = Statement::from_sql_and_values(
self.pool.get_database_backend(),
sql,
[
channel.id.to_proto().into(),
channel.id.to_proto().into(),
parent.to_proto().into(),
],
);
tx.execute(channel_paths_stmt).await?;
} else {
channel_path::Entity::insert(channel_path::ActiveModel {
channel_id: ActiveValue::Set(channel.id),
id_path: ActiveValue::Set(format!("/{}/", channel.id)),
})
.exec(&*tx)
.await?;
}
channel_member::ActiveModel {
channel_id: ActiveValue::Set(channel.id),
user_id: ActiveValue::Set(creator_id),
accepted: ActiveValue::Set(true),
admin: ActiveValue::Set(true),
..Default::default()
}
.insert(&*tx)
.await?;
room::ActiveModel {
channel_id: ActiveValue::Set(Some(channel.id)),
live_kit_room: ActiveValue::Set(live_kit_room.to_string()),
..Default::default()
}
.insert(&*tx)
.await?;
Ok(channel.id)
})
.await
}
pub async fn remove_channel(
&self,
channel_id: ChannelId,
user_id: UserId,
) -> Result<(Vec<ChannelId>, Vec<UserId>)> {
self.transaction(move |tx| async move {
self.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await?;
// Don't remove descendant channels that have additional parents.
let mut channels_to_remove = self.get_channel_descendants([channel_id], &*tx).await?;
{
let mut channels_to_keep = channel_path::Entity::find()
.filter(
channel_path::Column::ChannelId
.is_in(
channels_to_remove
.keys()
.copied()
.filter(|&id| id != channel_id),
)
.and(
channel_path::Column::IdPath
.not_like(&format!("%/{}/%", channel_id)),
),
)
.stream(&*tx)
.await?;
while let Some(row) = channels_to_keep.next().await {
let row = row?;
channels_to_remove.remove(&row.channel_id);
}
}
let channel_ancestors = self.get_channel_ancestors(channel_id, &*tx).await?;
let members_to_notify: Vec<UserId> = channel_member::Entity::find()
.filter(channel_member::Column::ChannelId.is_in(channel_ancestors))
.select_only()
.column(channel_member::Column::UserId)
.distinct()
.into_values::<_, QueryUserIds>()
.all(&*tx)
.await?;
channel::Entity::delete_many()
.filter(channel::Column::Id.is_in(channels_to_remove.keys().copied()))
.exec(&*tx)
.await?;
Ok((channels_to_remove.into_keys().collect(), members_to_notify))
})
.await
}
pub async fn invite_channel_member(
&self,
channel_id: ChannelId,
invitee_id: UserId,
inviter_id: UserId,
is_admin: bool,
) -> Result<()> {
self.transaction(move |tx| async move {
self.check_user_is_channel_admin(channel_id, inviter_id, &*tx)
.await?;
channel_member::ActiveModel {
channel_id: ActiveValue::Set(channel_id),
user_id: ActiveValue::Set(invitee_id),
accepted: ActiveValue::Set(false),
admin: ActiveValue::Set(is_admin),
..Default::default()
}
.insert(&*tx)
.await?;
Ok(())
})
.await
}
fn sanitize_channel_name(name: &str) -> Result<&str> {
let new_name = name.trim().trim_start_matches('#');
if new_name == "" {
Err(anyhow!("channel name can't be blank"))?;
}
Ok(new_name)
}
pub async fn rename_channel(
&self,
channel_id: ChannelId,
user_id: UserId,
new_name: &str,
) -> Result<String> {
self.transaction(move |tx| async move {
let new_name = Self::sanitize_channel_name(new_name)?.to_string();
self.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await?;
channel::ActiveModel {
id: ActiveValue::Unchanged(channel_id),
name: ActiveValue::Set(new_name.clone()),
..Default::default()
}
.update(&*tx)
.await?;
Ok(new_name)
})
.await
}
pub async fn respond_to_channel_invite(
&self,
channel_id: ChannelId,
user_id: UserId,
accept: bool,
) -> Result<()> {
self.transaction(move |tx| async move {
let rows_affected = if accept {
channel_member::Entity::update_many()
.set(channel_member::ActiveModel {
accepted: ActiveValue::Set(accept),
..Default::default()
})
.filter(
channel_member::Column::ChannelId
.eq(channel_id)
.and(channel_member::Column::UserId.eq(user_id))
.and(channel_member::Column::Accepted.eq(false)),
)
.exec(&*tx)
.await?
.rows_affected
} else {
channel_member::ActiveModel {
channel_id: ActiveValue::Unchanged(channel_id),
user_id: ActiveValue::Unchanged(user_id),
..Default::default()
}
.delete(&*tx)
.await?
.rows_affected
};
if rows_affected == 0 {
Err(anyhow!("no such invitation"))?;
}
Ok(())
})
.await
}
pub async fn remove_channel_member(
&self,
channel_id: ChannelId,
member_id: UserId,
remover_id: UserId,
) -> Result<()> {
self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, remover_id, &*tx)
.await?;
let result = channel_member::Entity::delete_many()
.filter(
channel_member::Column::ChannelId
.eq(channel_id)
.and(channel_member::Column::UserId.eq(member_id)),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("no such member"))?;
}
Ok(())
})
.await
}
pub async fn get_channel_invites_for_user(&self, user_id: UserId) -> Result<Vec<Channel>> {
self.transaction(|tx| async move {
let channel_invites = channel_member::Entity::find()
.filter(
channel_member::Column::UserId
.eq(user_id)
.and(channel_member::Column::Accepted.eq(false)),
)
.all(&*tx)
.await?;
let channels = channel::Entity::find()
.filter(
channel::Column::Id.is_in(
channel_invites
.into_iter()
.map(|channel_member| channel_member.channel_id),
),
)
.all(&*tx)
.await?;
let channels = channels
.into_iter()
.map(|channel| Channel {
id: channel.id,
name: channel.name,
parent_id: None,
})
.collect();
Ok(channels)
})
.await
}
pub async fn get_channels_for_user(&self, user_id: UserId) -> Result<ChannelsForUser> {
self.transaction(|tx| async move {
let tx = tx;
let channel_memberships = channel_member::Entity::find()
.filter(
channel_member::Column::UserId
.eq(user_id)
.and(channel_member::Column::Accepted.eq(true)),
)
.all(&*tx)
.await?;
let parents_by_child_id = self
.get_channel_descendants(channel_memberships.iter().map(|m| m.channel_id), &*tx)
.await?;
let channels_with_admin_privileges = channel_memberships
.iter()
.filter_map(|membership| membership.admin.then_some(membership.channel_id))
.collect();
let mut channels = Vec::with_capacity(parents_by_child_id.len());
{
let mut rows = channel::Entity::find()
.filter(channel::Column::Id.is_in(parents_by_child_id.keys().copied()))
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row = row?;
channels.push(Channel {
id: row.id,
name: row.name,
parent_id: parents_by_child_id.get(&row.id).copied().flatten(),
});
}
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryUserIdsAndChannelIds {
ChannelId,
UserId,
}
let mut channel_participants: HashMap<ChannelId, Vec<UserId>> = HashMap::default();
{
let mut rows = room_participant::Entity::find()
.inner_join(room::Entity)
.filter(room::Column::ChannelId.is_in(channels.iter().map(|c| c.id)))
.select_only()
.column(room::Column::ChannelId)
.column(room_participant::Column::UserId)
.into_values::<_, QueryUserIdsAndChannelIds>()
.stream(&*tx)
.await?;
while let Some(row) = rows.next().await {
let row: (ChannelId, UserId) = row?;
channel_participants.entry(row.0).or_default().push(row.1)
}
}
Ok(ChannelsForUser {
channels,
channel_participants,
channels_with_admin_privileges,
})
})
.await
}
pub async fn get_channel_members(&self, id: ChannelId) -> Result<Vec<UserId>> {
self.transaction(|tx| async move { self.get_channel_members_internal(id, &*tx).await })
.await
}
pub async fn set_channel_member_admin(
&self,
channel_id: ChannelId,
from: UserId,
for_user: UserId,
admin: bool,
) -> Result<()> {
self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, from, &*tx)
.await?;
let result = channel_member::Entity::update_many()
.filter(
channel_member::Column::ChannelId
.eq(channel_id)
.and(channel_member::Column::UserId.eq(for_user)),
)
.set(channel_member::ActiveModel {
admin: ActiveValue::set(admin),
..Default::default()
})
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("no such member"))?;
}
Ok(())
})
.await
}
pub async fn get_channel_member_details(
&self,
channel_id: ChannelId,
user_id: UserId,
) -> Result<Vec<proto::ChannelMember>> {
self.transaction(|tx| async move {
self.check_user_is_channel_admin(channel_id, user_id, &*tx)
.await?;
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryMemberDetails {
UserId,
Admin,
IsDirectMember,
Accepted,
}
let tx = tx;
let ancestor_ids = self.get_channel_ancestors(channel_id, &*tx).await?;
let mut stream = channel_member::Entity::find()
.distinct()
.filter(channel_member::Column::ChannelId.is_in(ancestor_ids.iter().copied()))
.select_only()
.column(channel_member::Column::UserId)
.column(channel_member::Column::Admin)
.column_as(
channel_member::Column::ChannelId.eq(channel_id),
QueryMemberDetails::IsDirectMember,
)
.column(channel_member::Column::Accepted)
.order_by_asc(channel_member::Column::UserId)
.into_values::<_, QueryMemberDetails>()
.stream(&*tx)
.await?;
let mut rows = Vec::<proto::ChannelMember>::new();
while let Some(row) = stream.next().await {
let (user_id, is_admin, is_direct_member, is_invite_accepted): (
UserId,
bool,
bool,
bool,
) = row?;
let kind = match (is_direct_member, is_invite_accepted) {
(true, true) => proto::channel_member::Kind::Member,
(true, false) => proto::channel_member::Kind::Invitee,
(false, true) => proto::channel_member::Kind::AncestorMember,
(false, false) => continue,
};
let user_id = user_id.to_proto();
let kind = kind.into();
if let Some(last_row) = rows.last_mut() {
if last_row.user_id == user_id {
if is_direct_member {
last_row.kind = kind;
last_row.admin = is_admin;
}
continue;
}
}
rows.push(proto::ChannelMember {
user_id,
kind,
admin: is_admin,
});
}
Ok(rows)
})
.await
}
pub async fn get_channel_members_internal(
&self,
id: ChannelId,
tx: &DatabaseTransaction,
) -> Result<Vec<UserId>> {
let ancestor_ids = self.get_channel_ancestors(id, tx).await?;
let user_ids = channel_member::Entity::find()
.distinct()
.filter(
channel_member::Column::ChannelId
.is_in(ancestor_ids.iter().copied())
.and(channel_member::Column::Accepted.eq(true)),
)
.select_only()
.column(channel_member::Column::UserId)
.into_values::<_, QueryUserIds>()
.all(&*tx)
.await?;
Ok(user_ids)
}
pub async fn check_user_is_channel_member(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
let channel_ids = self.get_channel_ancestors(channel_id, tx).await?;
channel_member::Entity::find()
.filter(
channel_member::Column::ChannelId
.is_in(channel_ids)
.and(channel_member::Column::UserId.eq(user_id)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("user is not a channel member or channel does not exist"))?;
Ok(())
}
pub async fn check_user_is_channel_admin(
&self,
channel_id: ChannelId,
user_id: UserId,
tx: &DatabaseTransaction,
) -> Result<()> {
let channel_ids = self.get_channel_ancestors(channel_id, tx).await?;
channel_member::Entity::find()
.filter(
channel_member::Column::ChannelId
.is_in(channel_ids)
.and(channel_member::Column::UserId.eq(user_id))
.and(channel_member::Column::Admin.eq(true)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("user is not a channel admin or channel does not exist"))?;
Ok(())
}
pub async fn get_channel_ancestors(
&self,
channel_id: ChannelId,
tx: &DatabaseTransaction,
) -> Result<Vec<ChannelId>> {
let paths = channel_path::Entity::find()
.filter(channel_path::Column::ChannelId.eq(channel_id))
.all(tx)
.await?;
let mut channel_ids = Vec::new();
for path in paths {
for id in path.id_path.trim_matches('/').split('/') {
if let Ok(id) = id.parse() {
let id = ChannelId::from_proto(id);
if let Err(ix) = channel_ids.binary_search(&id) {
channel_ids.insert(ix, id);
}
}
}
}
Ok(channel_ids)
}
async fn get_channel_descendants(
&self,
channel_ids: impl IntoIterator<Item = ChannelId>,
tx: &DatabaseTransaction,
) -> Result<HashMap<ChannelId, Option<ChannelId>>> {
let mut values = String::new();
for id in channel_ids {
if !values.is_empty() {
values.push_str(", ");
}
write!(&mut values, "({})", id).unwrap();
}
if values.is_empty() {
return Ok(HashMap::default());
}
let sql = format!(
r#"
SELECT
descendant_paths.*
FROM
channel_paths parent_paths, channel_paths descendant_paths
WHERE
parent_paths.channel_id IN ({values}) AND
descendant_paths.id_path LIKE (parent_paths.id_path || '%')
"#
);
let stmt = Statement::from_string(self.pool.get_database_backend(), sql);
let mut parents_by_child_id = HashMap::default();
let mut paths = channel_path::Entity::find()
.from_raw_sql(stmt)
.stream(tx)
.await?;
while let Some(path) = paths.next().await {
let path = path?;
let ids = path.id_path.trim_matches('/').split('/');
let mut parent_id = None;
for id in ids {
if let Ok(id) = id.parse() {
let id = ChannelId::from_proto(id);
if id == path.channel_id {
break;
}
parent_id = Some(id);
}
}
parents_by_child_id.insert(path.channel_id, parent_id);
}
Ok(parents_by_child_id)
}
/// Returns the channel with the given ID and:
/// - true if the user is a member
/// - false if the user hasn't accepted the invitation yet
pub async fn get_channel(
&self,
channel_id: ChannelId,
user_id: UserId,
) -> Result<Option<(Channel, bool)>> {
self.transaction(|tx| async move {
let tx = tx;
let channel = channel::Entity::find_by_id(channel_id).one(&*tx).await?;
if let Some(channel) = channel {
if self
.check_user_is_channel_member(channel_id, user_id, &*tx)
.await
.is_err()
{
return Ok(None);
}
let channel_membership = channel_member::Entity::find()
.filter(
channel_member::Column::ChannelId
.eq(channel_id)
.and(channel_member::Column::UserId.eq(user_id)),
)
.one(&*tx)
.await?;
let is_accepted = channel_membership
.map(|membership| membership.accepted)
.unwrap_or(false);
Ok(Some((
Channel {
id: channel.id,
name: channel.name,
parent_id: None,
},
is_accepted,
)))
} else {
Ok(None)
}
})
.await
}
pub async fn room_id_for_channel(&self, channel_id: ChannelId) -> Result<RoomId> {
self.transaction(|tx| async move {
let tx = tx;
let room = channel::Model {
id: channel_id,
..Default::default()
}
.find_related(room::Entity)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("invalid channel"))?;
Ok(room.id)
})
.await
}
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryUserIds {
UserId,
}

View File

@@ -0,0 +1,298 @@
use super::*;
impl Database {
pub async fn get_contacts(&self, user_id: UserId) -> Result<Vec<Contact>> {
#[derive(Debug, FromQueryResult)]
struct ContactWithUserBusyStatuses {
user_id_a: UserId,
user_id_b: UserId,
a_to_b: bool,
accepted: bool,
should_notify: bool,
user_a_busy: bool,
user_b_busy: bool,
}
self.transaction(|tx| async move {
let user_a_participant = Alias::new("user_a_participant");
let user_b_participant = Alias::new("user_b_participant");
let mut db_contacts = contact::Entity::find()
.column_as(
Expr::tbl(user_a_participant.clone(), room_participant::Column::Id)
.is_not_null(),
"user_a_busy",
)
.column_as(
Expr::tbl(user_b_participant.clone(), room_participant::Column::Id)
.is_not_null(),
"user_b_busy",
)
.filter(
contact::Column::UserIdA
.eq(user_id)
.or(contact::Column::UserIdB.eq(user_id)),
)
.join_as(
JoinType::LeftJoin,
contact::Relation::UserARoomParticipant.def(),
user_a_participant,
)
.join_as(
JoinType::LeftJoin,
contact::Relation::UserBRoomParticipant.def(),
user_b_participant,
)
.into_model::<ContactWithUserBusyStatuses>()
.stream(&*tx)
.await?;
let mut contacts = Vec::new();
while let Some(db_contact) = db_contacts.next().await {
let db_contact = db_contact?;
if db_contact.user_id_a == user_id {
if db_contact.accepted {
contacts.push(Contact::Accepted {
user_id: db_contact.user_id_b,
should_notify: db_contact.should_notify && db_contact.a_to_b,
busy: db_contact.user_b_busy,
});
} else if db_contact.a_to_b {
contacts.push(Contact::Outgoing {
user_id: db_contact.user_id_b,
})
} else {
contacts.push(Contact::Incoming {
user_id: db_contact.user_id_b,
should_notify: db_contact.should_notify,
});
}
} else if db_contact.accepted {
contacts.push(Contact::Accepted {
user_id: db_contact.user_id_a,
should_notify: db_contact.should_notify && !db_contact.a_to_b,
busy: db_contact.user_a_busy,
});
} else if db_contact.a_to_b {
contacts.push(Contact::Incoming {
user_id: db_contact.user_id_a,
should_notify: db_contact.should_notify,
});
} else {
contacts.push(Contact::Outgoing {
user_id: db_contact.user_id_a,
});
}
}
contacts.sort_unstable_by_key(|contact| contact.user_id());
Ok(contacts)
})
.await
}
pub async fn is_user_busy(&self, user_id: UserId) -> Result<bool> {
self.transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(room_participant::Column::UserId.eq(user_id))
.one(&*tx)
.await?;
Ok(participant.is_some())
})
.await
}
pub async fn has_contact(&self, user_id_1: UserId, user_id_2: UserId) -> Result<bool> {
self.transaction(|tx| async move {
let (id_a, id_b) = if user_id_1 < user_id_2 {
(user_id_1, user_id_2)
} else {
(user_id_2, user_id_1)
};
Ok(contact::Entity::find()
.filter(
contact::Column::UserIdA
.eq(id_a)
.and(contact::Column::UserIdB.eq(id_b))
.and(contact::Column::Accepted.eq(true)),
)
.one(&*tx)
.await?
.is_some())
})
.await
}
pub async fn send_contact_request(&self, sender_id: UserId, receiver_id: UserId) -> Result<()> {
self.transaction(|tx| async move {
let (id_a, id_b, a_to_b) = if sender_id < receiver_id {
(sender_id, receiver_id, true)
} else {
(receiver_id, sender_id, false)
};
let rows_affected = contact::Entity::insert(contact::ActiveModel {
user_id_a: ActiveValue::set(id_a),
user_id_b: ActiveValue::set(id_b),
a_to_b: ActiveValue::set(a_to_b),
accepted: ActiveValue::set(false),
should_notify: ActiveValue::set(true),
..Default::default()
})
.on_conflict(
OnConflict::columns([contact::Column::UserIdA, contact::Column::UserIdB])
.values([
(contact::Column::Accepted, true.into()),
(contact::Column::ShouldNotify, false.into()),
])
.action_and_where(
contact::Column::Accepted.eq(false).and(
contact::Column::AToB
.eq(a_to_b)
.and(contact::Column::UserIdA.eq(id_b))
.or(contact::Column::AToB
.ne(a_to_b)
.and(contact::Column::UserIdA.eq(id_a))),
),
)
.to_owned(),
)
.exec_without_returning(&*tx)
.await?;
if rows_affected == 1 {
Ok(())
} else {
Err(anyhow!("contact already requested"))?
}
})
.await
}
/// Returns a bool indicating whether the removed contact had originally accepted or not
///
/// Deletes the contact identified by the requester and responder ids, and then returns
/// whether the deleted contact had originally accepted or was a pending contact request.
///
/// # Arguments
///
/// * `requester_id` - The user that initiates this request
/// * `responder_id` - The user that will be removed
pub async fn remove_contact(&self, requester_id: UserId, responder_id: UserId) -> Result<bool> {
self.transaction(|tx| async move {
let (id_a, id_b) = if responder_id < requester_id {
(responder_id, requester_id)
} else {
(requester_id, responder_id)
};
let contact = contact::Entity::find()
.filter(
contact::Column::UserIdA
.eq(id_a)
.and(contact::Column::UserIdB.eq(id_b)),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such contact"))?;
contact::Entity::delete_by_id(contact.id).exec(&*tx).await?;
Ok(contact.accepted)
})
.await
}
pub async fn dismiss_contact_notification(
&self,
user_id: UserId,
contact_user_id: UserId,
) -> Result<()> {
self.transaction(|tx| async move {
let (id_a, id_b, a_to_b) = if user_id < contact_user_id {
(user_id, contact_user_id, true)
} else {
(contact_user_id, user_id, false)
};
let result = contact::Entity::update_many()
.set(contact::ActiveModel {
should_notify: ActiveValue::set(false),
..Default::default()
})
.filter(
contact::Column::UserIdA
.eq(id_a)
.and(contact::Column::UserIdB.eq(id_b))
.and(
contact::Column::AToB
.eq(a_to_b)
.and(contact::Column::Accepted.eq(true))
.or(contact::Column::AToB
.ne(a_to_b)
.and(contact::Column::Accepted.eq(false))),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("no such contact request"))?
} else {
Ok(())
}
})
.await
}
pub async fn respond_to_contact_request(
&self,
responder_id: UserId,
requester_id: UserId,
accept: bool,
) -> Result<()> {
self.transaction(|tx| async move {
let (id_a, id_b, a_to_b) = if responder_id < requester_id {
(responder_id, requester_id, false)
} else {
(requester_id, responder_id, true)
};
let rows_affected = if accept {
let result = contact::Entity::update_many()
.set(contact::ActiveModel {
accepted: ActiveValue::set(true),
should_notify: ActiveValue::set(true),
..Default::default()
})
.filter(
contact::Column::UserIdA
.eq(id_a)
.and(contact::Column::UserIdB.eq(id_b))
.and(contact::Column::AToB.eq(a_to_b)),
)
.exec(&*tx)
.await?;
result.rows_affected
} else {
let result = contact::Entity::delete_many()
.filter(
contact::Column::UserIdA
.eq(id_a)
.and(contact::Column::UserIdB.eq(id_b))
.and(contact::Column::AToB.eq(a_to_b))
.and(contact::Column::Accepted.eq(false)),
)
.exec(&*tx)
.await?;
result.rows_affected
};
if rows_affected == 1 {
Ok(())
} else {
Err(anyhow!("no such contact request"))?
}
})
.await
}
}

View File

@@ -0,0 +1,926 @@
use super::*;
impl Database {
pub async fn project_count_excluding_admins(&self) -> Result<usize> {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryAs {
Count,
}
self.transaction(|tx| async move {
Ok(project::Entity::find()
.select_only()
.column_as(project::Column::Id.count(), QueryAs::Count)
.inner_join(user::Entity)
.filter(user::Column::Admin.eq(false))
.into_values::<_, QueryAs>()
.one(&*tx)
.await?
.unwrap_or(0i64) as usize)
})
.await
}
pub async fn share_project(
&self,
room_id: RoomId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionServerId
.eq(connection.owner_id as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("could not find participant"))?;
if participant.room_id != room_id {
return Err(anyhow!("shared project on unexpected room"))?;
}
let project = project::ActiveModel {
room_id: ActiveValue::set(participant.room_id),
host_user_id: ActiveValue::set(participant.user_id),
host_connection_id: ActiveValue::set(Some(connection.id as i32)),
host_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
..Default::default()
}
.insert(&*tx)
.await?;
if !worktrees.is_empty() {
worktree::Entity::insert_many(worktrees.iter().map(|worktree| {
worktree::ActiveModel {
id: ActiveValue::set(worktree.id as i64),
project_id: ActiveValue::set(project.id),
abs_path: ActiveValue::set(worktree.abs_path.clone()),
root_name: ActiveValue::set(worktree.root_name.clone()),
visible: ActiveValue::set(worktree.visible),
scan_id: ActiveValue::set(0),
completed_scan_id: ActiveValue::set(0),
}
}))
.exec(&*tx)
.await?;
}
project_collaborator::ActiveModel {
project_id: ActiveValue::set(project.id),
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(ReplicaId(0)),
is_host: ActiveValue::set(true),
..Default::default()
}
.insert(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
Ok((project.id, room))
})
.await
}
pub async fn unshare_project(
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project not found"))?;
if project.host_connection()? == connection {
project::Entity::delete(project.into_active_model())
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
Ok((room, guest_connection_ids))
} else {
Err(anyhow!("cannot unshare a project hosted by another user"))?
}
})
.await
}
pub async fn update_project(
&self,
project_id: ProjectId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
.add(
project::Column::HostConnectionServerId.eq(connection.owner_id as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
self.update_project_worktrees(project.id, worktrees, &tx)
.await?;
let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
let room = self.get_room(project.room_id, &tx).await?;
Ok((room, guest_connection_ids))
})
.await
}
pub(in crate::db) async fn update_project_worktrees(
&self,
project_id: ProjectId,
worktrees: &[proto::WorktreeMetadata],
tx: &DatabaseTransaction,
) -> Result<()> {
if !worktrees.is_empty() {
worktree::Entity::insert_many(worktrees.iter().map(|worktree| worktree::ActiveModel {
id: ActiveValue::set(worktree.id as i64),
project_id: ActiveValue::set(project_id),
abs_path: ActiveValue::set(worktree.abs_path.clone()),
root_name: ActiveValue::set(worktree.root_name.clone()),
visible: ActiveValue::set(worktree.visible),
scan_id: ActiveValue::set(0),
completed_scan_id: ActiveValue::set(0),
}))
.on_conflict(
OnConflict::columns([worktree::Column::ProjectId, worktree::Column::Id])
.update_column(worktree::Column::RootName)
.to_owned(),
)
.exec(&*tx)
.await?;
}
worktree::Entity::delete_many()
.filter(worktree::Column::ProjectId.eq(project_id).and(
worktree::Column::Id.is_not_in(worktrees.iter().map(|worktree| worktree.id as i64)),
))
.exec(&*tx)
.await?;
Ok(())
}
pub async fn update_worktree(
&self,
update: &proto::UpdateWorktree,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let worktree_id = update.worktree_id as i64;
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
// Ensure the update comes from the host.
let _project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
.add(
project::Column::HostConnectionServerId.eq(connection.owner_id as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
// Update metadata.
worktree::Entity::update(worktree::ActiveModel {
id: ActiveValue::set(worktree_id),
project_id: ActiveValue::set(project_id),
root_name: ActiveValue::set(update.root_name.clone()),
scan_id: ActiveValue::set(update.scan_id as i64),
completed_scan_id: if update.is_last_update {
ActiveValue::set(update.scan_id as i64)
} else {
ActiveValue::default()
},
abs_path: ActiveValue::set(update.abs_path.clone()),
..Default::default()
})
.exec(&*tx)
.await?;
if !update.updated_entries.is_empty() {
worktree_entry::Entity::insert_many(update.updated_entries.iter().map(|entry| {
let mtime = entry.mtime.clone().unwrap_or_default();
worktree_entry::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
id: ActiveValue::set(entry.id as i64),
is_dir: ActiveValue::set(entry.is_dir),
path: ActiveValue::set(entry.path.clone()),
inode: ActiveValue::set(entry.inode as i64),
mtime_seconds: ActiveValue::set(mtime.seconds as i64),
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
is_symlink: ActiveValue::set(entry.is_symlink),
is_ignored: ActiveValue::set(entry.is_ignored),
is_external: ActiveValue::set(entry.is_external),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
is_deleted: ActiveValue::set(false),
scan_id: ActiveValue::set(update.scan_id as i64),
}
}))
.on_conflict(
OnConflict::columns([
worktree_entry::Column::ProjectId,
worktree_entry::Column::WorktreeId,
worktree_entry::Column::Id,
])
.update_columns([
worktree_entry::Column::IsDir,
worktree_entry::Column::Path,
worktree_entry::Column::Inode,
worktree_entry::Column::MtimeSeconds,
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::IsSymlink,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
])
.to_owned(),
)
.exec(&*tx)
.await?;
}
if !update.removed_entries.is_empty() {
worktree_entry::Entity::update_many()
.filter(
worktree_entry::Column::ProjectId
.eq(project_id)
.and(worktree_entry::Column::WorktreeId.eq(worktree_id))
.and(
worktree_entry::Column::Id
.is_in(update.removed_entries.iter().map(|id| *id as i64)),
),
)
.set(worktree_entry::ActiveModel {
is_deleted: ActiveValue::Set(true),
scan_id: ActiveValue::Set(update.scan_id as i64),
..Default::default()
})
.exec(&*tx)
.await?;
}
if !update.updated_repositories.is_empty() {
worktree_repository::Entity::insert_many(update.updated_repositories.iter().map(
|repository| worktree_repository::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
work_directory_id: ActiveValue::set(repository.work_directory_id as i64),
scan_id: ActiveValue::set(update.scan_id as i64),
branch: ActiveValue::set(repository.branch.clone()),
is_deleted: ActiveValue::set(false),
},
))
.on_conflict(
OnConflict::columns([
worktree_repository::Column::ProjectId,
worktree_repository::Column::WorktreeId,
worktree_repository::Column::WorkDirectoryId,
])
.update_columns([
worktree_repository::Column::ScanId,
worktree_repository::Column::Branch,
])
.to_owned(),
)
.exec(&*tx)
.await?;
}
if !update.removed_repositories.is_empty() {
worktree_repository::Entity::update_many()
.filter(
worktree_repository::Column::ProjectId
.eq(project_id)
.and(worktree_repository::Column::WorktreeId.eq(worktree_id))
.and(
worktree_repository::Column::WorkDirectoryId
.is_in(update.removed_repositories.iter().map(|id| *id as i64)),
),
)
.set(worktree_repository::ActiveModel {
is_deleted: ActiveValue::Set(true),
scan_id: ActiveValue::Set(update.scan_id as i64),
..Default::default()
})
.exec(&*tx)
.await?;
}
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
})
.await
}
pub async fn update_diagnostic_summary(
&self,
update: &proto::UpdateDiagnosticSummary,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let worktree_id = update.worktree_id as i64;
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let summary = update
.summary
.as_ref()
.ok_or_else(|| anyhow!("invalid summary"))?;
// Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.host_connection()? != connection {
return Err(anyhow!("can't update a project hosted by someone else"))?;
}
// Update summary.
worktree_diagnostic_summary::Entity::insert(worktree_diagnostic_summary::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
path: ActiveValue::set(summary.path.clone()),
language_server_id: ActiveValue::set(summary.language_server_id as i64),
error_count: ActiveValue::set(summary.error_count as i32),
warning_count: ActiveValue::set(summary.warning_count as i32),
..Default::default()
})
.on_conflict(
OnConflict::columns([
worktree_diagnostic_summary::Column::ProjectId,
worktree_diagnostic_summary::Column::WorktreeId,
worktree_diagnostic_summary::Column::Path,
])
.update_columns([
worktree_diagnostic_summary::Column::LanguageServerId,
worktree_diagnostic_summary::Column::ErrorCount,
worktree_diagnostic_summary::Column::WarningCount,
])
.to_owned(),
)
.exec(&*tx)
.await?;
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
})
.await
}
pub async fn start_language_server(
&self,
update: &proto::StartLanguageServer,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let server = update
.server
.as_ref()
.ok_or_else(|| anyhow!("invalid language server"))?;
// Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.host_connection()? != connection {
return Err(anyhow!("can't update a project hosted by someone else"))?;
}
// Add the newly-started language server.
language_server::Entity::insert(language_server::ActiveModel {
project_id: ActiveValue::set(project_id),
id: ActiveValue::set(server.id as i64),
name: ActiveValue::set(server.name.clone()),
..Default::default()
})
.on_conflict(
OnConflict::columns([
language_server::Column::ProjectId,
language_server::Column::Id,
])
.update_column(language_server::Column::Name)
.to_owned(),
)
.exec(&*tx)
.await?;
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
})
.await
}
pub async fn update_worktree_settings(
&self,
update: &proto::UpdateWorktreeSettings,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
// Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.host_connection()? != connection {
return Err(anyhow!("can't update a project hosted by someone else"))?;
}
if let Some(content) = &update.content {
worktree_settings_file::Entity::insert(worktree_settings_file::ActiveModel {
project_id: ActiveValue::Set(project_id),
worktree_id: ActiveValue::Set(update.worktree_id as i64),
path: ActiveValue::Set(update.path.clone()),
content: ActiveValue::Set(content.clone()),
})
.on_conflict(
OnConflict::columns([
worktree_settings_file::Column::ProjectId,
worktree_settings_file::Column::WorktreeId,
worktree_settings_file::Column::Path,
])
.update_column(worktree_settings_file::Column::Content)
.to_owned(),
)
.exec(&*tx)
.await?;
} else {
worktree_settings_file::Entity::delete(worktree_settings_file::ActiveModel {
project_id: ActiveValue::Set(project_id),
worktree_id: ActiveValue::Set(update.worktree_id as i64),
path: ActiveValue::Set(update.path.clone()),
..Default::default()
})
.exec(&*tx)
.await?;
}
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
Ok(connection_ids)
})
.await
}
pub async fn join_project(
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(Project, ReplicaId)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
.add(
room_participant::Column::AnsweringConnectionId
.eq(connection.id as i32),
)
.add(
room_participant::Column::AnsweringConnectionServerId
.eq(connection.owner_id as i32),
),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("must join a room first"))?;
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
if project.room_id != participant.room_id {
return Err(anyhow!("no such project"))?;
}
let mut collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let replica_ids = collaborators
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(1);
while replica_ids.contains(&replica_id) {
replica_id.0 += 1;
}
let new_collaborator = project_collaborator::ActiveModel {
project_id: ActiveValue::set(project_id),
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(replica_id),
is_host: ActiveValue::set(false),
..Default::default()
}
.insert(&*tx)
.await?;
collaborators.push(new_collaborator);
let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
let mut worktrees = db_worktrees
.into_iter()
.map(|db_worktree| {
(
db_worktree.id as u64,
Worktree {
id: db_worktree.id as u64,
abs_path: db_worktree.abs_path,
root_name: db_worktree.root_name,
visible: db_worktree.visible,
entries: Default::default(),
repository_entries: Default::default(),
diagnostic_summaries: Default::default(),
settings_files: Default::default(),
scan_id: db_worktree.scan_id as u64,
completed_scan_id: db_worktree.completed_scan_id as u64,
},
)
})
.collect::<BTreeMap<_, _>>();
// Populate worktree entries.
{
let mut db_entries = worktree_entry::Entity::find()
.filter(
Condition::all()
.add(worktree_entry::Column::ProjectId.eq(project_id))
.add(worktree_entry::Column::IsDeleted.eq(false)),
)
.stream(&*tx)
.await?;
while let Some(db_entry) = db_entries.next().await {
let db_entry = db_entry?;
if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) {
worktree.entries.push(proto::Entry {
id: db_entry.id as u64,
is_dir: db_entry.is_dir,
path: db_entry.path,
inode: db_entry.inode as u64,
mtime: Some(proto::Timestamp {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
});
}
}
}
// Populate repository entries.
{
let mut db_repository_entries = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project_id))
.add(worktree_repository::Column::IsDeleted.eq(false)),
)
.stream(&*tx)
.await?;
while let Some(db_repository_entry) = db_repository_entries.next().await {
let db_repository_entry = db_repository_entry?;
if let Some(worktree) =
worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
{
worktree.repository_entries.insert(
db_repository_entry.work_directory_id as u64,
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
},
);
}
}
}
// Populate worktree diagnostic summaries.
{
let mut db_summaries = worktree_diagnostic_summary::Entity::find()
.filter(worktree_diagnostic_summary::Column::ProjectId.eq(project_id))
.stream(&*tx)
.await?;
while let Some(db_summary) = db_summaries.next().await {
let db_summary = db_summary?;
if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) {
worktree
.diagnostic_summaries
.push(proto::DiagnosticSummary {
path: db_summary.path,
language_server_id: db_summary.language_server_id as u64,
error_count: db_summary.error_count as u32,
warning_count: db_summary.warning_count as u32,
});
}
}
}
// Populate worktree settings files
{
let mut db_settings_files = worktree_settings_file::Entity::find()
.filter(worktree_settings_file::Column::ProjectId.eq(project_id))
.stream(&*tx)
.await?;
while let Some(db_settings_file) = db_settings_files.next().await {
let db_settings_file = db_settings_file?;
if let Some(worktree) =
worktrees.get_mut(&(db_settings_file.worktree_id as u64))
{
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
});
}
}
}
// Populate language servers.
let language_servers = project
.find_related(language_server::Entity)
.all(&*tx)
.await?;
let project = Project {
collaborators: collaborators
.into_iter()
.map(|collaborator| ProjectCollaborator {
connection_id: collaborator.connection(),
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
})
.collect(),
worktrees,
language_servers: language_servers
.into_iter()
.map(|language_server| proto::LanguageServer {
id: language_server.id as u64,
name: language_server.name,
})
.collect(),
};
Ok((project, replica_id as ReplicaId))
})
.await
}
pub async fn leave_project(
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(proto::Room, LeftProject)>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
.add(project_collaborator::Column::ProjectId.eq(project_id))
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_collaborator::Column::ConnectionServerId
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
Err(anyhow!("not a collaborator on this project"))?;
}
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let connection_ids = collaborators
.into_iter()
.map(|collaborator| collaborator.connection())
.collect();
follower::Entity::delete_many()
.filter(
Condition::any()
.add(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::LeaderConnectionServerId
.eq(connection.owner_id),
)
.add(follower::Column::LeaderConnectionId.eq(connection.id)),
)
.add(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::FollowerConnectionServerId
.eq(connection.owner_id),
)
.add(follower::Column::FollowerConnectionId.eq(connection.id)),
),
)
.exec(&*tx)
.await?;
let room = self.get_room(project.room_id, &tx).await?;
let left_project = LeftProject {
id: project_id,
host_user_id: project.host_user_id,
host_connection_id: project.host_connection()?,
connection_ids,
};
Ok((room, left_project))
})
.await
}
pub async fn project_collaborators(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<Vec<ProjectCollaborator>>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.all(&*tx)
.await?
.into_iter()
.map(|collaborator| ProjectCollaborator {
connection_id: collaborator.connection(),
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
})
.collect::<Vec<_>>();
if collaborators
.iter()
.any(|collaborator| collaborator.connection_id == connection_id)
{
Ok(collaborators)
} else {
Err(anyhow!("no such project"))?
}
})
.await
}
pub async fn project_connection_ids(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let mut collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.stream(&*tx)
.await?;
let mut connection_ids = HashSet::default();
while let Some(collaborator) = collaborators.next().await {
let collaborator = collaborator?;
connection_ids.insert(collaborator.connection());
}
if connection_ids.contains(&connection_id) {
Ok(connection_ids)
} else {
Err(anyhow!("no such project"))?
}
})
.await
}
async fn project_guest_connection_ids(
&self,
project_id: ProjectId,
tx: &DatabaseTransaction,
) -> Result<Vec<ConnectionId>> {
let mut collaborators = project_collaborator::Entity::find()
.filter(
project_collaborator::Column::ProjectId
.eq(project_id)
.and(project_collaborator::Column::IsHost.eq(false)),
)
.stream(tx)
.await?;
let mut guest_connection_ids = Vec::new();
while let Some(collaborator) = collaborators.next().await {
let collaborator = collaborator?;
guest_connection_ids.push(collaborator.connection());
}
Ok(guest_connection_ids)
}
pub async fn room_id_for_project(&self, project_id: ProjectId) -> Result<RoomId> {
self.transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project {} not found", project_id))?;
Ok(project.room_id)
})
.await
}
pub async fn follow(
&self,
project_id: ProjectId,
leader_connection: ConnectionId,
follower_connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
follower::ActiveModel {
room_id: ActiveValue::set(room_id),
project_id: ActiveValue::set(project_id),
leader_connection_server_id: ActiveValue::set(ServerId(
leader_connection.owner_id as i32,
)),
leader_connection_id: ActiveValue::set(leader_connection.id as i32),
follower_connection_server_id: ActiveValue::set(ServerId(
follower_connection.owner_id as i32,
)),
follower_connection_id: ActiveValue::set(follower_connection.id as i32),
..Default::default()
}
.insert(&*tx)
.await?;
let room = self.get_room(room_id, &*tx).await?;
Ok(room)
})
.await
}
pub async fn unfollow(
&self,
project_id: ProjectId,
leader_connection: ConnectionId,
follower_connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
follower::Entity::delete_many()
.filter(
Condition::all()
.add(follower::Column::ProjectId.eq(project_id))
.add(
follower::Column::LeaderConnectionServerId
.eq(leader_connection.owner_id),
)
.add(follower::Column::LeaderConnectionId.eq(leader_connection.id))
.add(
follower::Column::FollowerConnectionServerId
.eq(follower_connection.owner_id),
)
.add(follower::Column::FollowerConnectionId.eq(follower_connection.id)),
)
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &*tx).await?;
Ok(room)
})
.await
}
}

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