Compare commits

...

314 Commits

Author SHA1 Message Date
Piotr Osiewicz
f5da886564 WIP
Co-authored-by: Bennet <bennetbo@gmx.de>
2024-04-10 13:32:46 +02:00
Conrad Irwin
3a6887db53 WIP
Co-Authored-By: Bennet <bennetbo@gmx.de>
2024-04-09 12:33:35 -06:00
Piotr Osiewicz
b19ad92a1e Another WIP 2024-04-09 19:28:58 +02:00
Piotr Osiewicz
2a47df9d3b wip: coordinates 2024-04-09 13:22:11 +02:00
Andrew Lygin
935e0d547e Improve Find/Replace shortcuts (#10297)
This PR changes ways the Find/Replace functionality in the
Buffer/Project Search is accessible via shortcuts. It makes those panels
work the same way as in VS Code and Sublime Text.

The details are described in the issue: [Make Find/Replace easier to
use](https://github.com/zed-industries/zed/issues/9142)

There's a difficulty with the Linux keybindings:

VS Code uses on MacOS (this PR replicates it):

| Action | Buffer Search | Project Search |
| --- | --- | --- |
| Find | `cmd-f` | `cmd-shift-f` |
| Replace | `cmd-alt-f` | `cmd-shift-h` |

VS Code uses on Linux (this PR replicates all but one):

| Action | Buffer Search | Project Search |
| --- | --- | --- |
| Find | `ctrl-f` | `ctrl-shift-f` |
| Replace | `ctrl-h`  | `ctrl-shift-h` |

The problem is that `ctrl-h` is already taken by the `editor::Backspace`
action in Zed on Linux.

There's two options here:

1. Change keybinding for `editor::Backspace` on Linux to something else,
and use `ctrl-h` for the "replace in buffer" action.
2. Use some other keybinding on Linux in Zed. This PR introduces
`ctrl-r` for this purpose, though I'm not sure it's the best choice.

What do you think?

fixes #9142

Release Notes:

- Improved access to "Find/Replace in Buffer" and "Find/Replace in
Files" via shortcuts (#9142).

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

- N/A
2024-04-08 22:07:59 -07:00
Max Brunsfeld
cc367d43d6 Sanitize ranges in code labels coming from extensions (#10307)
Without any sanitization, extensions would be able to crash zed, because
the editor code assumes these ranges are valid.

Release Notes:

- N/A
2024-04-08 19:53:25 -07:00
Marshall Bowers
a4566c36a3 gleam: Strip newlines in completion details returned from language server (#10304)
This PR updates the Gleam extension to strip out newlines in the
completion details returned from the language server.

These newlines were causing the completion menu to compute a large
height for each item, resulting in lots of empty space in the completion
menu:

<img width="878" alt="Screenshot 2024-04-08 at 8 53 29 PM"
src="https://github.com/zed-industries/zed/assets/1486634/383c52ec-e5cb-4496-ae4c-28744b4ecaf5">

The approach to stripping newlines allocates a bit more than I would
like.

It would be good to see if it is possible for the Gleam language server
to not send us these newlines in the first place.

Release Notes:

- N/A
2024-04-08 21:43:18 -04:00
Marshall Bowers
843aad80c6 Flip the optionality of the auto_update setting (#10302)
This PR flips the optionality of the `AutoUpdateSettingContent` to make
it a bit easier to work with.

#### Before

```rs
struct AutoUpdateSettingContent(Option<bool>);

type FileContent = AutoUpdateSettingContent;
```

#### After

```rs
struct AutoUpdateSettingContent(bool);

type FileContent = Option<AutoUpdateSettingContent>;
```

Release Notes:

- N/A
2024-04-08 20:16:05 -04:00
Mikayla Maki
def87a8d76 WIP: Refactor Linux platform implementation (#10227)
This puts the Linux platform implementation at a similar code style and
quality to the macOS platform. The largest change is that I collapsed
the `LinuxPlatform` -> `[Backend]` -> `[Backend]State` ->
`[Backend]StateInner` to just `[Backend]` and `[Backend]State`, and in
the process removed most of the `Rc`s and `RefCell`s.

TODO:
- [x] Make sure that this is on-par with the existing implementation
- [x] Review in detail, now that the large changes are done.
- [ ] Update the roadmap

Release Notes:

- N/A
2024-04-08 16:40:35 -07:00
Marshall Bowers
ee1642a50f Fix broken loading of auto_update setting (#10301)
This PR fixes a panic when attempting to load the `auto_update` setting.

This was leftover from #10296.

I'm going to see if there's a better way we can handle these cases so
they're more obviously correct.

Release Notes:

- N/A
2024-04-08 19:30:47 -04:00
Marshall Bowers
7c5bc3c26f Add the ability for extensions to provide language settings (#10296)
This PR adds the ability for extensions to provide certain language
settings via the language `config.toml`.

These settings are then merged in with the rest of the settings when the
language is loaded from the extension.

The language settings that are available are:

- `tab_size`
- `hard_tabs`
- `soft_wrap`

Additionally, for bundled languages we moved these settings out of the
`settings/default.json` and into their respective `config.toml`s .

For languages currently provided by extensions, we are leaving the
values in the `settings/default.json` temporarily until all released
versions of Zed are able to load these settings from the extension.

---

Along the way we ended up refactoring the `Settings::load` method
slightly, introducing a new `SettingsSources` struct to better convey
where the settings are being loaded from.

This makes it easier to load settings from specific locations/sets of
locations in an explicit way.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-04-08 19:17:12 -04:00
Kirill Bulatov
4a3032c5e5 Append -- --nocapture to Rust function-level tests (#10299)
Release Notes:

- N/A
2024-04-09 01:36:24 +03:00
Conrad Irwin
f327118e06 vim: Allow search with operators & visual mode (#10226)
Fixes: #4346

Release Notes:

- vim: Add search motions (`/,?,n,N,*,#`) in visual modes and as targets
for operators like `d`,`c`,`y`
([#4346](https://github.com/zed-industries/zed/issues/4346)).
2024-04-08 15:20:14 -06:00
joaquin30
f9bf60f017 vim: Fix cgn backwards movement when there is no matches (#10237)
Release Notes:

- Fixed `cgn` backwards movement problem in #9982

There are two issues:

- When there are no more matches, the next repetition still moves the
cursor to the left. After that, the recording is cleared. For this I
simply move the cursor to the right, but it doesn't work when the cursor
is at the end of the line.
- If `cgn` is used when there are no matches, it cleans the previous
recorded actions. Maybe there should be a way to revert the recording.
This also happens when using `c` and `esc`

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-08 14:51:36 -06:00
Hans
0390df27d4 Fix block cursor does not render italic for vim (#10249)
Release Notes:

- Fixed #8799
2024-04-08 14:50:50 -06:00
Mikayla Maki
cf5a113751 Don't panic when multiple Zed instances are open (#10295)
This stops an annoying panic that can occur when developing Zed 

Release Notes:

- N/A
2024-04-08 12:26:38 -07:00
Bennet Bo Fenner
7dccbd8e3b markdown preview: Improve live preview (#10205)
This PR contains various improvements for the markdown preview (some of
which were originally part of #7601).
Some improvements can be seen in the video (see also release notes down
below):


https://github.com/zed-industries/zed/assets/53836821/93324ee8-d366-464a-9728-981eddbfdaf7

Release Notes:
- Added action to open markdown preview in the same pane
- Added support for displaying channel notes in markdown preview
- Added support for displaying the current active editor when opening
markdown preview
- Added support for scrolling the editor to the corresponding block when
double clicking an element in markdown preview
- Improved pane creation handling when opening markdown preview
- Fixed markdown preview displaying non-markdown files
2024-04-08 21:17:40 +02:00
Marshall Bowers
d009d84ead Add support for using a language server with multiple languages (#10293)
This PR updates the `extension.toml` to allow specifying multiple
languages for a language server to work with.

The `languages` field takes precedence over `language`. In the future
the `language` field will be removed.

As part of this, the Emmet extension has been extended with support for
PHP and ERB.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-04-08 14:24:56 -04:00
Hans
5e44748677 Adjust string splitting function (#10221)
- Fixed #9729 and #10193

This commit fixes an issue where the string splitting function was
handling characters in the input string improperly. We adjusted the use
of the `take_while` function to calculate the length of the numeric
prefix, rather than directly splitting the string, thus correctly
splitting the string into a numeric prefix part and the remaining part
2024-04-08 11:05:03 -07:00
Mikayla Maki
d2bf80ca3d Make search context larger (#10289)
This increases search context from 1 above, 2 below, to 2 above and 2
below, matching the Sublime Text search results.

Release Notes:

- Increased search result context from 3 lines to 4 lines
2024-04-08 10:57:36 -07:00
Hans
44aed4a0cb Add surrounds support for vim (#9400)
For #4965

There are still some minor issues: 
1. When change the surround and delete the surround, we should also
decide whether there are spaces inside after deleting/replacing
according to whether it is open parentheses, and replace them
accordingly, but at present, delete and change, haven't done this
adaptation for current pr, I'm not sure if I can fit it in the back or
if it needs to be fitted together.
2. In the selection mode, pressing s plus brackets should also trigger
the Add Surrounds function, but this MR has not adapted the selection
mode for the time being, I think we need to support different add
behaviors for the three selection modes.(Currently in select mode, s is
used for Substitute)
3. For the current change surrounds, if the user does not find the
bracket that needs to be matched after entering cs, but it is a valid
bracket, and will wait for the second input before failing, the better
practice here should be to return to normal mode if the first bracket is
not found
4. I reused BracketPair in language, but two of its properties weren't
used in this mr, so I'm not sure if I should create a new struct with
only start and end, which would have less code

I'm not sure which ones need to be changed in the first issue, and which
ones can be revised in the future, and it seems that they can be solved

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-08 11:41:06 -06:00
Conrad Irwin
e826ef83e2 Fix panic in visual line mode with folds (#10284)
Fixes: #10266



Release Notes:

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

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

**or**

- N/A
2024-04-08 11:39:06 -06:00
Marshall Bowers
56c0345cf3 Respect language server's capabilities when calling GetReferences (#10285)
This PR makes Zed respect the language server's capabilities when
calling the `GetReferences` command (used in "Find All References",
etc.).

This fixes a crash that could occur when using Zed with Gleam v1.0.

Release Notes:

- Made "Find All References" respect the language server's capabilities.
This fixes some instances where certain language servers would stop
working after receiving a "Find All References" request.

---------

Co-authored-by: Max <max@zed.dev>
2024-04-08 13:38:32 -04:00
Andrew Lygin
f1428fea4e Make scrollbar a bit wider (#10248)
At the moment, the editor scrollbar is 12px wide. One pixel is allocated
for the left border, so we have 11 pixels to display markers. It's not
enough to make three even marker columns (git, highlights, diagnostics)
that fully fill the scrollbar, so the current implementation allocates 3
pixels to each column.

As the result, we have 2 spare pixels on the right (before #10080 they
were occupied by the diagnostics column). Making the scrollbar just one
pixel wider allows us to give one additional pixel to each marker column
and make markers more pronounced ("as is" on the left, "to be" on the
right):

<img width="115" alt="zed-scrollbar-markers-1px"
src="https://github.com/zed-industries/zed/assets/2101250/4bdf0107-c0f1-4c9c-9063-d2ff461e1c32">

Other options:
- Remove scrollbar thumb border. That'll give us one missing pixel to
make markers wide and even. I, personally, prefer this option, but
themes now have `scrollbar.thumb.border` colors that differ from
`scrollbar.thumb.background` for some reason. This theme setting becomes
deprecated in this case. For the reference: VS Code doesn't have
scrollbar slider borders, IntelliJ IDEA does have them.
- Don't try to make markers evenly wide. For instance, IntelliJ uses
very narrow git-diff markers that are separated from other markers. But
it requires much wider scrollbar (it's 20px in IDEA).
- Use the spare two pixels to make diagnostic markers wider (it's the
pre #10080 approach), or split them between the highlight and diagnostic
markers (have 3px+4px+4px marker columns).
- Do nothing. It leaves us with two unused pixels :(

Release Notes:

- N/A

Related Issues:

- The previous discussion:
https://github.com/zed-industries/zed/pull/9080#issuecomment-1997979968
2024-04-08 10:32:09 -07:00
Conrad Irwin
9b88259b1f Fix panic in drag entered (#10277)
Co-Authored-By: Kirill <kirill@zed.dev>

Release Notes:

- Fixed panic when dragging into Zed.

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

**or**

- N/A

Co-authored-by: Kirill <kirill@zed.dev>
2024-04-08 11:18:10 -06:00
Thorsten Ball
4d68bf2fa6 Fix panic when deleting just-generated text (#10282)
We ran into a panic when deleting text that was just generated by a code
action.

This fixes it by ensuring we don't run into a 0-minus-1 panic when a
buffer_range is empty.

Release Notes:

- Fixed panic that could occur when deleting generated-by-unsaved text.

Co-authored-by: Conrad <conrad@zed.dev>
2024-04-08 17:59:25 +02:00
Thorsten Ball
87c282d8f1 Send along diagnostics when requesting code actions (#10281)
This fixes #10177 by sending along the correct diagnostics when querying
the language server for diagnostics for a given cursor location.

Turns out that `gopls` takes the `range`, `source`, `message` of the
diagnostics sent along to check whether it has any code actions for the
given location.

Release Notes:

- Fixed "quickfix" code actions that were based on diagnostics not
showing up in Go files.
([#10177](https://github.com/zed-industries/zed/issues/10177)).

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
2024-04-08 17:54:06 +02:00
Marshall Bowers
134decb75e Add compatibility table between Zed and zed_extension_api versions (#10279)
This PR updates the README of the `zed_extension_api` crate to show the
compatibility between different versions of `zed_extension_api` and Zed.

Release Notes:

- N/A
2024-04-08 11:15:35 -04:00
Piotr Osiewicz
f0d4d71e97 pane: Always notify status bar items on Pane::Focused events (#10275)
Due to peculiarities in handling of terminal panes (namely the fact that
they are not actually tracked by the Workspace::active_pane member), it
was possible to get into a state where status bar items "lost track" of
an active pane item; one way to reproduce it was to open a new terminal
via "workspace: new terminal" with a pane open in a central view; once a
new terminal is opened, the language selector and line number indicator
lose track of an active item. Focusing central view does nothing - it
will only go away after switching a tab in the central view.

To remedy this, we now always notify the status bar items of a pane
focus change, even if Workspace::active_pane points to the same pane.

Release Notes:

- Fixed status bar focus issues when spawning a terminal via `workspace:
new terminal` action.
2024-04-08 17:03:25 +02:00
Mike Sun
bcdae9fefa Add settings to hide/show navigation history buttons (#10240)
This is another variant for this [original
PR](https://github.com/zed-industries/zed/pull/10091) to add settings to
show/hide navigation history buttons that puts the settings under a new
section called `tab_bar`:

```
  "tab_bar": {
    // Whether or not to show the navigation history buttons.
    "show_nav_history_buttons": true
  }
```

<img width="314" alt="Screenshot 2024-04-02 at 3 00 53 PM"
src="https://github.com/zed-industries/zed/assets/1253505/23c4fa19-5a63-4160-b3b7-1b5e976c36bf">
<img width="329" alt="Screenshot 2024-04-02 at 3 01 03 PM"
src="https://github.com/zed-industries/zed/assets/1253505/64c2ebd2-9311-4589-a4e8-bd149c6c4ece">
2024-04-08 10:46:36 -04:00
Piotr Osiewicz
7aef447f47 chore: Remove tasks.md (#10273)
The file has been moved over to zed.dev repo and resurrected some time
ago.

Release Notes:

- N/A
2024-04-08 16:21:24 +02:00
Max Brunsfeld
4bdfc12b79 Remove duplicated code for unchanged parts of different extension API versions (#10218)
Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-08 10:16:12 -04:00
Piotr Osiewicz
4ce5b22989 tasks: Add status indicator to the status bar (#10267)
Release Notes:

- Added task status indicator to the status bar.
2024-04-08 14:43:00 +02:00
Ben Hamment
ce5bc399df Improve Ruby Syntax (#10255)
Release Notes:

fixes #9995  being able to target constants
<img width="336" alt="image"
src="https://github.com/zed-industries/zed/assets/7274458/9e8cc438-10c4-441f-9140-3f4b418bd3bd">

Adds highlighting for parameters In blocks
<img width="318" alt="image"
src="https://github.com/zed-industries/zed/assets/7274458/4fa45fbe-104b-4778-994b-3b6d6ba930d4">
2024-04-08 13:12:24 +02:00
Piotr Osiewicz
4f9ad300a7 tasks: Use icons instead of secondary text in a modal (#10264)
Before:

![image](https://github.com/zed-industries/zed/assets/24362066/feae9c98-37d4-437d-965a-047d2e089a7b)
After:

![image](https://github.com/zed-industries/zed/assets/24362066/43e48985-5aba-44d9-9128-cfafb9b61fd4)

Release Notes:

- N/A
2024-04-08 11:41:54 +02:00
Joseph T. Lyons
3e6a9f6890 Bump PyGithub 2024-04-07 01:13:34 -04:00
Daniel Zhu
4944dc9d78 Show status of LSP actions (#9818)
Fixes #4380

Parts im still unsure about:
- [x] where exactly I should call `on_lsp_start`/`on_lsp_end`
- [x] how to handle things better than `let is_references =
TypeId::of::<R>() == TypeId::of::<GetReferences>();`, which feels very
janky
- [x] I want to have the message be something like `"Finding references
to [...]"` instead of just `textDocument/references`, but I'm not sure
how to retrieve the name of the symbol that's being queried
- [ ] I think the bulk of the runtime is occupied by `let result =
language_server.request::<R::LspRequest>(lsp_params).await;`, but since
`ModelContext` isn't passed into it, I'm not sure how to update progress
from within that function
- [x] A good way to disambiguate between multiple calls to the same lsp
function; im currently using the function name itself as the unique
identifier for that request, which could create issues if multiple
`textDocument/references` requests are sent in parallel

Any help with these would be deeply appreciated!

Release Notes:

- Adds a status indicator for LSP actions

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-06 19:48:11 -07:00
Kyle Kelley
c7961b9054 Implement ObjectFit::ScaleDown for images (#10063)
While working towards fixes for the image viewer, @mikayla-maki and I
discovered that we didn't have `object-fit: scale-down` implemented.
This doesn't _fully_ solve the image issues as there is some issue where
only the bounds width is updating on layout change that I haven't fully
chased down.

Co-Authored-By: @mikayla-maki 

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-04-06 15:20:30 -07:00
Andrew Dunbar
c64c2758c0 Fix reference to soft_wrap option in comment (#10239)
Brought this up briefly in Discord:
https://discord.com/channels/869392257814519848/873293828805771284/1226224505760776192

Release Notes:

- Fixed an incorrect reference to the `soft_wrap` setting in the default
settings documentation.
2024-04-06 15:46:06 -04:00
Conrad Irwin
0325bda89a Improve lsp notifications (#10220)
1. They now will not go off-screen
2. You can scroll long messages.
3. Only one notification per language server is shown at a time
4. The title/text are now distinguished visually
5. You can copy the error message to the clipboard

Fixes: #10217
Fixes: #10190
Fixes: #10090

Release Notes:

- Fixed language server notifications being too large
([#10090](https://github.com/zed-industries/zed/issues/10090)).
2024-04-06 10:17:18 -06:00
Mikayla Maki
3aa242e076 Disable format on save for C and C++ (#10141)
We want Zed to be opinionated and low-configuration. Your code editor
should get out of the way, and just do the right thing.

However, some ecosystems aren't opinionated enough for us to
automatically detect the right way to format your code, so let's turn it
off.

Release Notes:

- Disabled `format_on_save` by default in C and C++.
2024-04-05 19:25:53 -07:00
Mikayla Maki
518cfdbd56 Adjust env parsing to account for multiline env values (#10216)
fixes https://github.com/zed-industries/zed/issues/6012

Release Notes:

- N/A
2024-04-05 19:24:46 -07:00
joaquin30
bf9b443b4a vim: Support gn command and remap gn to gl (#9982)
Release Notes:

- Resolves #4273

@algora-pbc /claim #4273

This is a work-in-progress. The process for `gn` command is:

- maintain updated vim.workspace_state.search.initial_query
- modify editor.select_next_state with
vim.workspace_state.search.initial_query
- use editor.select_next()
- merge selections
- set editor.select_next_state to previous state

To make this possible, several private members and editor structures are
made public. `gN` is not yet implemented and the cursor still does not
jump to the next selection in the first use.

Maybe there is an better way to do this?

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-05 20:23:37 -06:00
Mikayla Maki
fe4b345603 Fix interpretation of \n in hovers (#10214)
I ran into this specifically when looking at the documentation of
https://crates.io/crates/wayland-client

Release Notes:

- Fixed a bug where some hover popovers would render `\n` instead of a
new line.
2024-04-05 15:59:37 -07:00
Kirill Bulatov
7b636d9774 Limit the extension tasks in the modal to current language only (#10207)
Release Notes:

- N/A
2024-04-06 00:18:32 +03:00
Marshall Bowers
c851e6edba Add language_server_workspace_configuration to extension API (#10212)
This PR adds the ability for extensions to implement
`language_server_workspace_configuration` to provide workspace
configuration to the language server.

We've used the Dart extension as a motivating example for this, pulling
it out into an extension in the process.

Release Notes:

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

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-04-05 17:04:07 -04:00
Marshall Bowers
4aaf3459c4 Improve extension API documentation (#10210)
This PR adds more documentation for various constructs in the
`zed_extension_api` crate.

`wit_bindgen` is able to generate doc comments on the Rust constructs
using the the doc comments in the WIT files, so we're able to leverage
that for the majority of the constructs that we expose.

Release Notes:

- N/A
2024-04-05 13:00:24 -04:00
Thorsten Ball
b05aa381aa Handle old versions of /usr/bin/env when loading shell env (#10202)
This fixes #9786 by using an invocation of `/usr/bin/env` that's
supported by macOS 12.

As it turns out, on macOS 12 (and maybe 13?) `/usr/bin/env` doesn't
support the `-0` flag. In our case it would silently fail, since we
`exit 0` in our shell invocation and because the program we run and
whose exit code we check is the `$SHELL` and not `/usr/bin/env`.

What this change does is to drop the `-0` and instead split the
environment on `\n`. This works even if an environment variable contains
a newline character because that would then be escaped.

Release Notes:

- Fixed Zed not picking up shell environments correctly when running on
macOS 12. ([#9786](https://github.com/zed-industries/zed/issues/9786)).

Co-authored-by: Dave Smith <davesmithsemail@gmail.com>
2024-04-05 15:46:56 +02:00
Hans
ec6efe262f Fix crash when joining two consecutive lines (#10000)
Release notes:

- Fixed a crash when joining two consecutive lines
([#9692](https://github.com/zed-industries/zed/pull/9692)).


This crash is not caused by `vim` or `editor`'s code logic, `join_line`
logic is okay, I found that the crash is caused by a refresh of git
`diff` after every update, hhen git diff generates hunks, it will look
for the cursor to the beginning of a line, and judge that if the cursor
result column is greater than 0, that is, it is not the beginning of a
line, it will correct the row to the next line, I think before we forgot
here that I need to adjust the column to 0 at the same time, otherwise
it is easy to go out of bounds, I am not sure if I need to add more
tests for this method, I can add if I need to, but I feel that this case
is a bit extreme

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-04-05 15:45:29 +02:00
Joseph T. Lyons
6c45bc2b3d Bump Python version in actions 2024-04-05 08:24:00 -04:00
Joseph T. Lyons
83364c709b Bump setup-python version 2024-04-05 08:22:44 -04:00
Joseph T. Lyons
4cab4e8a10 Fix flag name 2024-04-05 08:17:55 -04:00
Joseph T. Lyons
1737329e84 Use existence of issue_reference_number flag to determine if prod 2024-04-05 08:13:33 -04:00
Andrew Lygin
3ae6463869 Fix scrollbar markers in large files (#10181)
#10080 introduced a minor change in how the min marker height is
enforced. Before the change, it was applied to the aggregated marker,
but after the change it's applied to each individual marker before
aggregation.

The difference is not noticeable on small files, where even single row
markers are higher than `MIN_MARKER_HEIGHT`, but it leads to visible
differences in large files with repeating blocks of highlighted and
not-highlighted blocks, like in [this
case](https://github.com/zed-industries/zed/pull/9080#issuecomment-2006796376).

This PR fixes how the `MIN_MARKER_HEIGHT` is applied.

Before the fix:

<img width="727" alt="zed-scroll-markers-before"
src="https://github.com/zed-industries/zed/assets/2101250/a1c34746-af4f-4054-8de2-edabf3db7cee">

After the fix:

<img width="736" alt="zed-scroll-markers-after"
src="https://github.com/zed-industries/zed/assets/2101250/b9ee843d-055e-42a6-af26-e7fd4f7729f8">


Release Notes:

- N/A

/cc @mrnugget
2024-04-05 10:17:39 +02:00
Joseph T. Lyons
773a3e83ad Don't require an issue number while in dev mode 2024-04-05 01:49:21 -04:00
Drazen
cedbfac844 Fix typo (#10183)
Release Notes:

- N/A
2024-04-05 01:07:19 +02:00
Conrad Irwin
73d8a43c81 vim: Allow : in empty panes and screen shares (#10171)
Release Notes:

- vim: Fixed `:` when no files are open
2024-04-04 14:24:49 -06:00
Marshall Bowers
4a325614f0 Add label_for_symbol to extension API (#10179)
This PR adds `label_for_symbol` to the extension API.

As a motivating example, we implemented `label_for_symbol` for the
Haskell extension.

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-04-04 15:38:38 -04:00
Bennet Bo Fenner
5d88d9c0d7 markdown preview: Add link tooltips (#10161)
Adds tooltips to the markdown preview, similar to how its done for
`RichText`


https://github.com/zed-industries/zed/assets/53836821/523519d4-e392-46ef-9fe0-6692871b317d

Release Notes:

- Added tooltips when hovering over links inside the markdown preview
2024-04-04 21:06:30 +02:00
Bennet Bo Fenner
dde87f6468 markdown preview: Auto detect raw links (#10162)
Similar to the work done in `rich_text`, raw links now get picked up in
the markdown preview.


https://github.com/zed-industries/zed/assets/53836821/3c5173fd-cf8b-4819-ad7f-3127c158acaa

Release Notes:

- Added support for detecting and highlighting links in markdown preview
2024-04-04 21:05:35 +02:00
Marshall Bowers
d306b531c7 Add label_for_completion to extension API (#10175)
This PR adds the ability for extensions to implement
`label_for_completion` to customize completions coming back from the
language server.

We've used the Gleam extension as a motivating example, adding
`label_for_completion` support to it.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-04-04 13:56:04 -04:00
Kirill Bulatov
0f1c2e6f2b Return back the ability to save non-dirty singleton buffers (#10174) 2024-04-04 18:06:47 +02:00
Jason Lee
0861ceaac2 Add yield keyword highlight for Rust (#10104)
Release Notes:

- Added `yield` keyword highlight for Rust


Ref:

- https://github.com/rust-lang/rust-analyzer/pull/7209
-
https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide/src/syntax_highlighting/highlight.rs#L177
-
https://doc.rust-lang.org/reference/keywords.html?highlight=yield#reserved-keywords


In VS Code:
 

![SCR-20240403-hrb](https://github.com/zed-industries/zed/assets/5518/ec3e84ce-ea9d-4b2d-832d-ecdfec0def91)

docs.rs:
https://docs.rs/async-stream/latest/async_stream/macro.try_stream.html


![SCR-20240403-gpk](https://github.com/zed-industries/zed/assets/5518/07010c2c-341d-4ae2-ba80-5f4eab4dbf60)

## Before

<img width="644" alt="image"
src="https://github.com/zed-industries/zed/assets/5518/da349187-57e6-4cea-b3e3-f628ce6a99e8">


## After update in Zed

![SCR-20240403-hqk](https://github.com/zed-industries/zed/assets/5518/44f1687b-ec38-42c2-984d-15177bed7e5b)
2024-04-04 17:56:33 +02:00
Piotr Osiewicz
1c485a0d05 tasks: change placeholder text in a modal (#10166)
Related to: #10132

Release Notes:

- N/A
2024-04-04 15:37:53 +02:00
Piotr Osiewicz
7d1a5d2ddf zed-local: add --stable flag to zed-local (#10165)
`--stable` makes all clients except for the first one use a stable
version of Zed (hardcoded to `/Applications/Zed/Contents/MacOS/zed` for
now).

That should make testing cross-channel collab changes a bit easier. /cc
@maxbrunsfeld @ConradIrwin

Release Notes:

- N/A
2024-04-04 15:26:41 +02:00
Bennet Bo Fenner
27165e9927 channel chat: Set first loaded message ID when sending a message (#10034)
Discovered while looking into #10024.

When clicking on a reply message text, the original message should be
highlighted accordingly. However this would not work when the channel
was just created and the user is the only one that sent messages.
 
Release Notes:

- Fixed highlighting of messages when clicking on the reply message text
in the chat and there were no other messages from other users
2024-04-04 15:12:35 +02:00
Kirill Bulatov
1085642c88 Stricten Zed Task variable API (#10163)
Introduce `VariableName` enum to simplify Zed task templating
management: now all the variables can be looked up statically and can be
checked/modified in a centralized way: e.g. `ZED_` prefix is now added
for all such custom vars.

Release Notes:

- N/A
2024-04-04 16:02:24 +03:00
Thorsten Ball
ee1b1779f1 Document formatter: code_actions (#10157)
This documents what was introduced in #10121 and shipped in 0.130.x

Release Notes:

- N/A
2024-04-04 14:12:28 +02:00
Bennet Bo Fenner
5b4ff74dca collab ui: Dismiss project shared notifications when leaving room (#10160)
When leaving a call/room in which a project was shared, the shared
project notification was not getting dismissed when the person that
shared the project left the room.
Although there was a `cx.emit(Event::Left)` call inside room, the event
was never received in the `project_shared_notification` module, because
the room is dropped before the event can be dispatched. Moving the
`cx.emit(Event::Left)` to the active call fixed the problem. Also
renamed `Event::Left` to `Event::RoomLeft` because the room join
equivalent is also called `Event::RoomJoined`.


Release Notes:

- Fixed project shared notification staying open, when the user that
shared the project left the room
2024-04-04 13:43:14 +02:00
Thorsten Ball
8e9543aefe Improve handling of prettier errors on format (#10156)
When no formatter for a language is specified, Zed has the default
behaviour:

1. Attempt to format the buffer with `prettier`
2. If that doesn't work, use the language server.

The problem was that if `prettier` failed to format a buffer due to
legitimate syntax errors, we simply did a fallback to the language
server, which would then format over the syntax errors.

With JavaScript/React/TypeScript projects this could lead to a situation
where

1. Syntax error was introduced
2. Prettier fails
3. Zed ignores the error
4. typescript-language-server formats the buffer despite syntax errors

This would lead to some very weird formatting issues.

What this PR does is to fix the issue by handling `prettier` errors and
results in two user facing changes:

1. When no formatter is set (or set to `auto`) and if we attempted to
start a prettier instance to format, we will now display that error and
*not* fall back to language server formatting.
2. If the formatter is explicitly set to `prettier`, we will now show
errors if we failed to spawn prettier or failed to format with it.

This means that we now might show *more* errors than previously, but I
think that's better than not showing anything to the user at all.

And, of course, it also fixes the issue of invalid syntax being
formatted by the language server even though `prettier` failed with an
error.

Release Notes:

- Improved error handling when formatting buffers with `prettier`.
Previously `prettier` errors would be logged but ignored. Now `prettier`
errors are shown in the UI, just like language server errors when
formatting. And if no formatter is specified (or set to `"auto"`) and
Zed attempts to use `prettier` for formatting, then `prettier` errors
are no longer skipped. That fixes the issue of `prettier` not formatting
invalid syntax, but its error being skipped, leading to
`typescript-language-server` or another language server formatting
invalid syntax.
2024-04-04 11:41:55 +02:00
Remco Smits
c0d117182f Fix clear reply to message and edit message state when you switch state (#10044)
This pull request fixes the following issue #10042.

Release Notes:
- Fixed clear chat state when switching edit/reply message state.
([#10042](https://github.com/zed-industries/zed/issues/10042)).
2024-04-04 08:36:27 +02:00
Hans
9cbde74274 Refactor selection expansion logic into a separate method (#10117)
Release Notes:

- N/A

This commit introduces a new method `range` to calculate the target
range for selection expansion based on the current selection, movement
times, and other parameters. The `expand_selection` method is refactored
to use this new `range` method, simplifying the logic for expanding a
selection and making the code more modular and reusable. The `range`
method encapsulates the logic for calculating the new selection range,
including handling linewise selection and adjustments for surrounding
newlines, making it easier to understand and maintain the selection
expansion functionality.

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-04-04 08:35:26 +02:00
Piotr Osiewicz
879f361966 tasks: fix panic in render_match (#10137)
Release Notes:

- Fixed panic in tasks modal (Preview only).
2024-04-03 22:09:36 +02:00
Piotr Osiewicz
79272b75e3 extensions: Add author to the manifest (#10134)
Related to #9910 
Also, this PR will add a release note, since I've missed that in the
original PR.
Release Notes:

- Added Emmet language extension to the extension store.
2024-04-03 20:18:31 +02:00
Piotr Osiewicz
0ddec2753a Add emmet-language-server support (#9910)
Note that I want to move this into an extension before merging.

Fixes: #4992 

Release Notes:

- Added Emmet snippets support
2024-04-03 19:54:53 +02:00
Conrad Irwin
ccb2d02ce0 Remove datadog (#10133)
Release Notes:

- N/A
2024-04-03 11:35:23 -06:00
Conrad Irwin
fc08ea9b0d Fix undo in replace mode (#10086)
Fixes: #10031

Co-Authored-By: Petros <petros@amignosis.com>

Release Notes:

- vim: Fix undo grouping in Replace mode
([#10031](https://github.com/zed-industries/zed/issues/10031)).

---------

Co-authored-by: Petros <petros@amignosis.com>
2024-04-03 11:35:04 -06:00
Marshall Bowers
49c53bc0ec Extract HTML support into an extension (#10130)
This PR extracts HTML support into an extension and removes the built-in
HTML support from Zed.

Release Notes:

- Removed built-in support for HTML, in favor of making it available as
an extension. The HTML extension will be suggested for download when you
open a `.html`, `.htm`, or `.shtml` file.
2024-04-03 12:42:36 -04:00
Max Brunsfeld
256b446bdf Refactor LSP adapter methods to compute labels in batches (#10097)
Once we enable extensions to customize the labels of completions and
symbols, this new structure will allow this to be done with a single
WASM call, instead of one WASM call per completion / symbol.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Marshall <marshall@zed.dev>
2024-04-03 09:22:56 -07:00
Joseph T. Lyons
ef3d04efe6 v0.131.x dev 2024-04-03 12:11:28 -04:00
Marshall Bowers
469be39a32 file_icons: Add license file (#10128)
This PR adds a missing license file to the `file_icons` crate.

Release Notes:

- N/A
2024-04-03 12:07:53 -04:00
Marshall Bowers
db5d53d1d1 clojure: Add license file (#10127)
This PR adds a symlink to the `LICENSE-APACHE` file for the Clojure
extension.

I knew I was forgetting something 😓

Release Notes:

- N/A
2024-04-03 12:04:14 -04:00
Piotr Osiewicz
b118b76272 editor: Fix "Toggle comments" not respecting multiple line_comments in language config (#10126)
This does not try to heuristically pick a comment style based on
surroundings anyhow. It does improve our story around uncommenting
though.

Fixes #10113.

Release Notes:

- Fixed "Toggle comment" action not working in presence of non-default
line comments such as doc comments in Rust
([#10113](https://github.com/zed-industries/zed/issues/10113)).
2024-04-03 17:34:59 +02:00
Andrew Lygin
57a1b9b2cd tab_switcher: Add tab close buttons (#9968)
Support for closing tabs from Tab Switcher:

- Close button color matches the indicator color to preserve the
information that the buffer is dirty (as in SublimeText).
- `ctrl-backspace` closes the currently selected item.


https://github.com/zed-industries/zed/assets/2101250/8ea33911-2f62-4199-826d-c17556db8e9a

Release Notes:

- N/A
2024-04-03 17:28:51 +02:00
Jason Lee
8eeecdafec Add crate keyword to Rust (#10110)
Release Notes:

- Fixed `crate` keyword not being highlighted in Rust files.

Ref: #10104 

And I make a sort for them.
2024-04-03 17:27:22 +02:00
Thorsten Ball
eb231d0449 Add code_actions as formatter type (#10121)
This fixes #8992 and solves a problem that ESLint/Prettier/... users
have been running into:

They want to format _only_ with ESLint, which is *not* a primary
language server (so `formatter: language server` does not help) and it
is not a formatter.

What they want to use is what they get when they have configured
something like this:

```json
{
  "languages": {
    "JavaScript": {
      "code_actions_on_format": {
        "source.fixAll.eslint": true
      }
    }
  }
}
```

BUT they don't want to run the formatter.

So what this PR does is to add a new formatter type: `code_actions`.

With that, users can only use code actions to format:

```json
{
  "languages": {
    "JavaScript": {
      "formatter": {
        "code_actions": {
          "source.fixAll.eslint": true
        }
      }
    }
  }
}
```

This means that when formatting (via `editor: format` or on-save) only
the code actions that are specified are being executed, no formatter.


Release Notes:

- Added a new `formatter`/`format_on_save` option: `code_actions`. When
configured, this uses language server code actions to format a buffer.
This can be used if one wants to, for example, format a buffer with
ESLint and *not* run prettier or another formatter afterwards. Example
configuration: `{"languages": {"JavaScript": {"formatter":
{"code_actions": {"source.fixAll.eslint": true}}}}}`
([#8992](https://github.com/zed-industries/zed/issues/8992)).

---------

Co-authored-by: JH Chabran <jh@chabran.fr>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-03 16:16:03 +02:00
Marshall Bowers
654504d5ee Remove basic.conf (#10120)
This PR removes the `basic.conf` file.

In #10099 we suppressed some typo warnings that had cropped up in this
file, but it turns out we don't need the file at all.

Release Notes:

- N/A
2024-04-03 09:38:36 -04:00
Thorsten Ball
08e8ffcef2 docs: minor changes to ESLint docs (#10116)
We allow more than just `shortenToSingleLine`, so let's document the
whole thing.

Release Notes:

- N/A
2024-04-03 14:07:23 +02:00
Kirill Bulatov
027897e003 Do not spawn oneshot tasks from blank prompts (#10115) 2024-04-03 13:54:16 +02:00
Thorsten Ball
c4ceeb715a Fix git blame not working correctly with submodules (#10114)
This fixes #9958 by using the correct working directory in which to run
`git blame`.

This wasn't an issue for a single repository, because we could go from
`.git` one directory up, but it doesn't work for submodules.

Luckily there's a `workdir()` method on `self.repository` that does
exactly what we need.

In submodule:

![screenshot-2024-04-03-12 37
18@2x](https://github.com/zed-industries/zed/assets/1185253/67e89abb-d04c-4e9d-802f-5b8468e0962e)

Not in submodule:

![screenshot-2024-04-03-12 37
36@2x](https://github.com/zed-industries/zed/assets/1185253/cfe59f59-798b-43e1-980d-2225db4c0302)

Release Notes:

- N/A
2024-04-03 13:49:12 +02:00
Kirill Bulatov
58aec1de75 Do not push invisible local worktrees into recent documents (#10112)
Follow-up of https://github.com/zed-industries/zed/pull/9919 that
removes invisible worktrees from the list, to avoid things like

![image](https://github.com/zed-industries/zed/assets/2690773/90ce1c29-a1dd-4a03-b09a-effdba620c8f)

Release Notes:

- N/A
2024-04-03 13:47:01 +03:00
Kirill Bulatov
9aad30a559 Query code actions and hovers from all related local language servers (from remote clients) (#10111)
Supersedes https://github.com/zed-industries/zed/pull/8634
Fixes https://github.com/zed-industries/zed/issues/7947 by continuing
https://github.com/zed-industries/zed/pull/9943 with the remote part.

Now, clients are able to issue collab requests, that query all related
language servers, not only the primary one.
Such mode is enabled for GetHover and GetCodeActions LSP requests only.

Release Notes:

- Added Tailwind CSS hover popovers for Zed in multi player mode
([7947](https://github.com/zed-industries/zed/issues/7947))
2024-04-03 13:34:56 +03:00
Thorsten Ball
3a0d3cee87 Compute scrollbar markers asynchronously (#10080)
Refs #9647
Fixes https://github.com/zed-industries/zed/issues/9792

This pull request moves the computation of scrollbar markers off the
main thread, to prevent them from grinding the editor to a halt when we
have a lot of them (e.g., when there are lots of search results on a
large file). With these changes we also avoid generating multiple quads
for adjacent markers, thus fixing an issue where we stop drawing other
primitives because we've drawn too many quads in the scrollbar.

Release Notes:

- Improved editor performance when displaying lots of search results,
diagnostics, or symbol highlights in the scrollbar
([#9792](https://github.com/zed-industries/zed/issues/9792)).

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Nathan <nathan@zed.dev>
2024-04-03 12:21:17 +02:00
Thorsten Ball
7dbcace839 Fix accidentally dropping shell environment variable (#10105)
Previously this code would run the changed commend, take its output,
remove the `marker` from the front and then split on `0` byte.

Problem was that `echo` adds a newline, which we did *NOT* skip. So
whatever `env` printed as the first environment variable would have a
`\n` in front of it.

Instead of setting, say, `HOME`, Zed would set `\nHOME`.

This change fixes the issue by switching to `printf '%s' marker`, which
is more portable than using `echo -n`.

This is related to https://github.com/zed-industries/zed/issues/9786 but
I'm not sure yet whether that fixes it.

Release Notes:

- Fixed Zed sometimes missing environment variables from shell in case
they were the first environment variable listed by `/usr/bin/env`.
2024-04-03 09:34:17 +02:00
William Villeneuve
463c16a402 Allow users to configure ESLint's problems settings (#9981)
Presently the only available setting under `problems` is
`shortenToSingleLine`, which defaults to `false`.

Example Zed `settings.json` to shorten eslint error squiggles to only
show on the first line of the problem:
```json
{
    "lsp": {
        "eslint": {
            "settings": {
                "problems": {
                    "shortenToSingleLine": true
                }
            }
        }
    }
}
```


Release Notes:

- Added support for configuring ESLint `problems` settings, ie. `{"lsp":
{"eslint": {"settings": {"problems": {"shortenToSingleLine": true}}}}}`

Demo:



https://github.com/zed-industries/zed/assets/2072378/379faa75-1f37-4fd1-85da-1510f1397d07
2024-04-03 09:29:07 +02:00
Conrad Irwin
5a2a85a7db Fix vim code working on display map chars (#10103)
Release Notes:

- vim: Fixed motion bugs when softwrap, folds or inlay hints were used.
2024-04-02 22:16:52 -06:00
Remco Smits
754547f349 Fix mention notifications are not updated after message change and not removed after a message is deleted (#9847)
@ConradIrwin This is a followup for #9035 as agreed.

Release Notes:

- Fixed mention notifications are updated when channel message is
updated. And mention notifications are removed when message is removed.

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2024-04-02 20:40:00 -06:00
Conrad Irwin
fe7b12c444 fix rejoin after quit (#10100)
Release Notes:

- collab: Fixed rejoining channels quickly after a restart
2024-04-02 20:35:14 -06:00
Marshall Bowers
8958c9e10f Suppress typos in basic.conf (#10099)
This PR updates the `typos` configuration to suppress some typos in
`basic.conf`.

AFAICT there must have been an update to `typos` that caused this new
warning to appear.

Release Notes:

- N/A
2024-04-02 21:02:25 -04:00
Max Brunsfeld
7d5048e909 Revert PR #6924 - go to reference when there's only one (#10094)
This PR reverts #6924, for the reasons stated in
https://github.com/zed-industries/zed/pull/6924#issuecomment-2033076300.

It also fixes an issue were the `find_all_references_task_sources`
wasn't cleaned up in all cases.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-02 14:31:58 -07:00
Conrad Irwin
65cde17063 Fix collab logging (#10095)
span! statically determines which fields are available, and record
silently fails if you write to a field that is not available :/

Release Notes:

- N/A
2024-04-02 15:31:30 -06:00
Conrad Irwin
9317fe46af Revert "Revert "Revert dependency updates in #9836 (#10089)""
This reverts commit c8b14ee2cb.
2024-04-02 13:12:38 -06:00
Conrad Irwin
c8b14ee2cb Revert "Revert dependency updates in #9836 (#10089)"
This reverts commit 55c897d993.
2024-04-02 12:59:10 -06:00
Mikayla Maki
55c897d993 Revert dependency updates in #9836 (#10089)
Due to: https://github.com/zed-industries/zed/issues/9985 and an
abundance of caution, I'm reverting the image and svg rendering updates
for now until we can debug the issue. cc: @niklaswimmer

Release Notes:

- N/A
2024-04-02 12:27:48 -06:00
Marshall Bowers
6121bfc5a4 Extract Clojure support into an extension (#10088)
This PR extracts Clojure support into an extension and removes the
built-in Clojure support from Zed.

Release Notes:

- Removed built-in support for Clojure, in favor of making it available
as an extension. The Clojure extension will be suggested for download
when you open a `.clj` or other Clojure-related files.

---------

Co-authored-by: Max <max@zed.dev>
2024-04-02 13:47:03 -04:00
Max Brunsfeld
46544d7354 Revert PR #8327 - Fix autocomplete completions being cut in half (#10084)
This reverts https://github.com/zed-industries/zed/pull/8327

That PR introduced a regression where completions' syntax highlighting
would be corrupted in a non-deterministic way, such that it varied from
frame to frame:

In the screenshot below, many of the field names (e.g. `cursor`,
`depth`) are incorrectly colored as types instead of fields. The
`finished_states` field has highlighting that changes at the wrong
offset. All of these values changed from frame to frame, creating a
strange flickering effect:

<img width="599" alt="Screenshot 2024-04-01 at 5 56 36 PM"
src="https://github.com/zed-industries/zed/assets/326587/b6a48f02-f146-4f76-92e6-32fb417d86c0">

Release Notes:

- N/A
2024-04-02 09:24:55 -07:00
Piotr Osiewicz
8df888e5b1 task: Add "remove" button next to oneshot tasks (#10083)
Release Notes:

- Added a "remove" button next to oneshot tasks in tasks modal.
2024-04-02 17:59:22 +02:00
Marshall Bowers
a1cb6772bf Make conversions to wasmtime::Result postfix-friendly (#10082)
This PR refactors the conversions to `wasmtime::Result` to use a trait
so that we can perform the conversions in a postfix-friendly way.

Release Notes:

- N/A
2024-04-02 11:38:15 -04:00
moshyfawn
c62239e9f0 Include commit hash in Nightly & Dev builds (#10054)
Release Notes:

- N/A

<img width="1338" alt="image"
src="https://github.com/zed-industries/zed/assets/16290753/c8442dbe-d293-46ef-abb1-ed8a6d9bf37d">

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-02 11:19:19 -04:00
René
15ef3f3017 Add Tailwind autocomplete for Vue (#10078)
This fixes #4403 by adding TailwindLsp to .vue files too and
autocomplete aswell


![image](https://github.com/zed-industries/zed/assets/49145060/8b06a478-cade-4cbc-9da7-f31f5197f304)

Release Notes:

- Added Tailwind support in `.vue` files
([#4403](https://github.com/zed-industries/zed/issues/4403)).
2024-04-02 10:34:41 -04:00
Thorsten Ball
ad03a7e72c Remove tooltips on scroll wheel events (#10069)
This fixes #9928 by invalidating the tooltip on mouse scroll.

I think _ideally_ we'd have a solution that only invalidates it if,
after mouse scroll, we're not hovering over the element. But I tried
that (by essentially duplicating the code for `MouseMoveEvent` but that
lead to some inconsistencies. I think we don't redraw when we finish
scrolling.

This now behaves exactly like tooltips in Chrome: invalidate on scroll,
move mouse again to trigger the tooltip.

It also behaves like the hover tooltips in the editor.


https://github.com/zed-industries/zed/assets/1185253/05b9170e-414c-4453-84e5-90510b943c15


Release Notes:

- N/A
2024-04-02 14:10:51 +02:00
Thorsten Ball
84cca62b2e Fix alignment in git blame gutter (#10067)
Fixes #9977.

Instead of doing nasty string alignment, this now uses the layout
engine.

![screenshot-2024-04-02-10 13
20@2x](https://github.com/zed-industries/zed/assets/1185253/ef167f9d-50de-4cc9-8a93-659a676c7855)


Release Notes:

- N/A
2024-04-02 13:36:28 +02:00
Piotr Osiewicz
b43602f21b editor: indent from cursor position with a single selection (#10073)
In 9970 @JosephTLyons noticed that tab + tab_prev action sequence leaves
a buffer in the dirty state, whereas "similar" indent-outdent does not.
I've tracked it down to the fact that tabs are always inserted at the
start of the line, regardless of the cursor position, whereas tab-prevs
act from cursor position.

This PR adjust tab/tab-prev actions (and indent-outdent) to act from
cursor position if possible. That way we can correctly report buffer
dirty state for these event sequences.

Fixes #9970 
Release Notes:

- Fixed buffer being marked as dirty when using tab/tab-prev actions.
2024-04-02 13:33:11 +02:00
Bennet Bo Fenner
1dbd520cc9 project search: Persist search history across session (#9932)
Partially implements #9717, persistence between restarts is currently
missing, but I would like to get feedback on the implementation first.

Previously the search history was not saved across different project
searches. As the `SearchHistory` is now maintained inside of the
project, it can be persisted across different project searches.

I also removed the behavior that a new query replaces the previous
search query, if it contains the text of the previous query.
I believe this was only intended to make buffer search work, therefore I
disabled this behavior but only for the project search.

Currently when you navigated through the queries the tab title changed
even if the search was not started, which doesn't make sense to me.
Current behavior:


https://github.com/zed-industries/zed/assets/53836821/1c365702-e93c-4cab-a1eb-0af3fef95476


With this PR the tab header will actually keep the search name until you
start another search again.

---

Showcase:


https://github.com/zed-industries/zed/assets/53836821/c0d6e496-915f-44bc-be16-12d7c3cda2d7


Release Notes:

- Added support for persisting project search history across a session
- Fixed tab header of project search changing when cycling through
search history, even when there is no search submitted
2024-04-02 11:13:18 +02:00
Kirill Bulatov
c15b9d4e1c Avoid failing format test with current date (#10068)
Replace the test that tested with
`chrono::offset::Local::now().naive_local()` taken, failing the
formatting once per year at least.


Release Notes:

- N/A
2024-04-02 10:37:14 +02:00
Mikayla Maki
1da2441e7b Fix assorted linux issues (#10061)
- Fix a bug where modifiers would be dispatched before they changed
- Add a secondary modifier
- Improve keybindings

Release Notes:

- N/A
2024-04-01 17:22:59 -07:00
Marshall Bowers
e0cd96db7b Fix up comments and remove some commented-out code (#10059)
Release Notes:

- N/A
2024-04-01 19:47:48 -04:00
Marshall Bowers
f19e84dc22 Fix doc comments for StyledText (#10058)
This PR fixes some doc comments for `StyledText` to better reflect Rust
doc comment conventions.

Release Notes:

- N/A
2024-04-01 19:25:17 -04:00
Mehmet Efe Akça
dde27483a4 vim: Avoid removing keymap context when blurred (#9960)
Release Notes:

- Fixes #4502 

Notes:
I removed this line of code which removes the vim keymap contexts when
an editor is blurred.


16e6f5643c/crates/vim/src/vim.rs (L703-L705)

I tried whether the editor context would be poisoned when switching
between two editors and disabling vim mode and switching back but the
context looked normal. If this change is wrong, please advise. I could
not find why this piece of code was required.

This fixes #4502 as the reason why keybinds did not show up was because
the vim context was removed from the editor's keymap contexts. Other
paths for a fix could be to filter out vim predicates when finding
keybinds for actions but I believe that'd add unnecessary complexity.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-01 16:43:14 -06:00
Conrad Irwin
499887d931 fix 9766 (#10055)
Release Notes:

- Fixed a panic in editor::SelectPrevious (`gN` in vim)
([#9766](https://github.com/zed-industries/zed/issues/9766)).
2024-04-01 16:30:14 -06:00
moshyfawn
fbf3e1d79d Use "install" to refer to extension installation process (#10049)
Release Notes:

- Improved discoverability of dev extension installation action
([#10048](https://github.com/zed-industries/zed/issues/10048)).
2024-04-01 15:27:03 -07:00
Marshall Bowers
83ce783856 Respect version constraints when installing extensions (#10052)
This PR modifies the extension installation and update process to
respect version constraints (schema version and Wasm API version) to
ensure only compatible versions of extensions are able to be installed.

To achieve this there is a new `GET /extensions/updates` endpoint that
will return extension versions based on the provided constraints.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-04-01 17:10:30 -04:00
Marshall Bowers
39cc3c0778 Allow extensions to provide data for language_ids (#10053)
This PR makes it so extensions can provide values for the `language_ids`
method on the `LspAdapter` trait.

These are provided as data in the `language_servers` section of the
`extension.toml`, like so:

```toml
[language_servers.intelephense]
name = "Intelephense"
language = "PHP"
language_ids = { PHP = "php"}
```

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-04-01 17:01:11 -04:00
Conrad Irwin
65f0712713 vim: fix v$% (#10051)
Release Notes:

- vim: Fixed `%` in visual mode when at the end of a line.
2024-04-01 14:18:09 -06:00
Marshall Bowers
8b586ef8e7 Add new make-file-executable API for extensions (#10047)
This PR adds a new function, `make-file-executable`, to the Zed
extension API that can be used to mark a given file as executable
(typically the language server binary).

This is available in v0.0.5 of the `zed_extension_api` crate.

We also reworked how we represent the various WIT versions on disk to
make it a bit clearer what the version number entails.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-04-01 15:28:24 -04:00
Ephram
6e49a2460e Fix autocomplete completions being cut in half (#8327)
Release Notes:

- Fixed LSP completions being cut in half
([#8126](https://github.com/zed-industries/zed/issues/8126)).

Previously, autocomplete suggestions were covered by autocomplete
documentation, which only appeared after a short delay.

Now, when an autocomplete suggestion is too long to fit, the name is
truncated with ellipses like how VSCode does it:

![image](https://github.com/zed-industries/zed/assets/50590465/bf3c6271-7d7a-44b1-ab76-647df5620fcd)

Additionally `completion_documentation_secondary_query_debounce`'s
default was changed from 300ms to 0ms, which makes the editor feel
significantly faster (in my opinion).

Before:


https://github.com/zed-industries/zed/assets/50590465/6443670b-fe25-4428-9a39-54405d9a7cec

After:


https://github.com/zed-industries/zed/assets/50590465/72572487-3eb4-4a96-a2f9-608e563a1f05

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-04-01 13:20:51 -06:00
apricotbucket28
d1d4f83722 Revert "Fix key repeat after releasing a different key on Wayland" (#10039)
Reverts zed-industries/zed#9768

That change didn't seem necessary and it made symbols that need a key
shortcut to be written (e.g. SHIFT + 2 for a quote) infinitely repeat.
 
Release Notes:

- N/A
2024-04-01 11:43:52 -07:00
Marshall Bowers
aa76182ca7 Skip .DS_Store files when looking for extension directories (#10046)
This PR makes it so `.DS_Store` files are skipped when trying to load
extension directories.

Previously it would fail and log an error.

Release Notes:

- Fixed an issue where the presence of a `.DS_Store` file in the
extensions directory would produce an error in the logs.
2024-04-01 13:34:00 -04:00
Stephen Belanger
30fad09dac Use hard tabs for Makefiles (#9978)
If you use soft tabs by default, editing Makefiles will be broken as
they require tab indentation to parse correctly.

Release Notes;

- Changed default settings for `Makefile`s to use hard tabs.
2024-04-01 12:47:08 -04:00
Andrew Lygin
a0f236af5d themes: Add pane_group.border color (#9986)
This PR adds the `pane_group.border` theme attribute that defines the
color of the borders between pane groups.

- Defaults to the `border` color, so nothing changes in the existing
themes.
- VSCode theme converter takes it from the `editorGroup.border`.

The borders marked by red are affected:

<img width="878" alt="pane_group_borders"
src="https://github.com/zed-industries/zed/assets/2101250/54b9fd39-b3e1-4898-a047-ee0b6ec953ed">

Release Notes:

- Added `pane_group.border` to the theme for modifying the border color
for panes within a pane group.

Related Issues:

- First discussed in
https://github.com/zed-industries/zed/pull/9754#issuecomment-2026497213

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-04-01 12:07:26 -04:00
Marshall Bowers
65840b3633 Hoist profile.dev.package setting to workspace-level (#10041)
This PR hoists the `profile.dev.package` settings for compiling the
`resvg` crate with optimizations up to the workspace level, since Cargo
was complaining:

```
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
package:   /Users/maxdeviant/projects/zed/crates/gpui/Cargo.toml
workspace: /Users/maxdeviant/projects/zed/Cargo.toml
```

Release Notes:

- N/A
2024-04-01 11:56:17 -04:00
Marshall Bowers
954c772e29 Use ignored color from theme for items ignored by Git (#10038)
This PR updates the color of the label used for Git-aware items to use
the `ignored` color from the theme when the item is ignored by Git.

The built-in themes have had their `ignored` color updated to match
`text.disabled`, as the existing `ignored` color did not sufficiently
differentiate from non-ignored items.

Fixes #9976.

Release Notes:

- Updated items in the project panel to use the `ignored` color from the
theme when they are ignored by Git
([#9976](https://github.com/zed-industries/zed/issues/9976)).
2024-04-01 11:34:49 -04:00
Kirill Bulatov
63e566e56e Remove git diff base from symlinked files (#10037)
Closes https://github.com/zed-industries/zed/issues/4730

![image](https://github.com/zed-industries/zed/assets/2690773/d3c5317f-8120-45b5-b57c-c0fb5d8c066d)

To the left is a symlink, to the right — the real file.
The issue was due to the fact, that symlinks files contain the file path
to the real file, and git (properly) treats that symlink file contents
as diff base, returning in `load_index_text` (via `let content =
repo.find_blob(oid)?.content().to_owned();`) the contents of that
symlink file — the path.

The fix checks for FS metadata before fetching the git diff base, and
skips it entirely for symlinks: Zed opens the symlink file contents
instead, fully obscuring the git symlink diff hunks.

Interesting, that VSCode behaves as Zed before the fix; while the fix
makes Zed behave like Intellij* IDEs now.

Release Notes:

- Fixed git diff hunks appearing in the symlinked files
([4730](https://github.com/zed-industries/zed/issues/4730))
2024-04-01 18:22:25 +03:00
Morgan Gallant
351693ccdf zig: Add support for .zig.zon files (#10012)
Release Notes:

- N/A

Signed-off-by: Morgan Gallant <morgan@morgangallant.com>
2024-04-01 10:02:09 -04:00
Bennet Bo Fenner
c126fdb616 Fix panel drag leaking through overlay (#10035)
Closes #10017. While reworking the `overlay` element in #9911, I did not
realize that all overlay elements called `defer_draw` with a priority of
`1`.

/cc @as-cii 

Not including release notes, since it was only present in nightly.

Release Notes:

- N/A
2024-04-01 12:31:19 +02:00
Kirill Bulatov
5602593089 Check license generation for every PR to avoid license-less crate additions (#10033)
Also fix `anthropic` crate and make it AGPL-licensed, as it's used in
the AGPL-licensed collab part only.

Release Notes:

- N/A
2024-04-01 12:16:16 +03:00
d1y
bd7fdcfb18 Update languages doc (#10019)
- Remove Dockerfile language doc
- Add Uiua doc
- Update Vue doc

Release Notes:

- N/A
2024-04-01 12:02:58 +03:00
moshyfawn
de041f9fe5 Remove feature mock textarea (#10030) 2024-04-01 02:26:42 -04:00
Nathan Sobo
9b673089db Enable Claude 3 models to be used via the Zed server if "language-models" feature flag is enabled for user (#10015)
Release Notes:

- N/A
2024-03-31 15:57:57 -06:00
Matthias Grandl
b1ccead0f6 gpui: fix #9931 img object-fit regression (#10006)
PR: #9931 broke image scaling, such that it ignores the object-fit
parameter and instead always scales the image to fit the bounds. This
fixes the regression.
2024-03-31 08:17:09 -07:00
Petros Amoiridis
3c8b376764 Fix broken character (#9992)
This is extremely minor but I couldn't help it.


![broken-character](https://github.com/zed-industries/zed/assets/28818/1b598b53-2a6a-4fd7-8857-a43e682db35e)

Release Notes:

- N/A
2024-03-30 14:39:45 -04:00
Joseph T. Lyons
480e3c9daf Fix test name (#9979)
This must've come about from copying and pasting another test and
forgetting to update the name.

Release Notes:

- N/A
2024-03-29 21:12:47 -04:00
Matthias Grandl
f9becbd3d1 gpui: Add SVG rendering to img element and generic asset cache (#9931)
This is a follow up to #9436 . It has a cleaner API and generalized the
image_cache to be a generic asset cache, that all GPUI elements can make
use off. The changes have been discussed with @mikayla-maki on Discord.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-03-29 17:09:49 -07:00
Piotr Osiewicz
ed5bfcdddc tab_switcher: Add support for tab switcher in terminal panel (#9963)
tab switcher retrieves active pane from workspace, but that function is
not aware of Terminal Panel's pane. Thus in this PR we retrieve it
manually and use it as the active pane if terminal panel has focus.

Release Notes:

- Fixed tab switcher not working in terminal panel.
2024-03-30 00:19:02 +01:00
Marshall Bowers
79b3b0c8ff zig: Remove folds.scm (#9975)
This PR removes the `folds.scm` file from the `zig` extension, as Zed
doesn't make use of it.

Release Notes:

- N/A
2024-03-29 18:07:44 -04:00
Marshall Bowers
b0fb02e4be Extract Erlang support into an extension (#9974)
This PR extracts Erlang support into an extension and removes the
built-in Erlang support from Zed.

Tested using a Nix shell:

```
nix-shell -p erlang-ls
```

Release Notes:

- Removed built-in support for Erlang, in favor of making it available
as an extension. The Erlang extension will be suggested for download
when you open a `.erl` or `.hrl` file.
2024-03-29 18:03:38 -04:00
Daniel Zhu
30193647f3 Fix Recent Documents List (continues #8952) (#9919)
@SomeoneToIgnore This code should 100% work for future Zed users, but
for current Zed users, Zed's internal list of recents may not be synced
w/ macOS' Recent Documents at first. If needed this can be fixed by
calling `cx.refresh_recent_documents` on startup, but that feels a bit
unnecessary.

Release Notes:

- Fixes behavior of Recent Documents list on macOS
2024-03-29 23:17:25 +02:00
Marshall Bowers
35e1229fbb toml: Sync Cargo.toml version with extension.toml (#9973)
This PR syncs the version number in the `Cargo.toml` with the one in
`extension.toml` for the `toml` extension, since they had gotten
out-of-sync.

Release Notes:

- N/A
2024-03-29 16:59:46 -04:00
Joseph T. Lyons
8dc3d719bb Add default keybinding for ToggleGitBlame (#9972) 2024-03-29 16:56:14 -04:00
Kyle Kelley
d77e553466 File context for assistant panel (#9712)
Introducing the Active File Context portion of #9705. When someone is in
the assistant panel it now includes the active file as a system message
on send while showing them a nice little display in the lower right:


![image](https://github.com/zed-industries/zed/assets/836375/9abc56e0-e8f2-45ee-9e7e-b83b28b483ea)

For this iteration, I'd love to see the following before we land this:

* [x] Toggle-able context - user should be able to disable sending this
context
* [x] Show nothing if there is no context coming in
* [x] Update token count as we change items
* [x] Listen for a more finely scoped event for when the active item
changes
* [x] Create a global for pulling a file icon based on a path. Zed's
main way to do this is nested within project panel's `FileAssociation`s.
* [x] Get the code fence name for a Language for the system prompt
* [x] Update the token count when the buffer content changes

I'm seeing this PR as the foundation for providing other kinds of
context -- diagnostic summaries, failing tests, additional files, etc.

Release Notes:

- Added file context to assistant chat panel
([#9705](https://github.com/zed-industries/zed/issues/9705)).

<img width="1558" alt="image"
src="https://github.com/zed-industries/zed/assets/836375/86eb7e50-3e28-4754-9c3f-895be588616d">

---------

Co-authored-by: Conrad Irwin <conrad@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-03-29 13:55:01 -07:00
Marshall Bowers
df3050dac1 Extract C# support into an extension (#9971)
This PR extracts C# support into an extension and removes the built-in
C# support from Zed.

Tested using a Nix shell:

```
nix-shell -p dotnet-sdk omnisharp-roslyn
```

Release Notes:

- Removed built-in support for C#, in favor of making it available as an
extension. The C# extension will be suggested for download when you open
a `.cs` file.
2024-03-29 16:38:27 -04:00
Kirill Bulatov
5d531037c4 Omit empty hovers (#9967)
Closes https://github.com/zed-industries/zed/issues/9962

Release Notes:

- N/A
2024-03-29 21:59:01 +02:00
Marshall Bowers
e252f90e30 Extract PHP support into an extension (#9966)
This PR extracts PHP support into an extension and removes the built-in
PHP support from Zed.

There's a small workaround necessary in order for us to provide the
`language_ids` on the `LspAdapter` that are needed for the language
server to run properly. Eventually we'll want to build this into the
extension API, but for now we're just hard-coding it on the host side.

Release Notes:

- Removed built-in support for PHP, in favor of making it available as
an extension. The PHP extension will be suggested for download when you
open a `.php` file.
2024-03-29 14:51:54 -04:00
Marshall Bowers
764e256755 Add support for building a Tree-sitter grammar at a given path (#9965)
This PR extends the extension builder—and by extension, the
`zed-extension` CLI—with support for building a Tree-sitter grammar at a
given path within the grammar repository.

Some Tree-sitter grammar repos contain multiple grammars inside of them.
For instance,
[`tree-sitter-php`](29838ad107)
has subfolders for `php` and `php_only`.

The grammar entries in `extension.toml` can now have an optional `path`
field that will be interpreted relative to the root of the grammar
repository:

```toml
[grammars.php]
repository = "https://github.com/tree-sitter/tree-sitter-php"
commit = "8ab93274065cbaf529ea15c24360cfa3348ec9e4"
path = "php"
```

This was something we supported in the old extension packaging script,
but hadn't yet carried it over when we built the new extension builder.

Release Notes:

- N/A
2024-03-29 14:30:10 -04:00
Joseph T. Lyons
290f41b97d Tweak top-ranking issues 2024-03-29 14:01:27 -04:00
Joseph T. Lyons
400540772c Update Top-Ranking Issues script to include Windows/Linux 2024-03-29 13:42:40 -04:00
Piotr Osiewicz
cff9ad19f8 Add spawning of tasks without saving them in the task stack (#9951)
These tasks are not considered for reruns with `task::Rerun`. 
This PR tears a bunch of stuff up around tasks:
- `menu::SecondaryConfirm` for tasks is gonna spawn a task without
storing it in history instead of being occupied by oneshot tasks. This
is done so that cmd-clicking on the menu item actually does something
meaningful.
- `menu::UseSelectedQuery` got moved into picker, as tasks are it's only
user (and it doesn't really make sense as a menu action).

TODO:
- [x] add release note
- [x] Actually implement the core of this feature, which is spawning a
task without saving it in history, lol.

Fixes #9804 
Release Notes:

- Added "fire-and-forget" task spawning; `menu::SecondaryConfirm` in
tasks modal now spawns a task without registering it as the last spawned
task for the purposes of `task::Rerun`. By default you can spawn a task
in this fashion with cmd+enter or by holding cmd and clicking on a task
entry in a list. Spawning oneshots has been rebound to `option-enter`
(under a `picker::ConfirmInput` name). Fixes #9804 (breaking change)
- Moved `menu::UseSelectedQuery` action to `picker` namespace (breaking
change).
2024-03-29 18:41:14 +01:00
Joseph T. Lyons
e7bd91c6c7 Update pyrightconfig.json 2024-03-29 13:38:17 -04:00
Marshall Bowers
a4b55b9924 Fix GitHub commit permalinks (#9961)
This PR fixes an issue where GitHub commit permalinks were being
constructed with the wrong URL segment.

This would result in clicking on a commit from the Git blame view taking
you to the wrong page on GitHub.

### Before

```
a3d985028c
```

<img width="1654" alt="Screenshot 2024-03-29 at 12 59 51 PM"
src="https://github.com/zed-industries/zed/assets/1486634/122fd678-de56-42cb-a0c5-1ce1b9b104b5">

### After

```
a3d985028c
```

<img width="1654" alt="Screenshot 2024-03-29 at 12 59 56 PM"
src="https://github.com/zed-industries/zed/assets/1486634/1c92b2ef-7925-46bc-aebf-b739be1eae74">

Release Notes:

- N/A
2024-03-29 13:17:48 -04:00
Marshall Bowers
64ea74d1db Fix vertical alignment of labels in file tree (#9959)
This PR fixes the vertical alignment of the labels in the file tree in
the project panel.

This appears to have been introduced in
https://github.com/zed-industries/zed/pull/8988 through the addition of
the `.h_6` in conjunction with a `div`, causing the contents to not be
vertically aligned.

### Before

<img width="287" alt="Screenshot 2024-03-29 at 12 44 15 PM"
src="https://github.com/zed-industries/zed/assets/1486634/b275b66c-55eb-4980-95b9-6751d0b4998a">

### After

<img width="259" alt="Screenshot 2024-03-29 at 12 44 42 PM"
src="https://github.com/zed-industries/zed/assets/1486634/8d7c1799-255f-4e01-8980-ccb19f49279a">


Release Notes:

- Fixed the vertical alignment of labels in the file tree to better
align with the file icons.
2024-03-29 12:55:31 -04:00
Marshall Bowers
16e6f5643c Extract SemanticVersion into its own crate (#9956)
This PR extracts the `SemanticVersion` out of `util` and into its own
`SemanticVersion` crate.

This allows for making use of `SemanticVersion` without needing to pull
in some of the heavier dependencies included in the `util` crate.

As part of this the public API for `SemanticVersion` has been tidied up
a bit.

Release Notes:

- N/A
2024-03-29 12:11:57 -04:00
Bennet Bo Fenner
77f1cc95b8 gpui: Rework overlay element (#9911)
There was a problem using deferred draws with `overlay` and tooltips at
the same time.

The `overlay` element was removed and was split up into two separate
elements
- `deferred`
- `anchored` - Mimics the `overlay` behavior but does not render its
children as deferred

`tooltip_container` does not defer its drawing anymore and only uses
`anchored`.

/cc @as-cii 


Release Notes:
- Fixed tooltip for the recent projects popover not showing anymore

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-03-29 16:26:16 +01:00
jansol
49144d94bf gpui: Add support for window transparency & blur on macOS (#9610)
This PR adds support for transparent and blurred window backgrounds on
macOS.

Release Notes:

- Added support for transparent and blurred window backgrounds on macOS
([#5040](https://github.com/zed-industries/zed/issues/5040)).
- This requires themes to specify a new `background.appearance` key
("opaque", "transparent" or "blurred") and to include an alpha value in
colors that should be transparent.

<img width="913" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/7547ee2a-e376-4d55-9114-e6fc2f5110bc">
<img width="994" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/b36fbc14-6e4d-4140-9448-69cad803c45a">
<img width="1020" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/d70e2005-54fd-4991-a211-ed484ccf26ef">

---------

Co-authored-by: Luiz Marcondes <luizgustavodevergennes@gmail.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-03-29 11:10:47 -04:00
Piotr Osiewicz
1360dffead task: Make UseSelectedQuery modal action expand to full command (#9947)
Previously it expanded to a label, which was correct for oneshots, but
wrong for everything else.

Release Notes:

- Improved UseSelectedQuery (shift-enter) action for tasks modal by
making it substitute a full command and not the task label.
- Fixed one-shot tasks having duplicates in tasks modal.
2024-03-29 11:45:50 +01:00
Kirill Bulatov
c7f04691d9 Query code actions and hovers from all related local language servers (#9943)
<img width="1122" alt="Screenshot 2024-03-28 at 21 51 18"
src="https://github.com/zed-industries/zed/assets/2690773/37ef7202-f10f-462f-a2fa-044b2d806191">


Part of https://github.com/zed-industries/zed/issues/7947 and
https://github.com/zed-industries/zed/issues/9912 that adds makes Zed
query all related language servers instead of the primary one.

Collab clients are still querying the primary one only, but this is
quite hard to solve, https://github.com/zed-industries/zed/pull/8634
drafts a part of it.
The local part is useful per se, as many people use Zed & Tailwind but
do not use collab features.

Unfortunately, eslint still returns empty actions list when queried, but
querying actions for all related language servers looks reasonable and
rare enough to be dangerous.

Release Notes:

- Added Tailwind CSS hover popovers for Zed in single player mode
([7947](https://github.com/zed-industries/zed/issues/7947))
2024-03-29 12:18:38 +02:00
Marshall Bowers
c4bc172850 Improve extension suggestions (#9941)
This PR improves the behavior for suggesting extensions.

Previously if the file had an extension, it would only look for
suggestions based on that extension. This prevented us from making
suggestions for files like `Cargo.lock`.

Suggestions are now made in the following order:

1. Check for any suggestions based on the entire file name
2. Check for any suggestions based on the file extension (if present)

This PR also fixes a bug where file name-based suggestions were looking
at the entire path, not just the file name.

Finally, the suggestion notification has been updated to include the ID
of the extension, to make it clearer which extension will be installed.

Release Notes:

- Improved extension suggestions.
2024-03-28 19:15:40 -04:00
Marshall Bowers
d074586fbf Extract TOML support into an extension (#9940)
This PR extracts TOML support into an extension and removes the built-in
TOML support from Zed.

There's a small workaround necessary in order for us to set the file
permissions on the `taplo` binary so that it can be run. Eventually
we'll want to build this into the extension API, but for now we're just
hard-coding it on the host side.

Release Notes:

- Removed built-in support for TOML, in favor of making it available as
an extension. The TOML extension will be suggested for download when you
open a `.toml` or `Cargo.lock` file.
2024-03-28 18:40:12 -04:00
Marshall Bowers
90cf73b746 Update extension descriptions (#9939)
This PR updates the descriptions of some of the extensions to match the
others.

Release Notes:

- N/A
2024-03-28 17:14:55 -04:00
Marshall Bowers
0d7f5f49e6 Disable incompatible extension versions in extension view (#9938)
This PR makes it so extension versions that are incompatible with what
the current Zed instance supports are disabled in the UI, to prevent
attempting to install them.

Here's what it looks like in the extension version picker:

<img width="589" alt="Screenshot 2024-03-28 at 4 21 15 PM"
src="https://github.com/zed-industries/zed/assets/1486634/8ef11c72-c8f0-4de8-a73b-5c82e96f6bfe">

Release Notes:

- N/A
2024-03-28 16:49:26 -04:00
Max Brunsfeld
95fd426eff Add auto-update system for extensions (#9890)
* [x] auto update extensions on startup
* [ ] add a manual way of updating all?
* [x] add a way to opt out of auto-updates for a particular extension

We don't believe that there should be any background polling for
extension auto-updates, because it could be disruptive to the user.

Release Notes:

- Added an auto-update system for extensions.

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-03-28 15:41:22 -04:00
Thorsten Ball
3a36b10e3a Truncate commit messages in blame tooltip (#9937)
This truncates git commit messages to 15 lines.


Before:
![screenshot-2024-03-28-20 10
17@2x](https://github.com/zed-industries/zed/assets/1185253/03bea6bb-2ead-4bf6-bb12-22338c8745fd)

After:

![screenshot-2024-03-28-20 10
02@2x](https://github.com/zed-industries/zed/assets/1185253/0bd655ee-57ce-424f-b471-b7ce01e5fbf7)




Release Notes:

- N/A
2024-03-28 20:19:04 +01:00
Thorsten Ball
98adc7b108 Use correct font family and line_height in git blame sidebar (#9935)
This fixes the git blame sidebar looking wrong if the buffer font size
is higher than the UI font size (which is what was previously used).

It fixes this:

![screenshot-2024-03-28-19 46
48@2x](https://github.com/zed-industries/zed/assets/1185253/eca360ac-c8e8-41e0-85a1-52bdd05b9413)

To now look like this:

![screenshot-2024-03-28-19 47
42@2x](https://github.com/zed-industries/zed/assets/1185253/1fe93370-b7a2-44d4-a505-6368d72e2659)


Release Notes:

- N/A
2024-03-28 20:05:29 +01:00
Marshall Bowers
50fc54c321 Extend extension API to support auto-updating extensions (#9929)
This PR extends the extension API with some additional features to
support auto-updating extensions:

- The `GET /extensions` endpoint now accepts an optional `ids` parameter
that can be used to filter the results down to just the extensions with
the specified IDs.
- This should be a comma-delimited list of extension IDs (e.g.,
`wgsl,gleam,tokyo-night`).
- A new `GET /extensions/:extension_id` endpoint that returns all of the
extension versions for a particular extension.

Extracted from #9890, as these changes can be landed and deployed
independently.

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-03-28 14:20:57 -04:00
Maksim Bondarenkov
eaf65ab704 windows: Support compiling with MinGW toolchain (part 2) (#9843)
crates/languages and extensions/gleam: handle different target envs (a
new variant of os: `pc-windows-gnu`)
crates/storybook: compile manifest for all windows targets (same as
#9815)
looks like fixes #9807, but there are still errors presented

<details>

```
[2024-03-27T12:07:25+03:00 INFO  Zed] ========== starting zed ==========
[2024-03-27T12:07:26+03:00 INFO  cosmic_text::font::system] Parsed 398 font faces in 60ms.
[2024-03-27T12:07:26+03:00 INFO  db] Opening main db
[2024-03-27T12:07:26+03:00 ERROR util] crates\settings\src\settings_file.rs:76: EOF while parsing a value at line 1 column 0
[2024-03-27T12:07:26+03:00 ERROR util] crates\settings\src\keymap_file.rs:89: invalid binding value for keystroke escape, context Some("ChatPanel > MessageEditor")

Caused by:
    no action type registered for chat_panel::CloseReplyPreview
[2024-03-27T12:07:26+03:00 INFO  gpui::platform::windows::platform] use DCompositionWaitForCompositorClock for vsync
[2024-03-27T12:07:26+03:00 ERROR util] crates\zed\src\zed.rs:629: EOF while parsing a value at line 1 column 0
[2024-03-27T12:07:26+03:00 ERROR util] crates\zed\src/main.rs:720: Системе не удается найти указанный путь. (os error 3)
[2024-03-27T12:07:26+03:00 INFO  db] Opening main db
[2024-03-27T12:07:26+03:00 INFO  node_runtime] Node runtime install_if_needed
[2024-03-27T12:07:26+03:00 ERROR util] crates\workspace\src/workspace.rs:912: Error in last_window, select_row_bound expected single row result but found none for: SELECT
  display,
  window_state,
  window_x,
  window_y,
  window_width,
  window_height,
  fullscreen
FROM
  workspaces
WHERE
  workspace_location IS NOT NULL
ORDER BY
  timestamp DESC
LIMIT
  1
[2024-03-27T12:07:26+03:00 INFO  blade_graphics::hal::init] Adapter "NVIDIA GeForce RTX 3050 Ti Laptop GPU"
[2024-03-27T12:07:26+03:00 INFO  blade_graphics::hal::init] Ray tracing is supported
[2024-03-27T12:07:27+03:00 WARN  blade_graphics::hal::init] Requested size 1x1 is outside of surface capabilities
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_impl")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_device_position_transformed")
[2024-03-27T12:07:27+03:00 INFO  naga::back::spv::writer] Skip function Some("to_tile_position")
[2024-03-27T12:07:27+03:00 INFO  blade_graphics::hal::resource] Creating texture 0x2b2528fec20 of size 1024x1024x1 and format R8Unorm, name 'atlas', handle 0
[2024-03-27T12:07:27+03:00 INFO  blade_graphics::hal::resource] Creating buffer 0x2b2524762a0 of size 65536, name 'chunk-0', handle 1
[2024-03-27T12:07:27+03:00 INFO  blade_graphics::hal::resource] Creating buffer 0x2b252477ba0 of size 4096, name 'chunk-0', handle 2
[2024-03-27T12:07:27+03:00 INFO  blade_graphics::hal::resource] Creating buffer 0x2b2524765c0 of size 9184, name 'chunk-1', handle 3
[2024-03-27T12:07:27+03:00 ERROR util] crates\copilot_ui\src\copilot_completion_provider.rs:207: copilot is still starting
error: process didn't exit successfully: `target\release\Zed.exe` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
fish: Job 1, 'RUST_BACKTRACE=full RUST_LOG=in…' terminated by signal SIGSEGV (Address boundary error)
```

</details>

Release Notes:

- N/A
2024-03-28 10:40:07 -07:00
Piotr Osiewicz
fcaf4383e9 editor: Preserve scroll position when jumping from multibuffer (#9921)
This is a best-effort attempt, as the target offset from the top is just
an estimate; furthermore, this does not account for things like project
search header (which adds a bit of vertical offset by itself and is
removed once we jump into a buffer), but it still should improve the
situation quite a bit.

Fixes: #5296

Release Notes:

- Improved target selection when jumping from multibuffer; final
position in the buffer should more closely match the original position
of the cursor in the multibuffer.
2024-03-28 18:33:57 +01:00
Thorsten Ball
7f54935324 Add git blame (#8889)
This adds a new action to the editor: `editor: toggle git blame`. When
used it turns on a sidebar containing `git blame` information for the
currently open buffer.

The git blame information is updated when the buffer changes. It handles
additions, deletions, modifications, changes to the underlying git data
(new commits, changed commits, ...), file saves. It also handles folding
and wrapping lines correctly.

When the user hovers over a commit, a tooltip displays information for
the commit that introduced the line. If the repository has a remote with
the name `origin` configured, then clicking on a blame entry opens the
permalink to the commit on the code host.

Users can right-click on a blame entry to get a context menu which
allows them to copy the SHA of the commit.

The feature also works on shared projects, e.g. when collaborating a
peer can request `git blame` data.

As of this PR, Zed now comes bundled with a `git` binary so that users
don't have to have `git` installed locally to use this feature.

### Screenshots

![screenshot-2024-03-28-13 57
43@2x](https://github.com/zed-industries/zed/assets/1185253/ee8ec55d-3b5e-4d63-a85a-852da914f5ba)

![screenshot-2024-03-28-14 01
23@2x](https://github.com/zed-industries/zed/assets/1185253/2ba8efd7-e887-4076-a87a-587a732b9e9a)
![screenshot-2024-03-28-14 01
32@2x](https://github.com/zed-industries/zed/assets/1185253/496f4a06-b189-4881-b427-2289ae6e6075)

### TODOs

- [x] Bundling `git` binary

### Release Notes

Release Notes:

- Added `editor: toggle git blame` command that toggles a sidebar with
git blame information for the current buffer.

---------

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Bennet <bennetbo@gmx.de>
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-03-28 18:32:11 +01:00
Niklas Wimmer
e2d6b0deba gpui: Update dependencies (second attempt) (#9836)
Updated version of #9741 with fixes for the problems raised in #9774. I
only verified that the images no longer look blueish on Linux, because I
don't have a Mac.

cc @osiewicz

Release Notes:

- N/A

---------

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
2024-03-28 10:22:31 -07:00
白山風露
94c51c6ac9 Windows: Enable clippy deny warnings (#9920)
~Waiting #9918~

Release Notes:

- N/A
2024-03-28 11:55:35 -04:00
Hans
659ea7054a Adjust image viewer tab title font (#9903)
Fix #9895 

Release notes:

- Changed the tab title of the image preview to be the same as the other
tabs ([#9895](https://github.com/zed-industries/zed/issues/9895)).

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-03-28 11:44:15 -04:00
白山風露
403b912767 Windows: Implement signal in collab (#9918)
Only `CtrlC` and `CtrlBreak` signals are supported. `CtrlLogoff` and
`CtrlShutdown` is service only signal and I have not tried these yet.
`CtrlClose` occurs when terminal window is closed, but I found tokio's
`ctrl_close` does not work well, so I put comment in code.

Release Notes:

- N/A
2024-03-28 11:36:28 -04:00
Thorsten Ball
5da951ce29 Revert "Add working directories for eslint (#9738)" (#9914)
This reverts commit 96a1af7b0f from
https://github.com/zed-industries/zed/pull/9738 since it doesn't seem to
do anything. See:
https://github.com/zed-industries/zed/issues/9648#issuecomment-2025132087



Release Notes:

- N/A
2024-03-28 15:09:05 +01:00
Piotr Osiewicz
cb7c53bc52 workspace: Fix panel resize handles leaking through zoomed panels (#9909)
Fixes #9501 

Release Notes:

- Fixed panel resize handle "leaking through" into a zoomed panel or
pane.
2024-03-28 12:18:51 +01:00
Daniel Zhu
f5823f9942 Split DuplicateLine into DuplicateLineUp and DuplicateLineDown (#9715)
Fixes #9601

Release Notes:
- `DuplicateLine` is now split into `DuplicateLineUp` and
`DuplicateLineDown`
2024-03-28 12:52:08 +02:00
Antonio Scandurra
c33ee52046 Don't update active completion for editors that are not focused (#9904)
Release Notes:

- N/A
2024-03-28 10:51:55 +01:00
Hans
eaec04632a vim: Fix t operand not working correctly when cursor is on tag (#9899)
Fix #8994 and #9844 

Release notes:
* Fixed the `t` object in Vim mode not working correctly when cursor was
on a tag. #9844 and #8994

This mr fixes the above two problems, for #9844, because our previous
logic is to only think that the minimum html tag containing the current
cursor is qualified, but the approach of nvim is to get the tag after
the current cursor first, followed by the tag around the current cursor,
so I modified the corresponding condition

For #8994, the situation is a bit more complicated, in our previous
implementation, we could only get the range of the object by a `cursor
position`, but there are two possible cases for the html tag:
When the current cursor length is 1, nvim will return the first tag
after the current cursor, as described above
When the current cursor length is greater than 1, nvim will return just
the smallest tag that can cover the current selection

So we may need to pass the current selection to the inside of the
method, and the point alone is not enough to support us in calculating
these conditions
2024-03-28 10:16:54 +01:00
Hans
96a1af7b0f Add working directories for eslint (#9738)
Fix #9648 

Release notes:

- Added ability to configure ESLint's `workingDirectories` in settings.
Example:
`{"lsp":{"eslint":{"settings":{"workingDirectories":["./client","./server"]}}}}`.
#9648

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-03-28 07:01:19 +01:00
Hans
2f2f236afe vim: Make cc and S auto-indent (#9731)
Fix #9612 

Release notes:

* Changed `cc` and `S` in Vim mode to only change the current line after
its indentation. #9612
2024-03-28 07:01:00 +01:00
Marshall Bowers
ff685b299d Extract Zig support into an extension (#9893)
This PR extracts Zig support into an extension and removes the built-in
Zig support from Zed.

There's a small workaround necessary in order for us to set the file
permissions on the `zls` binary so that it can be run. Eventually we'll
want to build this into the extension API, but for now we're just
hard-coding it on the host side.

Release Notes:

- Removed built-in support for Zig, in favor of making it available as
an extension. The Zig extension will be suggested for download when you
open a `.zig` file.
2024-03-27 20:56:30 -04:00
Mikayla Maki
9bce5e8b82 Improve diagnostic header UI (#9888)
This PR rearranges the diagnostics to put the headers to the left of the
diagnostic messages and adds an additional button to close the
diagnostics.

<img width="394" alt="Screenshot 2024-03-27 at 2 01 19 PM"
src="https://github.com/zed-industries/zed/assets/2280405/83be4051-6441-47c6-9b48-77c75ce9c8eb">

<img width="326" alt="Screenshot 2024-03-27 at 2 01 56 PM"
src="https://github.com/zed-industries/zed/assets/2280405/d849ca34-91e9-4de6-9d9c-503b75e97d60">

As a drive by, I also quieted a useless but loud log message.

Release Notes:

- Added a close button to the `f8` diagnostics.
2024-03-27 14:30:27 -07:00
Kirill Bulatov
80242584e7 Prepare editor to display multiple LSP hover responses for the same place (#9868) 2024-03-27 20:49:26 +01:00
Kirill Bulatov
ce37885f49 Use different icons for terminal tasks (#9876) 2024-03-27 20:49:10 +01:00
Marshall Bowers
687d2a41d6 gleam: Bump to v0.0.2 (#9883)
This PR bumps the Gleam extension to v0.0.2.

Release Notes:

- N/A
2024-03-27 14:47:37 -04:00
Jason Wen
3046ef6471 windows: Prevent command line from opening in release mode (#9839)
Release Notes:

- Prevents the terminal from opening on release mode on Windows

Note: this also prevents Zed from logging to the terminal when it is
launched from the terminal. Is this expected behaviour on other
platforms?

---------

Co-authored-by: 白山風露 <shirayama.kazatsuyu@gmail.com>
2024-03-27 11:30:23 -07:00
Marshall Bowers
95699a07f4 gleam: Check for gleam on the PATH before installing the latest version (#9882)
This PR updates the Gleam extension to give priority to the `gleam`
binary that is already on the PATH before downloading/installing a
separate Gleam version.

Release Notes:

- N/A
2024-03-27 14:25:18 -04:00
Andrew Lygin
894b39a918 Add tab switcher (#7987)
The Tab Switcher implementation (#7653):
- `ctrl-tab` opens the Tab Switcher and moves selection to the
previously selcted tab. It also cycles selection forward.
- `ctrl-shift-tab` opens the Tab Switcher and moves selection to the
last tab in the list. It also cycles selection backward.
- Tab is selected and the Tab Switcher is closed on the shortcut
modifier key (`ctrl` by default) release.
- List items are in reverse activation history order.
- The list reacts to the item changes in background (new tab, tab
closed, tab title changed etc.)

Intentionally not in scope of this PR:
- File icons
- Close buttons

I will come back to these features. I think they need to be implemented
in separate PRs, and be synchronized with changes in how tabs are
rendered, to reuse the code as it's done in the current implementation.
The Tab Switcher looks usable even without them.

Known Issues:

Tab Switcher doesn't react to mouse click on a list item. It's not a tab
switcher specific problem, it looks like ctrl-clicks are not handled the
same way in Zed as cmd-clicks. For instance, menu items can be activated
with cmd-click, but don't react to ctrl-click. Since the Tab Switcher's
default keybinding is `ctrl-tab`, the user can only click an item with
`ctrl` pushed down, thus preventing `on_click()` from firing.

fixes #7653, #7321

Release Notes:

- Added Tab Switcher which is accessible via `ctrl-tab` and
`ctrl-shift-tab` (#7653) (#7321)

Related issues:

- Unblocks #7356, I hope 😄

How it looks and works (it's only `ctrl-tab`'s and `ctrl-shift-tab`'s,
no `enter`'s or mouse clicks):


https://github.com/zed-industries/zed/assets/2101250/4ad4ec6a-5314-481b-8b35-7ac85e43eb92

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-03-27 11:15:08 -07:00
Marshall Bowers
9c22009e7b Look up extensions in the new index when reporting extension events (#9879)
This PR fixes a bug that was causing extension telemetry events to not
be reported.

We need to look up the extensions in the new index, as the extensions to
load won't be found in the old index.

Release Notes:

- N/A
2024-03-27 13:45:19 -04:00
Piotr Osiewicz
044b516d98 typescript: Highlight variables and enums in completions, add details (#9873)
This partially fixes #5287 by surfacing origin of a completion.

Before:

![image](https://github.com/zed-industries/zed/assets/24362066/7cae421d-9523-43c5-bfc3-eed613a21ac4)

After:

![image](https://github.com/zed-industries/zed/assets/24362066/3d5e360c-c496-4542-82b5-a22d5d00113d)

Release Notes:

- Improved typescript-language-server integration by surfacing more
information about completion items.
2024-03-27 17:55:22 +01:00
Marshall Bowers
b1ad60a2ef Log when events are written to Clickhouse (#9875)
This PR adds some logging when we write events to Clickhouse in `POST
/telemetry/events`, for observability purposes.

Release Notes:

- N/A
2024-03-27 12:33:34 -04:00
Marshall Bowers
3f5f64a044 Wrap extension schema version in a newtype (#9872)
This PR wraps the extension schema version in a newtype, for some
additional type safety.

Release Notes:

- N/A
2024-03-27 12:11:12 -04:00
Joseph T. Lyons
8c56a4b305 v0.130.x dev 2024-03-27 10:53:09 -04:00
Dunqing
96b812b2c4 Pin Vue language server to 1.8 (#9846)
After `@vue/language-server` release 2.0, vue lsp doesn't work. I tried
to support 2.0, but since I'm not familiar with `@vue/language-server`
and `zed` I was unsuccessful. To avoid long-term unavailability, I
temporarily fixed the version to 1.8 until we have 2.0 support.

Release Notes:

- Pinned `@vue/language-server` to version `1.8` until Zed supports
`2.x`. ([#9388](https://github.com/zed-industries/zed/issues/9388) &
[#9329](https://github.com/zed-industries/zed/issues/9329)).

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-03-27 14:23:10 +01:00
Piotr Osiewicz
de4a54a204 chore: Bump ahash 0.7.6 (yanked) -> 0.7.8 (#9860)
Fixes #9855 

Release Notes:

- N/A
2024-03-27 13:51:58 +01:00
Kirill Bulatov
63f17c50b9 Fix mac bundling errors (#9848)
Based on
https://github.com/zed-industries/zed/pull/8952#issuecomment-2021693384
and
https://github.com/zed-industries/zed/pull/8952#issuecomment-2022241455

Fixes `./script/bundle-mac -l` workflow errors

* Use proper WebRTC.framework location path (without the arch name dir
in its path)

* Fix `./script/bundle-mac -l` behavior that unconditionally installed
the app and broke it on rerun.
Now the installation is done with `-i` flag only and always cleans up
the target dir (always using `-f` flag logic, hence removed it).


Release Notes:

- N/A
2024-03-27 13:04:32 +02:00
Conrad Irwin
140b8418c1 Stop reading deserialize_fingerprint (#9668)
Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-03-27 11:24:31 +01:00
Mikayla Maki
8583c3bd94 Add go to implementation shortcut (#9837)
This adds a keybinding for an existing action. Notably, our bindings for
`Go To Type Definition` and `Go To Implementation` are swapped from
VSCode. We use `cmd` and `shift`, they use `shift` and `cmd`.

Release Notes:

- Added a keybinding for `editor::GoToImplementation`
2024-03-26 17:04:55 -07:00
Mikayla Maki
40f60ebe2d Fix the linux keymap (#9829)
Earlier versions where a simple find-replace of `cmd` => `ctrl`. In this
PR, I've gone over every keybinding individually and checked them.

Release Notes:

- Removed the `ShowContextMenu` action, it's only usage was in the
collab panel and it's been rebound to `SecondaryConfirm`
2024-03-26 16:10:09 -07:00
Marshall Bowers
3676ca879b Extract Astro support into an extension (#9835)
This PR extracts Astro support into an extension and removes the
built-in Astro support from Zed.

Release Notes:

- Removed built-in support for Astro, in favor of making it available as
an extension. The Astro extension will be suggested for download when
you open a `.astro` file.
2024-03-26 18:50:08 -04:00
Marshall Bowers
7807f23e2a Extract Dockerfile extension (#9832)
This PR extracts Dockerfile support into an extension and removes the
built-in Dockerfile support from Zed.

There's already an existing [Dockerfile
extension](https://github.com/d1y/dockerfile.zed) that was just missing
language server support. Language server support is being added to that
extension in https://github.com/d1y/dockerfile.zed/pull/2.

Release Notes:

- Removed built-in support for Dockerfile, in favor of making it
available as an extension. The Dockerfile extension will be suggested
for download when you open a `Dockerfile`.
2024-03-26 16:38:21 -04:00
Marshall Bowers
181dc86b48 Fix typo in PureScript extension's struct name (#9831)
This PR fixes a copy/paste typo in the name of the PureScript
extension's struct name.

Release Notes:

- N/A
2024-03-26 16:29:55 -04:00
Bennet Bo Fenner
e272acd1bc collab ui: Fix notification windows on external monitors (#9817)
Sharing a project displays a notification (window) on every screen.
Previously there was an issue with the positioning of windows on all
screens except the primary screen.

As you can see here:


![image](https://github.com/zed-industries/zed/assets/53836821/314cf367-8c70-4e8e-bc4a-dcbb99cb4f71)

Now:


![image](https://github.com/zed-industries/zed/assets/53836821/42af9ef3-8af9-453a-ad95-147b5f9d90ba)

@mikayla-maki and I also decided to refactor the `WindowOptions` a bit. 
Previously you could specify bounds which controlled the positioning and
size of the window in the global coordinate space, while also providing
a display id (which screen to show the window on). This can lead to
unusual behavior because you could theoretically specify a global bound
which does not even belong to the display id which was provided.

Therefore we changed the api to this:
```rust
struct WindowOptions {
    /// The bounds of the window in screen coordinates
    /// None -> inherit, Some(bounds) -> set bounds.
    pub bounds: Option<Bounds<DevicePixels>>,

    /// The display to create the window on, if this is None,
    /// the window will be created on the main display
    pub display_id: Option<DisplayId>,
}
```

This lets you specify a display id, which maps to the screen where the
window should be created and bounds relative to the upper left of the
screen.

Release Notes:

- Fixed positioning of popup windows (e.g. when sharing a project) when
using multiple external displays.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-03-26 13:07:38 -07:00
Marshall Bowers
ffd698be14 Remove lingering uiua.rs file (#9828)
This PR removes a lingering file related to Uiua support.

This file was no longer being referenced after #9085, but just hadn't
been removed.

Release Notes:

- N/A
2024-03-26 16:07:23 -04:00
Remco Smits
0fd91652de Fixed channel chat notifications are not working anymore (#9827)
This PR will fix the following issues that were introduced by the
following pull request #9557.

- Cannot create messages with a mention inside it
- Cannot invite a user
- Cannot accept an invitation

Release Notes:

- Fixed channel chat notifications are not working anymore.

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2024-03-26 20:25:09 +01:00
Marshall Bowers
9604b22d98 Suppress error logs from CopilotCompletionProvider when Copilot is disabled (#9826)
This PR fixes some noisy error logs from the `CopilotCompletionProvider`
when Copilot is disabled entirely via the settings.

I have the following in my settings file:

```json
{
  "features": {
    "copilot": false
  },
}
```

After #9777 I started seeing my Zed logs getting filled up with messages
like this:

```
[2024-03-26T14:33:09-04:00 ERROR util] crates/copilot_ui/src/copilot_completion_provider.rs:206: copilot is disabled
```

Release Notes:

- N/A
2024-03-26 15:14:44 -04:00
Marshall Bowers
cd32ef64ff Use correct file extension for Haskell suggestions (#9825)
This PR fixes the file extension used for suggesting the Haskell
extension.

Release Notes:

- N/A
2024-03-26 14:21:03 -04:00
Marshall Bowers
b8ef97015c Extract PureScript support into an extension (#9824)
This PR extracts PureScript support into an extension and removes the
built-in PureScript support from Zed.

Release Notes:

- Removed built-in support for PureScript, in favor of making it
available as an extension. The PureScript extension will be suggested
for download when you open a `.purs` file.
2024-03-26 13:55:46 -04:00
Maksim Bondarenkov
d77cda1ea9 windows: Support compiling with MinGW toolchain (#9815)
Fixes #9757: compile manifest using `embed-manifest` crate, which
supports both MSVC and MinGW

Release Notes:

- N/A
2024-03-26 10:39:39 -07:00
白山風露
35b39e02ce Windows: Fullscreen (#9728)
~~This is still a work in progress, but to show the public where I am
working on it~~ Ready for review

TODO:
- [x] Justify fullscreen size to display
- [x] Record and apply restored size

Release Notes:

- N/A
2024-03-26 09:58:16 -07:00
Piotr Osiewicz
fc5a0885f3 Fix visual glitches in titlebar when there's an active prompt
It looks like a fractional traffic lights position doesn't play well with Mac 12+.
Related to #7339 and #8128
2024-03-26 17:57:16 +01:00
Marshall Bowers
dbcff2a420 Extract Prisma support into an extension (#9820)
This PR extracts Prisma support into an extension and removes the
built-in Prisma support from Zed.

Release Notes:

- Removed built-in support for Prisma, in favor of making it available
as an extension. The Prisma extension will be suggested for download
when you open a `.prisma` file.
2024-03-26 12:50:44 -04:00
Marshall Bowers
71441317bd Remove blank line in Cargo.toml 2024-03-26 11:44:15 -04:00
Marshall Bowers
1d6792b17d Extract Haskell support into an extension (#9814)
This PR extracts Haskell support into an extension and removes the
built-in Haskell support from Zed.

I tested out the extension locally in a Nix shell using `nix-shell -p
ghc haskell-language-server` to confirm the language server still
operated as expected:

<img width="341" alt="Screenshot 2024-03-26 at 11 26 26 AM"
src="https://github.com/zed-industries/zed/assets/1486634/df16fd38-4046-4a45-ac9f-c2b85bffe5c0">

Release Notes:

- Removed built-in support for Haskell, in favor of making it available
as an extension. The Haskell extension will be suggested for download
when you open a `.hs` file.
2024-03-26 11:41:41 -04:00
Kirill Bulatov
9d4c6c60fb Do not format or fully save non-dirty buffers (#9813)
Fixes https://github.com/zed-industries/zed/issues/9475

Release Notes:

- Start skipping formatting and actual FS saving for non-dirty buffers
([9475](https://github.com/zed-industries/zed/issues/9475))
2024-03-26 17:17:20 +02:00
张小白
f495ee0848 windows: implement mouse double click event (#9642)
Release Notes:

- N/A
2024-03-26 07:45:32 -07:00
Antonio Scandurra
fb6cff89d7 Introduce InlineCompletionProvider (#9777)
This pull request introduces a new `InlineCompletionProvider` trait,
which enables making `Editor` copilot-agnostic and lets us push all the
copilot functionality into the `copilot_ui` module. Long-term, I would
like to merge `copilot` and `copilot_ui`, but right now `project`
depends on `copilot`, which makes this impossible.

The reason for adding this new trait is so that we can experiment with
other inline completion providers and swap them at runtime using config
settings.

Please, note also that we renamed some of the existing copilot actions
to be more agnostic (see release notes below). We still kept the old
actions bound for backwards-compatibility, but we should probably remove
them at some later version.

Also, as a drive-by, we added new methods to the `Global` trait that let
you read or mutate a global directly, e.g.:

```rs
MyGlobal::update(cx, |global, cx| {
});
```

Release Notes:

- Renamed the `copilot::Suggest` action to
`editor::ShowInlineCompletion`
- Renamed the `copilot::NextSuggestion` action to
`editor::NextInlineCompletion`
- Renamed the `copilot::PreviousSuggestion` action to
`editor::PreviousInlineCompletion`
- Renamed the `editor::AcceptPartialCopilotSuggestion` action to
`editor::AcceptPartialInlineCompletion`

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Kyle <kylek@zed.dev>
Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
2024-03-26 13:28:06 +01:00
Kirill Bulatov
b8663e56a9 Make UniformList non-occluding. (#9806)
Fixes https://github.com/zed-industries/zed/issues/9723

Move the `occlude` in the places where they are needed.

Release Notes:

- Fixed right click in the project panel's empty region
([9723](https://github.com/zed-industries/zed/issues/9723))

Co-authored-by: Antonio Scandurra <antonio@zed.dev>
2024-03-26 13:13:10 +02:00
Bennet Bo Fenner
db9221aa57 markdown preview: Handle line breaks in between task list items correctly (#9795)
Closes #9783 

Release Notes:

- Fixed task list rendering when there was a line break between two list
items ([#9783](https://github.com/zed-industries/zed/issues/9783))
2024-03-26 12:12:57 +02:00
Thorsten Ball
157fb98a8b Add ability to specify binary path/args for gopls (#9803)
This uses the language server settings added in #9293 to allow users to
specify the binary path and arguments with which to start up `gopls`.

Example user settings for `gopls`:

```json
{
  "lsp": {
    "gopls": {
      "binary": {
        "path": "/Users/thorstenball/tmp/gopls",
        "arguments": ["-debug=0.0.0.0:8080"]
      },
    }
  }
}
```

Constraints:

* Right now this only allows ABSOLUTE paths.

Release Notes:

- Added ability to specify `gopls` binary `path` (must be absolute) and
`arguments` in user settings. Example: `{"lsp": {"gopls": {"binary":
{"path": "/my/abs/path/gopls", "arguments": ["-debug=0.0.0.0:8080"]
}}}}`
2024-03-26 07:09:06 +01:00
Max Brunsfeld
b0409ddd68 Consolidate more extension API structs that were duplicated btwn client and server (#9797)
Release Notes:

- N/A
2024-03-25 21:28:18 -07:00
Max Brunsfeld
5e7fcc02fa Remove old extension dir when upgrading (#9800)
Fixes #9799

Release Notes:

- Fixed a bug where upgrading an extension did not work correctly if the
extension had switched from using an old extension schema with
`extension.json` to the new schema with `extension.toml`.
2024-03-25 21:27:30 -07:00
Max Brunsfeld
5adc51f113 Add telemetry events for loading extensions (#9793)
* Store extensions versions' wasm API version in the database
* Share a common struct for extension API responses between collab and
client
* Add wasm API version and schema version to extension API responses

Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-03-25 17:30:48 -04:00
Ezekiel Warren
9b62e461ed windows: Add extension builder support (#9791)
Release Notes:

- N/A
2024-03-25 17:25:03 -04:00
Andrew Lygin
1b4c82dc2c Fix next/prev shortcuts handling in the File Finder (#9785)
This PR fixes the unexpected File Finder behaviour described in
https://github.com/zed-industries/zed/pull/8782#issuecomment-2018551041

Any change of the modifier keys except for the release of the initial
modifier keys now prevents opening the selected file.

Release Notes:

- N/A
2024-03-25 14:06:37 -07:00
Mikayla Maki
bdea804c48 Restore the hitbox of the excerpt header (#9790)
In https://github.com/zed-industries/zed/pull/9722, the
jump-to-excerpt-source buttons where shrunk too far.

Release Notes:

- N/A
2024-03-25 12:41:54 -07:00
Ezekiel Warren
00a8659491 More C++ path suffixes (#9761)
There is also `.C` and `.H` (capital), but I can't imagine they are very
popular and I'd be worried clashing with C.

Release Notes:

- Added more path suffixes recognized as C++
2024-03-25 15:23:09 -04:00
Paul
4789c02a19 X11: Double click (#9739)
This copies the logic from #9608 to the X11 client.

Fixes #9707.

Release Notes:
- N/A

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-03-25 11:44:31 -07:00
RoblKyogre
030e299b27 Fix key repeat after releasing a different key on Wayland (#9768)
Quick fix that fixes key repeat not working when releasing a different
key than the current one being held

Don't really know much rust yet, so unsure this is the best way to
handle this, but this does seem like a good starting point to get at
least a tad familiar with it

Release Notes:
- N/A
2024-03-25 11:44:24 -07:00
白山風露
6231df978b Windows: fix initial active status (#9694)
Separate from #9451

On Windows, a new window may already active immediate after creation.

Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-03-25 11:44:18 -07:00
Ezekiel Warren
78dc458231 windows: Mouse wheel coordinates fix (#9749)
mouse scroll wasn't working unless the window was maximized or in the
top left corner because the Windows wheel events give screen coordinates

Release Notes:

- N/A
2024-03-25 11:14:51 -07:00
Raunak Raj
2646ed08e7 linux: Implement restart and app_path (#9681)
Added `restart` and `app_path` method for linux platform which was
marked as `//todo(linux)`


Release Notes:

- N/A
2024-03-25 11:14:29 -07:00
Jakob Grønhaug
7bba9da281 Fix dependency install script on RHEL derivatives (#9684)
Added a check to `script/linux` so the script does not try to enable CSB
or add EPEL if the user is on Fedora, which does not need these steps.
The script now runs nicely on Fedora! :)

Release Notes:

- N/A
2024-03-25 11:14:11 -07:00
Daniel Zhu
569a7234fd Handle first click on Zed window (#9553)
Fixes #4336
2024-03-25 10:52:18 -07:00
白山風露
5361a4d72d Windows: Better cursor (#9451)
Moved `SetCursor` calls to `WM_SETCURSOR`, which occurs when OS is
requires set cursor.

Release Notes:

- N/A
2024-03-25 10:06:52 -07:00
Max Brunsfeld
053d05f6f5 Bump Tree-sitter for inclusion of strncat in wasm c stdlib
Co-authored-by: Marshall <marshall@zed.dev>
2024-03-25 09:54:43 -07:00
Marshall Bowers
0981c97a22 extension_cli: Clear out existing manifest collections for old extension.json (#9780)
This PR fixes an issue in the extension CLI when building extensions
using the old manifest schema (`extension.json`).

If there were values provided for the `languages`, `grammars`, or
`themes` collections, these could interfere with the packaging process.

We aren't expecting these fields to be set in the source
`extension.json` (just in the generated one), so we can clear them out
when building up the manifest.

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-03-25 12:44:32 -04:00
Waffle Maybe
eec8660759 Use .is_some_and() instead of .is_some() && .unwrap() (#9704)
That's nicer & more readable.

(I just noticed that this looks weird while trying to understand why zed
changes my cursor, so decided to make a quick fix (btw the issue with
the cursor is that zed always loads cursor named "default" on wayland))

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-03-25 11:11:35 -04:00
Kirill Bulatov
e3894a4e1e Document main workspace structs (#9772) 2024-03-25 16:09:51 +01:00
Aaron Ruan
f83884518a Change maximum height of TitleBar (#9758)
<img width="209" alt="image"
src="https://github.com/zed-industries/zed/assets/38318044/0dcc4d0b-db9e-4eba-aa36-5c35f185e7e3">

Release Notes:

- Fixed alignment of items in the title bar
([#9709](https://github.com/zed-industries/zed/issues/9709)).
2024-03-25 10:45:19 -04:00
Piotr Osiewicz
a7047f67fb chore: Revert "gpui: update dependencies" (#9774)
Reverts zed-industries/zed#9741

/cc @niklaswimmer it looks like that PR change broke our rendering of
avatars (as @bennetbo found out) - they have a blue-ish tint now, which
I suppose might have to do with change between BGRA and RGBA. I'm gonna
revert it for now, let's reopen it though.


![image](https://github.com/zed-industries/zed/assets/24362066/3078d9c6-9638-441b-8b32-d969c46951e0)

Release Notes:

- N/A
2024-03-25 15:27:16 +01:00
Ko
6776688987 Fix Prisma indentation size (#9753)
This PR fixes #9567 

Release Notes:

- Changed default indentation for Prisma files to 2 spaces
([#9567](https://github.com/zed-industries/zed/issues/9567)).
2024-03-25 08:39:49 -04:00
Niklas Wimmer
7f9355e11f windows: update text system to new cosmic version
The same changes have been used on linux here 5003504031
and here 34832d49b09071846ff6f55f8ca1df019980a1df.

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
2024-03-25 13:03:57 +01:00
Niklas Wimmer
6a22c8a298 gpui: Update image dependency
The latest update to resvg bumped some transitive dependencies
which lead to duplicates. The update to the image dependency
unifies most of their versions again.

Most notably, gif and kurbo are still duplicated, which is best fixed
downstream however.

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
2024-03-25 13:03:57 +01:00
Niklas Wimmer
007acc4bc2 gpui: Update cosmic-text and resvg dependency
This unifies the rustybuzz dependency to the same version.

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
2024-03-25 13:03:57 +01:00
Niklas Wimmer
97f1d61a4a gpui: make build dependencies mac only
This removes bindgen and cbindgen from the dependency graph on non-macos
systems, improving compile times on those systems.

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
2024-03-25 13:03:57 +01:00
Niklas Wimmer
50ab60b9f0 gpui: update ashpd and open dependency
The ashpd update removes the dependency on an older zbus version which
decreases compile times.

Signed-off-by: Niklas Wimmer <mail@nwimmer.me>
2024-03-25 13:03:57 +01:00
Mayfield
4785520d99 Support newline and tab literals in regex search-and-replace operations (#9609)
Closes #7645

Release Notes:

- Added support for inserting newlines (`\n`) and tabs (`\t`) in editor
Regex search replacements
([#7645](https://github.com/zed-industries/zed/issues/7645)).
2024-03-25 12:21:04 +01:00
Hans
eb3264c0ad change HashSet to BTreeSet (#9734)
I found that there may be some minor problems here, in editor.edit may
be more dependent on the order of operations, if the same set of
operations, different execution orders may lead to some different
results, so maybe we need to use BTreeSet instead of HashSet, because
HashSet may not be able to ensure that the same set of data order is
consistent, but maybe my worries are too much

Release notes:

- N/A
2024-03-25 12:13:44 +01:00
Bennet Bo Fenner
e77d313839 markdown preview: Improve task list visuals (#9695)
Instead of using some arbitrary unicode characters to render a task as
completed/not completed, I feel that using an actual checkbox from the
components crate makes it look more polished.

Before:

![image](https://github.com/zed-industries/zed/assets/53836821/700de8f8-2e01-4e03-b237-e3da2971f039)

After:

<img width="883" alt="image"
src="https://github.com/zed-industries/zed/assets/53836821/f63d56c3-bfbb-41c8-b150-8ebf973f75e2">


Release Notes:

- Improved visuals of task lists inside the markdown preview
2024-03-25 09:43:17 +01:00
Richard Taylor
5181d3f719 Workspace configuration for elixir-ls LSP (#9330)
This allows the workspace configuration settings to be passed to the
elixir-ls LSP via lsp settings.

This following example settings disable dialyzer in the LSP:

```
"lsp": {
  "elixir-ls": {
    "settings": {
      "dialyzerEnabled": false
    }
  }
}
```

It follows the same pattern used in
[#8568](https://github.com/zed-industries/zed/pull/8568) and resolves
[#4260](https://github.com/zed-industries/zed/issues/4260).

Zed's language server logs show the settings are being sent to the
language server:

```
Received client configuration via workspace/configuration
%{"dialyzerEnabled" => false}
Registering for workspace/didChangeConfiguration notifications
Starting build with MIX_ENV: test MIX_TARGET: host
client/registerCapability succeeded
Registering for workspace/didChangeWatchedFiles notifications
client/registerCapability succeeded
Received workspace/didChangeConfiguration
Received client configuration via workspace/didChangeConfiguration
%{"dialyzerEnabled" => false}
```

Release Notes:

- Added workspace configuration settings support for elixir-ls language
server. Those can now be configured by setting `{"lsp": {"elixir-ls": {
"settings: { "your-settings-here": "here"} } }` in Zed settings.
[#4260](https://github.com/zed-industries/zed/issues/4260).
2024-03-25 09:35:28 +01:00
Bennet Bo Fenner
6d78737973 markdown preview: Insert missing line break on hard break (#9687)
Closes #8990

For this input
```
Test \
Test
```

pulldown_cmark reports
```
Start(Paragraph)
Text(Borrowed("Test "))
HardBreak
Text(Borrowed("Test"))
End(Paragraph)
```

Previously `Event::HardBreak` just marked the paragraph block as
completed and ignored all the remaining text inside the paragraph.

Before:
See https://github.com/zed-industries/zed/issues/8990#issue-2173197637

After:

![image](https://github.com/zed-industries/zed/assets/53836821/48237ea6-d749-4207-89a3-b0f146b0e544)


Release Notes:

- Fixed markdown preview not handling hard breaks (e.g. `\`) correctly
([#8990](https://github.com/zed-industries/zed/issues/8990)).
2024-03-25 10:06:00 +02:00
Max Brunsfeld
7367350f41 Use upstream cargo-about 2024-03-22 22:12:24 -07:00
Max Brunsfeld
478e2a29a5 Add license symlinks for svelta and uiua extensions 2024-03-22 21:56:47 -07:00
Max Brunsfeld
6ebe599c98 Fix issues with extension API that come up when moving Svelte into an extension (#9611)
We're doing it. Svelte support is moving into an extension. This PR
fixes some issues that came up along the way.

Notes

* extensions need to be able to retrieve the path the `node` binary
installed by Zed
* previously we were silently swallowing any errors that occurred while
loading a grammar
* npm commands ran by extensions weren't run in the right directory
* Tree-sitter's WASM stdlib didn't support a C function (`strncmp`)
needed by the Svelte parser's external scanner
* the way that LSP installation status was reported was unnecessarily
complex

Release Notes:

- Removed built-in support for the Svelte and Gleam languages, because
full support for those languages is now available via extensions. These
extensions will be suggested for download when you open a `.svelte` or
`.gleam` file.

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-03-22 17:29:06 -07:00
Mikayla Maki
4459eacc98 Improve the clarity of multi buffer headers (#9722)
<img width="544" alt="Screenshot 2024-03-22 at 3 23 09 PM"
src="https://github.com/zed-industries/zed/assets/2280405/83fde9ad-76e1-4eed-a3f2-bc25d5a88d84">

Release Notes:


- Improved the clarity of the UI for diagnostic and search result
headers
2024-03-22 15:38:19 -07:00
Thorsten Ball
16a2013021 Update to vscode-eslint 2.4.4 & support flat config file extensions (#9708)
This upgrades to vscode-eslint 2.4.4 to support flat configs, in
multiple configuration files, ending in `.js`, `.cjs`, `.mjs`.

We changed the code to not use the GitHub release because we actually
don't need the artifacts of the release, we just need the source code,
which we compile anyway.

Fixes #7271.

Release Notes:

- Added support for ESLint flat config files.
([#7271](https://github.com/zed-industries/zed/issues/7271)).

Co-authored-by: Kristján Oddsson <koddsson@gmail.com>
2024-03-22 17:19:23 +01:00
Marshall Bowers
c6d479715d Add setting to allow disabling the Assistant (#9706)
This PR adds a new `assistant.enabled` setting that controls whether the
Zed Assistant is enabled.

Some users have requested the ability to disable the AI-related features
in Zed if they don't use them. Changing `assistant.enabled` to `false`
will hide the Assistant icon in the status bar (taking priority over the
`assistant.button` setting) as well as filter out the `assistant:`
actions.

The Assistant is enabled by default.

Release Notes:

- Added an `assistant.enabled` setting to control whether the Assistant
is enabled.
2024-03-22 11:55:29 -04:00
Piotr Osiewicz
4dc61f7ccd Extensions registering tasks (#9572)
This PR also introduces built-in tasks for Rust and Elixir. Note that
this is not a precedent for future PRs to include tasks for more
languages; we simply want to find the rough edges with tasks & language
integrations before proceeding to task contexts provided by extensions.

As is, we'll load tasks for all loaded languages, so in order to get
Elixir tasks, you have to open an Elixir buffer first. I think it sort
of makes sense (though it's not ideal), as in the future where
extensions do provide their own tasks.json, we'd like to limit the # of
tasks surfaced to the user to make them as relevant to the project at
hand as possible.

Release Notes:

- Added built-in tasks for Rust and Elixir files.
2024-03-22 16:18:33 +01:00
Conrad Irwin
cb4f868815 remoting (#9680)
This PR provides some of the plumbing needed for a "remote" zed
instance.

The way this will work is:
* From zed on your laptop you'll be able to manage a set of dev servers,
each of which is identified by a token.
* You'll run `zed --dev-server-token XXXX` to boot a remotable dev
server.
* From the zed on your laptop you'll be able to open directories and
work on the projects on the remote server (exactly like collaboration
works today).

For now all this PR does is provide the ability for a zed instance to
sign in
using a "dev server token". The next steps will be:
* Adding support to the collaboration protocol to instruct a dev server
to "open" a directory and share it into a channel.
* Adding UI to manage these servers and tokens (manually for now)

Related #5347

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-03-22 08:44:56 -06:00
Nathan Sobo
f56707e076 Assign OPENAI_API_KEY from a k8s secret in the collab deployment (#9703)
Merging this eagerly because it's just a configuration change, and I want to test it on staging.
2024-03-22 08:36:52 -06:00
Bennet Bo Fenner
ce57db497e chat panel: Fix tooltips not working for links (#9691)
Closes #9418 

Noticed a difference in the `cx.set_tooltip(...)` calls between `div`
and `InteractiveText`. `div` calls `cx.set_tooltip(...)` inside
`after_layout`, but `InteractiveText` was calling this inside `paint`. I
believe as #9012 was merged, we need to call `cx.set_tooltip` inside
`after_layout`, as inserting inside `paint` does not seem to be
supported anymore.

I moved the code for setting the tooltip to `after_layout` and hovering
over links inside the chat seems to bring up the tooltips again.

Before:

See https://github.com/zed-industries/zed/issues/9418#issue-2189398784

After:


![image](https://github.com/zed-industries/zed/assets/53836821/a623164c-1ce0-40d7-bc53-020f176fba4a)


Release Notes:

- Fixed tooltip not showing up when hovering over links inside the chat
panel ([#9418](https://github.com/zed-industries/zed/issues/9418))
2024-03-22 14:47:57 +01:00
Piotr Osiewicz
945d8c2112 Revert "Revert "chore: Bump Rust version to 1.77 (#9631)"" (#9672)
Reverts zed-industries/zed#9658, as the Docker image is now available.

Release notes:

- N/A
2024-03-22 11:17:16 +01:00
张小白
ae6f138b6c Fix compute_width_for_char (#9643)
Release Notes:

- N/A
2024-03-22 09:10:42 +01:00
Nathan Sobo
6d5787cfdc Hard code max token counts for supported models (#9675) 2024-03-21 20:30:33 -06:00
Daniel Zhu
441677c90a Fix typo (mimized -> minimized) (#9674) 2024-03-21 16:14:31 -07:00
apricotbucket28
95b2f4caf2 linux: fix word move/select shortcuts (#9673)
`alt+left/right` are never used on Linux.
The Linux keymap still has some other issues, but these shortcuts in
particular are really common when editing text.

Release Notes:

- N/A
2024-03-21 15:28:39 -07:00
Ezekiel Warren
0f15fd37d6 windows: User installed language server support (#9606)
Release Notes:

- N/A
2024-03-21 15:25:26 -07:00
张小白
4183805a39 windows: fix window activate action (#9664)
Now, the window activation event can be triggered correctly. As shown in
the video, when the window is activated, the caret blinks; when the
window loses activation due to me clicking on the PowerShell window, the
caret stops blinking.



https://github.com/zed-industries/zed/assets/14981363/4c1b2bec-319d-4f21-879e-5a0af0a00d8e



Release Notes:

- N/A
2024-03-21 15:24:17 -07:00
Conrad Irwin
eaa803298e Fix error handling in buffer open (#9667)
Co-Authored-By: Max <max@zed.dev>
Co-Authored-By: Marshall <marshall@zed.dev>

Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
2024-03-21 15:57:20 -06:00
Conrad Irwin
caed275fbf Revert "language: Remove buffer fingerprinting (#9007)"
This reverts commit 6f2f61c9b1.
2024-03-21 14:10:18 -06:00
Remco Smits
35e3935e8f Fix invalid highlight position for (edited) text (#9660)
This pull request fixes a bug that was inserting the wrong position for
the `(edited)` text. This is only an issue when you have other styling
inside the markdown that causes the `richt_text.text.len()` to increase.

**Before**
<img wdth="234" alt="Screenshot 2024-03-21 at 19 53 34"
src="https://github.com/zed-industries/zed/assets/62463826/bf9e7fec-1563-415b-9b14-f7b95fc7d784">
**After**
<img width="234" alt="Screenshot 2024-03-21 at 19 53 48"
src="https://github.com/zed-industries/zed/assets/62463826/c0b7ba3a-5bdc-4b9e-a4f2-c3df4064f0ec">

Release Notes:

- N/A
2024-03-21 13:37:45 -06:00
Stanislav Alekseev
3b7cd9cf1e Remove incorrect venv base directory used (#9661)
Follow-up of https://github.com/zed-industries/zed/pull/8444

Release Notes:

- N/A
2024-03-21 21:34:14 +02:00
张小白
1e543b9755 windows: implement IME caret movement and editing while composing (#9659)
https://github.com/zed-industries/zed/assets/14981363/598440f7-0364-4053-9f44-710291f8aa92



Release Notes:

- N/A
2024-03-21 12:10:22 -07:00
Marshall Bowers
d557f8e36c Revert "chore: Bump Rust version to 1.77 (#9631)" (#9658)
This reverts commit 6184278faf.

We can't upgrade to Rust 1.77 until there are Rust 1.77 Docker images
available
(https://github.com/docker-library/official-images/pull/16457).


Release Notes:

- N/A
2024-03-21 14:07:22 -04:00
Ezekiel Warren
b6201a34b9 Check for user installed clangd (#9605)
Release Notes:

- Improved C/C++ support using user installed clangd when available
2024-03-21 10:50:42 -07:00
Stanislav Alekseev
85fdcef564 Do not enable venv in terminal for bash-like oneshot task invocations (#8444)
Release Notes:
- Work around #8334 by only activating venv in the terminal not in tasks
(see #8440 for a proper solution)
- To use venv modify your tasks in the following way:
```json
{
  "label": "Python main.py",
  "command": "sh",
  "args": ["-c", "source .venv/bin/activate && python3 main.py"]
}
```

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-03-21 19:40:33 +02:00
Piotr Osiewicz
cd61297740 collab: Bump minimal client version to 0.127.3 (#9649)
Release Notes:

- N/A
2024-03-21 18:23:18 +01:00
Mikayla Maki
e07192e4e3 Implement is_minimized for macOS (#9651)
Release Notes:

- N/A
2024-03-21 09:36:54 -07:00
Marshall Bowers
adcb591629 extension_cli: Populate grammars from grammars directory for legacy extension formats (#9650)
This PR makes the extension CLI populate the grammars in the manifest
from the contents of the `grammars` directory for legacy extensions
using the `extension.json` format (`schema_version == 0`).

This allows us to continue packaging these older extensions until they
can be migrated to the new schema version.

Release Notes:

- N/A
2024-03-21 12:31:53 -04:00
张小白
d89905fc3d Fix IME window position with scale factor greater than 1.0 (#9637)
In #9456 I forgot to handle this...

Release Notes:

- N/A
2024-03-21 09:31:43 -07:00
白山風露
e1d1d575c3 Windows: Fix XButton direction (#9629)
Release Notes:

- N/A
2024-03-21 09:31:19 -07:00
张小白
3fd62a2313 windows: display icon (#9571)
Now `Zed` can display icons. The image below shows the icon of the
`zed.exe` file and the icon in the right-click properties.

![Screenshot 2024-03-20
181054](https://github.com/zed-industries/zed/assets/14981363/8f1ccc7f-aab0-46cf-8c32-a3545ba710a3)

I used the `crates\zed\resources\app-icon@2x.png` file to generate the
`.ico` file. Due to some blank space around the logo in the original
file, the logo appears slightly smaller on Windows compared to other
software.

![Screenshot 2024-03-20
181155](https://github.com/zed-industries/zed/assets/14981363/874c5ed3-6796-428c-9a91-f91231bb6510)

The current `.ico` file contains logo files of multiple sizes: 16x16,
24x24, 32x32, 48x48, 64x64, 96x96, 128x128, 256x256, 512x512.

Release Notes:

- N/A
2024-03-21 09:30:01 -07:00
白山風露
f179158913 windows: Avoid recording minimized position to database (#9407)
Minimizing window records strange position to DB. When Zed is restarted,
the window goes far away.
<img width="975" alt="image"
src="https://github.com/zed-industries/zed/assets/6465609/98dbab71-fdbb-4911-b41a-9c3e498e5478">

So I fixed.

Release Notes:

- N/A
2024-03-21 09:15:16 -07:00
白山風露
f88f1bce20 Windows: Not logging frequent WM_PAINTs (#9566)
The logging of WM_PAINT for each frame was not very meaningful, so it
was eliminated.
Other logging levels were also reduced to trace.

Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-03-21 09:11:33 -07:00
Marshall Bowers
20b88b6078 Add a script for bumping the extension CLI (#9646)
This PR adds a script for bumping the extension CLI (thus kicking off a
new build).

Release Notes:

- N/A
2024-03-21 12:05:31 -04:00
Piotr Osiewicz
6f2f61c9b1 language: Remove buffer fingerprinting (#9007)
Followup to #9005 that actually removes buffer fingerprinting.

Release Notes:

- N/A
2024-03-21 17:03:26 +01:00
Marshall Bowers
1f21088591 Normalize - to _ in resulting Wasm file names (#9644)
This PR updates the extension builder to normalize `-` to `_` in the
Rust package names when computing the resulting `.wasm` file name.

The `wasm32-wasi` target does this normalization internally already, so
we need to do the same to ensure we're looking for the resulting `.wasm`
file in the right spot.

Release Notes:

- N/A
2024-03-21 11:48:08 -04:00
Marshall Bowers
3831088251 extension_cli: Don't propagate errors caused by trying to read Cargo.toml (#9641)
This PR fixes an issue where the extension CLI would error if a
`Cargo.toml` didn't exist when we were trying to check for its
existence. Since we're just checking if it exists for the purposes of
detecting a Rust extension, we can safely ignore the errors.

Also improved the logging/error handling in a few spots to make other
errors easier to troubleshoot in the future.

Release Notes:

- N/A
2024-03-21 11:29:35 -04:00
Piotr Osiewicz
e20508f66c lsp: Add partial support for insert/replace completions (#9634)
Most notably, this should do away with completions overriding the whole
word around completion trigger text. Fixes: #4816



Release Notes:

- Fixed code completions overriding text around the cursor.
2024-03-21 16:19:21 +01:00
Piotr Osiewicz
6184278faf chore: Bump Rust version to 1.77 (#9631)
Release Notes:

- N/A
2024-03-21 15:42:59 +01:00
Remco Smits
65152baa3f Fix prettier-plugin-organize-imports plugin removes used imports (#9598)
### Issue
So this pull request fixes an issue that was driven me crazy. The issue
was that when you use the `prettier-plugin-organize-imports` It would
remove some imports that should not be removed before they were used
inside the module itself.

You can reproduce it with the following `prettierrc.json` config and
source code. When you **save** the file, it would remove the `import
clsx from "clsx";` import from the file.

**Prettier config**
```json
{
  "semi": true,
  "tabWidth": 4,
  "trailingComma": "es5",
  "useTabs": true,
  "plugins": [
    "prettier-plugin-tailwindcss",
    "prettier-plugin-organize-imports"
  ]
}
```

**Source code**
```typescript
import clsx from "clsx";

export default function Home() {
  return (
      <main>
	      {clsx("asdjklasdjlkasd", "asdjlkasjdjlk")}
      </main>
  );
}
``` 

### Findings
After a deep dive with @mrnugget, I was debugging deep down the prettier
plugin system and found the issue that was causing this issue. When I
was looking inside the
`node_modules/prettier-plugin-organize-imports/lib/organize.js`. I saw
the following code that looked strange to me because it falls back to
`file.ts` if `filepath` is not passed through inside the prettier config
options.

<img width="860" alt="Screenshot 2024-03-20 at 21 31 46"
src="https://github.com/zed-industries/zed/assets/62463826/47177fe5-e5a9-41d8-9f2f-0304b2c2159f">

So the issue was small, if you look at the following code, the `path`
key should be `filepath` inside the
`crates/prettier/src/prettier_server.js:205`
![Screenshot 2024-03-20 at 21 35
25](https://github.com/zed-industries/zed/assets/62463826/1eea0a88-c886-4632-9c69-9f3095126971)

Release Notes:

- Fixed prettier integration not using the correct filepath when
invoking prettier, which could lead to some prettier plugins failing to
format correctly.
([#9496](https://github.com/zed-industries/zed/issues/9496)).
2024-03-21 08:23:15 +01:00
Conrad Irwin
65c6bfebda get-preview-channel-changes errors on invalid token (#9616)
Release Notes:

- N/A
2024-03-20 21:44:12 -06:00
Conrad Irwin
ac4c6c60f1 Make it (a tiny bit) easier to run your own collab (#9557)
* Allow creating channels when seeding
* Allow configuring a custom `SEED_PATH`
* Seed the database when creating/migrating it so you don't need a
  separate step for this.

Release Notes:

- N/A
2024-03-20 21:00:02 -06:00
Conrad Irwin
1062c5bd26 Fix copilot modal (#9613)
Release Notes:

- Fixed copilot modal not responding
([#9596](https://github.com/zed-industries/zed/issues/9596)). (preview
only)
2024-03-20 20:37:40 -06:00
Mikayla Maki
0b019282c3 Wayland: double click (#9608)
This PR builds off of an earlier version of
https://github.com/zed-industries/zed/pull/9595, rearranges some of the
logic, and removes an unused platform API.

Release Notes:

- N/A

---------

Co-authored-by: apricotbucket28 <agustin.nicolas.marcos@outlook.com>
2024-03-20 19:22:47 -07:00
Ezekiel Warren
9b0949b6fb Allow specifying no base keymap (#9471)
This PR is a bit of a shot in the dark. I'm not sure if this will be
acceptable and I understand if it gets rejected.

I've been trying to integrate Zed as my daily driver and the key
bindings have been a major hurdle for me. Mostly due to the
windows/linux keybindings being messed up, but also me wanting to have
more chained key bindings similar to helix or common in custom neovim
configurations.

I think having a `None` base keymap would allow someone to more easily
implement a new base keymap (#4642) and would make my daily use of Zed a
little nicer 😅.

Also I am aware that there would need to be a little more work done in
this PR for the other base keymaps such as 'atom' since they assume the
'default' (vscode) base keymaps are loaded. I'm happy to do that work if
a 'none' base keymap is acceptable.

Release Notes:

- Added ability to specify no base keymap which allows for full
keybinding customization
2024-03-20 18:52:17 -06:00
Andrew Lygin
5602c48136 Action release handlers (#8782)
This PR adds support for handling action releases &mdash; events that
are fired when the user releases all the modifier keys that were part of
an action-triggering shortcut.

If the user holds modifiers and invokes several actions sequentially via
shortcuts (same or different), only the last action is "released" when
its modifier keys released.

~The following methods were added to `Div`:~
- ~`capture_action_release()`~
- ~`on_action_release()`~
- ~`on_boxed_action_release()`~

~They work similarly to `capture_action()`, `on_action()` and
`on_boxed_action()`.~

See the implementation details in [this
comment](https://github.com/zed-industries/zed/pull/8782#issuecomment-2009154646).

Release Notes:

- Added a fast-switch mode to the file finder: hit `p` or `shift-p`
while holding down `cmd` to select a file immediately. (#8258).

Related Issues:

- Implements #8757 
- Implements #8258
- Part of #7653 

Co-authored-by: @ConradIrwin
2024-03-20 18:43:31 -06:00
Mikayla Maki
91ab95ec82 Fix bugs in linux text system (#9604)
Supersedes https://github.com/zed-industries/zed/pull/9579 by manually
including it.

Release Notes:

- N/A
2024-03-20 15:27:56 -07:00
Max Brunsfeld
585e8671e3 Add a schema to extensions, to prevent installing extensions on too old of a Zed version (#9599)
Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-03-20 17:33:26 -04:00
白山風露
b1feeb9f29 Windows: Refactoring (#9580)
Aggregate `DefWindowProc` calls with individual handler return value as
`Option<isize>`

Release Notes:

- N/A
2024-03-20 13:36:28 -07:00
Mikayla Maki
78e116c111 Fix skip prompt warning (#9590)
This fixes a non-panicking log error caused by
https://github.com/zed-industries/zed/pull/9452

Release Notes:

- N/A
2024-03-20 13:35:29 -07:00
Max Brunsfeld
d699b8e104 Allow extensions to define more of the methods in the LspAdapter trait (#9554)
Our goal is to extract Svelte support into an extension, since we've
seen problems with the Tree-sitter Svelte parser crashing due to bugs in
the external scanner. In order to do this, we need a couple more
capabilities in LSP extensions:

* [x] `initialization_options` - programmatically controlling the JSON
initialization params sent to the language server
* [x] `prettier_plugins` - statically specifying a list of prettier
plugins that apply for a given language.
* [x] `npm_install_package`

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-03-20 12:47:04 -07:00
Jason Lee
0ce5cdc48f Only allow opening one Extensions view (#9569)
Release Notes:

- Changed the extensions view to only allow one open instance at a time.


## Before

<img width="494" alt="image"
src="https://github.com/zed-industries/zed/assets/5518/9329e685-1946-4384-bec3-f7eadf18a0cc">
2024-03-20 14:49:36 -04:00
Thorsten Ball
3853991c20 Format prettier_server.js (#9583)
This PURELY formats the file by opening it in Zed and hitting save with
save-on-format on.

It's been bugging me that I can't change the file without the whole
thing getting reformatted, so here we are.

Release Notes:

- N/A
2024-03-20 19:49:14 +01:00
Marshall Bowers
3a2eb12f68 Fix binary name for extension CLI (#9591)
This PR fixes the binary name for the extension CLI.

This was originally done in #9541, but got accidentally reverted in
#9549.

Release Notes:

- N/A
2024-03-20 14:16:23 -04:00
Mikayla
59bc81d1bc v0.129.x dev 2024-03-20 09:16:41 -07:00
635 changed files with 29576 additions and 13096 deletions

View File

@@ -23,12 +23,6 @@ body:
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: If applicable, add mockups / screenshots to help explain present your vision of the feature
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.

View File

@@ -54,6 +54,9 @@ jobs:
- name: Check unused dependencies
uses: bnjbvr/cargo-machete@main
- name: Check license generation
run: script/generate-licenses /tmp/zed_licenses_output
- name: Ensure fresh merge
shell: bash -euxo pipefail {0}
run: |

View File

@@ -9,10 +9,10 @@ jobs:
if: github.repository_owner == 'zed-industries'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10.5"
python-version: "3.11"
architecture: "x64"
cache: "pip"
- run: pip install -r script/update_top_ranking_issues/requirements.txt
- run: python script/update_top_ranking_issues/main.py 5393 --github-token ${{ secrets.GITHUB_TOKEN }} --prod
- run: python script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 5393

View File

@@ -9,10 +9,10 @@ jobs:
if: github.repository_owner == 'zed-industries'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10.5"
python-version: "3.11"
architecture: "x64"
cache: "pip"
- run: pip install -r script/update_top_ranking_issues/requirements.txt
- run: python script/update_top_ranking_issues/main.py 6952 --github-token ${{ secrets.GITHUB_TOKEN }} --prod --query-day-interval 7
- run: python script/update_top_ranking_issues/main.py --github-token ${{ secrets.GITHUB_TOKEN }} --issue-reference-number 6952 --query-day-interval 7

View File

@@ -15,6 +15,10 @@
"JSON": {
"tab_size": 2,
"formatter": "prettier"
},
"JavaScript": {
"tab_size": 2,
"formatter": "prettier"
}
},
"formatter": "auto"

1018
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/activity_indicator",
"crates/anthropic",
"crates/assets",
"crates/assistant",
"crates/audio",
@@ -28,6 +29,7 @@ members = [
"crates/feature_flags",
"crates/feedback",
"crates/file_finder",
"crates/file_icons",
"crates/fs",
"crates/fsevent",
"crates/fuzzy",
@@ -70,6 +72,7 @@ members = [
"crates/task",
"crates/tasks_ui",
"crates/search",
"crates/semantic_version",
"crates/settings",
"crates/snippet",
"crates/sqlez",
@@ -77,6 +80,7 @@ members = [
"crates/story",
"crates/storybook",
"crates/sum_tree",
"crates/tab_switcher",
"crates/terminal",
"crates/terminal_view",
"crates/text",
@@ -95,8 +99,22 @@ members = [
"crates/zed",
"crates/zed_actions",
"extensions/astro",
"extensions/clojure",
"extensions/csharp",
"extensions/dart",
"extensions/emmet",
"extensions/erlang",
"extensions/gleam",
"extensions/haskell",
"extensions/html",
"extensions/php",
"extensions/prisma",
"extensions/purescript",
"extensions/svelte",
"extensions/toml",
"extensions/uiua",
"extensions/zig",
"tooling/xtask",
]
@@ -106,6 +124,7 @@ resolver = "2"
[workspace.dependencies]
activity_indicator = { path = "crates/activity_indicator" }
ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
audio = { path = "crates/audio" }
@@ -133,6 +152,7 @@ extensions_ui = { path = "crates/extensions_ui" }
feature_flags = { path = "crates/feature_flags" }
feedback = { path = "crates/feedback" }
file_finder = { path = "crates/file_finder" }
file_icons = { path = "crates/file_icons" }
fs = { path = "crates/fs" }
fsevent = { path = "crates/fsevent" }
fuzzy = { path = "crates/fuzzy" }
@@ -176,6 +196,7 @@ rpc = { path = "crates/rpc" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
search = { path = "crates/search" }
semantic_version = { path = "crates/semantic_version" }
settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
sqlez = { path = "crates/sqlez" }
@@ -183,6 +204,7 @@ sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
sum_tree = { path = "crates/sum_tree" }
tab_switcher = { path = "crates/tab_switcher" }
terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
@@ -201,16 +223,18 @@ zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
anyhow = "1.0.57"
any_vec = "0.13"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-fs = "1.6"
async-recursion = "1.0.0"
async-tar = "0.4.2"
async-trait = "0.1"
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "61cbd6b2c224791d52b150fe535cee665cc91bb2" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "61cbd6b2c224791d52b150fe535cee665cc91bb2" }
# 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" }
cap-std = "2.0"
cap-std = "3.0"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
clickhouse = { version = "0.11.6" }
@@ -272,6 +296,8 @@ tempfile = "3.9.0"
thiserror = "1.0.29"
tiktoken-rs = "0.5.7"
time = { version = "0.3", features = [
"macros",
"parsing",
"serde",
"serde-well-known",
"formatting",
@@ -280,25 +306,17 @@ toml = "0.8"
tokio = { version = "1", features = ["full"] }
tower-http = "0.4.4"
tree-sitter = { version = "0.20", features = ["wasm"] }
tree-sitter-astro = { git = "https://github.com/virchau13/tree-sitter-astro.git", rev = "e924787e12e8a03194f36a113290ac11d6dc10f3" }
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
tree-sitter-c = "0.20.1"
tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts" }
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
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-dockerfile = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev = "33e22c33bcdbfc33d42806ee84cfd0b1248cc392" }
tree-sitter-dart = { git = "https://github.com/agent3bood/tree-sitter-dart", rev = "48934e3bf757a9b78f17bdfaa3e2b4284656fdc7" }
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-erlang = "0.4.0"
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
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-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "8a99848fc734f9c4ea523b3f2a07df133cbbcec2" }
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" }
@@ -310,38 +328,32 @@ tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown",
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-php = "0.21.1"
tree-sitter-prisma-io = { git = "https://github.com/victorhqc/tree-sitter-prisma" }
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
tree-sitter-purescript = { git = "https://github.com/postsolar/tree-sitter-purescript", rev = "v0.1.0" }
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-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "bd60db7d3d06f89b6ec3b287c9a6e9190b5564bd" }
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
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" }
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
unindent = "0.1.7"
unicase = "2.6"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
wasmparser = "0.121"
wasm-encoder = "0.41"
wasmtime = { version = "18.0", default-features = false, features = [
wasmparser = "0.201"
wasm-encoder = "0.201"
wasmtime = { version = "19.0.0", default-features = false, features = [
"async",
"demangle",
"runtime",
"cranelift",
"component-model",
] }
wasmtime-wasi = "18.0"
wasmtime-wasi = "19.0.0"
which = "6.0.0"
wit-component = "0.20"
wit-component = "0.201"
sys-locale = "0.3.1"
[workspace.dependencies.windows]
@@ -375,7 +387,7 @@ features = [
]
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "4294e59279205f503eb14348dd5128bd5910c8fb" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f21c3b98c0749ac192da67a0d65dfe3eabc4a63" }
# 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" }
@@ -386,9 +398,11 @@ debug = "limited"
[profile.dev.package]
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }
resvg = { opt-level = 3 }
rustybuzz = { opt-level = 3 }
ttf-parser = { opt-level = 3 }
wasmtime-cranelift = { opt-level = 3 }
wasmtime = { opt-level = 3 }
[profile.release]
debug = "limited"

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.76-bookworm as builder
FROM rust:1.77-bookworm as builder
WORKDIR app
COPY . .

View File

@@ -1,4 +1,6 @@
[
// todo(linux): Review the editor bindings
// Standard Linux bindings
{
"bindings": {
"up": "menu::SelectPrev",
@@ -9,14 +11,14 @@
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext",
"ctrl-up": "menu::SelectFirst",
"ctrl-down": "menu::SelectLast",
"enter": "menu::Confirm",
"shift-f10": "menu::ShowContextMenu",
"ctrl-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"shift-enter": "menu::UseSelectedQuery",
"shift-enter": "picker::UseSelectedQuery",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
"ctrl-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
"ctrl-o": "workspace::Open",
@@ -26,9 +28,7 @@
"ctrl-0": "zed::ResetBufferFontSize",
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"ctrl-h": "zed::Hide",
"alt-ctrl-h": "zed::HideOthers",
"ctrl-m": "zed::Minimize",
"alt-f9": "zed::Hide",
"f11": "zed::ToggleFullScreen"
}
},
@@ -38,87 +38,107 @@
"escape": "editor::Cancel",
"backspace": "editor::Backspace",
"shift-backspace": "editor::Backspace",
"ctrl-h": "editor::Backspace",
"delete": "editor::Delete",
"ctrl-d": "editor::Delete",
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-t": "editor::Transpose",
"ctrl-backspace": "editor::DeleteToBeginningOfLine",
"ctrl-delete": "editor::DeleteToEndOfLine",
"alt-backspace": "editor::DeleteToPreviousWordStart",
"alt-delete": "editor::DeleteToNextWordEnd",
"alt-h": "editor::DeleteToPreviousWordStart",
"alt-d": "editor::DeleteToNextWordEnd",
// "ctrl-backspace": "editor::DeleteToBeginningOfLine",
// "ctrl-delete": "editor::DeleteToEndOfLine",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
// "ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
// "alt-h": "editor::DeleteToPreviousWordStart",
// "alt-d": "editor::DeleteToNextWordEnd",
"ctrl-x": "editor::Cut",
"ctrl-c": "editor::Copy",
"ctrl-v": "editor::Paste",
"ctrl-z": "editor::Undo",
"ctrl-shift-z": "editor::Redo",
"ctrl-y": "editor::Redo",
"up": "editor::MoveUp",
"ctrl-up": "editor::MoveToStartOfParagraph",
// "ctrl-up": "editor::MoveToStartOfParagraph", todo(linux) Should be "scroll down by 1 line"
"pageup": "editor::PageUp",
"shift-pageup": "editor::MovePageUp",
// "shift-pageup": "editor::MovePageUp", todo(linux) should be 'select page up'
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"ctrl-down": "editor::MoveToEndOfParagraph",
// "ctrl-down": "editor::MoveToEndOfParagraph", todo(linux) should be "scroll up by 1 line"
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::MovePageDown",
// "shift-pagedown": "editor::MovePageDown", todo(linux) should be 'select page down'
"end": "editor::MoveToEndOfLine",
"left": "editor::MoveLeft",
"right": "editor::MoveRight",
"ctrl-p": "editor::MoveUp",
"ctrl-n": "editor::MoveDown",
"ctrl-b": "editor::MoveLeft",
"ctrl-f": "editor::MoveRight",
"ctrl-shift-l": "editor::NextScreen", // todo(linux): What is this
"alt-left": "editor::MoveToPreviousWordStart",
"alt-b": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd",
"alt-f": "editor::MoveToNextWordEnd",
"ctrl-e": "editor::MoveToEndOfLine",
"ctrl-left": "editor::MoveToPreviousWordStart",
// "alt-b": "editor::MoveToPreviousWordStart",
"ctrl-right": "editor::MoveToNextWordEnd",
// "alt-f": "editor::MoveToNextWordEnd",
// "cmd-left": "editor::MoveToBeginningOfLine",
// "ctrl-a": "editor::MoveToBeginningOfLine",
// "cmd-right": "editor::MoveToEndOfLine",
// "ctrl-e": "editor::MoveToEndOfLine",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-=end": "editor::MoveToEnd",
"ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"shift-down": "editor::SelectDown",
"ctrl-shift-n": "editor::SelectDown",
"shift-left": "editor::SelectLeft",
"ctrl-shift-b": "editor::SelectLeft",
"shift-right": "editor::SelectRight",
"ctrl-shift-f": "editor::SelectRight",
"alt-shift-left": "editor::SelectToPreviousWordStart",
"alt-shift-b": "editor::SelectToPreviousWordStart",
"alt-shift-right": "editor::SelectToNextWordEnd",
"alt-shift-f": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::AddSelectionAbove",
"ctrl-shift-down": "editor::AddSelectionBelow",
// "ctrl-shift-up": "editor::SelectToStartOfParagraph",
// "ctrl-shift-down": "editor::SelectToEndOfParagraph",
"ctrl-shift-home": "editor::SelectToBeginning",
"ctrl-shift-end": "editor::SelectToEnd",
"ctrl-a": "editor::SelectAll",
"ctrl-l": "editor::SelectLine",
"ctrl-shift-i": "editor::Format",
// "cmd-shift-left": [
// "editor::SelectToBeginningOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
"shift-home": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
// "ctrl-shift-a": [
// "editor::SelectToBeginningOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
// "cmd-shift-right": [
// "editor::SelectToEndOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
"shift-end": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
"ctrl-shift-e": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
// "ctrl-shift-e": [
// "editor::SelectToEndOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
// "alt-v": [
// "editor::MovePageUp",
// {
// "center_cursor": true
// }
// ],
"ctrl-alt-space": "editor::ShowCharacterPalette",
"ctrl-;": "editor::ToggleLineNumbers",
"ctrl-alt-z": "editor::RevertSelectedHunks"
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
"ctrl-alt-g b": "editor::ToggleGitBlame"
}
},
{
@@ -126,30 +146,37 @@
"bindings": {
"enter": "editor::Newline",
"shift-enter": "editor::Newline",
"ctrl-shift-enter": "editor::NewlineAbove",
"ctrl-enter": "editor::NewlineBelow",
"ctrl-shift-enter": "editor::NewlineBelow",
"ctrl-enter": "editor::NewlineAbove",
"alt-z": "editor::ToggleSoftWrap",
"ctrl-f": [
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": [
"buffer_search::Deploy",
{
"focus": true
"replace_enabled": true
}
],
// "cmd-e": [
// "buffer_search::Deploy",
// {
// "focus": false
// }
// ],
"ctrl->": "assistant::QuoteSelection"
}
},
{
"context": "Editor && mode == full && copilot_suggestion",
"context": "Editor && mode == full && inline_completion",
"bindings": {
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion",
"alt-right": "editor::AcceptPartialCopilotSuggestion"
"alt-]": "editor::NextInlineCompletion",
"alt-[": "editor::PreviousInlineCompletion",
"alt-right": "editor::AcceptPartialInlineCompletion"
}
},
{
"context": "Editor && !copilot_suggestion",
"context": "Editor && !inline_completion",
"bindings": {
"alt-\\": "copilot::Suggest"
"alt-\\": "editor::ShowInlineCompletion"
}
},
{
@@ -163,8 +190,8 @@
{
"context": "AssistantPanel",
"bindings": {
"f3": "search::SelectNextMatch",
"shift-f3": "search::SelectPrevMatch"
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPrevMatch"
}
},
{
@@ -185,7 +212,9 @@
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"alt-tab": "search::CycleMode"
"alt-tab": "search::CycleMode",
"ctrl-f": "search::FocusSearch",
"ctrl-h": "search::ToggleReplace"
}
},
{
@@ -207,10 +236,10 @@
"bindings": {
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode",
"ctrl-shift-f": "search::FocusSearch",
"ctrl-shift-h": "search::ToggleReplace",
"ctrl-alt-g": "search::ActivateRegexMode",
"ctrl-alt-s": "search::ActivateSemanticMode",
"ctrl-alt-x": "search::ActivateTextMode"
"alt-ctrl-g": "search::ActivateRegexMode",
"alt-ctrl-x": "search::ActivateTextMode"
}
},
{
@@ -224,7 +253,7 @@
"context": "ProjectSearchBar && in_replace",
"bindings": {
"enter": "search::ReplaceNext",
"ctrl-enter": "search::ReplaceAll"
"ctrl-alt-enter": "search::ReplaceAll"
}
},
{
@@ -233,35 +262,31 @@
"escape": "project_search::ToggleFocus",
"alt-tab": "search::CycleMode",
"ctrl-shift-h": "search::ToggleReplace",
"ctrl-alt-g": "search::ActivateRegexMode",
"ctrl-alt-s": "search::ActivateSemanticMode",
"ctrl-alt-x": "search::ActivateTextMode"
"alt-ctrl-g": "search::ActivateRegexMode",
"alt-ctrl-x": "search::ActivateTextMode"
}
},
{
"context": "Pane",
"bindings": {
"ctrl-{": "pane::ActivatePrevItem",
"ctrl-}": "pane::ActivateNextItem",
"ctrl-alt-left": "pane::ActivatePrevItem",
"ctrl-alt-right": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-w": "pane::CloseActiveItem",
"ctrl-alt-t": "pane::CloseInactiveItems",
"ctrl-alt-shift-w": "workspace::CloseInactiveTabsAndPanes",
"alt-ctrl-t": "pane::CloseInactiveItems",
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k u": "pane::CloseCleanItems",
"ctrl-k ctrl-w": "pane::CloseAllItems",
"ctrl-f": "project_search::ToggleFocus",
"f3": "search::SelectNextMatch",
"shift-f3": "search::SelectPrevMatch",
"ctrl-shift-h": "search::ToggleReplace",
"ctrl-k w": "pane::CloseAllItems",
"ctrl-shift-f": "project_search::ToggleFocus",
"ctrl-alt-g": "search::SelectNextMatch",
"ctrl-alt-shift-g": "search::SelectPrevMatch",
"ctrl-alt-shift-h": "search::ToggleReplace",
"alt-enter": "search::SelectAllMatches",
"ctrl-alt-c": "search::ToggleCaseSensitive",
"ctrl-alt-w": "search::ToggleWholeWord",
"alt-tab": "search::CycleMode",
"ctrl-alt-f": "project_search::ToggleFilters",
"ctrl-alt-g": "search::ActivateRegexMode",
"ctrl-alt-s": "search::ActivateSemanticMode",
"ctrl-alt-x": "search::ActivateTextMode"
"alt-c": "search::ToggleCaseSensitive",
"alt-w": "search::ToggleWholeWord",
"alt-r": "search::CycleMode",
"alt-ctrl-f": "project_search::ToggleFilters",
"ctrl-alt-shift-r": "search::ActivateRegexMode",
"ctrl-alt-shift-x": "search::ActivateTextMode"
}
},
// Bindings from VS Code
@@ -270,8 +295,22 @@
"bindings": {
"ctrl-[": "editor::Outdent",
"ctrl-]": "editor::Indent",
"ctrl-alt-up": "editor::AddSelectionAbove",
"ctrl-alt-down": "editor::AddSelectionBelow",
"shift-alt-up": "editor::AddSelectionAbove",
"shift-alt-down": "editor::AddSelectionBelow",
"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-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
"ctrl-shift-down": "editor::SelectSmallerSyntaxNode", //todo(linux) tmp keybinding
"ctrl-d": [
"editor::SelectNext",
{
@@ -304,8 +343,6 @@
"advance_downwards": false
}
],
"alt-up": "editor::SelectLargerSyntaxNode",
"alt-down": "editor::SelectSmallerSyntaxNode",
"ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic",
@@ -314,15 +351,16 @@
"f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
"ctrl-f12": "editor::GoToTypeDefinition",
"ctrl-alt-f12": "editor::GoToTypeDefinitionSplit",
"shift-f12": "editor::GoToImplementation",
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-alt-[": "editor::Fold",
"ctrl-alt-]": "editor::UnfoldLines",
"ctrl-shift-[": "editor::Fold",
"ctrl-shift-]": "editor::UnfoldLines",
"ctrl-space": "editor::ShowCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-alt-r": "editor::RevealInFinder",
"ctrl-alt-c": "editor::DisplayCursorNames"
"alt-ctrl-r": "editor::RevealInFinder",
"ctrl-alt-shift-c": "editor::DisplayCursorNames"
}
},
{
@@ -335,18 +373,18 @@
{
"context": "Pane",
"bindings": {
"ctrl-1": ["pane::ActivateItem", 0],
"ctrl-2": ["pane::ActivateItem", 1],
"ctrl-3": ["pane::ActivateItem", 2],
"ctrl-4": ["pane::ActivateItem", 3],
"ctrl-5": ["pane::ActivateItem", 4],
"ctrl-6": ["pane::ActivateItem", 5],
"ctrl-7": ["pane::ActivateItem", 6],
"ctrl-8": ["pane::ActivateItem", 7],
"ctrl-9": ["pane::ActivateItem", 8],
"ctrl-0": "pane::ActivateLastItem",
"ctrl--": "pane::GoBack",
"ctrl-_": "pane::GoForward",
"alt-1": ["pane::ActivateItem", 0],
"alt-2": ["pane::ActivateItem", 1],
"alt-3": ["pane::ActivateItem", 2],
"alt-4": ["pane::ActivateItem", 3],
"alt-5": ["pane::ActivateItem", 4],
"alt-6": ["pane::ActivateItem", 5],
"alt-7": ["pane::ActivateItem", 6],
"alt-8": ["pane::ActivateItem", 7],
"alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem",
"ctrl-alt--": "pane::GoBack",
"ctrl-alt-_": "pane::GoForward",
"ctrl-shift-t": "pane::ReopenClosedItem",
"ctrl-shift-f": "project_search::ToggleFocus"
}
@@ -361,8 +399,8 @@
// "create_new_window": true
// }
// ]
"ctrl-alt-o": "projects::OpenRecent",
"ctrl-alt-b": "branches::OpenRecent",
"alt-ctrl-o": "projects::OpenRecent",
"alt-ctrl-shift-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"ctrl-s": "workspace::Save",
"ctrl-k s": "workspace::SaveWithoutFormat",
@@ -370,24 +408,33 @@
"ctrl-n": "workspace::NewFile",
"ctrl-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"ctrl-1": ["workspace::ActivatePane", 0],
"ctrl-2": ["workspace::ActivatePane", 1],
"ctrl-3": ["workspace::ActivatePane", 2],
"ctrl-4": ["workspace::ActivatePane", 3],
"ctrl-5": ["workspace::ActivatePane", 4],
"ctrl-6": ["workspace::ActivatePane", 5],
"ctrl-7": ["workspace::ActivatePane", 6],
"ctrl-8": ["workspace::ActivatePane", 7],
"ctrl-9": ["workspace::ActivatePane", 8],
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-r": "workspace::ToggleRightDock",
"alt-1": ["workspace::ActivatePane", 0],
"alt-2": ["workspace::ActivatePane", 1],
"alt-3": ["workspace::ActivatePane", 2],
"alt-4": ["workspace::ActivatePane", 3],
"alt-5": ["workspace::ActivatePane", 4],
"alt-6": ["workspace::ActivatePane", 5],
"alt-7": ["workspace::ActivatePane", 6],
"alt-8": ["workspace::ActivatePane", 7],
"alt-9": ["workspace::ActivatePane", 8],
"ctrl-alt-b": "workspace::ToggleLeftDock",
"ctrl-b": "workspace::ToggleRightDock",
"ctrl-j": "workspace::ToggleBottomDock",
"ctrl-alt-y": "workspace::CloseAllDocks",
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-shift-h": [
"pane::DeploySearch",
{
"replace_enabled": true
}
],
"ctrl-k ctrl-s": "zed::OpenKeymap",
"ctrl-t": "project_symbols::Toggle",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-shift-t": "project_symbols::Toggle",
"ctrl-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"ctrl-e": "file_finder::Toggle",
"ctrl-shift-p": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
@@ -408,15 +455,12 @@
}
},
// Bindings from Sublime Text
// todo(linux) make sure these match linux bindings or remove above comment?
{
"context": "Editor",
"bindings": {
"ctrl-shift-k": "editor::DeleteLine",
"ctrl-shift-d": "editor::DuplicateLine",
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-j": "editor::JoinLines",
"ctrl-alt-up": "editor::MoveLineUp",
"ctrl-alt-down": "editor::MoveLineDown",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
@@ -432,7 +476,6 @@
}
},
// Bindings from Atom
// todo(linux) make sure these match linux bindings or remove above comment?
{
"context": "Pane",
"bindings": {
@@ -478,7 +521,7 @@
"bindings": {
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"ctrl-alt-c": "collab_panel::ToggleFocus",
"ctrl-shift-c": "collab_panel::ToggleFocus",
"ctrl-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints"
}
@@ -505,19 +548,19 @@
"left": "project_panel::CollapseSelectedEntry",
"right": "project_panel::ExpandSelectedEntry",
"ctrl-n": "project_panel::NewFile",
"ctrl-alt-n": "project_panel::NewDirectory",
"alt-ctrl-n": "project_panel::NewDirectory",
"ctrl-x": "project_panel::Cut",
"ctrl-c": "project_panel::Copy",
"ctrl-v": "project_panel::Paste",
"ctrl-alt-c": "project_panel::CopyPath",
"ctrl-alt-shift-c": "project_panel::CopyRelativePath",
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
"f2": "project_panel::Rename",
"enter": "project_panel::Rename",
"backspace": "project_panel::Delete",
"delete": "project_panel::Delete",
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": true }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": true }],
"ctrl-alt-r": "project_panel::RevealInFinder",
"alt-ctrl-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory"
}
},
@@ -558,29 +601,35 @@
"escape": "chat_panel::CloseReplyPreview"
}
},
{
"context": "FileFinder",
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
},
{
"context": "TabSwitcher",
"bindings": {
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}
},
{
"context": "Terminal",
"bindings": {
"ctrl-alt-space": "terminal::ShowCharacterPalette",
"ctrl-shift-c": "terminal::Copy",
"ctrl-shift-v": "terminal::Paste",
"ctrl-k": "terminal::Clear",
// Some nice conveniences
"ctrl-backspace": ["terminal::SendText", "\u0015"],
"ctrl-right": ["terminal::SendText", "\u0005"],
"ctrl-left": ["terminal::SendText", "\u0001"],
// Terminal.app compatibility
"alt-left": ["terminal::SendText", "\u001bb"],
"alt-right": ["terminal::SendText", "\u001bf"],
// There are conflicting bindings for these keys in the global context.
// these bindings override them, remove at your own risk:
"shift-ctrl-c": "terminal::Copy",
"shift-ctrl-v": "terminal::Paste",
"up": ["terminal::SendKeystroke", "up"],
"pageup": ["terminal::SendKeystroke", "pageup"],
"down": ["terminal::SendKeystroke", "down"],
"pagedown": ["terminal::SendKeystroke", "pagedown"],
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
// Some nice conveniences
"ctrl-backspace": ["terminal::SendText", "\u0015"],
"ctrl-right": ["terminal::SendText", "\u0005"],
"ctrl-left": ["terminal::SendText", "\u0001"]
}
}
]

View File

@@ -13,11 +13,15 @@
"cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast",
"enter": "menu::Confirm",
"ctrl-enter": "menu::ShowContextMenu",
"ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"shift-enter": "menu::UseSelectedQuery",
"shift-enter": "picker::UseSelectedQuery",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
"cmd-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
"cmd-o": "workspace::Open",
@@ -154,7 +158,8 @@
],
"ctrl-cmd-space": "editor::ShowCharacterPalette",
"cmd-;": "editor::ToggleLineNumbers",
"cmd-alt-z": "editor::RevertSelectedHunks"
"cmd-alt-z": "editor::RevertSelectedHunks",
"cmd-alt-g b": "editor::ToggleGitBlame"
}
},
{
@@ -165,10 +170,11 @@
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-enter": "editor::NewlineBelow",
"alt-z": "editor::ToggleSoftWrap",
"cmd-f": [
"cmd-f": "buffer_search::Deploy",
"cmd-alt-f": [
"buffer_search::Deploy",
{
"focus": true
"replace_enabled": true
}
],
"cmd-e": [
@@ -181,17 +187,17 @@
}
},
{
"context": "Editor && mode == full && copilot_suggestion",
"context": "Editor && mode == full && inline_completion",
"bindings": {
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion",
"alt-right": "editor::AcceptPartialCopilotSuggestion"
"alt-]": "editor::NextInlineCompletion",
"alt-[": "editor::PreviousInlineCompletion",
"alt-right": "editor::AcceptPartialInlineCompletion"
}
},
{
"context": "Editor && !copilot_suggestion",
"context": "Editor && !inline_completion",
"bindings": {
"alt-\\": "copilot::Suggest"
"alt-\\": "editor::ShowInlineCompletion"
}
},
{
@@ -227,7 +233,9 @@
"enter": "search::SelectNextMatch",
"shift-enter": "search::SelectPrevMatch",
"alt-enter": "search::SelectAllMatches",
"alt-tab": "search::CycleMode"
"alt-tab": "search::CycleMode",
"cmd-f": "search::FocusSearch",
"cmd-alt-f": "search::ToggleReplace"
}
},
{
@@ -249,6 +257,7 @@
"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"
@@ -316,13 +325,8 @@
"cmd-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
"alt-shift-up": [
"editor::DuplicateLine",
{
"move_upwards": true
}
],
"alt-shift-down": "editor::DuplicateLine",
"alt-shift-up": "editor::DuplicateLineUp",
"alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-right": "editor::SelectLargerSyntaxNode",
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode",
"cmd-d": [
@@ -365,6 +369,7 @@
"f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
"cmd-f12": "editor::GoToTypeDefinition",
"shift-f12": "editor::GoToImplementation",
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
@@ -435,10 +440,18 @@
"cmd-j": "workspace::ToggleBottomDock",
"alt-cmd-y": "workspace::CloseAllDocks",
"cmd-shift-f": "pane::DeploySearch",
"cmd-shift-h": [
"pane::DeploySearch",
{
"replace_enabled": true
}
],
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "project_panel::ToggleFocus",
@@ -597,6 +610,17 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"bindings": { "cmd-shift-p": "file_finder::SelectPrev" }
},
{
"context": "TabSwitcher",
"bindings": {
"ctrl-shift-tab": "menu::SelectPrev",
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}
},
{
"context": "Terminal",
"bindings": {

View File

@@ -11,7 +11,7 @@
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"cmd-d": "editor::DuplicateLine",
"cmd-d": "editor::DuplicateLineDown",
"cmd-backspace": "editor::DeleteLine",
"cmd-pagedown": "editor::MovePageDown",
"cmd-pageup": "editor::MovePageUp",

View File

@@ -13,7 +13,7 @@
"cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast",
"enter": "menu::Confirm",
"ctrl-enter": "menu::ShowContextMenu",
"ctrl-enter": "menu::SecondaryConfirm",
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",

View File

@@ -9,7 +9,7 @@
"context": "Editor",
"bindings": {
"cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLine",
"ctrl-shift-d": "editor::DuplicateLineDown",
"cmd-b": "editor::GoToDefinition",
"cmd-j": "editor::ScrollCursorCenter",
"cmd-enter": "editor::NewlineBelow",

View File

@@ -73,8 +73,17 @@
],
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
"n": "search::SelectNextMatch",
"shift-n": "search::SelectPrevMatch",
"/": "vim::Search",
"?": [
"vim::Search",
{
"backwards": true
}
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching",
"f": [
"vim::PushOperator",
@@ -137,8 +146,10 @@
"g d": "editor::GoToDefinition",
"g shift-d": "editor::GoToTypeDefinition",
"g x": "editor::OpenUrl",
"g n": "vim::SelectNext",
"g shift-n": "vim::SelectPrevious",
"g n": "vim::SelectNextMatch",
"g shift-n": "vim::SelectPreviousMatch",
"g l": "vim::SelectNext",
"g shift-l": "vim::SelectPrevious",
"g >": [
"editor::SelectNext",
{
@@ -349,15 +360,6 @@
],
"u": "editor::Undo",
"ctrl-r": "editor::Redo",
"/": "vim::Search",
"?": [
"vim::Search",
{
"backwards": true
}
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"r": ["vim::PushOperator", "Replace"],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
@@ -382,18 +384,46 @@
"d": "editor::Rename" // zed specific
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == c",
"bindings": {
"s": [
"vim::PushOperator",
{
"ChangeSurrounds": {}
}
]
}
},
{
"context": "Editor && vim_operator == d",
"bindings": {
"d": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == d",
"bindings": {
"s": ["vim::PushOperator", "DeleteSurrounds"]
}
},
{
"context": "Editor && vim_operator == y",
"bindings": {
"y": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == y",
"bindings": {
"s": [
"vim::PushOperator",
{
"AddSurrounds": {}
}
]
}
},
{
"context": "Editor && VimObject",
"bindings": {
@@ -510,7 +540,7 @@
"ctrl-[": "vim::NormalBefore",
"ctrl-x ctrl-o": "editor::ShowCompletions",
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
"ctrl-x ctrl-c": "copilot::Suggest", // zed specific
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
"ctrl-x ctrl-z": "editor::Cancel",
"ctrl-w": "editor::DeleteToPreviousWordStart",
@@ -546,6 +576,12 @@
"escape": "buffer_search::Dismiss"
}
},
{
"context": "EmptyPane || SharedScreen",
"bindings": {
":": "command_palette::Toggle"
}
},
{
// netrw compatibility
"context": "ProjectPanel && not_editing",

View File

@@ -48,7 +48,8 @@
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
// The key to use for adding multiple cursors
// Currently "alt" or "cmd" are supported.
// 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
"vim_mode": false,
@@ -69,7 +70,7 @@
// documentation when not included in original completion list.
"completion_documentation_secondary_query_debounce": 300,
// Whether to show wrap guides in the editor. Setting this to true will
// show a guide at the 'preferred_line_length' value if softwrap is set to
// show a guide at the 'preferred_line_length' value if 'soft_wrap' is set to
// 'preferred_line_length', and will show any additional guides as specified
// by the 'wrap_guides' setting.
"show_wrap_guides": true,
@@ -245,6 +246,8 @@
"assistant": {
// Version of this setting.
"version": "1",
// Whether the assistant is enabled.
"enabled": true,
// Whether to show the assistant panel button in the status bar.
"button": true,
// Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'.
@@ -281,6 +284,11 @@
// 4. Save when idle for a certain amount of time:
// "autosave": { "after_delay": {"milliseconds": 500} },
"autosave": "off",
// Settings related to the editor's tab bar.
"tab_bar": {
// Whether or not to show the navigation history buttons.
"show_nav_history_buttons": true
},
// Settings related to the editor's tabs
"tabs": {
// Show git status colors in the editor tabs.
@@ -542,53 +550,29 @@
"file_types": {},
// Different settings for specific languages.
"languages": {
"Plain Text": {
"soft_wrap": "preferred_line_length"
"C++": {
"format_on_save": "off"
},
"Elixir": {
"tab_size": 2
"C": {
"format_on_save": "off"
},
"Gleam": {
"tab_size": 2
},
"Go": {
"tab_size": 4,
"hard_tabs": true,
"code_actions_on_format": {
"source.organizeImports": true
}
},
"Markdown": {
"tab_size": 2,
"soft_wrap": "preferred_line_length"
"Make": {
"hard_tabs": true
},
"JavaScript": {
"tab_size": 2
},
"Terraform": {
"tab_size": 2
},
"TypeScript": {
"tab_size": 2
},
"TSX": {
"tab_size": 2
},
"YAML": {
"tab_size": 2
},
"JSON": {
"tab_size": 2
},
"OCaml": {
"tab_size": 2
},
"OCaml Interface": {
"Prisma": {
"tab_size": 2
}
},
// Zed's Prettier integration settings.
// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
// If Prettier is enabled, Zed will use this for its Prettier instance for any applicable file, if
// project has no other Prettier installed.
"prettier": {
// Use regular Prettier json configuration:
@@ -637,5 +621,10 @@
// Mostly useful for developers who are managing multiple instances of Zed.
"dev": {
// "theme": "Andromeda"
},
// Task-related settings.
"task": {
// Whether to show task status indicator in the status bar. Default: true
"show_status_indicator": true
}
}

View File

@@ -111,7 +111,7 @@
"hint": "#618399ff",
"hint.background": "#12231fff",
"hint.border": "#183934ff",
"ignored": "#aca8aeff",
"ignored": "#6b6b73ff",
"ignored.background": "#262933ff",
"ignored.border": "#2b2f38ff",
"info": "#10a793ff",

View File

@@ -111,7 +111,7 @@
"hint": "#706897ff",
"hint.background": "#161a35ff",
"hint.border": "#222953ff",
"ignored": "#898591ff",
"ignored": "#756f7eff",
"ignored.background": "#3a353fff",
"ignored.border": "#56505eff",
"info": "#566ddaff",
@@ -495,7 +495,7 @@
"hint": "#776d9dff",
"hint.background": "#e1e0f9ff",
"hint.border": "#c8c7f2ff",
"ignored": "#5a5462ff",
"ignored": "#6e6876ff",
"ignored.background": "#bfbcc5ff",
"ignored.border": "#8f8b96ff",
"info": "#586cdaff",
@@ -879,7 +879,7 @@
"hint": "#b17272ff",
"hint.background": "#171e38ff",
"hint.border": "#262f56ff",
"ignored": "#a4a08bff",
"ignored": "#8f8b77ff",
"ignored.background": "#45433bff",
"ignored.border": "#6c695cff",
"info": "#6684e0ff",
@@ -1263,7 +1263,7 @@
"hint": "#b37979ff",
"hint.background": "#e3e5faff",
"hint.border": "#cdd1f5ff",
"ignored": "#706d5fff",
"ignored": "#878471ff",
"ignored.background": "#cecab4ff",
"ignored.border": "#a8a48eff",
"info": "#6684dfff",
@@ -1647,7 +1647,7 @@
"hint": "#6f815aff",
"hint.background": "#142319ff",
"hint.border": "#1c3927ff",
"ignored": "#91907fff",
"ignored": "#7d7c6aff",
"ignored.background": "#424136ff",
"ignored.border": "#5d5c4cff",
"info": "#36a165ff",
@@ -2031,7 +2031,7 @@
"hint": "#758961ff",
"hint.background": "#d9ecdfff",
"hint.border": "#bbddc6ff",
"ignored": "#61604fff",
"ignored": "#767463ff",
"ignored.background": "#c5c4b9ff",
"ignored.border": "#969585ff",
"info": "#37a165ff",
@@ -2415,7 +2415,7 @@
"hint": "#a77087ff",
"hint.background": "#0f1c3dff",
"hint.border": "#182d5bff",
"ignored": "#a79f9dff",
"ignored": "#8e8683ff",
"ignored.background": "#443c39ff",
"ignored.border": "#665f5cff",
"info": "#407ee6ff",
@@ -2799,7 +2799,7 @@
"hint": "#a67287ff",
"hint.background": "#dfe3fbff",
"hint.border": "#c6cef7ff",
"ignored": "#6a6360ff",
"ignored": "#837b78ff",
"ignored.background": "#ccc7c5ff",
"ignored.border": "#aaa3a1ff",
"info": "#407ee6ff",
@@ -3183,7 +3183,7 @@
"hint": "#8d70a8ff",
"hint.background": "#0d1a43ff",
"hint.border": "#192961ff",
"ignored": "#a899a8ff",
"ignored": "#908190ff",
"ignored.background": "#433a43ff",
"ignored.border": "#675b67ff",
"info": "#5169ebff",
@@ -3567,7 +3567,7 @@
"hint": "#8c70a6ff",
"hint.background": "#e2dffcff",
"hint.border": "#cac7faff",
"ignored": "#6b5e6bff",
"ignored": "#857785ff",
"ignored.background": "#c6b8c6ff",
"ignored.border": "#ad9dadff",
"info": "#5169ebff",
@@ -3951,7 +3951,7 @@
"hint": "#52809aff",
"hint.background": "#121c24ff",
"hint.border": "#1a2f3cff",
"ignored": "#7c9fb3ff",
"ignored": "#688c9dff",
"ignored.background": "#33444dff",
"ignored.border": "#4f6a78ff",
"info": "#267eadff",
@@ -4335,7 +4335,7 @@
"hint": "#5a87a0ff",
"hint.background": "#d8e4eeff",
"hint.border": "#b9cee0ff",
"ignored": "#526f7dff",
"ignored": "#628496ff",
"ignored.background": "#a6cadcff",
"ignored.border": "#80a4b6ff",
"info": "#267eadff",
@@ -4719,7 +4719,7 @@
"hint": "#8a647aff",
"hint.background": "#1c1b29ff",
"hint.border": "#2c2b45ff",
"ignored": "#898383ff",
"ignored": "#756e6eff",
"ignored.background": "#3b3535ff",
"ignored.border": "#564e4eff",
"info": "#7272caff",
@@ -5103,7 +5103,7 @@
"hint": "#91697fff",
"hint.background": "#e4e1f5ff",
"hint.border": "#cecaecff",
"ignored": "#5a5252ff",
"ignored": "#6e6666ff",
"ignored.background": "#c1bbbbff",
"ignored.border": "#8e8989ff",
"info": "#7272caff",
@@ -5487,7 +5487,7 @@
"hint": "#607e76ff",
"hint.background": "#151e20ff",
"hint.border": "#1f3233ff",
"ignored": "#859188ff",
"ignored": "#6f7e74ff",
"ignored.background": "#353f39ff",
"ignored.border": "#505e55ff",
"info": "#468b8fff",
@@ -5871,7 +5871,7 @@
"hint": "#66847cff",
"hint.background": "#dae7e8ff",
"hint.border": "#bed4d6ff",
"ignored": "#546259ff",
"ignored": "#68766dff",
"ignored.background": "#bcc5bfff",
"ignored.border": "#8b968eff",
"info": "#488b90ff",
@@ -6255,7 +6255,7 @@
"hint": "#008b9fff",
"hint.background": "#051949ff",
"hint.border": "#102667ff",
"ignored": "#8ba48bff",
"ignored": "#778f77ff",
"ignored.background": "#3b453bff",
"ignored.border": "#5c6c5cff",
"info": "#3e62f4ff",
@@ -6639,7 +6639,7 @@
"hint": "#008fa1ff",
"hint.background": "#e1ddfeff",
"hint.border": "#c9c4fdff",
"ignored": "#5f705fff",
"ignored": "#718771ff",
"ignored.background": "#b4ceb4ff",
"ignored.border": "#8ea88eff",
"info": "#3e61f4ff",
@@ -7023,7 +7023,7 @@
"hint": "#6c81a5ff",
"hint.background": "#161f2bff",
"hint.border": "#203348ff",
"ignored": "#959bb2ff",
"ignored": "#7e849eff",
"ignored.background": "#3e4769ff",
"ignored.border": "#5b6385ff",
"info": "#3e8ed0ff",
@@ -7407,7 +7407,7 @@
"hint": "#7087b2ff",
"hint.background": "#dde7f6ff",
"hint.border": "#c2d5efff",
"ignored": "#5f6789ff",
"ignored": "#767d9aff",
"ignored.background": "#c1c5d8ff",
"ignored.border": "#9a9fb6ff",
"info": "#3e8fd0ff",

View File

@@ -111,7 +111,7 @@
"hint": "#628b80ff",
"hint.background": "#0d2f4eff",
"hint.border": "#1b4a6eff",
"ignored": "#8a8986ff",
"ignored": "#696a6aff",
"ignored.background": "#313337ff",
"ignored.border": "#3f4043ff",
"info": "#5ac1feff",
@@ -480,7 +480,7 @@
"hint": "#8ca7c2ff",
"hint.background": "#deebfaff",
"hint.border": "#c4daf6ff",
"ignored": "#8b8e92ff",
"ignored": "#a9acaeff",
"ignored.background": "#dcdddeff",
"ignored.border": "#cfd1d2ff",
"info": "#3b9ee5ff",
@@ -849,7 +849,7 @@
"hint": "#7399a3ff",
"hint.background": "#123950ff",
"hint.border": "#24556fff",
"ignored": "#9a9a98ff",
"ignored": "#7b7d7fff",
"ignored.background": "#464a52ff",
"ignored.border": "#53565dff",
"info": "#72cffeff",

View File

@@ -111,7 +111,7 @@
"hint": "#8c957dff",
"hint.background": "#1e2321ff",
"hint.border": "#303a36ff",
"ignored": "#c5b597ff",
"ignored": "#998b78ff",
"ignored.background": "#4c4642ff",
"ignored.border": "#5b534dff",
"info": "#83a598ff",
@@ -485,7 +485,7 @@
"hint": "#6a695bff",
"hint.background": "#1e2321ff",
"hint.border": "#303a36ff",
"ignored": "#c5b597ff",
"ignored": "#998b78ff",
"ignored.background": "#4c4642ff",
"ignored.border": "#5b534dff",
"info": "#83a598ff",
@@ -859,7 +859,7 @@
"hint": "#8c957dff",
"hint.background": "#1e2321ff",
"hint.border": "#303a36ff",
"ignored": "#c5b597ff",
"ignored": "#998b78ff",
"ignored.background": "#4c4642ff",
"ignored.border": "#5b534dff",
"info": "#83a598ff",
@@ -1233,7 +1233,7 @@
"hint": "#677562ff",
"hint.background": "#d2dee2ff",
"hint.border": "#adc5ccff",
"ignored": "#5f5650ff",
"ignored": "#897b6eff",
"ignored.background": "#d9c8a4ff",
"ignored.border": "#c8b899ff",
"info": "#0b6678ff",
@@ -1607,7 +1607,7 @@
"hint": "#677562ff",
"hint.background": "#d2dee2ff",
"hint.border": "#adc5ccff",
"ignored": "#5f5650ff",
"ignored": "#897b6eff",
"ignored.background": "#d9c8a4ff",
"ignored.border": "#c8b899ff",
"info": "#0b6678ff",
@@ -1981,7 +1981,7 @@
"hint": "#677562ff",
"hint.background": "#d2dee2ff",
"hint.border": "#adc5ccff",
"ignored": "#5f5650ff",
"ignored": "#897b6eff",
"ignored.background": "#d9c8a4ff",
"ignored.border": "#c8b899ff",
"info": "#0b6678ff",

View File

@@ -111,7 +111,7 @@
"hint": "#5a6f89ff",
"hint.background": "#18243dff",
"hint.border": "#293b5bff",
"ignored": "#838994ff",
"ignored": "#555a63ff",
"ignored.background": "#3b414dff",
"ignored.border": "#464b57ff",
"info": "#74ade8ff",
@@ -485,7 +485,7 @@
"hint": "#9294beff",
"hint.background": "#e2e2faff",
"hint.border": "#cbcdf6ff",
"ignored": "#7e8087ff",
"ignored": "#a1a1a3ff",
"ignored.background": "#dcdcddff",
"ignored.border": "#c9c9caff",
"info": "#5c78e2ff",

View File

@@ -111,7 +111,7 @@
"hint": "#5e768cff",
"hint.background": "#2f3639ff",
"hint.border": "#435255ff",
"ignored": "#74708dff",
"ignored": "#2f2b43ff",
"ignored.background": "#292738ff",
"ignored.border": "#423f55ff",
"info": "#9bced6ff",
@@ -490,7 +490,7 @@
"hint": "#7a92aaff",
"hint.background": "#dde9ebff",
"hint.border": "#c3d7dbff",
"ignored": "#706c8cff",
"ignored": "#938fa3ff",
"ignored.background": "#dcd8d8ff",
"ignored.border": "#dcd6d5ff",
"info": "#57949fff",
@@ -869,7 +869,7 @@
"hint": "#728aa2ff",
"hint.background": "#2f3639ff",
"hint.border": "#435255ff",
"ignored": "#85819eff",
"ignored": "#605d7aff",
"ignored.background": "#38354eff",
"ignored.border": "#504c68ff",
"info": "#9bced6ff",

View File

@@ -111,7 +111,7 @@
"hint": "#727d68ff",
"hint.background": "#171e1eff",
"hint.border": "#223131ff",
"ignored": "#a69782ff",
"ignored": "#827568ff",
"ignored.background": "#333944ff",
"ignored.border": "#3d4350ff",
"info": "#518b8bff",

View File

@@ -111,7 +111,7 @@
"hint": "#4f8297ff",
"hint.background": "#141f2cff",
"hint.border": "#1b3149ff",
"ignored": "#93a1a1ff",
"ignored": "#6f8389ff",
"ignored.background": "#073743ff",
"ignored.border": "#2b4e58ff",
"info": "#278ad1ff",
@@ -480,7 +480,7 @@
"hint": "#5789a3ff",
"hint.background": "#dbe6f6ff",
"hint.border": "#bfd3efff",
"ignored": "#34555eff",
"ignored": "#6a7f86ff",
"ignored.background": "#cfd0c4ff",
"ignored.border": "#9faaa8ff",
"info": "#288bd1ff",

View File

@@ -111,7 +111,7 @@
"hint": "#246e61ff",
"hint.background": "#0e2242ff",
"hint.border": "#193760ff",
"ignored": "#736e55ff",
"ignored": "#4c4735ff",
"ignored.background": "#2a261cff",
"ignored.border": "#302c21ff",
"info": "#499befff",

View File

@@ -16,6 +16,7 @@ doctest = false
anyhow.workspace = true
auto_update.workspace = true
editor.workspace = true
extension.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true

View File

@@ -1,5 +1,6 @@
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor;
use extension::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, svg, AppContext, CursorStyle, EventEmitter, InteractiveElement as _, Model,
@@ -205,7 +206,7 @@ impl ActivityIndicator {
}
LanguageServerBinaryStatus::Downloading => downloading.push(status.name.0.as_ref()),
LanguageServerBinaryStatus::Failed { .. } => failed.push(status.name.0.as_ref()),
LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {}
LanguageServerBinaryStatus::None => {}
}
}
@@ -288,6 +289,18 @@ impl ActivityIndicator {
};
}
if let Some(extension_store) =
ExtensionStore::try_global(cx).map(|extension_store| extension_store.read(cx))
{
if let Some(extension_id) = extension_store.outstanding_operations().keys().next() {
return Content {
icon: Some(DOWNLOAD_ICON),
message: format!("Updating {extension_id} extension…"),
on_click: None,
};
}
}
Default::default()
}
}

View File

@@ -0,0 +1,22 @@
[package]
name = "anthropic"
version = "0.1.0"
edition = "2021"
publish = false
license = "AGPL-3.0-or-later"
[lib]
path = "src/anthropic.rs"
[dependencies]
anyhow.workspace = true
futures.workspace = true
serde.workspace = true
serde_json.workspace = true
util.workspace = true
[dev-dependencies]
tokio.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1 @@
../../LICENSE-AGPL

View File

@@ -0,0 +1,234 @@
use anyhow::{anyhow, Result};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use util::http::{AsyncBody, HttpClient, Method, Request as HttpRequest};
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
pub enum Model {
#[default]
#[serde(rename = "claude-3-opus-20240229")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet-20240229")]
Claude3Sonnet,
#[serde(rename = "claude-3-haiku-20240307")]
Claude3Haiku,
}
impl Model {
pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-3-opus") {
Ok(Self::Claude3Opus)
} else if id.starts_with("claude-3-sonnet") {
Ok(Self::Claude3Sonnet)
} else if id.starts_with("claude-3-haiku") {
Ok(Self::Claude3Haiku)
} else {
Err(anyhow!("Invalid model id: {}", id))
}
}
pub fn display_name(&self) -> &'static str {
match self {
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
}
}
pub fn max_token_count(&self) -> usize {
200_000
}
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
Assistant,
}
impl TryFrom<String> for Role {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self> {
match value.as_str() {
"user" => Ok(Self::User),
"assistant" => Ok(Self::Assistant),
_ => Err(anyhow!("invalid role '{value}'")),
}
}
}
impl From<Role> for String {
fn from(val: Role) -> Self {
match val {
Role::User => "user".to_owned(),
Role::Assistant => "assistant".to_owned(),
}
}
}
#[derive(Debug, Serialize)]
pub struct Request {
pub model: Model,
pub messages: Vec<RequestMessage>,
pub stream: bool,
pub system: String,
pub max_tokens: u32,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct RequestMessage {
pub role: Role,
pub content: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ResponseEvent {
MessageStart {
message: ResponseMessage,
},
ContentBlockStart {
index: u32,
content_block: ContentBlock,
},
Ping {},
ContentBlockDelta {
index: u32,
delta: TextDelta,
},
ContentBlockStop {
index: u32,
},
MessageDelta {
delta: ResponseMessage,
usage: Usage,
},
MessageStop {},
}
#[derive(Deserialize, Debug)]
pub struct ResponseMessage {
#[serde(rename = "type")]
pub message_type: Option<String>,
pub id: Option<String>,
pub role: Option<String>,
pub content: Option<Vec<String>>,
pub model: Option<String>,
pub stop_reason: Option<String>,
pub stop_sequence: Option<String>,
pub usage: Option<Usage>,
}
#[derive(Deserialize, Debug)]
pub struct Usage {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text { text: String },
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TextDelta {
TextDelta { text: String },
}
pub async fn stream_completion(
client: &dyn HttpClient,
api_url: &str,
api_key: &str,
request: Request,
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
let uri = format!("{api_url}/v1/messages");
let request = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", "messages-2023-12-15")
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json")
.body(AsyncBody::from(serde_json::to_string(&request)?))?;
let mut response = client.send(request).await?;
if response.status().is_success() {
let reader = BufReader::new(response.into_body());
Ok(reader
.lines()
.filter_map(|line| async move {
match line {
Ok(line) => {
let line = line.strip_prefix("data: ")?;
match serde_json::from_str(line) {
Ok(response) => Some(Ok(response)),
Err(error) => Some(Err(anyhow!(error))),
}
}
Err(error) => Some(Err(anyhow!(error))),
}
})
.boxed())
} else {
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
let body_str = std::str::from_utf8(&body)?;
match serde_json::from_str::<ResponseEvent>(body_str) {
Ok(_) => Err(anyhow!(
"Unexpected success response while expecting an error: {}",
body_str,
)),
Err(_) => Err(anyhow!(
"Failed to connect to API: {} {}",
response.status(),
body_str,
)),
}
}
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use util::http::IsahcHttpClient;
// #[tokio::test]
// async fn stream_completion_success() {
// let http_client = IsahcHttpClient::new().unwrap();
// let request = Request {
// model: Model::Claude3Opus,
// messages: vec![RequestMessage {
// role: Role::User,
// content: "Ping".to_string(),
// }],
// stream: true,
// system: "Respond to ping with pong".to_string(),
// max_tokens: 4096,
// };
// let stream = stream_completion(
// &http_client,
// "https://api.anthropic.com",
// &std::env::var("ANTHROPIC_API_KEY").expect("ANTHROPIC_API_KEY not set"),
// request,
// )
// .await
// .unwrap();
// stream
// .for_each(|event| async {
// match event {
// Ok(event) => println!("{:?}", event),
// Err(e) => eprintln!("Error: {:?}", e),
// }
// })
// .await;
// }
// }

View File

@@ -14,7 +14,9 @@ anyhow.workspace = true
chrono.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
file_icons.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true

View File

@@ -6,15 +6,18 @@ mod prompts;
mod saved_conversation;
mod streaming_diff;
mod embedded_scope;
pub use assistant_panel::AssistantPanel;
use assistant_settings::{AssistantSettings, OpenAiModel, ZedDotDevModel};
use chrono::{DateTime, Local};
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub(crate) use completion_provider::*;
use gpui::{actions, AppContext, SharedString};
use gpui::{actions, AppContext, BorrowAppContext, Global, SharedString};
pub(crate) use saved_conversation::*;
use serde::{Deserialize, Serialize};
use settings::Settings;
use settings::{Settings, SettingsStore};
use std::{
fmt::{self, Display},
sync::Arc,
@@ -97,13 +100,8 @@ impl LanguageModel {
pub fn max_token_count(&self) -> usize {
match self {
LanguageModel::OpenAi(model) => tiktoken_rs::model::get_context_size(model.id()),
LanguageModel::ZedDotDev(model) => match model {
ZedDotDevModel::GptThreePointFiveTurbo
| ZedDotDevModel::GptFour
| ZedDotDevModel::GptFourTurbo => tiktoken_rs::model::get_context_size(model.id()),
ZedDotDevModel::Custom(_) => 30720, // TODO: Base this on the selected model.
},
LanguageModel::OpenAi(model) => model.max_token_count(),
LanguageModel::ZedDotDev(model) => model.max_token_count(),
}
}
@@ -187,10 +185,61 @@ enum MessageStatus {
Error(SharedString),
}
/// The state pertaining to the Assistant.
#[derive(Default)]
struct Assistant {
/// Whether the Assistant is enabled.
enabled: bool,
}
impl Global for Assistant {}
impl Assistant {
const NAMESPACE: &'static str = "assistant";
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
if self.enabled == enabled {
return;
}
self.enabled = enabled;
if !enabled {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Self::NAMESPACE);
});
return;
}
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.show_namespace(Self::NAMESPACE);
});
}
}
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.set_global(Assistant::default());
AssistantSettings::register(cx);
completion_provider::init(client, cx);
assistant_panel::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Assistant::NAMESPACE);
});
cx.update_global(|assistant: &mut Assistant, cx: &mut AppContext| {
let settings = AssistantSettings::get_global(cx);
assistant.set_enabled(settings.enabled, cx);
});
cx.observe_global::<SettingsStore>(|cx| {
cx.update_global(|assistant: &mut Assistant, cx: &mut AppContext| {
let settings = AssistantSettings::get_global(cx);
assistant.set_enabled(settings.enabled, cx);
});
})
.detach();
}
#[cfg(test)]

View File

@@ -1,13 +1,14 @@
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings, ZedDotDevModel},
codegen::{self, Codegen, CodegenKind},
embedded_scope::EmbeddedScope,
prompts::generate_content_prompt,
Assist, CompletionProvider, CycleMessageRole, InlineAssist, LanguageModel,
LanguageModelRequest, LanguageModelRequestMessage, MessageId, MessageMetadata, MessageStatus,
NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation,
};
use anyhow::Result;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Local};
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
@@ -16,9 +17,10 @@ use editor::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint,
},
scroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBufferSnapshot, ToOffset as _,
ToPoint,
Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, MultiBufferSnapshot,
ToOffset as _, ToPoint,
};
use file_icons::FileIcons;
use fs::Fs;
use futures::StreamExt;
use gpui::{
@@ -47,7 +49,7 @@ use uuid::Uuid;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
searchable::Direction,
Save, Toast, ToggleZoom, Toolbar, Workspace,
Event as WorkspaceEvent, Save, Toast, ToggleZoom, Toolbar, Workspace,
};
pub fn init(cx: &mut AppContext) {
@@ -55,6 +57,11 @@ pub fn init(cx: &mut AppContext) {
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace
.register_action(|workspace, _: &ToggleFocus, cx| {
let settings = AssistantSettings::get_global(cx);
if !settings.enabled {
return;
}
workspace.toggle_panel_focus::<AssistantPanel>(cx);
})
.register_action(AssistantPanel::inline_assist)
@@ -155,6 +162,11 @@ impl AssistantPanel {
];
let model = CompletionProvider::global(cx).default_model();
cx.observe_global::<FileIcons>(|_, cx| {
cx.notify();
})
.detach();
Self {
workspace: workspace_handle,
active_conversation_editor: None,
@@ -229,6 +241,11 @@ impl AssistantPanel {
_: &InlineAssist,
cx: &mut ViewContext<Workspace>,
) {
let settings = AssistantSettings::get_global(cx);
if !settings.enabled {
return;
}
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
@@ -328,7 +345,7 @@ impl AssistantPanel {
style: BlockStyle::Flex,
position: snapshot.anchor_before(point_selection.head()),
height: 2,
render: Arc::new({
render: Box::new({
let inline_assistant = inline_assistant.clone();
move |cx: &mut BlockContext| {
*measurements.lock() = BlockMeasurements {
@@ -678,7 +695,7 @@ impl AssistantPanel {
editor.clear_background_highlights::<PendingInlineAssist>(cx);
} else {
editor.highlight_background::<PendingInlineAssist>(
background_ranges,
&background_ranges,
|theme| theme.editor_active_line_background, // todo!("use the appropriate color")
cx,
);
@@ -699,18 +716,20 @@ impl AssistantPanel {
});
}
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ConversationEditor>> {
let workspace = self.workspace.upgrade()?;
let editor = cx.new_view(|cx| {
ConversationEditor::new(
self.model.clone(),
self.languages.clone(),
self.fs.clone(),
self.workspace.clone(),
workspace,
cx,
)
});
self.show_conversation(editor.clone(), cx);
editor
Some(editor)
}
fn show_conversation(
@@ -749,15 +768,18 @@ impl AssistantPanel {
open_ai::Model::FourTurbo => open_ai::Model::ThreePointFiveTurbo,
}),
LanguageModel::ZedDotDev(model) => LanguageModel::ZedDotDev(match &model {
ZedDotDevModel::GptThreePointFiveTurbo => ZedDotDevModel::GptFour,
ZedDotDevModel::GptFour => ZedDotDevModel::GptFourTurbo,
ZedDotDevModel::GptFourTurbo => {
ZedDotDevModel::Gpt3Point5Turbo => ZedDotDevModel::Gpt4,
ZedDotDevModel::Gpt4 => ZedDotDevModel::Gpt4Turbo,
ZedDotDevModel::Gpt4Turbo => ZedDotDevModel::Claude3Opus,
ZedDotDevModel::Claude3Opus => ZedDotDevModel::Claude3Sonnet,
ZedDotDevModel::Claude3Sonnet => ZedDotDevModel::Claude3Haiku,
ZedDotDevModel::Claude3Haiku => {
match CompletionProvider::global(cx).default_model() {
LanguageModel::ZedDotDev(custom) => custom,
_ => ZedDotDevModel::GptThreePointFiveTurbo,
_ => ZedDotDevModel::Gpt3Point5Turbo,
}
}
ZedDotDevModel::Custom(_) => ZedDotDevModel::GptThreePointFiveTurbo,
ZedDotDevModel::Custom(_) => ZedDotDevModel::Gpt3Point5Turbo,
}),
};
@@ -979,11 +1001,15 @@ impl AssistantPanel {
.await?;
this.update(&mut cx, |this, cx| {
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace dropped"))?;
let editor = cx.new_view(|cx| {
ConversationEditor::for_conversation(conversation, fs, workspace, cx)
});
this.show_conversation(editor, cx);
})?;
anyhow::Ok(())
})??;
Ok(())
})
}
@@ -1217,7 +1243,12 @@ impl Panel for AssistantPanel {
}
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button)
let settings = AssistantSettings::get_global(cx);
if !settings.enabled || !settings.button {
return None;
}
Some(IconName::Ai)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -1249,9 +1280,10 @@ struct Summary {
done: bool,
}
struct Conversation {
pub struct Conversation {
id: Option<String>,
buffer: Model<Buffer>,
embedded_scope: EmbeddedScope,
message_anchors: Vec<MessageAnchor>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
next_message_id: MessageId,
@@ -1273,6 +1305,7 @@ impl Conversation {
fn new(
model: LanguageModel,
language_registry: Arc<LanguageRegistry>,
embedded_scope: EmbeddedScope,
cx: &mut ModelContext<Self>,
) -> Self {
let markdown = language_registry.language_for_name("Markdown");
@@ -1306,7 +1339,9 @@ impl Conversation {
pending_save: Task::ready(Ok(())),
path: None,
buffer,
embedded_scope,
};
let message = MessageAnchor {
id: MessageId(post_inc(&mut this.next_message_id.0)),
start: language::Anchor::MIN,
@@ -1407,6 +1442,7 @@ impl Conversation {
pending_save: Task::ready(Ok(())),
path: Some(path),
buffer,
embedded_scope: EmbeddedScope::new(),
};
this.count_remaining_tokens(cx);
this
@@ -1425,7 +1461,7 @@ impl Conversation {
}
}
fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
let request = self.to_completion_request(cx);
self.pending_token_count = cx.spawn(|this, mut cx| {
async move {
@@ -1588,7 +1624,7 @@ impl Conversation {
}
fn to_completion_request(&self, cx: &mut ModelContext<Conversation>) -> LanguageModelRequest {
let request = LanguageModelRequest {
let mut request = LanguageModelRequest {
model: self.model.clone(),
messages: self
.messages(cx)
@@ -1598,6 +1634,9 @@ impl Conversation {
stop: vec![],
temperature: 1.0,
};
let context_message = self.embedded_scope.message(cx);
request.messages.extend(context_message);
request
}
@@ -1987,17 +2026,18 @@ impl ConversationEditor {
model: LanguageModel,
language_registry: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
workspace: View<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
let conversation = cx.new_model(|cx| Conversation::new(model, language_registry, cx));
let conversation = cx
.new_model(|cx| Conversation::new(model, language_registry, EmbeddedScope::new(), cx));
Self::for_conversation(conversation, fs, workspace, cx)
}
fn for_conversation(
conversation: Model<Conversation>,
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
workspace: View<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
let editor = cx.new_view(|cx| {
@@ -2012,6 +2052,7 @@ impl ConversationEditor {
cx.observe(&conversation, |_, _, cx| cx.notify()),
cx.subscribe(&conversation, Self::handle_conversation_event),
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(&workspace, Self::handle_workspace_event),
];
let mut this = Self {
@@ -2020,9 +2061,10 @@ impl ConversationEditor {
blocks: Default::default(),
scroll_position: None,
fs,
workspace,
workspace: workspace.downgrade(),
_subscriptions,
};
this.update_active_buffer(workspace, cx);
this.update_message_headers(cx);
this
}
@@ -2156,6 +2198,37 @@ impl ConversationEditor {
}
}
fn handle_workspace_event(
&mut self,
workspace: View<Workspace>,
event: &WorkspaceEvent,
cx: &mut ViewContext<Self>,
) {
if let WorkspaceEvent::ActiveItemChanged = event {
self.update_active_buffer(workspace, cx);
}
}
fn update_active_buffer(
&mut self,
workspace: View<Workspace>,
cx: &mut ViewContext<'_, ConversationEditor>,
) {
let active_buffer = workspace
.read(cx)
.active_item(cx)
.and_then(|item| Some(item.act_as::<Editor>(cx)?.read(cx).buffer().clone()));
self.conversation.update(cx, |conversation, cx| {
conversation
.embedded_scope
.set_active_buffer(active_buffer.clone(), cx);
conversation.count_remaining_tokens(cx);
cx.notify();
});
}
fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
self.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
@@ -2193,7 +2266,7 @@ impl ConversationEditor {
.unwrap(),
height: 2,
style: BlockStyle::Sticky,
render: Arc::new({
render: Box::new({
let conversation = self.conversation.clone();
move |_cx| {
let message_id = message.id;
@@ -2289,11 +2362,11 @@ impl ConversationEditor {
let start_language = buffer.language_at(range.start);
let end_language = buffer.language_at(range.end);
let language_name = if start_language == end_language {
start_language.map(|language| language.name())
start_language.map(|language| language.code_fence_block_name())
} else {
None
};
let language_name = language_name.as_deref().unwrap_or("").to_lowercase();
let language_name = language_name.as_deref().unwrap_or("");
let selected_text = buffer.text_for_range(range).collect::<String>();
let text = if selected_text.is_empty() {
@@ -2317,15 +2390,17 @@ impl ConversationEditor {
if let Some(text) = text {
panel.update(cx, |panel, cx| {
let conversation = panel
if let Some(conversation) = panel
.active_conversation_editor()
.cloned()
.unwrap_or_else(|| panel.new_conversation(cx));
conversation.update(cx, |conversation, cx| {
conversation
.editor
.update(cx, |editor, cx| editor.insert(&text, cx))
});
.or_else(|| panel.new_conversation(cx))
{
conversation.update(cx, |conversation, cx| {
conversation
.editor
.update(cx, |editor, cx| editor.insert(&text, cx))
});
};
});
}
}
@@ -2390,12 +2465,120 @@ impl ConversationEditor {
.map(|summary| summary.text.clone())
.unwrap_or_else(|| "New Conversation".into())
}
fn render_embedded_scope(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
let active_buffer = self
.conversation
.read(cx)
.embedded_scope
.active_buffer()?
.clone();
Some(
div()
.p_4()
.v_flex()
.child(
div()
.h_flex()
.items_center()
.child(Icon::new(IconName::File))
.child(
div()
.h_6()
.child(Label::new("File Contexts"))
.ml_1()
.font_weight(FontWeight::SEMIBOLD),
),
)
.child(
div()
.ml_4()
.child(self.render_active_buffer(active_buffer, cx)),
),
)
}
fn render_active_buffer(
&self,
buffer: Model<MultiBuffer>,
cx: &mut ViewContext<Self>,
) -> impl Element {
let buffer = buffer.read(cx);
let icon_path;
let path;
if let Some(singleton) = buffer.as_singleton() {
let singleton = singleton.read(cx);
path = singleton.file().map(|file| file.full_path(cx));
icon_path = path
.as_ref()
.and_then(|path| FileIcons::get_icon(path.as_path(), cx))
.map(SharedString::from)
.unwrap_or_else(|| SharedString::from("icons/file_icons/file.svg"));
} else {
icon_path = SharedString::from("icons/file_icons/file.svg");
path = None;
}
let file_name = path.map_or("Untitled".to_string(), |path| {
path.to_string_lossy().to_string()
});
let enabled = self
.conversation
.read(cx)
.embedded_scope
.active_buffer_enabled();
let file_name_text_color = if enabled {
Color::Default
} else {
Color::Disabled
};
div()
.id("active-buffer")
.h_flex()
.cursor_pointer()
.child(Icon::from_path(icon_path).color(file_name_text_color))
.child(
div()
.h_6()
.child(Label::new(file_name).color(file_name_text_color))
.ml_1(),
)
.children(enabled.then(|| {
div()
.child(Icon::new(IconName::Check).color(file_name_text_color))
.ml_1()
}))
.on_click(cx.listener(move |this, _, cx| {
this.conversation.update(cx, |conversation, cx| {
conversation
.embedded_scope
.set_active_buffer_enabled(!enabled);
cx.notify();
})
}))
}
}
impl EventEmitter<ConversationEditorEvent> for ConversationEditor {}
impl Render for ConversationEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
//
// The ConversationEditor has two main segments
//
// 1. Messages Editor
// 2. Context
// - File Context (currently only the active file)
// - Project Diagnostics (Planned)
// - Deep Code Context (Planned, for query and other tools for the model)
//
div()
.key_context("ConversationEditor")
.capture_action(cx.listener(ConversationEditor::cancel_last_assist))
@@ -2405,14 +2588,15 @@ impl Render for ConversationEditor {
.on_action(cx.listener(ConversationEditor::assist))
.on_action(cx.listener(ConversationEditor::split))
.size_full()
.relative()
.v_flex()
.child(
div()
.size_full()
.flex_grow()
.pl_4()
.bg(cx.theme().colors().editor_background)
.child(self.editor.clone()),
)
.child(div().flex_shrink().children(self.render_embedded_scope(cx)))
}
}
@@ -2784,8 +2968,9 @@ mod tests {
init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let conversation =
cx.new_model(|cx| Conversation::new(LanguageModel::default(), registry, cx));
let conversation = cx.new_model(|cx| {
Conversation::new(LanguageModel::default(), registry, EmbeddedScope::new(), cx)
});
let buffer = conversation.read(cx).buffer.clone();
let message_1 = conversation.read(cx).message_anchors[0].clone();
@@ -2916,8 +3101,9 @@ mod tests {
init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let conversation =
cx.new_model(|cx| Conversation::new(LanguageModel::default(), registry, cx));
let conversation = cx.new_model(|cx| {
Conversation::new(LanguageModel::default(), registry, EmbeddedScope::new(), cx)
});
let buffer = conversation.read(cx).buffer.clone();
let message_1 = conversation.read(cx).message_anchors[0].clone();
@@ -3015,8 +3201,9 @@ mod tests {
cx.set_global(settings_store);
init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let conversation =
cx.new_model(|cx| Conversation::new(LanguageModel::default(), registry, cx));
let conversation = cx.new_model(|cx| {
Conversation::new(LanguageModel::default(), registry, EmbeddedScope::new(), cx)
});
let buffer = conversation.read(cx).buffer.clone();
let message_1 = conversation.read(cx).message_anchors[0].clone();
@@ -3100,8 +3287,14 @@ mod tests {
cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
cx.update(init);
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let conversation =
cx.new_model(|cx| Conversation::new(LanguageModel::default(), registry.clone(), cx));
let conversation = cx.new_model(|cx| {
Conversation::new(
LanguageModel::default(),
registry.clone(),
EmbeddedScope::new(),
cx,
)
});
let buffer = conversation.read_with(cx, |conversation, _| conversation.buffer.clone());
let message_0 =
conversation.read_with(cx, |conversation, _| conversation.message_anchors[0].id);

View File

@@ -10,14 +10,17 @@ use serde::{
de::{self, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use settings::Settings;
use settings::{Settings, SettingsSources};
#[derive(Clone, Debug, Default, PartialEq)]
pub enum ZedDotDevModel {
GptThreePointFiveTurbo,
GptFour,
Gpt3Point5Turbo,
Gpt4,
#[default]
GptFourTurbo,
Gpt4Turbo,
Claude3Opus,
Claude3Sonnet,
Claude3Haiku,
Custom(String),
}
@@ -49,9 +52,9 @@ impl<'de> Deserialize<'de> for ZedDotDevModel {
E: de::Error,
{
match value {
"gpt-3.5-turbo" => Ok(ZedDotDevModel::GptThreePointFiveTurbo),
"gpt-4" => Ok(ZedDotDevModel::GptFour),
"gpt-4-turbo-preview" => Ok(ZedDotDevModel::GptFourTurbo),
"gpt-3.5-turbo" => Ok(ZedDotDevModel::Gpt3Point5Turbo),
"gpt-4" => Ok(ZedDotDevModel::Gpt4),
"gpt-4-turbo-preview" => Ok(ZedDotDevModel::Gpt4Turbo),
_ => Ok(ZedDotDevModel::Custom(value.to_owned())),
}
}
@@ -94,21 +97,37 @@ impl JsonSchema for ZedDotDevModel {
impl ZedDotDevModel {
pub fn id(&self) -> &str {
match self {
Self::GptThreePointFiveTurbo => "gpt-3.5-turbo",
Self::GptFour => "gpt-4",
Self::GptFourTurbo => "gpt-4-turbo-preview",
Self::Gpt3Point5Turbo => "gpt-3.5-turbo",
Self::Gpt4 => "gpt-4",
Self::Gpt4Turbo => "gpt-4-turbo-preview",
Self::Claude3Opus => "claude-3-opus",
Self::Claude3Sonnet => "claude-3-sonnet",
Self::Claude3Haiku => "claude-3-haiku",
Self::Custom(id) => id,
}
}
pub fn display_name(&self) -> &str {
match self {
Self::GptThreePointFiveTurbo => "gpt-3.5-turbo",
Self::GptFour => "gpt-4",
Self::GptFourTurbo => "gpt-4-turbo",
Self::Gpt3Point5Turbo => "GPT 3.5 Turbo",
Self::Gpt4 => "GPT 4",
Self::Gpt4Turbo => "GPT 4 Turbo",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
Self::Custom(id) => id.as_str(),
}
}
pub fn max_token_count(&self) -> usize {
match self {
Self::Gpt3Point5Turbo => 2048,
Self::Gpt4 => 4096,
Self::Gpt4Turbo => 128000,
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 200000,
Self::Custom(_) => 4096, // TODO: Make this configurable
}
}
}
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
@@ -151,6 +170,7 @@ fn open_ai_url() -> String {
#[derive(Default, Debug, Deserialize, Serialize)]
pub struct AssistantSettings {
pub enabled: bool,
pub button: bool,
pub dock: AssistantDockPosition,
pub default_width: Pixels,
@@ -192,42 +212,26 @@ impl AssistantSettingsContent {
AssistantSettingsContent::Versioned(settings) => match settings {
VersionedAssistantSettingsContent::V1(settings) => settings.clone(),
},
AssistantSettingsContent::Legacy(settings) => {
if let Some(open_ai_api_url) = settings.openai_api_url.as_ref() {
AssistantSettingsContentV1 {
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
default_height: settings.default_height,
provider: Some(AssistantProvider::OpenAi {
default_model: settings
.default_open_ai_model
.clone()
.unwrap_or_default(),
api_url: open_ai_api_url.clone(),
}),
}
} else if let Some(open_ai_model) = settings.default_open_ai_model.clone() {
AssistantSettingsContentV1 {
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
default_height: settings.default_height,
provider: Some(AssistantProvider::OpenAi {
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV1 {
enabled: None,
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
default_height: settings.default_height,
provider: if let Some(open_ai_api_url) = settings.openai_api_url.as_ref() {
Some(AssistantProvider::OpenAi {
default_model: settings.default_open_ai_model.clone().unwrap_or_default(),
api_url: open_ai_api_url.clone(),
})
} else {
settings.default_open_ai_model.clone().map(|open_ai_model| {
AssistantProvider::OpenAi {
default_model: open_ai_model,
api_url: open_ai_url(),
}),
}
} else {
AssistantSettingsContentV1 {
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
default_height: settings.default_height,
provider: None,
}
}
}
}
})
},
},
}
}
@@ -255,6 +259,7 @@ pub enum VersionedAssistantSettingsContent {
impl Default for VersionedAssistantSettingsContent {
fn default() -> Self {
Self::V1(AssistantSettingsContentV1 {
enabled: None,
button: None,
dock: None,
default_width: None,
@@ -266,6 +271,10 @@ impl Default for VersionedAssistantSettingsContent {
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
pub struct AssistantSettingsContentV1 {
/// Whether the Assistant is enabled.
///
/// Default: true
enabled: Option<bool>,
/// Whether to show the assistant panel button in the status bar.
///
/// Default: true
@@ -323,14 +332,14 @@ impl Settings for AssistantSettings {
type FileContent = AssistantSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
let mut settings = AssistantSettings::default();
for value in [default_value].iter().chain(user_values) {
for value in sources.defaults_and_customizations() {
let value = value.upgrade();
merge(&mut settings.enabled, value.enabled);
merge(&mut settings.button, value.button);
merge(&mut settings.dock, value.dock);
merge(
@@ -383,7 +392,7 @@ fn merge<T: Copy>(target: &mut T, value: Option<T>) {
#[cfg(test)]
mod tests {
use gpui::AppContext;
use gpui::{AppContext, BorrowAppContext};
use settings::SettingsStore;
use super::*;

View File

@@ -15,7 +15,7 @@ use crate::{
use anyhow::Result;
use client::Client;
use futures::{future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, AppContext, Task, WindowContext};
use gpui::{AnyView, AppContext, BorrowAppContext, Task, WindowContext};
use settings::{Settings, SettingsStore};
use std::sync::Arc;

View File

@@ -1,5 +1,5 @@
use crate::{
assistant_settings::ZedDotDevModel, count_open_ai_tokens, CompletionProvider,
assistant_settings::ZedDotDevModel, count_open_ai_tokens, CompletionProvider, LanguageModel,
LanguageModelRequest,
};
use anyhow::{anyhow, Result};
@@ -78,13 +78,21 @@ impl ZedDotDevCompletionProvider {
cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
match request.model {
crate::LanguageModel::OpenAi(_) => future::ready(Err(anyhow!("invalid model"))).boxed(),
crate::LanguageModel::ZedDotDev(ZedDotDevModel::GptFour)
| crate::LanguageModel::ZedDotDev(ZedDotDevModel::GptFourTurbo)
| crate::LanguageModel::ZedDotDev(ZedDotDevModel::GptThreePointFiveTurbo) => {
LanguageModel::OpenAi(_) => future::ready(Err(anyhow!("invalid model"))).boxed(),
LanguageModel::ZedDotDev(ZedDotDevModel::Gpt4)
| LanguageModel::ZedDotDev(ZedDotDevModel::Gpt4Turbo)
| LanguageModel::ZedDotDev(ZedDotDevModel::Gpt3Point5Turbo) => {
count_open_ai_tokens(request, cx.background_executor())
}
crate::LanguageModel::ZedDotDev(ZedDotDevModel::Custom(model)) => {
LanguageModel::ZedDotDev(
ZedDotDevModel::Claude3Opus
| ZedDotDevModel::Claude3Sonnet
| ZedDotDevModel::Claude3Haiku,
) => {
// Can't find a tokenizer for Claude 3, so for now just use the same as OpenAI's as an approximation.
count_open_ai_tokens(request, cx.background_executor())
}
LanguageModel::ZedDotDev(ZedDotDevModel::Custom(model)) => {
let request = self.client.request(proto::CountTokensWithLanguageModel {
model,
messages: request

View File

@@ -0,0 +1,91 @@
use editor::MultiBuffer;
use gpui::{AppContext, Model, ModelContext, Subscription};
use crate::{assistant_panel::Conversation, LanguageModelRequestMessage, Role};
#[derive(Default)]
pub struct EmbeddedScope {
active_buffer: Option<Model<MultiBuffer>>,
active_buffer_enabled: bool,
active_buffer_subscription: Option<Subscription>,
}
impl EmbeddedScope {
pub fn new() -> Self {
Self {
active_buffer: None,
active_buffer_enabled: true,
active_buffer_subscription: None,
}
}
pub fn set_active_buffer(
&mut self,
buffer: Option<Model<MultiBuffer>>,
cx: &mut ModelContext<Conversation>,
) {
self.active_buffer_subscription.take();
if let Some(active_buffer) = buffer.clone() {
self.active_buffer_subscription =
Some(cx.subscribe(&active_buffer, |conversation, _, e, cx| {
if let multi_buffer::Event::Edited { .. } = e {
conversation.count_remaining_tokens(cx)
}
}));
}
self.active_buffer = buffer;
}
pub fn active_buffer(&self) -> Option<&Model<MultiBuffer>> {
self.active_buffer.as_ref()
}
pub fn active_buffer_enabled(&self) -> bool {
self.active_buffer_enabled
}
pub fn set_active_buffer_enabled(&mut self, enabled: bool) {
self.active_buffer_enabled = enabled;
}
/// Provide a message for the language model based on the active buffer.
pub fn message(&self, cx: &AppContext) -> Option<LanguageModelRequestMessage> {
if !self.active_buffer_enabled {
return None;
}
let active_buffer = self.active_buffer.as_ref()?;
let buffer = active_buffer.read(cx);
if let Some(singleton) = buffer.as_singleton() {
let singleton = singleton.read(cx);
let filename = singleton
.file()
.map(|file| file.path().to_string_lossy())
.unwrap_or("Untitled".into());
let text = singleton.text();
let language = singleton
.language()
.map(|l| {
let name = l.code_fence_block_name();
name.to_string()
})
.unwrap_or_default();
let markdown =
format!("User's active file `{filename}`:\n\n```{language}\n{text}```\n\n");
return Some(LanguageModelRequestMessage {
role: Role::System,
content: markdown,
});
}
None
}
}

View File

@@ -1,6 +1,6 @@
use assets::SoundRegistry;
use derive_more::{Deref, DerefMut};
use gpui::{AppContext, AssetSource, Global};
use gpui::{AppContext, AssetSource, BorrowAppContext, Global};
use rodio::{OutputStream, OutputStreamHandle};
use util::ResultExt;

View File

@@ -11,13 +11,13 @@ use gpui::{
};
use isahc::AsyncBody;
use markdown_preview::markdown_preview_view::MarkdownPreviewView;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use smol::io::AsyncReadExt;
use settings::{Settings, SettingsStore};
use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs::File, process::Command};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
@@ -82,25 +82,22 @@ struct AutoUpdateSetting(bool);
/// Whether or not to automatically check for updates.
///
/// Default: true
#[derive(Clone, Default, JsonSchema, Deserialize, Serialize)]
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
#[serde(transparent)]
struct AutoUpdateSettingOverride(Option<bool>);
struct AutoUpdateSettingContent(bool);
impl Settings for AutoUpdateSetting {
const KEY: Option<&'static str> = Some("auto_update");
type FileContent = AutoUpdateSettingOverride;
type FileContent = Option<AutoUpdateSettingContent>;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
Ok(Self(
Self::json_merge(default_value, user_values)?
.0
.ok_or_else(Self::missing_default)?,
))
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
let auto_update = [sources.release_channel, sources.user]
.into_iter()
.find_map(|value| value.copied().flatten())
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
Ok(Self(auto_update.0))
}
}
@@ -238,10 +235,11 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx));
let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
Some(tab_description),
language_registry,
Some(tab_description),
cx,
);
workspace.add_item_to_active_pane(Box::new(view.clone()), cx);

View File

@@ -373,7 +373,10 @@ impl ActiveCall {
self.report_call_event("hang up", cx);
Audio::end_call(cx);
let channel_id = self.channel_id(cx);
if let Some((room, _)) = self.room.take() {
cx.emit(Event::RoomLeft { channel_id });
room.update(cx, |room, cx| room.leave(cx))
} else {
Task::ready(Ok(()))

View File

@@ -2,7 +2,7 @@ use anyhow::Result;
use gpui::AppContext;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::Settings;
use settings::{Settings, SettingsSources};
#[derive(Deserialize, Debug)]
pub struct CallSettings {
@@ -29,14 +29,7 @@ impl Settings for CallSettings {
type FileContent = CallSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_cx: &mut AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}

View File

@@ -52,7 +52,7 @@ pub enum Event {
RemoteProjectInvitationDiscarded {
project_id: u64,
},
Left {
RoomLeft {
channel_id: Option<ChannelId>,
},
}
@@ -366,9 +366,6 @@ impl Room {
pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
cx.emit(Event::Left {
channel_id: self.channel_id(),
});
self.leave_internal(cx)
}

View File

@@ -222,6 +222,9 @@ impl ChannelChat {
let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
this.update(&mut cx, |this, cx| {
this.insert_messages(SumTree::from_item(message, &()), cx);
if this.first_loaded_message_id.is_none() {
this.first_loaded_message_id = Some(id);
}
})?;
Ok(id)
}))

View File

@@ -17,7 +17,7 @@ use rpc::{
};
use settings::Settings;
use std::{mem, sync::Arc, time::Duration};
use util::{async_maybe, maybe, ResultExt};
use util::{maybe, ResultExt};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
@@ -227,7 +227,7 @@ impl ChannelStore {
_watch_connection_status: watch_connection_status,
disconnect_channel_buffers_task: None,
_update_channels: cx.spawn(|this, mut cx| async move {
async_maybe!({
maybe!(async move {
while let Some(update_channels) = update_channels_rx.next().await {
if let Some(this) = this.upgrade() {
let update_task = this.update(&mut cx, |this, cx| {

View File

@@ -1,4 +1,4 @@
#![cfg_attr(target_os = "linux", allow(dead_code))]
#![cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
use anyhow::{anyhow, Context, Result};
use clap::Parser;

View File

@@ -17,7 +17,8 @@ use futures::{
TryFutureExt as _, TryStreamExt,
};
use gpui::{
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, BorrowAppContext, Global, Model,
Task, WeakModel,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
@@ -27,8 +28,8 @@ use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use settings::{Settings, SettingsSources, SettingsStore};
use std::fmt;
use std::{
any::TypeId,
convert::TryFrom,
@@ -52,6 +53,15 @@ pub use rpc::*;
pub use telemetry_events::Event;
pub use user::*;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DevServerToken(pub String);
impl fmt::Display for DevServerToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
lazy_static! {
static ref ZED_SERVER_URL: Option<String> = std::env::var("ZED_SERVER_URL").ok();
static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
@@ -87,15 +97,8 @@ impl Settings for ClientSettings {
type FileContent = ClientSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self>
where
Self: Sized,
{
let mut result = Self::load_via_json_merge(default_value, user_values)?;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
let mut result = sources.json_merge::<Self>()?;
if let Some(server_url) = &*ZED_SERVER_URL {
result.server_url = server_url.clone()
}
@@ -277,10 +280,22 @@ enum WeakSubscriber {
Pending(Vec<Box<dyn AnyTypedEnvelope>>),
}
#[derive(Clone, Debug)]
pub struct Credentials {
pub user_id: u64,
pub access_token: String,
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Credentials {
DevServer { token: DevServerToken },
User { user_id: u64, access_token: String },
}
impl Credentials {
pub fn authorization_header(&self) -> String {
match self {
Credentials::DevServer { token } => format!("dev-server-token {}", token),
Credentials::User {
user_id,
access_token,
} => format!("{} {}", user_id, access_token),
}
}
}
impl Default for ClientState {
@@ -405,21 +420,19 @@ impl settings::Settings for TelemetrySettings {
type FileContent = TelemetrySettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
Ok(Self {
diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or(
default_value
diagnostics: sources.user.as_ref().and_then(|v| v.diagnostics).unwrap_or(
sources
.default
.diagnostics
.ok_or_else(Self::missing_default)?,
),
metrics: user_values
.first()
metrics: sources
.user
.as_ref()
.and_then(|v| v.metrics)
.unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?),
.unwrap_or(sources.default.metrics.ok_or_else(Self::missing_default)?),
})
}
}
@@ -497,11 +510,11 @@ impl Client {
}
pub fn user_id(&self) -> Option<u64> {
self.state
.read()
.credentials
.as_ref()
.map(|credentials| credentials.user_id)
if let Some(Credentials::User { user_id, .. }) = self.state.read().credentials.as_ref() {
Some(*user_id)
} else {
None
}
}
pub fn peer_id(&self) -> Option<PeerId> {
@@ -746,6 +759,10 @@ impl Client {
read_credentials_from_keychain(cx).await.is_some()
}
pub fn set_dev_server_token(&self, token: DevServerToken) {
self.state.write().credentials = Some(Credentials::DevServer { token });
}
#[async_recursion(?Send)]
pub async fn authenticate_and_connect(
self: &Arc<Self>,
@@ -764,7 +781,6 @@ impl Client {
}
Status::UpgradeRequired => return Err(EstablishConnectionError::UpgradeRequired)?,
};
if was_disconnected {
self.set_status(Status::Authenticating, cx);
} else {
@@ -796,7 +812,9 @@ impl Client {
}
}
let credentials = credentials.unwrap();
self.set_id(credentials.user_id);
if let Credentials::User { user_id, .. } = &credentials {
self.set_id(*user_id);
}
if was_disconnected {
self.set_status(Status::Connecting, cx);
@@ -812,7 +830,9 @@ impl Client {
Ok(conn) => {
self.state.write().credentials = Some(credentials.clone());
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
write_credentials_to_keychain(credentials, cx).await.log_err();
if let Credentials::User{user_id, access_token} = credentials {
write_credentials_to_keychain(user_id, access_token, cx).await.log_err();
}
}
futures::select_biased! {
@@ -1020,10 +1040,7 @@ impl Client {
.unwrap_or_default();
let request = Request::builder()
.header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
.header("Authorization", credentials.authorization_header())
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION)
.header("x-zed-app-version", app_version)
.header(
@@ -1176,7 +1193,7 @@ impl Client {
.decrypt_string(&access_token)
.context("failed to decrypt access token")?;
Ok(Credentials {
Ok(Credentials::User {
user_id: user_id.parse()?,
access_token,
})
@@ -1226,7 +1243,7 @@ impl Client {
// Use the admin API token to authenticate as the impersonated user.
api_token.insert_str(0, "ADMIN_TOKEN:");
Ok(Credentials {
Ok(Credentials::User {
user_id: response.user.id,
access_token: api_token,
})
@@ -1439,21 +1456,22 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti
.await
.log_err()??;
Some(Credentials {
Some(Credentials::User {
user_id: user_id.parse().ok()?,
access_token: String::from_utf8(access_token).ok()?,
})
}
async fn write_credentials_to_keychain(
credentials: Credentials,
user_id: u64,
access_token: String,
cx: &AsyncAppContext,
) -> Result<()> {
cx.update(move |cx| {
cx.write_credentials(
&ClientSettings::get_global(cx).server_url,
&credentials.user_id.to_string(),
credentials.access_token.as_bytes(),
&user_id.to_string(),
access_token.as_bytes(),
)
})?
.await
@@ -1558,7 +1576,7 @@ mod tests {
// Time out when client tries to connect.
client.override_authenticate(move |cx| {
cx.background_executor().spawn(async move {
Ok(Credentials {
Ok(Credentials::User {
user_id,
access_token: "token".into(),
})

View File

@@ -15,7 +15,8 @@ use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CopilotEvent, CpuEvent,
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, MemoryEvent,
SettingEvent,
};
use tempfile::NamedTempFile;
use util::http::{self, HttpClient, HttpClientWithUrl, Method};
@@ -326,6 +327,13 @@ impl Telemetry {
self.report_event(event)
}
pub fn report_extension_event(self: &Arc<Self>, extension_id: Arc<str>, version: Arc<str>) {
self.report_event(Event::Extension(ExtensionEvent {
extension_id,
version,
}))
}
pub fn log_edit_event(self: &Arc<Self>, environment: &'static str) {
let mut state = self.state.lock();
let period_data = state.event_coalescer.log_event(environment);
@@ -470,7 +478,11 @@ impl Telemetry {
let request = http::Request::builder()
.method(Method::POST)
.uri(this.http_client.build_zed_api_url("/telemetry/events"))
.uri(
this.http_client
.build_zed_api_url("/telemetry/events", &[])?
.as_ref(),
)
.header("Content-Type", "text/plain")
.header("x-zed-checksum", checksum)
.body(json_bytes.into());
@@ -578,7 +590,10 @@ mod tests {
}
#[gpui::test]
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
async fn test_telemetry_flush_on_flush_interval(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),

View File

@@ -48,7 +48,7 @@ impl FakeServer {
let mut state = state.lock();
state.auth_count += 1;
let access_token = state.access_token.to_string();
Ok(Credentials {
Ok(Credentials::User {
user_id: client_user_id,
access_token,
})
@@ -71,9 +71,12 @@ impl FakeServer {
)))?
}
assert_eq!(credentials.user_id, client_user_id);
if credentials.access_token != state.lock().access_token.to_string() {
if credentials
!= (Credentials::User {
user_id: client_user_id,
access_token: state.lock().access_token.to_string(),
})
{
Err(EstablishConnectionError::Unauthorized)?
}

View File

@@ -1,8 +0,0 @@
[
"nathansobo",
"as-cii",
"maxbrunsfeld",
"iamnbutler",
"mikayla-maki",
"JosephTLyons"
]

View File

@@ -1,4 +1,5 @@
DATABASE_URL = "postgres://postgres@localhost/zed"
# DATABASE_URL = "sqlite:////home/zed/.config/zed/db.sqlite3?mode=rwc"
DATABASE_MAX_CONNECTIONS = 5
HTTP_PORT = 8080
API_TOKEN = "secret"
@@ -13,6 +14,7 @@ BLOB_STORE_BUCKET = "the-extensions-bucket"
BLOB_STORE_URL = "http://127.0.0.1:9000"
BLOB_STORE_REGION = "the-region"
ZED_CLIENT_CHECKSUM_SEED = "development-checksum-seed"
SEED_PATH = "crates/collab/seed.default.json"
# CLICKHOUSE_URL = ""
# CLICKHOUSE_USER = "default"

View File

@@ -13,10 +13,12 @@ workspace = true
[[bin]]
name = "collab"
[[bin]]
name = "seed"
[features]
sqlite = ["sea-orm/sqlx-sqlite", "sqlx/sqlite"]
test-support = ["sqlite"]
[dependencies]
anthropic.workspace = true
anyhow.workspace = true
async-tungstenite = "0.16"
aws-config = { version = "1.1.5" }
@@ -45,6 +47,7 @@ reqwest = { version = "0.11", features = ["json"] }
rpc.workspace = true
scrypt = "0.7"
sea-orm = { version = "0.12.x", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
semantic_version.workspace = true
semver.workspace = true
serde.workspace = true
serde_derive.workspace = true

View File

@@ -6,21 +6,21 @@ 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).
Detailed instructions on getting started are [here](https://zed.dev/docs/local-collaboration).
# Deployment
We run two instances of collab:
* Staging (https://staging-collab.zed.dev)
* Production (https://collab.zed.dev)
- Staging (https://staging-collab.zed.dev)
- Production (https://collab.zed.dev)
Both of these run on the Kubernetes cluster hosted in Digital Ocean.
Deployment is triggered by pushing to the `collab-staging` (or `collab-production`) tag in Github. The best way to do this is:
* `./script/deploy-collab staging`
* `./script/deploy-collab production`
- `./script/deploy-collab staging`
- `./script/deploy-collab production`
You can tell what is currently deployed with `./script/what-is-deployed`.
@@ -29,7 +29,7 @@ You can tell what is currently deployed with `./script/what-is-deployed`.
To create a new migration:
```
./script/sqlx migrate add <name>
./script/create-migration <name>
```
Migrations are run automatically on service start, so run `foreman start` again. The service will crash if the migrations fail.

View File

@@ -1,12 +0,0 @@
[Interface]
PrivateKey = B5Fp/yVfP0QYlb+YJv9ea+EMI1mWODPD3akh91cVjvc=
Address = fdaa:0:2ce3:a7b:bea:0:a:2/120
DNS = fdaa:0:2ce3::3
[Peer]
PublicKey = RKAYPljEJiuaELNDdQIEJmQienT9+LRISfIHwH45HAw=
AllowedIPs = fdaa:0:2ce3::/48
Endpoint = ord1.gateway.6pn.dev:51820
PersistentKeepalive = 15

View File

@@ -47,19 +47,6 @@ spec:
metadata:
labels:
app: ${ZED_SERVICE_NAME}
annotations:
ad.datadoghq.com/collab.check_names: |
["openmetrics"]
ad.datadoghq.com/collab.init_configs: |
[{}]
ad.datadoghq.com/collab.instances: |
[
{
"openmetrics_endpoint": "http://%%host%%:%%port%%/metrics",
"namespace": "collab_${ZED_KUBE_NAMESPACE}",
"metrics": [".*"]
}
]
spec:
containers:
- name: ${ZED_SERVICE_NAME}
@@ -125,6 +112,16 @@ spec:
secretKeyRef:
name: livekit
key: secret
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: openai
key: api_key
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: anthropic
key: api_key
- name: BLOB_STORE_ACCESS_KEY
valueFrom:
secretKeyRef:

View File

@@ -373,6 +373,8 @@ CREATE TABLE extension_versions (
authors TEXT NOT NULL,
repository TEXT NOT NULL,
description TEXT NOT NULL,
schema_version INTEGER NOT NULL DEFAULT 0,
wasm_api_version TEXT,
download_count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (extension_id, version)
);
@@ -399,3 +401,11 @@ CREATE TABLE hosted_projects (
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
CREATE TABLE dev_servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
hashed_token TEXT NOT NULL
);
CREATE INDEX idx_dev_servers_on_channel_id ON dev_servers (channel_id);

View File

@@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE extension_versions ADD COLUMN schema_version INTEGER NOT NULL DEFAULT 0;

View File

@@ -0,0 +1,7 @@
CREATE TABLE dev_servers (
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
channel_id INT NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
hashed_token TEXT NOT NULL
);
CREATE INDEX idx_dev_servers_on_channel_id ON dev_servers (channel_id);

View File

@@ -0,0 +1 @@
ALTER TABLE extension_versions ADD COLUMN wasm_api_version TEXT;

View File

@@ -0,0 +1,12 @@
{
"admins": [
"nathansobo",
"as-cii",
"maxbrunsfeld",
"iamnbutler",
"mikayla-maki",
"JosephTLyons"
],
"channels": ["zed"],
"number_of_users": 100
}

View File

@@ -1,5 +1,5 @@
use std::sync::{Arc, OnceLock};
use super::ips_file::IpsFile;
use crate::{api::slack, AppState, Error, Result};
use anyhow::{anyhow, Context};
use aws_sdk_s3::primitives::ByteStream;
use axum::{
@@ -9,17 +9,15 @@ use axum::{
routing::post,
Extension, Router, TypedHeader,
};
use rpc::ExtensionMetadata;
use semantic_version::SemanticVersion;
use serde::{Serialize, Serializer};
use sha2::{Digest, Sha256};
use std::sync::{Arc, OnceLock};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, CopilotEvent, CpuEvent, EditEvent,
EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, MemoryEvent, SettingEvent,
};
use util::SemanticVersion;
use crate::{api::slack, AppState, Error, Result};
use super::ips_file::IpsFile;
pub fn router() -> Router {
Router::new()
@@ -331,6 +329,21 @@ pub async fn post_events(
&request_body,
first_event_at,
)),
Event::Extension(event) => {
let metadata = app
.db
.get_extension_version(&event.extension_id, &event.version)
.await?;
to_upload
.extension_events
.push(ExtensionEventRow::from_event(
event.clone(),
&wrapper,
&request_body,
metadata,
first_event_at,
))
}
}
}
@@ -352,6 +365,7 @@ struct ToUpload {
memory_events: Vec<MemoryEventRow>,
app_events: Vec<AppEventRow>,
setting_events: Vec<SettingEventRow>,
extension_events: Vec<ExtensionEventRow>,
edit_events: Vec<EditEventRow>,
action_events: Vec<ActionEventRow>,
}
@@ -410,6 +424,15 @@ impl ToUpload {
.await
.with_context(|| format!("failed to upload to table '{SETTING_EVENTS_TABLE}'"))?;
const EXTENSION_EVENTS_TABLE: &str = "extension_events";
Self::upload_to_table(
EXTENSION_EVENTS_TABLE,
&self.extension_events,
clickhouse_client,
)
.await
.with_context(|| format!("failed to upload to table '{EXTENSION_EVENTS_TABLE}'"))?;
const EDIT_EVENTS_TABLE: &str = "edit_events";
Self::upload_to_table(EDIT_EVENTS_TABLE, &self.edit_events, clickhouse_client)
.await
@@ -436,6 +459,12 @@ impl ToUpload {
}
insert.end().await?;
let event_count = rows.len();
log::info!(
"wrote {event_count} {event_specifier} to '{table}'",
event_specifier = if event_count == 1 { "event" } else { "events" }
);
}
Ok(())
@@ -499,9 +528,9 @@ impl EditorEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -561,9 +590,9 @@ impl CopilotEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
@@ -616,9 +645,9 @@ impl CallEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
@@ -665,9 +694,9 @@ impl AssistantEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
@@ -709,9 +738,9 @@ impl CpuEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
@@ -756,9 +785,9 @@ impl MemoryEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
@@ -802,9 +831,9 @@ impl AppEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
@@ -847,9 +876,9 @@ impl SettingEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
@@ -861,6 +890,68 @@ impl SettingEventRow {
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct ExtensionEventRow {
// AppInfoBase
app_version: String,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
release_channel: String,
// ClientEventBase
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
time: i64,
// ExtensionEventRow
extension_id: Arc<str>,
extension_version: Arc<str>,
dev: bool,
schema_version: Option<i32>,
wasm_api_version: Option<String>,
}
impl ExtensionEventRow {
fn from_event(
event: ExtensionEvent,
wrapper: &EventWrapper,
body: &EventRequestBody,
extension_metadata: Option<ExtensionMetadata>,
first_event_at: chrono::DateTime<chrono::Utc>,
) -> Self {
let semver = body.semver();
let time =
first_event_at + chrono::Duration::milliseconds(wrapper.milliseconds_since_first_event);
Self {
app_version: body.app_version.clone(),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
extension_id: event.extension_id,
extension_version: event.version,
dev: extension_metadata.is_none(),
schema_version: extension_metadata
.as_ref()
.and_then(|metadata| metadata.manifest.schema_version),
wasm_api_version: extension_metadata.as_ref().and_then(|metadata| {
metadata
.manifest
.wasm_api_version
.as_ref()
.map(|version| version.to_string())
}),
}
}
}
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditEventRow {
// AppInfoBase
@@ -900,9 +991,9 @@ impl EditEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
@@ -949,9 +1040,9 @@ impl ActionEventRow {
Self {
app_version: body.app_version.clone(),
major: semver.map(|s| s.major as i32),
minor: semver.map(|s| s.minor as i32),
patch: semver.map(|s| s.patch as i32),
major: semver.map(|v| v.major() as i32),
minor: semver.map(|v| v.minor() as i32),
patch: semver.map(|v| v.patch() as i32),
release_channel: body.release_channel.clone().unwrap_or_default(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),

View File

@@ -1,7 +1,5 @@
use crate::{
db::{ExtensionMetadata, NewExtensionVersion},
AppState, Error, Result,
};
use crate::db::ExtensionVersionConstraints;
use crate::{db::NewExtensionVersion, AppState, Error, Result};
use anyhow::{anyhow, Context as _};
use aws_sdk_s3::presigning::PresigningConfig;
use axum::{
@@ -12,14 +10,18 @@ use axum::{
Extension, Json, Router,
};
use collections::HashMap;
use serde::{Deserialize, Serialize};
use rpc::{ExtensionApiManifest, GetExtensionsResponse};
use semantic_version::SemanticVersion;
use serde::Deserialize;
use std::{sync::Arc, time::Duration};
use time::PrimitiveDateTime;
use util::ResultExt;
use util::{maybe, ResultExt};
pub fn router() -> Router {
Router::new()
.route("/extensions", get(get_extensions))
.route("/extensions/updates", get(get_extension_updates))
.route("/extensions/:extension_id", get(get_extension_versions))
.route(
"/extensions/:extension_id/download",
get(download_latest_extension),
@@ -33,60 +35,122 @@ pub fn router() -> Router {
#[derive(Debug, Deserialize)]
struct GetExtensionsParams {
filter: Option<String>,
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionParams {
extension_id: String,
}
#[derive(Debug, Deserialize)]
struct DownloadExtensionParams {
extension_id: String,
version: String,
}
#[derive(Debug, Serialize)]
struct GetExtensionsResponse {
pub data: Vec<ExtensionMetadata>,
}
#[derive(Deserialize)]
struct ExtensionManifest {
name: String,
version: String,
description: Option<String>,
authors: Vec<String>,
repository: String,
#[serde(default)]
ids: Option<String>,
#[serde(default)]
max_schema_version: i32,
}
async fn get_extensions(
Extension(app): Extension<Arc<AppState>>,
Query(params): Query<GetExtensionsParams>,
) -> Result<Json<GetExtensionsResponse>> {
let extensions = app.db.get_extensions(params.filter.as_deref(), 500).await?;
let extension_ids = params
.ids
.as_ref()
.map(|s| s.split(',').map(|s| s.trim()).collect::<Vec<_>>());
let extensions = if let Some(extension_ids) = extension_ids {
app.db.get_extensions_by_ids(&extension_ids, None).await?
} else {
app.db
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
.await?
};
Ok(Json(GetExtensionsResponse { data: extensions }))
}
#[derive(Debug, Deserialize)]
struct GetExtensionUpdatesParams {
ids: String,
min_schema_version: i32,
max_schema_version: i32,
min_wasm_api_version: SemanticVersion,
max_wasm_api_version: SemanticVersion,
}
async fn get_extension_updates(
Extension(app): Extension<Arc<AppState>>,
Query(params): Query<GetExtensionUpdatesParams>,
) -> Result<Json<GetExtensionsResponse>> {
let constraints = ExtensionVersionConstraints {
schema_versions: params.min_schema_version..=params.max_schema_version,
wasm_api_versions: params.min_wasm_api_version..=params.max_wasm_api_version,
};
let extension_ids = params.ids.split(',').map(|s| s.trim()).collect::<Vec<_>>();
let extensions = app
.db
.get_extensions_by_ids(&extension_ids, Some(&constraints))
.await?;
Ok(Json(GetExtensionsResponse { data: extensions }))
}
#[derive(Debug, Deserialize)]
struct GetExtensionVersionsParams {
extension_id: String,
}
async fn get_extension_versions(
Extension(app): Extension<Arc<AppState>>,
Path(params): Path<GetExtensionVersionsParams>,
) -> Result<Json<GetExtensionsResponse>> {
let extension_versions = app.db.get_extension_versions(&params.extension_id).await?;
Ok(Json(GetExtensionsResponse {
data: extension_versions,
}))
}
#[derive(Debug, Deserialize)]
struct DownloadLatestExtensionParams {
extension_id: String,
min_schema_version: Option<i32>,
max_schema_version: Option<i32>,
min_wasm_api_version: Option<SemanticVersion>,
max_wasm_api_version: Option<SemanticVersion>,
}
async fn download_latest_extension(
Extension(app): Extension<Arc<AppState>>,
Path(params): Path<DownloadLatestExtensionParams>,
) -> 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?;
Some(ExtensionVersionConstraints {
schema_versions: min_schema_version..=max_schema_version,
wasm_api_versions: min_wasm_api_version..=max_wasm_api_version,
})
});
let extension = app
.db
.get_extension(&params.extension_id)
.get_extension(&params.extension_id, constraints.as_ref())
.await?
.ok_or_else(|| anyhow!("unknown extension"))?;
download_extension(
Extension(app),
Path(DownloadExtensionParams {
extension_id: params.extension_id,
version: extension.version,
version: extension.manifest.version.to_string(),
}),
)
.await
}
#[derive(Debug, Deserialize)]
struct DownloadExtensionParams {
extension_id: String,
version: String,
}
async fn download_extension(
Extension(app): Extension<Arc<AppState>>,
Path(params): Path<DownloadExtensionParams>,
@@ -267,7 +331,7 @@ async fn fetch_extension_manifest(
})?
.to_vec();
let manifest =
serde_json::from_slice::<ExtensionManifest>(&manifest_bytes).with_context(|| {
serde_json::from_slice::<ExtensionApiManifest>(&manifest_bytes).with_context(|| {
format!(
"invalid manifest for extension {extension_id} version {version}: {}",
String::from_utf8_lossy(&manifest_bytes)
@@ -287,6 +351,8 @@ async fn fetch_extension_manifest(
description: manifest.description.unwrap_or_default(),
authors: manifest.authors,
repository: manifest.repository,
schema_version: manifest.schema_version.unwrap_or(0),
wasm_api_version: manifest.wasm_api_version,
published_at,
})
}

View File

@@ -1,9 +1,8 @@
use collections::HashMap;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use util::SemanticVersion;
#[derive(Debug)]
pub struct IpsFile {

View File

@@ -1,5 +1,6 @@
use crate::{
db::{self, AccessTokenId, Database, UserId},
db::{self, dev_server, AccessTokenId, Database, DevServerId, UserId},
rpc::Principal,
AppState, Error, Result,
};
use anyhow::{anyhow, Context};
@@ -19,11 +20,11 @@ use std::sync::OnceLock;
use std::{sync::Arc, time::Instant};
use subtle::ConstantTimeEq;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Impersonator(pub Option<db::User>);
/// Validates the authorization header. This has two mechanisms, one for the ADMIN_TOKEN
/// and one for the access tokens that we issue.
/// Validates the authorization header and adds an Extension<Principal> to the request.
/// Authorization: <user-id> <token>
/// <token> can be an access_token attached to that user, or an access token of an admin
/// or (in development) the string ADMIN:<config.api_token>.
/// Authorization: "dev-server-token" <token>
pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
let mut auth_header = req
.headers()
@@ -37,7 +38,26 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
})?
.split_whitespace();
let user_id = UserId(auth_header.next().unwrap_or("").parse().map_err(|_| {
let state = req.extensions().get::<Arc<AppState>>().unwrap();
let first = auth_header.next().unwrap_or("");
if first == "dev-server-token" {
let dev_server_token = auth_header.next().ok_or_else(|| {
Error::Http(
StatusCode::BAD_REQUEST,
"missing dev-server-token token in authorization header".to_string(),
)
})?;
let dev_server = verify_dev_server_token(dev_server_token, &state.db)
.await
.map_err(|e| Error::Http(StatusCode::UNAUTHORIZED, format!("{}", e)))?;
req.extensions_mut()
.insert(Principal::DevServer(dev_server));
return Ok::<_, Error>(next.run(req).await);
}
let user_id = UserId(first.parse().map_err(|_| {
Error::Http(
StatusCode::BAD_REQUEST,
"missing user id in authorization header".to_string(),
@@ -51,8 +71,6 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
)
})?;
let state = req.extensions().get::<Arc<AppState>>().unwrap();
// In development, allow impersonation using the admin API token.
// Don't allow this in production because we can't tell who is doing
// the impersonating.
@@ -76,18 +94,17 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
.await?
.ok_or_else(|| anyhow!("user {} not found", user_id))?;
let impersonator = if let Some(impersonator_id) = validate_result.impersonator_id {
let impersonator = state
if let Some(impersonator_id) = validate_result.impersonator_id {
let admin = state
.db
.get_user_by_id(impersonator_id)
.await?
.ok_or_else(|| anyhow!("user {} not found", impersonator_id))?;
Some(impersonator)
req.extensions_mut()
.insert(Principal::Impersonated { user, admin });
} else {
None
req.extensions_mut().insert(Principal::User(user));
};
req.extensions_mut().insert(user);
req.extensions_mut().insert(Impersonator(impersonator));
return Ok::<_, Error>(next.run(req).await);
}
}
@@ -213,6 +230,33 @@ 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 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 token_hash = hash_access_token(&token);
let server = db.get_dev_server(id).await?;
if server
.hashed_token
.as_bytes()
.ct_eq(token_hash.as_ref())
.into()
{
Ok(server)
} else {
Err(anyhow!("wrong token for dev server"))
}
}
#[cfg(test)]
mod test {
use rand::thread_rng;

View File

@@ -1,97 +0,0 @@
use collab::{
db::{self, NewUserParams},
env::load_dotenv,
executor::Executor,
};
use db::{ConnectOptions, Database};
use serde::{de::DeserializeOwned, Deserialize};
use std::{fmt::Write, fs};
#[derive(Debug, Deserialize)]
struct GitHubUser {
id: i32,
login: String,
email: Option<String>,
}
#[tokio::main]
async fn main() {
load_dotenv().expect("failed to load .env.toml file");
let mut admin_logins = load_admins("crates/collab/.admins.default.json")
.expect("failed to load default admins file");
if let Ok(other_admins) = load_admins("./.admins.json") {
admin_logins.extend(other_admins);
}
let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var");
let db = Database::new(ConnectOptions::new(database_url), Executor::Production)
.await
.expect("failed to connect to postgres database");
let client = reqwest::Client::new();
// Create admin users for all of the users in `.admins.toml` or `.admins.default.toml`.
for admin_login in admin_logins {
let user = fetch_github::<GitHubUser>(
&client,
&format!("https://api.github.com/users/{admin_login}"),
)
.await;
db.create_user(
&user.email.unwrap_or(format!("{admin_login}@example.com")),
true,
NewUserParams {
github_login: user.login,
github_user_id: user.id,
},
)
.await
.expect("failed to create admin user");
}
// Fetch 100 other random users from GitHub and insert them into the database.
let mut user_count = db
.get_all_users(0, 200)
.await
.expect("failed to load users from db")
.len();
let mut last_user_id = None;
while user_count < 100 {
let mut uri = "https://api.github.com/users?per_page=100".to_string();
if let Some(last_user_id) = last_user_id {
write!(&mut uri, "&since={}", last_user_id).unwrap();
}
let users = fetch_github::<Vec<GitHubUser>>(&client, &uri).await;
for github_user in users {
last_user_id = Some(github_user.id);
user_count += 1;
db.get_or_create_user_by_github_account(
&github_user.login,
Some(github_user.id),
github_user.email.as_deref(),
None,
)
.await
.expect("failed to insert user");
}
}
}
fn load_admins(path: &str) -> anyhow::Result<Vec<String>> {
let file_content = fs::read_to_string(path)?;
Ok(serde_json::from_str(&file_content)?)
}
async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
let response = client
.get(url)
.header("user-agent", "zed")
.send()
.await
.unwrap_or_else(|_| panic!("failed to fetch '{}'", url));
response
.json()
.await
.unwrap_or_else(|_| panic!("failed to deserialize github user from '{}'", url))
}

View File

@@ -12,7 +12,7 @@ use futures::StreamExt;
use rand::{prelude::StdRng, Rng, SeedableRng};
use rpc::{
proto::{self},
ConnectionId,
ConnectionId, ExtensionMetadata,
};
use sea_orm::{
entity::prelude::*,
@@ -21,11 +21,13 @@ use sea_orm::{
FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
TransactionTrait,
};
use serde::{ser::Error as _, Deserialize, Serialize, Serializer};
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use sqlx::{
migrate::{Migrate, Migration, MigrationSource},
Connection,
};
use std::ops::RangeInclusive;
use std::{
fmt::Write as _,
future::Future,
@@ -36,7 +38,7 @@ use std::{
sync::Arc,
time::Duration,
};
use time::{format_description::well_known::iso8601, PrimitiveDateTime};
use time::PrimitiveDateTime;
use tokio::sync::{Mutex, OwnedMutexGuard};
#[cfg(test)]
@@ -128,12 +130,6 @@ impl Database {
Ok(new_migrations)
}
/// Initializes static data that resides in the database by upserting it.
pub async fn initialize_static_data(&mut self) -> Result<()> {
self.initialize_notification_kinds().await?;
Ok(())
}
/// Transaction runs things in a transaction. If you want to call other methods
/// and pass the transaction around you need to reborrow the transaction at each
/// call site with: `&*tx`.
@@ -464,6 +460,8 @@ pub struct UpdatedChannelMessage {
pub notifications: NotificationBatch,
pub reply_to_message_id: Option<MessageId>,
pub timestamp: PrimitiveDateTime,
pub deleted_mention_notification_ids: Vec<NotificationId>,
pub updated_mention_notifications: Vec<rpc::proto::Notification>,
}
#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
@@ -731,36 +729,12 @@ pub struct NewExtensionVersion {
pub description: String,
pub authors: Vec<String>,
pub repository: String,
pub schema_version: i32,
pub wasm_api_version: Option<String>,
pub published_at: PrimitiveDateTime,
}
#[derive(Debug, Serialize, PartialEq)]
pub struct ExtensionMetadata {
pub id: String,
pub name: String,
pub version: String,
pub authors: Vec<String>,
pub description: String,
pub repository: String,
#[serde(serialize_with = "serialize_iso8601")]
pub published_at: PrimitiveDateTime,
pub download_count: u64,
}
pub fn serialize_iso8601<S: Serializer>(
datetime: &PrimitiveDateTime,
serializer: S,
) -> Result<S::Ok, S::Error> {
const SERDE_CONFIG: iso8601::EncodedConfig = iso8601::Config::DEFAULT
.set_year_is_six_digits(false)
.set_time_precision(iso8601::TimePrecision::Second {
decimal_digits: None,
})
.encode();
datetime
.assume_utc()
.format(&time::format_description::well_known::Iso8601::<SERDE_CONFIG>)
.map_err(S::Error::custom)?
.serialize(serializer)
pub struct ExtensionVersionConstraints {
pub schema_versions: RangeInclusive<i32>,
pub wasm_api_versions: RangeInclusive<SemanticVersion>,
}

View File

@@ -67,28 +67,29 @@ macro_rules! id_type {
};
}
id_type!(BufferId);
id_type!(AccessTokenId);
id_type!(BufferId);
id_type!(ChannelBufferCollaboratorId);
id_type!(ChannelChatParticipantId);
id_type!(ChannelId);
id_type!(ChannelMemberId);
id_type!(MessageId);
id_type!(ContactId);
id_type!(DevServerId);
id_type!(ExtensionId);
id_type!(FlagId);
id_type!(FollowerId);
id_type!(HostedProjectId);
id_type!(MessageId);
id_type!(NotificationId);
id_type!(NotificationKindId);
id_type!(ProjectCollaboratorId);
id_type!(ProjectId);
id_type!(ReplicaId);
id_type!(RoomId);
id_type!(RoomParticipantId);
id_type!(ProjectId);
id_type!(ProjectCollaboratorId);
id_type!(ReplicaId);
id_type!(ServerId);
id_type!(SignupId);
id_type!(UserId);
id_type!(ChannelBufferCollaboratorId);
id_type!(FlagId);
id_type!(ExtensionId);
id_type!(NotificationId);
id_type!(NotificationKindId);
id_type!(HostedProjectId);
/// ChannelRole gives you permissions for both channels and calls.
#[derive(

View File

@@ -5,6 +5,7 @@ pub mod buffers;
pub mod channels;
pub mod contacts;
pub mod contributors;
pub mod dev_servers;
pub mod extensions;
pub mod hosted_projects;
pub mod messages;

View File

@@ -0,0 +1,18 @@
use sea_orm::EntityTrait;
use super::{dev_server, Database, DevServerId};
impl Database {
pub async fn get_dev_server(
&self,
dev_server_id: DevServerId,
) -> crate::Result<dev_server::Model> {
self.transaction(|tx| async move {
Ok(dev_server::Entity::find_by_id(dev_server_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow::anyhow!("no dev server with id {}", dev_server_id))?)
})
.await
}
}

View File

@@ -1,87 +1,197 @@
use std::str::FromStr;
use chrono::Utc;
use sea_orm::sea_query::IntoCondition;
use util::ResultExt;
use super::*;
impl Database {
pub async fn get_extensions(
&self,
filter: Option<&str>,
max_schema_version: i32,
limit: usize,
) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move {
let mut condition = Condition::all();
let mut condition = Condition::all()
.add(
extension::Column::LatestVersion
.into_expr()
.eq(extension_version::Column::Version.into_expr()),
)
.add(extension_version::Column::SchemaVersion.lte(max_schema_version));
if let Some(filter) = filter {
let fuzzy_name_filter = Self::fuzzy_like_string(filter);
condition = condition.add(Expr::cust_with_expr("name ILIKE $1", fuzzy_name_filter));
}
self.get_extensions_where(condition, Some(limit as u64), &tx)
.await
})
.await
}
pub async fn get_extensions_by_ids(
&self,
ids: &[&str],
constraints: Option<&ExtensionVersionConstraints>,
) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move {
let extensions = extension::Entity::find()
.filter(condition)
.order_by_desc(extension::Column::TotalDownloadCount)
.order_by_asc(extension::Column::Name)
.limit(Some(limit as u64))
.filter(
extension::Column::LatestVersion
.into_expr()
.eq(extension_version::Column::Version.into_expr()),
)
.inner_join(extension_version::Entity)
.select_also(extension_version::Entity)
.filter(extension::Column::ExternalId.is_in(ids.iter().copied()))
.all(&*tx)
.await?;
let mut max_versions = self
.get_latest_versions_for_extensions(&extensions, constraints, &tx)
.await?;
Ok(extensions
.into_iter()
.filter_map(|(extension, latest_version)| {
let version = latest_version?;
Some(ExtensionMetadata {
id: extension.external_id,
name: extension.name,
version: version.version,
authors: version
.authors
.split(',')
.map(|author| author.trim().to_string())
.collect::<Vec<_>>(),
description: version.description,
repository: version.repository,
published_at: version.published_at,
download_count: extension.total_download_count as u64,
})
.filter_map(|extension| {
let (version, _) = max_versions.remove(&extension.id)?;
Some(metadata_from_extension_and_version(extension, version))
})
.collect())
})
.await
}
pub async fn get_extension(&self, extension_id: &str) -> Result<Option<ExtensionMetadata>> {
async fn get_latest_versions_for_extensions(
&self,
extensions: &[extension::Model],
constraints: Option<&ExtensionVersionConstraints>,
tx: &DatabaseTransaction,
) -> Result<HashMap<ExtensionId, (extension_version::Model, SemanticVersion)>> {
let mut versions = extension_version::Entity::find()
.filter(
extension_version::Column::ExtensionId
.is_in(extensions.iter().map(|extension| extension.id)),
)
.stream(tx)
.await?;
let mut max_versions =
HashMap::<ExtensionId, (extension_version::Model, SemanticVersion)>::default();
while let Some(version) = versions.next().await {
let version = version?;
let Some(extension_version) = SemanticVersion::from_str(&version.version).log_err()
else {
continue;
};
if let Some((_, max_extension_version)) = &max_versions.get(&version.extension_id) {
if max_extension_version > &extension_version {
continue;
}
}
if let Some(constraints) = constraints {
if !constraints
.schema_versions
.contains(&version.schema_version)
{
continue;
}
if let Some(wasm_api_version) = version.wasm_api_version.as_ref() {
if let Some(version) = SemanticVersion::from_str(wasm_api_version).log_err() {
if !constraints.wasm_api_versions.contains(&version) {
continue;
}
} else {
continue;
}
}
}
max_versions.insert(version.extension_id, (version, extension_version));
}
Ok(max_versions)
}
/// Returns all of the versions for the extension with the given ID.
pub async fn get_extension_versions(
&self,
extension_id: &str,
) -> Result<Vec<ExtensionMetadata>> {
self.transaction(|tx| async move {
let condition = extension::Column::ExternalId
.eq(extension_id)
.into_condition();
self.get_extensions_where(condition, None, &tx).await
})
.await
}
async fn get_extensions_where(
&self,
condition: Condition,
limit: Option<u64>,
tx: &DatabaseTransaction,
) -> Result<Vec<ExtensionMetadata>> {
let extensions = extension::Entity::find()
.inner_join(extension_version::Entity)
.select_also(extension_version::Entity)
.filter(condition)
.order_by_desc(extension::Column::TotalDownloadCount)
.order_by_asc(extension::Column::Name)
.limit(limit)
.all(tx)
.await?;
Ok(extensions
.into_iter()
.filter_map(|(extension, version)| {
Some(metadata_from_extension_and_version(extension, version?))
})
.collect())
}
pub async fn get_extension(
&self,
extension_id: &str,
constraints: Option<&ExtensionVersionConstraints>,
) -> Result<Option<ExtensionMetadata>> {
self.transaction(|tx| async move {
let extension = extension::Entity::find()
.filter(extension::Column::ExternalId.eq(extension_id))
.filter(
extension::Column::LatestVersion
.into_expr()
.eq(extension_version::Column::Version.into_expr()),
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such extension: {extension_id}"))?;
let extensions = [extension];
let mut versions = self
.get_latest_versions_for_extensions(&extensions, constraints, &tx)
.await?;
let [extension] = extensions;
Ok(versions.remove(&extension.id).map(|(max_version, _)| {
metadata_from_extension_and_version(extension, max_version)
}))
})
.await
}
pub async fn get_extension_version(
&self,
extension_id: &str,
version: &str,
) -> Result<Option<ExtensionMetadata>> {
self.transaction(|tx| async move {
let extension = extension::Entity::find()
.filter(extension::Column::ExternalId.eq(extension_id))
.filter(extension_version::Column::Version.eq(version))
.inner_join(extension_version::Entity)
.select_also(extension_version::Entity)
.one(&*tx)
.await?;
Ok(extension.and_then(|(extension, latest_version)| {
let version = latest_version?;
Some(ExtensionMetadata {
id: extension.external_id,
name: extension.name,
version: version.version,
authors: version
.authors
.split(',')
.map(|author| author.trim().to_string())
.collect::<Vec<_>>(),
description: version.description,
repository: version.repository,
published_at: version.published_at,
download_count: extension.total_download_count as u64,
})
Ok(extension.and_then(|(extension, version)| {
Some(metadata_from_extension_and_version(extension, version?))
}))
})
.await
@@ -170,6 +280,8 @@ impl Database {
authors: ActiveValue::Set(version.authors.join(", ")),
repository: ActiveValue::Set(version.repository.clone()),
description: ActiveValue::Set(version.description.clone()),
schema_version: ActiveValue::Set(version.schema_version),
wasm_api_version: ActiveValue::Set(version.wasm_api_version.clone()),
download_count: ActiveValue::NotSet,
}
}))
@@ -239,3 +351,35 @@ impl Database {
.await
}
}
fn metadata_from_extension_and_version(
extension: extension::Model,
version: extension_version::Model,
) -> ExtensionMetadata {
ExtensionMetadata {
id: extension.external_id.into(),
manifest: rpc::ExtensionApiManifest {
name: extension.name,
version: version.version.into(),
authors: version
.authors
.split(',')
.map(|author| author.trim().to_string())
.collect::<Vec<_>>(),
description: Some(version.description),
repository: version.repository,
schema_version: Some(version.schema_version),
wasm_api_version: version.wasm_api_version,
},
published_at: convert_time_to_chrono(version.published_at),
download_count: extension.total_download_count as u64,
}
}
pub fn convert_time_to_chrono(time: time::PrimitiveDateTime) -> chrono::DateTime<Utc> {
chrono::DateTime::from_naive_utc_and_offset(
chrono::NaiveDateTime::from_timestamp_opt(time.assume_utc().unix_timestamp(), 0).unwrap(),
Utc,
)
}

View File

@@ -1,7 +1,8 @@
use super::*;
use rpc::Notification;
use sea_orm::TryInsertResult;
use sea_orm::{SelectColumns, TryInsertResult};
use time::OffsetDateTime;
use util::ResultExt;
impl Database {
/// Inserts a record representing a user joining the chat for a given channel.
@@ -480,13 +481,20 @@ impl Database {
Ok(results)
}
fn get_notification_kind_id_by_name(&self, notification_kind: &str) -> Option<i32> {
self.notification_kinds_by_id
.iter()
.find(|(_, kind)| **kind == notification_kind)
.map(|kind| kind.0 .0)
}
/// Removes the channel message with the given ID.
pub async fn remove_channel_message(
&self,
channel_id: ChannelId,
message_id: MessageId,
user_id: UserId,
) -> Result<Vec<ConnectionId>> {
) -> Result<(Vec<ConnectionId>, Vec<NotificationId>)> {
self.transaction(|tx| async move {
let mut rows = channel_chat_participant::Entity::find()
.filter(channel_chat_participant::Column::ChannelId.eq(channel_id))
@@ -531,7 +539,29 @@ impl Database {
}
}
Ok(participant_connection_ids)
let notification_kind_id =
self.get_notification_kind_id_by_name("ChannelMessageMention");
let existing_notifications = notification::Entity::find()
.filter(notification::Column::EntityId.eq(message_id))
.filter(notification::Column::Kind.eq(notification_kind_id))
.select_column(notification::Column::Id)
.all(&*tx)
.await?;
let existing_notification_ids = existing_notifications
.into_iter()
.map(|notification| notification.id)
.collect();
// remove all the mention notifications for this message
notification::Entity::delete_many()
.filter(notification::Column::EntityId.eq(message_id))
.filter(notification::Column::Kind.eq(notification_kind_id))
.exec(&*tx)
.await?;
Ok((participant_connection_ids, existing_notification_ids))
})
.await
}
@@ -629,14 +659,44 @@ impl Database {
.await?;
}
let mut mentioned_user_ids = mentions.iter().map(|m| m.user_id).collect::<HashSet<_>>();
let mut update_mention_user_ids = HashSet::default();
let mut new_mention_user_ids =
mentions.iter().map(|m| m.user_id).collect::<HashSet<_>>();
// Filter out users that were mentioned before
for mention in old_mentions {
mentioned_user_ids.remove(&mention.user_id.to_proto());
for mention in &old_mentions {
if new_mention_user_ids.contains(&mention.user_id.to_proto()) {
update_mention_user_ids.insert(mention.user_id.to_proto());
}
new_mention_user_ids.remove(&mention.user_id.to_proto());
}
let notification_kind_id =
self.get_notification_kind_id_by_name("ChannelMessageMention");
let existing_notifications = notification::Entity::find()
.filter(notification::Column::EntityId.eq(message_id))
.filter(notification::Column::Kind.eq(notification_kind_id))
.all(&*tx)
.await?;
// determine which notifications should be updated or deleted
let mut deleted_notification_ids = HashSet::default();
let mut updated_mention_notifications = Vec::new();
for notification in existing_notifications {
if update_mention_user_ids.contains(&notification.recipient_id.to_proto()) {
if let Some(notification) =
self::notifications::model_to_proto(self, notification).log_err()
{
updated_mention_notifications.push(notification);
}
} else {
deleted_notification_ids.insert(notification.id);
}
}
let mut notifications = Vec::new();
for mentioned_user in mentioned_user_ids {
for mentioned_user in new_mention_user_ids {
notifications.extend(
self.create_notification(
UserId::from_proto(mentioned_user),
@@ -658,6 +718,10 @@ impl Database {
notifications,
reply_to_message_id: channel_message.reply_to_message_id,
timestamp: channel_message.sent_at,
deleted_mention_notification_ids: deleted_notification_ids
.into_iter()
.collect::<Vec<_>>(),
updated_mention_notifications,
})
})
.await

View File

@@ -1,5 +1,6 @@
use super::*;
use rpc::Notification;
use util::ResultExt;
impl Database {
/// Initializes the different kinds of notifications by upserting records for them.
@@ -53,11 +54,8 @@ impl Database {
.await?;
while let Some(row) = rows.next().await {
let row = row?;
let kind = row.kind;
if let Some(proto) = model_to_proto(self, row) {
if let Some(proto) = model_to_proto(self, row).log_err() {
result.push(proto);
} else {
log::warn!("unknown notification kind {:?}", kind);
}
}
result.reverse();
@@ -200,7 +198,9 @@ impl Database {
})
.exec(tx)
.await?;
Ok(model_to_proto(self, row).map(|notification| (recipient_id, notification)))
Ok(model_to_proto(self, row)
.map(|notification| (recipient_id, notification))
.ok())
} else {
Ok(None)
}
@@ -241,9 +241,12 @@ impl Database {
}
}
fn model_to_proto(this: &Database, row: notification::Model) -> Option<proto::Notification> {
let kind = this.notification_kinds_by_id.get(&row.kind)?;
Some(proto::Notification {
pub fn model_to_proto(this: &Database, row: notification::Model) -> Result<proto::Notification> {
let kind = this
.notification_kinds_by_id
.get(&row.kind)
.ok_or_else(|| anyhow!("Unknown notification kind"))?;
Ok(proto::Notification {
id: row.id.to_proto(),
kind: kind.to_string(),
timestamp: row.created_at.assume_utc().unix_timestamp() as u64,

View File

@@ -349,6 +349,17 @@ impl Database {
.await
}
pub async fn stale_room_connection(&self, user_id: UserId) -> Result<Option<ConnectionId>> {
self.transaction(|tx| async move {
let participant = room_participant::Entity::find()
.filter(room_participant::Column::UserId.eq(user_id))
.one(&*tx)
.await?;
Ok(participant.and_then(|p| p.answering_connection()))
})
.await
}
async fn get_next_participant_index_internal(
&self,
room_id: RoomId,
@@ -403,39 +414,50 @@ impl Database {
.get_next_participant_index_internal(room_id, tx)
.await?;
room_participant::Entity::insert_many([room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(user_id),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
answering_connection_lost: ActiveValue::set(false),
calling_user_id: ActiveValue::set(user_id),
calling_connection_id: ActiveValue::set(connection.id as i32),
calling_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
participant_index: ActiveValue::Set(Some(participant_index)),
role: ActiveValue::set(Some(role)),
id: ActiveValue::NotSet,
location_kind: ActiveValue::NotSet,
location_project_id: ActiveValue::NotSet,
initial_project_id: ActiveValue::NotSet,
}])
.on_conflict(
OnConflict::columns([room_participant::Column::UserId])
.update_columns([
room_participant::Column::AnsweringConnectionId,
room_participant::Column::AnsweringConnectionServerId,
room_participant::Column::AnsweringConnectionLost,
room_participant::Column::ParticipantIndex,
room_participant::Column::Role,
])
.to_owned(),
)
.exec(tx)
.await?;
// If someone has been invited into the room, accept the invite instead of inserting
let result = room_participant::Entity::update_many()
.filter(
Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
.add(room_participant::Column::UserId.eq(user_id))
.add(room_participant::Column::AnsweringConnectionId.is_null()),
)
.set(room_participant::ActiveModel {
participant_index: ActiveValue::Set(Some(participant_index)),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
answering_connection_lost: ActiveValue::set(false),
..Default::default()
})
.exec(tx)
.await?;
if result.rows_affected == 0 {
room_participant::Entity::insert(room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(user_id),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
answering_connection_lost: ActiveValue::set(false),
calling_user_id: ActiveValue::set(user_id),
calling_connection_id: ActiveValue::set(connection.id as i32),
calling_connection_server_id: ActiveValue::set(Some(ServerId(
connection.owner_id as i32,
))),
participant_index: ActiveValue::Set(Some(participant_index)),
role: ActiveValue::set(Some(role)),
id: ActiveValue::NotSet,
location_kind: ActiveValue::NotSet,
location_project_id: ActiveValue::NotSet,
initial_project_id: ActiveValue::NotSet,
})
.exec(tx)
.await?;
}
let (channel, room) = self.get_channel_room(room_id, &tx).await?;
let channel = channel.ok_or_else(|| anyhow!("no channel for room"))?;

View File

@@ -10,6 +10,7 @@ pub mod channel_message;
pub mod channel_message_mention;
pub mod contact;
pub mod contributor;
pub mod dev_server;
pub mod extension;
pub mod extension_version;
pub mod feature_flag;

View File

@@ -0,0 +1,17 @@
use crate::db::{ChannelId, DevServerId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "dev_servers")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: DevServerId,
pub name: String,
pub channel_id: ChannelId,
pub hashed_token: String,
}
impl ActiveModelBehavior for ActiveModel {}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

View File

@@ -13,6 +13,8 @@ pub struct Model {
pub authors: String,
pub repository: String,
pub description: String,
pub schema_version: i32,
pub wasm_api_version: Option<String>,
pub download_count: i64,
}

View File

@@ -1,10 +1,10 @@
use super::Database;
use crate::db::ExtensionVersionConstraints;
use crate::{
db::{ExtensionMetadata, NewExtensionVersion},
db::{queries::extensions::convert_time_to_chrono, ExtensionMetadata, NewExtensionVersion},
test_both_dbs,
};
use std::sync::Arc;
use time::{OffsetDateTime, PrimitiveDateTime};
test_both_dbs!(
test_extensions,
@@ -16,11 +16,13 @@ async fn test_extensions(db: &Arc<Database>) {
let versions = db.get_known_extension_versions().await.unwrap();
assert!(versions.is_empty());
let extensions = db.get_extensions(None, 5).await.unwrap();
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
assert!(extensions.is_empty());
let t0 = OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
let t0 = PrimitiveDateTime::new(t0.date(), t0.time());
let t0 = time::OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
let t0 = time::PrimitiveDateTime::new(t0.date(), t0.time());
let t0_chrono = convert_time_to_chrono(t0);
db.insert_extension_versions(
&[
@@ -33,6 +35,8 @@ async fn test_extensions(db: &Arc<Database>) {
description: "an extension".into(),
authors: vec!["max".into()],
repository: "ext1/repo".into(),
schema_version: 1,
wasm_api_version: None,
published_at: t0,
},
NewExtensionVersion {
@@ -41,6 +45,8 @@ async fn test_extensions(db: &Arc<Database>) {
description: "a good extension".into(),
authors: vec!["max".into(), "marshall".into()],
repository: "ext1/repo".into(),
schema_version: 1,
wasm_api_version: None,
published_at: t0,
},
],
@@ -53,6 +59,8 @@ async fn test_extensions(db: &Arc<Database>) {
description: "a great extension".into(),
authors: vec!["marshall".into()],
repository: "ext2/repo".into(),
schema_version: 0,
wasm_api_version: None,
published_at: t0,
}],
),
@@ -75,33 +83,61 @@ async fn test_extensions(db: &Arc<Database>) {
);
// The latest version of each extension is returned.
let extensions = db.get_extensions(None, 5).await.unwrap();
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
assert_eq!(
extensions,
&[
ExtensionMetadata {
id: "ext1".into(),
name: "Extension One".into(),
version: "0.0.2".into(),
authors: vec!["max".into(), "marshall".into()],
description: "a good extension".into(),
repository: "ext1/repo".into(),
published_at: t0,
manifest: rpc::ExtensionApiManifest {
name: "Extension One".into(),
version: "0.0.2".into(),
authors: vec!["max".into(), "marshall".into()],
description: Some("a good extension".into()),
repository: "ext1/repo".into(),
schema_version: Some(1),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 0,
},
ExtensionMetadata {
id: "ext2".into(),
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: "a great extension".into(),
repository: "ext2/repo".into(),
published_at: t0,
manifest: rpc::ExtensionApiManifest {
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: Some("a great extension".into()),
repository: "ext2/repo".into(),
schema_version: Some(0),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 0
},
]
);
// Extensions with too new of a schema version are excluded.
let extensions = db.get_extensions(None, 0, 5).await.unwrap();
assert_eq!(
extensions,
&[ExtensionMetadata {
id: "ext2".into(),
manifest: rpc::ExtensionApiManifest {
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: Some("a great extension".into()),
repository: "ext2/repo".into(),
schema_version: Some(0),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 0
},]
);
// Record extensions being downloaded.
for _ in 0..7 {
assert!(db.record_extension_download("ext2", "0.0.2").await.unwrap());
@@ -122,28 +158,36 @@ async fn test_extensions(db: &Arc<Database>) {
.unwrap());
// Extensions are returned in descending order of total downloads.
let extensions = db.get_extensions(None, 5).await.unwrap();
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
assert_eq!(
extensions,
&[
ExtensionMetadata {
id: "ext2".into(),
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: "a great extension".into(),
repository: "ext2/repo".into(),
published_at: t0,
manifest: rpc::ExtensionApiManifest {
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: Some("a great extension".into()),
repository: "ext2/repo".into(),
schema_version: Some(0),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 7
},
ExtensionMetadata {
id: "ext1".into(),
name: "Extension One".into(),
version: "0.0.2".into(),
authors: vec!["max".into(), "marshall".into()],
description: "a good extension".into(),
repository: "ext1/repo".into(),
published_at: t0,
manifest: rpc::ExtensionApiManifest {
name: "Extension One".into(),
version: "0.0.2".into(),
authors: vec!["max".into(), "marshall".into()],
description: Some("a good extension".into()),
repository: "ext1/repo".into(),
schema_version: Some(1),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 5,
},
]
@@ -161,6 +205,8 @@ async fn test_extensions(db: &Arc<Database>) {
description: "a real good extension".into(),
authors: vec!["max".into(), "marshall".into()],
repository: "ext1/repo".into(),
schema_version: 1,
wasm_api_version: None,
published_at: t0,
}],
),
@@ -172,6 +218,8 @@ async fn test_extensions(db: &Arc<Database>) {
description: "an old extension".into(),
authors: vec!["marshall".into()],
repository: "ext2/repo".into(),
schema_version: 0,
wasm_api_version: None,
published_at: t0,
}],
),
@@ -196,30 +244,143 @@ async fn test_extensions(db: &Arc<Database>) {
.collect()
);
let extensions = db.get_extensions(None, 5).await.unwrap();
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
assert_eq!(
extensions,
&[
ExtensionMetadata {
id: "ext2".into(),
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: "a great extension".into(),
repository: "ext2/repo".into(),
published_at: t0,
manifest: rpc::ExtensionApiManifest {
name: "Extension Two".into(),
version: "0.2.0".into(),
authors: vec!["marshall".into()],
description: Some("a great extension".into()),
repository: "ext2/repo".into(),
schema_version: Some(0),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 7
},
ExtensionMetadata {
id: "ext1".into(),
name: "Extension One".into(),
version: "0.0.3".into(),
authors: vec!["max".into(), "marshall".into()],
description: "a real good extension".into(),
repository: "ext1/repo".into(),
published_at: t0,
manifest: rpc::ExtensionApiManifest {
name: "Extension One".into(),
version: "0.0.3".into(),
authors: vec!["max".into(), "marshall".into()],
description: Some("a real good extension".into()),
repository: "ext1/repo".into(),
schema_version: Some(1),
wasm_api_version: None,
},
published_at: t0_chrono,
download_count: 5,
},
]
);
}
test_both_dbs!(
test_extensions_by_id,
test_extensions_by_id_postgres,
test_extensions_by_id_sqlite
);
async fn test_extensions_by_id(db: &Arc<Database>) {
let versions = db.get_known_extension_versions().await.unwrap();
assert!(versions.is_empty());
let extensions = db.get_extensions(None, 1, 5).await.unwrap();
assert!(extensions.is_empty());
let t0 = time::OffsetDateTime::from_unix_timestamp_nanos(0).unwrap();
let t0 = time::PrimitiveDateTime::new(t0.date(), t0.time());
let t0_chrono = convert_time_to_chrono(t0);
db.insert_extension_versions(
&[
(
"ext1",
vec![
NewExtensionVersion {
name: "Extension 1".into(),
version: semver::Version::parse("0.0.1").unwrap(),
description: "an extension".into(),
authors: vec!["max".into()],
repository: "ext1/repo".into(),
schema_version: 1,
wasm_api_version: Some("0.0.4".into()),
published_at: t0,
},
NewExtensionVersion {
name: "Extension 1".into(),
version: semver::Version::parse("0.0.2").unwrap(),
description: "a good extension".into(),
authors: vec!["max".into()],
repository: "ext1/repo".into(),
schema_version: 1,
wasm_api_version: Some("0.0.4".into()),
published_at: t0,
},
NewExtensionVersion {
name: "Extension 1".into(),
version: semver::Version::parse("0.0.3").unwrap(),
description: "a real good extension".into(),
authors: vec!["max".into(), "marshall".into()],
repository: "ext1/repo".into(),
schema_version: 1,
wasm_api_version: Some("0.0.5".into()),
published_at: t0,
},
],
),
(
"ext2",
vec![NewExtensionVersion {
name: "Extension 2".into(),
version: semver::Version::parse("0.2.0").unwrap(),
description: "a great extension".into(),
authors: vec!["marshall".into()],
repository: "ext2/repo".into(),
schema_version: 0,
wasm_api_version: None,
published_at: t0,
}],
),
]
.into_iter()
.collect(),
)
.await
.unwrap();
let extensions = db
.get_extensions_by_ids(
&["ext1"],
Some(&ExtensionVersionConstraints {
schema_versions: 1..=1,
wasm_api_versions: "0.0.1".parse().unwrap()..="0.0.4".parse().unwrap(),
}),
)
.await
.unwrap();
assert_eq!(
extensions,
&[ExtensionMetadata {
id: "ext1".into(),
manifest: rpc::ExtensionApiManifest {
name: "Extension 1".into(),
version: "0.0.2".into(),
authors: vec!["max".into()],
description: Some("a good extension".into()),
repository: "ext1/repo".into(),
schema_version: Some(1),
wasm_api_version: Some("0.0.4".into()),
},
published_at: t0_chrono,
download_count: 0,
}]
);
}

View File

@@ -6,6 +6,7 @@ pub mod env;
pub mod executor;
mod rate_limiter;
pub mod rpc;
pub mod seed;
#[cfg(test)]
mod tests;
@@ -111,6 +112,8 @@ impl std::error::Error for Error {}
pub struct Config {
pub http_port: u16,
pub database_url: String,
pub migrations_path: Option<PathBuf>,
pub seed_path: Option<PathBuf>,
pub database_max_connections: u32,
pub api_token: String,
pub clickhouse_url: Option<String>,
@@ -131,6 +134,7 @@ pub struct Config {
pub zed_environment: Arc<str>,
pub openai_api_key: Option<Arc<str>>,
pub google_ai_api_key: Option<Arc<str>>,
pub anthropic_api_key: Option<Arc<str>>,
pub zed_client_checksum_seed: Option<String>,
pub slack_panics_webhook: Option<String>,
pub auto_join_channel_id: Option<ChannelId>,
@@ -142,12 +146,6 @@ impl Config {
}
}
#[derive(Default, Deserialize)]
pub struct MigrateConfig {
pub database_url: String,
pub migrations_path: Option<PathBuf>,
}
pub struct AppState {
pub db: Arc<Database>,
pub live_kit_client: Option<Arc<dyn live_kit_server::api::Client>>,

View File

@@ -7,7 +7,7 @@ use axum::{
};
use collab::{
api::fetch_extensions_from_blob_store_periodically, db, env, executor::Executor, AppState,
Config, MigrateConfig, RateLimiter, Result,
Config, RateLimiter, Result,
};
use db::Database;
use std::{
@@ -43,7 +43,16 @@ async fn main() -> Result<()> {
println!("collab v{} ({})", VERSION, REVISION.unwrap_or("unknown"));
}
Some("migrate") => {
run_migrations().await?;
let config = envy::from_env::<Config>().expect("error loading config");
run_migrations(&config).await?;
}
Some("seed") => {
let config = envy::from_env::<Config>().expect("error loading config");
let db_options = db::ConnectOptions::new(config.database_url.clone());
let mut db = Database::new(db_options, Executor::Production).await?;
db.initialize_notification_kinds().await?;
collab::seed::seed(&config, &db, true).await?;
}
Some("serve") => {
let (is_api, is_collab) = if let Some(next) = args.next() {
@@ -53,14 +62,14 @@ async fn main() -> Result<()> {
};
if !is_api && !is_collab {
Err(anyhow!(
"usage: collab <version | migrate | serve [api|collab]>"
"usage: collab <version | migrate | seed | serve [api|collab]>"
))?;
}
let config = envy::from_env::<Config>().expect("error loading config");
init_tracing(&config);
run_migrations().await?;
run_migrations(&config).await?;
let state = AppState::new(config, Executor::Production).await?;
@@ -128,18 +137,38 @@ async fn main() -> Result<()> {
);
#[cfg(unix)]
let signal = async move {
let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate())
.expect("failed to listen for interrupt signal");
let mut sigint = tokio::signal::unix::signal(SignalKind::interrupt())
.expect("failed to listen for interrupt signal");
let sigterm = sigterm.recv();
let sigint = sigint.recv();
futures::pin_mut!(sigterm, sigint);
futures::future::select(sigterm, sigint).await;
};
#[cfg(windows)]
let signal = async move {
// todo(windows):
// `ctrl_close` does not work well, because tokio's signal handler always returns soon,
// but system termiates the application soon after returning CTRL+CLOSE handler.
// So we should implement blocking handler to treat CTRL+CLOSE signal.
let mut ctrl_break = tokio::signal::windows::ctrl_break()
.expect("failed to listen for interrupt signal");
let mut ctrl_c = tokio::signal::windows::ctrl_c()
.expect("failed to listen for interrupt signal");
let ctrl_break = ctrl_break.recv();
let ctrl_c = ctrl_c.recv();
futures::pin_mut!(ctrl_break, ctrl_c);
futures::future::select(ctrl_break, ctrl_c).await;
};
axum::Server::from_tcp(listener)
.map_err(|e| anyhow!(e))?
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
.with_graceful_shutdown(async move {
let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate())
.expect("failed to listen for interrupt signal");
let mut sigint = tokio::signal::unix::signal(SignalKind::interrupt())
.expect("failed to listen for interrupt signal");
let sigterm = sigterm.recv();
let sigint = sigint.recv();
futures::pin_mut!(sigterm, sigint);
futures::future::select(sigterm, sigint).await;
signal.await;
tracing::info!("Received interrupt signal");
if let Some(rpc_server) = rpc_server {
@@ -148,29 +177,28 @@ async fn main() -> Result<()> {
})
.await
.map_err(|e| anyhow!(e))?;
// todo("windows")
#[cfg(windows)]
unimplemented!();
}
_ => {
Err(anyhow!(
"usage: collab <version | migrate | serve [api|collab]>"
"usage: collab <version | migrate | seed | serve [api|collab]>"
))?;
}
}
Ok(())
}
async fn run_migrations() -> Result<()> {
let config = envy::from_env::<MigrateConfig>().expect("error loading config");
async fn run_migrations(config: &Config) -> Result<()> {
let db_options = db::ConnectOptions::new(config.database_url.clone());
let db = Database::new(db_options, Executor::Production).await?;
let mut db = Database::new(db_options, Executor::Production).await?;
let migrations_path = config
.migrations_path
.as_deref()
.unwrap_or_else(|| Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")));
let migrations_path = config.migrations_path.as_deref().unwrap_or_else(|| {
#[cfg(feature = "sqlite")]
let default_migrations = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations.sqlite");
#[cfg(not(feature = "sqlite"))]
let default_migrations = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
Path::new(default_migrations)
});
let migrations = db.migrate(&migrations_path, false).await?;
for (migration, duration) in migrations {
@@ -182,6 +210,12 @@ async fn run_migrations() -> Result<()> {
);
}
db.initialize_notification_kinds().await?;
if config.seed_path.is_some() {
collab::seed::seed(&config, &db, false).await?;
}
return Ok(());
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,10 @@ use crate::db::{ChannelId, ChannelRole, UserId};
use anyhow::{anyhow, Result};
use collections::{BTreeMap, HashMap, HashSet};
use rpc::ConnectionId;
use semantic_version::SemanticVersion;
use serde::Serialize;
use std::fmt;
use tracing::instrument;
use util::{semver, SemanticVersion};
#[derive(Default, Serialize)]
pub struct ConnectionPool {
@@ -20,7 +21,6 @@ struct ConnectedUser {
#[derive(Debug, Serialize)]
pub struct ZedVersion(pub SemanticVersion);
use std::fmt;
impl fmt::Display for ZedVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -30,7 +30,7 @@ impl fmt::Display for ZedVersion {
impl ZedVersion {
pub fn can_collaborate(&self) -> bool {
self.0 >= semver(0, 127, 3) || (self.0 >= semver(0, 126, 3) && self.0 < semver(0, 127, 0))
self.0 >= SemanticVersion::new(0, 127, 3)
}
}

137
crates/collab/src/seed.rs Normal file
View File

@@ -0,0 +1,137 @@
use crate::db::{self, ChannelRole, NewUserParams};
use anyhow::Context;
use db::Database;
use serde::{de::DeserializeOwned, Deserialize};
use std::{fmt::Write, fs, path::Path};
use crate::Config;
#[derive(Debug, Deserialize)]
struct GitHubUser {
id: i32,
login: String,
email: Option<String>,
}
#[derive(Deserialize)]
struct SeedConfig {
// Which users to create as admins.
admins: Vec<String>,
// Which channels to create (all admins are invited to all channels)
channels: Vec<String>,
// Number of random users to create from the Github API
number_of_users: Option<usize>,
}
pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result<()> {
let client = reqwest::Client::new();
if !db.get_all_users(0, 1).await?.is_empty() && !force {
return Ok(());
}
let seed_path = config
.seed_path
.as_ref()
.context("called seed with no SEED_PATH")?;
let seed_config = load_admins(seed_path)
.context(format!("failed to load {}", seed_path.to_string_lossy()))?;
let mut first_user = None;
let mut others = vec![];
for admin_login in seed_config.admins {
let user = fetch_github::<GitHubUser>(
&client,
&format!("https://api.github.com/users/{admin_login}"),
)
.await;
let user = db
.create_user(
&user.email.unwrap_or(format!("{admin_login}@example.com")),
true,
NewUserParams {
github_login: user.login,
github_user_id: user.id,
},
)
.await
.context("failed to create admin user")?;
if first_user.is_none() {
first_user = Some(user.user_id);
} else {
others.push(user.user_id)
}
}
for channel in seed_config.channels {
let (channel, _) = db
.create_channel(&channel, None, first_user.unwrap())
.await
.context("failed to create channel")?;
for user_id in &others {
db.invite_channel_member(
channel.id,
*user_id,
first_user.unwrap(),
ChannelRole::Admin,
)
.await
.context("failed to add user to channel")?;
}
}
if let Some(number_of_users) = seed_config.number_of_users {
// Fetch 100 other random users from GitHub and insert them into the database
// (for testing autocompleters, etc.)
let mut user_count = db
.get_all_users(0, 200)
.await
.expect("failed to load users from db")
.len();
let mut last_user_id = None;
while user_count < number_of_users {
let mut uri = "https://api.github.com/users?per_page=100".to_string();
if let Some(last_user_id) = last_user_id {
write!(&mut uri, "&since={}", last_user_id).unwrap();
}
let users = fetch_github::<Vec<GitHubUser>>(&client, &uri).await;
for github_user in users {
last_user_id = Some(github_user.id);
user_count += 1;
db.get_or_create_user_by_github_account(
&github_user.login,
Some(github_user.id),
github_user.email.as_deref(),
None,
)
.await
.expect("failed to insert user");
}
}
}
Ok(())
}
fn load_admins(path: impl AsRef<Path>) -> anyhow::Result<SeedConfig> {
let file_content = fs::read_to_string(path)?;
Ok(serde_json::from_str(&file_content)?)
}
async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
let response = client
.get(url)
.header("user-agent", "zed")
.send()
.await
.unwrap_or_else(|_| panic!("failed to fetch '{}'", url));
response
.json()
.await
.unwrap_or_else(|_| panic!("failed to deserialize github user from '{}'", url))
}

View File

@@ -222,8 +222,18 @@ async fn test_remove_channel_message(
.update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
.await
.unwrap();
channel_chat_a
.update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
let msg_id_2 = channel_chat_a
.update(cx_a, |c, cx| {
c.send_message(
MessageParams {
text: "two @user_b".to_string(),
mentions: vec![(4..12, client_b.id())],
reply_to_message_id: None,
},
cx,
)
.unwrap()
})
.await
.unwrap();
channel_chat_a
@@ -233,10 +243,24 @@ async fn test_remove_channel_message(
// Clients A and B see all of the messages.
executor.run_until_parked();
let expected_messages = &["one", "two", "three"];
let expected_messages = &["one", "two @user_b", "three"];
assert_messages(&channel_chat_a, expected_messages, cx_a);
assert_messages(&channel_chat_b, expected_messages, cx_b);
// Ensure that client B received a notification for the mention.
client_b.notification_store().read_with(cx_b, |store, _| {
assert_eq!(store.notification_count(), 2);
let entry = store.notification_at(0).unwrap();
assert_eq!(
entry.notification,
Notification::ChannelMessageMention {
message_id: msg_id_2,
sender_id: client_a.id(),
channel_id: channel_id.0,
}
);
});
// Client A deletes one of their messages.
channel_chat_a
.update(cx_a, |c, cx| {
@@ -261,6 +285,13 @@ async fn test_remove_channel_message(
.await
.unwrap();
assert_messages(&channel_chat_c, expected_messages, cx_c);
// Ensure we remove the notifications when the message is removed
client_b.notification_store().read_with(cx_b, |store, _| {
// First notification is the channel invitation, second would be the mention
// notification, which should now be removed.
assert_eq!(store.notification_count(), 1);
});
}
#[track_caller]
@@ -598,4 +629,97 @@ async fn test_chat_editing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
}
);
});
// Test update message and keep the mention and check that the body is updated correctly
channel_chat_a
.update(cx_a, |c, cx| {
c.update_message(
msg_id,
MessageParams {
text: "Updated body v2 including a mention for @user_b".into(),
reply_to_message_id: None,
mentions: vec![(37..45, client_b.id())],
},
cx,
)
.unwrap()
})
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
channel_chat_a.update(cx_a, |channel_chat, _| {
assert_eq!(
channel_chat.find_loaded_message(msg_id).unwrap().body,
"Updated body v2 including a mention for @user_b",
)
});
channel_chat_b.update(cx_b, |channel_chat, _| {
assert_eq!(
channel_chat.find_loaded_message(msg_id).unwrap().body,
"Updated body v2 including a mention for @user_b",
)
});
client_b.notification_store().read_with(cx_b, |store, _| {
let message = store.channel_message_for_id(msg_id);
assert!(message.is_some());
assert_eq!(
message.unwrap().body,
"Updated body v2 including a mention for @user_b"
);
assert_eq!(store.notification_count(), 2);
let entry = store.notification_at(0).unwrap();
assert_eq!(
entry.notification,
Notification::ChannelMessageMention {
message_id: msg_id,
sender_id: client_a.id(),
channel_id: channel_id.0,
}
);
});
// If we remove a mention from a message the corresponding mention notification
// should also be removed.
channel_chat_a
.update(cx_a, |c, cx| {
c.update_message(
msg_id,
MessageParams {
text: "Updated body without a mention".into(),
reply_to_message_id: None,
mentions: vec![],
},
cx,
)
.unwrap()
})
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
channel_chat_a.update(cx_a, |channel_chat, _| {
assert_eq!(
channel_chat.find_loaded_message(msg_id).unwrap().body,
"Updated body without a mention",
)
});
channel_chat_b.update(cx_b, |channel_chat, _| {
assert_eq!(
channel_chat.find_loaded_message(msg_id).unwrap().body,
"Updated body without a mention",
)
});
client_b.notification_store().read_with(cx_b, |store, _| {
// First notification is the channel invitation, second would be the mention
// notification, which should now be removed.
assert_eq!(store.notification_count(), 1);
});
}

View File

@@ -12,7 +12,7 @@ use editor::{
Editor,
};
use futures::StreamExt;
use gpui::{TestAppContext, VisualContext, VisualTestContext};
use gpui::{BorrowAppContext, TestAppContext, VisualContext, VisualTestContext};
use indoc::indoc;
use language::{
language_settings::{AllLanguageSettings, InlayHintSettings},
@@ -23,6 +23,7 @@ use rpc::RECEIVE_TIMEOUT;
use serde_json::json;
use settings::SettingsStore;
use std::{
ops::Range,
path::Path,
sync::{
atomic::{self, AtomicBool, AtomicUsize},
@@ -1986,6 +1987,187 @@ struct Row10;"#};
struct Row1220;"#});
}
#[gpui::test(iterations = 10)]
async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let mut server = TestServer::start(cx_a.executor()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
client_a
.fs()
.insert_tree(
"/my-repo",
json!({
".git": {},
"file.txt": "line1\nline2\nline3\nline\n",
}),
)
.await;
let blame = git::blame::Blame {
entries: vec![
blame_entry("1b1b1b", 0..1),
blame_entry("0d0d0d", 1..2),
blame_entry("3a3a3a", 2..3),
blame_entry("4c4c4c", 3..4),
],
permalinks: [
("1b1b1b", "http://example.com/codehost/idx-0"),
("0d0d0d", "http://example.com/codehost/idx-1"),
("3a3a3a", "http://example.com/codehost/idx-2"),
("4c4c4c", "http://example.com/codehost/idx-3"),
]
.into_iter()
.map(|(sha, url)| (sha.parse().unwrap(), url.parse().unwrap()))
.collect(),
messages: [
("1b1b1b", "message for idx-0"),
("0d0d0d", "message for idx-1"),
("3a3a3a", "message for idx-2"),
("4c4c4c", "message for idx-3"),
]
.into_iter()
.map(|(sha, message)| (sha.parse().unwrap(), message.into()))
.collect(),
};
client_a.fs().set_blame_for_repo(
Path::new("/my-repo/.git"),
vec![(Path::new("file.txt"), blame)],
);
let (project_a, worktree_id) = client_a.build_local_project("/my-repo", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
// Create editor_a
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
// Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
// client_b now requests git blame for the open buffer
editor_b.update(cx_b, |editor_b, cx| {
assert!(editor_b.blame().is_none());
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, cx);
});
cx_a.executor().run_until_parked();
cx_b.executor().run_until_parked();
editor_b.update(cx_b, |editor_b, cx| {
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
.blame_for_rows((0..4).map(Some), cx)
.collect::<Vec<_>>()
});
assert_eq!(
entries,
vec![
Some(blame_entry("1b1b1b", 0..1)),
Some(blame_entry("0d0d0d", 1..2)),
Some(blame_entry("3a3a3a", 2..3)),
Some(blame_entry("4c4c4c", 3..4)),
]
);
blame.update(cx, |blame, _| {
for (idx, entry) in entries.iter().flatten().enumerate() {
assert_eq!(
blame.permalink_for_entry(entry).unwrap().to_string(),
format!("http://example.com/codehost/idx-{}", idx)
);
assert_eq!(
blame.message_for_entry(entry).unwrap(),
format!("message for idx-{}", idx)
);
}
});
});
// editor_b updates the file, which gets sent to client_a, which updates git blame,
// which gets back to client_b.
editor_b.update(cx_b, |editor_b, cx| {
editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
});
cx_a.executor().run_until_parked();
cx_b.executor().run_until_parked();
editor_b.update(cx_b, |editor_b, cx| {
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
.blame_for_rows((0..4).map(Some), cx)
.collect::<Vec<_>>()
});
assert_eq!(
entries,
vec![
None,
Some(blame_entry("0d0d0d", 1..2)),
Some(blame_entry("3a3a3a", 2..3)),
Some(blame_entry("4c4c4c", 3..4)),
]
);
});
// Now editor_a also updates the file
editor_a.update(cx_a, |editor_a, cx| {
editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
});
cx_a.executor().run_until_parked();
cx_b.executor().run_until_parked();
editor_b.update(cx_b, |editor_b, cx| {
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
.blame_for_rows((0..4).map(Some), cx)
.collect::<Vec<_>>()
});
assert_eq!(
entries,
vec![
None,
None,
Some(blame_entry("3a3a3a", 2..3)),
Some(blame_entry("4c4c4c", 3..4)),
]
);
});
}
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
for hint in editor.inlay_hint_cache().hints() {
@@ -1996,3 +2178,11 @@ fn extract_hint_labels(editor: &Editor) -> Vec<String> {
}
labels
}
fn blame_entry(sha: &str, range: Range<u32>) -> git::blame::BlameEntry {
git::blame::BlameEntry {
sha: sha.parse().unwrap(),
range,
..Default::default()
}
}

View File

@@ -7,8 +7,8 @@ use collab_ui::{
};
use editor::{Editor, ExcerptRange, MultiBuffer};
use gpui::{
point, BackgroundExecutor, Context, Entity, SharedString, TestAppContext, View, VisualContext,
VisualTestContext,
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
View, VisualContext, VisualTestContext,
};
use language::Capability;
use live_kit_client::MacOSDisplay;
@@ -2007,7 +2007,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
});
}
async fn join_channel(
pub(crate) async fn join_channel(
channel_id: ChannelId,
client: &TestClient,
cx: &mut TestAppContext,

View File

@@ -1,6 +1,9 @@
use crate::{
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
tests::{channel_id, room_participants, rust_lang, RoomParticipants, TestClient, TestServer},
tests::{
channel_id, following_tests::join_channel, room_participants, rust_lang, RoomParticipants,
TestClient, TestServer,
},
};
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT};
@@ -8,8 +11,8 @@ use collections::{HashMap, HashSet};
use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions};
use futures::StreamExt as _;
use gpui::{
px, size, AppContext, BackgroundExecutor, Model, Modifiers, MouseButton, MouseDownEvent,
TestAppContext,
px, size, AppContext, BackgroundExecutor, BorrowAppContext, Model, Modifiers, MouseButton,
MouseDownEvent, TestAppContext,
};
use language::{
language_settings::{AllLanguageSettings, Formatter},
@@ -1863,6 +1866,24 @@ async fn test_active_call_events(
executor.run_until_parked();
assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
// Unsharing a project should dispatch the RemoteProjectUnshared event.
active_call_a
.update(cx_a, |call, cx| call.hang_up(cx))
.await
.unwrap();
executor.run_until_parked();
assert_eq!(
mem::take(&mut *events_a.borrow_mut()),
vec![room::Event::RoomLeft { channel_id: None }]
);
assert_eq!(
mem::take(&mut *events_b.borrow_mut()),
vec![room::Event::RemoteProjectUnshared {
project_id: project_a_id,
}]
);
}
fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
@@ -4638,9 +4659,16 @@ async fn test_references(
let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
references_provider: Some(lsp::OneOf::Left(true)),
..Default::default()
},
..Default::default()
},
);
client_a
.fs()
@@ -4931,9 +4959,35 @@ async fn test_lsp_hover(
.await;
client_a.language_registry().add(rust_lang());
let language_server_names = ["rust-analyzer", "CrabLang-ls"];
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
.register_specific_fake_lsp_adapter(
"Rust",
true,
FakeLspAdapter {
name: "rust-analyzer",
capabilities: lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
},
);
let _other_server = client_a
.language_registry()
.register_specific_fake_lsp_adapter(
"Rust",
false,
FakeLspAdapter {
name: "CrabLang-ls",
capabilities: lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
},
);
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
let project_id = active_call_a
@@ -4946,62 +5000,133 @@ async fn test_lsp_hover(
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
let mut servers_with_hover_requests = HashMap::default();
for i in 0..language_server_names.len() {
let new_server = fake_language_servers.next().await.unwrap_or_else(|| {
panic!(
"Failed to get language server #{i} with name {}",
&language_server_names[i]
)
});
let new_server_name = new_server.server.name();
assert!(
!servers_with_hover_requests.contains_key(new_server_name),
"Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
);
let new_server_name = new_server_name.to_string();
match new_server_name.as_str() {
"CrabLang-ls" => {
servers_with_hover_requests.insert(
new_server_name.clone(),
new_server.handle_request::<lsp::request::HoverRequest, _, _>(
move |params, _| {
assert_eq!(
params
.text_document_position_params
.text_document
.uri
.as_str(),
"file:///root-1/main.rs"
);
let name = new_server_name.clone();
async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Scalar(
lsp::MarkedString::String(format!("{name} hover")),
),
range: None,
}))
}
},
),
);
}
"rust-analyzer" => {
servers_with_hover_requests.insert(
new_server_name.clone(),
new_server.handle_request::<lsp::request::HoverRequest, _, _>(
|params, _| async move {
assert_eq!(
params
.text_document_position_params
.text_document
.uri
.as_str(),
"file:///root-1/main.rs"
);
assert_eq!(
params.text_document_position_params.position,
lsp::Position::new(0, 22)
);
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Array(vec![
lsp::MarkedString::String("Test hover content.".to_string()),
lsp::MarkedString::LanguageString(lsp::LanguageString {
language: "Rust".to_string(),
value: "let foo = 42;".to_string(),
}),
]),
range: Some(lsp::Range::new(
lsp::Position::new(0, 22),
lsp::Position::new(0, 29),
)),
}))
},
),
);
}
unexpected => panic!("Unexpected server name: {unexpected}"),
}
}
// Request hover information as the guest.
let fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
|params, _| async move {
assert_eq!(
params
.text_document_position_params
.text_document
.uri
.as_str(),
"file:///root-1/main.rs"
);
assert_eq!(
params.text_document_position_params.position,
lsp::Position::new(0, 22)
);
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Array(vec![
lsp::MarkedString::String("Test hover content.".to_string()),
lsp::MarkedString::LanguageString(lsp::LanguageString {
language: "Rust".to_string(),
value: "let foo = 42;".to_string(),
}),
]),
range: Some(lsp::Range::new(
lsp::Position::new(0, 22),
lsp::Position::new(0, 29),
)),
}))
},
let mut hovers = project_b
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
.await;
assert_eq!(
hovers.len(),
2,
"Expected two hovers from both language servers, but got: {hovers:?}"
);
let hover_info = project_b
.update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
.await
.unwrap()
.unwrap();
let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
|mut hover_request| async move {
hover_request
.next()
.await
.expect("All hover requests should have been triggered")
},
))
.await;
hovers.sort_by_key(|hover| hover.contents.len());
let first_hover = hovers.first().cloned().unwrap();
assert_eq!(
first_hover.contents,
vec![project::HoverBlock {
text: "CrabLang-ls hover".to_string(),
kind: HoverBlockKind::Markdown,
},]
);
let second_hover = hovers.last().cloned().unwrap();
assert_eq!(
second_hover.contents,
vec![
project::HoverBlock {
text: "Test hover content.".to_string(),
kind: HoverBlockKind::Markdown,
},
project::HoverBlock {
text: "let foo = 42;".to_string(),
kind: HoverBlockKind::Code {
language: "Rust".to_string()
},
}
]
);
buffer_b.read_with(cx_b, |buffer, _| {
let snapshot = buffer.snapshot();
assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
assert_eq!(
hover_info.contents,
vec![
project::HoverBlock {
text: "Test hover content.".to_string(),
kind: HoverBlockKind::Markdown,
},
project::HoverBlock {
text: "let foo = 42;".to_string(),
kind: HoverBlockKind::Code {
language: "Rust".to_string()
},
}
]
);
assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29);
});
}
@@ -5891,6 +6016,7 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
position: new_tab_button_bounds.center(),
modifiers: Modifiers::default(),
click_count: 1,
first_mouse: false,
});
// regression test that the right click menu for tabs does not open.
@@ -5902,13 +6028,14 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
position: tab_bounds.center(),
modifiers: Modifiers::default(),
click_count: 1,
first_mouse: false,
});
assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
}
#[gpui::test]
async fn test_cmd_k_left(cx: &mut TestAppContext) {
let client = TestServer::start1(cx).await;
let (_, client) = TestServer::start1(cx).await;
let (workspace, cx) = client.build_test_workspace(cx).await;
cx.simulate_keystrokes("cmd-n");
@@ -5928,3 +6055,16 @@ async fn test_cmd_k_left(cx: &mut TestAppContext) {
assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
});
}
#[gpui::test]
async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppContext) {
let (mut server, client) = TestServer::start1(cx1).await;
let channel1 = server.make_public_channel("channel1", &client, cx1).await;
let channel2 = server.make_public_channel("channel2", &client, cx1).await;
join_channel(channel1, &client, cx1).await.unwrap();
drop(client);
let client2 = server.create_client(cx2, "user_a").await;
join_channel(channel2, &client2, cx2).await.unwrap();
}

View File

@@ -832,7 +832,7 @@ impl RandomizedTest for ProjectCollaborationTest {
.boxed(),
LspRequestKind::CodeAction => project
.code_actions(&buffer, offset..offset, cx)
.map_ok(|_| ())
.map(|_| Ok(()))
.boxed(),
LspRequestKind::Definition => project
.definition(&buffer, offset, cx)

View File

@@ -1,7 +1,7 @@
use crate::{
db::{tests::TestDb, NewUserParams, UserId},
executor::Executor,
rpc::{Server, ZedVersion, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
rpc::{Principal, Server, ZedVersion, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
AppState, Config, RateLimiter,
};
use anyhow::anyhow;
@@ -19,7 +19,6 @@ use futures::{channel::oneshot, StreamExt as _};
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
use language::LanguageRegistry;
use node_runtime::FakeNodeRuntime;
use notifications::NotificationStore;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
@@ -27,6 +26,7 @@ use rpc::{
proto::{self, ChannelRole},
RECEIVE_TIMEOUT,
};
use semantic_version::SemanticVersion;
use serde_json::json;
use settings::SettingsStore;
use std::{
@@ -39,7 +39,7 @@ use std::{
Arc,
},
};
use util::{http::FakeHttpClient, SemanticVersion};
use util::http::FakeHttpClient;
use workspace::{Workspace, WorkspaceId, WorkspaceStore};
pub struct TestServer {
@@ -135,9 +135,10 @@ impl TestServer {
(server, client_a, client_b, channel_id)
}
pub async fn start1(cx: &mut TestAppContext) -> TestClient {
pub async fn start1(cx: &mut TestAppContext) -> (TestServer, TestClient) {
let mut server = Self::start(cx.executor().clone()).await;
server.create_client(cx, "user_a").await
let client = server.create_client(cx, "user_a").await;
(server, client)
}
pub async fn reset(&self) {
@@ -197,15 +198,20 @@ impl TestServer {
.override_authenticate(move |cx| {
cx.spawn(|_| async move {
let access_token = "the-token".to_string();
Ok(Credentials {
Ok(Credentials::User {
user_id: user_id.to_proto(),
access_token,
})
})
})
.override_establish_connection(move |credentials, cx| {
assert_eq!(credentials.user_id, user_id.0 as u64);
assert_eq!(credentials.access_token, "the-token");
assert_eq!(
credentials,
&Credentials::User {
user_id: user_id.0 as u64,
access_token: "the-token".into()
}
);
let server = server.clone();
let db = db.clone();
@@ -230,9 +236,8 @@ impl TestServer {
.spawn(server.handle_connection(
server_conn,
client_name,
user,
Principal::User(user),
ZedVersion(SemanticVersion::new(1, 0, 0)),
None,
Some(connection_id_tx),
Executor::Deterministic(cx.background_executor().clone()),
))
@@ -508,6 +513,7 @@ impl TestServer {
blob_store_bucket: None,
openai_api_key: None,
google_ai_api_key: None,
anthropic_api_key: None,
clickhouse_url: None,
clickhouse_user: None,
clickhouse_password: None,
@@ -515,6 +521,8 @@ impl TestServer {
zed_client_checksum_seed: None,
slack_panics_webhook: None,
auto_join_channel_id: None,
migrations_path: None,
seed_path: None,
},
})
}

View File

@@ -156,7 +156,7 @@ impl ChatPanel {
}
}
}
room::Event::Left { channel_id } => {
room::Event::RoomLeft { channel_id } => {
if channel_id == &this.channel_id(cx) {
cx.emit(PanelEvent::Close)
}
@@ -615,6 +615,8 @@ impl ChatPanel {
.child(
IconButton::new(("reply", message_id), IconName::ReplyArrowRight)
.on_click(cx.listener(move |this, _, cx| {
this.cancel_edit_message(cx);
this.message_editor.update(cx, |editor, cx| {
editor.set_reply_to_message_id(message_id);
editor.focus_handle(cx).focus(cx);
@@ -636,6 +638,8 @@ impl ChatPanel {
IconButton::new(("edit", message_id), IconName::Pencil)
.on_click(cx.listener(move |this, _, cx| {
this.message_editor.update(cx, |editor, cx| {
editor.clear_reply_to_message_id();
let message = this
.active_chat()
.and_then(|active_chat| {
@@ -762,7 +766,7 @@ impl ChatPanel {
if message.edited_at.is_some() {
rich_text.highlights.push((
message.body.len()..(message.body.len() + MESSAGE_UPDATED.len()),
(rich_text.text.len() - MESSAGE_UPDATED.len())..rich_text.text.len(),
Highlight::Highlight(HighlightStyle {
fade_out: Some(0.8),
..Default::default()

View File

@@ -9,12 +9,12 @@ use gpui::{
Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace,
};
use language::{
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, Completion,
LanguageRegistry, LanguageServerId, ToOffset,
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
LanguageServerId, ToOffset,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use project::search::SearchQuery;
use project::{search::SearchQuery, Completion};
use settings::Settings;
use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings;
@@ -48,7 +48,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
buffer: &Model<Buffer>,
buffer_position: language::Anchor,
cx: &mut ViewContext<Editor>,
) -> Task<anyhow::Result<Vec<language::Completion>>> {
) -> Task<anyhow::Result<Vec<Completion>>> {
let Some(handle) = self.0.upgrade() else {
return Task::ready(Ok(Vec::new()));
};
@@ -60,7 +60,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
fn resolve_completions(
&self,
_completion_indices: Vec<usize>,
_completions: Arc<RwLock<Box<[language::Completion]>>>,
_completions: Arc<RwLock<Box<[Completion]>>>,
_cx: &mut ViewContext<Editor>,
) -> Task<anyhow::Result<bool>> {
Task::ready(Ok(false))

View File

@@ -14,12 +14,12 @@ use db::kvp::KEY_VALUE_STORE;
use editor::{Editor, EditorElement, EditorStyle};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext,
AsyncWindowContext, Bounds, ClickEvent, ClipboardItem, DismissEvent, Div, EventEmitter,
FocusHandle, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, ListOffset,
ListState, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render,
SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext,
WeakView, WhiteSpace,
actions, anchored, canvas, deferred, div, fill, list, point, prelude::*, px, AnyElement,
AppContext, AsyncWindowContext, Bounds, ClickEvent, ClipboardItem, DismissEvent, Div,
EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, InteractiveElement,
IntoElement, ListOffset, ListState, Model, MouseDownEvent, ParentElement, Pixels, Point,
PromptLevel, Render, SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext,
VisualContext, WeakView, WhiteSpace,
};
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
use project::{Fs, Project};
@@ -1802,7 +1802,7 @@ impl CollabPanel {
}
}
fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
fn show_inline_context_menu(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
let Some(bounds) = self
.selection
.and_then(|ix| self.list_state.bounds_for_item(ix))
@@ -2767,10 +2767,13 @@ impl Render for CollabPanel {
self.render_signed_in(cx)
})
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
overlay()
.position(*position)
.anchor(gpui::AnchorCorner::TopLeft)
.child(menu.clone())
deferred(
anchored()
.position(*position)
.anchor(gpui::AnchorCorner::TopLeft)
.child(menu.clone()),
)
.with_priority(1)
}))
}
}

View File

@@ -5,9 +5,9 @@ use client::{
};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, div, overlay, AppContext, ClipboardItem, DismissEvent, EventEmitter, FocusableView,
Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext,
WeakView,
actions, anchored, deferred, div, AppContext, ClipboardItem, DismissEvent, EventEmitter,
FocusableView, Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
@@ -409,9 +409,12 @@ impl PickerDelegate for ChannelModalDelegate {
.children(
if let (Some((menu, _)), true) = (&self.context_menu, selected) {
Some(
overlay()
.anchor(gpui::AnchorCorner::TopRight)
.child(menu.clone()),
deferred(
anchored()
.anchor(gpui::AnchorCorner::TopRight)
.child(menu.clone()),
)
.with_priority(1),
)
} else {
None

View File

@@ -13,8 +13,8 @@ use call::{report_call_event_for_room, ActiveCall};
pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowContext,
WindowKind, WindowOptions,
actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task,
WindowBackgroundAppearance, WindowContext, WindowKind, WindowOptions,
};
use panel_settings::MessageEditorSettings;
pub use panel_settings::{
@@ -97,13 +97,13 @@ fn notification_window_options(
screen: Rc<dyn PlatformDisplay>,
window_size: Size<Pixels>,
) -> WindowOptions {
let notification_margin_width = GlobalPixels::from(16.);
let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
let notification_margin_width = DevicePixels::from(16);
let notification_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
let screen_bounds = screen.bounds();
let size: Size<GlobalPixels> = window_size.into();
let size: Size<DevicePixels> = window_size.into();
let bounds = gpui::Bounds::<GlobalPixels> {
let bounds = gpui::Bounds::<DevicePixels> {
origin: screen_bounds.upper_right()
- point(
size.width + notification_margin_width,
@@ -121,5 +121,6 @@ fn notification_window_options(
is_movable: false,
display_id: Some(screen.id()),
fullscreen: false,
window_background: WindowBackgroundAppearance::default(),
}
}

View File

@@ -58,7 +58,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
}
}
room::Event::Left { .. } => {
room::Event::RoomLeft { .. } => {
for (_, windows) in notification_windows.drain() {
for window in windows {
window

View File

@@ -2,7 +2,7 @@ use anyhow;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::Settings;
use settings::{Settings, SettingsSources};
use workspace::dock::DockPosition;
#[derive(Deserialize, Debug)]
@@ -53,48 +53,52 @@ pub struct MessageEditorSettings {
impl Settings for CollaborationPanelSettings {
const KEY: Option<&'static str> = Some("collaboration_panel");
type FileContent = PanelSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
sources.json_merge()
}
}
impl Settings for ChatPanelSettings {
const KEY: Option<&'static str> = Some("chat_panel");
type FileContent = PanelSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
sources.json_merge()
}
}
impl Settings for NotificationPanelSettings {
const KEY: Option<&'static str> = Some("notification_panel");
type FileContent = PanelSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
sources.json_merge()
}
}
impl Settings for MessageEditorSettings {
const KEY: Option<&'static str> = Some("message_editor");
type FileContent = MessageEditorSettings;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
sources.json_merge()
}
}

View File

@@ -10,5 +10,6 @@ pub type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "test-support"))]
pub type HashSet<T> = std::collections::HashSet<T>;
pub use rustc_hash::FxHasher;
pub use rustc_hash::{FxHashMap, FxHashSet};
pub use std::collections::*;

View File

@@ -6,7 +6,7 @@ use std::any::TypeId;
use collections::HashSet;
use derive_more::{Deref, DerefMut};
use gpui::{Action, AppContext, Global};
use gpui::{Action, AppContext, BorrowAppContext, Global};
/// Initializes the command palette hooks.
pub fn init(cx: &mut AppContext) {

View File

@@ -29,8 +29,7 @@ use std::{
sync::Arc,
};
use util::{
async_maybe, fs::remove_matching, github::latest_github_release, http::HttpClient, paths,
ResultExt,
fs::remove_matching, github::latest_github_release, http::HttpClient, maybe, paths, ResultExt,
};
actions!(
@@ -377,6 +376,7 @@ impl Copilot {
use node_runtime::FakeNodeRuntime;
let (server, fake_server) = FakeLanguageServer::new(
LanguageServerId(0),
LanguageServerBinary {
path: "path/to/copilot".into(),
arguments: vec![],
@@ -798,7 +798,7 @@ impl Copilot {
) -> Task<Result<()>> {
let server = match self.server.as_authenticated() {
Ok(server) => server,
Err(error) => return Task::ready(Err(error)),
Err(_) => return Task::ready(Ok(())),
};
let request =
server
@@ -1006,7 +1006,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
e @ Err(..) => {
e.log_err();
// Fetch a cached binary, if it exists
async_maybe!({
maybe!(async {
let mut last_version_dir = None;
let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
while let Some(entry) = entries.next().await {

View File

@@ -14,6 +14,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
client.workspace = true
copilot.workspace = true
editor.workspace = true
fs.workspace = true
@@ -27,4 +28,11 @@ workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
copilot = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }
futures.workspace = true
indoc.workspace = true
lsp = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] }
serde_json.workspace = true
theme = { workspace = true, features = ["test-support"] }

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
pub mod copilot_button;
mod copilot_completion_provider;
mod sign_in;
pub use copilot_button::*;
pub use copilot_completion_provider::*;
pub use sign_in::*;

View File

@@ -194,9 +194,8 @@ impl Render for CopilotCodeVerification {
.on_action(cx.listener(|_, _: &menu::Cancel, cx| {
cx.emit(DismissEvent);
}))
.capture_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, cx| {
.on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, cx| {
cx.focus(&this.focus_handle);
cx.stop_propagation();
}))
.child(
svg()

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