Compare commits

..

396 Commits

Author SHA1 Message Date
Conrad Irwin
284ad179d7 Don't auto-start during install flow 2024-04-30 16:09:19 -06:00
Conrad Irwin
30b55279b5 Bundle linux preview releases too 2024-04-30 16:04:42 -06:00
Conrad Irwin
28bcc95468 installer (#11229)
Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-30 16:01:07 -06:00
Marshall Bowers
d55b637b7e assistant2: Fix the height of collapsed messages (#11230)
This PR fixes the height of collapsed messages, addressing the
associated TODO comment.

Release Notes:

- N/A
2024-04-30 17:44:51 -04:00
Marshall Bowers
f2a1226e18 assistant2: Setup storybook (#11228)
This PR sets up the `assistant2` crate with the storybook so that UI
elements can be iterated on in isolation.

To start, we have some stories for the `ChatMessage` component:

```sh
cargo run -p storybook -- components/assistant_chat_message
```

<img width="1233" alt="Screenshot 2024-04-30 at 5 20 03 PM"
src="https://github.com/zed-industries/zed/assets/1486634/510967ea-0e9b-4fa9-94fb-421ee74bcc45">

Release Notes:

- N/A
2024-04-30 17:33:32 -04:00
Marshall Bowers
96b1fc4650 assistant2: Align chat messages with composer (#11227)
This PR adds some additional spacing to the composer so it aligns with
the chat messages.

Release Notes:

- N/A
2024-04-30 17:00:23 -04:00
Conrad Irwin
1d4814e5b6 installer (#11224)
Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-30 14:25:12 -06:00
Conrad Irwin
cb7350174e Don't allow dropping files on remote projects (#11218)
Release Notes:

- Fixed a panic when a remote participant dropped a local file on your
project
2024-04-30 14:24:47 -06:00
Marshall Bowers
f11a7811f3 assistant2: Use composer for editing inline messages (#11222)
This PR updates the assistant to render historical user messages the
same as ones from the assistant.

Double-clicking on a message will open a composer inline for editing.
Pressing `Esc` will cancel the edit.

We don't yet restore the previous state of the message upon canceling.

<img width="401" alt="Screenshot 2024-04-30 at 4 04 01 PM"
src="https://github.com/zed-industries/zed/assets/1486634/5f253fa8-6578-4054-be30-c495e326d700">

<img width="401" alt="Screenshot 2024-04-30 at 4 04 28 PM"
src="https://github.com/zed-industries/zed/assets/1486634/edf25cea-d97e-44d2-8772-3690eac017a4">


Release Notes:

- N/A
2024-04-30 16:12:20 -04:00
Marshall Bowers
e8ee0131f1 Remove unused assistant prompt (#11221)
This PR removes an unused assistant prompt.

Release Notes:

- N/A

Co-authored-by: Nathan <nathan@zed.dev>
2024-04-30 15:32:11 -04:00
Max Brunsfeld
38b9d5cc36 Fix some semantic index issues (#11216)
* [x] Fixed an issue where embeddings would be assigned incorrectly to
files if a subset of embedding batches failed
* [x] Added a command to debug which paths are present in the semantic
index
* [x] Determine why so many paths are often missing from the semantic
index
* we erroring out if an embedding batch contained multiple texts that
were the same, which can happen if a worktree contains multiple copies
of the same text (e.g. a license).

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Kyle <kylek@zed.dev>
Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
2024-04-30 10:55:38 -07:00
Marshall Bowers
d01428e69c assistant2: Add create buffer tool (#11219)
This PR adds a new tool to the `assistant2` crate that allows the
assistant to create a new buffer with some content.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-04-30 13:43:25 -04:00
Piotr Osiewicz
ada2791fa3 editor: Move code actions menu closer to the indicator when it is deployed via click (#11214)
Before:

![image](https://github.com/zed-industries/zed/assets/24362066/98d633a7-c982-4522-b4dc-b944b70b8081)

After: 

![image](https://github.com/zed-industries/zed/assets/24362066/79931e12-0e6c-4ece-b734-5af7d02f7e50)

Release Notes:

- N/A
2024-04-30 18:51:20 +02:00
Marshall Bowers
713c314d67 Clean up doc comments on LanguageModelTool trait (#11217)
This PR cleans up the doc comments on the `LanguageModelTool` trait to
follow Rust documentation conventions.

Release Notes:

- N/A
2024-04-30 12:32:59 -04:00
Marshall Bowers
e3de440715 assistant2: Restructure tools in preparation for adding more (#11213)
This PR does a slight restructuring of how tools are defined in the
`assistant2` crate to make it more amenable to adding more tools in the
near future.

Release Notes:

- N/A
2024-04-30 11:12:44 -04:00
Thorsten Ball
d743c19fe2 Add script to package Linux binary into archive (#11165)
Release Notes:

- N/A
2024-04-30 14:56:48 +02:00
Thorsten Ball
73d0600ad2 project: Set completion to undocumented if text empty (#11207)
I think the previous code was missing a `return` in there because it
always overwrote the `completion.documentation` field, even if the
`text.is_empty()` is true.

Release Notes:

- N/A
2024-04-30 14:56:22 +02:00
Stanislav Alekseev
f96cab286c Add avatar support for codeberg in git blame (#10991)
Release Notes:

- Added support for avatars in git blame for repositories hosted on
codeberg

<img width="1144" alt="Screenshot 2024-04-25 at 16 45 22"
src="https://github.com/zed-industries/zed/assets/43210583/d44770d8-44ea-4c6b-a1c0-ac2d1d49408f">

Questions:
- Should we move git stuff like `Commit`, `Author`, etc outside of
hosting-specific files (I don't think so, as other hostings can have
different stuff)
- Should we also add support for self hosted forgejo instances or should
it be a different PR?
2024-04-30 12:57:10 +02:00
Conrad Irwin
8152e0676f Allow triggering preview and stable simultaneously (#11201)
Release Notes:


- N/A
2024-04-29 20:55:50 -06:00
Conrad Irwin
62c12cd549 Reduce frequency of workspace save (#11183)
Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:


- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-29 20:27:01 -06:00
Hans
4fad96b179 Add yss operation support for vim surrounds: (#11084)
The Motion::CurrentLine operation will contain the newline of the
current line, so we need to deal with this edge case



Release Notes:

- N/A
2024-04-29 19:38:14 -06:00
Conrad Irwin
ad44237467 Fix panic in SVG rendering (#11196)
Release Notes:

- Fixed a panic in SVG rendering
2024-04-29 19:03:37 -06:00
Danny Hua
bc736265be support vim replace command with range (#10709)
Release Notes:

- Added support for line ranges in vim replace commands #9428


- not supporting anything other than bare line numbers right now
- ~need to figure out how to show range in question in search bar~
@ConradIrwin implemented showing a highlight of the selected range for a
short direction instead
- ~tests lol~
2024-04-29 18:49:30 -06:00
Conrad Irwin
0697b417a0 Hang diagnostics (#11190)
Release Notes:

- Added diagnostics for main-thread hangs on macOS. These are only
enabled if you've opted into diagnostics.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-29 17:13:28 -07:00
Marshall Bowers
04cd8dd0f2 assistant2: Add the ability to collapse chat messages (#11194)
This PR adds the ability to collapse/uncollapse chat messages.

I think the spacing might be a little off with the collapsed
calculations, so we'll need to look into that.


https://github.com/zed-industries/zed/assets/1486634/4009c831-b44e-4b30-85ed-0266cb5b8a26

Release Notes:

- N/A
2024-04-29 19:25:58 -04:00
shimataro
ce643e6bef Add Gentoo support to Linux dependency script (#11141)
This PR adds dependencies for Gentoo Linux.

Release Notes:

- N/A
2024-04-29 16:08:22 -07:00
apricotbucket28
6ab9c3c3ab x11: HiDPI support (#11140)
Fixes https://github.com/zed-industries/zed/issues/11121

Release Notes:

- N/A
2024-04-29 16:07:54 -07:00
apricotbucket28
a765535557 wayland: Implement cursor-shape-v1 (#11106)
Fixes wrong cursor icons and sizes on KDE 6 (and possibly other
compositors)

Gnome still doesn't support this protocol, so to fix cursor settings
there we'll need to read `gsettings`.

Before:

![image](https://github.com/zed-industries/zed/assets/71973804/f0c3dc1b-2bed-43f7-b579-df6c9c0b547f)

After:

![image](https://github.com/zed-industries/zed/assets/71973804/6de639f9-653d-4896-80ca-b7c69641eded)


Release Notes:

- N/A
2024-04-29 16:06:18 -07:00
Marshall Bowers
089ea7852d assistant2: Use ChatMessage component to render chat messages (#11193)
This PR updates the new assistant panel to use the `ChatMessage`
component to render its chat messages.

This also lays the foundation for collapsing the messages, though that
has yet to be wired up.

Adapted from the work on the `assistant-chat-ui` branch.

Release Notes:

- N/A
2024-04-29 18:47:16 -04:00
Marshall Bowers
ae650342ce assistant2: Add headers to chat messages (#11191)
This PR adds headers to the chat messages in the new assistant panel.

Adapted from the work on the `assistant-chat-ui` branch.

Release Notes:

- N/A
2024-04-29 18:21:43 -04:00
Max Brunsfeld
1c09b69384 Pin message composer to the bottom of the new assistant panel (#11186)
Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Nate <nate@zed.dev>
Co-authored-by: Kyle <kylek@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-29 14:26:19 -07:00
Nate Butler
f842d19b0b Remove extra space right of traffic lights on macOS (#11176)
This PR balances the left and right spacing around the traffic lights on
macOS.

Release Notes:

- Minor UI changes to the titlebar
2024-04-29 16:43:21 -04:00
Marshall Bowers
d633a0da78 gpui: Fix Global trait (#11187)
This PR restores the `Global` trait's status as a marker trait.

This was the original intent from #7095, when it was added, that had
been lost in #9777.

The purpose of the `Global` trait is to statically convey what types can
and can't be accessed as `Global` state, as well as provide a way of
restricting access to said globals.

For example, in the case of the `ThemeRegistry` we have a private
`GlobalThemeRegistry` that is marked as `Global`:
91b3c24ed3/crates/theme/src/registry.rs (L25-L34)

We're then able to permit reading the `ThemeRegistry` from the
`GlobalThemeRegistry` via a custom getter, while still restricting which
callers are able to mutate the global:
91b3c24ed3/crates/theme/src/registry.rs (L46-L61)

Release Notes:

- N/A
2024-04-29 16:37:37 -04:00
Piotr Osiewicz
91b3c24ed3 editor: Clear diagnostics when folding a range that contains it (#11167)
Fixes #4659 

Release Notes:

- Fixed active diagnostic in editor showing up when it's line is in a
folded range.
2024-04-29 20:50:14 +02:00
Bennet Bo Fenner
20625e98ad preview tabs: Allow replacing preview tab when using code navigation (#10730)
This PR adds support for replacing the current preview tab when using
GoToDefinition. Previously a tab, that was navigated away from, was
converted into a permanent tab and the new tab was opened as preview.

Without `enable_preview_from_code_navigation`:


https://github.com/zed-industries/zed/assets/53836821/99840724-d6ff-4738-a9c4-ee71a0001634

With `enable_preview_from_code_navigation`:


https://github.com/zed-industries/zed/assets/53836821/8c60efcb-d597-40bf-b08b-13faf5a289b6

Note: In the future I would like to improve support for the navigation
history, because right now tabs that are not "normal" project items,
e.g. FindAllReferences cannot be reopened

Release Notes:

- Added support for replacing the current preview tab when using code
navigation (`enable_preview_from_code_navigation`)
2024-04-29 20:47:01 +02:00
Marshall Bowers
9ff847753e Register ESLint as an available language server (#11178)
This PR adds the ability for the ESLint language server (`eslint`) to be
controlled by the `language_servers` setting.

Now in your settings you can indicate that the ESLint language server
should be used for a given language, even if that language does not have
the ESLint language server registered for it already:

```json
{
  "languages": {
    "My Language": {
      "language_servers": ["eslint", "..."]
    }
  }
}
```

Release Notes:

- N/A
2024-04-29 13:18:30 -04:00
Owen Law
ec95605fec XI2 Smooth Scrolling for X11 - Attempt 2 (#11110)
This should have fixed the problems that some users were reporting with
https://github.com/zed-industries/zed/pull/10695 .

The problem was with devices which send more than one valuator axis in a
single event whereas the original PR assumed there would only ever be
one axis per event. This version also does away with the complicated
device selection and instead just uses the master pointer device, which
automatically uses all sub-pointers.

Edit: Confirmed working for one of the user's which the first attempt
was broken for.

Release Notes:

- Added smooth scrolling for X11 on Linux
- Added horizontal scrolling for X11 on Linux
2024-04-29 09:40:42 -07:00
Jakob Hellermann
ff8e7f91c1 Fix wayland keyrepeat: use modifier-independant keycode instead of keysym (#11095)
fix bug introduced by #11052



https://github.com/zed-industries/zed/pull/11052#issuecomment-2080475491
the keysym for `a` pressed with or without shift is different, so
keyrepeat was never ended for `Shift-A`.

Release Notes:

- N/A
2024-04-29 09:33:53 -07:00
apricotbucket28
2614215090 wayland: Fix crash on wayfire (#11081)
It is a protocol violation to attach a buffer before acknowledging in
xdg_surface::configure.

Release Notes:

- N/A
2024-04-29 09:33:11 -07:00
Jakob Hellermann
2386ae9f0e Set appid/wmclass for zed window (#10909)
fixes https://github.com/zed-industries/zed/issues/9132

By setting the app id, window managers like `sway` can apply custom
configuration like `for_window [app_id="zed"] floating enable`.
Tested using `wlprop`/`hyprctl activewindow` for wayland, `xprop` for
x11.


Release Notes:

- Zed now sets the window app id / class, which can be used e.g. in
window managers like `sway`/`i3` to define custom rules
2024-04-29 09:27:25 -07:00
Conrad Irwin
5674ba2a49 Validate buffer_id of anchors in is_valid (#11170)
Release Notes:

- Fixes diagnostic panic better (follow up from #11066)
2024-04-29 10:24:04 -06:00
William Viktorsson
95118c6568 Restore previous workspace on application resume (#10813)
Addresses #10812 

Release Notes:

- Launching an empty already-running Zed application now behaves like a
regular startup and respects the user `resume_on_startup` setting.
([#10812](https://github.com/zed-industries/zed/issues/10812)).

See attached showcase which highlights how the previous project can now
be re-opened through both "quit" and "close window".

This has a noticeable performance benefit on startup/project resume
time.

This should also make the behaviour of closing/opening an application
consistent between macOS/Linux/Windows.


https://github.com/zed-industries/zed/assets/22855292/9c37ba31-ce0a-4c3d-940d-a56e3347e64a
2024-04-29 09:56:23 -06:00
jansol
35c3af7fd0 gpui/mac: Disable shadows on non-opaque windows (#10896)
The culprit behind ghost images in transparent windows and bad
performance with blurred windows turns out to be one and the same:
window shadows. The simplest and most popular fix appears to be to
simply disable shadows on non-opaque windows so let's just do that.

Disabling shadows on a window that is already visible however leaves the
shadow on screen, detached from the window, until a full screen effect
such as exposé or a virtual desktop switch wipes it clean. There does
not seem to be any known solution to this, and it does not affect
windows created after switching to a transparent theme so this is a good
enough compromise for now.



Release Notes:

- Fixed ghostly artifacts in transparent window backgrounds.
- Fixed sluggishness with blurred window backgrounds.
2024-04-29 11:04:11 -04:00
Thorsten Ball
5ef75919f0 git: Do not log error if repository has no commits (#11163)
This is a follow-up to #10685 which started to hide these errors instead
of displaying them to the user.

But the errors are still noisy and not actionable, so we hide them
instead.

Release Notes:

- Removed error message being logged when `git blame` is run in a
repository without commits.

Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-29 16:00:29 +02:00
Bennet Bo Fenner
9746f4f267 Fix relative line numbers (#11161)
Closes #11105

Release Notes:

- Fixed rendering of relative line numbers in editor
([#11105](https://github.com/zed-industries/zed/issues/11105)).

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-04-29 15:54:40 +02:00
Thorsten Ball
9693e394f7 Remove unused fields (#11158)
Release Notes:


- N/A
2024-04-29 15:34:34 +02:00
Bennet Bo Fenner
45d217f6e0 Set destructive action only when prompt level is set to Destructive (#11154)
Oversight from #11015, where we added `PromptLevel::Destructive`, which
should be used when a prompt performs a "destructive" action (e.g.
deleting a file). However, we accidentally set `setHasDestructiveAction`
to `true` regardless of which prompt level would be specified

Release Notes:

- N/A
2024-04-29 12:12:11 +02:00
William Villeneuve
ca187c8386 Allow users to configure ESLint's rulesCustomizations settings (#11135)
https://github.com/zed-industries/zed/assets/2072378/18f0bb28-0546-4234-a11f-39af6c9fcc12

`rulesCustomizations` is an array of rule severity overrides. Globs can
be used to apply default severities for multiple rules. See
[docs](553e632fb4/README.md (L312-L333))
& [type
definitions](553e632fb4/%24shared/settings.ts (L168))

Example Zed `settings.json` to override all eslint errors to warnings:
```jsonc
{
    "lsp": {
        "eslint": {
            "settings": {
                "rulesCustomizations": [
                    // set all eslint errors/warnings to show as warnings
                    { "rule": "*", "severity": "warn" }
                ]
            }
        }
    }
}
```


Release Notes:

- Added support for configuring ESLint's `rulesCustomizations` settings,
ie. `{"lsp": {"eslint": {"settings": {"rulesCustomizations": [{"rule":
"*", "severity": "warn"}]}}}}`
2024-04-29 11:41:48 +03:00
d1y
f72cf2afe3 Highlight reference types directive in TypeScript (#11039)
Before:

<img width="443" alt="image"
src="https://github.com/zed-industries/zed/assets/45585937/a8037edb-ed5b-4b12-92b4-2d409442ad4b">


After:

<img width="429" alt="image"
src="https://github.com/zed-industries/zed/assets/45585937/1fd66523-d667-4041-952f-c1125e7feab9">


Release Notes:

- Add typescript reference types directive highlighted(#11001)
2024-04-29 11:38:53 +03:00
Thorsten Ball
8a79535b84 vim: Document jk (#11150)
Release Notes:


- N/A
2024-04-29 07:27:35 +02:00
Joseph T. Lyons
d4ec68b9ab Add hpp file icon (#11149)
Release Notes:

- Added `.hpp` file icon.
2024-04-29 01:08:59 -04:00
CharlesChen0823
c8a496ec4b Windows: Fix direct_write crash when clicking an empty line (#11117)
Windows platform direct_write using `LineLayout::default` indicates
`font_size` will be 0. Zed crashed when click empty line.

Release Notes:
- N/A
2024-04-28 21:37:45 -07:00
Michael Angerman
c826ad2f82 gpui: Improve the image example (#11111)
### fix cropping problem

Prior to these changes the images were being cropped so you never
actually saw
the full image but you had to use your mouse to make the window bigger
to see
both the text and the images...

### activate
 
```rust
cx.activate(true);
```
was not in place so the window did not appear when you ran the example

### No longer need to Ctrl-c to quit the example

Now you can hit *Cmd-q* to quit out of the example instead of having to
*Ctrl-c* in your
terminal where you fired off the example

Release Notes:

- N/A
2024-04-28 20:59:21 -07:00
Mikayla Maki
f458f90673 Fix bugs from recent PRs (#11147)
Fix missed delete in project panel
Fix blinking scrollbar from selections-in-scrollbar change

Release Notes:

- N/A (nightly only)
2024-04-28 20:56:17 -07:00
Nathan Sobo
39fb1d567d Incorporate ElementId as part of the Element::id trait method and expose GlobalId (#11101)
We're planning to associate "selection sources" with global element ids
to allow arbitrary UI text to be selected in GPUI. Previously, global
ids were not exposed outside the framework and we entangled management
of the element id stack with element state access. This was more
acceptable when element state was the only place we used global element
ids, but now that we're planning to use them more places, it makes sense
to deal with element identity as a first-class part of the element
system. We now ensure that the stack of element ids which forms the
current global element id is correctly managed in every phase of element
layout and paint and make the global id available to each element
method. In a subsequent PR, we'll use the global element id as part of
implementing arbitrary selection for UI text.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2024-04-28 13:59:21 -06:00
Marshall Bowers
8b55494351 Properly register new icons (#11132)
This PR fixes some issues with the icons added in #11116, where they
weren't being registered properly.

Release Notes:

- N/A
2024-04-28 10:43:10 -04:00
Derek Briggs
32e6424543 Icons for js, c, cc, h, jsx, and tsx file types (#11116)
Release Notes:
Add Icons for react (jsx/tsx), C, and C++ files

![CleanShot 2024-04-27 at 21 06
26@2x](https://github.com/zed-industries/zed/assets/1648941/3f23c058-e0c1-4aed-bfdd-0a1a9c8d7203)

Release Notes:

- Added icons for JS, React, C, and C++
2024-04-27 21:24:54 -06:00
Mikayla Maki
d2569afe66 Switch from delete file by default to trash file by default (#10875)
TODO:

- [x] Don't immediately seg fault
- [x] Implement for directories 
- [x] Add cmd-delete to remove files
- [ ] ~~Add setting for trash vs. delete~~ You can just use keybindings
to change the behavior.

fixes https://github.com/zed-industries/zed/issues/7228
fixes https://github.com/zed-industries/zed/issues/5094

Release Notes:

- Added a new `project_panel::Trash` action and changed the default
behavior for `backspace` and `delete` in the project panel to send a
file to the systems trash, instead of permanently deleting it
([#7228](https://github.com/zed-industries/zed/issues/7228),
[#5094](https://github.com/zed-industries/zed/issues/5094)). The
original behavior can be restored by adding the following section to
your keybindings:

```json5
[
// ...Other keybindings...
  {
    "context": "ProjectPanel",
    "bindings": {
        "backspace": "project_panel::Delete",
        "delete": "project_panel::Delete",
    }
  }
]
2024-04-26 17:43:50 -07:00
blufony
5dbd23f6b0 vim: add keybinding to jump to parent directory in project panel (#11078)
Release Notes:

- vim: Support `-` to go to `parent directory` of the project panel.

As mentioned in the comments of #11073 this adds support for netrw's `-`
to go to the parent directory in the project panel. Again tested on
linux only.


- N/A
2024-04-26 18:22:15 -06:00
Max Brunsfeld
b7d9aeb29d Semantic index progress (#11071)
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Kyle <kylek@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-26 17:06:05 -07:00
Andrew Lygin
1aa9c868d4 Scrollbar markers for cursors (#10816)
How it looks:

https://github.com/zed-industries/zed/assets/2101250/f564111c-1019-4442-b8a6-de338e12b12e

This PR adds cursor markers to the scrollbar. They work similar to
VSCode:

1. A cursor marker takes the whole scrollbar width.
2. It's always 2px high.
3. It uses the player's `cursor` color, so it may be helpful in the
collaboration mode.

There's a setting to switch cursor markers on/off:

```json
{
  "scrollbar": {
    "cursors": true
  }
}
```

Implementation details:

- Unlike other markers, cursor markers are displayed synchronously.
Otherwise they don't feel smooth and sometimes freez on prolonged
up/down navigation.
- Cursor markers are automatically switched off when it's more than 100
of them.
- The minimum (non-cursor) marker height is now 5px. It allows the user
to see other markers under the cursor marker.
- The way the minimum height is imposed on markers has changed a bit to
keep consistency between markers of different types.
- Selected symbol markers use less vibrant color (`info` faded out a
little).

Release Notes:

- Added displaying of cursor markers in the scrollbar. They can be
switched on/off by the `scrollbar.cursors` setting.
2024-04-27 02:26:42 +03:00
Conrad Irwin
848bb97ba7 release notes vN (#11077)
Make it more likely the draft release notes work...

Release Notes:

- N/A
2024-04-26 17:18:17 -06:00
Max Brunsfeld
8e925bf58f Don't panic when a tree-sitter parse fails (#11076)
Fixes
https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1714162894982749

Release Notes:
* Fixed a crash that could happen if an error occurred in a parser
provided by an extension.

Co-authored-by: Conrad <conrad@zed.dev>
2024-04-26 16:59:35 -06:00
blufony
adcaa211ec Add keybindings to jump to first / last file in project panel (#11073)
Release Notes:
- vim: Support `g g`/`G` to go to top/bottom of the project panel.

In vim type environments i usually expect to be able to jump to the top
and bottom and i was confused as to why that wasn't possible in the
project panel. So i added it. If anyone using different keymaps also
thinks this might be useful i would be happy to add other defaults. I
think for vim mode it is the most useful though, because you tend to not
use your mouse in vim mode.

This is my first contribution to any rust project, but it seemed like a
good starting point.
The function select_last() is inspired by select_first.
I was also thinking about adding a bigger jump keybinding, that would
jump for example 5 entries up / down, with vim default keybindings "{" /
"}".
FYI: I tested this on linux only and don't have access to any macos
machines.


- N/A
2024-04-26 16:35:57 -06:00
Jakob Hellermann
393b16d226 Fix Wayland keyrepeat getting cancelled by unrelated keyup (#11052)
fixes #11048

## Problem
in the situation `press right`, `press left`, `release right` the
following happens right now:

- `keypressed right`, `current_keysym` is set to `right`
- `keypressed left`, `current_keysym` is set to `left`

the repeat timer runs asynchronously and emits keyrepeats since
`current_keysym.is_some()`

- `keyreleased right`, `current_keysym` is set to None

the repeat timer no longer emits keyrepeats

- `keyreleased left`, this is where `current_keysym` should actually be
set to None.

## Solution
Only reset `current_keysym` if the released key matches the last pressed
key.

Release Notes:

- N/A
2024-04-26 14:07:05 -07:00
Akilan Elango
7bd18fa653 Sync maximized state from top-level configure event for a wayland window (#11003)
* Otherwise is_maximized always returns `true`



Release Notes:

- Fixed maximized state. Tested with a dummy maximize/restore button
with the `zoom()` (not implemented yet). Without the right `maximized`,
in toggle zoom function is not possible to call `set_maximized()` or
`unset_maximized()`.

```rust
    fn zoom(&self) {
      if self.is_maximized() {
        self.borrow_mut().toplevel.unset_maximized();
      } else {
        self.borrow_mut().toplevel.set_maximized();
      }
    }
```
2024-04-26 14:03:19 -07:00
张小白
11dc3c2582 windows: Support all OpenType font features (#10756)
Release Notes:

- Added support for all `OpenType` font features to DirectWrite.



https://github.com/zed-industries/zed/assets/14981363/cb2848cd-9178-4d87-881a-54dc646b2b61

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-04-26 13:58:12 -07:00
张小白
268cb948a7 windows: Move manifest file to gpui (#11036)
This is a follow up of #10810 , `embed-resource` crate uses a different
method to link the manifest file, so this makes moving manifest file to
`gpui` possible.

Now, examples can run as expected:
![Screenshot 2024-04-26
111559](https://github.com/zed-industries/zed/assets/14981363/bb040690-8129-490b-83b3-0a7d3cbd4953)

TODO:
- [ ] check if it builds with gnu toolchain

Release Notes:

- N/A
2024-04-26 13:56:48 -07:00
张小白
6a915e349c windows: Fix panicking on startup (#11028)
### Connection:
Closes #10954 

Release Notes:

- N/A
2024-04-26 13:55:41 -07:00
apricotbucket28
70d03e4841 x11: Fix window close (#11008)
Fixes https://github.com/zed-industries/zed/issues/10483 on X11

Also calls the `should_close` callback before closing the window (needed
for the "Do you want to save?" dialog).

Release Notes:

- N/A
2024-04-26 13:53:49 -07:00
DissolveDZ
b1eb0291dc Re-add README.md which might have been deleted by mistake (#11067)
Release Notes:

- N/A
2024-04-26 16:04:25 -04:00
Conrad Irwin
e0644de90e Fix panic in Diagnostics (#11066)
cc @maxbrunsfeld

Release Notes:

- Fixed a panic in populating diagnostics
2024-04-26 14:04:18 -06:00
Bennet Bo Fenner
9329ef1d78 markdown preview: Break up list items into individual blocks (#10852)
Fixes a panic related to rendering checkboxes, see #10824.

Currently we are rendering a list into a single block, meaning the whole
block has to be rendered when it is visible on screen. This would lead
to performance problems when a single list block contained a lot of
items (especially if it contained checkboxes). This PR splits up list
items into separate blocks, meaning only the actual visible list items
on screen get rendered, instead of the whole list.
A nice side effect of the refactoring is, that you can actually click on
individual list items now:


https://github.com/zed-industries/zed/assets/53836821/5ef4200c-bd85-4e96-a8bf-e0c8b452f762

Release Notes:

- Improved rendering performance of list elements inside the markdown
preview

---------

Co-authored-by: Remco <djsmits12@gmail.com>
2024-04-26 21:34:45 +02:00
Conrad Irwin
664f779eb4 new path picker (#11015)
Still TODO:

* Disable the new save-as for local projects
* Wire up sending the new path to the remote server

Release Notes:

- Added the ability to "Save-as" in remote projects

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-26 13:25:25 -06:00
Bennet Bo Fenner
314b723292 remote projects: Allow reusing window (#11058)
Release Notes:

- Allow reusing the window when opening a remote project from the recent
projects picker
- Fixed an issue, which would not let you rejoin a remote project after
disconnecting from it for the first time

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Remco <djsmits12@gmail.com>
2024-04-26 21:04:34 +02:00
Tim Masliuchenko
1af1a9e8b3 Toggle tasks modal in task::Rerun, when no tasks have been scheduled (#11059)
Currently, when no tasks have been scheduled, the `task::Rerun` action
does nothing.
This PR adds a fallback, so when no tasks have been scheduled so far the
`task::Rerun` action toggles the tasks modal

https://github.com/zed-industries/zed/assets/471335/72f7a71e-cfa8-49db-a295-fb05b2e7c905

Release Notes:

- Improved the `task::Rerun` action to toggle the tasks modal when no
tasks have been scheduled so far
2024-04-26 17:56:34 +02:00
Jakob Hellermann
8006f69513 Fix Cargo.toml typo ref -> rev (#11047)
Release Notes:

- N/A
2024-04-26 08:31:04 -04:00
Piotr Osiewicz
bacc92333a tasks: Fix divider position in modal (#11049)
The divider between templates and recent runs is constant, regardless of
the currently used filter string; this can lead to situations where an
user can remove the predefined task, which isn't good at all.

Additionally, in this PR I've made it so that recent runs always show up
before task templates in filtered list.

Release Notes:

- Fixed position of list divider in task modal.
2024-04-26 14:29:16 +02:00
Conrad Irwin
eb7bd0b98a Use fewer fancy cursors even for vim users (#11041)
Release Notes:

- N/A
2024-04-26 09:42:21 +02:00
Conrad Irwin
7f229dc202 Remove unread notes indicator for now (#11035)
I'd like to add something back here, but it's more distracting than
helpful today.

Fixes: #10887

Release Notes:

- Removed channel notes unread indicator

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-25 23:50:31 -04:00
Conrad Irwin
03d0b68f0c Fix panic in rename selections (#11033)
cc @someonetoignore

Release Notes:

- Fixed a panic when renaming with a selection (preview only)
2024-04-25 21:29:56 -06:00
Hans
5c2f27a501 Fix VIM cw on last character of a word doesn't work as expected: (#10963)
At the moment, using the default expand_selection seems to do the job
well, without the need for some additional logic, which may also make
the code a little clearer, Fix #10945



Release Notes:


- N/A
2024-04-25 21:09:06 -06:00
Conrad Irwin
d9d509a2bb Send installation id with crashes (#11032)
This will let us prioritize crashes that affect many users.

Release Notes:

- N/A
2024-04-25 21:07:06 -06:00
Marshall Bowers
a4ad3bcc08 Hoist nanoid to workspace-level (#11029)
This PR hoists `nanoid` up to a workspace dependency.

Release Notes:

- N/A
2024-04-25 22:37:40 -04:00
Conrad Irwin
6d7332e80c Fix panic in vim search (#11022)
Release Notes:

- vim: Fixed a panic when searching
2024-04-25 20:32:15 -06:00
Marshall Bowers
1b614ef63b Add an Assistant example that can interact with the filesystem (#11027)
This PR adds a new Assistant example that is able to interact with the
filesystem using a tool.

Release Notes:

- N/A
2024-04-25 22:21:18 -04:00
Hendrik Sollich
604857ed2e vim: Increment search right (#10866)
Hi there, nice editor!
Here's my attempt at fixing #10865.

Thanks

Release Notes:

-vim: Fix ctrl+a when cursor is on a decimal point
([#10865](https://github.com/zed-industries/zed/issues/10865)).

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-25 19:47:52 -06:00
DissolveDZ
d9eb3c4b35 vim: Fix hollow cursor being offset when selecting text (#11000)
Fixed the cursor selection being offset, the hollow cursor was being
displayed fine when not having text selected that's why it might not
have been noticed at first.

Release Notes:
- N/A

Improved:
0d6fb08b67
2024-04-25 19:47:12 -06:00
Marshall Bowers
f8beda0704 Rename chat_with_functions to use snake_case (#11020)
This PR renames the `chat-with-functions.rs` example to use snake_case
for the filename, as is convention.

Release Notes:

- N/A
2024-04-25 21:43:02 -04:00
Max Brunsfeld
40fe5275cf Rework project diagnostics to prevent showing inconsistent state (#10922)
For a long time, we've had problems where diagnostics can end up showing
up inconsistently in different views. This PR is my attempt to prevent
that, and to simplify the system in the process. There are some UX
changes.

Diagnostic behaviors that have *not* changed:

* In-buffer diagnostics update immediately when LSPs send diagnostics
updates.
* The diagnostic counts in the status bar indicator also update
immediately.

Diagnostic behaviors that this PR changes:

* [x] The tab title for the project diagnostics view now simply shows
the same counts as the status bar indicator - the project's current
totals. Previously, this tab title showed something slightly different -
the numbers of diagnostics *currently shown* in the diagnostics view's
excerpts. But it was pretty confusing that you could sometimes see two
different diagnostic counts.
* [x] The project diagnostics view **never** updates its excerpts while
the user might be in the middle of typing it that view, unless the user
expressed an intent for the excerpts to update (by e.g. saving the
buffer). This was the behavior we originally implemented, but has
changed a few times since then, in attempts to fix other issues. I've
restored that invariant.

    Times when the excerpts will update:
     * diagnostics are updated while the diagnostics view is not focused
     * the user changes focus away from the diagnostics view
* the language server sends a `work done progress end` message for its
disk-based diagnostics token (i.e. cargo check finishes)
* the user saves a buffer associated with a language server, and then a
debounce timer expires

* [x] The project diagnostics view indicates when its diagnostics are
stale. States:
* when diagnostics have been updated while the diagnostics view was
focused:
        * the indicator shows a 'refresh' icon
        * clicking the indicator updates the excerpts
* when diagnostics have been updated, but a file has been saved, so that
the diagnostics will soon update, the indicator is disabled

With these UX changes, the only 'complex' part of the our diagnostics
presentation is the Project Diagnostics view's excerpt management,
because it needs to implement the deferred updates in order to avoid
disrupting the user while they may be typing. I want to take some steps
to reduce the potential for bugs in this view.

* [x] Reduce the amount of state that the view uses, and simplify its
implementation
* [x] Add a randomized test that checks the invariant that a mutated
diagnostics view matches a freshly computed diagnostics view


##  Release Notes

- Reworked the project diagnostics view:
- Fixed an issue where the project diagnostics view could update its
excerpts while you were typing in it.
- Fixed bugs where the project diagnostics view could show the wrong
excerpts.
- Changed the diagnostics view to always update its excerpts eagerly
when not focused.
- Added an indicator to the project diagnostics view's toolbar, showing
when diagnostics have been changed.

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2024-04-25 18:12:15 -07:00
Kyle Kelley
cf2272a949 Always submit function definitions in Simple mode too (#11016)
Switches Assistant2 to always provide functions. It's up to the model to
choose to use them. At a later point, the `Submit::Codebase` should
change the `tool_choice` to `query_codebase` rather than `auto`. For
now, I think this will improve the experience for folks testing.

Release Notes:

- N/A
2024-04-25 16:34:07 -07:00
Nate Butler
366d7e7728 Break typography styles out of StyledExt (#11013)
- Centralizes typography-related UI styles and methods in
`styles/typography.rs`
- Breaks the typography-related styles out of `StyledExt`. This means we
add a `StyledTypography` trait – this should more or less be an
invisible change as we publish it in the prelude.
- adds the ability to easily grab the UI or Buffer font sizes
(`ui_font_size`, `buffer_font_size`) with `TextSize::UI`,
`TextSize::Editor`

Release Notes:

- N/A
2024-04-25 17:42:53 -04:00
Michael Angerman
4c780568bc storybook: Fix Backspace in Auto Height Editor and Picker stories (#11011)
Currently in the *Auto Height Editor* story the backspace key is not
working when you type into the Editor.

The same thing is true for the *Picker* story as one is not able to
backspace...

By adding an entry in the keymap file

```rust
assets/keymaps/storybook.json
```

both of these issues are solved...

Release Notes:

- N/A
2024-04-25 17:37:47 -04:00
Marshall Bowers
7af96a15fe Fix typo in comment 2024-04-25 17:30:47 -04:00
Marshall Bowers
3eac581a62 Allow controlling Tailwind via the language_servers setting (#11012)
This PR adds the ability for the Tailwind language server
(`tailwindcss-language-server`) to be controlled by the
`language_servers` setting.

Now in your settings you can indicate that the Tailwind language server
should be used for a given language, even if that language does not have
the Tailwind language server registered for it already:

```json
{
  "languages": {
    "My Language": {
      "language_servers": ["tailwindcss-language-server", "..."]
    }
  }
}
```

Release Notes:

- N/A
2024-04-25 17:29:47 -04:00
Conrad Irwin
c833a7e662 Don't use fancy cursors for non-vim people (#11010)
Release Notes:

- N/A
2024-04-25 14:21:01 -06:00
Kyle Kelley
f176e8f0e4 Accept Views on LanguageModelTools (#10956)
Creates a `ToolView` trait to allow interactivity. This brings expanding
and collapsing to the excerpts from project index searches.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-04-25 13:03:43 -07:00
Marshall Bowers
7005f0b424 Remove outdated instructions for adding languages (#11005)
This PR removes the outdated comment regarding adding languages to Zed.

New languages should be added as extensions.

Release Notes:

- N/A
2024-04-25 14:30:31 -04:00
James Thurley
d3f6ca7a1e Add @operator, @lifetime and @punctuation.delimiters captures for Rust (#10885)
Adds additional captures for theming rust code.

I'm uncertain about whether `#` belongs in the `@operator` capture, but
I didn't see a more appropriate capture name in my brief hunt in other
files. It is the prefix of an `attribute_item`.. suggestions welcome.

Release Notes:

- Added `@operator`, `@lifetime` and `@punctuation.delimiter` captures
to Rust highlights file.
2024-04-25 14:20:20 -04:00
Marshall Bowers
544bd490ac Extract Elixir extension (#10948)
This PR extracts Elixir support into an extension and removes the
built-in Elixir support from Zed.

As part of this, [Lexical](https://github.com/lexical-lsp/lexical) has
been added as an available language server for Elixir.

Since the Elixir extension provides three different language servers,
you'll need to use the `language_servers` setting to select the one you
want to use:

#### Elixir LS

```json
{
  "languages": {
    "Elixir": {
      "language_servers": [ "elixir-ls", "!next-ls", "!lexical", "..."]
    }
  }
}
```

#### Next LS

```json
{
  "languages": {
    "Elixir": {
      "language_servers": [ "next-ls", "!elixir-ls", "!lexical", "..."]
    }
  }
}
```

#### Lexical

```json
{
  "languages": {
    "Elixir": {
      "language_servers": [ "lexical", "!elixir-ls", "!next-ls", "..."]
    }
  }
}
```

These can either go in your user settings or your project settings.

Release Notes:

- Removed built-in support for Elixir, in favor of making it available
as an extension.
2024-04-25 13:59:14 -04:00
Jason Lee
7065da2b98 Fix project-panel double click file support on Windows (#10917)
Release Notes:

- Fixed project panel double click to force open file on Windows.

Ref issue: #10898 

@bennetbo 

I saw you was also used `event.down.click_count` in Markdown Preview.


7dccbd8e3b (diff-c8d1735cb347ea08d03198df112343ec50a74de8d50414a6f3be6c6d674c6d19R161)

And this also used in other place:

<img width="870" alt="image"
src="https://github.com/zed-industries/zed/assets/5518/b844e700-b95f-4cd2-987f-9e4305ebdd7c">

## Test demo after updated

Looks like it is no side effect

![2024-04-24 10 17
45](https://github.com/zed-industries/zed/assets/5518/0df4cf06-7448-4014-9df2-f2608a5f5314)
2024-04-25 10:01:24 -07:00
Thorsten Ball
0d6fb08b67 vim: set cursor to hollow-block if editor loses focus (#10995)
This has been bugging me for a while now. Finally figured out how to do
it.

Release Notes:

- Fixed cursor in Vim mode not changing into a hollow block when editor
loses focus.


Demo: 


https://github.com/zed-industries/zed/assets/1185253/c7585282-156d-4ab2-b516-eb1940d6d0d3
2024-04-25 17:56:53 +02:00
Marshall Bowers
3ce4ff94ae Update Cargo.lock 2024-04-25 11:17:25 -04:00
Congyu
21022f1644 Fix cmd+click find all references fallback not working in Vim mode (#10684)
Exclude go-to-definition links returned by LSP that points to the
current cursor position. This fixes #10392 . Related PR #9243 .

The previous implementation first performs go-to-definition, and if the
selected text covers the clicked position, it figures out that it is
already clicking on a definition, and should instead look for
references.

However, the selected range does not necessarily cover the clicked
position after clicking on a definition, as in VIM mode.

After the PR, if cmd+click on definitions, the definition links would be
an empty list, so no go-to-definition is performed, and
find-all-references is performed instead.

Release Notes:

- Fixed #10392 , now `cmd+click`ing to find all references works in vim
mode.
2024-04-25 09:07:14 -06:00
Antonio Scandurra
11bcfea6d2 Fix single-line editors not working anymore (#10994)
This was introduced with #10979 and was caused by a missing call to
`cx.set_view_id` in `EditorElement`, which is necessary when rendering
`EditorElement` manually instead of via a view.

Release Notes:

- N/A
2024-04-25 17:04:20 +02:00
Jakob Hellermann
1cd34fdd9c Recognize PKGBUILD as bash script (#10946)
[PKGBUILD] is a file used in the build system of arch linux, and it is
basically just a bash script with special functions.


Release Notes:

- Changed `PKGBUILD` files to be recognized as bash.
2024-04-25 09:44:52 -04:00
Antonio Scandurra
530224527d Allow pressing escape to cancel the current assistant generation (#10987)
If the assistant has already emitted some text, we will leave the
assistant message but maintain the cursor on the previous user message,
so that the user can easily discard the message by submitting again.

If no output was emitted yet, we simply delete the empty assistant
message.

Release Notes:

- N/A
2024-04-25 15:12:58 +02:00
Marshall Bowers
0de2636324 Revert "Changed cmd+w with no open tabs to close window (#10740)" (#10986)
This PR reverts #10740, as it makes it too easy to close Zed
accidentally.

Quitting Zed when you don't mean to is disruptive and can break your
flow. This is even more the case when you're collaborating.

Therefore, we shouldn't make it easy to quit Zed when you don't mean to.

If we want to bring back this behavior it needs to have a corresponding
setting that should, in my opinion, be **off** by default. Additionally,
someone made the good point that this behavior should not be present on
Linux or Windows.

This reverts commit 5102e37a5b.

Release Notes:

- Changed `cmd-w` with no open tabs to no longer close the window
(preview-only).
2024-04-25 09:10:02 -04:00
Thorsten Ball
7ec963664e git blame: Do not try to blame buffer if it has no file (#10985)
Release Notes:

- Fixed error messages being logged due to inline git blame not working
on an empty buffer that hasn't been saved yet.
2024-04-25 15:02:19 +02:00
Thorsten Ball
019821d62c eslint: register as language server for Vue.js (#10983)
This fixes #9934 and does two things:

1. It registers ESLint as a secondary language server for Vue.js files
(`.vue`)
2. It registers ESLint as a _secondary_ (instead of primary) language
server for TypeScript, JavaScript and TSX.

The second point because I don't see any reason why ESLint should be
registered as a primary language server for these languages. I read
through the code in `project.rs` that uses the primary language server
and I don't think there will be any differences to how it previously
worked.

I also manually tested ESLint support in a Vue.js project, a Next.js
project and a plain old JS project — still works in all three.

Release Notes:

- Added ESLint support for Vue.js files by starting it as a language
server on `.vue` files.
([#9934](https://github.com/zed-industries/zed/issues/9934)).
2024-04-25 14:49:07 +02:00
Thorsten Ball
bb213b6e37 Fix keybinding errors on Linux (#10982)
These showed up as error messages. One of them has been removed and the
other two have changed names.



Release Notes:

- N/A
2024-04-25 13:44:24 +02:00
Antonio Scandurra
6a7761e620 Merge ElementContext into WindowContext (#10979)
The new `ElementContext` was originally introduced to ensure the element
APIs could only be used inside of elements. Unfortunately, there were
many places where some of those APIs needed to be used, so
`WindowContext::with_element_context` was introduced, which defeated the
original safety purposes of having a specific context for elements.

This pull request merges `ElementContext` into `WindowContext` and adds
(debug) runtime checks to APIs that can only be used during certain
phases of element drawing.

Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
2024-04-25 12:54:39 +02:00
Thorsten Ball
031580f4dc git: Fix inline blame moving on horizontal scroll (#10974)
This fixes the behaviour reported by @mikayla-maki.

## Before


https://github.com/zed-industries/zed/assets/1185253/35aa4e6d-295b-4050-b5cc-cab0f91b27e1


## After


https://github.com/zed-industries/zed/assets/1185253/a17cbc9c-fc2c-43d6-918b-1205b327507b

## Release notes

Release Notes:

- Fixed inline git blame information moving when horizontally scrolling.
2024-04-25 11:29:29 +02:00
Hans
1a27016123 Improve logic for obtaining surrounds range in Vim mode (#10938)
now correctly retrieves range in cases where escape characters are
present. Fixed #10827


Release Notes:

- vim: Fix logic for finding surrounding quotes to ignore escaped
characters (#10827)
2024-04-24 21:19:15 -06:00
Conrad Irwin
d1425603f6 Fix misalignment of vim mode indicator (#10962)
Credit-to: @elkowar

New is the top
<img width="220" alt="Screenshot 2024-04-24 at 19 00 48"
src="https://github.com/zed-industries/zed/assets/94272/9d917bf1-e175-494d-9653-757d15584921">

Release Notes:

- N/A
2024-04-24 19:17:10 -06:00
Marshall Bowers
583a662ddc Fix issues with drafting release notes in CI (#10955)
This PR addresses some issues I ran into with the way we draft release
notes in CI when doing builds.

The first issue I encountered was that `script/draft-release-notes` was
failing, seemingly due to CI doing a shallow Git checkout and not having
all of the tags available in order to compare then. This was addressed
by setting the `fetch-depth` during the Git checkout.

The second issue is that (in my opinion) we shouldn't fail the build if
drafting release notes fails. After well, we're doing it as a
convenience to ourselves, and it isn't a mandatory part of the build.
This was addressed by making any failures in
`script/draft-release-notes` not fail the CI step as a whole.

These changes were already applied to the `v0.133.x` branch.

Release Notes:

- N/A
2024-04-24 18:56:36 -04:00
Conrad Irwin
64617a0ede Read settings in headless mode (#10950)
Release Notes:

- N/A
2024-04-24 16:06:36 -06:00
Marshall Bowers
b673494f4d Restore the previous styles for single-line editors (#10951)
This PR fixes a bug introduced in #10870 that caused editors used as
single-line inputs to have the wrong text style.

If this change was intentional for something relating to the new
Assistant panel, we'll need to figure out a way to change it without
breaking these other usages.

### Before

<img width="589" alt="Screenshot 2024-04-24 at 5 35 36 PM"
src="https://github.com/zed-industries/zed/assets/1486634/31624cfd-75d1-4771-9402-c14ef9e9483e">

<img width="326" alt="Screenshot 2024-04-24 at 5 35 46 PM"
src="https://github.com/zed-industries/zed/assets/1486634/1b76a3ef-7205-49ee-b391-7609f90461bd">

### After

<img width="588" alt="Screenshot 2024-04-24 at 5 36 14 PM"
src="https://github.com/zed-industries/zed/assets/1486634/9d550ee2-80c0-4afb-9b45-a2956471c546">

<img width="260" alt="Screenshot 2024-04-24 at 5 36 31 PM"
src="https://github.com/zed-industries/zed/assets/1486634/63240f27-1679-45d5-b39c-016860ff9683">

Release Notes:

- Fixed a bug where some inputs were using the wrong font style
(preview-only).
2024-04-24 17:47:25 -04:00
Dzmitry Malyshau
53f67a8241 Update blade with transparency and exclusive fullscreen fixes (#10880)
Release Notes:

- N/A

Picks up https://github.com/kvark/blade/pull/113 and a bunch of other
fixes.
Should prevent the exclusive full-screen on Vulkan - related to #9728
cc @kazatsuyu 

Note: this PR doesn't enable transparency, this is left to follow-up
2024-04-24 13:02:11 -07:00
张小白
06d2d9da5f windows: Let the OS decide which font to use as the UI font (#10877)
On my computer, I get `Yahei UI`, which makes sense since I'm using a
Chinese operating system, and `Yahei UI` includes Chinese codepoints. On
an English operating system, `Segoe UI` should be used instead.

Edit: I also choose to use the UI font selected by the system as the
fallback font, rather than hard-coding the `Arial` font.

Release Notes:

- N/A
2024-04-24 13:00:25 -07:00
Maksim Bondarenkov
9e88155a48 Use winresource instead of embed-manifest (#10810)
use winresource for crates/zed and crates/storybook. tested on
`x86_64-pc-windows-gnu`. on `x86_64-pc-windows-msvc` I receive a error
message, that looks like a problem with my machine
 
Release Notes:

- N/A
2024-04-24 12:59:18 -07:00
Conrad Irwin
048fc7ad09 Allow cli to accept --dev-server-token (#10944)
Release Notes:

- N/A
2024-04-24 13:15:19 -06:00
Marshall Bowers
bd77232f65 dart: Bump to v0.0.2 (#10940)
This PR bumps the Dart extension to v0.0.2.

Changes:

- https://github.com/zed-industries/zed/pull/8347
- https://github.com/zed-industries/zed/pull/10552

Release Notes:

- N/A
2024-04-24 13:03:56 -04:00
Joseph T. Lyons
facd04c902 v0.134.x dev 2024-04-24 12:46:30 -04:00
Marshall Bowers
d8437136c7 Fix primary language server selection for formatting (#10939)
This PR fixes the way we select the primary language server for use with
formatting.

Previously we were just taking the first one in the list, but this could
be the wrong one in cases where a language server was provided by an
extension in conjunction with a built-in language server (e.g.,
Tailwind).

We now use the `primary_language_server_for_buffer` method to more
accurately identify the primary one.

Fixes https://github.com/zed-industries/zed/issues/10902.

Release Notes:

- Fixed an issue where the wrong language server could be used for
formatting.
2024-04-24 12:42:33 -04:00
Marshall Bowers
d0a5dbd8cb terraform: Sync Cargo.toml version with extension.toml version (#10937)
This PR syncs the `Cargo.toml` version with the `extension.toml`
version.

We should try to keep these in sync.

Release Notes:

- N/A
2024-04-24 11:42:18 -04:00
Marshall Bowers
76ff467965 Log which language servers will be started (#10936)
This PR adds a new log message indicating which language servers will be
started for a given language.

The aim is to make debugging the usage of the new `language_servers`
setting (#10911) easier.

Release Notes:

- N/A
2024-04-24 11:22:42 -04:00
Antonio Scandurra
e1791b7dd0 Autoscroll containing element when editor has a pending selection (#10931)
Release Notes:

- N/A
2024-04-24 14:33:57 +02:00
Antonio Scandurra
25e239d986 Fix autoscroll in the new assistant (#10928)
This removes the manual calls to `scroll_to_reveal_item` in the new
assistant, as they are superseded by the new autoscrolling behavior of
the `List` when the editor requests one.

Release Notes:

- N/A
2024-04-24 14:12:44 +02:00
Thorsten Ball
f7ea1370a4 Update docstring for SumTree (#10927)
Need the updated docstring for the blog post.
Release Notes:

- N/A
2024-04-24 14:01:10 +02:00
Kirill Bulatov
6108140a02 Properly extract package name out of cargo pkgid (#10929)
Fixes https://github.com/zed-industries/zed/issues/10925

Uses correct package name to generate Rust `cargo` tasks.
Also deduplicates lines in task modal item tooltips.

Release Notes:

- Fixed Rust tasks using incorrect package name
([10925](https://github.com/zed-industries/zed/issues/10925))
2024-04-24 14:12:59 +03:00
Hans
135a5f2114 Enable unfocused windows to update their status based on whether they are clickable or not (#10229)
- Fixed #9784 By removing the interception of the MouseMove event, zed
can update the corresponding Hover even when it is inactive
2024-04-24 10:44:00 +02:00
hardlydearly
dfd4d2a437 chore: remove repetitive word (#10923) 2024-04-24 11:34:32 +03:00
Antonio Scandurra
fbc6e930a7 Fix regressions in List (#10924)
Release Notes:

- N/A
2024-04-24 10:18:52 +02:00
Kyle Kelley
af5a9fabc6 Include root schema as parameters for tool calling (#10914)
Allows `LanguageModelTool`s to include nested structures, by exposing
the definitions section of their JSON Schema.

Release Notes:

- N/A
2024-04-23 20:49:29 -07:00
Marshall Bowers
25981550d5 Extract Deno extension (#10912)
This PR extracts Deno support into an extension and removes the built-in
Deno support from Zed.

When using the Deno extension, you'll want to add the following to your
settings to disable the built-in TypeScript and ESLint language servers
so that they don't conflict with Deno's functionality:

```json
{
  "languages": {
    "TypeScript": {
      "language_servers": ["deno", "!typescript-language-server", "!eslint", "..."]
    },
    "TSX": {
      "language_servers": ["deno", "!typescript-language-server", "!eslint", "..."]
    }
  }
}

```

Release Notes:

- Removed built-in support for Deno, in favor of making it available as
an extension.
2024-04-23 20:44:11 -04:00
Marshall Bowers
cf67fc9055 Add language_servers setting for customizing which language servers run (#10911)
This PR adds a new `language_servers` setting underneath the language
settings.


This setting controls which of the available language servers for a
given language will run.

The `language_servers` setting is an array of strings. Each item in the
array must be either:

- A language server ID (e.g., `"rust-analyzer"`,
`"typescript-language-server"`, `"eslint"`, etc.) denoting a language
server that should be enabled.
- A language server ID prefixed with a `!` (e.g., `"!rust-analyzer"`,
`"!typescript-language-server"`, `"!eslint"`, etc.) denoting a language
server that should be disabled.
- A `"..."` placeholder, which will be replaced by the remaining
available language servers that haven't already been mentioned in the
array.

For example, to enable the Biome language server in place of the default
TypeScript language server, you would add the following to your
settings:

```json
{
  "languages": {
    "TypeScript": {
      "language_servers": ["biome", "!typescript-language-server", "..."]
    }
  }
}
```

More details can be found in #10906.

Release Notes:

- Added `language_servers` setting to language settings for customizing
which language server(s) run for a given language.
2024-04-23 19:38:57 -04:00
Kyle Kelley
68a1ad89bb New revision of the Assistant Panel (#10870)
This is a crate only addition of a new version of the AssistantPanel.
We'll be putting this behind a feature flag while we iron out the new
experience.

Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Antonio Scandurra <antonio@zed.dev>
Co-authored-by: Nate Butler <nate@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Max <max@zed.dev>
2024-04-23 16:23:26 -07:00
Conrad Irwin
e0c83a1d32 remote projects per user (#10594)
Release Notes:

- Made remote projects per-user instead of per-channel. If you'd like to
be part of the remote development alpha, please email hi@zed.dev.

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
Co-authored-by: Bennet <bennetbo@gmx.de>
Co-authored-by: Nate Butler <1714999+iamnbutler@users.noreply.github.com>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
2024-04-23 15:33:09 -06:00
Michael Angerman
8ae4c3277f storybook: Fix crash in Kitchen Sink and Auto Height Editor stories (#10904)
The *Kitchen Sink* as well as the *Auto Height Editor* story is crashing
for the same reason that the Picker story was crashing...

### Related Topics

- Picker Story PR : #10793 
- Picker Story Issue : #10739 
- Introduced By : #10620 

Release Notes:

- N/A
2024-04-23 16:45:12 -04:00
Joseph T. Lyons
f6eaa8b00f Clean up whitespace (#10755)
I saved the `file_types.json` file and got a diff because it had some
trailing whitespace. I ran
[`lineman`](https://github.com/JosephTLyons/lineman) on the codebase.
I've done this before, but this time, I've added in the following
settings to our `.zed` local settings, to make sure every future save
respects our desire to have consistent whitespace formatting.

```json
"remove_trailing_whitespace_on_save": true,
"ensure_final_newline_on_save": true
```

Release Notes:

- N/A
2024-04-23 13:31:21 -04:00
Thorsten Ball
85b26e9788 Store goldenfiles with trailing newline (#10900)
Release Notes:


- N/A
2024-04-23 19:13:04 +02:00
Piotr Osiewicz
2ee257a562 task_ui: Move status indicator into tab bar of terminal panel (#10846)
I'm not a huge fan of this change (& I expect the placement to change).
The plan is to have the button in a toolbar of terminal panel, but I'm
not sure if occupying a whole line of vertical space for a single button
is worth it; I suppose we might want to put more of tasks ui inside of
that toolbar.
Release Notes:

- Removed task status indicator and added "Spawn task" action to
terminal panel context menu.
2024-04-23 16:27:18 +02:00
Antonio Scandurra
bcbf2f2fd3 Introduce autoscroll support for elements (#10889)
This pull request introduces the new
`ElementContext::request_autoscroll(bounds)` and
`ElementContext::take_autoscroll()` methods in GPUI. These new APIs
enable container elements such as `List` to change their scroll position
if one of their children requested an autoscroll. We plan to use this in
the revamped assistant.

As a drive-by, we also:

- Renamed `Element::before_layout` to `Element::request_layout`
- Renamed `Element::after_layout` to `Element::prepaint`
- Introduced a new `List::splice_focusable` method to splice focusable
elements into the list, which enables rendering offscreen elements that
are focused.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-04-23 15:14:22 +02:00
Kyle Kelley
efcd31c254 Update documentation and handling to use a crates/collab/seed.json (#10874)
Updates `collab` to accept a `seed.json` file that allows you to
override the defaults. Updated the `README` in collab to just have
directions inside instead of redirecting the developer to the website.

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-04-23 05:41:31 -07:00
apricotbucket28
ae3c641bbe wayland: File drag and drop (#10817)
Implements file drag and drop on Wayland


https://github.com/zed-industries/zed/assets/71973804/febcfbfe-3a23-4593-8dd3-e85254e58eb5


Release Notes:

- N/A
2024-04-22 16:20:24 -07:00
Marshall Bowers
029eb67043 Add SettingsSources::<T>::json_merge_with function (#10869)
This PR adds a `json_merge_with` function to `SettingsSources::<T>` to
allow JSON merging settings from custom sources.

This should help avoid repeating the actual merging logic when all that
needs to be customized is which sources are being respected.

Release Notes:

- N/A
2024-04-22 18:38:34 -04:00
Marshall Bowers
63c529552c Automatically install the HTML extension (#10867)
This PR makes it so the HTML extension will be installed in Zed by
default.

We feel we should keep HTML available out-of-the-box, but we want to do
so while still keeping it as an extension (as opposed to built-in to Zed
natively). There may be a world where we bundle the extension in with
the Zed binary itself, but installing it on startup gets us 99% of the
way there.

The approach for making HTML available by default is quite general, and
could be applied to any extension that we choose (likely other languages
that we want to come out-of-the-box, but that could then be moved to
extensions).

If you do not want the HTML extension in Zed, you can disable the
auto-installation in your `settings.json` and then uninstall the
extension:

```json
{
  "auto_install_extensions": {
    "html": false
  }
}
```

Release Notes:

- Added auto-installation for the HTML extension on startup.
- This can be disabled by adding `{ "auto_install_extensions": { "html":
false } }` to your settings.
2024-04-22 18:02:22 -04:00
Ben Hamment
c96a96b3ce Add traits in Rust highlights (#10731)
Question: I use type.super here because I made a similar change to the
ruby syntax to apply the same style to superclasses.
With this in mind, should this change be renamed to type.trait or should
it be renamed to something like type.italic so the ruby syntax or any
other language can all use type.italic? or maybe something else
altogether.

<img width="597" alt="image"
src="https://github.com/zed-industries/zed/assets/7274458/9d02dba0-75a4-4439-9f31-fd8aa0873075">

Release Notes:

- Exposed Rust traits as `type.interface` for individual syntax theming.
2024-04-22 16:43:48 -04:00
ElKowar
7f81bfb6b7 Make keymaps reusable across platforms (#10811)
This PR includes two relevant changes:
- Platform binds (super, windows, cmd) will now parse on all platforms,
regardless of which one is being used. While very counter-intuitive
(this means that `cmd-d` will actually be triggered by `win-d` on
windows) this makes it possible to reuse keymap files across platforms
easily
- There is now a KeyContext `os == linux`, `os == macos` or `os ==
windows` available in keymaps. This allows users to specify certain
blocks of keybinds only for one OS, allowing you to minimize the amount
of keymappings that you have to re-configure for each platform.

Release Notes:

- Added `os` KeyContext, set to either `linux`, `macos` or `windows`
- Fixed keymap parsing errors when `cmd` was used on linux, `super` was
used on mac, etc.
2024-04-22 13:24:25 -07:00
Marshall Bowers
33baa377c7 docs: Add note about manually using the bump_patch_version action (#10862)
This PR updates the releases docs to make a note about
`bump_patch_version` action through the GitHub UI.

Not all of us have `gh` (or `brew`) installed.

Release Notes:

- N/A
2024-04-22 15:58:25 -04:00
Marshall Bowers
07f490f9e9 Ensure target directory exists before drafting release notes 2024-04-22 15:56:54 -04:00
ElKowar
b29643168c XDG_BASE_DIR support (linux, windows) (#10808)
This PR adds XDG_BASE_DIR support on linux, and cleans up the path
declarations slightly. Additionally, we move the embeddings and
conversations directly to the SUPPORT_DIR on those platforms.
I _think_ that should also be done on MacOS in the future, but that has
been left out here for now to not break existing users setups.

Additionally, we move the SUPPORT_DIR into LocalAppData on windows for
consistency.

Release Notes:

- Fixed missing support of `XDG_BASE_DIR` on linux
- Fixed improper placement of data in XDG_CONFIG_HOME on linux and
windows (https://github.com/zed-industries/zed/issues/9308,
https://github.com/zed-industries/zed/issues/7155)

---------

Co-authored-by: phisch <philipp.schaffrath@gmail.com>
2024-04-22 12:42:18 -07:00
Max Linke
e9a965fe81 Add missing linux dependencies (#10814)
At least one of the dependencies requires cmake to configure the build
process.

On ubuntu libgit2-dev was missing. Debian and derivates do not install
development headers by default.



Release Notes:

- Improved Linux development setup scripts.

Co-authored-by: Max Linke <max.linke88@gmail.com>
2024-04-22 12:05:12 -07:00
Marshall Bowers
b964fe2ccf Fix reading workspace-level LSP settings in extensions (#10859)
This PR fixes an issue where workspace-level LSP settings could be not
read using `LspSettings::for_worktree` in extensions.

We we erroneously always reading the global settings instead of
respecting the passed-in location.

Release Notes:

- Fixed a bug where workspace LSP settings could not be read by
extensions.
2024-04-22 14:40:23 -04:00
Piotr Osiewicz
1be452744a cli: Use leading dashes in channel designators (#10858)
/cc @maxbrunsfeld 

Release Notes:

- N/A
2024-04-22 20:26:49 +02:00
Nate Butler
d298df823f Minor script fix (#10857)
Fixes a minor error in the analyze highlight script.

Release Notes:

- N/A
2024-04-22 13:05:41 -04:00
Piotr Osiewicz
a111b959d2 cli: Treat first argument as name of release channel to use for the cli (#10856)
With this commit, it is now possible to invoke cli with a release
channel of bundle as an argument. E.g: `zed stable some_arguments` will
find CLI binary of Stable channel installed on your machine and invoke
it with `some_arguments` (so the first argument is essentially omitted).

Fixes #10851

Release Notes:

- CLI now accepts an optional name of release channel as it's first
argument. For example, `zed stable` will always use your Stable
installation's CLI. Trailing args are passed along.
2024-04-22 18:01:06 +02:00
Nate Butler
189cece03e Add analyze highlights script (#10855)
Adds a script to print all unique highlight keys for building syntax
themes.

Usage:

- `python script/analyze_highlights.py` OR
- `python script/analyze_highlights.py -v`
- Using the `-v` or `--verbose` arg will print each language that uses
each key.

Example output:

```
@attribute (6)
@boolean (5)
@charset (1)
@comment (19)
@comment.doc (3)
@comment.unused (2)
@constant (27)
@constant.builtin (15)
@constant.character (1)
@constructor (4)
@embedded (10)
@emphasis (1)
@emphasis.strong (1)
@escape (4)
@function (44)
@function.builtin (2)
@function.definition (2)
@function.method (22)
@function.method.builtin (3)
@function.special (4)
@function.special.definition (1)
@import (1)
@keyframes (1)
@keyword (32)
@label (2)
@link_text (1)
@link_uri (1)
@media (1)
@module (1)
@namespace (1)
@number (16)
@operator (24)
@property (11)
@property.json_key (1)
@punctuation (1)
@punctuation.bracket (28)
@punctuation.delimiter (12)
@punctuation.list_marker (1)
@punctuation.special (17)
@string (23)
@string.doc (1)
@string.escape (5)
@string.regex (7)
@string.special (4)
@string.special.symbol (2)
@supports (1)
@tag (14)
@text.literal (2)
@title (1)
@type (28)
@type.builtin (4)
@type.super (3)
@variable (5)
@variable.member (3)
@variable.parameter (4)
@variable.special (12)

Extension-only:

@tag.delimiter (1)
```

Verbose example output:

```
Shared:

@attribute (6) - [css, heex, javascript, tsx]
@boolean (5) - [javascript, proto, tsx, typescript, yaml]
@charset (1) - [css]
@comment (19) - [bash, c, cpp, css, elixir, erb, go, gomod, gowork, heex, javascript, json, proto, python, ruby, rust, tsx, typescript, yaml]
@comment.doc (3) - [elixir]
@comment.unused (2) - [elixir]
@constant (27) - [bash, c, cpp, elixir, heex, javascript, json, proto, python, ruby, rust, tsx, typescript]
@constant.builtin (15) - [elixir, go, javascript, python, ruby, tsx, typescript, yaml]
@constant.character (1) - [regex]
@constructor (4) - [tsx, typescript]
@embedded (10) - [bash, elixir, javascript, python, ruby, tsx, typescript]
@emphasis (1) - [markdown]
@emphasis.strong (1) - [markdown]
@escape (4) - [go, python, regex, ruby]
@function (44) - [bash, c, cpp, css, elixir, go, heex, javascript, python, rust, tsx, typescript]
@function.builtin (2) - [python]
@function.definition (2) - [rust]
@function.method (22) - [go, javascript, python, ruby, rust, tsx, typescript]
@function.method.builtin (3) - [ruby]
@function.special (4) - [c, cpp, rust]
@function.special.definition (1) - [rust]
@import (1) - [css]
@keyframes (1) - [css]
@keyword (32) - [bash, c, cpp, css, elixir, erb, go, gomod, gowork, heex, javascript, jsdoc, proto, python, ruby, rust, tsx, typescript]
@label (2) - [c, cpp]
@link_text (1) - [markdown]
@link_uri (1) - [markdown]
@media (1) - [css]
@module (1) - [heex]
@namespace (1) - [css]
@number (16) - [bash, c, cpp, css, elixir, go, javascript, json, proto, python, regex, ruby, rust, tsx, typescript, yaml]
@operator (24) - [bash, c, cpp, css, elixir, go, gomod, gowork, heex, javascript, proto, python, regex, ruby, tsx, typescript]
@property (11) - [bash, c, cpp, css, javascript, python, regex, rust, tsx, typescript, yaml]
@property.json_key (1) - [json]
@punctuation (1) - [elixir]
@punctuation.bracket (28) - [c, cpp, elixir, go, heex, javascript, json, proto, regex, ruby, rust, tsx, typescript, yaml]
@punctuation.delimiter (12) - [c, cpp, css, elixir, heex, javascript, proto, regex, ruby, tsx, typescript, yaml]
@punctuation.list_marker (1) - [markdown]
@punctuation.special (17) - [elixir, javascript, python, ruby, tsx, typescript, yaml]
@string (23) - [bash, c, cpp, css, elixir, go, gomod, gowork, heex, javascript, json, proto, python, regex, ruby, rust, tsx, typescript, yaml]
@string.doc (1) - [python]
@string.escape (5) - [elixir, javascript, tsx, typescript, yaml]
@string.regex (7) - [elixir, javascript, ruby, tsx, typescript]
@string.special (4) - [css, elixir]
@string.special.symbol (2) - [elixir, ruby]
@supports (1) - [css]
@tag (14) - [css, heex, javascript, tsx]
@text.literal (2) - [markdown]
@title (1) - [markdown]
@type (28) - [c, cpp, css, elixir, go, javascript, jsdoc, proto, python, ruby, rust, tsx, typescript, yaml]
@type.builtin (4) - [javascript, rust, tsx, typescript]
@type.super (3) - [ruby]
@variable (5) - [c, cpp, javascript, tsx, typescript]
@variable.member (3) - [go, ruby]
@variable.parameter (4) - [ruby]
@variable.special (12) - [cpp, css, javascript, ruby, rust, tsx, typescript]

Extension-only:

@tag.delimiter (1) - [astro]
```

Release Notes:

- N/A

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2024-04-22 11:51:06 -04:00
Marshall Bowers
ee531b6f4d Sort the list of suggested extensions (#10854)
This PR sorts the list of suggested extensions.

Release Notes:

- N/A
2024-04-22 11:41:16 -04:00
Karolis Narkevicius
67e7c33428 Add ReScript suggested extension for res and resi files (#10822)
Release Notes:

- Added ReScript as a suggested extension for .res and .resi files
2024-04-22 11:22:51 -04:00
Piotr Osiewicz
615de381da terminal: hide navigation buttons (#10847)
We were effectively discarding value set by display_nav_history_buttons
once we've updated settings for a pane. This commit adds another bit of
state to display_nav_history_buttons by allowing it to hard-deny setting
updates.

Release Notes:

- Fixed a bug that caused disabled navigation buttons to show up in
terminal panel.
2024-04-22 14:38:16 +02:00
Lachlan Campbell
74241d9f93 Add woff(2) to file type icon list (#10833)
I noticed the sidebar was using the fallback icons for woff/woff2
webfont files, instead of the font icon:

<img width="195" alt="CleanShot 2024-04-22 at 03 01 18@2x"
src="https://github.com/zed-industries/zed/assets/5074763/2e925c33-0be5-4ed9-ae87-ce72f95f8416">

With this PR, I'm hoping all those font files would use the A icon
instead.

Release Notes:

- Updated`.woff` & `.woff2` file types in the sidebar to display the
font icon.
2024-04-22 12:37:05 +03:00
Kirill Bulatov
dd41c10099 Filter out other languages' tasks from the task modal (#10839)
Release Notes:

- Fixed tasks modal showing history from languages, not matching the
currently active buffer's one
2024-04-22 12:36:26 +03:00
Thorsten Ball
a0fa8a489b ruby: Check if solargraph exists in $PATH or is configured (#10835)
This fixes #9811 by checking for the `solargraph` binary in the `$PATH`
as it's setup in the project shell.

It also adds support for configuring the path to `solargraph` manually:

```json
{
  "lsp": {
    "solargraph": {
      "binary": {
        "path": "/Users/thorstenball/bin/solargraph",
        "arguments": ["stdio"]
      }
    }
  }
}
```

## Example

Given the following setup:

- `ruby@3.3.0` used globally, no `solargraph` installed globally
- `ruby@3.2.2` used in a project, `solargraph` installed as binstub in
`$project/bin/solargraph`, `.envrc` to configure `direnv` to add
`$project/bin` to `$PATH

Which looks like this in practice:

```shell
# GLOBAL
~ $ ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]
~ $ which solargraph
solargraph not found

# IN PROJECT
~ $ cd work/projs/rails-proj
direnv: loading ~/work/projs/rails-proj/.envrc
direnv: export ~PATH
~/work/projs/rails-proj $ ruby --version
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin23]
~/work/projs/rails-proj $ which solargraph
/Users/thorstenball/work/projs/rails-proj/bin/solargraph
```

The expectation is that Zed, when opening `~/work/projs/rails-proj`,
picks up the local `solargraph`.

But with **Zed Stable** that doesn't work, as we can see in the logs:

```
2024-04-22T10:21:37+02:00 [INFO] starting language server. binary path: "solargraph", working directory: "/Users/thorstenball/work/projs/rails-proj", args: ["stdio"]
2024-04-22T10:21:37+02:00 [ERROR] failed to start language server "solargraph": No such file or directory (os error 2)
```

With the change in this PR, it uses `rails/proj/bin/solargraph`:

```
[2024-04-22T10:33:06+02:00 INFO  language] found user-installed language server for Ruby. path: "/Users/thorstenball/work/projs/rails-proj/bin/solargraph", arguments: ["stdio"]
[2024-04-22T10:33:06+02:00 INFO  lsp] starting language server. binary path: "/Users/thorstenball/work/projs/rails-proj/bin/solargraph", working directory: "/Users/thorstenball/work/projs/rails-proj", args: ["stdio"]
```

**NOTE**: depending on whether `mise` (or `rbenv`, `asdf`, `chruby`,
...) or `direnv` come first in the shell-rc file, it picks one or the
other, depending on what puts itself first in `$PATH`.

## Release Notes

Release Notes:

- Added support for finding the Ruby language server `solargraph` in the
user's `$PATH` as it is when `cd`ing into a project's directory.
([#9811](https://github.com/zed-industries/zed/issues/9811))
- Added support for configuring the `path` and `arguments` for
`solargraph` language server manually. Example from settings: `{"lsp":
{"solargraph": {"binary":
{"path":"/Users/thorstenball/bin/solargraph","arguments": ["stdio"]}}}}`
([#9811](https://github.com/zed-industries/zed/issues/9811))
2024-04-22 10:44:05 +02:00
Owen Law
e1685deb29 Revert "Use XI2 for Scrolling on X11" (#10818)
Reverts zed-industries/zed#10695

Some users are experiencing broken scrolling due to the changes from
this PR, so it should be reverted while I investigate what causes the
problems.

Release Notes:

- N/A
2024-04-20 20:07:13 -07:00
Michael Angerman
4ab48c689f storybook: Fix crash in Picker story (#10793)
@mikayla-maki approved my suggested change as noted in the issue
below...

Release Notes:

- Fixed: [#10739 ](https://github.com/zed-industries/zed/issues/10739)
2024-04-20 19:41:45 -07:00
d1y
2677ec7568 Suggest .log file (#10796)
Release Notes:

- Suggest .log file
2024-04-21 03:23:31 +03:00
Kyle Kelley
cd6acff635 Remove TODOs from semantic index build-up (#10789)
Quick touch up of some `// todo!()` that got left in `semantic-index`

Release Notes:

- N/A
2024-04-19 18:59:12 -07:00
William Viktorsson
5102e37a5b Changed cmd+w with no open tabs to close window (#10740)
Fixes https://github.com/zed-industries/zed/issues/5322


Release Notes:

- Changed cmd+w with no open tabs to close window
[#5322](https://github.com/zed-industries/zed/issues/5322)



https://github.com/zed-industries/zed/assets/22855292/113b1635-ae30-4718-a3d3-758a4bf53714
2024-04-19 14:42:33 -07:00
张小白
fee2065b64 windows: Fix main thread blocking when resizing or moving window (#10758)
Connection: Fix #10703 


https://github.com/zed-industries/zed/assets/14981363/59abfab7-ebb2-4da7-ad13-0a9e42f9c1d3




Release Notes:

- N/A
2024-04-19 14:40:21 -07:00
张小白
c3bcfb374c windows: Fix wrong bitmap format (#10773)
I accidentally wrote the bitmap format incorrectly during a refactor.

Release Notes:

- N/A
2024-04-19 14:40:06 -07:00
Mikayla Maki
8a02159b82 Add a command to expand the context for a multibuffer (#10300)
This PR adds an action to expand the excerpts lines of context in a
multibuffer.

Release Notes:

- Added an `editor::ExpandExcerpts` action (bound to `shift-enter` by
default), which can expand the excerpt the cursor is currently in by 3
lines. You can customize the number of lines by rebinding this action
like so:

```json5
// In your keybindings array...
  {
    "context": "Editor && mode == full",
    "bindings": {
      "shift-enter": ["editor::ExpandExcerpts", { "lines": 5 }],
    }
  }
```

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Max <max@zed.dev>
2024-04-19 14:27:56 -07:00
Andrew Lygin
9d9bce08a7 Fix scroll thumb (#10667)
Editor scrollbar has several issues that show up on large files:

- The thumb scrolls beyond the window.
- When dragged, the thumb goes out of sync with the mouse pointer.
- When the scrollbar trunk is clicked, the thumb jumps incorrectly.


https://github.com/zed-industries/zed/assets/2101250/320dba59-a526-4e68-99b3-1186271ba839

The reason is that the scrollbar now has two modes:
1. The "basic mode" for small files, when the thumb height correctly
represents the visible area, i.e. the top of the thumb matches the top
visible row (let's call it top-to-top sync), and the bottom of the thumb
matches the bottom visible row.
2. The "extended mode" for large files, when thumb becomes too small and
we have to impose minimal height to it. In this mode we have a vertical
offset of the first row position inside the scrollbar, we try to
position the thumb center-to-center with the editor.

...and the second mode is not implemented correctly. Also, mouse event
handlers ignore it. It is possible to fix this implementation, but I'm
not sure if it worth doing because it a) leads to some confusing cases
(for instance, in the extended mode the first row marker is not at the
top of the scrollbar), and b) differs from what all other editors do.

Here's a previous mentioning of this problem:
https://github.com/zed-industries/zed/pull/9080#pullrequestreview-1927465293

This PR changes the "extended mode", making it synchronize the thumb
top-to-top with the editor. It solves all the mentioned problems and
makes the scroll thumb work the same whay as in other editors.

But if you want to stick to the idea of the center-to-center sync for
large files, I can do that too.

Release Notes:

- Fixed scroll thumb behaviour.

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.

- N/A
2024-04-19 23:18:37 +03:00
Max Brunsfeld
247b0317b9 Add bracket queries for HCL, Terraform (#10785)
Release Notes:

- N/A
2024-04-19 10:59:14 -07:00
Thorsten Ball
f082344747 Add pull requests to git blame tooltip (#10784)
Release Notes:

- Added links to GitHub pull requests to the git blame tooltips, if they
are available.

Screenshot:

(Yes, the icon will be resized! cc @iamnbutler)

![screenshot-2024-04-19-18 31
13@2x](https://github.com/zed-industries/zed/assets/1185253/774af0b3-f587-4acc-aa1e-1846c2bec127)
2024-04-19 18:54:20 +02:00
Thorsten Ball
70427daed2 Remove unused field on CommitDetails (#10783)
Release Notes:


- N/A
2024-04-19 18:34:38 +02:00
Kirill Bulatov
13c17267b9 Properly pass nested script arguments for tasks (#10776)
Closes
https://github.com/zed-industries/zed/discussions/10732#discussion-6524347
introduced by https://github.com/zed-industries/zed/pull/10548 while
keeping both Python and Bash run selection capabilities.

Also replaced redundant `SpawnTask` struct with `SpawnInTerminal` that
has identical fields.

Release Notes:

- Fixed incorrect task escaping of nested script arguments

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
2024-04-19 16:24:35 +03:00
Thorsten Ball
9247da77a3 git blame: Display GitHub avatars in blame tooltips, if available (#10767)
Release Notes:

- Added GitHub avatars to tooltips that appear when hovering over a `git
blame` entry (either inline or in the blame gutter).

Demo:



https://github.com/zed-industries/zed/assets/1185253/295c5aee-3a4e-46aa-812d-495439d8840d
2024-04-19 15:15:19 +02:00
Philipp Schaffrath
37e4f83a78 Fix stutter while dragging something over an element that stops event propagation (#10737)
~~This is extracted from #10643.~~

~~It looks like the editor had a small optimization to drop events when
hovering the gutter. This also happens while dragging a tab over the
gutter, and causes some stuttering. Please correct me if this wasn't
just a small optimization, but I could not derive a different reason for
this code to exist.~~

The window was waiting for event propagation to update any drag. This
change makes sure the drag always gets updated, which makes sure it will
always be fluid, no matter if any element stops event propagation. Ty
@as-cii for pointing me to a better solution!

Release Notes:

- Fixed issue where dragging tab over any element that stops event
propagation would stutter
2024-04-19 13:29:13 +02:00
Piotr Osiewicz
3273f5e404 fs: Move Repository trait into git crate (#10768)
/cc @mrnugget 
Release Notes:

- N/A
2024-04-19 11:57:17 +02:00
Piotr Osiewicz
8513a24dd8 chore: Revert accidental pushes to main (#10769)
This reverts two commits that I've just pushed to main instead of
`move-repository-trait-into-git`. Mea culpa.
Release Notes:

- N/A
2024-04-19 10:56:29 +02:00
Piotr Osiewicz
54699e39e7 fixup! fixup! Always provide default task context (#10764) 2024-04-19 10:54:14 +02:00
Piotr Osiewicz
8fc8309e45 fixup! Always provide default task context (#10764) 2024-04-19 10:53:37 +02:00
Kirill Bulatov
222034cacf Always provide default task context (#10764)
Based on
https://github.com/zed-industries/zed/issues/8324?notification_referrer_id=NT_kwDOACkO1bI5NTk0NjM0NzkyOjI2OTA3NzM&notifications_query=repo%3Azed-industries%2Fzed+is%3Aunread#issuecomment-2065551553

Release Notes:

- Fixed certain files' task modal not showing context-based tasks
2024-04-19 10:51:50 +03:00
张小白
9863b920b0 windows: Fix panic with some unicode characters (#10750)
Fix #10749 

Release Notes:

- N/A
2024-04-18 22:28:39 -07:00
Joseph T. Lyons
ea952b2a95 Remove empty script 2024-04-18 22:24:48 -04:00
Jason Wen
dd7eced2b6 Prevent command prompt from opening when running git blame on windows (#10747)
Release Notes:

- Fixed issue reported in discord where the git blame feature would open
command prompt windows
2024-04-18 16:02:24 -07:00
apricotbucket28
d4922eb10b wayland: Fix window close (#10702)
Partially fixes https://github.com/zed-industries/zed/issues/10483 (X11
still has this issue)

Also adds some missing destroy() calls for some objects.
Thanks @phisch!

Release Notes:

- N/A
2024-04-18 16:02:05 -07:00
Kirill Bulatov
95827d4c49 Fix the typo 2024-04-19 01:52:16 +03:00
Kirill Bulatov
2602fc47bb Match user selection when renaming (#10748)
Initial state:
<img width="337" alt="Screenshot 2024-04-19 at 01 35 34"
src="https://github.com/zed-industries/zed/assets/2690773/1720d06c-54ed-4479-b694-ea478ac5a55a">

Before the fix:
<img width="319" alt="Screenshot 2024-04-19 at 01 35 39"
src="https://github.com/zed-industries/zed/assets/2690773/64429088-e75b-44c3-b5d4-31a841e69a1d">

After:
<img width="336" alt="Screenshot 2024-04-19 at 01 36 43"
src="https://github.com/zed-industries/zed/assets/2690773/c523e549-c546-4a70-aa33-629912598466">
 

Release Notes:

- Improved rename selections to match the user ones
2024-04-19 01:49:14 +03:00
Kirill Bulatov
6d1ea782a4 Show tooltip in task spawn modal (#10744)
Tooltip shows original task template's label, if it differs from the one
displayed in the modal. Also, a resolved command with args will be shown
in the tooltip if different from the modal entry text.

<img width="578" alt="Screenshot 2024-04-19 at 00 40 28"
src="https://github.com/zed-industries/zed/assets/2690773/c89369d6-8ffc-4464-ab3b-ea5e8fb7625a">
<img width="761" alt="Screenshot 2024-04-19 at 00 40 32"
src="https://github.com/zed-industries/zed/assets/2690773/b02f1518-976a-4a9b-ba7c-f88c6e056217">
<img width="738" alt="Screenshot 2024-04-19 at 00 40 56"
src="https://github.com/zed-industries/zed/assets/2690773/be502537-f4bd-4ae0-a5e7-78e37fe8fb00">
<img width="785" alt="Screenshot 2024-04-19 at 00 41 01"
src="https://github.com/zed-industries/zed/assets/2690773/9bedcd21-8729-44c8-9a17-46a5a01c7f26">


Release Notes:

- Added tooltips into task spawn modal
2024-04-19 01:43:52 +03:00
Kirill Bulatov
870a61dd4d Add "Open in Terminal" context menu entries for project panel, editor and tab context menus (#10741)
Closes https://github.com/zed-industries/zed/issues/4566

Pane tabs (does not exist for multibuffer tabs):
<img width="439" alt="Screenshot 2024-04-18 at 23 01 08"
src="https://github.com/zed-industries/zed/assets/2690773/3af79ed8-07ea-4cf2-bcf9-735b1b3be8c4">

Editor context menu:
<img width="404" alt="Screenshot 2024-04-18 at 23 01 14"
src="https://github.com/zed-industries/zed/assets/2690773/38ea7afc-df2b-45ef-8331-eb6a4588af9f">

Project panel context menu (was not shown for file entries before this):
<img width="408" alt="Screenshot 2024-04-18 at 23 01 18"
src="https://github.com/zed-industries/zed/assets/2690773/e336fce1-7da0-4671-b8d2-8d3409c23eb6">

Release Notes:

- (breaking change) Moved `project_panel::OpenInTerminal` into
`workspace::OpenInTerminal` action and add it in editors, tab context
menus and proper panel file entries
([4566](https://github.com/zed-industries/zed/issues/4566))
2024-04-19 01:43:46 +03:00
Kirill Bulatov
250b71fb44 Select buffer search query on follow-up cmd-f (#10745)
https://github.com/zed-industries/zed/assets/2690773/fd754cfc-aca5-4c4d-9b42-53d1c8eca0e9

Unfortunately, the tests did not work for me, as the `FocusSearch`
action handlers were never called for me.

Release Notes:

- Improved buffer search workflow: follow-up cmd-f select query string
2024-04-19 01:43:33 +03:00
Philipp Schaffrath
15c4c4a308 wayland: Fix input_handler out of range access (#10724)
The wayland implementation takes an input handler from the state, but
only puts it back if the event was an IME key. I flipped the logic to
ensure it's always put back.

This should fix both:
- #10344
- #10652

Release Notes:

- Fixed input_handler out of range access
([#10344](https://github.com/zed-industries/zed/issues/10344),
[#10652](https://github.com/zed-industries/zed/issues/10652)).
2024-04-18 15:20:23 -07:00
apricotbucket28
b31df39ab0 linux: Primary clipboard (#10534)
Implements copying from and pasting to the primary selection.

Release Notes:

- N/A
2024-04-18 14:54:18 -07:00
Owen Law
98db7fa61e Use XI2 for Scrolling on X11 (#10695)
Changes the X11 platform code to use the xinput extension which allows
for smooth scrolling and horizontal scrolling.

Release Notes:

- Added smooth scrolling to X11 on Linux
- Added horizontal scrolling to X11 on Linux
2024-04-18 14:44:21 -07:00
Nimit Savant
bd5473a582 docs: Install Windows 10/11 SDK with VS installation (#10550)
This installation is also needed with VS installation. Only then they
would be able to target the WINDOWS SDK

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.

I was getting link.exe error before this stating that my MSVC was not
installed properly. But MSVC was perfectly installed. I went on
stackoverflow and checked for similar instances with `cargo run` on
windows and found this

https://stackoverflow.com/a/55603112/12859779

where in comment Fasis states that we need to install WINDOWS 10 SDK. I
had Windos 11 so Installed that and it worked :)

Release Notes:

- N/A
2024-04-18 14:42:20 -07:00
Marshall Bowers
1fbc04104c Move lints section to the top of Cargo.toml, to match the others 2024-04-18 15:53:48 -04:00
Philipp Schaffrath
2f892e3523 Improve drop targets (#10643)
This introduces multiple improvements to the drop targets.

## Hitbox shape
Currently, hitboxes are rectangles, where the vertical ones reach all
the way to the ends, which reduces the space for the horizontal ones,
making the hitboxes a bit awkward in the corners. This new approach just
determines the closest side.

Visual representation:
![Frame
3](https://github.com/zed-industries/zed/assets/1282767/1cd2ca31-d9d4-41dd-87fb-1a8fbb8b7fcc)

## Hitbox size
The width of the hitbox was currently always 8 rem all around. In setups
with many columns or rows, or when the font size was very large, this
could potentially overlap the center hitbox, not allowing to drop a tab
without another split. Now the width of the hitboxes are a fraction of
the smaller size of its parents width and height. This makes sure the
hitboxes have the same width all around, but never fully block the
center hitbox.

I've also made this value configurable through the new
`drop_target_size` config which takes a `f32` fraction and is set to 0.2
by default.

Not sure if this is worth mentioning, but this technically allows to
remove the split hitboxes all together by setting it to `0.0`, or
removing the center hitbox by setting it to any value `>=0.5`. Not that
this is necessary, but it would be possible now.

## Larger visualization
The visual overlay when using one of the side hitboxes were also `8em`
wide. Since their logical size now changed, and it can't currently be
represented with GPUI (without abusing the `canvas` element), I made the
visual feedback take half of the width or height of the available space,
just like how other editors do this.

Also, the opacity/alpha value set by a theme is currently ignored. This
change now respects the themes opacity for it!

## Respect alpha value
Currently, the alpha value of `drop_target.background` is ignored. Even
the default themes set a value that is overwritten by a hard coded
value. I have removed this hard coded value and it now respects the
alpha value.

This change affects existing themes, see
https://github.com/zed-industries/zed/pull/10643#issuecomment-2059641528


## ~~No more lag while dragging over gutter~~ Extracted into #10737
~~It looks like the editor had a small optimization to drop events when
hovering the gutter. This also happens while dragging a tab over the
gutter, and causes some stuttering. Please correct me if this wasn't
just a small optimization, but I could not derive a different reason for
this code to exist.~~

Here is a video that tries to show all those changes with a before on
the left, and the after on the right:


https://github.com/zed-industries/zed/assets/1282767/f97f3420-513f-410f-a1c8-7966429ad348


Release Notes:

- Added `drop_target_size` setting. This should be a fractional percent
(e.g., `0.5`).
- Improved the hitboxes for drop targets.
- Updated drop targets to respect the alpha channel of the
`drop_target.background` color.
2024-04-18 15:28:25 -04:00
张小白
5c3e5cc45d windows: Support emoji inputs (#10125)
To work properly, needs #10119 to be merged.



https://github.com/zed-industries/zed/assets/14981363/2bb0c51b-6c70-4b29-8baa-302fb4fb9e89



Release Notes:

- N/A
2024-04-18 11:59:22 -07:00
张小白
11a3d2b04b windows: Introduce Direct Write (#10119)
This PR brings `Direct Write` to Zed. Now, Zed first trys to query
dwrite interface, if not supported, which means runing on Windows below
win10 1703), will choose `cosmic` as a fallback text system.

This direct write text system supports:
- Full font features support
- Emoji support
- Default system fonts as fallback

### Font features


https://github.com/zed-industries/zed/assets/14981363/198eff88-47df-4bc8-a257-e3acf81fd61d

### Emoji

![Screenshot 2024-04-03
211354](https://github.com/zed-industries/zed/assets/14981363/a5bc5845-42e8-4af1-af7e-abba598c1e72)

**Note: input emoji through IME or IMM not working yet, copy paste emoji
works fine (will be fixed by #10125 )**

### Font fallback

I use `Zed mono` which dose not support chinese chars to test font
fallback



https://github.com/zed-industries/zed/assets/14981363/c97d0847-0ac5-47e6-aa00-f3ce6d1e50a5



Release Notes:

- N/A
2024-04-18 11:58:46 -07:00
Marshall Bowers
1127b1a0de glsl: Bump to v0.1.0 (#10734)
This PR bumps the GLSL extension to v0.1.0.

Changes:

- #10694

Release Notes:

- N/A
2024-04-18 11:57:21 -04:00
jansol
c55055599a glsl: Add glsl_analyzer (LSP) (#10694)
<img width="691" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/c5e02d12-d1e4-4407-971c-72de7e6599f0">

@mikayla-maki the extension lists you as the original author.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-18 11:23:11 -04:00
Bennet Bo Fenner
a202499c9a markdown preview: Update channel notes when other collaborator changes buffer (#10718)
https://github.com/zed-industries/zed/assets/53836821/9a57885e-83b0-49fb-b3a8-0a7868566b85


Release Notes:

- Markdown preview now re-renders when another collaborator changes the
content of channel notes
2024-04-18 14:26:32 +02:00
Thorsten Ball
c2428f9f5d git blame: Parse permalinks client side (#10714)
Release Notes:

- N/A
2024-04-18 12:36:22 +02:00
Kirill Bulatov
d5c5394693 Open exactly one terminal on workspace::NewTerminal action (#10721)
Fixes https://github.com/zed-industries/zed/issues/4567

Release Notes:

- Fixed multiple terminals being opened on `workspace::NewTerminal`
calls ([4567](https://github.com/zed-industries/zed/issues/4567))
2024-04-18 12:44:40 +03:00
Shreekar Halvi
bb97432e9a Add minimum column option to git inline blame (#10682)
Release Notes:

- Added a setting to determine the minimum column where the inline blame
information is shown. Example: `{{"git": {"inline_blame": {"min_column":
80}}}` ([#10555](https://github.com/zed-industries/zed/issues/10555)).

Demo Video:


https://github.com/zed-industries/zed/assets/1185253/61343dbe-9002-4bd1-b0d4-403f8da79050

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-04-18 11:28:47 +02:00
David Baldwin
1b75f9d620 Add a setting to show/hide the project panel button in the status bar (#10707)
### What?

A setting has been added to control the visibility of the Project Panel
button in the status bar.

### Why?

I don't tend to use the Project Panel, but use a keyboard shortcut to
access if needed. Thus, the button in the status bar provides me little
more than visual clutter. Additionally, there is precedent for this
configurability with other panels (collaboration, chat, notification,
terminal, etc).

Release Notes:

- Added a setting to show/hide the Project Panel button in the status
bar. `{"project_panel": {"button": false}}`
2024-04-18 10:14:06 +03:00
Conrad Irwin
4c3178e7a8 Have the CI server draft the release notes (#10700)
While I don't expect these to be useful for our weekly minor releases, I
hope that this will save a step for people doing mid-week patches.

Release Notes:

- N/A
2024-04-17 15:51:45 -06:00
Conrad Irwin
41c8f2caa6 Attempt to fix segfault in window drop (#10690)
By default NSWindow's release themselves when closed, which doesn't
interact well with rust's lifetime system.

Disable that behaviour, and explicitly release the NSWindow when the
window handle is dropped.

Release Notes:

- Fixed a (rare) panic when closing a window.
2024-04-17 15:29:10 -06:00
Piotr Osiewicz
b9e0269991 project panel: do not expand collapsed worktrees on "collapse all entries" (#10687)
Fixes #10597

Release Notes:

- Fixed "project panel: collapse all entries" expanding collapsed
worktrees.
2024-04-17 22:55:20 +02:00
Piotr Osiewicz
4f2214e1d6 terminal: Treat paths with non-digit col/rows as paths nonetheless (#10691)
This relaxes path parsing to allow paths like ./foo.rs:food or
./food/foo_bar.rs:2:12:food as some tools may add a suffix without
regard for col/row end.

Fixes #10688 

Release Notes:

- Made path parsing in terminal (for directory links) more lenient with
regards to row/column fields.
2024-04-17 22:55:08 +02:00
Marshall Bowers
e25f0dfb0a v0.133.x dev 2024-04-17 13:05:26 -04:00
Marshall Bowers
3c805d4c6b prisma: Bump to v0.0.2 (#10689)
This PR bumps the Prisma extension to v0.02.

Changes:

- The Prisma extension now provides its own `tab_size` setting
([#10296](https://github.com/zed-industries/zed/pull/10296))

Release Notes:

- N/A
2024-04-17 12:40:01 -04:00
Thorsten Ball
4f1861edb6 git blame: ignore uncommitted files or repos without commits (#10685)
This fixes useless error messages popping up in case a file hasn't been
committed yet or the repo doesn't have commits yet.

Release Notes:

- Fixed git blame functionality not handling errors correctly when there
are no commits yet or when file isn't committed yet.
2024-04-17 17:51:26 +02:00
Thorsten Ball
d7becce9aa git: Only show inline git blame when editor is focused (#10680)
Release Notes:

- N/A
2024-04-17 13:28:11 +02:00
Thorsten Ball
62171387f6 Do not show tooltip for editor controls if clicked (#10679)
This avoids the tooltip showing up when the context menu is visible.

It fixes this:

![screenshot-2024-04-17-13 17
41@2x](https://github.com/zed-industries/zed/assets/1185253/373bb70e-9c7f-4b9f-a928-8206697c6039)


Release Notes:

- N/A
2024-04-17 13:20:47 +02:00
Thorsten Ball
47ad010901 Backport documentation for inline git blame (#10677)
Only noticed this when editing zed.dev.

Release Notes:

- N/A
2024-04-17 13:08:04 +02:00
Piotr Osiewicz
06987edadb project panel: Fix alignment of entries overflowing the panel. (#10676)
With file icons turned off, we still reserve space for an icon and make
it invisible. However, that space was marked as flex, which made it
shrink in case subsequent file name could not fit in the current width
of the project panel. Fixes #10622



https://github.com/zed-industries/zed/assets/24362066/d565a03a-3712-49d1-bf52-407e4508a8cf


Release Notes:


- Fixed project panel entries misalignment with narrow panel & file
icons turned off.
2024-04-17 12:54:56 +02:00
Thorsten Ball
1e1a2807db Document inline git blame (#10675)
Release Notes:

- N/A
2024-04-17 12:53:53 +02:00
Bennet Bo Fenner
9782dd342f docs: Sync with zed.dev version (#10674)
This PR brings the docs in line with the version we have on
https://zed.dev

Release Notes:

- N/A
2024-04-17 12:23:30 +02:00
Keith
535bcfad10 Update crates/ui/docs/hello-world.md TODO with explanation of SharedString usage (#10664)
Filled out a comment where there was a TODO to explain SharedString
usage.

Release Notes:

- N/A
2024-04-17 13:04:28 +03:00
Thorsten Ball
c76bacb974 Rename label to toggle inline git blame on/off (#10673)
cc @iamnbutler I think we should differentiate between inline blame and
the gutter blame.

Release Notes:

- N/A
2024-04-17 11:34:34 +02:00
Kirill Bulatov
20554d0296 Fix center element wrapper size (#10672)
Fixes
https://github.com/zed-industries/zed/pull/9754#pullrequestreview-2005401133
Fixes
https://github.com/zed-industries/zed/pull/9754#issuecomment-2060536590
Closes https://github.com/zed-industries/zed/pull/10669

* Updates the docs to use a proper max value for the centered layout
padding (0.4 instead of 0.45)
* Makes the `center` wrapper (`h_flex`) to be of size of the `center`
element always, to ensure terminal lines are displayed correctly

The letter fix is somewhat hacky: while it does the right thing right
now, it does not prevent us from future mistakes like these, and does
not explain why the bottom dock could be of one, smaller, height, and
its contents, the terminal pane/terminal element/something else would
think that it has a larger height, thus breaking the scrolling and
rendering.
cc @alygin if you're interested to solve another layout-related thing.

Release Notes:

- N/A
2024-04-17 12:34:18 +03:00
Thorsten Ball
2c78cf349b Regenerate git blame info when buffer's dirty bit changed (#10670)
This fixes the https://github.com/zed-industries/zed/issues/10583 by
regenerating the blame information after the
buffer is edited. It uses a debounce of 2seconds. Meaning that undone
deletions show up again after 2secs.

Release Notes:

- Fixed `git blame` data not handling the undoing of deletions
correctly.
([#10583](https://github.com/zed-industries/zed/issues/10583)).
2024-04-17 11:25:53 +02:00
Pedro Augusto da Silva Soares
c81eb419d4 Clear credentials state and delete keychain on SignOut request (#10558)
Release Notes:

- Fixed ([#4716](https://github.com/zed-industries/zed/issues/4716)).

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-16 19:57:38 -06:00
Conrad Irwin
c4e446f8a8 ./script/trigger-release (#10589)
Add `./script/trigger-release {nightly|stable|preview}`

This command can be run regardless of the state of your local git
repository, and it
either triggers a workflow run of `bump_patch_version.yml` (for
stable/preview) or
it force pushes the nightly tag.

Also add some docs on releases to explain all of this.

Release Notes:

- N/A
2024-04-16 19:32:51 -06:00
Max Brunsfeld
bc7eaa6cd5 Add links to jobs page in README and in app, under help menu (#10658)
Release Notes:

- N/A

Co-authored-by: Nathan <nathan@zed.dev>
2024-04-16 15:47:23 -07:00
Nate Butler
e93d554725 Add Editor Controls Menu to Tool Bar (#10655)
This PR adds an editor controls menu to the tool bar. This menu will be
used to contain controls that toggle visual features in the editor, like
toggling inlay hints, showing git status or blame, hiding the gutter,
hiding or showing elements in the tool bar, etc.

For the moment, this consolidates the new Inline Git Blame toggle and
the old Inlay Hints toggle. In the future it will contain additional
controls.

Before: 

![CleanShot - 2024-04-16 at 16 38
53@2x](https://github.com/zed-industries/zed/assets/1714999/249e353f-786a-4391-8d49-66dd61feff8a)

After:

![CleanShot - 2024-04-16 at 16 38
43@2x](https://github.com/zed-industries/zed/assets/1714999/5b3cf4d5-855a-4475-ac05-8474b6c94b7b)

---

Release Notes:

- Added an editor controls menu to the tool bar. This will contain
visual, editor-specific options like toggling inlay hints, showing git
status or blame, etc.
- Removed the top level inlay hint toggle from the tool bar due to the
above change.
- Added the ability to toggle inline git blame from the new editor
controls menu.

---------

Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
2024-04-16 18:03:54 -04:00
Mikayla Maki
775539b3fa Fix order of migrations from #9754 (#10657)
This fixes a bug caused by mis-ordered database migration in #9754

Release Notes:

- N/A
2024-04-16 14:29:04 -07:00
Marshall Bowers
545319bced terraform: Bump to v0.0.2 (#10653)
This PR bumps the Terraform extension to v0.0.2.

Changes:

- #10641

Release Notes:

- N/A
2024-04-16 16:08:58 -04:00
Marshall Bowers
0b2de51c37 csharp: Bump to v0.0.2 (#10651)
This PR bumps the C# extension to v0.0.2.

Changes:

- #10638

Release Notes:

- N/A
2024-04-16 15:49:16 -04:00
Marshall Bowers
9a680dafc3 clojure: Bump to v0.0.2 (#10650)
This PR bumps the Clojure extension to v0.0.2.

Changes:

- #10636

Release Notes:

- N/A
2024-04-16 15:37:54 -04:00
Marshall Bowers
4c35cfaa69 gleam: Bump to v0.1.1 (#10648)
This PR bumps the Gleam extension to v0.1.1.

Changes:

- #10635

Release Notes:

- N/A
2024-04-16 15:21:14 -04:00
Kirill Bulatov
be2bf98529 Show task summary in its terminal after it stops running (#10615)
Based on https://github.com/alacritty/alacritty/issues/7795

Unknown error code commands (now includes the interrupted ones):

![image](https://github.com/zed-industries/zed/assets/2690773/801868bc-081c-453c-a353-233d4397bda9)

Successful command:

![image](https://github.com/zed-industries/zed/assets/2690773/874377c7-c967-4a6f-8a89-ec7bf398a8b3)

Unsuccessful command:

![image](https://github.com/zed-industries/zed/assets/2690773/6c99dc5d-d324-41e9-a71b-5d0bf705de27)

The "design", including wordings and special characters, is not final,
suggestions are welcome.
The main idea was to somehow distinguish the appended lines without
occupying extra vertical space.

Release Notes:

- Added task summary output into corresponding terminal tabs
2024-04-16 22:13:35 +03:00
Andrew Lygin
4eb1e65fbb Add centered layout support (#9754)
This PR implements the Centered Layout feature (#4685):
- Added the `toggle centered layout` action.
- The centered layout mode only takes effect when there's a single
central pane.
- The state of the centered layout toggle is saved / restored between
Zed restarts.
- The paddings are controlled by the `centered_layout` setting:

```json
"centered_layout": {
  "left_padding": 0.2,
  "right_padding": 0.2
}
```

This allows us to support both the VSCode-style (equal paddings) and
IntelliJ-style (only left padding in Zen mode).

Release Notes:

- Added support for Centered Layout
([#4685](https://github.com/zed-industries/zed/pull/9754)).


https://github.com/zed-industries/zed/assets/2101250/2d5b2a16-c248-48b5-9e8c-6f1219619398

Related Issues:

- Part of #4382
2024-04-16 12:12:41 -07:00
Marshall Bowers
52591905fb lua: Bump to v0.0.2
The previous v0.0.3 bump was in error, as we hadn't published a v0.0.2 yet.
2024-04-16 14:27:05 -04:00
Marshall Bowers
d2e83cc148 lua: Bump to v0.0.3 (#10646)
This PR bumps the Lua extension to v0.0.3.

Changes:

- #10639
- #10642

Release Notes:

- N/A
2024-04-16 14:12:41 -04:00
Marshall Bowers
f633460a8d zig: Bump to v0.1.1 (#10645)
This PR bumps the Zig extension to v0.1.1.

Changes:

- #10559
- #10634

Release Notes:

- N/A
2024-04-16 14:05:12 -04:00
Peter Tripp
9470a52b5d lua: Fix broken LuaLS download on x64 (#10642)
The changes in #10437 accidentally switched 'x64' to 'x86_64' which
breaks installs on linux x64, macos x64 and windows x64. This yields the
following error:

```
[2024-04-16T12:58:01-04:00 ERROR project] failed to start language server "lua-language-server": no asset found matching "lua-language-server-3.7.4-darwin-x86_64.tar.gz"
[2024-04-16T12:58:01-04:00 ERROR project] server stderr: Some("")
```

It's trying to download: 
`lua-language-server-3.7.4-darwin-x86_64.tar.gz`
which should be
`lua-language-server-3.7.4-darwin-x64.tar.gz`

See [LuaLS release
page](https://github.com/LuaLS/lua-language-server/releases/tag/3.6.25).

CC: @maxbrunsfeld

lua.rs before ef4c70c:

c6028f6651/crates/languages/src/lua.rs (L35)

lua.rs after:

5d7148bde1/extensions/lua/src/lua.rs (L49)

Release Notes:

- N/A
2024-04-16 13:48:45 -04:00
Marshall Bowers
fa0302f156 terraform: Don't cache user-installed terraform-ls (#10641)
This PR updates the Terraform extension to not cache the binary when it
is using the one on the $PATH.

Release Notes:

- N/A
2024-04-16 13:40:13 -04:00
Marshall Bowers
5d7148bde1 lua: Don't cache user-installed lua-language-server (#10639)
This PR updates the Lua extension to not cache the binary when it is
using the one on the $PATH.

Release Notes:

- N/A
2024-04-16 13:22:42 -04:00
Marshall Bowers
58991f332b csharp: Don't cache user-installed OmniSharp (#10638)
This PR updates the C# extension to not cache the binary when it is
using the one on the $PATH.

Release Notes:

- N/A
2024-04-16 13:22:36 -04:00
Marshall Bowers
9c569c8d95 zig: Rename cached_binary to cached_binary_path (#10637)
This PR renames the `cached_binary` field on the `ZigExtension` back to
`cached_binary_path` to make it match the other extensions.

Release Notes:

- N/A
2024-04-16 13:18:21 -04:00
Marshall Bowers
1ba0bf925b clojure: Don't cache user-installed clojure-lsp (#10636)
This PR updates the Clojure extension to not cache the binary when it is
using the one on the $PATH.

Release Notes:

- N/A
2024-04-16 13:12:08 -04:00
Marshall Bowers
53105ddd16 gleam: Don't cache user-installed gleam (#10635)
This PR updates the Gleam extension to not cache the binary when it is
using the one on the $PATH.

Release Notes:

- N/A
2024-04-16 13:11:58 -04:00
Thorsten Ball
210f8ebfed zig: Do not cache user-installed zls (#10634)
This was a bug introduced when moving to extensions: when we find a
binary in the user's project environment, we shouldn't cache that
globally since it might not work for other projects.

See also: https://github.com/zed-industries/zed/pull/10559

Release Notes:


- N/A
2024-04-16 19:05:11 +02:00
Thorsten Ball
c015b5c4cd Enable inline-git-blame by default (#10632)
Release Notes:

- N/A
2024-04-16 18:53:57 +02:00
Henrique Ferreiro
c1c8a74c7f Add ability to specify binary path/args for clangd (#10608)
This uses the language server settings added in #9293 to allow users to
specify the binary path and arguments with which to start up `clangd`.

Example user settings for `clangd`:

```json
{
  "lsp": {
    "clangd": {
      "binary": {
        "path": "/usr/bin/clangd",
        "arguments": ["--log=verbose"]
      },
    }
  }
}
```

Constraints:

* Right now this only allows ABSOLUTE paths.

Release Notes:

- Added ability to specify `clangd` binary `path` (must be absolute) and
`arguments` in user settings. Example: `{"lsp": {"clangd": {"binary":
{"path": "/usr/bin/clangd", "arguments": ["--log=verbose"] }}}}`
2024-04-16 18:17:15 +02:00
Peter Tripp
2f00fcbdf6 docs: use_autoclose (#10514)
Add some keywords (bracket, quote, etc)to the comments describing
`use_autoclose` preference in the settings json.

This setting took me a while to find -- so now it'll be more easily
searchable for others.

Release Notes:

- N/A
2024-04-16 18:17:04 +02:00
Thorsten Ball
5c5fb972d0 Handle setting git blame delay to 0 (#10626)
Release Notes:


- N/A
2024-04-16 17:25:24 +02:00
Piotr Osiewicz
7928095951 chore: parse cli args just once in zed crate (#10613)
Release Notes:

- N/A
2024-04-16 16:27:45 +02:00
Thorsten Ball
70c3ca4fdd Fix toggling of inline git blame when it's delayed (#10620)
Release Notes:

- N/A
2024-04-16 16:07:22 +02:00
Kirill Bulatov
d49271a112 Use project search action with the default keybinding in app menus (#10618)
Fixes https://github.com/zed-industries/zed/issues/10611 

Zed has `workspace::NewSearch` (without a default keybinding) and
`workspace::DeploySearch` (with the default keybinding for its
`DeploySearch::find()` form).

Use the one with the keybinding, as it's the whole point of the menu.

Release Notes:

- Fixed "Find In Project" app menu item having no keybinding
([10611](https://github.com/zed-industries/zed/issues/10611))
2024-04-16 16:47:30 +03:00
Piotr Osiewicz
e34c443331 lsp: Do not set error/result fields if they're missing. (#10610)
We were previously not conforming to LSP spec, as we were setting
**both** result field and error field on response fields, which could
confuse servers. Excerpt from the spec:
> * The result of a request. This member is REQUIRED on success.
> * This member MUST NOT exist if there was an error invoking the
method.

Fixes #10595

Release Notes:

- N/A
2024-04-16 14:27:12 +02:00
Kirill Bulatov
263023021d Show Zoom In/Out shortcuts in the labels (#10604)
Based on https://github.com/zed-industries/zed/discussions/10599 
Does the same as the assistant tab with the Zoom In/Out labels.


![image](https://github.com/zed-industries/zed/assets/2690773/afc59a3e-c3df-4fc8-bcaf-1d45a21aecf7)

Release Notes:

- Adjusted Zoom In/Out for Pane and Terminal Pane to show keybinding
labels
2024-04-16 14:00:25 +03:00
Thorsten Ball
7e1a184446 Fix Markdown code rendering in tooltips ignoring languages (#10607)
Some code blocks that are returned in tooltips (returned by language
servers, for example) use the language file extension as the language in
the the triple-backtick code blocks.

Example:

    ```rs
    fn rust_code() {}
    ```

    ```cpp
    fn rust_code() {}
    ```

Before this change we only looked up the language with the
`rs`/`cpp`/... being interpreted as the language name. Now we also treat
it as a possible file extension.



Release Notes:

- Fixed Markdown code blocks in tooltips not having correct language
highlighting.

Before:


![image](https://github.com/zed-industries/zed/assets/1185253/1f3870a6-467c-4e5f-9e49-1ff32240d10f)

After:

![screenshot-2024-04-16-12 43
39@2x](https://github.com/zed-industries/zed/assets/1185253/21a45ed5-825a-412d-9dc0-35a444fc64ba)

Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-16 12:49:35 +02:00
Thorsten Ball
c834ea75ef Fix --- in Markdown docs not rendered correctly (#10606)
This fixes #10511 by turning off the YAML metadata block rendering in
the Markdown parser.

`clangd` uses `---` as dividers, but our parser interpreted it as a YAML
metadata block, even though it didn't contain any valid YAML.

Example Markdown from `clangd`:

    ### instance-method `format`

    ---
    → `void`
    Parameters:
    - `const int &`
    - `const std::tm &`
    - `int & dest`

    ---
    ```cpp
    // In my_formatter_flag
    public: void format(const int &, const std::tm &, int &dest)
    ```

What's between the two `---` is *not* valid YAML. Neovim, too,
interprets these as dividers and renders them as such.

And since we don't handle any possible metadata anyway, we can turn off
the metadata handling, which causes the parser to interpret the `---` as
dividers.



Release Notes:

- Fixed Markdown returned by `clangd` being rendered the wrong way.
([#10511](https://github.com/zed-industries/zed/issues/10511)).

Before:

![screenshot-2024-04-16-12 32
15@2x](https://github.com/zed-industries/zed/assets/1185253/a268f106-9504-48aa-9744-42a7521de807)

After:

![screenshot-2024-04-16-12 33
02@2x](https://github.com/zed-industries/zed/assets/1185253/dd178a63-a075-48a9-85d9-565157a5b050)

Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-16 12:40:13 +02:00
Thorsten Ball
4d8cba2add Fix long git author names/emails overflowing blame tooltip (#10605)
This fixes https://github.com/zed-industries/zed/issues/10581.

Release Notes:

- N/A

Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-16 12:03:45 +02:00
Thorsten Ball
08aef198d5 Fix inline blame annotations handling wrapped lines (#10600)
Fixes inline blame not being displayed correctly for soft-wrapped lines.

(Can't find the ticket)
![screenshot-2024-04-16-10 50
29](https://github.com/zed-industries/zed/assets/1185253/e3ff9018-f796-469a-9d42-5997baf7d2f6)


Release Notes:

- N/A
2024-04-16 11:27:23 +02:00
Hans
2cfb1ffa77 add a setting to control show/hide terminal button for status bar (#10593)
Release Notes:

- Added a setting to show/hide the terminal button in the status bar:
`{"terminal": {"button": false}}` to hide it. (#10513)

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-04-16 09:40:32 +02:00
Andrew Lygin
f3192b6fa6 Fix scrollbar marker settings (#10530)
Zed displays scrollbar markers of three types: git diffs, background
highlights and diagnostics. At the moment, the "background highlights"
markers are displayed for all the supported highlights:

- Occurences of the symbol under cursor.
- Search results.
- Scope boundaries (only works when a symbol is selected).
- Active hover popover position.

They all use the same color, which leads to confusing results. For
instance, in the following case I expect to see markers for the
`new_anchor` occurences in lines 43 and 47. But besides them, there're
also scope-markers for `ScrollAnchor` initialization block in lines 46
and 49, which makes me think that there're four places where
`new_anchor` appears.

<img width="740" alt="zed-scrollbar-markers"
src="https://github.com/zed-industries/zed/assets/2101250/78700e6b-fdd1-4c2f-beff-e564d8defc13">

Existing settings `selection` and `symbol_selection` in the `scrollbar`
section [don't work as
expected](https://github.com/zed-industries/zed/pull/10080#discussion_r1552325493),
which increases confusion.

This PR only leaves two types of bg-highlight-markers and provides
dedicated settings for them:

- Occurences of the symbol under cursor. Setting: `selected_symbol`,
default is `true`.
- Search results. Setting: `search_results`, default is `true`.

The `selection` and `symbol_selection` settings are not used anymore.

Release Notes:

- Breaking changes. Settings `selection` and `symbol_selection` in the
`scrollbar` section renamed to `search_results` and `selected_symbol`
respectively. Fixed the effect of these settings on which markers are
displayed on the scrollbar.

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.

- N/A

/cc @mrnugget
2024-04-16 09:21:22 +02:00
Conrad Irwin
33b9aca090 Add a bump patch version workflow (#10588)
I'd like to make it less clunky to release a new patch version of
preview/stable.

Release Notes:

- N/A
2024-04-15 20:31:12 -06:00
Kirill Bulatov
57b087e41e On label conflict, prefer resolved tasks with never templates (#10586)
This way, static (and other) templates that change, e.g. env vars, will
get the new template resolved task in the modal to run.

Release Notes:

- N/A
2024-04-16 03:31:33 +03:00
Kyle Kelley
2a9ce3cec3 Handle no active editor available (#10503)
Cleaned up a stray `.unwrap()`

Release Notes:

- N/A
2024-04-15 16:53:08 -07:00
Kyle Kelley
f5c2483423 Close the backticks in gpui geometry examples (#10579)
I noticed some of the examples in `crates/gpui/src/geometry.rs` were
missing ending triple backticks.

Release Notes:

- N/A
2024-04-15 16:52:19 -07:00
Mikayla Maki
4d314b2dd0 Increase the context in other multibuffers (#10582)
This moves the diagnostics and find all references to be in line with
the search pane. This also centralizes the constant into the editor code
base.

Release Notes:

- Increased diagnostic context to match the project search context.
2024-04-15 14:44:14 -07:00
Abdullah Alsigar
7a112b22ac dart: Use upstream tree-sitter-dart (#10552)
Update `tree-sitter-dart` to the upstream package, there was an issue
building Rust package which is resolved now.


Release Notes:


- N/A
2024-04-15 17:32:35 -04:00
Marshall Bowers
575eb792fb Remove stray word in CONTRIBUTING.md 2024-04-15 16:30:18 -04:00
Nathan Sobo
4f776f9ebe Don't make -l imply -d in bundle-mac script (#10438)
If you want a debug build, you can easily pass `-ld`.

Release Notes:

- N/A
2024-04-15 14:29:45 -06:00
Marshall Bowers
0c77e1ce45 Disable extension entries when the corresponding dev extension is installed (#10580)
This PR updates the extension list to disable remote entries when the
corresponding dev extension is installed.

Here is what this looks like:

<img width="1189" alt="Screenshot 2024-04-15 at 4 09 45 PM"
src="https://github.com/zed-industries/zed/assets/1486634/48bb61d4-bc85-4ca6-b233-716831dfa7d8">


Release Notes:

- Disabled extension entries when there is a development copy of that
extension installed.
2024-04-15 16:27:54 -04:00
Conrad Irwin
904b740e16 More vim-like regexes (#10577)
Fixes:  #10539

Release Notes:

- vim: Use `\<` `\>` instead of `\b`
2024-04-15 14:26:05 -06:00
Conrad Irwin
f2fc84ab44 Revert change to tracing (#10578)
Although we thought this fixed the bug, it just worked around it, and
runnign two copies of tracing in one app is a bad idea.

Simplify default RUST_LOG in development to avoid
 https://github.com/tokio-rs/tracing/issues/2927#issuecomment-2040080189



Release Notes:

- N/A
2024-04-15 14:00:56 -06:00
Conrad Irwin
fda3c91f16 bump libgit2 (#10561)
Although this probably doesn't fix anything by itself, it'll make it
easier to fix https://github.com/libgit2/libgit2/issues/6795

Release Notes:

- N/A
2024-04-15 13:21:30 -06:00
apricotbucket28
3eb8464d19 wayland: Improve cursor (#10516)
Fixes the cursor not updating when (a) switching windows from another
program via a shortcut and (b) when cursor updates were triggered by
something other than moving the mouse (e.g. when scrolling or pressing a
key).

Release Notes:

- N/A
2024-04-15 12:09:24 -07:00
张小白
58f57491b1 windows: Remove last_ime_input (#10506)
It seems that windows always report IME composition string, so there is
no need to store this string manually.

Release Notes:

- N/A
2024-04-15 12:08:38 -07:00
Marshall Bowers
3e44e97177 Install the latest compatible version of an extension when clicking "Install" (#10575)
This PR makes it so clicking "Install" will install the latest
compatible version of an extension instead of disabling the button when
the latest version is not compatible.

The "Upgrade" button will still be disabled when the latest version is
not compatible. We will also now display a tooltip to better indicate
why the button is disabled:

<img width="607" alt="Screenshot 2024-04-15 at 2 41 26 PM"
src="https://github.com/zed-industries/zed/assets/1486634/16ad516e-1c0c-4505-b994-158ea655641b">

Related to https://github.com/zed-industries/zed/issues/10509.

Release Notes:

- Changed the "Install" button for extensions to always install the
latest compatible version instead of becoming disabled when the latest
version of an extension is incompatible with the current Zed version.
2024-04-15 15:06:07 -04:00
Marshall Bowers
fda21232ae Indicate which extension version is installed when on an older version (#10574)
This PR adds an indicator to show what extension version is currently
installed when on an extension version that is not the latest.

<img width="1156" alt="Screenshot 2024-04-15 at 2 10 33 PM"
src="https://github.com/zed-industries/zed/assets/1486634/61c5e4cf-a0b8-48fc-8e52-f04f1c351794">

Release Notes:

- Added an indicator to show the currently-installed extension version
when not on the latest version.
2024-04-15 14:30:53 -04:00
Antonio Scandurra
57a736d74a Fuse iterator supplied to SumTree::from_iter (#10571)
This fixes an issue that could cause `from_iter` to never finish if the
underlying iterator restarted after returning `None` for the first time.

We only saw this in development but I wanna cherry-pick it to stable and
preview, just in case.

Release Notes:

- N/A

Co-authored-by: Kyle <kylek@zed.dev>
2024-04-15 20:09:43 +02:00
Max Brunsfeld
015e2ecd19 Remove built-in Nu support in favor of extension (#10570)
Release Notes:

- Removed built-in Nu language support in favor of an extension.

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-15 10:47:01 -07:00
Kyle Kelley
5037f466f6 Defer first update active buffer for conversation (#10564)
This fixes when the workspace is not actually available for a
`.read(cx)`.

Release Notes:

- Fix a panic when quoting a selection before the assistant panel has
been started

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-04-15 11:11:02 -06:00
Marshall Bowers
f28fde5e58 Move constraints to query parameters for GET /extensions/:extension_id/download (#10562)
This PR fixes a bug where the constraints provided when downloading the
latest version of an extension were not being read properly.

These constraints are passed in the query string, but `collab` was
attempting to read them from the path.

This should fix https://github.com/zed-industries/zed/issues/10484, once
it is deployed.


Release Notes:

- N/A
2024-04-15 13:06:20 -04:00
Thorsten Ball
d1928f084e Remove TODO by defining constant (#10556)
Release Notes:

- N/A
2024-04-15 18:37:03 +02:00
Piotr Osiewicz
ad22bddffa diagnostics: Update diagnostics more eagerly (#10560)
Here comes a lenghty explanation for a short commit: We've had feedback
that our diagnostics tab often mismatches what's shown in the status
bar. E.g: https://x.com/fasterthanlime/status/1778764747732594753 Let's
dive into the lifetime of diagnostic tab first; it is actually spawned
*just once per workspace*, the first time you click on the diagnostics
status indicator. Even if you close this tab, we still reuse the same
object under the hood later on. This has upsides, as it means that you
can close a tab and then reopen it with your selections still in-tact
and so on. However, this also leads to the perceived staleness.
Crucially, the first time ever in a given session that you spawn the
diagnostics tab, the status bar counts match the content of a tab. That
is because we always call \`update_excerpts\` when we create diagnostics
tab for the first time, but later on we have severe constraints on when
we want to update the excerpts in diagnostics tab, mostly centered
around presence of selections in an editor... but, since we reuse the
diagnostic tab object under the hood, we're always gonna have at least
one selection in an editor sans the first time you open it. The end
result is that in order for diagnostic tab contents to be updated, we
have to get a "on-disk-diagnostics-finished" notification from language
server, which can take a long time.
Another example of this property manifesting itself is that if you fix a
diagnostic warning/error, it takes a while for diagnostic tab to reflect
it.

With this PR, I've afforded a bit of leniency in refreshing the contents
of that tab. The old check that discarded updates when diagnostics
editor had at least one selection has been updated to instead reject
multicursors; this is still overly conservative, as I'm not yet sure how
big of an issue is the cursor that's jumping around (as that's what the
selections constraint is supposed to prevent).



Release Notes:

- Fixed diagnostics tab showing outdated entries before the language
server is done with it's analysis.
2024-04-15 18:28:58 +02:00
Thorsten Ball
da0d968a2c zig: Use env if using zls from shell env (#10559)
This fixes the problem of the Zig extension picking up `zls` from the
shell env but `zls` then failing to launch because it cannot find `zig`.

Scenario in which this happens:
- `.envrc` in a project that sets `$PATH` up
- in that `$PATH` there's `zls` and `zig`
- Zed is started from Dock
- Project is opened
- Shell env from project directory is loaded and used to get to `zls`
- `zls` is then started, without that environment set on the process
- `zls` cannot find `zig`

Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-15 17:39:07 +02:00
Antonio Scandurra
200e36311c Intersect content mask with hitbox bounds only during hit test (#10554)
This fixes a bug that caused the editor to be rendered incorrectly when
its bounds extended outside the content mask. This is because the editor
uses the returned `Hitbox` bounds to determine the origin of its
elements.

With this commit, we will now store a new `content_mask` field within
the `Hitbox` struct which is captured when the hitbox is inserted. Then,
the content mask is applied on the fly when performing a hit test to
determine whether the hitbox is actually hovered.

Release Notes:

- N/A
2024-04-15 15:09:15 +02:00
Kirill Bulatov
db48c75231 Add basic bash and Python tasks (#10548)
Part of https://github.com/zed-industries/zed/issues/5141

* adds "run selection" and "run file" tasks for bash and Python.
* replaces newlines with `\n` symbols in the human-readable task labels
* properly escapes task command arguments when spawning the task in
terminal

Caveats:

* bash tasks will always use user's default shell to spawn the
selections, but they should rather respect the shebang line even if it's
not selected
* Python tasks will always use `python3` to spawn its tasks now, as
there's no proper mechanism in Zed to deal with different Python
executables

Release Notes:

- Added tasks for bash and Python to execute selections and open files
in terminal
2024-04-15 16:07:21 +03:00
Carter Olsen
1911a9f39b Add a setting to control the vertical and horizontal scroll sensitivity (#10244)
Some people (like myself) use touchpads for development and I find Zed's
default scroll sensitivity to be slower than I like. This change adds a
scroll sensitivity multiplier that allows users to customize the speed
of their scrolling.

Release Notes:

- Added a setting under "scroll_sensitivity" that allows user to control
the scroll sensitivity. This value acts as a multiplier for the
horizontal and vertical scroll speed.
2024-04-15 14:40:09 +02:00
Thorsten Ball
faebce8cd0 Inline git blame (#10398)
This adds so-called "inline git blame" to the editor that, when turned
on, shows `git blame` information about the current line inline:


![screenshot-2024-04-15-11 29
35@2x](https://github.com/zed-industries/zed/assets/1185253/21cef7be-3283-4556-a9f0-cc349c4e1d75)


When the inline information is hovered, a new tooltip appears that
contains more information on the current commit:


![screenshot-2024-04-15-11 28
24@2x](https://github.com/zed-industries/zed/assets/1185253/ee128460-f6a2-48c2-a70d-e03ff90a737f)

The commit message in this tooltip is rendered as Markdown, is
scrollable and clickable.

The tooltip is now also the tooltip used in the gutter:

![screenshot-2024-04-15-11 28
51@2x](https://github.com/zed-industries/zed/assets/1185253/42be3d63-91d0-4936-8183-570e024beabe)


## Settings

1. The inline git blame information can be turned on and off via
settings:
```json
{
  "git": {
    "inline_blame": {
      "enabled": true
    }
  }
}
```
2. Optionally, a delay can be configured. When a delay is set, the
inline blame information will only show up `x milliseconds` after a
cursor movement:
```json
{
  "git": {
    "inline_blame": {
      "enabled": true,
      "delay_ms": 600
    }
  }
}
```
3. It can also be turned on/off for the current buffer with `editor:
toggle git blame inline`.

## To be done in follow-up PRs

- [ ] Add link to pull request in tooltip
- [ ] Add avatars of users if possible

## Release notes

Release Notes:

- Added inline `git blame` information the editor. It can be turned on
in the settings with `{"git": { "inline_blame": "on" } }` for every
buffer or, temporarily for the current buffer, with `editor: toggle git
blame inline`.
2024-04-15 14:21:52 +02:00
Kirill Bulatov
573ba83034 Merge Zed task context providing logic (#10544)
Before, `tasks_ui` set most of the context with `SymbolContextProvider`
providing the symbol data part of the context. Now, there's a
`BasicContextProvider` that forms all standard Zed context and it
automatically serves as a base, with no need for other providers like
`RustContextProvider` to call it as before.

Also, stop adding `SelectedText` task variable into the context for
blank text selection.

Release Notes:

- N/A
2024-04-15 11:52:15 +03:00
Marshall Bowers
97c5cffbe3 Update contributing docs to point to extensions (#10537)
This PR updates the contributing docs to remove an outdated section
about extension support and instead point to the extension authoring
docs.

Release Notes:

- N/A
2024-04-14 19:31:19 -04:00
apricotbucket28
556ecd94c2 blade: Fix incorrect texture format (#10524)
Fixes image rendering
Closes https://github.com/zed-industries/zed/issues/10505

Before:

![image](https://github.com/zed-industries/zed/assets/71973804/3a903279-d631-4ca6-9f46-3065c7ed3073)


After:

![image](https://github.com/zed-industries/zed/assets/71973804/ab3a73e5-bf21-4df7-a9c1-a74bd1993a5b)


Release Notes:

- N/A
2024-04-14 11:46:31 -07:00
Mikayla Maki
3289188e0a linux: Simplify scrolling implementation (#10497)
This PR adjusts our scrolling implementation to delay the generation of
ScrollWheel events until we receive a complete frame.

Note that our implementation is still a bit off-spec, as we don't delay
any other kind of events. But it's been working so far on a variety of
compositors and the other events contain complete data; so I'll hold off
on that refactor for now.

Release Notes:

- N/A
2024-04-12 15:54:11 -07:00
Max Brunsfeld
5e4f707951 Change authors of lua extension 2024-04-12 15:15:40 -07:00
Conrad Irwin
5d7642d77d Update netrw bindings for preview panes (#10492)
Release Notes:

- N/A
2024-04-12 14:50:27 -06:00
Conrad Irwin
e64ecdc9ab Add missing block.copy() (#10496)
https://crates.io/crates/block implies this is necessary, and we're
still seeing segfaults in this method, so...

Release Notes:

- Fixed a panic when installing the CLI / registering for the zed://
protocol
2024-04-12 14:50:12 -06:00
Conrad Irwin
ba9c5929af Fix emojis when rendering with the system ui font (#10491)
Release Notes:

- N/A
2024-04-12 14:05:01 -06:00
Marshall Bowers
ad8dd1771a lua: Fix extension version (#10490)
This PR fixes the Lua extension version to be v0.0.1 instead of v0.1.0
for the initial release.

Release Notes:

- N/A
2024-04-12 15:38:26 -04:00
usr
cb6d0639db Windows: Fix crash when trying to copy nothing to clipboard (#10405)
Release Notes:

- N/A
2024-04-12 12:33:20 -07:00
Mikayla Maki
065f15e9a6 Use buffer font when rendering editor breadcrumbs and diagnostics (#10488)
Before:

<img width="592" alt="Screenshot 2024-04-12 at 12 00 00 PM"
src="https://github.com/zed-industries/zed/assets/2280405/3251743e-4f2c-4ca3-9bc5-88f37660f7b9">

After:

<img width="673" alt="Screenshot 2024-04-12 at 12 11 37 PM"
src="https://github.com/zed-industries/zed/assets/2280405/6a8ac597-261a-45d9-bf2a-a673b6f26b0e">


Release Notes:

- N/A
2024-04-12 12:29:00 -07:00
张小白
104558115f windows: Update WindowsDisplay::frequency() (#10476)
A subsequent update introduced the `HMONITOR` value to the
`WindowsDisplay` struct, eliminating the need for polling to retrieve
this value.

Release Notes:

- N/A
2024-04-12 12:19:49 -07:00
CharlesChen0823
4e6f24a841 Only emit resize event when size changed (#10419)
Currently, terminal will emit resize event every seconds, even if the
size not changed.
this PR fixed only emit resize event when size is changed.

Release Notes:

- N/A
2024-04-12 12:18:56 -07:00
Marshall Bowers
f3a78f613a Extract Vue extension (#10486)
This PR extracts Vue support into an extension and removes the built-in
C# support from Zed.

Release Notes:

- Removed built-in support for Vue, in favor of making it available as
an extension. The Vue extension will be suggested for download when you
open a `.vue` file.

---------

Co-authored-by: Max <max@zed.dev>
2024-04-12 14:39:27 -04:00
Yury Abykhodau
8bca9cea26 Fix Auto folded dirs performance issues (#8556)
Fixed auto folded dirs which caused significant performance issues #8476
(#7674)

Moved from iterating over snapshot entries to use `child_entries`
function from `worktree.rs` by making it public

@maxbrunsfeld 

Release Notes:

- Fixed a bug where project panel settings changes would not be applied
immediately.
- Added a `project_panel.auto_fold_dirs` setting which collapses the
nesting in the project panel when there is a chain of folders containing
a single folder.
<img width="288" alt="Screenshot 2024-04-12 at 11 10 58 AM"
src="https://github.com/zed-industries/zed/assets/2280405/efd61e75-026c-464d-ba4d-90db5f68bad3">

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-12 11:26:26 -07:00
Kirill Bulatov
28586060a1 Display more specific tasks above in the modal (#10485) 2024-04-12 20:19:11 +02:00
Kyle Kelley
49371b44cb Semantic Index (#10329)
This introduces semantic indexing in Zed based on chunking text from
files in the developer's workspace and creating vector embeddings using
an embedding model. As part of this, we've created an embeddings
provider trait that allows us to work with OpenAI, a local Ollama model,
or a Zed hosted embedding.

The semantic index is built by breaking down text for known
(programming) languages into manageable chunks that are smaller than the
max token size. Each chunk is then fed to a language model to create a
high dimensional vector which is then normalized to a unit vector to
allow fast comparison with other vectors with a simple dot product.
Alongside the vector, we store the path of the file and the range within
the document where the vector was sourced from.

Zed will soon grok contextual similarity across different text snippets,
allowing for natural language search beyond keyword matching. This is
being put together both for human-based search as well as providing
results to Large Language Models to allow them to refine how they help
developers.

Remaining todo:

* [x] Change `provider` to `model` within the zed hosted embeddings
database (as its currently a combo of the provider and the model in one
name)


Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Antonio <antonio@zed.dev>
2024-04-12 11:40:59 -06:00
Maxime Forveille
4b40e83b8b gpui: Fix window title special characters display on X11 (#9994)
Before:

![image](https://github.com/zed-industries/zed/assets/13511978/f12a144a-5c41-44e9-8422-aa73ea54fb9c)

After:

![image](https://github.com/zed-industries/zed/assets/13511978/45e9b701-77a8-4e63-9481-dab895a347f7)

Release Notes:

- Fixed window title special characters display on X11.

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-04-12 09:49:31 -07:00
Conrad Irwin
dffddaec4c Revert "Revert "language: Remove buffer fingerprinting (#9007)"" (#9671)
This reverts commit caed275fbf.

NOTE: this should not be merged until #9668 is on stable and the
`ZedVersion#can_collaborate` is updated to exclude all clients without
that change.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-04-12 18:40:35 +02:00
Marshall Bowers
a4d6c5da7c toml: Bump to v0.1.0 (#10482)
This PR bumps the TOML extension to v0.1.0.

This version of the extension has been updated to use v0.0.6 of the
`zed_extension_api`.

Release Notes:

- N/A
2024-04-12 12:39:43 -04:00
Hans
3ea17248c8 Adjust left movement when soft_wrap mode is used (#10464)
Release Notes:

- Added/Fixed #10350
2024-04-12 10:36:31 -06:00
Marshall Bowers
e0e1103228 zig: Bump to v0.1.0 (#10481)
This PR bumps the Zig extension to v0.1.0.

This version of the extension has been updated to use v0.0.6 of the
`zed_extension_api`.

It also adds support for treating `.zig.zon` files as Zig files
(#10012).

Release Notes:

- N/A
2024-04-12 12:30:29 -04:00
Marshall Bowers
65c9e7d3d1 php: Bump to v0.0.2 (#10480)
This PR bumps the PHP extension to v0.0.2.

This version of the PHP extension adds the `language_ids` mappings from
#10053.

Release Notes:

- N/A
2024-04-12 12:19:01 -04:00
Marshall Bowers
b5b872656b Extract Terraform extension (#10479)
This PR extracts Terraform support into an extension and removes the
built-in Terraform support from Zed.

Release Notes:

- Removed built-in support for Terraform, in favor of making it
available as
an extension. The Terraform extension will be suggested for download
when you
open a `.tf`, `.tfvars`, or `.hcl` file.
2024-04-12 11:49:49 -04:00
Bennet Bo Fenner
f4d9a97195 preview tabs: Support find all references (#10470)
`FindAllReferences` will now open a preview tab, jumping to a definition
will also open a preview tab.


https://github.com/zed-industries/zed/assets/53836821/fa3db1fd-ccb3-4559-b3d2-b1fe57f86481

Note: One thing I would like to improve here is also adding support for
reopening `FindAllReferences` using the navigation history. As of now
the navigation history is lacking support for reopening items other then
project files, which needs to be implemented first.

Release Notes:

- N/A
2024-04-12 17:22:12 +02:00
Bennet Bo Fenner
7b01a29f5a preview tabs: Fix tab selection getting out of sync (#10478)
There was an edge case where the project panel selection would not be
updated when opening a lot of tabs quickly using the preview tab
feature.
I spent way too long debugging this, thankfully @ConradIrwin spotted it
in like 5 minutes 🎉

Release Notes:

- N/A
2024-04-12 17:20:30 +02:00
张小白
04e89c4c51 Use workspace uuid (#10475)
Release Notes:

- N/A
2024-04-12 10:53:10 -04:00
Conrad Irwin
0ab5a524b0 Fix overlap (#10474)
Although I liked the symmetry of the count in the middle of the arrows,
it's
tricky to make the buttons not occlude the count on hover, so go back to
this arrangement.

Release Notes:

- N/A
2024-04-12 08:25:09 -06:00
Bennet Bo Fenner
cd5ddfe34b chat panel: Add timestamp in tooltip to edited message (#10444)
Hovering over the `(edited)` text inside a message displays a tooltip
with the timestamp of when the message was last edited:


![image](https://github.com/zed-industries/zed/assets/53836821/be6d68c2-7447-42bc-bd5e-7a9053b3c980)

---

Also removed the `fade_out` style for the `(edited)` text, as this was
causing tooltips to fade out as well:


![image](https://github.com/zed-industries/zed/assets/53836821/91d3cf6a-db58-4e1d-b257-663b2ce1aca4)

Instead it uses `theme().text_muted` now.


Release Notes:

- Hovering over an edited message now displays a tooltip revealing the
timestamp of the last edit.
2024-04-12 14:26:41 +02:00
Bennet Bo Fenner
0a4c3488dd Fix typo in README (#10471)
Fixes a typo in the README which I believe was accidentally committed
yesterday in #10459


Release Notes:

- N/A
2024-04-12 14:18:54 +02:00
Piotr Osiewicz
a1cbc23fee task: use full task label to distinguish a terminal (#10469)
Spotted by @SomeoneToIgnore, in #10468 I've used a shortened task label,
which might lead to collisions.

Release Notes:

- N/A
2024-04-12 13:25:46 +02:00
Piotr Osiewicz
298e9c9387 task: Allow Rerun action to override properties of task being reran (#10468)
For example:
```
"alt-t": [
    "task::Rerun",
     { "reevaluate_context": true, "allow_concurrent_runs": true }
],
```
Overriding `allow_concurrent_runs` to `true` by itself should terminate
current instance of the task, if there's any.

This PR also fixes task deduplication in terminal panel to use expanded
label and not the id, which depends on task context. It kinda aligns
with how task rerun worked prior to #10341 . That's omitted in the
release notes though, as it's not in Preview yet.

Release Notes:

- `Task::Rerun` action can now override `allow_concurrent_runs` and
`use_new_terminal` properties of the task that is being reran.
2024-04-12 12:44:50 +02:00
Thorsten Ball
6e1ba7e936 Allow hovering over tooltips in git blame sidebar (#10466)
This introduces a new API on `StatefulInteractiveElement` to create a
tooltip that can be hovered, scrolled inside, and clicked:
`.hoverable_tooltip`.

Right now we only use it in the `git blame` gutter, but the plan is to
use the new hover/click/scroll behavior in #10398 to introduce new
git-blame-tooltips.

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-04-12 11:47:32 +02:00
Thorsten Ball
bc0c2e0cae Extend Vim default keybindings (#10461)
This implements some of #10457.

Release notes:

- Added `g c c` and `g c` to Vim keybindings to toggle comments in
normal and visual mode respectively.
- Added `g ]` and `g [` to Vim keybindings to go to next and previous
diagnostic error.
- Changed `[ x` and `] x` (which select larger/smaller syntax node) in
Vim mode to also work in visual mode.
2024-04-12 08:05:38 +02:00
Mehmet Efe Akça
29a50573a9 Add git blame error reporting with notification (#10408)
<img width="1035" alt="Screenshot 2024-04-11 at 13 13 44"
src="https://github.com/zed-industries/zed/assets/13402668/cd0e96a0-41c6-4757-8840-97d15a75c511">

Release Notes:

- Added a notification to show possible `git blame` errors if it fails to run.

Caveats:
- ~git blame now executes in foreground
executor  (required since the Fut is !Send)~

TODOs:
- After a failed toggle, the app thinks the blame
is shown. This means toggling again will do nothing
instead of retrying. (Caused by editor.show_git_blame
being set to true before the git blame is generated)
- ~(Maybe) Trim error?~ Done

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-04-12 07:20:34 +02:00
Conrad Irwin
08786fa7bf Make BufferSearch less wide (#10459)
This also adds some "responsiveness" so that UI elements are hidden
before everything has to be occluded

Release Notes:

- Improved search UI. It now works in narrower panes, and avoids
scrolling the editor on open.

<img width="899" alt="Screenshot 2024-04-11 at 21 33 17"
src="https://github.com/zed-industries/zed/assets/94272/44b95d4f-08d6-4c40-a175-0e594402ca01">
<img width="508" alt="Screenshot 2024-04-11 at 21 33 45"
src="https://github.com/zed-industries/zed/assets/94272/baf4638d-427b-43e6-ad67-13d43f0f18a2">
<img width="361" alt="Screenshot 2024-04-11 at 21 34 00"
src="https://github.com/zed-industries/zed/assets/94272/ff60b561-2f77-49c0-9df7-e26227fe9225">
<img width="348" alt="Screenshot 2024-04-11 at 21 37 03"
src="https://github.com/zed-industries/zed/assets/94272/a2a700a2-ce99-41bd-bf47-9b14d7082b0e">
2024-04-11 23:07:29 -06:00
Hans
f2d61f3ea5 Add feature to display commands for vim mode (#10349)
Release Notes:

- Added the current operator stack to the Vim status bar at the bottom
of the editor. #4447

This commit introduces a new feature that displays the current partial
command in the vim mode, similar to the behavior in Vim plugin. This
helps users keep track of the commands they're entering.
2024-04-12 06:39:57 +02:00
Conrad Irwin
98533079e4 Use buffer font for search (#10455)
It's wierd to type code/regex in the UI font

Release Notes:

- Continue to use buffer font for search
2024-04-11 22:15:12 -06:00
Nate Butler
27ba165046 Organize Project Panel context menus (#10456)
This design polish PR brings the project panel context menu into better
alignment with other editors, better follows system patterns and
identifies focus shifting actions with the `…` indicator (like adding a
new folder to a project, which will open a modal window.)

## Before & After:

**Root level**

![CleanShot - 2024-04-11 at 22 40
53@2x](https://github.com/zed-industries/zed/assets/1714999/aa103d14-0747-4be9-acbf-1c3ed0542a15)

**Folder level**

![CleanShot - 2024-04-11 at 22 43
45@2x](https://github.com/zed-industries/zed/assets/1714999/180224f2-26d1-45bd-8f78-822f46068a6d)

**File level**

![CleanShot - 2024-04-11 at 22 44
56@2x](https://github.com/zed-industries/zed/assets/1714999/67edd0ae-bcb6-4920-a480-c4d50c6bccfa)

Note: That double divider in the after has been fixed 😅

---

Release Notes:

- Improved ordering and organization of context menus in the project
panel to bring them closer to those in similar applications.
2024-04-11 22:52:49 -04:00
Hans
32806b8320 vim: Don’t allow edits in the read-only state (#10404)
Fix #8423 

Release Notes:

- vim: Fixed vim-surround motions editing read-only buffer
(preview-only)
2024-04-11 18:19:49 -06:00
CharlesChen0823
3ab9700155 Windows: Add missing delete key (#10422)
Add miss delete key in windows platform.
Release Notes:

- N/A
2024-04-11 16:20:28 -07:00
Mikayla Maki
9d96ae6e78 Redo linux state again (#10452)
With the recent Linux rewrite, I attempted to simplify the number of
wrapper structs involved in the Linux code, following the macOS code as
an example. Unfortunately, I missed a vital component: pointers to the
platform state, held by platform data structures. As we hold all of the
platform data structures on Linux, this PR reintroduces a wrapper around
the internal state of both the platform and the window. This allows us
to close and drop windows correctly.

This PR also fixes a performance problem introduced by:
https://github.com/zed-industries/zed/pull/10343, where each configure
request would add a new frame callback quickly saturating the main
thread and slowing everything down.

Release Notes:
- N/A
2024-04-11 16:12:14 -07:00
Max Brunsfeld
8d7f5eab79 Extract Ocaml language support into an extension (#10450)
Release Notes:

- Extracted Ocaml language support into an extension

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 15:20:19 -07:00
Conrad Irwin
f6c85b28d5 WIP: remoting (#10085)
Release Notes:

- Added private alpha support for remote development. Please reach out to hi@zed.dev if you'd like to be part of shaping this feature.
2024-04-11 15:36:35 -06:00
Bennet Bo Fenner
ea4419076e Add preview tabs (#9125)
This PR implements the preview tabs feature from VSCode.
More details and thanks for the head start of the implementation here
#6782.

Here is what I have observed from using the vscode implementation ([x]
-> already implemented):
- [x] Single click on project file opens tab as preview
- [x] Double click on item in project panel opens tab as permanent
- [x] Double click on the tab makes it permanent
- [x] Navigating away from the tab makes the tab permanent and the new
tab is shown as preview (e.g. GoToReference)
- [x] Existing preview tab is reused when opening a new tab
- [x] Dragging tab to the same/another panel makes the tab permanent
- [x] Opening a tab from the file finder makes the tab permanent
- [x] Editing a preview tab will make the tab permanent
- [x] Using the space key in the project panel opens the tab as preview
- [x] Handle navigation history correctly (restore a preview tab as
preview as well)
- [x] Restore preview tabs after restarting
- [x] Support opening files from file finder in preview mode (vscode:
"Enable Preview From Quick Open")
 
I need to do some more testing of the vscode implementation, there might
be other behaviors/workflows which im not aware of that open an item as
preview/make them permanent.

Showcase:


https://github.com/zed-industries/zed/assets/53836821/9be16515-c740-4905-bea1-88871112ef86


TODOs
- [x] Provide `enable_preview_tabs` setting
- [x] Write some tests
- [x] How should we handle this in collaboration mode (have not tested
the behavior so far)
- [x] Keyboard driven usage (probably need workspace commands)
- [x] Register `TogglePreviewTab` only when setting enabled?
- [x] Render preview tabs in tab switcher as italic
- [x] Render preview tabs in image viewer as italic
- [x] Should this be enabled by default (it is the default behavior in
VSCode)?
- [x] Docs

Future improvements (out of scope for now):
- Support preview mode for find all references and possibly other
multibuffers (VSCode: "Enable Preview From Code Navigation")


Release Notes:

- Added preview tabs
([#4922](https://github.com/zed-industries/zed/issues/4922)).

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-11 23:09:12 +02:00
Kyle Kelley
edb1ea2433 Do not show image viewer for SVGs (#10435)
Absent some ability to toggle between viewing and editing a file, I
think it would be best to get a fix out quick for people to edit SVGs as
text files.

Release Notes:

- Fixed editing of SVG images by disabling it from the image viewer
([#10403](https://github.com/zed-industries/zed/issues/10403)).
2024-04-11 13:27:32 -07:00
Marshall Bowers
86aa352ad9 Remove leftover commented-out code (#10445)
This PR removes some commented-out code that was left over from #10430.

Release Notes:

- N/A
2024-04-11 16:08:27 -04:00
Max Brunsfeld
253aa28375 Extract Scheme and Racket language support into extensions (#10442)
Release Notes:

- Extracted Scheme and Racket language support into extensions.

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 12:45:46 -07:00
Piotr Osiewicz
165d6b9edb task: Fix variable substitution for free variables (#10434)
Fixes regression from https://github.com/zed-industries/zed/pull/10341
where it was not possible to use non-zed environmental variables (e.g.
$PATH) in task definitions.

No release note, as this didn't land on Preview yet.
Release Notes:

- N/A
2024-04-11 21:15:33 +02:00
Max Brunsfeld
0ac31302d3 Remove built-in Nix support (#10439)
Release Notes:

- Removed built-in Nix support, now that there is a Nix extension.

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 11:44:08 -07:00
Max Brunsfeld
176f440158 Extract lua language support into an extension (#10437)
Release Notes:

- Extracted lua language support into an extension, and improved Lua
highlighting and completion label styling.

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 11:32:10 -07:00
Sai Gokula Krishnan
c6028f6651 Fix Dart syntax highlighting issue (#8347)
Release Notes:
* Improved Dart syntax highlighting
([#8151](https://github.com/zed-industries/zed/pull/8347#8151))

Before:
<img width="1246" alt="Before"
src="https://github.com/zed-industries/zed/assets/25414681/d9143fe8-c474-40bb-afbd-56fef16edab3">

After:
<img width="1249" alt="After"
src="https://github.com/zed-industries/zed/assets/25414681/4b103c35-fb24-4693-8b9e-3dd494036c9f">


(Shameless plug, since I build the theme)
Theme: The Dark Side
2024-04-11 11:30:36 -07:00
Max Brunsfeld
c38f72d194 Extract GLSL language support into an extension (#10433)
Release Notes:

- Extracted GLSL language support into an extension.

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-11 13:52:10 -04:00
Max Brunsfeld
47f698d5a3 Validate content-length of downloaded extension tar gz files (#10430)
Release Notes:

- Fixed a bug where extension installation would appear to succeed even
if the download did not complete due to network interruptions
([#10330](https://github.com/zed-industries/zed/issues/10330)).

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 10:24:09 -07:00
Max Brunsfeld
bcd2ca6196 Extract Elm language into an extension (#10432)
Release Notes:

- Extracted Elm language support into an extension

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-11 10:23:49 -07:00
Bennet Bo Fenner
78d6beee80 Fix invisible chat icons (#10406)
As of #10393 some icons in the chat were invisible, looking at the icons
I noticed that the viewport was actually 800x800, I scaled that down to
16x16 and now they work fine again.

Also remove the `reply_arrow_left` icon because it is not used at all.

Thanks to @RemcoSmitsDev for noticing.

I don't have any expertise in svg's, so if something is off about the
svg markup reach out to me.

Release Notes:

- N/A
2024-04-11 18:36:58 +02:00
Marshall Bowers
2d21f6debf emmet: Bump to v0.0.2 (#10426)
This PR bumps the Emmet extension to v0.0.2.

This version of the Emmet extension adds Emmet support for PHP and ERB
files.

Release Notes:

- N/A
2024-04-11 11:56:25 -04:00
Nate Butler
837b7111b3 Update TextField (#10415)
This PR makes some simple updates to the TextField api and update it's
styling.

Release Notes:

- N/A
2024-04-11 10:03:36 -04:00
Piotr Osiewicz
ea165e134d gpui-macros: Hide autogenerated action types/functions (#10417)
I've found it a bit annoying that autogenerated code shows up in
completions when working on Zed, so I've moved them into an associated
function of a struct we're "implementing" Action on. That way, neither a
generated function nor a static show up in completions.

Note that this change only affects Zed codebase! I'm pushing it up right
after last Preview to give it some time on Nightly.
Before:

![image](https://github.com/zed-industries/zed/assets/24362066/7343201f-f05b-4342-a9f7-97f002d88a48)

![image](https://github.com/zed-industries/zed/assets/24362066/e67f9dcb-e090-40e0-873c-e51bd39e0c7e)

After:

![image](https://github.com/zed-industries/zed/assets/24362066/0704211a-73f5-4f12-8583-9e47f092e5b7)

![image](https://github.com/zed-industries/zed/assets/24362066/c6fa00f5-fd8f-4f06-8be7-b74acedccd7c)


Release Notes:

- N/A
2024-04-11 15:38:47 +02:00
Tatsuya Kyushima
15758c10bf docs: Fix installation command via Homebrew (#10416)
When I installed zed by running `brew install zed` following
`README.md`, [brimdata/zed](https://github.com/brimdata/zed) was
installed.
By running `brew install --cask zed`, zed was installed properly.
It seems like `--cask` option is needed.

### Environments:
- OS:
    - macOS 13.4
- Homebrew version: 
    ```sh
    Homebrew 4.2.17-40-gef1c54e
Homebrew/homebrew-core (git revision 0f61f2950ec; last commit
2024-04-03)
Homebrew/homebrew-cask (git revision 40c0a17ee0; last commit 2024-04-03)
    ```

Release Notes:

- N/A
2024-04-11 09:12:23 -04:00
Piotr Osiewicz
2f616fe8eb workspace: Add restore_on_startup setting that allows always opening an empty Zed instance (#10366)
Fixes #7694
The new setting accepts "last_workspace" (default) and "none" as
options.

In a follow-up PR I'll add a new option that re-launches all of the Zed
windows and not just the last one.

Release Notes:

- Added `restore_on_startup` option, accepting `last_workspace`
(default) and `none` options. With `none`, new Zed instances will not
restore workspaces that were open last.
2024-04-11 13:27:27 +02:00
Bennet Bo Fenner
fef0516f5b markdown preview: Allow toggling checkbox by click (#10364)
Release Notes:

- Added support for toggling a checkbox in markdown preview by clicking
on it (cmd+click)
([#5226](https://github.com/zed-industries/zed/issues/5226)).

---------

Co-authored-by: Remco Smits <62463826+RemcoSmitsDev@users.noreply.github.com>
2024-04-11 13:09:21 +02:00
Joseph T. Lyons
eb6f7c1240 Remove if-not-else patterns (#10402) 2024-04-11 03:48:06 -04:00
Remco Smits
36a87d0f5c Add allow to click on the reply preview to go to the message (#10357)
Release Notes:

- Added support for scrolling to the message you are replying to when
clicking on the reply preview
([#10028](https://github.com/zed-industries/zed/issues/10028)).

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2024-04-11 09:36:00 +02:00
Joseph T. Lyons
43c115a747 Use set literal notation 2024-04-11 00:43:56 -04:00
Joseph T. Lyons
859c5279c4 Allow arrow keys to be used in tab switcher (#10396)
In addition to `ctrl-tab` and `ctrl-shift-tab`, VS Code allows changing
the selection in the tab switcher via the up and down arrow keys.

Release Notes:

- Added bindings to allow `up` and `down` arrow keys to be used while
the tab switcher is open.
2024-04-11 00:11:48 -04:00
Marshall Bowers
13c14d9b96 Proxy Danger requests through a proxy service (#10395)
This PR updates Danger to proxy its requests to GitHub through a proxy
service.

## Motivation

Currently Danger is not able to run on PRs opened from forks of Zed.

This is due to GitHub Actions' security policies. Forks are not able to
see any of the repository secrets, and the built-in
`secrets.GITHUB_TOKEN` has its permissions
[restricted](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
to only reads when running on forks.

I asked around on the Danger repo, and some big projects
(DefinitelyTyped) are working around this by using a publicly-listed
(although slightly obfuscated) token:
https://github.com/danger/danger-js/issues/918#issuecomment-2048629487.

While this approach is _probably_ okay given the limited scope and
permissions of the GitHub token, I would still prefer a solution that
avoids disclosing the token at all.

## Explanation

I ended up writing a small proxy service, [Danger
Proxy](https://github.com/maxdeviant/danger-proxy), that can be used to
provide Danger with the ability to make authenticated GitHub requests,
but without disclosing the token.

From the README:

> Danger Proxy will:
>
> - Proxy all requests to `/github/*` to the GitHub API. The provided
GitHub API token will be used for authentication.
> - Restrict requests to the list of repositories specified in the
`ALLOWED_REPOS` environment variable.
> - Restrict requests to the subset of the GitHub API that Danger
requires.

I have an instance of this service deployed to
[danger-proxy.fly.dev](https://danger-proxy.fly.dev/).

Release Notes:

- N/A
2024-04-11 00:01:20 -04:00
Conrad Irwin
3b68665277 Update resvg to fix panic (#10393)
This bumps us up a *long* way on the resvg/usvg crate, to fix a panic

Release Notes:

- Fixed a panic when rendering certain malformed SVGs
2024-04-10 20:12:05 -06:00
Mehmet Efe Akça
339b29ef17 Add open vim keymap command (#9953)
Release Notes:

- Added a `vim: open default keymap` command to show the default Vim
keymap ([#8593](https://github.com/zed-industries/zed/issues/8593)).

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-10 19:03:13 -04:00
Kirill Bulatov
d1ad96782c Rework task modal (#10341)
New list (used tasks are above the separator line, sorted by the usage
recency), then all language tasks, then project-local and global tasks
are listed.
Note that there are two test tasks (for `test_name_1` and `test_name_2`
functions) that are created from the same task template:
<img width="563" alt="Screenshot 2024-04-10 at 01 00 46"
src="https://github.com/zed-industries/zed/assets/2690773/7455a82f-2af2-47bf-99bd-d9c5a36e64ab">

Tasks are deduplicated by labels, with the used tasks left in case of
the conflict with the new tasks from the template:
<img width="555" alt="Screenshot 2024-04-10 at 01 01 06"
src="https://github.com/zed-industries/zed/assets/2690773/8f5a249e-abec-46ef-a991-08c6d0348648">

Regular recent tasks can be now removed too:
<img width="565" alt="Screenshot 2024-04-10 at 01 00 55"
src="https://github.com/zed-industries/zed/assets/2690773/0976b8fe-b5d7-4d2a-953d-1d8b1f216192">

When the caret is in the place where no function symbol could be
retrieved, no cargo tests for function are listed in tasks:
<img width="556" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/df30feba-fe27-4645-8be9-02afc70f02da">


Part of https://github.com/zed-industries/zed/issues/10132
Reworks the task code to simplify it and enable proper task labels.

* removes `trait Task`, renames `Definition` into `TaskTemplate` and use
that instead of `Arc<dyn Task>` everywhere
* implement more generic `TaskId` generation that depends on the
`TaskContext` and `TaskTemplate`
* remove `TaskId` out of the template and only create it after
"resolving" the template into the `ResolvedTask`: this way, task
templates, task state (`TaskContext`) and task "result" (resolved state)
are clearly separated and are not mixed
* implement the logic for filtering out non-related language tasks and
tasks that have non-resolved Zed task variables
* rework Zed template-vs-resolved-task display in modal: now all reruns
and recently used tasks are resolved tasks with "fixed" context (unless
configured otherwise in the task json) that are always shown, and Zed
can add on top tasks with different context that are derived from the
same template as the used, resolved tasks
* sort the tasks list better, showing more specific and least recently
used tasks higher
* shows a separator between used and unused tasks, allow removing the
used tasks same as the oneshot ones
* remote the Oneshot task source as redundant: all oneshot tasks are now
stored in the inventory's history
* when reusing the tasks as query in the modal, paste the expanded task
label now, show trimmed resolved label in the modal
* adjusts Rust and Elixir task labels to be more descriptive and closer
to bash scripts

Release Notes:

- Improved task modal ordering, run and deletion capabilities
2024-04-11 02:02:04 +03:00
Jonathan Green
b0eda77d73 Add a few tests to cover other folder names (#10356)
Added additional tests to cover other folder names in regards to
[#10193](https://github.com/zed-industries/zed/issues/10193) and
[#9729](https://github.com/zed-industries/zed/issues/9729).

I had a similar issue but with folder names like '2.5' and '2.5_backup'.
I didn't see test coverage for those kinds of file names, so wanted to
add them.

![2024-04-10 09 07
03](https://github.com/zed-industries/zed/assets/127535196/eef26ce2-b0c7-4b1f-98ed-426ce1df0af2)

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-10 18:47:53 -04:00
Kirill Bulatov
fd3ee5a9d0 Use proper Workspace references when querying for tasks by name (#10383)
Closes https://github.com/zed-industries/zed/issues/10380

Release Notes:

- Fixed Zed panicking when running tasks via a keybinding
([10380](https://github.com/zed-industries/zed/issues/10380))
2024-04-11 00:42:17 +03:00
Marshall Bowers
8cbdd9e0fa Refactor workspace notifications to use explicit NotificationId type (#10342)
This PR reworks the way workspace notifications are identified to use a
new `NotificationId` type.

A `NotificationId` is bound to a given type that is used as a unique
identifier. Generally this will be a unit struct that can be used to
uniquely identify this notification.

A `NotificationId` can also accept an optional `ElementId` in order to
distinguish between different notifications of the same type.

This system avoids the issue we had previously of selecting `usize` IDs
somewhat arbitrarily and running the risk of having two independent
notifications collide (and thus interfere with each other).

This also fixes a bug where multiple suggestion notifications for the
same extension could be live at once

Fixes https://github.com/zed-industries/zed/issues/10320.

Release Notes:

- Fixed a bug where multiple extension suggestions for the same
extension could be shown at once
([#10320](https://github.com/zed-industries/zed/issues/10320)).

---------

Co-authored-by: Max <max@zed.dev>
2024-04-10 17:21:23 -04:00
Joel Selvaraj
322f68f3d6 linux: wayland: fix cursor set_icon (#10374)
Release Notes:

- Partially (Wayland implementation) Fixed
[#10124](https://github.com/zed-industries/zed/issues/10124)).

The recent refactor of the linux gpui implementation
(https://github.com/zed-industries/zed/pull/10227) broke the wayland
cursor update logic by setting the cursor icon as `None`. Fix it by
setting the `cursor_icon_name`.
2024-04-10 12:59:33 -07:00
Marshall Bowers
195f9d9b24 haskell: Bump to v0.1.0 (#10378)
This PR bumps the Haskell extension to v0.1.0.

This version of the Haskell extension brings improved labels for
symbols:

<img width="569" alt="Screenshot 2024-04-10 at 3 38 01 PM"
src="https://github.com/zed-industries/zed/assets/1486634/759fa52f-7b85-4395-abfd-070138201162">

Release Notes:

- N/A
2024-04-10 15:42:46 -04:00
Marshall Bowers
3a6e0bb9b6 gleam: Bump to v0.1.0 (#10376)
This PR bumps the Gleam extension to v0.1.0.

This version of the Gleam extension brings improved completion labels:

<img width="572" alt="Screenshot 2024-04-10 at 3 30 25 PM"
src="https://github.com/zed-industries/zed/assets/1486634/afca4563-c520-4f01-949f-2c8095769751">

Release Notes:

- N/A
2024-04-10 15:33:22 -04:00
张小白
fdddbfc179 Fix caret movement issue for some special characters (#10198)
Currently in Zed, certain characters require pressing the key twice to
move the caret through that character. For example: "❤️" and "y̆".

The reason for this is as follows:

Currently, Zed uses `chars` to distinguish different characters, and
calling `chars` on `y̆` will yield two `char` values: `y` and `\u{306}`,
and calling `chars` on `❤️` will yield two `char` values: `❤` and
`\u{fe0f}`.

Therefore, consider the following scenario (where ^ represents the
caret):

- what we see: ❤️ ^
- the actual buffer: ❤ \u{fe0f} ^

After pressing the left arrow key once:

- what we see: ❤️ ^
- the actual buffer: ❤ ^ \u{fe0f}

After pressing the left arrow key again:
- what we see: ^ ❤️
- the actual buffer: ^ ❤ \u{fe0f}

Thus, two left arrow key presses are needed to move the caret, and this
PR fixes this bug (or this is actually a feature?).

I have tried to keep the scope of code modifications as minimal as
possible. In this PR, Zed handles such characters as follows:

- what we see: ❤️ ^
- the actual buffer: ❤ \u{fe0f} ^

After pressing the left arrow key once:

- what we see: ^ ❤️
- the actual buffer: ^ ❤ \u{fe0f}

Or after pressing the delete key:

- what we see: ^
- the actual buffer: ^

Please note that currently, different platforms and software handle
these special characters differently, and even the same software may
handle these characters differently in different situations. For
example, in my testing on Chrome on macOS, GitHub treats `y̆` as a
single character, just like in this PR; however, in Rust Playground,
`y̆` is treated as two characters, and pressing the delete key does not
delete the entire `y̆` character, but instead deletes `\u{306}` to yield
the character `y`. And they both treat `❤️` as a single character,
pressing the delete key will delete the entire `❤️` character.

This PR is based on the principle of making changes with the smallest
impact on the code, and I think that deleting the entire character with
the delete key is more intuitive.

Release Notes:

- Fix caret movement issue for some special characters

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Thorsten <thorsten@zed.dev>
Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-10 13:01:25 -06:00
张小白
3648d79ddb Reduce the frequency writing window size and position information to the database (#10315)
Release Notes:

- N/A
2024-04-10 12:56:51 -06:00
Marshall Bowers
081e9b9a60 Fix panic when loading malformed Wasm files (#10370)
This PR fixes a potential panic that could occur when loading malformed
Wasm files.

We now use the `parse_wasm_extension_version` function that was
previously used just to extract the Zed extension API version from the
Wasm bytes as a pre-validation step. By parsing the entirety of the Wasm
file here instead of returning as soon as we find the version, the
invalid Wasm bytes are now surfaced as an `Err` instead of a panic.

We were able to replicate the panic using the following test:

```rs
#[gpui::test]
async fn test_bad_wasm(cx: &mut TestAppContext) {
    init_test(cx);

    let wasm_host = cx.update(|cx| {
        WasmHost::new(
            FakeFs::new(cx.background_executor().clone()),
            FakeHttpClient::with_200_response(),
            FakeNodeRuntime::new(),
            Arc::new(LanguageRegistry::test(cx.background_executor().clone())),
            PathBuf::from("/the/work/dir".to_string()),
            cx,
        )
    });

    let mut wasm_bytes = std::fs::read("/Users/maxdeviant/Library/Application Support/Zed/extensions/installed/dart/extension.wasm").unwrap();

    // This is the error message we were seeing in the stack trace:
    // range end index 267037 out of range for slice of length 253952

    dbg!(&wasm_bytes.len());

    // Truncate the bytes to the same point:
    wasm_bytes.truncate(253952);

    std::fs::write("/tmp/bad-extension.wasm", wasm_bytes.clone()).unwrap();

    let manifest = Arc::new(ExtensionManifest {
        id: "the-extension".into(),
        name: "The Extension".into(),
        version: "0.0.1".into(),
        schema_version: SchemaVersion(1),
        description: Default::default(),
        repository: Default::default(),
        authors: Default::default(),
        lib: LibManifestEntry {
            kind: None,
            version: None,
        },
        themes: Default::default(),
        languages: Default::default(),
        grammars: Default::default(),
        language_servers: Default::default(),
    });

    // 💥
    let result = wasm_host
        .load_extension(wasm_bytes, manifest, cx.executor())
        .await;
    dbg!(result.map(|_| ()));
```



Release Notes:

- Fixed a crash that could occur when loading malformed Wasm extensions
([#10352](https://github.com/zed-industries/zed/issues/10352)).

---------

Co-authored-by: Max <max@zed.dev>
2024-04-10 14:13:43 -04:00
Marshall Bowers
3cf93dfcf6 Revert "Update GITHUB_TOKEN environment variable for Danger (#10365)"
Danger doesn't appear to work with PRs from forks: https://github.com/danger/danger-js/issues/918

Will need to research this some more.

This reverts commit 53d0cc6146.
2024-04-10 13:29:54 -04:00
Marshall Bowers
53d0cc6146 Update GITHUB_TOKEN environment variable for Danger (#10365)
This PR updates the `GITHUB_TOKEN` environment variable that we use for
Danger so that it should (hopefully) be able to run for PRs of external
contributors.

Followed the instructions outlined
[here](https://danger.systems/js/guides/getting_started#setting-up-danger-to-run-on-your-ci).

Danger comments will now by left by the @zed-industries-bot:

<img width="951" alt="Screenshot 2024-04-10 at 1 07 15 PM"
src="https://github.com/zed-industries/zed/assets/1486634/d28cd537-9626-47df-8878-75a778824ef4">

Release Notes:

- N/A
2024-04-10 13:09:27 -04:00
Nate Butler
03d853d344 Introduce TextField by adding the ui_text_field crate (#10361)
There hasn't been a componentized way to create inputs or text fields
thus far due to the innate circular dependency between the `ui` and
`editor` crates. To bypass this issue we are introducing a new
`ui_text_field` crate to specifically handle this component.

`TextField` provides the ability to add stacked or inline labels, as
well as applies a standard visual style to inputs.

Example:

![CleanShot - 2024-04-10 at 11 22
13@2x](https://github.com/zed-industries/zed/assets/1714999/9bf5fc40-5024-4d01-9a8b-fb76f67d7e6e)

We'll continue to evolve this component in the near future and start
using it in the app once we've built out the needed functionality.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-10 11:53:25 -04:00
Joseph T. Lyons
d03f1c4cab v0.132.x dev 2024-04-10 11:45:05 -04:00
Conrad Irwin
fc10201ce2 Remove occasionally incorrect expectation (#10358)
This panic has occured a handful of times, I think it must be the case
that:

1. Item is dropped outside of an update loop
2. The next update is this one

In that case no flush effects will have called the release observers
yet, but we cannot upgrade the WeakModel because the ref count is 0



Release Notes:

- Fixed a (rare) panic while collaborating
2024-04-10 11:34:54 -04:00
Nathan Sobo
7abb63cfda Improve the ergonomics of creating local buffers (#10347)
This PR renames `language::Buffer::new` to `language::Buffer::local` and
simplifies its interface. Instead of taking a replica id (which should
always be 0 for the local case) and a `BufferId`, which was awkward and
verbose to construct, it simply takes text and a `cx`.

It uses the `cx` to derive a `BufferId` from the `EntityId` associated
with the `cx`, which should always be positive based on the following
analysis...

We convert the entity id to a u64 using this method on `EntityId`, which
is defined by macros in the `slotmap` crate:

```rust
    pub fn as_ffi(self) -> u64 {
        (u64::from(self.version.get()) << 32) | u64::from(self.idx)
    }
```

If you look at the type of `version` in `KeyData`, it is non-zero:

```rust
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyData {
    idx: u32,
    version: NonZeroU32,
}
```

This commit also adds `Context::reserve_model` and
`Context::insert_model` to determine a model's entity ID before it is
created, which we need in order to assign a `BufferId` in the background
when loading a buffer asynchronously.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-04-10 08:32:51 -06:00
Hans
664efef76b Remove line breaks when displaying file names in the project panel (#10231)
- Fixed #8603 

For the label title of the project panel, I find that there is no place
to use to get the title of the label to do some operations, it should be
safe to modify it, but I'm not sure how we need to modify the problem, I
can think of two scenarios:
1. Modify every place where you don't want multiple lines to appear
2. Make the label only display a single line (e.g. provide a new
parameter, or a new label type?)
2024-04-10 08:30:13 -06:00
Bennet Bo Fenner
26299fb8c9 chat panel: Fix new messages indicator appearing when message was deleted (#10278)
Release Notes:

Fixed an issue where the "New messages" indicator in the channel chat
would be shown even if the message was deleted
2024-04-10 12:21:45 +02:00
Piotr Osiewicz
39e0e26d1d Line numbers short mode (#10354)
Followup to #10327 
It can be enabled with the following setting:

"line_indicator_format": "short"

No release note, as the original change didn't go out to Preview yet.

/cc @bartekpacia @0x2CA

Release Notes:

- N/A
2024-04-10 12:08:07 +02:00
Kamal Ahmad
4151ba13a1 X11: Don't emit keypress events for modifiers (#10271)
This fixes certain shortcuts/motions on X11 like in vim mode "v i )",
where previously zed would interpret it as "v i SHIFT )" due to the x11
backend emitting key press events for modifier keys even though other
platforms like Wayland don't. This also adds support for
ModifiersChanged events to X11

Release Notes:

- Fixed vim motions like "v i )" not working on X11
([#10199](https://github.com/zed-industries/zed/issues/10199)).
2024-04-09 18:46:23 -07:00
CharlesChen0823
6ac343123d Windows: Fix render resource not being released (#10233)
Release Notes:
- N/A
2024-04-09 18:46:09 -07:00
张小白
c763c8c64b windows: Implement restart action (#10234)
Release Notes:

- N/A
2024-04-09 18:35:10 -07:00
Ezekiel Warren
bfd9bb8a7c Use shared text system on Linux and Windows (#10098)
closes #10129
closes #10269

Release Notes:

- N/A
2024-04-09 17:25:16 -07:00
Dzmitry Malyshau
8f69eac402 Update blade to a version that can run GLES for Zed (#10243)
Release Notes:
- N/A

Picks up https://github.com/kvark/blade/pull/105,
https://github.com/kvark/blade/pull/97, and more

Switches the presentation to be non-blocking, which will improve the
latency slightly.

Allows to start playing with GLES backend, e.g.
```bash
cd crates/gpui
RUSTFLAGS="--cfg gles" CARGO_TARGET_DIR=./target-gl cargo run --example hello_world
```

It doesn't currently render properly due to an issue that needs
investigation, see
https://github.com/kvark/blade/pull/105#issuecomment-2041006542
But at least it's a start

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-04-09 17:16:35 -07:00
Mikayla Maki
3fc08a0610 Fix a bug from the Linux rewrite where Wayland windows would not render properly (#10343)
Release Notes:

- N/A
2024-04-09 17:16:09 -07:00
Piotr Osiewicz
fff197b227 Show lines selected & selection quantity in status bar (#10327)
Fixes #4204 

![image](https://github.com/zed-industries/zed/assets/24362066/dac093e7-d8ba-4bf9-8936-748a93dbcecf)

![image](https://github.com/zed-industries/zed/assets/24362066/f836948c-a4ad-455c-a343-8bb3ce62d467)

![image](https://github.com/zed-industries/zed/assets/24362066/54310b2a-db00-4b87-9f96-a55a81be99ed)

![image](https://github.com/zed-industries/zed/assets/24362066/dd6ef78c-2bde-4808-856b-adaa28dd00d2)

Release Notes:

- Line number indicator now shows number of lines selected and a number
of selections.
2024-04-09 23:40:19 +02:00
Max Brunsfeld
6b320b9efe Fix cleanup of LSP request status messages (#10336)
This fixes a bug in https://github.com/zed-industries/zed/pull/9818,
where the status was not removed if the request failed. It also adds
replication of these new status messages to guests when collaborating.

Release Notes:

- Fixed an issue where the status of failed LSP actions was left in the
status bar

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-09 17:34:51 -04:00
Max Brunsfeld
a0ee29a806 Use first line comment prefix when toggling comments (#10335)
This fixed an issue introduced in
https://github.com/zed-industries/zed/pull/10126, where, when toggling
comments in a language with multiple line comment prefixes (e.g. Gleam,
Erlang) Zed would insert the *last* prefix instead of the first.

Release Notes:

- Fixed an issue where the `toggle comments` command inserted the wrong
line comment prefix in some languages (preview only).

Co-authored-by: Marshall <marshall@zed.dev>
2024-04-09 17:22:47 -04:00
Kirill Bulatov
0331fdebd3 Parse human readable module name for Rust tasks (#10337)
Part of https://github.com/zed-industries/zed/issues/10132 extracted out
of a bigger PR that refactors tasks and shows labels that look more like
commands (ergo needs better readable package names)

Release Notes:

- N/A
2024-04-10 00:18:09 +03:00
Kirill Bulatov
7dfc7184b1 Use task icons for all kinds of tasks (#10333)
<img width="565" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/cbb59154-1153-459a-978c-8c125e879d27">
<img width="562" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/d3f65a09-2f75-4db8-85c2-1abb591e609c">


Release Notes:

- Added more icons to task modal
2024-04-09 23:51:59 +03:00
Conrad Irwin
759c65d4bd Show errors when failing to create directories on startup (#10326)
Release Notes:

- Improved error reporting on startup
([#9036](https://github.com/zed-industries/zed/issues/9036)).
2024-04-09 13:26:16 -06:00
Thorsten Ball
0533923f91 Reduce memory usage to represent buffers by up to 50% (#10321)
This should help with some of the memory problems reported in
https://github.com/zed-industries/zed/issues/8436, especially the ones
related to large files (see:
https://github.com/zed-industries/zed/issues/8436#issuecomment2037442695),
by **reducing the memory required to represent a buffer in Zed by
~50%.**

### How?

Zed's memory consumption is dominated by the in-memory representation of
buffer contents.

On the lowest level, the buffer is represented as a
[Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) and that's
where the most memory is used. The layers above — buffer, syntax map,
fold map, display map, ... — basically use "no memory" compared to the
Rope.

Zed's `Rope` data structure is itself implemented as [a `SumTree` of
`Chunks`](8205c52d2b/crates/rope/src/rope.rs (L35-L38)).

An important constant at play here is `CHUNK_BASE`:

`CHUNK_BASE` is the maximum length of a single text `Chunk` in the
`SumTree` underlying the `Rope`. In other words: It determines into how
many pieces a given buffer is split up.

By changing `CHUNK_BASE` we can adjust the level of granularity
withwhich we index a given piece of text. Theoretical maximum is the
length of the text, theoretical minimum is 1. Sweet spot is somewhere
inbetween, where memory use and performance of write & read access are
optimal.

We started with `16` as the `CHUNK_BASE`, but that wasn't the result of
extensive benchmarks, more the first reasonable number that came to
mind.

### What

This changes `CHUNK_BASE` from `16` to `64`. That reduces the memory
usage, trading it in for slight reduction in performance in certain
benchmarks.

### Benchmarks

I added a benchmark suite for `Rope` to determine whether we'd regress
in performance as `CHUNK_BASE` goes up. I went from `16` to `32` and
then to `64`. While `32` increased performance and reduced memory usage,
`64` had one slight drop in performance, increases in other benchmarks
and substantial memory savings.

| `CHUNK_BASE` from `16` to `32` | `CHUNK_BASE` from `16` to `64` |
|-------------------|--------------------|
|
![chunk_base_16_to_32](https://github.com/zed-industries/zed/assets/1185253/fcf1f9c6-4f43-4e44-8ef5-29c1e5d8e2b9)
|
![chunk_base_16_to_64](https://github.com/zed-industries/zed/assets/1185253/d82a0478-eeef-43d0-9240-e0aa9df8d946)
|

### Real World Results

We tested this by loading a 138 MB `*.tex` file (parsed as plain text)
into Zed and measuring in `Instruments.app` the allocation.

#### standard allocator
Before, with `CHUNK_BASE: 16`, the memory usage was ~827MB after loading
the buffer.

| `CHUNK_BASE: 16` |
|---------------------|
|
![memory_consumption_chunk_base_16_std_alloc](https://github.com/zed-industries/zed/assets/1185253/c1e04c34-7d1a-49fa-bb3c-6ad10aec6e26)
|


After, with `CHUNK_BASE: 64`, the memory usage was ~396MB after loading
the buffer.

| `CHUNK_BASE: 64` |
|---------------------|
|
![memory_consumption_chunk_base_64_std_alloc](https://github.com/zed-industries/zed/assets/1185253/c728e134-1846-467f-b20f-114a582c7b5a)
|


#### `mimalloc`

`MiMalloc` by default and that seems to be pretty aggressive when it
comes to growing memory. Whereas the std allocator would go up to
~800mb, MiMalloc would jump straight to 1024MB.

I also can't get `MiMalloc` to work properly with `Instruments.app` (it
always shows 15MB of memory usage) so I had to use these `Activity
Monitor` screenshots:

| `CHUNK_BASE: 16` |
|---------------------|
|
![memory_consumption_chunk_base_16_mimalloc](https://github.com/zed-industries/zed/assets/1185253/1e6e05e9-80c2-4ec7-9b0e-8a6fa78836eb)
|

| `CHUNK_BASE: 64` |
|---------------------|
|
![memory_consumption_chunk_base_64_mimalloc](https://github.com/zed-industries/zed/assets/1185253/8a47e982-a675-4db0-b690-d60f1ff9acc8)
|

### Release Notes

Release Notes:

- Reduced memory usage for files by up to 50%.

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-04-09 18:07:53 +02:00
Marshall Bowers
b6857ca469 Use v0.0.6 of the zed_extension_api for extensions that need it (#10324)
This PR updates the extensions dependent on v0.0.6 of the
`zed_extension_api` crate to use the now-published version on crates.io
instead of a path dependency.

The impacted extensions are:

- `dart`
- `gleam`
- `haskell`
- `svelte`


Release Notes:

- N/A
2024-04-09 10:57:26 -04:00
Marshall Bowers
132b8aa5c7 Improve extension API documentation (#10322)
This PR adds some more documentation to symbols exported from the
`zed_extension_api` crate.

Release Notes:

- N/A
2024-04-09 10:38:29 -04:00
Nathan Sobo
414058379b Switch to the system UI font on macOS (#10317)
To reference the system font, use the special ".SystemUIFont" family
name.

/cc @PixelJanitor 

Release Notes:

- Switched to the system UI  font for user interface elements on macOS.

Co-authored-by: Antonio Scandurra <antonio@zed.dev>
2024-04-09 07:44:57 -06:00
Marshall Bowers
8205c52d2b Only apply host-side executable fix to binaries downloaded by the extension (#10318)
This PR makes it so our temporary host-side workaround for setting
certain language server binaries as executable only applies to binaries
that are downloaded by the extension.

Previously we would do this for any binary, including ones that could
have been sourced from the $PATH.

Release Notes:

- Fixed a file permissions issue when trying to use a Zig language
server (`zls`) present on the $PATH.
2024-04-09 09:13:36 -04:00
Piotr Osiewicz
4fb9f41e69 gpui/metal: Clamp max texture width/height to 16kB (#10314)
Fixed #10149
A user had Zed crash due to invalid font size in settings. It turned out
the width/height of glyphs does not pass validation in Metal texture
initialization with a large enough font size.

All modern Macs have a max texture width/height of 16kB (barring Apple
A8, used by iPhone 6 back in 2014, which uses 8kB). This commit clamps
texture size at 16kB. Note that while it fixes Zed crash, using a font
size that hits the limit is still pretty unusable - the users will still
have a pretty unusable editor, but at least it won't crash for them.



Release Notes:

- Fixed crashes with huge `buffer_font_size` values.
2024-04-09 14:09:09 +02:00
643 changed files with 36623 additions and 14688 deletions

View File

@@ -0,0 +1,49 @@
name: bump_patch_version
on:
workflow_dispatch:
inputs:
branch:
description: "Branch name to run on"
required: true
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ inputs.branch }}
cancel-in-progress: true
jobs:
bump_patch_version:
runs-on:
- self-hosted
- test
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.branch }}
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}
- name: Bump Patch Version
run: |
set -eux
channel=$(cat crates/zed/RELEASE_CHANNEL)
tag_suffix=""
case $channel in
stable)
;;
preview)
tag_suffix="-pre"
;;
*)
echo "this must be run on either of stable|preview release branches" >&2
exit 1
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag v${output}${tag_suffix}
git push origin HEAD v${output}${tag_suffix}

View File

@@ -173,6 +173,11 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v4
with:
# We need to fetch more than one commit so that `script/draft-release-notes`
# is able to diff between the current and previous tag.
#
# 25 was chosen arbitrarily.
fetch-depth: 25
clean: false
submodules: "recursive"
@@ -205,6 +210,9 @@ jobs:
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
mkdir -p target/
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
- name: Generate license file
run: script/generate-licenses
@@ -248,19 +256,17 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body: ""
body_file: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-deb:
name: Create a *.deb Linux bundle
bundle-linux:
name: Create a Linux bundle
runs-on: ubuntu-22.04 # keep the version fixed to avoid libc and dynamic linked library issues
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
@@ -304,28 +310,26 @@ jobs:
exit 1
fi
# TODO linux : Find a way to add licenses to the final bundle
# - name: Generate license file
# run: script/generate-licenses
- name: Generate license file
run: script/generate-licenses
- name: Create Linux *.deb bundle
- name: Create and upload Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload app bundle to workflow run if main branch or specific label
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.deb
path: target/release/*.deb
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
path: zed-*.tar.gz
# TODO linux : make it stable enough to be uploaded as a release
# - uses: softprops/action-gh-release@v1
# name: Upload app bundle to release
# if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
# with:
# draft: true
# prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
# files: target/release/Zed.dmg
# body: ""
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload app bundle to release
uses: softprops/action-gh-release@v1
if: ${{ env.RELEASE_CHANNEL == 'preview' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
files: target/release/zed-linux-x86_64.tar.gz
body: ""
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -32,4 +32,10 @@ jobs:
- name: Run Danger
run: pnpm run --dir script/danger danger ci
env:
GITHUB_TOKEN: ${{ github.token }}
# This GitHub token is not used, but the value needs to be here to prevent
# Danger from throwing an error.
GITHUB_TOKEN: "not_a_real_token"
# All requests are instead proxied through an instance of
# https://github.com/maxdeviant/danger-proxy that allows Danger to securely
# authenticate with GitHub while still being able to run on PRs from forks.
DANGER_GITHUB_API_BASE_URL: "https://danger-proxy.fly.dev/github"

View File

@@ -94,7 +94,7 @@ jobs:
run: script/upload-nightly macos
bundle-deb:
name: Create a *.deb Linux bundle
name: Create a Linux *.tar.gz bundle
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-22.04 # keep the version fixed to avoid libc and dynamic linked library issues
needs: tests
@@ -125,12 +125,11 @@ jobs:
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
# TODO linux : find a way to add licenses to the final bundle
# - name: Generate license file
# run: script/generate-licenses
- name: Generate license file
run: script/generate-licenses
- name: Create Linux *.deb bundle
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Zed Nightly
run: script/upload-nightly linux-deb
run: script/upload-nightly linux-targz

2
.gitignore vendored
View File

@@ -6,7 +6,7 @@
/plugins/bin
/script/node_modules
/crates/theme/schemas/theme.json
/crates/collab/.admins.json
/crates/collab/seed.json
/assets/*licenses.md
**/venv
.build

View File

@@ -21,5 +21,7 @@
"formatter": "prettier"
}
},
"formatter": "auto"
"formatter": "auto",
"remove_trailing_whitespace_on_save": true,
"ensure_final_newline_on_save": true
}

View File

@@ -0,0 +1,12 @@
[
{
"label": "clippy",
"command": "cargo",
"args": ["xtask", "clippy"]
},
{
"label": "assistant2",
"command": "cargo",
"args": ["run", "-p", "assistant2", "--example", "assistant_example"]
}
]

View File

@@ -2,8 +2,6 @@
Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor!
We want to avoid anyone spending time on a pull request that may not be accepted, so we suggest you discuss your ideas with the team and community before starting on major changes. Bug fixes, however, are almost always welcome.
All activity in Zed forums is subject to our [Code of Conduct](https://zed.dev/docs/code-of-conduct). Additionally, contributors must sign our [Contributor License Agreement](https://zed.dev/cla) before their contributions can be merged.
## Contribution ideas
@@ -13,7 +11,7 @@ If you're looking for ideas about what to work on, check out:
- Our [public roadmap](https://zed.dev/roadmap) contains a rough outline of our near-term priorities for Zed.
- Our [top-ranking issues](https://github.com/zed-industries/zed/issues/5393) based on votes by the community.
Outside of a handful of extremely popular languages and themes, we are generally not looking to extend Zed's language or theme support by directly building them into Zed. We really want to build a plugin system to handle making the editor extensible going forward. If you are passionate about shipping new languages or themes we suggest contributing to the extension system to help us get there faster.
For adding themes or support for a new language to Zed, check out our [extension docs](https://github.com/zed-industries/extensions/blob/main/AUTHORING_EXTENSIONS.md).
## Proposing changes

1182
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,8 @@ members = [
"crates/anthropic",
"crates/assets",
"crates/assistant",
"crates/assistant_tooling",
"crates/assistant2",
"crates/audio",
"crates/auto_update",
"crates/breadcrumbs",
@@ -38,6 +40,7 @@ members = [
"crates/google_ai",
"crates/gpui",
"crates/gpui_macros",
"crates/headless",
"crates/image_viewer",
"crates/install_cli",
"crates/journal",
@@ -66,12 +69,14 @@ members = [
"crates/refineable",
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/remote_projects",
"crates/rich_text",
"crates/rope",
"crates/rpc",
"crates/task",
"crates/tasks_ui",
"crates/search",
"crates/semantic_index",
"crates/semantic_version",
"crates/settings",
"crates/snippet",
@@ -90,6 +95,7 @@ members = [
"crates/telemetry_events",
"crates/time_format",
"crates/ui",
"crates/ui_text_field",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -103,17 +109,25 @@ members = [
"extensions/clojure",
"extensions/csharp",
"extensions/dart",
"extensions/deno",
"extensions/elixir",
"extensions/elm",
"extensions/emmet",
"extensions/erlang",
"extensions/gleam",
"extensions/glsl",
"extensions/haskell",
"extensions/html",
"extensions/lua",
"extensions/ocaml",
"extensions/php",
"extensions/prisma",
"extensions/purescript",
"extensions/svelte",
"extensions/terraform",
"extensions/toml",
"extensions/uiua",
"extensions/vue",
"extensions/zig",
"tooling/xtask",
@@ -127,6 +141,8 @@ ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
assistant2 = { path = "crates/assistant2" }
assistant_tooling = { path = "crates/assistant_tooling" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
base64 = "0.13"
@@ -161,6 +177,7 @@ go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui" }
gpui_macros = { path = "crates/gpui_macros" }
headless = { path = "crates/headless" }
install_cli = { path = "crates/install_cli" }
image_viewer = { path = "crates/image_viewer" }
journal = { path = "crates/journal" }
@@ -190,12 +207,14 @@ project_symbols = { path = "crates/project_symbols" }
quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
release_channel = { path = "crates/release_channel" }
remote_projects = { path = "crates/remote_projects" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" }
settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
@@ -214,6 +233,7 @@ theme_selector = { path = "crates/theme_selector" }
telemetry_events = { path = "crates/telemetry_events" }
time_format = { path = "crates/time_format" }
ui = { path = "crates/ui" }
ui_text_field = { path = "crates/ui_text_field" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
@@ -230,24 +250,27 @@ async-recursion = "1.0.0"
async-tar = "0.4.2"
async-trait = "0.1"
bitflags = "2.4.2"
# todo(linux): Remove these once https://github.com/kvark/blade/pull/107 is merged and we've upgraded our renderer
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "85981c0f4890a5fcd08da2a53cc4a0459247af44" }
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "85981c0f4890a5fcd08da2a53cc4a0459247af44" }
blade-rwh = { package = "raw-window-handle", version = "0.5" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
cap-std = "3.0"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
clickhouse = { version = "0.11.6" }
ctor = "0.2.6"
ctrlc = "3.4.4"
core-foundation = { version = "0.9.3" }
core-foundation-sys = "0.8.6"
derive_more = "0.99.17"
emojis = "0.6.1"
env_logger = "0.9"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.15", default-features = false }
git2 = { version = "0.18", default-features = false }
globset = "0.4"
heed = { git = "https://github.com/meilisearch/heed", rev = "036ac23f73a021894974b9adc815bc95b3e0482a", features = [
"read-txn-no-tls",
] }
hex = "0.4.3"
ignore = "0.4.22"
indoc = "1"
@@ -260,6 +283,8 @@ itertools = "0.11.0"
lazy_static = "1.4.0"
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
nanoid = "0.4"
nix = "0.28"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
@@ -311,37 +336,28 @@ tree-sitter-c = "0.20.1"
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 = "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" }
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" }
tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" }
tree-sitter-hcl = { git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "v1.1.0" }
rustc-demangle = "0.1.23"
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
tree-sitter-html = "0.19.0"
tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc", ref = "6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55" }
tree-sitter-jsdoc = { git = "https://github.com/tree-sitter/tree-sitter-jsdoc", rev = "6a6cf9e7341af32d8e2b2e24a37fbfebefc3dc55" }
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
tree-sitter-lua = "0.0.14"
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "7dd29f9616822e5fc259f5b4ae6c4ded9a71a132" }
tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "4abfdc1c7af2c6c77a370aee974627be1c285b3b" }
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
tree-sitter-python = "0.20.2"
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a" }
tree-sitter-regex = "0.20.0"
tree-sitter-ruby = "0.20.0"
tree-sitter-rust = "0.20.3"
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9" }
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42" }
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
unindent = "0.1.7"
unicase = "2.6"
unicode-segmentation = "1.10"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
uuid = { version = "1.1.2", features = ["v4", "v5"] }
wasmparser = "0.201"
wasm-encoder = "0.201"
wasmtime = { version = "19.0.0", default-features = false, features = [
@@ -360,10 +376,16 @@ sys-locale = "0.3.1"
version = "0.53.0"
features = [
"implement",
"Foundation_Numerics",
"Wdk_System_SystemServices",
"Win32_Globalization",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_Graphics_Imaging",
"Win32_Graphics_Imaging_D2D",
"Win32_Media",
"Win32_Security",
"Win32_Security_Credentials",
@@ -387,7 +409,7 @@ features = [
]
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f21c3b98c0749ac192da67a0d65dfe3eabc4a63" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7b4894ba2ae81b988846676f54c0988d4027ef4f" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }

View File

@@ -1,3 +1,3 @@
collab: RUST_LOG=${RUST_LOG:-warn,tower_http=info,collab=info} cargo run --package=collab serve
collab: RUST_LOG=${RUST_LOG:-info} cargo run --package=collab serve
livekit: livekit-server --dev
blob_store: ./script/run-local-minio

100
README.md
View File

@@ -1,49 +1,51 @@
# Zed
[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
## Installation
You can [download](https://zed.dev/download) Zed today for macOS (v10.15+).
Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
- Linux ([tracking issue](https://github.com/zed-industries/zed/issues/7015))
- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
For macOS users, you can also install Zed using [Homebrew](https://brew.sh/):
```sh
brew install zed
```
Alternatively, to install the Preview release:
```sh
brew tap homebrew/cask-versions
brew install zed-preview
```
## Developing Zed
- [Building Zed for macOS](./docs/src/developing_zed__building_zed_macos.md)
- [Building Zed for Linux](./docs/src/developing_zed__building_zed_linux.md)
- [Building Zed for Windows](./docs/src/developing_zed__building_zed_windows.md)
- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md)
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
## Licensing
License information for third party dependencies must be correctly provided for CI to pass.
We use [`cargo-about`](https://github.com/EmbarkStudios/cargo-about) to automatically comply with open source licenses. If CI is failing, check the following:
- Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml.
- Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`.
- Is `cargo-about` unable to find the license for a dependency? If so, add a clarification field at the end of `script/licenses/zed-licenses.toml`, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration).
# Zed
[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
## Installation
You can [download](https://zed.dev/download) Zed today for macOS (v10.15+).
Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
- Linux ([tracking issue](https://github.com/zed-industries/zed/issues/7015))
- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
For macOS users, you can also install Zed using [Homebrew](https://brew.sh/):
```sh
brew install --cask zed
```
Alternatively, to install the Preview release:
```sh
brew tap homebrew/cask-versions
brew install zed-preview
```
## Developing Zed
- [Building Zed for macOS](./docs/src/developing_zed__building_zed_macos.md)
- [Building Zed for Linux](./docs/src/developing_zed__building_zed_linux.md)
- [Building Zed for Windows](./docs/src/developing_zed__building_zed_windows.md)
- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md)
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
## Licensing
License information for third party dependencies must be correctly provided for CI to pass.
We use [`cargo-about`](https://github.com/EmbarkStudios/cargo-about) to automatically comply with open source licenses. If CI is failing, check the following:
- Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml.
- Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`.
- Is `cargo-about` unable to find the license for a dependency? If so, add a clarification field at the end of `script/licenses/zed-licenses.toml`, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration).

View File

@@ -1,6 +0,0 @@
User input begins on a line starting with /.
Don't apologize ever.
Never say "I apologize".
Use simple language and don't flatter the users.
Keep it short.
Risk being rude.

9
assets/icons/LICENSES Normal file
View File

@@ -0,0 +1,9 @@
Lucide License
ISC License
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-unfold-vertical"><path d="M12 22v-6"/><path d="M12 8V2"/><path d="M4 12H2"/><path d="M10 12H8"/><path d="M16 12h-2"/><path d="M22 12h-2"/><path d="m15 19-3 3-3-3"/><path d="m15 5-3-3-3 3"/></svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1791_43)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8473 4.79901C13.7531 4.63587 13.6232 4.4934 13.4803 4.4109L8.51958 1.54683C8.23382 1.38181 7.76613 1.38181 7.48037 1.54683L2.51961 4.4109C2.2338 4.57587 2 4.98089 2 5.31089V11.0391C2 11.2041 2.05864 11.3879 2.15283 11.551C2.24699 11.7141 2.37691 11.8566 2.51977 11.939L7.48053 14.8031C7.7663 14.9681 8.23398 14.9681 8.51974 14.8031L13.4805 11.939C13.6234 11.8565 13.7532 11.714 13.8473 11.5509C13.9415 11.3878 14 11.204 14 11.039V5.31085C14 5.14583 13.9415 4.96211 13.8473 4.79901ZM4 8.175C4 10.3806 5.79438 12.175 7.99998 12.175C9.42327 12.175 10.7506 11.4091 11.464 10.1761L9.73295 9.17441C9.37586 9.79162 8.71182 10.175 7.99998 10.175C6.89716 10.175 5.99999 9.27778 5.99999 8.175C5.99999 7.07218 6.89716 6.17501 7.99998 6.17501C8.71174 6.17501 9.37578 6.55838 9.73284 7.17548L11.4639 6.17375C10.7505 4.9409 9.42319 4.17502 7.99998 4.17502C5.79438 4.17502 4 5.9694 4 8.175Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_1791_43">
<rect width="11.9999" height="13.5039" fill="white" transform="translate(2 1.42307)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1791_60)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4803 4.4109C13.6232 4.4934 13.7531 4.63587 13.8473 4.79901C13.9415 4.96211 14 5.14583 14 5.31085V5.78958H12.8036V7.50745H11.0857V8.99892H12.8036V10.7168H14V11.039C14 11.204 13.9415 11.3878 13.8473 11.5509C13.7532 11.714 13.6234 11.8565 13.4805 11.939L8.51974 14.8031C8.23398 14.9681 7.7663 14.9681 7.48053 14.8031L2.51977 11.939C2.37691 11.8566 2.24699 11.7141 2.15283 11.551C2.05864 11.3879 2 11.2041 2 11.0391V5.31089C2 4.98089 2.2338 4.57587 2.51961 4.4109L7.48037 1.54683C7.76613 1.38181 8.23382 1.38181 8.51958 1.54683L13.4803 4.4109ZM7.99998 12.175C5.79438 12.175 4 10.3806 4 8.175C4 5.9694 5.79438 4.17502 7.99998 4.17502C9.42319 4.17502 10.7505 4.9409 11.4639 6.17375L9.73284 7.17548C9.37578 6.55838 8.71174 6.17501 7.99998 6.17501C6.89716 6.17501 5.99999 7.07218 5.99999 8.175C5.99999 9.27778 6.89716 10.175 7.99998 10.175C8.71182 10.175 9.37586 9.79162 9.73295 9.17441L11.464 10.1761C10.7506 11.4091 9.42327 12.175 7.99998 12.175Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_1791_60">
<rect width="11.9999" height="13.5039" fill="white" transform="translate(2 1.42307)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -19,11 +19,11 @@
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"cjs": "code",
"c": "c",
"cc": "cpp",
"cjs": "javascript",
"conf": "settings",
"cpp": "code",
"cpp": "cpp",
"css": "css",
"csv": "storage",
"cts": "typescript",
@@ -58,7 +58,8 @@
"gitmodules": "vcs",
"go": "go",
"graphql": "graphql",
"h": "code",
"h": "c",
"hpp": "cpp",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
@@ -77,7 +78,8 @@
"jp2": "image",
"jpeg": "image",
"jpg": "image",
"js": "code",
"js": "javascript",
"jsx": "react",
"json": "storage",
"jsonc": "storage",
"jxl": "image",
@@ -95,7 +97,7 @@
"mdx": "document",
"metadata": "code",
"mkv": "video",
"mjs": "code",
"mjs": "javascript",
"mka": "audio",
"ml": "ocaml",
"mli": "ocaml",
@@ -152,7 +154,7 @@
"ts": "typescript",
"tsv": "storage",
"ttf": "font",
"tsx": "code",
"tsx": "react",
"txt": "document",
"tcl": "tcl",
"vue": "vue",
@@ -161,6 +163,8 @@
"webp": "image",
"wma": "audio",
"wmv": "video",
"woff": "font",
"woff2": "font",
"wv": "audio",
"xls": "document",
"xlsx": "document",
@@ -193,6 +197,12 @@
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"c": {
"icon": "icons/file_icons/c.svg"
},
"cpp": {
"icon": "icons/file_icons/cpp.svg"
},
"css": {
"icon": "icons/file_icons/css.svg"
},
@@ -253,6 +263,9 @@
"java": {
"icon": "icons/file_icons/java.svg"
},
"javascript": {
"icon": "icons/file_icons/javascript.svg"
},
"kotlin": {
"icon": "icons/file_icons/kotlin.svg"
},
@@ -289,15 +302,18 @@
"python": {
"icon": "icons/file_icons/python.svg"
},
"r": {
"icon": "icons/file_icons/r.svg"
},
"react": {
"icon": "icons/file_icons/react.svg"
},
"ruby": {
"icon": "icons/file_icons/ruby.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"r": {
"icon": "icons/file_icons/r.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
@@ -327,7 +343,7 @@
},
"tcl": {
"icon": "icons/file_icons/tcl.svg"
},
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2C2.89543 2 2 2.89543 2 4V12C2 13.1046 2.89543 14 4 14H12C13.1046 14 14 13.1046 14 12V4C14 2.89543 13.1046 2 12 2H4ZM7.26917 6.80584H6.04784V10.8808C6.04784 11.0672 6.02025 11.2241 5.96508 11.3516C5.90991 11.4791 5.82906 11.5761 5.72253 11.6427C5.61789 11.7074 5.48948 11.7397 5.33729 11.7397C5.19271 11.7397 5.06715 11.7112 4.96062 11.6541C4.85599 11.5951 4.77323 11.5114 4.71235 11.403C4.65338 11.2926 4.62199 11.1604 4.61819 11.0063H3.38829C3.38639 11.3944 3.46914 11.7169 3.63655 11.9737C3.80396 12.2286 4.03035 12.4189 4.31571 12.5444C4.60297 12.6681 4.92257 12.7299 5.27451 12.7299C5.67021 12.7299 6.0174 12.6547 6.31607 12.5045C6.61475 12.3542 6.84779 12.1402 7.0152 11.8624C7.18452 11.5847 7.26917 11.2574 7.26917 10.8808V6.80584ZM11.1672 7.95013C11.3403 8.07759 11.4383 8.25641 11.4611 8.4866H12.6453C12.6396 8.13846 12.5464 7.83218 12.3657 7.56775C12.185 7.30331 11.9319 7.0969 11.6066 6.94852C11.2832 6.80013 10.9046 6.72594 10.4709 6.72594C10.0448 6.72594 9.66429 6.80013 9.32947 6.94852C8.99464 7.0969 8.73116 7.30331 8.53902 7.56775C8.34878 7.83218 8.25461 8.14132 8.25652 8.49516C8.25461 8.92701 8.39634 9.27039 8.6817 9.52531C8.96706 9.78023 9.3561 9.96762 9.84882 10.0875L10.4852 10.2473C10.6982 10.2986 10.878 10.3557 11.0245 10.4185C11.1729 10.4813 11.2851 10.5574 11.3612 10.6468C11.4392 10.7362 11.4782 10.8465 11.4782 10.9778C11.4782 11.1186 11.4354 11.2432 11.3498 11.3516C11.2642 11.46 11.1434 11.5447 10.9874 11.6056C10.8333 11.6665 10.6516 11.6969 10.4424 11.6969C10.2293 11.6969 10.0381 11.6646 9.86879 11.5999C9.70138 11.5333 9.56727 11.4353 9.46644 11.306C9.36751 11.1747 9.31139 11.0111 9.29808 10.8151H8.10242C8.11193 11.2356 8.21371 11.5885 8.40776 11.8738C8.6037 12.1573 8.87574 12.3713 9.22388 12.5159C9.57392 12.6605 9.98484 12.7327 10.4566 12.7327C10.9322 12.7327 11.3384 12.6614 11.6751 12.5187C12.0137 12.3741 12.2725 12.1715 12.4513 11.9109C12.632 11.6484 12.7233 11.3383 12.7252 10.9806C12.7233 10.7371 12.6786 10.5212 12.5911 10.3329C12.5055 10.1445 12.3847 9.98093 12.2287 9.84206C12.0727 9.70319 11.8882 9.58619 11.6751 9.49107C11.4621 9.39595 11.2281 9.31985 10.9731 9.26278L10.4481 9.13722C10.3206 9.10869 10.2008 9.07444 10.0885 9.03449C9.97628 8.99264 9.87736 8.94413 9.79175 8.88896C9.70614 8.83189 9.63861 8.76435 9.58914 8.68635C9.54158 8.60836 9.51971 8.51704 9.52351 8.41241C9.52351 8.28685 9.55966 8.17461 9.63195 8.07569C9.70614 7.97676 9.81267 7.89971 9.95155 7.84455C10.0904 7.78747 10.2607 7.75894 10.4623 7.75894C10.7591 7.75894 10.9941 7.82267 11.1672 7.95013Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99752 9.14577C8.62961 9.14577 9.14201 8.63336 9.14201 8.00127C9.14201 7.36919 8.62961 6.85678 7.99752 6.85678C7.36543 6.85678 6.85303 7.36919 6.85303 8.00127C6.85303 8.63336 7.36543 9.14577 7.99752 9.14577Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.37507 12.5467C5.35849 12.5371 5.26876 12.4764 5.21215 12.1996C5.15576 11.924 5.15423 11.5219 5.24075 11.0018C5.25336 10.926 5.2677 10.8485 5.28376 10.7695C5.61535 10.8289 5.96088 10.8775 6.31735 10.9146C6.52765 11.2048 6.74254 11.4797 6.9598 11.7371C6.8994 11.7906 6.83947 11.8417 6.7801 11.8906C6.37292 12.2255 6.02397 12.4252 5.75706 12.5142C5.48905 12.6036 5.39166 12.5562 5.37507 12.5467ZM4.63463 8.00002C4.48846 7.67278 4.35781 7.34921 4.24347 7.03232C4.16699 7.05793 4.09271 7.08426 4.0207 7.11126C3.52701 7.29639 3.17959 7.49875 2.96906 7.6854C2.75767 7.87282 2.75 7.98085 2.75 8C2.75 8.01916 2.75767 8.12719 2.96906 8.31461C3.17959 8.50126 3.52701 8.70361 4.0207 8.88875C4.09271 8.91575 4.167 8.94208 4.24348 8.96769C4.35782 8.65081 4.48846 8.32725 4.63463 8.00002ZM3.49402 5.70677C3.6016 5.66643 3.71247 5.6276 3.82645 5.59035C3.80173 5.47305 3.77992 5.35765 3.76108 5.24434C3.65732 4.62055 3.63633 4.01923 3.74257 3.49981C3.84858 2.98153 4.10358 2.45543 4.62507 2.15435C5.14656 1.85326 5.72968 1.89548 6.23152 2.06281C6.73448 2.23051 7.24474 2.54935 7.73308 2.95111C7.82179 3.02409 7.91084 3.10068 8.00007 3.18075C8.0893 3.10068 8.17835 3.02409 8.26706 2.9511C8.7554 2.54935 9.26566 2.23051 9.76862 2.06281C10.2705 1.89548 10.8536 1.85326 11.3751 2.15435C11.8966 2.45543 12.1516 2.98153 12.2576 3.49981C12.3638 4.01923 12.3428 4.62055 12.2391 5.24434C12.2202 5.35766 12.1984 5.47308 12.1737 5.59039C12.2876 5.62763 12.3984 5.66644 12.506 5.70677C13.0981 5.9288 13.6293 6.21129 14.026 6.56301C14.4219 6.91396 14.75 7.39783 14.75 8C14.75 8.60218 14.4219 9.08605 14.026 9.437C13.6293 9.78872 13.0981 10.0712 12.506 10.2932C12.3984 10.3336 12.2876 10.3724 12.1737 10.4096C12.1984 10.5269 12.2202 10.6424 12.2391 10.7557C12.3428 11.3795 12.3638 11.9808 12.2576 12.5002C12.1516 13.0185 11.8966 13.5446 11.3751 13.8457C10.8536 14.1468 10.2705 14.1046 9.76862 13.9372C9.26566 13.7695 8.7554 13.4507 8.26706 13.0489C8.17835 12.976 8.08931 12.8994 8.00007 12.8193C7.91084 12.8994 7.82179 12.976 7.73308 13.0489C7.24474 13.4507 6.73448 13.7695 6.23152 13.9372C5.72968 14.1046 5.14657 14.1468 4.62507 13.8457C4.10358 13.5446 3.84859 13.0185 3.74257 12.5002C3.63633 11.9808 3.65732 11.3795 3.76108 10.7557C3.77993 10.6424 3.80174 10.527 3.82646 10.4097C3.71248 10.3724 3.6016 10.3336 3.49402 10.2932C2.90192 10.0712 2.37066 9.78872 1.97395 9.437C1.57812 9.08605 1.25 8.60218 1.25 8C1.25 7.39783 1.57812 6.91396 1.97395 6.56301C2.37066 6.21129 2.90192 5.9288 3.49402 5.70677ZM9.22005 11.8906C9.16067 11.8417 9.10075 11.7906 9.04034 11.7371C9.25761 11.4797 9.4725 11.2048 9.68281 10.9145C10.0393 10.8775 10.3848 10.8289 10.7164 10.7695C10.7324 10.8485 10.7468 10.926 10.7594 11.0018C10.8459 11.5219 10.8444 11.924 10.788 12.1996C10.7314 12.4764 10.6417 12.5371 10.6251 12.5467C10.6085 12.5562 10.5111 12.6036 10.2431 12.5142C9.97617 12.4252 9.62722 12.2255 9.22005 11.8906ZM6.31737 5.08544C6.52766 4.79525 6.74254 4.52034 6.9598 4.26289C6.89939 4.20948 6.83947 4.15832 6.7801 4.10948C6.37292 3.77449 6.02397 3.57479 5.75706 3.4858C5.48905 3.39644 5.39165 3.44381 5.37507 3.45339C5.35849 3.46296 5.26876 3.52362 5.21214 3.80041C5.15576 4.07605 5.15423 4.4781 5.24075 4.99822C5.25336 5.07405 5.2677 5.15152 5.28375 5.23053C5.61535 5.1711 5.96089 5.12247 6.31737 5.08544ZM9.04034 4.26289C9.2576 4.52034 9.47249 4.79526 9.68278 5.08546C10.0393 5.12248 10.3848 5.17112 10.7164 5.23055C10.7324 5.15154 10.7468 5.07406 10.7594 4.99822C10.8459 4.4781 10.8444 4.07605 10.788 3.80041C10.7314 3.52362 10.6417 3.46296 10.6251 3.45339C10.6085 3.44381 10.5111 3.39644 10.2431 3.4858C9.97617 3.57479 9.62722 3.77449 9.22005 4.10947C9.16067 4.15832 9.10074 4.20948 9.04034 4.26289ZM11.3655 8.00002C11.5117 8.32723 11.6423 8.65078 11.7566 8.96765C11.8331 8.94205 11.9073 8.91574 11.9793 8.88875C12.473 8.70361 12.8204 8.50126 13.0309 8.31461C13.2423 8.12719 13.25 8.01916 13.25 8C13.25 7.98085 13.2423 7.87282 13.0309 7.6854C12.8204 7.49875 12.473 7.29639 11.9793 7.11126C11.9073 7.08427 11.8331 7.05796 11.7567 7.03237C11.6423 7.34924 11.5117 7.6728 11.3655 8.00002ZM7.99752 10.1458C9.18189 10.1458 10.142 9.18565 10.142 8.00127C10.142 6.8169 9.18189 5.85678 7.99752 5.85678C6.81315 5.85678 5.85303 6.8169 5.85303 8.00127C5.85303 9.18564 6.81315 10.1458 7.99752 10.1458Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail-open"><path d="M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0l8 6Z"/><path d="m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mail-open"><path d="M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0l8 6Z"/><path d="m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10"/></svg>

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 391 B

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 10L21 7L17 3L14 6M18 10L8 20H4V16L14 6M18 10L14 6" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m12 6.668 2-2L11.332 2l-2 2M12 6.668l-6.668 6.664H2.668v-2.664L9.332 4M12 6.668 9.332 4" stroke="black" stroke-width="1" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 379 B

After

Width:  |  Height:  |  Size: 239 B

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

@@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 0.875C5.49797 0.875 3.875 2.49797 3.875 4.5C3.875 6.15288 4.98124 7.54738 6.49373 7.98351C5.2997 8.12901 4.27557 8.55134 3.50407 9.31167C2.52216 10.2794 2.02502 11.72 2.02502 13.5999C2.02502 13.8623 2.23769 14.0749 2.50002 14.0749C2.76236 14.0749 2.97502 13.8623 2.97502 13.5999C2.97502 11.8799 3.42786 10.7206 4.17091 9.9883C4.91536 9.25463 6.02674 8.87499 7.49995 8.87499C8.97317 8.87499 10.0846 9.25463 10.8291 9.98831C11.5721 10.7206 12.025 11.8799 12.025 13.5999C12.025 13.8623 12.2376 14.0749 12.5 14.0749C12.7623 14.075 12.975 13.8623 12.975 13.6C12.975 11.72 12.4778 10.2794 11.4959 9.31166C10.7244 8.55135 9.70025 8.12903 8.50625 7.98352C10.0187 7.5474 11.125 6.15289 11.125 4.5C11.125 2.49797 9.50203 0.875 7.5 0.875ZM4.825 4.5C4.825 3.02264 6.02264 1.825 7.5 1.825C8.97736 1.825 10.175 3.02264 10.175 4.5C10.175 5.97736 8.97736 7.175 7.5 7.175C6.02264 7.175 4.825 5.97736 4.825 4.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-git-pull-request-arrow"><circle cx="5" cy="6" r="3"/><path d="M5 9v12"/><circle cx="19" cy="18" r="3"/><path d="m15 9-3-3 3-3"/><path d="M12 6h5a2 2 0 0 1 2 2v7"/></svg>

After

Width:  |  Height:  |  Size: 372 B

4
assets/icons/regex.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">
<circle cx="4" cy="11" r="1" fill="#787D87"/>
<path d="M9 2.5V5M9 5V7.5M9 5H11.5M9 5H6.5M9 5L10.6667 3.33333M9 5L7.33333 6.6667M9 5L10.6667 6.6667M9 5L7.33333 3.33333" stroke="#787D87" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 17V15.8C20 14.1198 20 13.2798 19.673 12.638C19.3854 12.0735 18.9265 11.6146 18.362 11.327C17.7202 11 16.8802 11 15.2 11H4M4 11L8 7M4 11L8 15" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 468 B

View File

@@ -1,56 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg
width="800px"
height="800px"
viewBox="0 0 24 24"
fill="none"
version="1.1"
id="svg1"
sodipodi:docname="reply-svgrepo-com.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="0.39996789"
inkscape:cx="435.03492"
inkscape:cy="417.53351"
inkscape:window-width="1440"
inkscape:window-height="847"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<g
id="SVGRepo_bgCarrier"
stroke-width="0" />
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round" />
<g
id="SVGRepo_iconCarrier"
transform="matrix(-1,0,0,1,24.001548,0)">
<path
d="M 20,17 V 15.8 C 20,14.1198 20,13.2798 19.673,12.638 19.3854,12.0735 18.9265,11.6146 18.362,11.327 17.7202,11 16.8802,11 15.2,11 H 4 m 0,0 4,-4 m -4,4 4,4"
stroke="#000000"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
id="path1" />
</g>
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.668 11.332v-.797c0-1.12 0-1.683.219-2.11.191-.374.496-.683.87-.874.43-.219.99-.219 2.11-.219h7.469m0 0-2.668-2.664m2.668 2.664L10.668 10" stroke="black" stroke-width="1.33334" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 296 B

View File

@@ -1,5 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<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="#11181C" stroke-width="1.25" stroke-linecap="round"/>
<path d="M2.19366 3.84943C2.19188 4.26418 2.32864 4.59864 2.60673 4.84707C2.88052 5.09166 3.25136 5.26933 3.71609 5.3824C3.71616 5.38242 3.71623 5.38243 3.7163 5.38245L4.30919 5.53134L4.30919 5.53134L4.30965 5.53145C4.50649 5.57891 4.67124 5.63133 4.80447 5.68843L4.80469 5.68852C4.93838 5.74508 5.03564 5.81206 5.10001 5.8877L5.10001 5.8877L5.10041 5.88816C5.16432 5.96142 5.19716 6.05222 5.19716 6.16389C5.19716 6.28412 5.1609 6.38933 5.0882 6.48141C5.01496 6.57418 4.91031 6.64838 4.77141 6.70259L4.77121 6.70266C4.63472 6.75659 4.47185 6.7843 4.28146 6.7843C4.08801 6.7843 3.91607 6.75496 3.76491 6.69726C3.61654 6.6382 3.49924 6.55209 3.41132 6.43942C3.3502 6.35821 3.30747 6.26204 3.28375 6.14992C3.26238 6.04888 3.1772 5.96225 3.06518 5.96225H2.26366C2.14682 5.96225 2.04842 6.05919 2.0592 6.18012C2.08842 6.50802 2.1826 6.79102 2.34331 7.02735L2.34352 7.02767C2.53217 7.30057 2.79377 7.50587 3.12633 7.64399L3.12642 7.64402C3.46009 7.78185 3.84993 7.85 4.29476 7.85C4.74293 7.85 5.12859 7.7828 5.45023 7.64651L5.45036 7.64646C5.77328 7.50857 6.02259 7.31417 6.19551 7.06217C6.37037 6.80817 6.4579 6.50901 6.45972 6.16682L6.45972 6.16616C6.4579 5.9333 6.41513 5.72482 6.33012 5.54178C6.2474 5.35987 6.13061 5.20175 5.98007 5.06773C5.83038 4.93448 5.65389 4.82273 5.4511 4.7322C5.24919 4.64206 5.02795 4.57016 4.78757 4.51632L4.29841 4.39935L4.29841 4.39934L4.29771 4.39919C4.18081 4.37301 4.07116 4.34168 3.9687 4.30523C3.86715 4.26734 3.77847 4.22375 3.70232 4.17471C3.62796 4.12508 3.57037 4.06717 3.52849 4.00124C3.49012 3.93815 3.47157 3.86312 3.47481 3.77407L3.47484 3.77407V3.77225C3.47484 3.66563 3.50527 3.57146 3.56612 3.48808C3.6287 3.40475 3.71977 3.33801 3.84235 3.28931L3.84235 3.28932L3.84289 3.28909C3.96465 3.23906 4.1165 3.21304 4.30008 3.21304C4.57006 3.21304 4.77746 3.27105 4.92754 3.38154C5.04235 3.46608 5.11838 3.57594 5.15673 3.71259C5.18352 3.80802 5.26636 3.89142 5.37611 3.89142H6.17259C6.28852 3.89142 6.38806 3.7953 6.37515 3.67382C6.34686 3.4077 6.26051 3.16831 6.1158 2.95658C5.94159 2.70169 5.6982 2.50368 5.38762 2.36201L5.36687 2.4075M2.19366 3.84943C2.19187 3.51004 2.28242 3.21139 2.46644 2.9556L2.46658 2.9554C2.65148 2.70093 2.90447 2.50326 3.22368 2.36179C3.54316 2.2202 3.90494 2.15 4.30807 2.15C4.71809 2.15 5.07841 2.22014 5.38773 2.36206L5.36687 2.4075M2.19366 3.84943C2.19366 3.84951 2.19366 3.84959 2.19366 3.84967L2.24366 3.8494L2.19366 3.84918C2.19366 3.84926 2.19366 3.84935 2.19366 3.84943ZM5.36687 2.4075C5.06537 2.26917 4.71244 2.2 4.30807 2.2C3.91079 2.2 3.55608 2.26917 3.24394 2.4075C2.93179 2.54584 2.68616 2.73827 2.50703 2.9848L3.82389 3.24285L3.82389 3.24285C3.95336 3.18964 4.11209 3.16304 4.30008 3.16304C4.57676 3.16304 4.79579 3.22245 4.95718 3.34128C5.08094 3.43239 5.1635 3.55166 5.20487 3.69908C5.2271 3.77827 5.29386 3.84142 5.37611 3.84142H6.17259C6.26198 3.84142 6.33488 3.76799 6.32543 3.6791C6.29797 3.4208 6.21433 3.18936 6.07452 2.9848C5.90603 2.73827 5.67015 2.54584 5.36687 2.4075ZM4.78958 6.74917C4.64593 6.80592 4.47655 6.8343 4.28146 6.8343C4.08283 6.8343 3.90458 6.80415 3.74674 6.74384C3.59067 6.68177 3.46563 6.59043 3.37163 6.46983L4.78958 6.74917ZM4.78958 6.74917C4.93502 6.69241 5.04764 6.61349 5.12745 6.5124M4.78958 6.74917L5.12745 6.5124M5.12745 6.5124C5.20726 6.4113 5.24716 6.29514 5.24716 6.16389M5.12745 6.5124L5.24716 6.16389M5.24716 6.16389C5.24716 6.04152 5.2108 5.93865 5.13809 5.85529L5.24716 6.16389Z" fill="#687076" stroke="#687076" stroke-width="0.1"/>
<path d="M9.5 7V9.5M9.5 9.5V12M9.5 9.5H12M9.5 9.5H7M9.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="#687076" stroke-width="1.25" stroke-linecap="round"/>
<path d="M2.19368 3.84945C2.1919 4.2642 2.32866 4.59866 2.60675 4.84709C2.88054 5.09168 3.25138 5.26935 3.71611 5.38242L4.30921 5.53136C4.50605 5.57882 4.67126 5.63135 4.80449 5.68845C4.93818 5.74501 5.03566 5.81208 5.10003 5.88772C5.16394 5.96098 5.19718 6.05224 5.19718 6.16391C5.19718 6.28414 5.16092 6.38935 5.08822 6.48143C5.01498 6.5742 4.91033 6.6484 4.77143 6.70261C4.63494 6.75654 4.47187 6.78432 4.28148 6.78432C4.08803 6.78432 3.91609 6.75498 3.76493 6.69728C3.61656 6.63822 3.49926 6.55211 3.41134 6.43944C3.35022 6.35823 3.30749 6.26206 3.28377 6.14994C3.2624 6.0489 3.17722 5.96227 3.0652 5.96227H2.26368C2.14684 5.96227 2.04844 6.05921 2.05922 6.18014C2.08844 6.50804 2.18262 6.79104 2.34333 7.02737C2.53198 7.30027 2.79379 7.50589 3.12635 7.64401C3.46002 7.78184 3.84995 7.85002 4.29478 7.85002C4.74295 7.85002 5.12861 7.78282 5.45025 7.64653C5.77317 7.50864 6.02261 7.31419 6.19553 7.06219C6.37039 6.80819 6.45792 6.50903 6.45974 6.16684C6.45792 5.93398 6.41515 5.72484 6.33014 5.5418C6.24742 5.35989 6.13063 5.20177 5.98009 5.06775C5.8304 4.9345 5.65391 4.82275 5.45112 4.73222C5.24921 4.64208 5.02797 4.57018 4.78759 4.51634L4.29843 4.39937C4.18153 4.37319 4.07118 4.3417 3.96872 4.30525C3.86717 4.26736 3.77849 4.22377 3.70234 4.17473C3.62798 4.1251 3.57039 4.06719 3.52851 4.00126C3.49014 3.93817 3.47159 3.86314 3.47483 3.77409L3.47486 3.77227C3.47486 3.66565 3.50529 3.57148 3.56614 3.4881C3.62872 3.40477 3.71979 3.33803 3.84237 3.28933C3.96413 3.2393 4.11652 3.21306 4.3001 3.21306C4.57008 3.21306 4.77748 3.27107 4.92756 3.38156C5.04237 3.4661 5.1184 3.57596 5.15675 3.71261C5.18354 3.80804 5.26638 3.89144 5.37613 3.89144H6.17261C6.28854 3.89144 6.38808 3.79532 6.37517 3.67384C6.34688 3.40772 6.26053 3.16833 6.11582 2.9566C5.94161 2.70171 5.69822 2.5037 5.38764 2.36203L5.36689 2.40752M2.19368 3.84945C2.19189 3.51006 2.28244 3.21141 2.46646 2.95562C2.65136 2.70115 2.90449 2.50328 3.2237 2.36181C3.54318 2.22022 3.90496 2.15002 4.30809 2.15002C4.71811 2.15002 5.07832 2.22011 5.38764 2.36203L5.36689 2.40752M4.7896 6.74919C4.93504 6.69243 5.04766 6.61351 5.12747 6.51242ZM4.7896 6.74919L5.12747 6.51242ZM5.12747 6.51242C5.20728 6.41132 5.24718 6.29516 5.24718 6.16391ZM5.12747 6.51242L5.24718 6.16391ZM5.24718 6.16391C5.24718 6.04154 5.21082 5.93867 5.13811 5.85531L5.24718 6.16391Z" fill="#687076"/>
<path d="M2.19368 3.84945C2.1919 4.2642 2.32866 4.59866 2.60675 4.84709C2.88054 5.09168 3.25138 5.26935 3.71611 5.38242L4.30921 5.53136C4.50605 5.57882 4.67126 5.63135 4.80449 5.68845C4.93818 5.74501 5.03566 5.81208 5.10003 5.88772C5.16394 5.96098 5.19718 6.05224 5.19718 6.16391C5.19718 6.28414 5.16092 6.38935 5.08822 6.48143C5.01498 6.5742 4.91033 6.6484 4.77143 6.70261C4.63494 6.75654 4.47187 6.78432 4.28148 6.78432C4.08803 6.78432 3.91609 6.75498 3.76493 6.69728C3.61656 6.63822 3.49926 6.55211 3.41134 6.43944C3.35022 6.35823 3.30749 6.26206 3.28377 6.14994C3.2624 6.0489 3.17722 5.96227 3.0652 5.96227H2.26368C2.14684 5.96227 2.04844 6.05921 2.05922 6.18014C2.08844 6.50804 2.18262 6.79104 2.34333 7.02737C2.53198 7.30027 2.79379 7.50589 3.12635 7.64401C3.46002 7.78184 3.84995 7.85002 4.29478 7.85002C4.74295 7.85002 5.12861 7.78282 5.45025 7.64653C5.77317 7.50864 6.02261 7.31419 6.19553 7.06219C6.37039 6.80819 6.45792 6.50903 6.45974 6.16684C6.45792 5.93398 6.41515 5.72484 6.33014 5.5418C6.24742 5.35989 6.13063 5.20177 5.98009 5.06775C5.8304 4.9345 5.65391 4.82275 5.45112 4.73222C5.24921 4.64208 5.02797 4.57018 4.78759 4.51634L4.29843 4.39937C4.18153 4.37319 4.07118 4.3417 3.96872 4.30525C3.86717 4.26736 3.77849 4.22377 3.70234 4.17473C3.62798 4.1251 3.57039 4.06719 3.52851 4.00126C3.49014 3.93817 3.47159 3.86314 3.47483 3.77409L3.47486 3.77227C3.47486 3.66565 3.50529 3.57148 3.56614 3.4881C3.62872 3.40477 3.71979 3.33803 3.84237 3.28933C3.96413 3.2393 4.11652 3.21306 4.3001 3.21306C4.57008 3.21306 4.77748 3.27107 4.92756 3.38156C5.04237 3.4661 5.1184 3.57596 5.15675 3.71261C5.18354 3.80804 5.26638 3.89144 5.37613 3.89144H6.17261C6.28854 3.89144 6.38808 3.79532 6.37517 3.67384C6.34688 3.40772 6.26053 3.16833 6.11582 2.9566C5.94161 2.70171 5.69822 2.5037 5.38764 2.36203M2.19368 3.84945C2.19189 3.51006 2.28244 3.21141 2.46646 2.95562C2.65136 2.70115 2.90449 2.50328 3.2237 2.36181C3.54318 2.22022 3.90496 2.15002 4.30809 2.15002C4.71811 2.15002 5.07832 2.22011 5.38764 2.36203M2.19368 3.84945L2.24368 3.84942M5.38764 2.36203L5.36689 2.40752M5.36689 2.40752C5.06539 2.26919 4.71246 2.20002 4.30809 2.20002C3.91081 2.20002 3.5561 2.26919 3.24396 2.40752C2.93181 2.54586 2.68618 2.73829 2.50705 2.98482M5.36689 2.40752C5.67017 2.54586 5.90605 2.73829 6.07454 2.98482C6.21435 3.18938 6.29799 3.42082 6.32545 3.67912C6.3349 3.76801 6.262 3.84144 6.17261 3.84144H5.37613C5.29388 3.84144 5.22712 3.77829 5.20489 3.6991C5.16352 3.55168 5.08096 3.43241 4.9572 3.3413C4.79581 3.22247 4.57678 3.16306 4.3001 3.16306M4.7896 6.74919C4.64595 6.80594 4.47657 6.83432 4.28148 6.83432C4.08285 6.83432 3.9046 6.80417 3.74676 6.74386C3.59069 6.68179 3.46565 6.59045 3.37165 6.46985M4.7896 6.74919C4.93504 6.69243 5.04766 6.61351 5.12747 6.51242M4.7896 6.74919L5.12747 6.51242M5.12747 6.51242C5.20728 6.41132 5.24718 6.29516 5.24718 6.16391M5.12747 6.51242L5.24718 6.16391M5.24718 6.16391C5.24718 6.04154 5.21082 5.93867 5.13811 5.85531L5.24718 6.16391Z" stroke="#687076" stroke-width="0.1"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

16
assets/icons/server.svg Normal file
View File

@@ -0,0 +1,16 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="20" height="8" x="2" y="2" rx="2" ry="2" />
<rect width="20" height="8" x="2" y="14" rx="2" ry="2" />
<line x1="6" x2="6.01" y1="6" y2="6" />
<line x1="6" x2="6.01" y1="18" y2="18" />
</svg>

After

Width:  |  Height:  |  Size: 413 B

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

@@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash-2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>

After

Width:  |  Height:  |  Size: 410 B

View File

@@ -3,4 +3,3 @@
<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>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -212,7 +212,6 @@
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"alt-tab": "search::CycleMode",
"ctrl-f": "search::FocusSearch",
"ctrl-h": "search::ToggleReplace"
}
@@ -235,11 +234,10 @@
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode",
"ctrl-shift-f": "search::FocusSearch",
"ctrl-shift-h": "search::ToggleReplace",
"alt-ctrl-g": "search::ActivateRegexMode",
"alt-ctrl-x": "search::ActivateTextMode"
"alt-ctrl-g": "search::ToggleRegex",
"alt-ctrl-x": "search::ToggleRegex"
}
},
{
@@ -260,10 +258,9 @@
"context": "ProjectSearchView",
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode",
"ctrl-shift-h": "search::ToggleReplace",
"alt-ctrl-g": "search::ActivateRegexMode",
"alt-ctrl-x": "search::ActivateTextMode"
"alt-ctrl-g": "search::ToggleRegex",
"alt-ctrl-x": "search::ToggleRegex"
}
},
{
@@ -283,10 +280,10 @@
"alt-enter": "search::SelectAllMatches",
"alt-c": "search::ToggleCaseSensitive",
"alt-w": "search::ToggleWholeWord",
"alt-r": "search::CycleMode",
"alt-r": "search::ToggleRegex",
"alt-ctrl-f": "project_search::ToggleFilters",
"ctrl-alt-shift-r": "search::ActivateRegexMode",
"ctrl-alt-shift-x": "search::ActivateTextMode"
"ctrl-alt-shift-r": "search::ToggleRegex",
"ctrl-alt-shift-x": "search::ToggleRegex"
}
},
// Bindings from VS Code
@@ -300,13 +297,8 @@
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
"ctrl-alt-shift-up": [
"editor::DuplicateLine",
{
"move_upwards": true
}
],
"ctrl-alt-shift-down": "editor::DuplicateLine",
"ctrl-alt-shift-up": "editor::DuplicateLineUp",
"ctrl-alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
@@ -530,6 +522,7 @@
"context": "Editor && mode == full",
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
@@ -556,8 +549,8 @@
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": "project_panel::Delete",
"delete": "project_panel::Delete",
"backspace": "project_panel::Trash",
"delete": "project_panel::Trash",
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": true }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": true }],
"alt-ctrl-r": "project_panel::RevealInFinder",
@@ -595,12 +588,6 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "ChatPanel > MessageEditor",
"bindings": {
"escape": "chat_panel::CloseReplyPreview"
}
},
{
"context": "FileFinder",
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
@@ -608,6 +595,8 @@
{
"context": "TabSwitcher",
"bindings": {
"ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext",
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}

View File

@@ -209,7 +209,15 @@
}
},
{
"context": "AssistantPanel",
"context": "AssistantChat > Editor", // Used in the assistant2 crate
"bindings": {
"enter": ["assistant2::Submit", "Simple"],
"cmd-enter": ["assistant2::Submit", "Codebase"],
"escape": "assistant2::Cancel"
}
},
{
"context": "AssistantPanel", // Used in the assistant crate, which we're replacing
"bindings": {
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch"
@@ -233,7 +241,6 @@
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"alt-tab": "search::CycleMode",
"cmd-f": "search::FocusSearch",
"cmd-alt-f": "search::ToggleReplace"
}
@@ -256,11 +263,10 @@
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode",
"cmd-shift-f": "search::FocusSearch",
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ActivateRegexMode",
"alt-cmd-x": "search::ActivateTextMode"
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex"
}
},
{
@@ -281,10 +287,9 @@
"context": "ProjectSearchView",
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode",
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ActivateRegexMode",
"alt-cmd-x": "search::ActivateTextMode"
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex"
}
},
{
@@ -306,10 +311,9 @@
"alt-enter": "search::SelectAllMatches",
"alt-cmd-c": "search::ToggleCaseSensitive",
"alt-cmd-w": "search::ToggleWholeWord",
"alt-tab": "search::CycleMode",
"alt-cmd-f": "project_search::ToggleFilters",
"alt-cmd-g": "search::ActivateRegexMode",
"alt-cmd-x": "search::ActivateTextMode"
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex"
}
},
// Bindings from VS Code
@@ -545,6 +549,7 @@
"context": "Editor && mode == full",
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
@@ -571,8 +576,8 @@
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": "project_panel::Delete",
"delete": "project_panel::Delete",
"backspace": "project_panel::Trash",
"delete": "project_panel::Trash",
"cmd-backspace": ["project_panel::Delete", { "skip_prompt": true }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": true }],
"alt-cmd-r": "project_panel::RevealInFinder",
@@ -617,6 +622,8 @@
{
"context": "TabSwitcher",
"bindings": {
"ctrl-up": "menu::SelectPrev",
"ctrl-down": "menu::SelectNext",
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}

View File

@@ -17,7 +17,11 @@
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"cmd-q": "storybook::Quit"
"cmd-q": "storybook::Quit",
"backspace": "editor::Backspace",
"delete": "editor::Delete",
"left": "editor::MoveLeft",
"right": "editor::MoveRight"
}
}
]

View File

@@ -234,6 +234,8 @@
"displayLines": true
}
],
"g ]": "editor::GoToDiagnostic",
"g [": "editor::GoToPrevDiagnostic",
"shift-h": "vim::WindowTop",
"shift-m": "vim::WindowMiddle",
"shift-l": "vim::WindowBottom",
@@ -367,6 +369,15 @@
"< <": "vim::Outdent",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
}
},
{
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
"bindings": {
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
}
@@ -424,6 +435,12 @@
]
}
},
{
"context": "Editor && vim_operator == ys",
"bindings": {
"s": "vim::CurrentLine"
}
},
{
"context": "Editor && VimObject",
"bindings": {
@@ -532,6 +549,18 @@
]
}
},
{
"context": "Editor && vim_mode == normal",
"bindings": {
"g c c": "editor::ToggleComments"
}
},
{
"context": "Editor && vim_mode == visual",
"bindings": {
"g c": "editor::ToggleComments"
}
},
{
"context": "Editor && vim_mode == insert",
"bindings": {
@@ -590,18 +619,22 @@
"%": "project_panel::NewFile",
"/": "project_panel::NewSearchInDirectory",
"d": "project_panel::NewDirectory",
"enter": "project_panel::Open",
"enter": "project_panel::OpenPermanent",
"escape": "project_panel::ToggleFocus",
"h": "project_panel::CollapseSelectedEntry",
"j": "menu::SelectNext",
"k": "menu::SelectPrev",
"l": "project_panel::ExpandSelectedEntry",
"o": "project_panel::Open",
"o": "project_panel::OpenPermanent",
"shift-d": "project_panel::Delete",
"shift-r": "project_panel::Rename",
"t": "project_panel::Open",
"v": "project_panel::Open",
"x": "project_panel::RevealInFinder"
"t": "project_panel::OpenPermanent",
"v": "project_panel::OpenPermanent",
"p": "project_panel::Open",
"x": "project_panel::RevealInFinder",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent"
}
}
]

View File

@@ -36,7 +36,7 @@
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
"ui_font_family": "Zed Sans",
"ui_font_family": ".SystemUIFont",
// The OpenType features to enable for text in the UI
"ui_font_features": {
// Disable ligatures:
@@ -47,17 +47,30 @@
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
// Centered layout related settings.
"centered_layout": {
// The relative width of the left padding of the central pane from the
// workspace when the centered layout is used.
"left_padding": 0.2,
// The relative width of the right padding of the central pane from the
// workspace when the centered layout is used.
"right_padding": 0.2
},
// The key to use for adding multiple cursors
// Currently "alt" or "cmd_or_ctrl" (also aliased as
// "cmd" and "ctrl") are supported.
"multi_cursor_modifier": "alt",
// Whether to enable vim modes and key bindings
// Whether to enable vim modes and key bindings.
"vim_mode": false,
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
"hover_popover_enabled": true,
// Whether to confirm before quitting Zed.
"confirm_quit": false,
// Whether to restore last closed project when fresh Zed instance is opened.
"restore_on_startup": "last_workspace",
// Size of the drop target in the editor.
"drop_target_size": 0.2,
// Whether the cursor blinks in the editor.
"cursor_blink": true,
// Whether to pop the completions menu while typing in an editor without
@@ -90,8 +103,9 @@
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
// Whether to automatically type closing characters for you. For example,
// when you type (, Zed will automatically add a closing ) at the correct position.
// Whether to automatically add matching closing characters when typing
// opening parenthesis, bracket, brace, single or double quote characters.
// For example, when you type (, Zed will add a closing ) at the correct position.
"use_autoclose": true,
// Controls how the editor handles the autoclosed characters.
// When set to `false`(default), skipping over and auto-removing of the closing characters
@@ -141,12 +155,14 @@
// 4. Never show the scrollbar:
// "never"
"show": "auto",
// Whether to show cursor positions in the scrollbar.
"cursors": true,
// Whether to show git diff indicators in the scrollbar.
"git_diff": true,
// Whether to show selections in the scrollbar.
"selections": true,
// Whether to show symbols selections in the scrollbar.
"symbols_selections": true,
// Whether to show buffer search results in the scrollbar.
"search_results": true,
// Whether to show selected symbol occurrences in the scrollbar.
"selected_symbol": true,
// Whether to show diagnostic indicators in the scrollbar.
"diagnostics": true
},
@@ -169,6 +185,9 @@
},
// The number of lines to keep above/below the cursor when scrolling.
"vertical_scroll_margin": 3,
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
"relative_line_numbers": false,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
@@ -197,6 +216,8 @@
"scroll_debounce_ms": 50
},
"project_panel": {
// Whether to show the project panel button in the status bar
"button": true,
// Default width of the project panel.
"default_width": 240,
// Where to dock the project panel. Can be 'left' or 'right'.
@@ -212,7 +233,10 @@
// Whether to reveal it in the project panel automatically,
// when a corresponding project entry becomes active.
// Gitignored entries are never auto revealed.
"auto_reveal_entries": true
"auto_reveal_entries": true,
/// Whether to fold directories automatically
/// when a directory has only one directory inside.
"auto_fold_dirs": false
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
@@ -272,6 +296,10 @@
"show_call_status_icon": true,
// Whether to use language servers to provide code intelligence.
"enable_language_server": true,
// The list of language servers to use (or disable) for all languages.
//
// This is typically customized on a per-language basis.
"language_servers": ["..."],
// When to automatically save edited buffers. This setting can
// take four values.
//
@@ -296,6 +324,18 @@
// Position of the close button on the editor tabs.
"close_position": "right"
},
// Settings related to preview tabs.
"preview_tabs": {
// Whether preview tabs should be enabled.
// Preview tabs allow you to open files in preview mode, where they close automatically
// when you switch to another file unless you explicitly pin them.
// This is useful for quickly viewing files without cluttering your workspace.
"enabled": true,
// Whether to open tabs in preview mode when selected from the file finder.
"enable_preview_from_file_finder": false,
// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
"enable_preview_from_code_navigation": false
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,
@@ -375,7 +415,15 @@
// "git_gutter": "tracked_files"
// 2. Hide the gutter
// "git_gutter": "hide"
"git_gutter": "tracked_files"
"git_gutter": "tracked_files",
// Control whether the git blame information is shown inline,
// in the currently focused line.
"inline_blame": {
"enabled": true
// Sets a delay after which the inline blame information is shown.
// Delay is restarted with every cursor movement.
// "delay_ms": 600
}
},
"copilot": {
// The set of glob patterns for which copilot should be disabled
@@ -464,6 +512,8 @@
// Whether or not selecting text in the terminal will automatically
// copy to the system clipboard.
"copy_on_select": false,
// Whether to show the terminal button in the status bar
"button": true,
// Any key-value pairs added to this list will be added to the terminal's
// environment. Use `:` to separate multiple values.
"env": {
@@ -509,31 +559,6 @@
// Existing terminals will not pick up this change until they are recreated.
// "max_scroll_history_lines": 10000,
},
// Settings specific to our elixir integration
"elixir": {
// Change the LSP zed uses for elixir.
// Note that changing this setting requires a restart of Zed
// to take effect.
//
// May take 3 values:
// 1. Use the standard ElixirLS, this is the default
// "lsp": "elixir_ls"
// 2. Use the experimental NextLs
// "lsp": "next_ls",
// 3. Use a language server installed locally on your machine:
// "lsp": {
// "local": {
// "path": "~/next-ls/bin/start",
// "arguments": ["--stdio"]
// }
// },
//
"lsp": "elixir_ls"
},
// Settings specific to our deno integration
"deno": {
"enable": false
},
"code_actions_on_format": {},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
@@ -548,6 +573,13 @@
// }
//
"file_types": {},
// The extensions that Zed should automatically install on startup.
//
// If you don't want any of these extensions, add this field to your settings
// and change the value to `false`.
"auto_install_extensions": {
"html": true
},
// Different settings for specific languages.
"languages": {
"C++": {
@@ -626,5 +658,12 @@
"task": {
// Whether to show task status indicator in the status bar. Default: true
"show_status_indicator": true
}
},
// Whether to show full labels in line indicator or short ones
//
// Values:
// - `short`: "2 s, 15 l, 32 c"
// - `long`: "2 selections, 15 lines, 32 characters"
// Default: long
"line_indicator_format": "long"
}

View File

@@ -99,6 +99,7 @@ impl ActivityIndicator {
Box::new(
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),
None,
cx,
);
}

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "AGPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/anthropic.rs"
@@ -17,6 +20,3 @@ util.workspace = true
[dev-dependencies]
tokio.workspace = true
[lints]
workspace = true

View File

@@ -5,6 +5,9 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/assets.rs"
[lints]
workspace = true

View File

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

View File

@@ -128,6 +128,8 @@ impl LanguageModelRequestMessage {
Role::System => proto::LanguageModelRole::LanguageModelSystem,
} as i32,
content: self.content.clone(),
tool_calls: Vec::new(),
tool_call_id: None,
}
}
}
@@ -147,6 +149,8 @@ impl LanguageModelRequest {
messages: self.messages.iter().map(|m| m.to_proto()).collect(),
stop: self.stop.clone(),
temperature: self.temperature,
tool_choice: None,
tools: Vec::new(),
}
}
}

View File

@@ -31,7 +31,7 @@ use gpui::{
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
};
use language::{language_settings::SoftWrap, Buffer, BufferId, LanguageRegistry, ToOffset as _};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
use parking_lot::Mutex;
use project::Project;
use search::{buffer_search::DivRegistrar, BufferSearchBar};
@@ -46,6 +46,7 @@ use ui::{
};
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
use workspace::notifications::NotificationId;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
searchable::Direction,
@@ -418,10 +419,14 @@ impl AssistantPanel {
if pending_assist.inline_assistant.is_none() {
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(inline_assist_id, error),
cx,
);
struct InlineAssistantError;
let id =
NotificationId::identified::<InlineAssistantError>(
inline_assist_id,
);
workspace.show_toast(Toast::new(id, error), cx);
})
}
@@ -620,10 +625,10 @@ impl AssistantPanel {
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
if language.as_ref() != "Markdown" {
0.5
} else {
if language.as_ref() == "Markdown" {
1.0
} else {
0.5
}
} else {
1.0
@@ -696,7 +701,7 @@ impl AssistantPanel {
} else {
editor.highlight_background::<PendingInlineAssist>(
&background_ranges,
|theme| theme.editor_active_line_background, // todo!("use the appropriate color")
|theme| theme.editor_active_line_background, // TODO use the appropriate color
cx,
);
}
@@ -1103,7 +1108,7 @@ impl AssistantPanel {
)
.track_scroll(scroll_handle)
.into_any_element();
saved_conversations.layout(
saved_conversations.prepaint_as_root(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
@@ -1114,8 +1119,8 @@ impl AssistantPanel {
)
.size_full()
.into_any_element()
} else {
let editor = self.active_conversation_editor().unwrap();
} else if let Some(editor) = self.active_conversation_editor() {
let editor = editor.clone();
let conversation = editor.read(cx).conversation.clone();
div()
.size_full()
@@ -1130,6 +1135,8 @@ impl AssistantPanel {
.children(self.render_remaining_tokens(&conversation, cx)),
)
.into_any_element()
} else {
div().into_any_element()
},
))
}
@@ -1310,7 +1317,7 @@ impl Conversation {
) -> Self {
let markdown = language_registry.language_for_name("Markdown");
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "");
let mut buffer = Buffer::local("", cx);
buffer.set_language_registry(language_registry);
cx.spawn(|buffer, mut cx| async move {
let markdown = markdown.await?;
@@ -1398,11 +1405,7 @@ impl Conversation {
let mut message_anchors = Vec::new();
let mut next_message_id = MessageId(0);
let buffer = cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
BufferId::new(cx.entity_id().as_u64()).unwrap(),
saved_conversation.text,
);
let mut buffer = Buffer::local(saved_conversation.text, cx);
for message in saved_conversation.messages {
message_anchors.push(MessageAnchor {
id: message.id,
@@ -2064,7 +2067,7 @@ impl ConversationEditor {
workspace: workspace.downgrade(),
_subscriptions,
};
this.update_active_buffer(workspace, cx);
cx.defer(|this, cx| this.update_active_buffer(workspace, cx));
this.update_message_headers(cx);
this
}
@@ -2870,7 +2873,7 @@ impl InlineAssistant {
cx.theme().colors().text
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,

View File

@@ -361,8 +361,8 @@ mod tests {
use gpui::{Context, TestAppContext};
use indoc::indoc;
use language::{
language_settings, tree_sitter_rust, Buffer, BufferId, Language, LanguageConfig,
LanguageMatcher, Point,
language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, LanguageMatcher,
Point,
};
use rand::prelude::*;
use serde::Serialize;
@@ -388,9 +388,8 @@ mod tests {
}
}
"};
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let range = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -447,9 +446,8 @@ mod tests {
le
}
"};
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let position = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);
@@ -506,9 +504,8 @@ mod tests {
" \n",
"}\n" //
);
let buffer = cx.new_model(|cx| {
Buffer::new(0, BufferId::new(1).unwrap(), text).with_language(Arc::new(rust_lang()), cx)
});
let buffer =
cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let position = buffer.read_with(cx, |buffer, cx| {
let snapshot = buffer.snapshot(cx);

View File

@@ -140,14 +140,24 @@ impl OpenAiCompletionProvider {
messages: request
.messages
.into_iter()
.map(|msg| RequestMessage {
role: msg.role.into(),
content: msg.content,
.map(|msg| match msg.role {
Role::User => RequestMessage::User {
content: msg.content,
},
Role::Assistant => RequestMessage::Assistant {
content: Some(msg.content),
tool_calls: Vec::new(),
},
Role::System => RequestMessage::System {
content: msg.content,
},
})
.collect(),
stream: true,
stop: request.stop,
temperature: request.temperature,
tools: Vec::new(),
tool_choice: None,
}
}
}
@@ -231,7 +241,7 @@ impl AuthenticationPrompt {
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,

View File

@@ -123,6 +123,8 @@ impl ZedDotDevCompletionProvider {
.collect(),
stop: request.stop,
temperature: request.temperature,
tools: Vec::new(),
tool_choice: None,
};
self.client

View File

@@ -0,0 +1,59 @@
[package]
name = "assistant2"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant2.rs"
[features]
default = []
stories = ["dep:story"]
[dependencies]
anyhow.workspace = true
assistant_tooling.workspace = true
client.workspace = true
collections.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
nanoid.workspace = true
open_ai.workspace = true
project.workspace = true
rich_text.workspace = true
schemars.workspace = true
semantic_index.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
story = { workspace = true, optional = true }
theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
[dev-dependencies]
assets.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
languages.workspace = true
node_runtime.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
release_channel.workspace = true
settings = { workspace = true, features = ["test-support"] }
theme = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
workspace = { workspace = true, features = ["test-support"] }

View File

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

View File

@@ -0,0 +1,378 @@
//! This example creates a basic Chat UI with a function for rolling a die.
use anyhow::{Context as _, Result};
use assets::Assets;
use assistant2::AssistantPanel;
use assistant_tooling::{LanguageModelTool, ToolRegistry};
use client::{Client, UserStore};
use fs::Fs;
use futures::StreamExt as _;
use gpui::{actions, AnyElement, App, AppContext, KeyBinding, Model, Task, View, WindowOptions};
use language::LanguageRegistry;
use project::Project;
use rand::Rng;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
use std::{path::PathBuf, sync::Arc};
use theme::LoadThemes;
use ui::{div, prelude::*, Render};
use util::ResultExt as _;
actions!(example, [Quit]);
struct RollDiceTool {}
impl RollDiceTool {
fn new() -> Self {
Self {}
}
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
#[serde(rename_all = "snake_case")]
enum Die {
D6 = 6,
D20 = 20,
}
impl Die {
fn into_str(&self) -> &'static str {
match self {
Die::D6 => "d6",
Die::D20 => "d20",
}
}
}
#[derive(Serialize, Deserialize, JsonSchema, Clone)]
struct DiceParams {
/// The number of dice to roll.
num_dice: u8,
/// Which die to roll. Defaults to a d6 if not provided.
die_type: Option<Die>,
}
#[derive(Serialize, Deserialize)]
struct DieRoll {
die: Die,
roll: u8,
}
impl DieRoll {
fn render(&self) -> AnyElement {
match self.die {
Die::D6 => {
let face = match self.roll {
6 => div().child(""),
5 => div().child(""),
4 => div().child(""),
3 => div().child(""),
2 => div().child(""),
1 => div().child(""),
_ => div().child("😅"),
};
face.text_3xl().into_any_element()
}
_ => div()
.child(format!("{}", self.roll))
.text_3xl()
.into_any_element(),
}
}
}
#[derive(Serialize, Deserialize)]
struct DiceRoll {
rolls: Vec<DieRoll>,
}
pub struct DiceView {
result: Result<DiceRoll>,
}
impl Render for DiceView {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let output = match &self.result {
Ok(output) => output,
Err(_) => return "Somehow dice failed 🎲".into_any_element(),
};
h_flex()
.children(
output
.rolls
.iter()
.map(|roll| div().p_2().child(roll.render())),
)
.into_any_element()
}
}
impl LanguageModelTool for RollDiceTool {
type Input = DiceParams;
type Output = DiceRoll;
type View = DiceView;
fn name(&self) -> String {
"roll_dice".to_string()
}
fn description(&self) -> String {
"Rolls N many dice and returns the results.".to_string()
}
fn execute(
&self,
input: &Self::Input,
_cx: &mut WindowContext,
) -> Task<gpui::Result<Self::Output>> {
let rolls = (0..input.num_dice)
.map(|_| {
let die_type = input.die_type.as_ref().unwrap_or(&Die::D6).clone();
DieRoll {
die: die_type.clone(),
roll: rand::thread_rng().gen_range(1..=die_type as u8),
}
})
.collect();
return Task::ready(Ok(DiceRoll { rolls }));
}
fn output_view(
_tool_call_id: String,
_input: Self::Input,
result: Result<Self::Output>,
cx: &mut WindowContext,
) -> gpui::View<Self::View> {
cx.new_view(|_cx| DiceView { result })
}
fn format(_: &Self::Input, output: &Result<Self::Output>) -> String {
let output = match output {
Ok(output) => output,
Err(_) => return "Somehow dice failed 🎲".to_string(),
};
let mut result = String::new();
for roll in &output.rolls {
let die = &roll.die;
result.push_str(&format!("{}: {}\n", die.into_str(), roll.roll));
}
result
}
}
struct FileBrowserTool {
fs: Arc<dyn Fs>,
root_dir: PathBuf,
}
impl FileBrowserTool {
fn new(fs: Arc<dyn Fs>, root_dir: PathBuf) -> Self {
Self { fs, root_dir }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
struct FileBrowserParams {
command: FileBrowserCommand,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
enum FileBrowserCommand {
Ls { path: PathBuf },
Cat { path: PathBuf },
}
#[derive(Serialize, Deserialize)]
enum FileBrowserOutput {
Ls { entries: Vec<String> },
Cat { content: String },
}
pub struct FileBrowserView {
result: Result<FileBrowserOutput>,
}
impl Render for FileBrowserView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let Ok(output) = self.result.as_ref() else {
return h_flex().child("Failed to perform operation");
};
match output {
FileBrowserOutput::Ls { entries } => v_flex().children(
entries
.into_iter()
.map(|entry| h_flex().text_ui(cx).child(entry.clone())),
),
FileBrowserOutput::Cat { content } => h_flex().child(content.clone()),
}
}
}
impl LanguageModelTool for FileBrowserTool {
type Input = FileBrowserParams;
type Output = FileBrowserOutput;
type View = FileBrowserView;
fn name(&self) -> String {
"file_browser".to_string()
}
fn description(&self) -> String {
"A tool for browsing the filesystem.".to_string()
}
fn execute(
&self,
input: &Self::Input,
cx: &mut WindowContext,
) -> Task<gpui::Result<Self::Output>> {
cx.spawn({
let fs = self.fs.clone();
let root_dir = self.root_dir.clone();
let input = input.clone();
|_cx| async move {
match input.command {
FileBrowserCommand::Ls { path } => {
let path = root_dir.join(path);
let mut output = fs.read_dir(&path).await?;
let mut entries = Vec::new();
while let Some(entry) = output.next().await {
let entry = entry?;
entries.push(entry.display().to_string());
}
Ok(FileBrowserOutput::Ls { entries })
}
FileBrowserCommand::Cat { path } => {
let path = root_dir.join(path);
let output = fs.load(&path).await?;
Ok(FileBrowserOutput::Cat { content: output })
}
}
}
})
}
fn output_view(
_tool_call_id: String,
_input: Self::Input,
result: Result<Self::Output>,
cx: &mut WindowContext,
) -> gpui::View<Self::View> {
cx.new_view(|_cx| FileBrowserView { result })
}
fn format(_input: &Self::Input, output: &Result<Self::Output>) -> String {
let Ok(output) = output else {
return "Failed to perform command: {input:?}".to_string();
};
match output {
FileBrowserOutput::Ls { entries } => entries.join("\n"),
FileBrowserOutput::Cat { content } => content.to_owned(),
}
}
}
fn main() {
env_logger::init();
App::new().with_assets(Assets).run(|cx| {
cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
cx.on_action(|_: &Quit, cx: &mut AppContext| {
cx.quit();
});
settings::init(cx);
language::init(cx);
Project::init_settings(cx);
editor::init(cx);
theme::init(LoadThemes::JustBase, cx);
Assets.load_fonts(cx).unwrap();
KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
client::init_settings(cx);
release_channel::init("0.130.0", cx);
let client = Client::production(cx);
{
let client = client.clone();
cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
.detach_and_log_err(cx);
}
assistant2::init(client.clone(), cx);
let language_registry = Arc::new(LanguageRegistry::new(
Task::ready(()),
cx.background_executor().clone(),
));
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
languages::init(language_registry.clone(), node_runtime, cx);
cx.spawn(|cx| async move {
cx.update(|cx| {
let fs = Arc::new(fs::RealFs::new(None));
let cwd = std::env::current_dir().expect("Failed to get current working directory");
cx.open_window(WindowOptions::default(), |cx| {
let mut tool_registry = ToolRegistry::new();
tool_registry
.register(RollDiceTool::new(), cx)
.context("failed to register DummyTool")
.log_err();
tool_registry
.register(FileBrowserTool::new(fs, cwd), cx)
.context("failed to register FileBrowserTool")
.log_err();
let tool_registry = Arc::new(tool_registry);
println!("Tools registered");
for definition in tool_registry.definitions() {
println!("{}", definition);
}
cx.new_view(|cx| Example::new(language_registry, tool_registry, user_store, cx))
});
cx.activate(true);
})
})
.detach_and_log_err(cx);
})
}
struct Example {
assistant_panel: View<AssistantPanel>,
}
impl Example {
fn new(
language_registry: Arc<LanguageRegistry>,
tool_registry: Arc<ToolRegistry>,
user_store: Model<UserStore>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
assistant_panel: cx.new_view(|cx| {
AssistantPanel::new(language_registry, tool_registry, user_store, None, cx)
}),
}
}
}
impl Render for Example {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl ui::prelude::IntoElement {
div().size_full().child(self.assistant_panel.clone())
}
}

View File

@@ -0,0 +1,795 @@
mod assistant_settings;
mod completion_provider;
mod tools;
pub mod ui;
use ::ui::{div, prelude::*, Color, ViewContext};
use anyhow::{Context, Result};
use assistant_tooling::{ToolFunctionCall, ToolRegistry};
use client::{proto, Client, UserStore};
use collections::HashMap;
use completion_provider::*;
use editor::Editor;
use feature_flags::FeatureFlagAppExt as _;
use futures::{future::join_all, StreamExt};
use gpui::{
list, AnyElement, AppContext, AsyncWindowContext, ClickEvent, EventEmitter, FocusHandle,
FocusableView, ListAlignment, ListState, Model, Render, Task, View, WeakView,
};
use language::{language_settings::SoftWrap, LanguageRegistry};
use open_ai::{FunctionContent, ToolCall, ToolCallContent};
use rich_text::RichText;
use semantic_index::{CloudEmbeddingProvider, ProjectIndex, SemanticIndex};
use serde::Deserialize;
use settings::Settings;
use std::sync::Arc;
use ui::Composer;
use util::{paths::EMBEDDINGS_DIR, ResultExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
Workspace,
};
pub use assistant_settings::AssistantSettings;
use crate::tools::{CreateBufferTool, ProjectIndexTool};
use crate::ui::UserOrAssistant;
const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
pub struct Submit(SubmitMode);
/// There are multiple different ways to submit a model request, represented by this enum.
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
pub enum SubmitMode {
/// Only include the conversation.
Simple,
/// Send the current file as context.
CurrentFile,
/// Search the codebase and send relevant excerpts.
Codebase,
}
gpui::actions!(assistant2, [Cancel, ToggleFocus, DebugProjectIndex]);
gpui::impl_actions!(assistant2, [Submit]);
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
AssistantSettings::register(cx);
cx.spawn(|mut cx| {
let client = client.clone();
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticIndex::new(
EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
.await?;
cx.update(|cx| cx.set_global(semantic_index))
}
})
.detach();
cx.set_global(CompletionProvider::new(CloudCompletionProvider::new(
client,
)));
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
});
},
)
.detach();
}
pub fn enabled(cx: &AppContext) -> bool {
cx.is_staff()
}
pub struct AssistantPanel {
chat: View<AssistantChat>,
width: Option<Pixels>,
}
impl AssistantPanel {
pub fn load(
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let (app_state, project) = workspace.update(&mut cx, |workspace, _| {
(workspace.app_state().clone(), workspace.project().clone())
})?;
let user_store = app_state.user_store.clone();
cx.new_view(|cx| {
// todo!("this will panic if the semantic index failed to load or has not loaded yet")
let project_index = cx.update_global(|semantic_index: &mut SemanticIndex, cx| {
semantic_index.project_index(project.clone(), cx)
});
let mut tool_registry = ToolRegistry::new();
tool_registry
.register(
ProjectIndexTool::new(project_index.clone(), app_state.fs.clone()),
cx,
)
.context("failed to register ProjectIndexTool")
.log_err();
tool_registry
.register(
CreateBufferTool::new(workspace.clone(), project.clone()),
cx,
)
.context("failed to register CreateBufferTool")
.log_err();
let tool_registry = Arc::new(tool_registry);
Self::new(
app_state.languages.clone(),
tool_registry,
user_store,
Some(project_index),
cx,
)
})
})
}
pub fn new(
language_registry: Arc<LanguageRegistry>,
tool_registry: Arc<ToolRegistry>,
user_store: Model<UserStore>,
project_index: Option<Model<ProjectIndex>>,
cx: &mut ViewContext<Self>,
) -> Self {
let chat = cx.new_view(|cx| {
AssistantChat::new(
language_registry.clone(),
tool_registry.clone(),
user_store,
project_index,
cx,
)
});
Self { width: None, chat }
}
}
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.size_full()
.v_flex()
.p_2()
.bg(cx.theme().colors().background)
.child(self.chat.clone())
}
}
impl Panel for AssistantPanel {
fn persistent_name() -> &'static str {
"AssistantPanelv2"
}
fn position(&self, _cx: &WindowContext) -> workspace::dock::DockPosition {
// todo!("Add a setting / use assistant settings")
DockPosition::Right
}
fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool {
matches!(position, DockPosition::Right)
}
fn set_position(&mut self, _: workspace::dock::DockPosition, _: &mut ViewContext<Self>) {
// Do nothing until we have a setting for this
}
fn size(&self, _cx: &WindowContext) -> Pixels {
self.width.unwrap_or(px(400.))
}
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
self.width = size;
cx.notify();
}
fn icon(&self, _cx: &WindowContext) -> Option<::ui::IconName> {
Some(IconName::Ai)
}
fn icon_tooltip(&self, _: &WindowContext) -> Option<&'static str> {
Some("Assistant Panel ✨")
}
fn toggle_action(&self) -> Box<dyn gpui::Action> {
Box::new(ToggleFocus)
}
}
impl EventEmitter<PanelEvent> for AssistantPanel {}
impl FocusableView for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.chat.read(cx).composer_editor.read(cx).focus_handle(cx)
}
}
pub struct AssistantChat {
model: String,
messages: Vec<ChatMessage>,
list_state: ListState,
language_registry: Arc<LanguageRegistry>,
composer_editor: View<Editor>,
user_store: Model<UserStore>,
next_message_id: MessageId,
collapsed_messages: HashMap<MessageId, bool>,
editing_message_id: Option<MessageId>,
pending_completion: Option<Task<()>>,
tool_registry: Arc<ToolRegistry>,
project_index: Option<Model<ProjectIndex>>,
}
impl AssistantChat {
fn new(
language_registry: Arc<LanguageRegistry>,
tool_registry: Arc<ToolRegistry>,
user_store: Model<UserStore>,
project_index: Option<Model<ProjectIndex>>,
cx: &mut ViewContext<Self>,
) -> Self {
let model = CompletionProvider::get(cx).default_model();
let view = cx.view().downgrade();
let list_state = ListState::new(
0,
ListAlignment::Bottom,
px(1024.),
move |ix, cx: &mut WindowContext| {
view.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
},
);
Self {
model,
messages: Vec::new(),
composer_editor: cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_placeholder_text("Type a message to the assistant", cx);
editor
}),
list_state,
user_store,
language_registry,
project_index,
next_message_id: MessageId(0),
editing_message_id: None,
collapsed_messages: HashMap::default(),
pending_completion: None,
tool_registry,
}
}
fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
self.messages.iter().find_map(|message| match message {
ChatMessage::User(message) => message
.body
.focus_handle(cx)
.contains_focused(cx)
.then_some(message.id),
ChatMessage::Assistant(_) => None,
})
}
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
// If we're currently editing a message, cancel the edit.
self.editing_message_id.take();
if self.pending_completion.take().is_none() {
cx.propagate();
return;
}
if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
if message.body.text.is_empty() {
self.pop_message(cx);
}
}
}
fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
// Don't allow multiple concurrent completions.
if self.pending_completion.is_some() {
cx.propagate();
return;
}
if let Some(focused_message_id) = self.focused_message_id(cx) {
self.truncate_messages(focused_message_id, cx);
} else if self.composer_editor.focus_handle(cx).is_focused(cx) {
let message = self.composer_editor.update(cx, |composer_editor, cx| {
let text = composer_editor.text(cx);
let id = self.next_message_id.post_inc();
let body = cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
editor.set_text(text, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor
});
composer_editor.clear(cx);
ChatMessage::User(UserMessage { id, body })
});
self.push_message(message, cx);
} else {
log::error!("unexpected state: no user message editor is focused.");
return;
}
let mode = *mode;
self.pending_completion = Some(cx.spawn(move |this, mut cx| async move {
Self::request_completion(
this.clone(),
mode,
MAX_COMPLETION_CALLS_PER_SUBMISSION,
&mut cx,
)
.await
.log_err();
this.update(&mut cx, |this, cx| {
let composer_focus_handle = this.composer_editor.focus_handle(cx);
cx.focus(&composer_focus_handle);
this.pending_completion = None;
})
.context("Failed to push new user message")
.log_err();
}));
}
fn can_submit(&self) -> bool {
self.pending_completion.is_none()
}
fn debug_project_index(&mut self, _: &DebugProjectIndex, cx: &mut ViewContext<Self>) {
if let Some(index) = &self.project_index {
index.update(cx, |project_index, cx| {
project_index.debug(cx).detach_and_log_err(cx)
});
}
}
async fn request_completion(
this: WeakView<Self>,
mode: SubmitMode,
limit: usize,
cx: &mut AsyncWindowContext,
) -> Result<()> {
let mut call_count = 0;
loop {
let complete = async {
let completion = this.update(cx, |this, cx| {
this.push_new_assistant_message(cx);
let definitions = if call_count < limit
&& matches!(mode, SubmitMode::Codebase | SubmitMode::Simple)
{
this.tool_registry.definitions()
} else {
&[]
};
call_count += 1;
let messages = this.completion_messages(cx);
CompletionProvider::get(cx).complete(
this.model.clone(),
messages,
Vec::new(),
1.0,
definitions,
)
});
let mut stream = completion?.await?;
let mut body = String::new();
while let Some(delta) = stream.next().await {
let delta = delta?;
this.update(cx, |this, cx| {
if let Some(ChatMessage::Assistant(AssistantMessage {
body: message_body,
tool_calls: message_tool_calls,
..
})) = this.messages.last_mut()
{
if let Some(content) = &delta.content {
body.push_str(content);
}
for tool_call in delta.tool_calls {
let index = tool_call.index as usize;
if index >= message_tool_calls.len() {
message_tool_calls.resize_with(index + 1, Default::default);
}
let call = &mut message_tool_calls[index];
if let Some(id) = &tool_call.id {
call.id.push_str(id);
}
match tool_call.variant {
Some(proto::tool_call_delta::Variant::Function(tool_call)) => {
if let Some(name) = &tool_call.name {
call.name.push_str(name);
}
if let Some(arguments) = &tool_call.arguments {
call.arguments.push_str(arguments);
}
}
None => {}
}
}
*message_body =
RichText::new(body.clone(), &[], &this.language_registry);
cx.notify();
} else {
unreachable!()
}
})?;
}
anyhow::Ok(())
}
.await;
let mut tool_tasks = Vec::new();
this.update(cx, |this, cx| {
if let Some(ChatMessage::Assistant(AssistantMessage {
error: message_error,
tool_calls,
..
})) = this.messages.last_mut()
{
if let Err(error) = complete {
message_error.replace(SharedString::from(error.to_string()));
cx.notify();
} else {
for tool_call in tool_calls.iter() {
tool_tasks.push(this.tool_registry.call(tool_call, cx));
}
}
}
})?;
if tool_tasks.is_empty() {
return Ok(());
}
let tools = join_all(tool_tasks.into_iter()).await;
// If the WindowContext went away for any tool's view we don't include it
// especially since the below call would fail for the same reason.
let tools = tools.into_iter().filter_map(|tool| tool.ok()).collect();
this.update(cx, |this, cx| {
if let Some(ChatMessage::Assistant(AssistantMessage { tool_calls, .. })) =
this.messages.last_mut()
{
*tool_calls = tools;
cx.notify();
}
})?;
}
}
fn push_new_assistant_message(&mut self, cx: &mut ViewContext<Self>) {
let message = ChatMessage::Assistant(AssistantMessage {
id: self.next_message_id.post_inc(),
body: RichText::default(),
tool_calls: Vec::new(),
error: None,
});
self.push_message(message, cx);
}
fn push_message(&mut self, message: ChatMessage, cx: &mut ViewContext<Self>) {
let old_len = self.messages.len();
let focus_handle = Some(message.focus_handle(cx));
self.messages.push(message);
self.list_state
.splice_focusable(old_len..old_len, focus_handle);
cx.notify();
}
fn pop_message(&mut self, cx: &mut ViewContext<Self>) {
if self.messages.is_empty() {
return;
}
self.messages.pop();
self.list_state
.splice(self.messages.len()..self.messages.len() + 1, 0);
cx.notify();
}
fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
if let Some(index) = self.messages.iter().position(|message| match message {
ChatMessage::User(message) => message.id == last_message_id,
ChatMessage::Assistant(message) => message.id == last_message_id,
}) {
self.list_state.splice(index + 1..self.messages.len(), 0);
self.messages.truncate(index + 1);
cx.notify();
}
}
fn is_message_collapsed(&self, id: &MessageId) -> bool {
self.collapsed_messages.get(id).copied().unwrap_or_default()
}
fn toggle_message_collapsed(&mut self, id: MessageId) {
let entry = self.collapsed_messages.entry(id).or_insert(false);
*entry = !*entry;
}
fn render_error(
&self,
error: Option<SharedString>,
_ix: usize,
cx: &mut ViewContext<Self>,
) -> AnyElement {
let theme = cx.theme();
if let Some(error) = error {
div()
.py_1()
.px_2()
.neg_mx_1()
.rounded_md()
.border()
.border_color(theme.status().error_border)
// .bg(theme.status().error_background)
.text_color(theme.status().error)
.child(error.clone())
.into_any_element()
} else {
div().into_any_element()
}
}
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let is_last = ix == self.messages.len() - 1;
match &self.messages[ix] {
ChatMessage::User(UserMessage { id, body }) => div()
.id(SharedString::from(format!("message-{}-container", id.0)))
.when(!is_last, |element| element.mb_2())
.map(|element| {
if self.editing_message_id.as_ref() == Some(id) {
element.child(Composer::new(
body.clone(),
self.user_store.read(cx).current_user(),
self.can_submit(),
self.tool_registry.clone(),
crate::ui::ModelSelector::new(
cx.view().downgrade(),
self.model.clone(),
)
.into_any_element(),
))
} else {
element
.on_click(cx.listener({
let id = *id;
move |assistant_chat, event: &ClickEvent, _cx| {
if event.up.click_count == 2 {
assistant_chat.editing_message_id = Some(id);
}
}
}))
.child(crate::ui::ChatMessage::new(
*id,
UserOrAssistant::User(self.user_store.read(cx).current_user()),
Some(
RichText::new(
body.read(cx).text(cx),
&[],
&self.language_registry,
)
.element(ElementId::from(id.0), cx),
),
self.is_message_collapsed(id),
Box::new(cx.listener({
let id = *id;
move |assistant_chat, _event, _cx| {
assistant_chat.toggle_message_collapsed(id)
}
})),
))
}
})
.into_any(),
ChatMessage::Assistant(AssistantMessage {
id,
body,
error,
tool_calls,
..
}) => {
let assistant_body = if body.text.is_empty() {
None
} else {
Some(
div()
.p_2()
.child(body.element(ElementId::from(id.0), cx))
.into_any_element(),
)
};
div()
.when(!is_last, |element| element.mb_2())
.child(crate::ui::ChatMessage::new(
*id,
UserOrAssistant::Assistant,
assistant_body,
self.is_message_collapsed(id),
Box::new(cx.listener({
let id = *id;
move |assistant_chat, _event, _cx| {
assistant_chat.toggle_message_collapsed(id)
}
})),
))
// TODO: Should the errors and tool calls get passed into `ChatMessage`?
.child(self.render_error(error.clone(), ix, cx))
.children(tool_calls.iter().map(|tool_call| {
let result = &tool_call.result;
let name = tool_call.name.clone();
match result {
Some(result) => {
div().p_2().child(result.into_any_element(&name)).into_any()
}
None => div()
.p_2()
.child(Label::new(name).color(Color::Modified))
.child("Running...")
.into_any(),
}
}))
.into_any()
}
}
}
fn completion_messages(&self, cx: &mut WindowContext) -> Vec<CompletionMessage> {
let mut completion_messages = Vec::new();
for message in &self.messages {
match message {
ChatMessage::User(UserMessage { body, .. }) => {
// When we re-introduce contexts like active file, we'll inject them here instead of relying on the model to request them
// contexts.iter().for_each(|context| {
// completion_messages.extend(context.completion_messages(cx))
// });
// Show user's message last so that the assistant is grounded in the user's request
completion_messages.push(CompletionMessage::User {
content: body.read(cx).text(cx),
});
}
ChatMessage::Assistant(AssistantMessage {
body, tool_calls, ..
}) => {
// In no case do we want to send an empty message. This shouldn't happen, but we might as well
// not break the Chat API if it does.
if body.text.is_empty() && tool_calls.is_empty() {
continue;
}
let tool_calls_from_assistant = tool_calls
.iter()
.map(|tool_call| ToolCall {
content: ToolCallContent::Function {
function: FunctionContent {
name: tool_call.name.clone(),
arguments: tool_call.arguments.clone(),
},
},
id: tool_call.id.clone(),
})
.collect();
completion_messages.push(CompletionMessage::Assistant {
content: Some(body.text.to_string()),
tool_calls: tool_calls_from_assistant,
});
for tool_call in tool_calls {
// todo!(): we should not be sending when the tool is still running / has no result
// For now I'm going to have to assume we send an empty string because otherwise
// the Chat API will break -- there is a required message for every tool call by ID
let content = match &tool_call.result {
Some(result) => result.format(&tool_call.name),
None => "".to_string(),
};
completion_messages.push(CompletionMessage::Tool {
content,
tool_call_id: tool_call.id.clone(),
});
}
}
}
}
completion_messages
}
}
impl Render for AssistantChat {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.relative()
.flex_1()
.v_flex()
.key_context("AssistantChat")
.on_action(cx.listener(Self::submit))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::debug_project_index))
.text_color(Color::Default.color(cx))
.child(list(self.list_state.clone()).flex_1())
.child(Composer::new(
self.composer_editor.clone(),
self.user_store.read(cx).current_user(),
self.can_submit(),
self.tool_registry.clone(),
crate::ui::ModelSelector::new(cx.view().downgrade(), self.model.clone())
.into_any_element(),
))
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct MessageId(usize);
impl MessageId {
fn post_inc(&mut self) -> Self {
let id = *self;
self.0 += 1;
id
}
}
enum ChatMessage {
User(UserMessage),
Assistant(AssistantMessage),
}
impl ChatMessage {
fn focus_handle(&self, cx: &AppContext) -> Option<FocusHandle> {
match self {
ChatMessage::User(UserMessage { body, .. }) => Some(body.focus_handle(cx)),
ChatMessage::Assistant(_) => None,
}
}
}
struct UserMessage {
id: MessageId,
body: View<Editor>,
}
struct AssistantMessage {
id: MessageId,
body: RichText,
tool_calls: Vec<ToolFunctionCall>,
error: Option<SharedString>,
}

View File

@@ -0,0 +1,26 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
pub struct AssistantSettings {
pub enabled: bool,
}
#[derive(Default, Debug, Deserialize, Serialize, Clone, JsonSchema)]
pub struct AssistantSettingsContent {
pub enabled: Option<bool>,
}
impl Settings for AssistantSettings {
const KEY: Option<&'static str> = Some("assistant_v2");
type FileContent = AssistantSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Ok(sources.json_merge().unwrap_or_else(|_| Default::default()))
}
}

View File

@@ -0,0 +1,183 @@
use anyhow::Result;
use assistant_tooling::ToolFunctionDefinition;
use client::{proto, Client};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AppContext, Global};
use std::sync::Arc;
pub use open_ai::RequestMessage as CompletionMessage;
#[derive(Clone)]
pub struct CompletionProvider(Arc<dyn CompletionProviderBackend>);
impl CompletionProvider {
pub fn get(cx: &AppContext) -> &Self {
cx.global::<CompletionProvider>()
}
pub fn new(backend: impl CompletionProviderBackend) -> Self {
Self(Arc::new(backend))
}
pub fn default_model(&self) -> String {
self.0.default_model()
}
pub fn available_models(&self) -> Vec<String> {
self.0.available_models()
}
pub fn complete(
&self,
model: String,
messages: Vec<CompletionMessage>,
stop: Vec<String>,
temperature: f32,
tools: &[ToolFunctionDefinition],
) -> BoxFuture<'static, Result<BoxStream<'static, Result<proto::LanguageModelResponseMessage>>>>
{
self.0.complete(model, messages, stop, temperature, tools)
}
}
impl Global for CompletionProvider {}
pub trait CompletionProviderBackend: 'static {
fn default_model(&self) -> String;
fn available_models(&self) -> Vec<String>;
fn complete(
&self,
model: String,
messages: Vec<CompletionMessage>,
stop: Vec<String>,
temperature: f32,
tools: &[ToolFunctionDefinition],
) -> BoxFuture<'static, Result<BoxStream<'static, Result<proto::LanguageModelResponseMessage>>>>;
}
pub struct CloudCompletionProvider {
client: Arc<Client>,
}
impl CloudCompletionProvider {
pub fn new(client: Arc<Client>) -> Self {
Self { client }
}
}
impl CompletionProviderBackend for CloudCompletionProvider {
fn default_model(&self) -> String {
"gpt-4-turbo".into()
}
fn available_models(&self) -> Vec<String> {
vec!["gpt-4-turbo".into(), "gpt-4".into(), "gpt-3.5-turbo".into()]
}
fn complete(
&self,
model: String,
messages: Vec<CompletionMessage>,
stop: Vec<String>,
temperature: f32,
tools: &[ToolFunctionDefinition],
) -> BoxFuture<'static, Result<BoxStream<'static, Result<proto::LanguageModelResponseMessage>>>>
{
let client = self.client.clone();
let tools: Vec<proto::ChatCompletionTool> = tools
.iter()
.filter_map(|tool| {
Some(proto::ChatCompletionTool {
variant: Some(proto::chat_completion_tool::Variant::Function(
proto::chat_completion_tool::FunctionObject {
name: tool.name.clone(),
description: Some(tool.description.clone()),
parameters: Some(serde_json::to_string(&tool.parameters).ok()?),
},
)),
})
})
.collect();
let tool_choice = match tools.is_empty() {
true => None,
false => Some("auto".into()),
};
async move {
let stream = client
.request_stream(proto::CompleteWithLanguageModel {
model,
messages: messages
.into_iter()
.map(|message| match message {
CompletionMessage::Assistant {
content,
tool_calls,
} => proto::LanguageModelRequestMessage {
role: proto::LanguageModelRole::LanguageModelAssistant as i32,
content: content.unwrap_or_default(),
tool_call_id: None,
tool_calls: tool_calls
.into_iter()
.map(|tool_call| match tool_call.content {
open_ai::ToolCallContent::Function { function } => {
proto::ToolCall {
id: tool_call.id,
variant: Some(proto::tool_call::Variant::Function(
proto::tool_call::FunctionCall {
name: function.name,
arguments: function.arguments,
},
)),
}
}
})
.collect(),
},
CompletionMessage::User { content } => {
proto::LanguageModelRequestMessage {
role: proto::LanguageModelRole::LanguageModelUser as i32,
content,
tool_call_id: None,
tool_calls: Vec::new(),
}
}
CompletionMessage::System { content } => {
proto::LanguageModelRequestMessage {
role: proto::LanguageModelRole::LanguageModelSystem as i32,
content,
tool_calls: Vec::new(),
tool_call_id: None,
}
}
CompletionMessage::Tool {
content,
tool_call_id,
} => proto::LanguageModelRequestMessage {
role: proto::LanguageModelRole::LanguageModelTool as i32,
content,
tool_call_id: Some(tool_call_id),
tool_calls: Vec::new(),
},
})
.collect(),
stop,
temperature,
tool_choice,
tools,
})
.await?;
Ok(stream
.filter_map(|response| async move {
match response {
Ok(mut response) => Some(Ok(response.choices.pop()?.delta?)),
Err(error) => Some(Err(error)),
}
})
.boxed())
}
.boxed()
}
}

View File

@@ -0,0 +1,5 @@
mod create_buffer;
mod project_index;
pub use create_buffer::*;
pub use project_index::*;

View File

@@ -0,0 +1,111 @@
use anyhow::Result;
use assistant_tooling::LanguageModelTool;
use editor::Editor;
use gpui::{prelude::*, Model, Task, View, WeakView};
use project::Project;
use schemars::JsonSchema;
use serde::Deserialize;
use ui::prelude::*;
use util::ResultExt;
use workspace::Workspace;
pub struct CreateBufferTool {
workspace: WeakView<Workspace>,
project: Model<Project>,
}
impl CreateBufferTool {
pub fn new(workspace: WeakView<Workspace>, project: Model<Project>) -> Self {
Self { workspace, project }
}
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateBufferInput {
/// The contents of the buffer.
text: String,
/// The name of the language to use for the buffer.
///
/// This should be a human-readable name, like "Rust", "JavaScript", or "Python".
language: String,
}
pub struct CreateBufferOutput {}
impl LanguageModelTool for CreateBufferTool {
type Input = CreateBufferInput;
type Output = CreateBufferOutput;
type View = CreateBufferView;
fn name(&self) -> String {
"create_buffer".to_string()
}
fn description(&self) -> String {
"Create a new buffer in the current codebase".to_string()
}
fn execute(&self, input: &Self::Input, cx: &mut WindowContext) -> Task<Result<Self::Output>> {
cx.spawn({
let workspace = self.workspace.clone();
let project = self.project.clone();
let text = input.text.clone();
let language_name = input.language.clone();
|mut cx| async move {
let language = cx
.update(|cx| {
project
.read(cx)
.languages()
.language_for_name(&language_name)
})?
.await?;
let buffer = cx.update(|cx| {
project.update(cx, |project, cx| {
project.create_buffer(&text, Some(language), cx)
})
})??;
workspace
.update(&mut cx, |workspace, cx| {
workspace.add_item_to_active_pane(
Box::new(
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project), cx)),
),
None,
cx,
);
})
.log_err();
Ok(CreateBufferOutput {})
}
})
}
fn format(input: &Self::Input, output: &Result<Self::Output>) -> String {
match output {
Ok(_) => format!("Created a new {} buffer", input.language),
Err(err) => format!("Failed to create buffer: {err:?}"),
}
}
fn output_view(
_tool_call_id: String,
_input: Self::Input,
_output: Result<Self::Output>,
cx: &mut WindowContext,
) -> View<Self::View> {
cx.new_view(|_cx| CreateBufferView {})
}
}
pub struct CreateBufferView {}
impl Render for CreateBufferView {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div().child("Opening a buffer")
}
}

View File

@@ -0,0 +1,267 @@
use anyhow::Result;
use assistant_tooling::LanguageModelTool;
use gpui::{prelude::*, AnyView, Model, Task};
use project::Fs;
use schemars::JsonSchema;
use semantic_index::{ProjectIndex, Status};
use serde::Deserialize;
use std::sync::Arc;
use ui::{
div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, SharedString,
WindowContext,
};
use util::ResultExt as _;
const DEFAULT_SEARCH_LIMIT: usize = 20;
#[derive(Clone)]
pub struct CodebaseExcerpt {
path: SharedString,
text: SharedString,
score: f32,
element_id: ElementId,
expanded: bool,
}
// Note: Comments on a `LanguageModelTool::Input` become descriptions on the generated JSON schema as shown to the language model.
// Any changes or deletions to the `CodebaseQuery` comments will change model behavior.
#[derive(Deserialize, JsonSchema)]
pub struct CodebaseQuery {
/// Semantic search query
query: String,
/// Maximum number of results to return, defaults to 20
limit: Option<usize>,
}
pub struct ProjectIndexView {
input: CodebaseQuery,
output: Result<ProjectIndexOutput>,
}
impl ProjectIndexView {
fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext<Self>) {
if let Ok(output) = &mut self.output {
if let Some(excerpt) = output
.excerpts
.iter_mut()
.find(|excerpt| excerpt.element_id == element_id)
{
excerpt.expanded = !excerpt.expanded;
cx.notify();
}
}
}
}
impl Render for ProjectIndexView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let query = self.input.query.clone();
let result = &self.output;
let output = match result {
Err(err) => {
return div().child(Label::new(format!("Error: {}", err)).color(Color::Error));
}
Ok(output) => output,
};
div()
.v_flex()
.gap_2()
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.child(Label::new("Query: ").color(Color::Modified))
.child(Label::new(query).color(Color::Muted)),
),
)
.children(output.excerpts.iter().map(|excerpt| {
let element_id = excerpt.element_id.clone();
let expanded = excerpt.expanded;
CollapsibleContainer::new(element_id.clone(), expanded)
.start_slot(
h_flex()
.gap_1()
.child(Icon::new(IconName::File).color(Color::Muted))
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
)
.on_click(cx.listener(move |this, _, cx| {
this.toggle_expanded(element_id.clone(), cx);
}))
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(excerpt.text.clone()),
)
}))
}
}
pub struct ProjectIndexTool {
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
}
pub struct ProjectIndexOutput {
excerpts: Vec<CodebaseExcerpt>,
status: Status,
}
impl ProjectIndexTool {
pub fn new(project_index: Model<ProjectIndex>, fs: Arc<dyn Fs>) -> Self {
// Listen for project index status and update the ProjectIndexTool directly
// TODO: setup a better description based on the user's current codebase.
Self { project_index, fs }
}
}
impl LanguageModelTool for ProjectIndexTool {
type Input = CodebaseQuery;
type Output = ProjectIndexOutput;
type View = ProjectIndexView;
fn name(&self) -> String {
"query_codebase".to_string()
}
fn description(&self) -> String {
"Semantic search against the user's current codebase, returning excerpts related to the query by computing a dot product against embeddings of chunks and an embedding of the query".to_string()
}
fn execute(&self, query: &Self::Input, cx: &mut WindowContext) -> Task<Result<Self::Output>> {
let project_index = self.project_index.read(cx);
let status = project_index.status();
let results = project_index.search(
query.query.as_str(),
query.limit.unwrap_or(DEFAULT_SEARCH_LIMIT),
cx,
);
let fs = self.fs.clone();
cx.spawn(|cx| async move {
let results = results.await;
let excerpts = results.into_iter().map(|result| {
let abs_path = result
.worktree
.read_with(&cx, |worktree, _| worktree.abs_path().join(&result.path));
let fs = fs.clone();
async move {
let path = result.path.clone();
let text = fs.load(&abs_path?).await?;
let mut start = result.range.start;
let mut end = result.range.end.min(text.len());
while !text.is_char_boundary(start) {
start += 1;
}
while !text.is_char_boundary(end) {
end -= 1;
}
anyhow::Ok(CodebaseExcerpt {
element_id: ElementId::Name(nanoid::nanoid!().into()),
expanded: false,
path: path.to_string_lossy().to_string().into(),
text: SharedString::from(text[start..end].to_string()),
score: result.score,
})
}
});
let excerpts = futures::future::join_all(excerpts)
.await
.into_iter()
.filter_map(|result| result.log_err())
.collect();
anyhow::Ok(ProjectIndexOutput { excerpts, status })
})
}
fn output_view(
_tool_call_id: String,
input: Self::Input,
output: Result<Self::Output>,
cx: &mut WindowContext,
) -> gpui::View<Self::View> {
cx.new_view(|_cx| ProjectIndexView { input, output })
}
fn status_view(&self, cx: &mut WindowContext) -> Option<AnyView> {
Some(
cx.new_view(|cx| ProjectIndexStatusView::new(self.project_index.clone(), cx))
.into(),
)
}
fn format(_input: &Self::Input, output: &Result<Self::Output>) -> String {
match &output {
Ok(output) => {
let mut body = "Semantic search results:\n".to_string();
if output.status != Status::Idle {
body.push_str("Still indexing. Results may be incomplete.\n");
}
if output.excerpts.is_empty() {
body.push_str("No results found");
return body;
}
for excerpt in &output.excerpts {
body.push_str("Excerpt from ");
body.push_str(excerpt.path.as_ref());
body.push_str(", score ");
body.push_str(&excerpt.score.to_string());
body.push_str(":\n");
body.push_str("~~~\n");
body.push_str(excerpt.text.as_ref());
body.push_str("~~~\n");
}
body
}
Err(err) => format!("Error: {}", err),
}
}
}
struct ProjectIndexStatusView {
project_index: Model<ProjectIndex>,
}
impl ProjectIndexStatusView {
pub fn new(project_index: Model<ProjectIndex>, cx: &mut ViewContext<Self>) -> Self {
cx.subscribe(&project_index, |_this, _, _status: &Status, cx| {
cx.notify();
})
.detach();
Self { project_index }
}
}
impl Render for ProjectIndexStatusView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let status = self.project_index.read(cx).status();
h_flex().gap_2().map(|element| match status {
Status::Idle => element.child(Label::new("Project index ready")),
Status::Loading => element.child(Label::new("Project index loading...")),
Status::Scanning { remaining_count } => element.child(Label::new(format!(
"Project index scanning: {remaining_count} remaining..."
))),
})
}
}

View File

@@ -0,0 +1,11 @@
mod chat_message;
mod composer;
#[cfg(feature = "stories")]
mod stories;
pub use chat_message::*;
pub use composer::*;
#[cfg(feature = "stories")]
pub use stories::*;

View File

@@ -0,0 +1,134 @@
use std::sync::Arc;
use client::User;
use gpui::{AnyElement, ClickEvent};
use ui::{prelude::*, Avatar};
use crate::MessageId;
pub enum UserOrAssistant {
User(Option<Arc<User>>),
Assistant,
}
#[derive(IntoElement)]
pub struct ChatMessage {
id: MessageId,
player: UserOrAssistant,
message: Option<AnyElement>,
collapsed: bool,
on_collapse_handle_click: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
}
impl ChatMessage {
pub fn new(
id: MessageId,
player: UserOrAssistant,
message: Option<AnyElement>,
collapsed: bool,
on_collapse_handle_click: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
) -> Self {
Self {
id,
player,
message,
collapsed,
on_collapse_handle_click,
}
}
}
impl RenderOnce for ChatMessage {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let collapse_handle_id = SharedString::from(format!("{}_collapse_handle", self.id.0));
let collapse_handle = h_flex()
.id(collapse_handle_id.clone())
.group(collapse_handle_id.clone())
.flex_none()
.justify_center()
.w_1()
.mx_2()
.h_full()
.on_click(self.on_collapse_handle_click)
.child(
div()
.w_px()
.h_full()
.rounded_lg()
.overflow_hidden()
.bg(cx.theme().colors().element_background)
.group_hover(collapse_handle_id, |this| {
this.bg(cx.theme().colors().element_hover)
}),
);
let content_padding = rems(1.);
// Clamp the message height to exactly 1.5 lines when collapsed.
let collapsed_height = content_padding.to_pixels(cx.rem_size()) + cx.line_height() * 1.5;
let content = self.message.map(|message| {
div()
.overflow_hidden()
.w_full()
.p(content_padding)
.rounded_lg()
.when(self.collapsed, |this| this.h(collapsed_height))
.bg(cx.theme().colors().surface_background)
.child(message)
});
v_flex()
.gap_1()
.child(ChatMessageHeader::new(self.player))
.child(h_flex().gap_3().child(collapse_handle).children(content))
}
}
#[derive(IntoElement)]
struct ChatMessageHeader {
player: UserOrAssistant,
contexts: Vec<()>,
}
impl ChatMessageHeader {
fn new(player: UserOrAssistant) -> Self {
Self {
player,
contexts: Vec::new(),
}
}
}
impl RenderOnce for ChatMessageHeader {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let (username, avatar_uri) = match self.player {
UserOrAssistant::Assistant => (
"Assistant".into(),
Some("https://zed.dev/assistant_avatar.png".into()),
),
UserOrAssistant::User(Some(user)) => {
(user.github_login.clone(), Some(user.avatar_uri.clone()))
}
UserOrAssistant::User(None) => ("You".into(), None),
};
h_flex()
.justify_between()
.child(
h_flex()
.gap_3()
.map(|this| {
let avatar_size = rems(20.0 / 16.0);
if let Some(avatar_uri) = avatar_uri {
this.child(Avatar::new(avatar_uri).size(avatar_size))
} else {
this.child(div().size(avatar_size))
}
})
.child(Label::new(username).color(Color::Default)),
)
.child(div().when(!self.contexts.is_empty(), |this| {
this.child(Label::new(self.contexts.len().to_string()).color(Color::Muted))
}))
}
}

View File

@@ -0,0 +1,220 @@
use assistant_tooling::ToolRegistry;
use client::User;
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{AnyElement, FontStyle, FontWeight, TextStyle, View, WeakView, WhiteSpace};
use settings::Settings;
use std::sync::Arc;
use theme::ThemeSettings;
use ui::{popover_menu, prelude::*, Avatar, ButtonLike, ContextMenu, Tooltip};
use crate::{AssistantChat, CompletionProvider, Submit, SubmitMode};
#[derive(IntoElement)]
pub struct Composer {
editor: View<Editor>,
player: Option<Arc<User>>,
can_submit: bool,
tool_registry: Arc<ToolRegistry>,
model_selector: AnyElement,
}
impl Composer {
pub fn new(
editor: View<Editor>,
player: Option<Arc<User>>,
can_submit: bool,
tool_registry: Arc<ToolRegistry>,
model_selector: AnyElement,
) -> Self {
Self {
editor,
player,
can_submit,
tool_registry,
model_selector,
}
}
}
impl RenderOnce for Composer {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let mut player_avatar = div().size(rems(20.0 / 16.0)).into_any_element();
if let Some(player) = self.player.clone() {
player_avatar = Avatar::new(player.avatar_uri.clone())
.size(rems(20.0 / 16.0))
.into_any_element();
}
let font_size = rems(0.875);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
h_flex()
.w_full()
.items_start()
.mt_4()
.gap_3()
.child(player_avatar)
.child(
v_flex()
.size_full()
.gap_1()
.pr_4()
.child(
v_flex()
.w_full()
.p_4()
.bg(cx.theme().colors().editor_background)
.rounded_lg()
.child(
v_flex()
.justify_between()
.w_full()
.gap_1()
.min_h(line_height * 4 + px(74.0))
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: font_size.into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: line_height.into(),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
})
.child(
h_flex()
.flex_none()
.gap_2()
.justify_between()
.w_full()
.child(
h_flex().gap_1().child(
// IconButton/button
// Toggle - if enabled, .selected(true).selected_style(IconButtonStyle::Filled)
//
// match status
// Tooltip::with_meta("some label explaining project index + status", "click to enable")
IconButton::new(
"add-context",
IconName::FileDoc,
)
.icon_color(Color::Muted),
), // .child(
// IconButton::new(
// "add-context",
// IconName::Plus,
// )
// .icon_color(Color::Muted),
// ),
)
.child(
Button::new("send-button", "Send")
.style(ButtonStyle::Filled)
.disabled(!self.can_submit)
.on_click(|_, cx| {
cx.dispatch_action(Box::new(Submit(
SubmitMode::Codebase,
)))
})
.tooltip(|cx| {
Tooltip::for_action(
"Submit message",
&Submit(SubmitMode::Codebase),
cx,
)
}),
),
),
),
)
.child(
h_flex()
.w_full()
.justify_between()
.child(self.model_selector)
.children(self.tool_registry.status_views().iter().cloned()),
),
)
}
}
#[derive(IntoElement)]
pub struct ModelSelector {
assistant_chat: WeakView<AssistantChat>,
model: String,
}
impl ModelSelector {
pub fn new(assistant_chat: WeakView<AssistantChat>, model: String) -> Self {
Self {
assistant_chat,
model,
}
}
}
impl RenderOnce for ModelSelector {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
popover_menu("model-switcher")
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::get(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();
move |_| Label::new(model.clone()).into_any_element()
},
{
let assistant_chat = self.assistant_chat.clone();
move |cx| {
_ = assistant_chat.update(cx, |assistant_chat, cx| {
assistant_chat.model = model.clone();
cx.notify();
});
}
},
);
}
menu
})
.into()
})
.trigger(
ButtonLike::new("active-model")
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(Label::new(self.model)),
)
.child(
div().child(Icon::new(IconName::ChevronDown).color(Color::Muted)),
),
)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Change Model", cx)),
)
.anchor(gpui::AnchorCorner::BottomRight)
}
}

View File

@@ -0,0 +1,3 @@
mod chat_message;
pub use chat_message::*;

View File

@@ -0,0 +1,101 @@
use std::sync::Arc;
use client::User;
use story::{StoryContainer, StoryItem, StorySection};
use ui::prelude::*;
use crate::ui::{ChatMessage, UserOrAssistant};
use crate::MessageId;
pub struct ChatMessageStory;
impl Render for ChatMessageStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let user_1 = Arc::new(User {
id: 12345,
github_login: "iamnbutler".into(),
avatar_uri: "https://avatars.githubusercontent.com/u/1714999?v=4".into(),
});
StoryContainer::new(
"ChatMessage Story",
"crates/assistant2/src/ui/stories/chat_message.rs",
)
.child(
StorySection::new()
.child(StoryItem::new(
"User chat message",
ChatMessage::new(
MessageId(0),
UserOrAssistant::User(Some(user_1.clone())),
Some(div().child("What can I do here?").into_any_element()),
false,
Box::new(|_, _| {}),
),
))
.child(StoryItem::new(
"User chat message (collapsed)",
ChatMessage::new(
MessageId(0),
UserOrAssistant::User(Some(user_1.clone())),
Some(div().child("What can I do here?").into_any_element()),
true,
Box::new(|_, _| {}),
),
)),
)
.child(
StorySection::new()
.child(StoryItem::new(
"Assistant chat message",
ChatMessage::new(
MessageId(0),
UserOrAssistant::Assistant,
Some(div().child("You can talk to me!").into_any_element()),
false,
Box::new(|_, _| {}),
),
))
.child(StoryItem::new(
"Assistant chat message (collapsed)",
ChatMessage::new(
MessageId(0),
UserOrAssistant::Assistant,
Some(div().child(MULTI_LINE_MESSAGE).into_any_element()),
true,
Box::new(|_, _| {}),
),
)),
)
.child(
StorySection::new().child(StoryItem::new(
"Conversation between user and assistant",
v_flex()
.gap_2()
.child(ChatMessage::new(
MessageId(0),
UserOrAssistant::User(Some(user_1.clone())),
Some(div().child("What is Rust??").into_any_element()),
false,
Box::new(|_, _| {}),
))
.child(ChatMessage::new(
MessageId(0),
UserOrAssistant::Assistant,
Some(div().child("Rust is a multi-paradigm programming language focused on performance and safety").into_any_element()),
false,
Box::new(|_, _| {}),
))
.child(ChatMessage::new(
MessageId(0),
UserOrAssistant::User(Some(user_1)),
Some(div().child("Sounds pretty cool!").into_any_element()),
false,
Box::new(|_, _| {}),
)),
)),
)
}
}
const MULTI_LINE_MESSAGE: &str = "In 2010, the movies nominated for the 82nd Academy Awards, for films released in 2009, were as follows. Note that 2010 nominees were announced for the ceremony happening in that year, but they honor movies from the previous year";

View File

@@ -0,0 +1,22 @@
[package]
name = "assistant_tooling"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant_tooling.rs"
[dependencies]
anyhow.workspace = true
gpui.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }

View File

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

View File

@@ -0,0 +1,208 @@
# Assistant Tooling
Bringing OpenAI compatible tool calling to GPUI.
This unlocks:
- **Structured Extraction** of model responses
- **Validation** of model inputs
- **Execution** of chosen toolsn
## Overview
Language Models can produce structured outputs that are perfect for calling functions. The most famous of these is OpenAI's tool calling. When make a chat completion you can pass a list of tools available to the model. The model will choose `0..n` tools to help them complete a user's task. It's up to _you_ to create the tools that the model can call.
> **User**: "Hey I need help with implementing a collapsible panel in GPUI"
>
> **Assistant**: "Sure, I can help with that. Let me see what I can find."
>
> `tool_calls: ["name": "query_codebase", arguments: "{ 'query': 'GPUI collapsible panel' }"]`
>
> `result: "['crates/gpui/src/panel.rs:12: impl Panel { ... }', 'crates/gpui/src/panel.rs:20: impl Panel { ... }']"`
>
> **Assistant**: "Here are some excerpts from the GPUI codebase that might help you."
This library is designed to facilitate this interaction mode by allowing you to go from `struct` to `tool` with a simple trait, `LanguageModelTool`.
## Example
Let's expose querying a semantic index directly by the model. First, we'll set up some _necessary_ imports
```rust
use anyhow::Result;
use assistant_tooling::{LanguageModelTool, ToolRegistry};
use gpui::{App, AppContext, Task};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::json;
```
Then we'll define the query structure the model must fill in. This _must_ derive `Deserialize` from `serde` and `JsonSchema` from the `schemars` crate.
```rust
#[derive(Deserialize, JsonSchema)]
struct CodebaseQuery {
query: String,
}
```
After that we can define our tool, with the expectation that it will need a `ProjectIndex` to search against. For this example, the index uses the same interface as `semantic_index::ProjectIndex`.
```rust
struct ProjectIndex {}
impl ProjectIndex {
fn new() -> Self {
ProjectIndex {}
}
fn search(&self, _query: &str, _limit: usize, _cx: &AppContext) -> Task<Result<Vec<String>>> {
// Instead of hooking up a real index, we're going to fake it
if _query.contains("gpui") {
return Task::ready(Ok(vec![r#"// crates/gpui/src/gpui.rs
//! # Welcome to GPUI!
//!
//! GPUI is a hybrid immediate and retained mode, GPU accelerated, UI framework
//! for Rust, designed to support a wide variety of applications
"#
.to_string()]));
}
return Task::ready(Ok(vec![]));
}
}
struct ProjectIndexTool {
project_index: ProjectIndex,
}
```
Now we can implement the `LanguageModelTool` trait for our tool by:
- Defining the `Input` from the model, which is `CodebaseQuery`
- Defining the `Output`
- Implementing the `name` and `description` functions to provide the model information when it's choosing a tool
- Implementing the `execute` function to run the tool
```rust
impl LanguageModelTool for ProjectIndexTool {
type Input = CodebaseQuery;
type Output = String;
fn name(&self) -> String {
"query_codebase".to_string()
}
fn description(&self) -> String {
"Executes a query against the codebase, returning excerpts related to the query".to_string()
}
fn execute(&self, query: Self::Input, cx: &AppContext) -> Task<Result<Self::Output>> {
let results = self.project_index.search(query.query.as_str(), 10, cx);
cx.spawn(|_cx| async move {
let results = results.await?;
if !results.is_empty() {
Ok(results.join("\n"))
} else {
Ok("No results".to_string())
}
})
}
}
```
For the sake of this example, let's look at the types that OpenAI will be passing to us
```rust
// OpenAI definitions, shown here for demonstration
#[derive(Deserialize)]
struct FunctionCall {
name: String,
args: String,
}
#[derive(Deserialize, Eq, PartialEq)]
enum ToolCallType {
#[serde(rename = "function")]
Function,
Other,
}
#[derive(Deserialize, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
struct ToolCallId(String);
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum ToolCall {
Function {
#[allow(dead_code)]
id: ToolCallId,
function: FunctionCall,
},
Other {
#[allow(dead_code)]
id: ToolCallId,
},
}
#[derive(Deserialize)]
struct AssistantMessage {
role: String,
content: Option<String>,
tool_calls: Option<Vec<ToolCall>>,
}
```
When the model wants to call tools, it will pass a list of `ToolCall`s. When those are `function`s that we can handle, we'll pass them to our `ToolRegistry` to get a future that we can await.
```rust
// Inside `fn main()`
App::new().run(|cx: &mut AppContext| {
let tool = ProjectIndexTool {
project_index: ProjectIndex::new(),
};
let mut registry = ToolRegistry::new();
let registered = registry.register(tool);
assert!(registered.is_ok());
```
Let's pretend the model sent us back a message requesting
```rust
let model_response = json!({
"role": "assistant",
"tool_calls": [
{
"id": "call_1",
"function": {
"name": "query_codebase",
"args": r#"{"query":"GPUI Task background_executor"}"#
},
"type": "function"
}
]
});
let message: AssistantMessage = serde_json::from_value(model_response).unwrap();
// We know there's a tool call, so let's skip straight to it for this example
let tool_calls = message.tool_calls.as_ref().unwrap();
let tool_call = tool_calls.get(0).unwrap();
```
We can now use our registry to call the tool.
```rust
let task = registry.call(
tool_call.name,
tool_call.args,
);
cx.spawn(|_cx| async move {
let result = task.await?;
println!("{}", result.unwrap());
Ok(())
})
```

View File

@@ -0,0 +1,5 @@
pub mod registry;
pub mod tool;
pub use crate::registry::ToolRegistry;
pub use crate::tool::{LanguageModelTool, ToolFunctionCall, ToolFunctionDefinition};

View File

@@ -0,0 +1,259 @@
use anyhow::{anyhow, Result};
use gpui::{AnyView, Task, WindowContext};
use std::collections::HashMap;
use crate::tool::{
LanguageModelTool, ToolFunctionCall, ToolFunctionCallResult, ToolFunctionDefinition,
};
pub struct ToolRegistry {
tools: HashMap<
String,
Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>,
>,
definitions: Vec<ToolFunctionDefinition>,
status_views: Vec<AnyView>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
definitions: Vec::new(),
status_views: Vec::new(),
}
}
pub fn definitions(&self) -> &[ToolFunctionDefinition] {
&self.definitions
}
pub fn register<T: 'static + LanguageModelTool>(
&mut self,
tool: T,
cx: &mut WindowContext,
) -> Result<()> {
self.definitions.push(tool.definition());
if let Some(tool_view) = tool.status_view(cx) {
self.status_views.push(tool_view);
}
let name = tool.name();
let previous = self.tools.insert(
name.clone(),
// registry.call(tool_call, cx)
Box::new(
move |tool_call: &ToolFunctionCall, cx: &mut WindowContext| {
let name = tool_call.name.clone();
let arguments = tool_call.arguments.clone();
let id = tool_call.id.clone();
let Ok(input) = serde_json::from_str::<T::Input>(arguments.as_str()) else {
return Task::ready(Ok(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::ParsingFailed),
}));
};
let result = tool.execute(&input, cx);
cx.spawn(move |mut cx| async move {
let result: Result<T::Output> = result.await;
let for_model = T::format(&input, &result);
let view = cx.update(|cx| T::output_view(id.clone(), input, result, cx))?;
Ok(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::Finished {
view: view.into(),
for_model,
}),
})
})
},
),
);
if previous.is_some() {
return Err(anyhow!("already registered a tool with name {}", name));
}
Ok(())
}
/// Task yields an error if the window for the given WindowContext is closed before the task completes.
pub fn call(
&self,
tool_call: &ToolFunctionCall,
cx: &mut WindowContext,
) -> Task<Result<ToolFunctionCall>> {
let name = tool_call.name.clone();
let arguments = tool_call.arguments.clone();
let id = tool_call.id.clone();
let tool = match self.tools.get(&name) {
Some(tool) => tool,
None => {
let name = name.clone();
return Task::ready(Ok(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::NoSuchTool),
}));
}
};
tool(tool_call, cx)
}
pub fn status_views(&self) -> &[AnyView] {
&self.status_views
}
}
#[cfg(test)]
mod test {
use super::*;
use gpui::{div, prelude::*, Render, TestAppContext};
use gpui::{EmptyView, View};
use schemars::schema_for;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Deserialize, Serialize, JsonSchema)]
struct WeatherQuery {
location: String,
unit: String,
}
struct WeatherTool {
current_weather: WeatherResult,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
struct WeatherResult {
location: String,
temperature: f64,
unit: String,
}
struct WeatherView {
result: WeatherResult,
}
impl Render for WeatherView {
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
div().child(format!("temperature: {}", self.result.temperature))
}
}
impl LanguageModelTool for WeatherTool {
type Input = WeatherQuery;
type Output = WeatherResult;
type View = WeatherView;
fn name(&self) -> String {
"get_current_weather".to_string()
}
fn description(&self) -> String {
"Fetches the current weather for a given location.".to_string()
}
fn execute(
&self,
input: &Self::Input,
_cx: &mut WindowContext,
) -> Task<Result<Self::Output>> {
let _location = input.location.clone();
let _unit = input.unit.clone();
let weather = self.current_weather.clone();
Task::ready(Ok(weather))
}
fn output_view(
_tool_call_id: String,
_input: Self::Input,
result: Result<Self::Output>,
cx: &mut WindowContext,
) -> View<Self::View> {
cx.new_view(|_cx| {
let result = result.unwrap();
WeatherView { result }
})
}
fn format(_: &Self::Input, output: &Result<Self::Output>) -> String {
serde_json::to_string(&output.as_ref().unwrap()).unwrap()
}
}
#[gpui::test]
async fn test_openai_weather_example(cx: &mut TestAppContext) {
cx.background_executor.run_until_parked();
let (_, cx) = cx.add_window_view(|_cx| EmptyView);
let tool = WeatherTool {
current_weather: WeatherResult {
location: "San Francisco".to_string(),
temperature: 21.0,
unit: "Celsius".to_string(),
},
};
let tools = vec![tool.definition()];
assert_eq!(tools.len(), 1);
let expected = ToolFunctionDefinition {
name: "get_current_weather".to_string(),
description: "Fetches the current weather for a given location.".to_string(),
parameters: schema_for!(WeatherQuery),
};
assert_eq!(tools[0].name, expected.name);
assert_eq!(tools[0].description, expected.description);
let expected_schema = serde_json::to_value(&tools[0].parameters).unwrap();
assert_eq!(
expected_schema,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "WeatherQuery",
"type": "object",
"properties": {
"location": {
"type": "string"
},
"unit": {
"type": "string"
}
},
"required": ["location", "unit"]
})
);
let args = json!({
"location": "San Francisco",
"unit": "Celsius"
});
let query: WeatherQuery = serde_json::from_value(args).unwrap();
let result = cx.update(|cx| tool.execute(&query, cx)).await;
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result, tool.current_weather);
}
}

View File

@@ -0,0 +1,111 @@
use anyhow::Result;
use gpui::{AnyElement, AnyView, IntoElement as _, Render, Task, View, WindowContext};
use schemars::{schema::RootSchema, schema_for, JsonSchema};
use serde::Deserialize;
use std::fmt::Display;
#[derive(Default, Deserialize)]
pub struct ToolFunctionCall {
pub id: String,
pub name: String,
pub arguments: String,
#[serde(skip)]
pub result: Option<ToolFunctionCallResult>,
}
pub enum ToolFunctionCallResult {
NoSuchTool,
ParsingFailed,
Finished { for_model: String, view: AnyView },
}
impl ToolFunctionCallResult {
pub fn format(&self, name: &String) -> String {
match self {
ToolFunctionCallResult::NoSuchTool => format!("No tool for {name}"),
ToolFunctionCallResult::ParsingFailed => {
format!("Unable to parse arguments for {name}")
}
ToolFunctionCallResult::Finished { for_model, .. } => for_model.clone(),
}
}
pub fn into_any_element(&self, name: &String) -> AnyElement {
match self {
ToolFunctionCallResult::NoSuchTool => {
format!("Language Model attempted to call {name}").into_any_element()
}
ToolFunctionCallResult::ParsingFailed => {
format!("Language Model called {name} with bad arguments").into_any_element()
}
ToolFunctionCallResult::Finished { view, .. } => view.clone().into_any_element(),
}
}
}
#[derive(Clone)]
pub struct ToolFunctionDefinition {
pub name: String,
pub description: String,
pub parameters: RootSchema,
}
impl Display for ToolFunctionDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let schema = serde_json::to_string(&self.parameters).ok();
let schema = schema.unwrap_or("None".to_string());
write!(f, "Name: {}:\n", self.name)?;
write!(f, "Description: {}\n", self.description)?;
write!(f, "Parameters: {}", schema)
}
}
pub trait LanguageModelTool {
/// The input type that will be passed in to `execute` when the tool is called
/// by the language model.
type Input: for<'de> Deserialize<'de> + JsonSchema;
/// The output returned by executing the tool.
type Output: 'static;
type View: Render;
/// Returns the name of the tool.
///
/// This name is exposed to the language model to allow the model to pick
/// which tools to use. As this name is used to identify the tool within a
/// tool registry, it should be unique.
fn name(&self) -> String;
/// Returns the description of the tool.
///
/// This can be used to _prompt_ the model as to what the tool does.
fn description(&self) -> String;
/// Returns the OpenAI Function definition for the tool, for direct use with OpenAI's API.
fn definition(&self) -> ToolFunctionDefinition {
let root_schema = schema_for!(Self::Input);
ToolFunctionDefinition {
name: self.name(),
description: self.description(),
parameters: root_schema,
}
}
/// Executes the tool with the given input.
fn execute(&self, input: &Self::Input, cx: &mut WindowContext) -> Task<Result<Self::Output>>;
fn format(input: &Self::Input, output: &Result<Self::Output>) -> String;
fn output_view(
tool_call_id: String,
input: Self::Input,
output: Result<Self::Output>,
cx: &mut WindowContext,
) -> View<Self::View>;
fn status_view(&self, _cx: &mut WindowContext) -> Option<AnyView> {
None
}
}

View File

@@ -32,6 +32,7 @@ use util::{
http::{HttpClient, HttpClientWithUrl},
ResultExt,
};
use workspace::notifications::NotificationId;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
@@ -242,7 +243,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
Some(tab_description),
cx,
);
workspace.add_item_to_active_pane(Box::new(view.clone()), cx);
workspace.add_item_to_active_pane(Box::new(view.clone()), None, cx);
cx.notify();
})
.log_err();
@@ -262,9 +263,11 @@ pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
workspace.show_notification(0, cx, |cx| {
cx.new_view(|_| UpdateNotification::new(version))
});
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_view(|_| UpdateNotification::new(version)),
);
updater
.read(cx)
.set_should_show_update_notification(false, cx)

View File

@@ -33,7 +33,7 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex().text_ui();
let element = h_flex().text_ui(cx);
let Some(active_item) = self.active_item.as_ref() else {
return element;
};
@@ -52,15 +52,22 @@ impl Render for Breadcrumbs {
Some(BreadcrumbText {
text: "".into(),
highlights: None,
font: None,
}),
);
}
let highlighted_segments = segments.into_iter().map(|segment| {
let mut text_style = cx.text_style();
if let Some(font) = segment.font {
text_style.font_family = font.family;
text_style.font_features = font.features;
text_style.font_style = font.style;
text_style.font_weight = font.weight;
}
text_style.color = Color::Muted.color(cx);
StyledText::new(segment.text)
StyledText::new(segment.text.replace('\n', ""))
.with_highlights(&text_style, segment.highlights.unwrap_or_default())
.into_any()
});

View File

@@ -432,7 +432,9 @@ impl ActiveCall {
room: Option<Model<Room>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
if room.as_ref() == self.room.as_ref().map(|room| &room.0) {
Task::ready(Ok(()))
} else {
cx.notify();
if let Some(room) = room {
if room.read(cx).status().is_offline() {
@@ -462,8 +464,6 @@ impl ActiveCall {
self.room = None;
Task::ready(Ok(()))
}
} else {
Task::ready(Ok(()))
}
}

View File

@@ -1182,7 +1182,7 @@ impl Room {
cx.emit(Event::RemoteProjectJoined { project_id: id });
cx.spawn(move |this, mut cx| async move {
let project =
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
Project::in_room(id, client, user_store, language_registry, fs, cx.clone()).await?;
this.update(&mut cx, |this, cx| {
this.joined_projects.retain(|project| {
@@ -1203,14 +1203,24 @@ impl Room {
project: Model<Project>,
cx: &mut ModelContext<Self>,
) -> Task<Result<u64>> {
if let Some(project_id) = project.read(cx).remote_id() {
return Task::ready(Ok(project_id));
}
let request = if let Some(remote_project_id) = project.read(cx).remote_project_id() {
self.client.request(proto::ShareProject {
room_id: self.id(),
worktrees: vec![],
remote_project_id: Some(remote_project_id.0),
})
} else {
if let Some(project_id) = project.read(cx).remote_id() {
return Task::ready(Ok(project_id));
}
self.client.request(proto::ShareProject {
room_id: self.id(),
worktrees: project.read(cx).worktree_metadata_protos(cx),
remote_project_id: None,
})
};
let request = self.client.request(proto::ShareProject {
room_id: self.id(),
worktrees: project.read(cx).worktree_metadata_protos(cx),
});
cx.spawn(|this, mut cx| async move {
let response = request.await?;

View File

@@ -652,13 +652,27 @@ impl ChannelChat {
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
if let Some(item) = cursor.item() {
if item.id == ChannelMessageId::Saved(id) {
let ix = messages.summary().count;
let deleted_message_ix = messages.summary().count;
cursor.next(&());
messages.append(cursor.suffix(&()), &());
drop(cursor);
self.messages = messages;
// If the message that was deleted was the last acknowledged message,
// replace the acknowledged message with an earlier one.
self.channel_store.update(cx, |store, _| {
let summary = self.messages.summary();
if summary.count == 0 {
store.set_acknowledged_message_id(self.channel_id, None);
} else if deleted_message_ix == summary.count {
if let ChannelMessageId::Saved(id) = summary.max_id {
store.set_acknowledged_message_id(self.channel_id, Some(id));
}
}
});
cx.emit(ChannelChatEvent::MessagesUpdated {
old_range: ix..ix + 1,
old_range: deleted_message_ix..deleted_message_ix + 1,
new_count: 0,
});
}

View File

@@ -40,7 +40,6 @@ pub struct HostedProject {
name: SharedString,
_visibility: proto::ChannelVisibility,
}
impl From<proto::HostedProject> for HostedProject {
fn from(project: proto::HostedProject) -> Self {
Self {
@@ -51,7 +50,6 @@ impl From<proto::HostedProject> for HostedProject {
}
}
}
pub struct ChannelStore {
pub channel_index: ChannelIndex,
channel_invitations: Vec<Arc<Channel>>,
@@ -380,6 +378,12 @@ impl ChannelStore {
.is_some_and(|state| state.has_new_messages())
}
pub fn set_acknowledged_message_id(&mut self, channel_id: ChannelId, message_id: Option<u64>) {
if let Some(state) = self.channel_states.get_mut(&channel_id) {
state.latest_chat_message = message_id;
}
}
pub fn last_acknowledge_message_id(&self, channel_id: ChannelId) -> Option<u64> {
self.channel_states.get(&channel_id).and_then(|state| {
if let Some(last_message_id) = state.latest_chat_message {
@@ -811,7 +815,6 @@ impl ChannelStore {
Ok(())
})
}
pub fn get_channel_member_details(
&self,
channel_id: ChannelId,

View File

@@ -264,7 +264,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
);
assert_eq!(
channel.next_event(cx),
channel.next_event(cx).await,
ChannelChatEvent::MessagesUpdated {
old_range: 2..2,
new_count: 1,
@@ -317,7 +317,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
);
assert_eq!(
channel.next_event(cx),
channel.next_event(cx).await,
ChannelChatEvent::MessagesUpdated {
old_range: 0..0,
new_count: 2,

View File

@@ -20,6 +20,7 @@ path = "src/main.rs"
anyhow.workspace = true
clap.workspace = true
ipc-channel = "0.18"
release_channel.workspace = true
serde.workspace = true
util.workspace = true

View File

@@ -7,7 +7,7 @@ use serde::Deserialize;
use std::{
env,
ffi::OsStr,
fs::{self},
fs,
path::{Path, PathBuf},
};
use util::paths::PathLikeWithPosition;
@@ -36,6 +36,9 @@ struct Args {
/// Custom Zed.app path
#[arg(short, long)]
bundle_path: Option<PathBuf>,
/// Run zed in dev-server mode
#[arg(long)]
dev_server_token: Option<String>,
}
fn parse_path_with_position(
@@ -53,10 +56,24 @@ struct InfoPlist {
}
fn main() -> Result<()> {
// Intercept version designators
#[cfg(target_os = "macos")]
if let Some(channel) = std::env::args().nth(1).filter(|arg| arg.starts_with("--")) {
// When the first argument is a name of a release channel, we're gonna spawn off a cli of that version, with trailing args passed along.
use std::str::FromStr as _;
if let Ok(channel) = release_channel::ReleaseChannel::from_str(&channel[2..]) {
return mac_os::spawn_channel_cli(channel, std::env::args().skip(2).collect());
}
}
let args = Args::parse();
let bundle = Bundle::detect(args.bundle_path.as_deref()).context("Bundle detection")?;
if let Some(dev_server_token) = args.dev_server_token {
return bundle.spawn(vec!["--dev-server-token".into(), dev_server_token]);
}
if args.version {
println!("{}", bundle.zed_version_string());
return Ok(());
@@ -159,6 +176,10 @@ mod linux {
unimplemented!()
}
pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
unimplemented!()
}
pub fn zed_version_string(&self) -> String {
unimplemented!()
}
@@ -192,6 +213,10 @@ mod windows {
unimplemented!()
}
pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
unimplemented!()
}
pub fn zed_version_string(&self) -> String {
unimplemented!()
}
@@ -200,14 +225,14 @@ mod windows {
#[cfg(target_os = "macos")]
mod mac_os {
use anyhow::Context;
use anyhow::{Context, Result};
use core_foundation::{
array::{CFArray, CFIndex},
string::kCFStringEncodingUTF8,
url::{CFURLCreateWithBytes, CFURL},
};
use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
use std::{fs, path::Path, ptr};
use std::{fs, path::Path, process::Command, ptr};
use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME};
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
@@ -268,6 +293,15 @@ mod mac_os {
}
}
pub fn spawn(&self, args: Vec<String>) -> Result<()> {
let path = match self {
Self::App { app_bundle, .. } => app_bundle.join("Contents/MacOS/zed"),
Self::LocalPath { executable, .. } => executable.clone(),
};
Command::new(path).args(args).status()?;
Ok(())
}
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
let (server, server_name) =
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
@@ -348,4 +382,33 @@ mod mac_os {
)
}
}
pub(super) fn spawn_channel_cli(
channel: release_channel::ReleaseChannel,
leftover_args: Vec<String>,
) -> Result<()> {
use anyhow::bail;
let app_id_prompt = format!("id of app \"{}\"", channel.display_name());
let app_id_output = Command::new("osascript")
.arg("-e")
.arg(&app_id_prompt)
.output()?;
if !app_id_output.status.success() {
bail!("Could not determine app id for {}", channel.display_name());
}
let app_name = String::from_utf8(app_id_output.stdout)?.trim().to_owned();
let app_path_prompt = format!("kMDItemCFBundleIdentifier == '{app_name}'");
let app_path_output = Command::new("mdfind").arg(app_path_prompt).output()?;
if !app_path_output.status.success() {
bail!(
"Could not determine app path for {}",
channel.display_name()
);
}
let app_path = String::from_utf8(app_path_output.stdout)?.trim().to_owned();
let cli_path = format!("{app_path}/Contents/MacOS/cli");
Command::new(cli_path).args(leftover_args).spawn()?;
Ok(())
}
}

View File

@@ -132,7 +132,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
move |_: &SignOut, cx| {
if let Some(client) = client.upgrade() {
cx.spawn(|cx| async move {
client.disconnect(&cx);
client.sign_out(&cx).await;
})
.detach();
}
@@ -457,6 +457,14 @@ impl Client {
})
}
pub fn production(cx: &mut AppContext) -> Arc<Self> {
let clock = Arc::new(clock::RealSystemClock);
let http = Arc::new(HttpClientWithUrl::new(
&ClientSettings::get_global(cx).server_url,
));
Self::new(clock, http.clone(), cx)
}
pub fn id(&self) -> u64 {
self.id.load(Ordering::SeqCst)
}
@@ -759,8 +767,9 @@ impl Client {
read_credentials_from_keychain(cx).await.is_some()
}
pub fn set_dev_server_token(&self, token: DevServerToken) {
pub fn set_dev_server_token(&self, token: DevServerToken) -> &Self {
self.state.write().credentials = Some(Credentials::DevServer { token });
self
}
#[async_recursion(?Send)]
@@ -1118,6 +1127,8 @@ impl Client {
if let Some((login, token)) =
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
{
eprintln!("authenticate as admin {login}, {token}");
return Self::authenticate_as_admin(http, login.clone(), token.clone())
.await;
}
@@ -1249,6 +1260,15 @@ impl Client {
})
}
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncAppContext) {
self.state.write().credentials = None;
self.disconnect(&cx);
if self.has_keychain_credentials(cx).await {
delete_credentials_from_keychain(cx).await.log_err();
}
}
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
self.peer.teardown();
self.set_status(Status::SignedOut, cx);

View File

@@ -421,7 +421,7 @@ impl Telemetry {
return;
}
let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else {
if ZED_CLIENT_CHECKSUM_SEED.is_none() {
return;
};
@@ -466,15 +466,9 @@ impl Telemetry {
serde_json::to_writer(&mut json_bytes, &request_body)?;
}
let mut summer = Sha256::new();
summer.update(checksum_seed);
summer.update(&json_bytes);
summer.update(checksum_seed);
let mut checksum = String::new();
for byte in summer.finalize().as_slice() {
use std::fmt::Write;
write!(&mut checksum, "{:02x}", byte).unwrap();
}
let Some(checksum) = calculate_json_checksum(&json_bytes) else {
return Ok(());
};
let request = http::Request::builder()
.method(Method::POST)
@@ -657,3 +651,21 @@ mod tests {
&& telemetry.state.lock().first_event_date_time.is_none()
}
}
pub fn calculate_json_checksum(json: &impl AsRef<[u8]>) -> Option<String> {
let Some(checksum_seed) = &*ZED_CLIENT_CHECKSUM_SEED else {
return None;
};
let mut summer = Sha256::new();
summer.update(checksum_seed);
summer.update(&json);
summer.update(checksum_seed);
let mut checksum = String::new();
for byte in summer.finalize().as_slice() {
use std::fmt::Write;
write!(&mut checksum, "{:02x}", byte).unwrap();
}
Some(checksum)
}

View File

@@ -27,6 +27,14 @@ impl std::fmt::Display for ChannelId {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct ProjectId(pub u64);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct DevServerId(pub u64);
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
)]
pub struct RemoteProjectId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParticipantIndex(pub u32);

View File

@@ -1,5 +1,5 @@
DATABASE_URL = "postgres://postgres@localhost/zed"
# DATABASE_URL = "sqlite:////home/zed/.config/zed/db.sqlite3?mode=rwc"
# DATABASE_URL = "sqlite:////root/0/zed/db.sqlite3?mode=rwc"
DATABASE_MAX_CONNECTIONS = 5
HTTP_PORT = 8080
API_TOKEN = "secret"

View File

@@ -37,7 +37,7 @@ google_ai.workspace = true
hex.workspace = true
live_kit_server.workspace = true
log.workspace = true
nanoid = "0.4"
nanoid.workspace = true
open_ai.workspace = true
parking_lot.workspace = true
prometheus = "0.13"
@@ -63,8 +63,8 @@ tokio.workspace = true
toml.workspace = true
tower = "0.4"
tower-http = { workspace = true, features = ["trace"] }
tracing = "0.1.34"
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json", "registry", "tracing-log"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
util.workspace = true
uuid.workspace = true
@@ -93,6 +93,7 @@ notifications = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] }
release_channel.workspace = true
remote_projects.workspace = true
rpc = { workspace = true, features = ["test-support"] }
sea-orm = { version = "0.12.x", features = ["sqlx-sqlite"] }
serde_json.workspace = true
@@ -102,3 +103,4 @@ theme.workspace = true
unindent.workspace = true
util.workspace = true
workspace = { workspace = true, features = ["test-support"] }
headless.workspace = true

View File

@@ -6,7 +6,43 @@ It contains our back-end logic for collaboration, to which we connect from the Z
# Local Development
Detailed instructions on getting started are [here](https://zed.dev/docs/local-collaboration).
## Database setup
Before you can run the collab server locally, you'll need to set up a zed Postgres database.
```
script/bootstrap
```
This script will set up the `zed` Postgres database, and populate it with some users. It requires internet access, because it fetches some users from the GitHub API.
The script will create several _admin_ users, who you'll sign in as by default when developing locally. The GitHub logins for the default users are specified in the `seed.default.json` file.
To use a different set of admin users, create `crates/collab/seed.json`.
```json
{
"admins": ["yourgithubhere"],
"channels": ["zed"],
"number_of_users": 20
}
```
## Testing collaborative features locally
In one terminal, run Zed's collaboration server and the livekit dev server:
```
foreman start
```
In a second terminal, run two or more instances of Zed.
```
script/zed-local -2
```
This script starts one to four instances of Zed, depending on the `-2`, `-3` or `-4` flags. Each instance will be connected to the local `collab` server, signed in as a different user from `seed.json` or `seed.default.json`.
# Deployment

View File

@@ -45,12 +45,13 @@ CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id");
CREATE TABLE "projects" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL,
"room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE,
"host_user_id" INTEGER REFERENCES users (id),
"host_connection_id" INTEGER,
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
"hosted_project_id" INTEGER REFERENCES hosted_projects (id)
"hosted_project_id" INTEGER REFERENCES hosted_projects (id),
"remote_project_id" INTEGER REFERENCES remote_projects(id)
);
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
@@ -404,8 +405,15 @@ CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_project
CREATE TABLE dev_servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id),
user_id INTEGER NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
hashed_token TEXT NOT NULL
);
CREATE INDEX idx_dev_servers_on_channel_id ON dev_servers (channel_id);
CREATE TABLE remote_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dev_server_id INTEGER NOT NULL REFERENCES dev_servers(id),
path TEXT NOT NULL
);
ALTER TABLE hosted_projects ADD COLUMN remote_project_id INTEGER REFERENCES remote_projects(id);

View File

@@ -0,0 +1,9 @@
CREATE TABLE remote_projects (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
channel_id INT NOT NULL REFERENCES channels(id),
dev_server_id INT NOT NULL REFERENCES dev_servers(id),
name TEXT NOT NULL,
path TEXT NOT NULL
);
ALTER TABLE projects ADD COLUMN remote_project_id INTEGER REFERENCES remote_projects(id);

View File

@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS "embeddings" (
"model" TEXT,
"digest" BYTEA,
"dimensions" FLOAT4[1536],
"retrieved_at" TIMESTAMP NOT NULL DEFAULT now(),
PRIMARY KEY ("model", "digest")
);
CREATE INDEX IF NOT EXISTS "idx_retrieved_at_on_embeddings" ON "embeddings" ("retrieved_at");

View File

@@ -0,0 +1,7 @@
DELETE FROM remote_projects;
DELETE FROM dev_servers;
ALTER TABLE dev_servers DROP COLUMN channel_id;
ALTER TABLE dev_servers ADD COLUMN user_id INT NOT NULL REFERENCES users(id);
ALTER TABLE remote_projects DROP COLUMN channel_id;

View File

@@ -0,0 +1,3 @@
ALTER TABLE remote_projects DROP COLUMN name;
ALTER TABLE remote_projects
ADD CONSTRAINT unique_path_constraint UNIQUE(dev_server_id, path);

View File

@@ -5,7 +5,8 @@
"maxbrunsfeld",
"iamnbutler",
"mikayla-maki",
"JosephTLyons"
"JosephTLyons",
"rgbkrk"
],
"channels": ["zed"],
"number_of_users": 100

View File

@@ -1,5 +1,6 @@
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context as _, Result};
use rpc::proto;
use util::ResultExt as _;
pub fn language_model_request_to_open_ai(
request: proto::CompleteWithLanguageModel,
@@ -9,24 +10,83 @@ pub fn language_model_request_to_open_ai(
messages: request
.messages
.into_iter()
.map(|message| {
.map(|message: proto::LanguageModelRequestMessage| {
let role = proto::LanguageModelRole::from_i32(message.role)
.ok_or_else(|| anyhow!("invalid role {}", message.role))?;
Ok(open_ai::RequestMessage {
role: match role {
proto::LanguageModelRole::LanguageModelUser => open_ai::Role::User,
proto::LanguageModelRole::LanguageModelAssistant => {
open_ai::Role::Assistant
}
proto::LanguageModelRole::LanguageModelSystem => open_ai::Role::System,
let openai_message = match role {
proto::LanguageModelRole::LanguageModelUser => open_ai::RequestMessage::User {
content: message.content,
},
content: message.content,
})
proto::LanguageModelRole::LanguageModelAssistant => {
open_ai::RequestMessage::Assistant {
content: Some(message.content),
tool_calls: message
.tool_calls
.into_iter()
.filter_map(|call| {
Some(open_ai::ToolCall {
id: call.id,
content: match call.variant? {
proto::tool_call::Variant::Function(f) => {
open_ai::ToolCallContent::Function {
function: open_ai::FunctionContent {
name: f.name,
arguments: f.arguments,
},
}
}
},
})
})
.collect(),
}
}
proto::LanguageModelRole::LanguageModelSystem => {
open_ai::RequestMessage::System {
content: message.content,
}
}
proto::LanguageModelRole::LanguageModelTool => open_ai::RequestMessage::Tool {
tool_call_id: message
.tool_call_id
.ok_or_else(|| anyhow!("tool message is missing tool call id"))?,
content: message.content,
},
};
Ok(openai_message)
})
.collect::<Result<Vec<open_ai::RequestMessage>>>()?,
stream: true,
stop: request.stop,
temperature: request.temperature,
tools: request
.tools
.into_iter()
.filter_map(|tool| {
Some(match tool.variant? {
proto::chat_completion_tool::Variant::Function(f) => {
open_ai::ToolDefinition::Function {
function: open_ai::FunctionDefinition {
name: f.name,
description: f.description,
parameters: if let Some(params) = &f.parameters {
Some(
serde_json::from_str(params)
.context("failed to deserialize tool parameters")
.log_err()?,
)
} else {
None
},
},
}
}
})
})
.collect(),
tool_choice: request.tool_choice,
})
}
@@ -58,6 +118,9 @@ pub fn language_model_request_message_to_google_ai(
proto::LanguageModelRole::LanguageModelUser => google_ai::Role::User,
proto::LanguageModelRole::LanguageModelAssistant => google_ai::Role::Model,
proto::LanguageModelRole::LanguageModelSystem => google_ai::Role::User,
proto::LanguageModelRole::LanguageModelTool => {
Err(anyhow!("we don't handle tool calls with google ai yet"))?
}
},
})
}

View File

@@ -18,11 +18,15 @@ use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, CopilotEvent, CpuEvent, EditEvent,
EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, MemoryEvent, SettingEvent,
};
use uuid::Uuid;
static CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
pub fn router() -> Router {
Router::new()
.route("/telemetry/events", post(post_events))
.route("/telemetry/crashes", post(post_crash))
.route("/telemetry/hangs", post(post_hang))
}
pub struct ZedChecksumHeader(Vec<u8>);
@@ -85,8 +89,6 @@ pub async fn post_crash(
headers: HeaderMap,
body: Bytes,
) -> Result<()> {
static CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
let report = IpsFile::parse(&body)?;
let version_threshold = SemanticVersion::new(0, 123, 0);
@@ -136,6 +138,13 @@ pub async fn post_crash(
.get("x-zed-panicked-on")
.and_then(|h| h.to_str().ok())
.and_then(|s| s.parse().ok());
let installation_id = headers
.get("x-zed-installation-id")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
.unwrap_or_default();
let mut recent_panic = None;
if let Some(recent_panic_on) = recent_panic_on {
@@ -160,6 +169,7 @@ pub async fn post_crash(
os_version = %report.header.os_version,
bundle_id = %report.header.bundle_id,
incident_id = %report.header.incident_id,
installation_id = %installation_id,
description = %description,
backtrace = %summary,
"crash report");
@@ -214,6 +224,107 @@ pub async fn post_crash(
Ok(())
}
pub async fn post_hang(
Extension(app): Extension<Arc<AppState>>,
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
body: Bytes,
) -> Result<()> {
let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
return Err(Error::Http(
StatusCode::INTERNAL_SERVER_ERROR,
"events not enabled".into(),
))?;
};
if checksum != expected {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid checksum".into(),
))?;
}
let incident_id = Uuid::new_v4().to_string();
// dump JSON into S3 so we can get frame offsets if we need to.
if let Some(blob_store_client) = app.blob_store_client.as_ref() {
blob_store_client
.put_object()
.bucket(CRASH_REPORTS_BUCKET)
.key(incident_id.clone() + ".hang.json")
.acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
.body(ByteStream::from(body.to_vec()))
.send()
.await
.map_err(|e| log::error!("Failed to upload crash: {}", e))
.ok();
}
let report: telemetry_events::HangReport = serde_json::from_slice(&body).map_err(|err| {
log::error!("can't parse report json: {err}");
Error::Internal(anyhow!(err))
})?;
let mut backtrace = "Possible hang detected on main threadL".to_string();
let unknown = "<unknown>".to_string();
for frame in report.backtrace.iter() {
backtrace.push_str(&format!("\n{}", frame.symbols.first().unwrap_or(&unknown)));
}
tracing::error!(
service = "client",
version = %report.app_version.unwrap_or_default().to_string(),
os_name = %report.os_name,
os_version = report.os_version.unwrap_or_default().to_string(),
incident_id = %incident_id,
installation_id = %report.installation_id.unwrap_or_default(),
backtrace = %backtrace,
"hang report");
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
let payload = slack::WebhookBody::new(|w| {
w.add_section(|s| s.text(slack::Text::markdown("Possible Hang".to_string())))
.add_section(|s| {
s.add_field(slack::Text::markdown(format!(
"*Version:*\n {} ",
report.app_version.unwrap_or_default()
)))
.add_field({
let hostname = app.config.blob_store_url.clone().unwrap_or_default();
let hostname = hostname.strip_prefix("https://").unwrap_or_else(|| {
hostname.strip_prefix("http://").unwrap_or_default()
});
slack::Text::markdown(format!(
"*Incident:*\n<https://{}.{}/{}.hang.json|{}…>",
CRASH_REPORTS_BUCKET,
hostname,
incident_id,
incident_id.chars().take(8).collect::<String>(),
))
})
})
.add_rich_text(|r| r.add_preformatted(|p| p.add_text(backtrace)))
});
let payload_json = serde_json::to_string(&payload).map_err(|err| {
log::error!("Failed to serialize payload to JSON: {err}");
Error::Internal(anyhow!(err))
})?;
reqwest::Client::new()
.post(slack_panics_webhook)
.header("Content-Type", "application/json")
.body(payload_json)
.send()
.await
.map_err(|err| {
log::error!("Failed to send payload to Slack: {err}");
Error::Internal(anyhow!(err))
})?;
}
Ok(())
}
pub async fn post_events(
Extension(app): Extension<Arc<AppState>>,
TypedHeader(ZedChecksumHeader(checksum)): TypedHeader<ZedChecksumHeader>,
@@ -227,19 +338,14 @@ pub async fn post_events(
))?
};
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
return Err(Error::Http(
StatusCode::INTERNAL_SERVER_ERROR,
"events not enabled".into(),
))?;
};
let mut summer = Sha256::new();
summer.update(checksum_seed);
summer.update(&body);
summer.update(checksum_seed);
if &checksum != &summer.finalize()[..] {
if checksum != expected {
return Err(Error::Http(
StatusCode::BAD_REQUEST,
"invalid checksum".into(),
@@ -1053,3 +1159,15 @@ impl ActionEventRow {
}
}
}
pub fn calculate_json_checksum(app: Arc<AppState>, json: &impl AsRef<[u8]>) -> Option<Vec<u8>> {
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
return None;
};
let mut summer = Sha256::new();
summer.update(checksum_seed);
summer.update(&json);
summer.update(checksum_seed);
Some(summer.finalize().into_iter().collect())
}

View File

@@ -106,8 +106,12 @@ async fn get_extension_versions(
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionParams {
struct DownloadLatestExtensionPathParams {
extension_id: String,
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionQueryParams {
min_schema_version: Option<i32>,
max_schema_version: Option<i32>,
min_wasm_api_version: Option<SemanticVersion>,
@@ -116,13 +120,14 @@ struct DownloadLatestExtensionParams {
async fn download_latest_extension(
Extension(app): Extension<Arc<AppState>>,
Path(params): Path<DownloadLatestExtensionParams>,
Path(params): Path<DownloadLatestExtensionPathParams>,
Query(query): Query<DownloadLatestExtensionQueryParams>,
) -> Result<Redirect> {
let constraints = maybe!({
let min_schema_version = params.min_schema_version?;
let max_schema_version = params.max_schema_version?;
let min_wasm_api_version = params.min_wasm_api_version?;
let max_wasm_api_version = params.max_wasm_api_version?;
let min_schema_version = query.min_schema_version?;
let max_schema_version = query.max_schema_version?;
let min_wasm_api_version = query.min_wasm_api_version?;
let max_wasm_api_version = query.max_wasm_api_version?;
Some(ExtensionVersionConstraints {
schema_versions: min_schema_version..=max_schema_version,

View File

@@ -10,6 +10,7 @@ use axum::{
response::IntoResponse,
};
use prometheus::{exponential_buckets, register_histogram, Histogram};
pub use rpc::auth::random_token;
use scrypt::{
password_hash::{PasswordHash, PasswordVerifier},
Scrypt,
@@ -152,7 +153,7 @@ pub async fn create_access_token(
/// Hashing prevents anyone with access to the database being able to login.
/// As the token is randomly generated, we don't need to worry about scrypt-style
/// protection.
fn hash_access_token(token: &str) -> String {
pub fn hash_access_token(token: &str) -> String {
let digest = sha2::Sha256::digest(token);
format!(
"$sha256${}",
@@ -230,18 +231,15 @@ pub async fn verify_access_token(
})
}
// a dev_server_token has the format <id>.<base64>. This is to make them
// relatively easy to copy/paste around.
pub fn generate_dev_server_token(id: usize, access_token: String) -> String {
format!("{}.{}", id, access_token)
}
pub async fn verify_dev_server_token(
dev_server_token: &str,
db: &Arc<Database>,
) -> anyhow::Result<dev_server::Model> {
let mut parts = dev_server_token.splitn(2, '.');
let id = DevServerId(parts.next().unwrap_or_default().parse()?);
let token = parts
.next()
.ok_or_else(|| anyhow!("invalid dev server token format"))?;
let (id, token) = split_dev_server_token(dev_server_token)?;
let token_hash = hash_access_token(&token);
let server = db.get_dev_server(id).await?;
@@ -257,6 +255,17 @@ pub async fn verify_dev_server_token(
}
}
// a dev_server_token has the format <id>.<base64>. This is to make them
// relatively easy to copy/paste around.
pub fn split_dev_server_token(dev_server_token: &str) -> anyhow::Result<(DevServerId, &str)> {
let mut parts = dev_server_token.splitn(2, '.');
let id = DevServerId(parts.next().unwrap_or_default().parse()?);
let token = parts
.next()
.ok_or_else(|| anyhow!("invalid dev server token format"))?;
Ok((id, token))
}
#[cfg(test)]
mod test {
use rand::thread_rng;

View File

@@ -56,6 +56,7 @@ pub struct Database {
options: ConnectOptions,
pool: DatabaseConnection,
rooms: DashMap<RoomId, Arc<Mutex<()>>>,
projects: DashMap<ProjectId, Arc<Mutex<()>>>,
rng: Mutex<StdRng>,
executor: Executor,
notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
@@ -74,6 +75,7 @@ impl Database {
options: options.clone(),
pool: sea_orm::Database::connect(options).await?,
rooms: DashMap::with_capacity(16384),
projects: DashMap::with_capacity(16384),
rng: Mutex::new(StdRng::seed_from_u64(0)),
notification_kinds_by_id: HashMap::default(),
notification_kinds_by_name: HashMap::default(),
@@ -86,6 +88,7 @@ impl Database {
#[cfg(test)]
pub fn reset(&self) {
self.rooms.clear();
self.projects.clear();
}
/// Runs the database migrations.
@@ -190,7 +193,10 @@ impl Database {
}
/// The same as room_transaction, but if you need to only optionally return a Room.
async fn optional_room_transaction<F, Fut, T>(&self, f: F) -> Result<Option<RoomGuard<T>>>
async fn optional_room_transaction<F, Fut, T>(
&self,
f: F,
) -> Result<Option<TransactionGuard<T>>>
where
F: Send + Fn(TransactionHandle) -> Fut,
Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
@@ -205,7 +211,7 @@ impl Database {
let _guard = lock.lock_owned().await;
match tx.commit().await.map_err(Into::into) {
Ok(()) => {
return Ok(Some(RoomGuard {
return Ok(Some(TransactionGuard {
data,
_guard,
_not_send: PhantomData,
@@ -240,10 +246,63 @@ impl Database {
self.run(body).await
}
async fn project_transaction<F, Fut, T>(
&self,
project_id: ProjectId,
f: F,
) -> Result<TransactionGuard<T>>
where
F: Send + Fn(TransactionHandle) -> Fut,
Fut: Send + Future<Output = Result<T>>,
{
let room_id = Database::room_id_for_project(&self, project_id).await?;
let body = async {
let mut i = 0;
loop {
let lock = if let Some(room_id) = room_id {
self.rooms.entry(room_id).or_default().clone()
} else {
self.projects.entry(project_id).or_default().clone()
};
let _guard = lock.lock_owned().await;
let (tx, result) = self.with_transaction(&f).await?;
match result {
Ok(data) => match tx.commit().await.map_err(Into::into) {
Ok(()) => {
return Ok(TransactionGuard {
data,
_guard,
_not_send: PhantomData,
});
}
Err(error) => {
if !self.retry_on_serialization_error(&error, i).await {
return Err(error);
}
}
},
Err(error) => {
tx.rollback().await?;
if !self.retry_on_serialization_error(&error, i).await {
return Err(error);
}
}
}
i += 1;
}
};
self.run(body).await
}
/// room_transaction runs the block in a transaction. It returns a RoomGuard, that keeps
/// the database locked until it is dropped. This ensures that updates sent to clients are
/// properly serialized with respect to database changes.
async fn room_transaction<F, Fut, T>(&self, room_id: RoomId, f: F) -> Result<RoomGuard<T>>
async fn room_transaction<F, Fut, T>(
&self,
room_id: RoomId,
f: F,
) -> Result<TransactionGuard<T>>
where
F: Send + Fn(TransactionHandle) -> Fut,
Fut: Send + Future<Output = Result<T>>,
@@ -257,7 +316,7 @@ impl Database {
match result {
Ok(data) => match tx.commit().await.map_err(Into::into) {
Ok(()) => {
return Ok(RoomGuard {
return Ok(TransactionGuard {
data,
_guard,
_not_send: PhantomData,
@@ -399,15 +458,16 @@ impl Deref for TransactionHandle {
}
}
/// [`RoomGuard`] keeps a database transaction alive until it is dropped.
/// so that updates to rooms are serialized.
pub struct RoomGuard<T> {
/// [`TransactionGuard`] keeps a database transaction alive until it is dropped.
/// It wraps data that depends on the state of the database and prevents an additional
/// transaction from starting that would invalidate that data.
pub struct TransactionGuard<T> {
data: T,
_guard: OwnedMutexGuard<()>,
_not_send: PhantomData<Rc<()>>,
}
impl<T> Deref for RoomGuard<T> {
impl<T> Deref for TransactionGuard<T> {
type Target = T;
fn deref(&self) -> &T {
@@ -415,13 +475,13 @@ impl<T> Deref for RoomGuard<T> {
}
}
impl<T> DerefMut for RoomGuard<T> {
impl<T> DerefMut for TransactionGuard<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.data
}
}
impl<T> RoomGuard<T> {
impl<T> TransactionGuard<T> {
/// Returns the inner value of the guard.
pub fn into_inner(self) -> T {
self.data
@@ -518,6 +578,7 @@ pub struct MembershipUpdated {
/// The result of setting a member's role.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum SetMemberRoleResult {
InviteUpdated(Channel),
MembershipUpdated(MembershipUpdated),
@@ -635,6 +696,30 @@ pub struct RejoinedProject {
pub language_servers: Vec<proto::LanguageServer>,
}
impl RejoinedProject {
pub fn to_proto(&self) -> proto::RejoinedProject {
proto::RejoinedProject {
id: self.id.to_proto(),
worktrees: self
.worktrees
.iter()
.map(|worktree| proto::WorktreeMetadata {
id: worktree.id,
root_name: worktree.root_name.clone(),
visible: worktree.visible,
abs_path: worktree.abs_path.clone(),
})
.collect(),
collaborators: self
.collaborators
.iter()
.map(|collaborator| collaborator.to_proto())
.collect(),
language_servers: self.language_servers.clone(),
}
}
}
#[derive(Debug)]
pub struct RejoinedWorktree {
pub id: u64,
@@ -677,6 +762,7 @@ pub struct Project {
pub collaborators: Vec<ProjectCollaborator>,
pub worktrees: BTreeMap<u64, Worktree>,
pub language_servers: Vec<proto::LanguageServer>,
pub remote_project_id: Option<RemoteProjectId>,
}
pub struct ProjectCollaborator {
@@ -699,8 +785,7 @@ impl ProjectCollaborator {
#[derive(Debug)]
pub struct LeftProject {
pub id: ProjectId,
pub host_user_id: Option<UserId>,
pub host_connection_id: Option<ConnectionId>,
pub should_unshare: bool,
pub connection_ids: Vec<ConnectionId>,
}

View File

@@ -84,6 +84,7 @@ id_type!(NotificationId);
id_type!(NotificationKindId);
id_type!(ProjectCollaboratorId);
id_type!(ProjectId);
id_type!(RemoteProjectId);
id_type!(ReplicaId);
id_type!(RoomId);
id_type!(RoomParticipantId);
@@ -270,3 +271,18 @@ impl Into<i32> for ChannelVisibility {
proto.into()
}
}
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
pub enum PrincipalId {
UserId(UserId),
DevServerId(DevServerId),
}
/// Indicate whether a [Buffer] has permissions to edit.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Capability {
/// The buffer is a mutable replica.
ReadWrite,
/// The buffer is a read-only replica.
ReadOnly,
}

View File

@@ -6,12 +6,14 @@ pub mod channels;
pub mod contacts;
pub mod contributors;
pub mod dev_servers;
pub mod embeddings;
pub mod extensions;
pub mod hosted_projects;
pub mod messages;
pub mod notifications;
pub mod projects;
pub mod rate_buckets;
pub mod remote_projects;
pub mod rooms;
pub mod servers;
pub mod users;

View File

@@ -1,6 +1,9 @@
use sea_orm::EntityTrait;
use rpc::proto;
use sea_orm::{
ActiveValue, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, QueryFilter,
};
use super::{dev_server, Database, DevServerId};
use super::{dev_server, remote_project, Database, DevServerId, UserId};
impl Database {
pub async fn get_dev_server(
@@ -15,4 +18,107 @@ impl Database {
})
.await
}
pub async fn get_dev_servers(&self, user_id: UserId) -> crate::Result<Vec<dev_server::Model>> {
self.transaction(|tx| async move {
Ok(dev_server::Entity::find()
.filter(dev_server::Column::UserId.eq(user_id))
.all(&*tx)
.await?)
})
.await
}
pub async fn remote_projects_update(
&self,
user_id: UserId,
) -> crate::Result<proto::RemoteProjectsUpdate> {
self.transaction(
|tx| async move { self.remote_projects_update_internal(user_id, &tx).await },
)
.await
}
pub async fn remote_projects_update_internal(
&self,
user_id: UserId,
tx: &DatabaseTransaction,
) -> crate::Result<proto::RemoteProjectsUpdate> {
let dev_servers = dev_server::Entity::find()
.filter(dev_server::Column::UserId.eq(user_id))
.all(tx)
.await?;
let remote_projects = remote_project::Entity::find()
.filter(
remote_project::Column::DevServerId
.is_in(dev_servers.iter().map(|d| d.id).collect::<Vec<_>>()),
)
.find_also_related(super::project::Entity)
.all(tx)
.await?;
Ok(proto::RemoteProjectsUpdate {
dev_servers: dev_servers
.into_iter()
.map(|d| d.to_proto(proto::DevServerStatus::Offline))
.collect(),
remote_projects: remote_projects
.into_iter()
.map(|(remote_project, project)| remote_project.to_proto(project))
.collect(),
})
}
pub async fn create_dev_server(
&self,
name: &str,
hashed_access_token: &str,
user_id: UserId,
) -> crate::Result<(dev_server::Model, proto::RemoteProjectsUpdate)> {
self.transaction(|tx| async move {
let dev_server = dev_server::Entity::insert(dev_server::ActiveModel {
id: ActiveValue::NotSet,
hashed_token: ActiveValue::Set(hashed_access_token.to_string()),
name: ActiveValue::Set(name.to_string()),
user_id: ActiveValue::Set(user_id),
})
.exec_with_returning(&*tx)
.await?;
let remote_projects = self.remote_projects_update_internal(user_id, &tx).await?;
Ok((dev_server, remote_projects))
})
.await
}
pub async fn delete_dev_server(
&self,
id: DevServerId,
user_id: UserId,
) -> crate::Result<proto::RemoteProjectsUpdate> {
self.transaction(|tx| async move {
let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
return Err(anyhow::anyhow!("no dev server with id {}", id))?;
};
if dev_server.user_id != user_id {
return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
}
remote_project::Entity::delete_many()
.filter(remote_project::Column::DevServerId.eq(id))
.exec(&*tx)
.await?;
dev_server::Entity::delete(dev_server.into_active_model())
.exec(&*tx)
.await?;
let remote_projects = self.remote_projects_update_internal(user_id, &tx).await?;
Ok(remote_projects)
})
.await
}
}

View File

@@ -0,0 +1,94 @@
use super::*;
use time::Duration;
use time::OffsetDateTime;
impl Database {
pub async fn get_embeddings(
&self,
model: &str,
digests: &[Vec<u8>],
) -> Result<HashMap<Vec<u8>, Vec<f32>>> {
self.weak_transaction(|tx| async move {
let embeddings = {
let mut db_embeddings = embedding::Entity::find()
.filter(
embedding::Column::Model.eq(model).and(
embedding::Column::Digest
.is_in(digests.iter().map(|digest| digest.as_slice())),
),
)
.stream(&*tx)
.await?;
let mut embeddings = HashMap::default();
while let Some(db_embedding) = db_embeddings.next().await {
let db_embedding = db_embedding?;
embeddings.insert(db_embedding.digest, db_embedding.dimensions);
}
embeddings
};
if !embeddings.is_empty() {
let now = OffsetDateTime::now_utc();
let retrieved_at = PrimitiveDateTime::new(now.date(), now.time());
embedding::Entity::update_many()
.filter(
embedding::Column::Digest
.is_in(embeddings.keys().map(|digest| digest.as_slice())),
)
.col_expr(embedding::Column::RetrievedAt, Expr::value(retrieved_at))
.exec(&*tx)
.await?;
}
Ok(embeddings)
})
.await
}
pub async fn save_embeddings(
&self,
model: &str,
embeddings: &HashMap<Vec<u8>, Vec<f32>>,
) -> Result<()> {
self.weak_transaction(|tx| async move {
embedding::Entity::insert_many(embeddings.iter().map(|(digest, dimensions)| {
let now_offset_datetime = OffsetDateTime::now_utc();
let retrieved_at =
PrimitiveDateTime::new(now_offset_datetime.date(), now_offset_datetime.time());
embedding::ActiveModel {
model: ActiveValue::set(model.to_string()),
digest: ActiveValue::set(digest.clone()),
dimensions: ActiveValue::set(dimensions.clone()),
retrieved_at: ActiveValue::set(retrieved_at),
}
}))
.on_conflict(
OnConflict::columns([embedding::Column::Model, embedding::Column::Digest])
.do_nothing()
.to_owned(),
)
.exec_without_returning(&*tx)
.await?;
Ok(())
})
.await
}
pub async fn purge_old_embeddings(&self) -> Result<()> {
self.weak_transaction(|tx| async move {
embedding::Entity::delete_many()
.filter(
embedding::Column::RetrievedAt
.lte(OffsetDateTime::now_utc() - Duration::days(60)),
)
.exec(&*tx)
.await?;
Ok(())
})
.await
}
}

View File

@@ -1,3 +1,5 @@
use util::ResultExt;
use super::*;
impl Database {
@@ -28,7 +30,8 @@ impl Database {
room_id: RoomId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
remote_project_id: Option<RemoteProjectId>,
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
@@ -56,6 +59,30 @@ impl Database {
return Err(anyhow!("guests cannot share projects"))?;
}
if let Some(remote_project_id) = remote_project_id {
let project = project::Entity::find()
.filter(project::Column::RemoteProjectId.eq(Some(remote_project_id)))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no remote project"))?;
if project.room_id.is_some() {
return Err(anyhow!("project already shared"))?;
};
let project = project::Entity::update(project::ActiveModel {
room_id: ActiveValue::Set(Some(room_id)),
..project.into_active_model()
})
.exec(&*tx)
.await?;
// todo! check user is a project-collaborator
let room = self.get_room(room_id, &tx).await?;
return Ok((project.id, room));
}
let project = project::ActiveModel {
room_id: ActiveValue::set(Some(participant.room_id)),
host_user_id: ActiveValue::set(Some(participant.user_id)),
@@ -65,6 +92,7 @@ impl Database {
))),
id: ActiveValue::NotSet,
hosted_project_id: ActiveValue::Set(None),
remote_project_id: ActiveValue::Set(None),
}
.insert(&*tx)
.await?;
@@ -108,24 +136,45 @@ impl Database {
&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 {
user_id: Option<UserId>,
) -> Result<TransactionGuard<(Option<proto::Room>, Vec<ConnectionId>)>> {
self.project_transaction(project_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"))?;
let room = if let Some(room_id) = project.room_id {
Some(self.get_room(room_id, &tx).await?)
} else {
None
};
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"))?
return Ok((room, guest_connection_ids));
}
if let Some(remote_project_id) = project.remote_project_id {
if let Some(user_id) = user_id {
if user_id
!= self
.owner_for_remote_project(remote_project_id, &tx)
.await?
{
Err(anyhow!("cannot unshare a project hosted by another user"))?
}
project::Entity::update(project::ActiveModel {
room_id: ActiveValue::Set(None),
..project.into_active_model()
})
.exec(&*tx)
.await?;
return Ok((room, guest_connection_ids));
}
}
Err(anyhow!("cannot unshare a project hosted by another user"))?
})
.await
}
@@ -136,9 +185,8 @@ impl Database {
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 {
) -> Result<TransactionGuard<(Option<proto::Room>, Vec<ConnectionId>)>> {
self.project_transaction(project_id, |tx| async move {
let project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
@@ -154,12 +202,14 @@ impl Database {
self.update_project_worktrees(project.id, worktrees, &tx)
.await?;
let room_id = project
.room_id
.ok_or_else(|| anyhow!("project not in a room"))?;
let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
let room = self.get_room(room_id, &tx).await?;
let room = if let Some(room_id) = project.room_id {
Some(self.get_room(room_id, &tx).await?)
} else {
None
};
Ok((room, guest_connection_ids))
})
.await
@@ -204,11 +254,10 @@ impl Database {
&self,
update: &proto::UpdateWorktree,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
) -> Result<TransactionGuard<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 {
self.project_transaction(project_id, |tx| async move {
// Ensure the update comes from the host.
let _project = project::Entity::find_by_id(project_id)
.filter(
@@ -360,11 +409,10 @@ impl Database {
&self,
update: &proto::UpdateDiagnosticSummary,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
) -> Result<TransactionGuard<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 {
self.project_transaction(project_id, |tx| async move {
let summary = update
.summary
.as_ref()
@@ -415,10 +463,9 @@ impl Database {
&self,
update: &proto::StartLanguageServer,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
) -> Result<TransactionGuard<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 {
self.project_transaction(project_id, |tx| async move {
let server = update
.server
.as_ref()
@@ -461,10 +508,9 @@ impl Database {
&self,
update: &proto::UpdateWorktreeSettings,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
) -> Result<TransactionGuard<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 {
self.project_transaction(project_id, |tx| async move {
// Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
@@ -542,46 +588,36 @@ impl Database {
.await
}
pub async fn get_project(&self, id: ProjectId) -> Result<project::Model> {
self.transaction(|tx| async move {
Ok(project::Entity::find_by_id(id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?)
})
.await
}
/// Adds the given connection to the specified project
/// in the current room.
pub async fn join_project_in_room(
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),
),
user_id: UserId,
) -> Result<TransactionGuard<(Project, ReplicaId)>> {
self.project_transaction(project_id, |tx| async move {
let (project, role) = self
.access_project(
project_id,
connection,
PrincipalId::UserId(user_id),
Capability::ReadOnly,
&tx,
)
.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 != Some(participant.room_id) {
return Err(anyhow!("no such project"))?;
}
self.join_project_internal(
project,
participant.user_id,
connection,
participant.role.unwrap_or(ChannelRole::Member),
&tx,
)
.await
.await?;
self.join_project_internal(project, user_id, connection, role, &tx)
.await
})
.await
}
@@ -761,6 +797,7 @@ impl Database {
name: language_server.name,
})
.collect(),
remote_project_id: project.remote_project_id,
};
Ok((project, replica_id as ReplicaId))
}
@@ -802,8 +839,7 @@ impl Database {
Ok(LeftProject {
id: project.id,
connection_ids,
host_user_id: None,
host_connection_id: None,
should_unshare: false,
})
})
.await
@@ -814,9 +850,8 @@ impl Database {
&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 {
) -> Result<TransactionGuard<(Option<proto::Room>, LeftProject)>> {
self.project_transaction(project_id, |tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
@@ -841,7 +876,7 @@ impl Database {
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let connection_ids = collaborators
let connection_ids: Vec<ConnectionId> = collaborators
.into_iter()
.map(|collaborator| collaborator.connection())
.collect();
@@ -871,11 +906,15 @@ impl Database {
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
let room = if let Some(room_id) = project.room_id {
Some(self.get_room(room_id, &tx).await?)
} else {
None
};
let left_project = LeftProject {
id: project_id,
host_user_id: project.host_user_id,
host_connection_id: Some(project.host_connection()?),
should_unshare: connection == project.host_connection()?,
connection_ids,
};
Ok((room, left_project))
@@ -888,17 +927,15 @@ impl Database {
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<()> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
project_collaborator::Entity::find()
self.project_transaction(project_id, |tx| async move {
project::Entity::find()
.filter(
Condition::all()
.add(project_collaborator::Column::ProjectId.eq(project_id))
.add(project_collaborator::Column::IsHost.eq(true))
.add(project_collaborator::Column::ConnectionId.eq(connection_id.id))
.add(project::Column::Id.eq(project_id))
.add(project::Column::HostConnectionId.eq(Some(connection_id.id as i32)))
.add(
project_collaborator::Column::ConnectionServerId
.eq(connection_id.owner_id),
project::Column::HostConnectionServerId
.eq(Some(connection_id.owner_id as i32)),
),
)
.one(&*tx)
@@ -911,39 +948,107 @@ impl Database {
.map(|guard| guard.into_inner())
}
/// Returns the current project if the given user is authorized to access it with the specified capability.
pub async fn access_project(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
principal_id: PrincipalId,
capability: Capability,
tx: &DatabaseTransaction,
) -> Result<(project::Model, ChannelRole)> {
let (mut project, remote_project) = project::Entity::find_by_id(project_id)
.find_also_related(remote_project::Entity)
.one(tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let user_id = match principal_id {
PrincipalId::DevServerId(_) => {
if project
.host_connection()
.is_ok_and(|connection| connection == connection_id)
{
return Ok((project, ChannelRole::Admin));
}
return Err(anyhow!("not the project host"))?;
}
PrincipalId::UserId(user_id) => user_id,
};
let role_from_room = if let Some(room_id) = project.room_id {
room_participant::Entity::find()
.filter(room_participant::Column::RoomId.eq(room_id))
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id))
.one(tx)
.await?
.and_then(|participant| participant.role)
} else {
None
};
let role_from_remote_project = if let Some(remote_project) = remote_project {
let dev_server = dev_server::Entity::find_by_id(remote_project.dev_server_id)
.one(tx)
.await?
.ok_or_else(|| anyhow!("no such channel"))?;
if user_id == dev_server.user_id {
// If the user left the room "uncleanly" they may rejoin the
// remote project before leave_room runs. IN that case kick
// the project out of the room pre-emptively.
if role_from_room.is_none() {
project = project::Entity::update(project::ActiveModel {
room_id: ActiveValue::Set(None),
..project.into_active_model()
})
.exec(tx)
.await?;
}
Some(ChannelRole::Admin)
} else {
None
}
} else {
None
};
let role = role_from_remote_project
.or(role_from_room)
.unwrap_or(ChannelRole::Banned);
match capability {
Capability::ReadWrite => {
if !role.can_edit_projects() {
return Err(anyhow!("not authorized to edit projects"))?;
}
}
Capability::ReadOnly => {
if !role.can_read_projects() {
return Err(anyhow!("not authorized to read projects"))?;
}
}
}
Ok((project, role))
}
/// Returns the host connection for a read-only request to join a shared project.
pub async fn host_for_read_only_project_request(
&self,
project_id: ProjectId,
connection_id: ConnectionId,
user_id: UserId,
) -> Result<ConnectionId> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let current_participant = room_participant::Entity::find()
.filter(room_participant::Column::RoomId.eq(room_id))
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such room"))?;
if !current_participant
.role
.map_or(false, |role| role.can_read_projects())
{
Err(anyhow!("not authorized to read projects"))?;
}
let host = project_collaborator::Entity::find()
.filter(
project_collaborator::Column::ProjectId
.eq(project_id)
.and(project_collaborator::Column::IsHost.eq(true)),
self.project_transaction(project_id, |tx| async move {
let (project, _) = self
.access_project(
project_id,
connection_id,
PrincipalId::UserId(user_id),
Capability::ReadOnly,
&tx,
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("failed to read project host"))?;
Ok(host.connection())
.await?;
project.host_connection()
})
.await
.map(|guard| guard.into_inner())
@@ -954,83 +1059,56 @@ impl Database {
&self,
project_id: ProjectId,
connection_id: ConnectionId,
user_id: UserId,
) -> Result<ConnectionId> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let current_participant = room_participant::Entity::find()
.filter(room_participant::Column::RoomId.eq(room_id))
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such room"))?;
if !current_participant
.role
.map_or(false, |role| role.can_edit_projects())
{
Err(anyhow!("not authorized to edit projects"))?;
}
let host = project_collaborator::Entity::find()
.filter(
project_collaborator::Column::ProjectId
.eq(project_id)
.and(project_collaborator::Column::IsHost.eq(true)),
self.project_transaction(project_id, |tx| async move {
let (project, _) = self
.access_project(
project_id,
connection_id,
PrincipalId::UserId(user_id),
Capability::ReadWrite,
&tx,
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("failed to read project host"))?;
Ok(host.connection())
.await?;
project.host_connection()
})
.await
.map(|guard| guard.into_inner())
}
pub async fn project_collaborators_for_buffer_update(
pub async fn connections_for_buffer_update(
&self,
project_id: ProjectId,
principal_id: PrincipalId,
connection_id: ConnectionId,
requires_write: bool,
) -> Result<RoomGuard<Vec<ProjectCollaborator>>> {
let room_id = self.room_id_for_project(project_id).await?;
self.room_transaction(room_id, |tx| async move {
let current_participant = room_participant::Entity::find()
.filter(room_participant::Column::RoomId.eq(room_id))
.filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id))
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such room"))?;
capability: Capability,
) -> Result<TransactionGuard<(ConnectionId, Vec<ConnectionId>)>> {
self.project_transaction(project_id, |tx| async move {
// Authorize
let (project, _) = self
.access_project(project_id, connection_id, principal_id, capability, &tx)
.await?;
if requires_write
&& !current_participant
.role
.map_or(false, |role| role.can_edit_projects())
{
Err(anyhow!("not authorized to edit projects"))?;
}
let host_connection_id = project.host_connection()?;
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<_>>();
.await?;
if collaborators
.iter()
.any(|collaborator| collaborator.connection_id == connection_id)
{
Ok(collaborators)
} else {
Err(anyhow!("no such project"))?
}
let guest_connection_ids = collaborators
.into_iter()
.filter_map(|collaborator| {
if collaborator.is_host {
None
} else {
Some(collaborator.connection())
}
})
.collect();
Ok((host_connection_id, guest_connection_ids))
})
.await
}
@@ -1043,24 +1121,39 @@ impl Database {
&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 {
exclude_dev_server: bool,
) -> Result<TransactionGuard<HashSet<ConnectionId>>> {
self.project_transaction(project_id, |tx| async move {
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let mut collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.stream(&*tx)
.await?;
let mut connection_ids = HashSet::default();
if let Some(host_connection) = project.host_connection().log_err() {
if !exclude_dev_server {
connection_ids.insert(host_connection);
}
}
while let Some(collaborator) = collaborators.next().await {
let collaborator = collaborator?;
connection_ids.insert(collaborator.connection());
}
if connection_ids.contains(&connection_id) {
if connection_ids.contains(&connection_id)
|| Some(connection_id) == project.host_connection().ok()
{
Ok(connection_ids)
} else {
Err(anyhow!("no such project"))?
Err(anyhow!(
"can only send project updates to a project you're in"
))?
}
})
.await
@@ -1089,15 +1182,12 @@ impl Database {
}
/// Returns the [`RoomId`] for the given project.
pub async fn room_id_for_project(&self, project_id: ProjectId) -> Result<RoomId> {
pub async fn room_id_for_project(&self, project_id: ProjectId) -> Result<Option<RoomId>> {
self.transaction(|tx| async move {
let project = project::Entity::find_by_id(project_id)
Ok(project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project {} not found", project_id))?;
Ok(project
.room_id
.ok_or_else(|| anyhow!("project not in room"))?)
.and_then(|project| project.room_id))
})
.await
}
@@ -1142,7 +1232,7 @@ impl Database {
project_id: ProjectId,
leader_connection: ConnectionId,
follower_connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
) -> Result<TransactionGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
follower::ActiveModel {
room_id: ActiveValue::set(room_id),
@@ -1173,7 +1263,7 @@ impl Database {
project_id: ProjectId,
leader_connection: ConnectionId,
follower_connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
) -> Result<TransactionGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
follower::Entity::delete_many()
.filter(

View File

@@ -0,0 +1,283 @@
use anyhow::anyhow;
use rpc::{proto, ConnectionId};
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait,
ModelTrait, QueryFilter,
};
use crate::db::ProjectId;
use super::{
dev_server, project, project_collaborator, remote_project, worktree, Database, DevServerId,
RejoinedProject, RemoteProjectId, ResharedProject, ServerId, UserId,
};
impl Database {
pub async fn get_remote_project(
&self,
remote_project_id: RemoteProjectId,
) -> crate::Result<remote_project::Model> {
self.transaction(|tx| async move {
Ok(remote_project::Entity::find_by_id(remote_project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no remote project with id {}", remote_project_id))?)
})
.await
}
pub async fn get_remote_projects_for_dev_server(
&self,
dev_server_id: DevServerId,
) -> crate::Result<Vec<proto::RemoteProject>> {
self.transaction(|tx| async move {
let servers = remote_project::Entity::find()
.filter(remote_project::Column::DevServerId.eq(dev_server_id))
.find_also_related(project::Entity)
.all(&*tx)
.await?;
Ok(servers
.into_iter()
.map(|(remote_project, project)| proto::RemoteProject {
id: remote_project.id.to_proto(),
project_id: project.map(|p| p.id.to_proto()),
dev_server_id: remote_project.dev_server_id.to_proto(),
path: remote_project.path,
})
.collect())
})
.await
}
pub async fn remote_project_ids_for_user(
&self,
user_id: UserId,
tx: &DatabaseTransaction,
) -> crate::Result<Vec<RemoteProjectId>> {
let dev_servers = dev_server::Entity::find()
.filter(dev_server::Column::UserId.eq(user_id))
.find_with_related(remote_project::Entity)
.all(tx)
.await?;
Ok(dev_servers
.into_iter()
.flat_map(|(_, projects)| projects.into_iter().map(|p| p.id))
.collect())
}
pub async fn owner_for_remote_project(
&self,
remote_project_id: RemoteProjectId,
tx: &DatabaseTransaction,
) -> crate::Result<UserId> {
let dev_server = remote_project::Entity::find_by_id(remote_project_id)
.find_also_related(dev_server::Entity)
.one(tx)
.await?
.and_then(|(_, dev_server)| dev_server)
.ok_or_else(|| anyhow!("no remote project"))?;
Ok(dev_server.user_id)
}
pub async fn get_stale_dev_server_projects(
&self,
connection: ConnectionId,
) -> crate::Result<Vec<ProjectId>> {
self.transaction(|tx| async move {
let projects = project::Entity::find()
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id))
.add(project::Column::HostConnectionServerId.eq(connection.owner_id)),
)
.all(&*tx)
.await?;
Ok(projects.into_iter().map(|p| p.id).collect())
})
.await
}
pub async fn create_remote_project(
&self,
dev_server_id: DevServerId,
path: &str,
user_id: UserId,
) -> crate::Result<(remote_project::Model, proto::RemoteProjectsUpdate)> {
self.transaction(|tx| async move {
let dev_server = dev_server::Entity::find_by_id(dev_server_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?;
if dev_server.user_id != user_id {
return Err(anyhow!("not your dev server"))?;
}
let project = remote_project::Entity::insert(remote_project::ActiveModel {
id: ActiveValue::NotSet,
dev_server_id: ActiveValue::Set(dev_server_id),
path: ActiveValue::Set(path.to_string()),
})
.exec_with_returning(&*tx)
.await?;
let status = self.remote_projects_update_internal(user_id, &tx).await?;
Ok((project, status))
})
.await
}
pub async fn share_remote_project(
&self,
remote_project_id: RemoteProjectId,
dev_server_id: DevServerId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> crate::Result<(proto::RemoteProject, UserId, proto::RemoteProjectsUpdate)> {
self.transaction(|tx| async move {
let dev_server = dev_server::Entity::find_by_id(dev_server_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?;
let remote_project = remote_project::Entity::find_by_id(remote_project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no remote project with id {}", remote_project_id))?;
if remote_project.dev_server_id != dev_server_id {
return Err(anyhow!("remote project shared from wrong server"))?;
}
let project = project::ActiveModel {
room_id: ActiveValue::Set(None),
host_user_id: ActiveValue::Set(None),
host_connection_id: ActiveValue::set(Some(connection.id as i32)),
host_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
id: ActiveValue::NotSet,
hosted_project_id: ActiveValue::Set(None),
remote_project_id: ActiveValue::Set(Some(remote_project_id)),
}
.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?;
}
let status = self
.remote_projects_update_internal(dev_server.user_id, &tx)
.await?;
Ok((
remote_project.to_proto(Some(project)),
dev_server.user_id,
status,
))
})
.await
}
pub async fn reshare_remote_projects(
&self,
reshared_projects: &Vec<proto::UpdateProject>,
dev_server_id: DevServerId,
connection: ConnectionId,
) -> crate::Result<Vec<ResharedProject>> {
// todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
self.transaction(|tx| async move {
let mut ret = Vec::new();
for reshared_project in reshared_projects {
let project_id = ProjectId::from_proto(reshared_project.project_id);
let (project, remote_project) = project::Entity::find_by_id(project_id)
.find_also_related(remote_project::Entity)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("project does not exist"))?;
if remote_project.map(|rp| rp.dev_server_id) != Some(dev_server_id) {
return Err(anyhow!("remote project reshared from wrong server"))?;
}
let Ok(old_connection_id) = project.host_connection() else {
return Err(anyhow!("remote project was not shared"))?;
};
project::Entity::update(project::ActiveModel {
id: ActiveValue::set(project_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()
})
.exec(&*tx)
.await?;
let collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
self.update_project_worktrees(project_id, &reshared_project.worktrees, &tx)
.await?;
ret.push(super::ResharedProject {
id: project_id,
old_connection_id,
collaborators: collaborators
.iter()
.map(|collaborator| super::ProjectCollaborator {
connection_id: collaborator.connection(),
user_id: collaborator.user_id,
replica_id: collaborator.replica_id,
is_host: collaborator.is_host,
})
.collect(),
worktrees: reshared_project.worktrees.clone(),
});
}
Ok(ret)
})
.await
}
pub async fn rejoin_remote_projects(
&self,
rejoined_projects: &Vec<proto::RejoinProject>,
user_id: UserId,
connection_id: ConnectionId,
) -> crate::Result<Vec<RejoinedProject>> {
// todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
self.transaction(|tx| async move {
let mut ret = Vec::new();
for rejoined_project in rejoined_projects {
if let Some(project) = self
.rejoin_project_internal(&tx, rejoined_project, user_id, connection_id)
.await?
{
ret.push(project);
}
}
Ok(ret)
})
.await
}
}

View File

@@ -6,7 +6,7 @@ impl Database {
&self,
room_id: RoomId,
new_server_id: ServerId,
) -> Result<RoomGuard<RefreshedRoom>> {
) -> Result<TransactionGuard<RefreshedRoom>> {
self.room_transaction(room_id, |tx| async move {
let stale_participant_filter = Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
@@ -149,7 +149,7 @@ impl Database {
calling_connection: ConnectionId,
called_user_id: UserId,
initial_project_id: Option<ProjectId>,
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
) -> Result<TransactionGuard<(proto::Room, proto::IncomingCall)>> {
self.room_transaction(room_id, |tx| async move {
let caller = room_participant::Entity::find()
.filter(
@@ -201,7 +201,7 @@ impl Database {
&self,
room_id: RoomId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
) -> Result<TransactionGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
room_participant::Entity::delete_many()
.filter(
@@ -221,7 +221,7 @@ impl Database {
&self,
expected_room_id: Option<RoomId>,
user_id: UserId,
) -> Result<Option<RoomGuard<proto::Room>>> {
) -> Result<Option<TransactionGuard<proto::Room>>> {
self.optional_room_transaction(|tx| async move {
let mut filter = Condition::all()
.add(room_participant::Column::UserId.eq(user_id))
@@ -258,7 +258,7 @@ impl Database {
room_id: RoomId,
calling_connection: ConnectionId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
) -> Result<TransactionGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
@@ -294,7 +294,7 @@ impl Database {
room_id: RoomId,
user_id: UserId,
connection: ConnectionId,
) -> Result<RoomGuard<JoinRoom>> {
) -> Result<TransactionGuard<JoinRoom>> {
self.room_transaction(room_id, |tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
enum QueryChannelId {
@@ -472,7 +472,7 @@ impl Database {
rejoin_room: proto::RejoinRoom,
user_id: UserId,
connection: ConnectionId,
) -> Result<RoomGuard<RejoinedRoom>> {
) -> Result<TransactionGuard<RejoinedRoom>> {
let room_id = RoomId::from_proto(rejoin_room.id);
self.room_transaction(room_id, |tx| async {
let tx = tx;
@@ -572,180 +572,12 @@ impl Database {
let mut rejoined_projects = Vec::new();
for rejoined_project in &rejoin_room.rejoined_projects {
let project_id = ProjectId::from_proto(rejoined_project.id);
let Some(project) = project::Entity::find_by_id(project_id).one(&*tx).await? else {
continue;
};
let mut worktrees = Vec::new();
let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
for db_worktree in db_worktrees {
let mut worktree = RejoinedWorktree {
id: db_worktree.id as u64,
abs_path: db_worktree.abs_path,
root_name: db_worktree.root_name,
visible: db_worktree.visible,
updated_entries: Default::default(),
removed_entries: Default::default(),
updated_repositories: Default::default(),
removed_repositories: 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,
};
let rejoined_worktree = rejoined_project
.worktrees
.iter()
.find(|worktree| worktree.id == db_worktree.id as u64);
// File entries
{
let entry_filter = if let Some(rejoined_worktree) = rejoined_worktree {
worktree_entry::Column::ScanId.gt(rejoined_worktree.scan_id)
} else {
worktree_entry::Column::IsDeleted.eq(false)
};
let mut db_entries = worktree_entry::Entity::find()
.filter(
Condition::all()
.add(worktree_entry::Column::ProjectId.eq(project.id))
.add(worktree_entry::Column::WorktreeId.eq(worktree.id))
.add(entry_filter),
)
.stream(&*tx)
.await?;
while let Some(db_entry) = db_entries.next().await {
let db_entry = db_entry?;
if db_entry.is_deleted {
worktree.removed_entries.push(db_entry.id as u64);
} else {
worktree.updated_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),
});
}
}
}
// Repository Entries
{
let repository_entry_filter =
if let Some(rejoined_worktree) = rejoined_worktree {
worktree_repository::Column::ScanId.gt(rejoined_worktree.scan_id)
} else {
worktree_repository::Column::IsDeleted.eq(false)
};
let mut db_repositories = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::WorktreeId.eq(worktree.id))
.add(repository_entry_filter),
)
.stream(&*tx)
.await?;
while let Some(db_repository) = db_repositories.next().await {
let db_repository = db_repository?;
if db_repository.is_deleted {
worktree
.removed_repositories
.push(db_repository.work_directory_id as u64);
} else {
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
});
}
}
}
worktrees.push(worktree);
}
let language_servers = project
.find_related(language_server::Entity)
.all(&*tx)
if let Some(rejoined_project) = self
.rejoin_project_internal(&tx, rejoined_project, user_id, connection)
.await?
.into_iter()
.map(|language_server| proto::LanguageServer {
id: language_server.id as u64,
name: language_server.name,
})
.collect::<Vec<_>>();
{
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
.iter_mut()
.find(|w| w.id == db_settings_file.worktree_id as u64)
{
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
});
}
}
rejoined_projects.push(rejoined_project);
}
let mut collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let self_collaborator = if let Some(self_collaborator_ix) = collaborators
.iter()
.position(|collaborator| collaborator.user_id == user_id)
{
collaborators.swap_remove(self_collaborator_ix)
} else {
continue;
};
let old_connection_id = self_collaborator.connection();
project_collaborator::Entity::update(project_collaborator::ActiveModel {
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
..self_collaborator.into_active_model()
})
.exec(&*tx)
.await?;
let 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::<Vec<_>>();
rejoined_projects.push(RejoinedProject {
id: project_id,
old_connection_id,
collaborators,
worktrees,
language_servers,
});
}
let (channel, room) = self.get_channel_room(room_id, &tx).await?;
@@ -760,10 +592,192 @@ impl Database {
.await
}
pub async fn rejoin_project_internal(
&self,
tx: &DatabaseTransaction,
rejoined_project: &proto::RejoinProject,
user_id: UserId,
connection: ConnectionId,
) -> Result<Option<RejoinedProject>> {
let project_id = ProjectId::from_proto(rejoined_project.id);
let Some(project) = project::Entity::find_by_id(project_id).one(tx).await? else {
return Ok(None);
};
let mut worktrees = Vec::new();
let db_worktrees = project.find_related(worktree::Entity).all(tx).await?;
for db_worktree in db_worktrees {
let mut worktree = RejoinedWorktree {
id: db_worktree.id as u64,
abs_path: db_worktree.abs_path,
root_name: db_worktree.root_name,
visible: db_worktree.visible,
updated_entries: Default::default(),
removed_entries: Default::default(),
updated_repositories: Default::default(),
removed_repositories: 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,
};
let rejoined_worktree = rejoined_project
.worktrees
.iter()
.find(|worktree| worktree.id == db_worktree.id as u64);
// File entries
{
let entry_filter = if let Some(rejoined_worktree) = rejoined_worktree {
worktree_entry::Column::ScanId.gt(rejoined_worktree.scan_id)
} else {
worktree_entry::Column::IsDeleted.eq(false)
};
let mut db_entries = worktree_entry::Entity::find()
.filter(
Condition::all()
.add(worktree_entry::Column::ProjectId.eq(project.id))
.add(worktree_entry::Column::WorktreeId.eq(worktree.id))
.add(entry_filter),
)
.stream(tx)
.await?;
while let Some(db_entry) = db_entries.next().await {
let db_entry = db_entry?;
if db_entry.is_deleted {
worktree.removed_entries.push(db_entry.id as u64);
} else {
worktree.updated_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),
});
}
}
}
// Repository Entries
{
let repository_entry_filter = if let Some(rejoined_worktree) = rejoined_worktree {
worktree_repository::Column::ScanId.gt(rejoined_worktree.scan_id)
} else {
worktree_repository::Column::IsDeleted.eq(false)
};
let mut db_repositories = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::WorktreeId.eq(worktree.id))
.add(repository_entry_filter),
)
.stream(tx)
.await?;
while let Some(db_repository) = db_repositories.next().await {
let db_repository = db_repository?;
if db_repository.is_deleted {
worktree
.removed_repositories
.push(db_repository.work_directory_id as u64);
} else {
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
});
}
}
}
worktrees.push(worktree);
}
let language_servers = project
.find_related(language_server::Entity)
.all(tx)
.await?
.into_iter()
.map(|language_server| proto::LanguageServer {
id: language_server.id as u64,
name: language_server.name,
})
.collect::<Vec<_>>();
{
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
.iter_mut()
.find(|w| w.id == db_settings_file.worktree_id as u64)
{
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
});
}
}
}
let mut collaborators = project
.find_related(project_collaborator::Entity)
.all(tx)
.await?;
let self_collaborator = if let Some(self_collaborator_ix) = collaborators
.iter()
.position(|collaborator| collaborator.user_id == user_id)
{
collaborators.swap_remove(self_collaborator_ix)
} else {
return Ok(None);
};
let old_connection_id = self_collaborator.connection();
project_collaborator::Entity::update(project_collaborator::ActiveModel {
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
..self_collaborator.into_active_model()
})
.exec(tx)
.await?;
let 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::<Vec<_>>();
return Ok(Some(RejoinedProject {
id: project_id,
old_connection_id,
collaborators,
worktrees,
language_servers,
}));
}
pub async fn leave_room(
&self,
connection: ConnectionId,
) -> Result<Option<RoomGuard<LeftRoom>>> {
) -> Result<Option<TransactionGuard<LeftRoom>>> {
self.optional_room_transaction(|tx| async move {
let leaving_participant = room_participant::Entity::find()
.filter(
@@ -835,11 +849,32 @@ impl Database {
.into_values::<_, QueryProjectIds>()
.all(&*tx)
.await?;
// if any project in the room has a remote-project-id that belongs to a dev server that this user owns.
let remote_projects_for_user = self
.remote_project_ids_for_user(leaving_participant.user_id, &tx)
.await?;
let remote_projects_to_unshare = project::Entity::find()
.filter(
Condition::all()
.add(project::Column::RoomId.eq(room_id))
.add(
project::Column::RemoteProjectId
.is_in(remote_projects_for_user.clone()),
),
)
.all(&*tx)
.await?
.into_iter()
.map(|project| project.id)
.collect::<HashSet<_>>();
let mut left_projects = HashMap::default();
let mut collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.is_in(project_ids))
.stream(&*tx)
.await?;
while let Some(collaborator) = collaborators.next().await {
let collaborator = collaborator?;
let left_project =
@@ -847,9 +882,8 @@ impl Database {
.entry(collaborator.project_id)
.or_insert(LeftProject {
id: collaborator.project_id,
host_user_id: Default::default(),
connection_ids: Default::default(),
host_connection_id: None,
should_unshare: false,
});
let collaborator_connection_id = collaborator.connection();
@@ -857,9 +891,10 @@ impl Database {
left_project.connection_ids.push(collaborator_connection_id);
}
if collaborator.is_host {
left_project.host_user_id = Some(collaborator.user_id);
left_project.host_connection_id = Some(collaborator_connection_id);
if (collaborator.is_host && collaborator.connection() == connection)
|| remote_projects_to_unshare.contains(&collaborator.project_id)
{
left_project.should_unshare = true;
}
}
drop(collaborators);
@@ -901,6 +936,17 @@ impl Database {
.exec(&*tx)
.await?;
if !remote_projects_to_unshare.is_empty() {
project::Entity::update_many()
.filter(project::Column::Id.is_in(remote_projects_to_unshare))
.set(project::ActiveModel {
room_id: ActiveValue::Set(None),
..Default::default()
})
.exec(&*tx)
.await?;
}
let (channel, room) = self.get_channel_room(room_id, &tx).await?;
let deleted = if room.participants.is_empty() {
let result = room::Entity::delete_by_id(room_id).exec(&*tx).await?;
@@ -935,7 +981,7 @@ impl Database {
room_id: RoomId,
connection: ConnectionId,
location: proto::ParticipantLocation,
) -> Result<RoomGuard<proto::Room>> {
) -> Result<TransactionGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async {
let tx = tx;
let location_kind;
@@ -997,7 +1043,7 @@ impl Database {
room_id: RoomId,
user_id: UserId,
role: ChannelRole,
) -> Result<RoomGuard<proto::Room>> {
) -> Result<TransactionGuard<proto::Room>> {
self.room_transaction(room_id, |tx| async move {
room_participant::Entity::find()
.filter(
@@ -1150,7 +1196,7 @@ impl Database {
&self,
room_id: RoomId,
connection_id: ConnectionId,
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
) -> Result<TransactionGuard<HashSet<ConnectionId>>> {
self.room_transaction(room_id, |tx| async move {
let mut participants = room_participant::Entity::find()
.filter(room_participant::Column::RoomId.eq(room_id))
@@ -1250,38 +1296,46 @@ impl Database {
}
drop(db_participants);
let mut db_projects = db_room
let db_projects = db_room
.find_related(project::Entity)
.find_with_related(worktree::Entity)
.stream(tx)
.all(tx)
.await?;
while let Some(row) = db_projects.next().await {
let (db_project, db_worktree) = row?;
for (db_project, db_worktrees) in db_projects {
let host_connection = db_project.host_connection()?;
if let Some(participant) = participants.get_mut(&host_connection) {
let project = if let Some(project) = participant
.projects
.iter_mut()
.find(|project| project.id == db_project.id.to_proto())
{
project
} else {
participant.projects.push(proto::ParticipantProject {
id: db_project.id.to_proto(),
worktree_root_names: Default::default(),
});
participant.projects.last_mut().unwrap()
};
participant.projects.push(proto::ParticipantProject {
id: db_project.id.to_proto(),
worktree_root_names: Default::default(),
});
let project = participant.projects.last_mut().unwrap();
if let Some(db_worktree) = db_worktree {
for db_worktree in db_worktrees {
if db_worktree.visible {
project.worktree_root_names.push(db_worktree.root_name);
}
}
} else if let Some(remote_project_id) = db_project.remote_project_id {
let host = self.owner_for_remote_project(remote_project_id, tx).await?;
if let Some((_, participant)) = participants
.iter_mut()
.find(|(_, v)| v.user_id == host.to_proto())
{
participant.projects.push(proto::ParticipantProject {
id: db_project.id.to_proto(),
worktree_root_names: Default::default(),
});
let project = participant.projects.last_mut().unwrap();
for db_worktree in db_worktrees {
if db_worktree.visible {
project.worktree_root_names.push(db_worktree.root_name);
}
}
}
}
}
drop(db_projects);
let mut db_followers = db_room.find_related(follower::Entity).stream(tx).await?;
let mut followers = Vec::new();

View File

@@ -11,6 +11,7 @@ pub mod channel_message_mention;
pub mod contact;
pub mod contributor;
pub mod dev_server;
pub mod embedding;
pub mod extension;
pub mod extension_version;
pub mod feature_flag;
@@ -24,6 +25,7 @@ pub mod observed_channel_messages;
pub mod project;
pub mod project_collaborator;
pub mod rate_buckets;
pub mod remote_project;
pub mod room;
pub mod room_participant;
pub mod server;

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