Compare commits

...

163 Commits

Author SHA1 Message Date
Thorsten Ball
de855fa4a7 Make debug actually do debug 2024-07-17 16:48:21 +02:00
Thorsten Ball
46239b7a04 debug: Debug a flaky test 2024-07-17 16:38:13 +02:00
Marshall Bowers
1d2d3b209e svelte: Bump to v0.0.3 (#14650)
This PR bumps the Svelte extension to v0.0.3.

Changes:

- #14614

Release Notes:

- N/A
2024-07-17 10:20:50 -04:00
Marshall Bowers
959fbd5e45 docs: Fix typo in Svelte docs (#14649)
This PR fixes a typo in the Svelte docs to reflect the fixes from
#14614.

Release Notes:

- N/A
2024-07-17 10:08:20 -04:00
Nemo
ff85f1d608 Fix default sublime tab navigation (#14427)
Co-authored-by: Peter Tripp <peter@zed.dev>
2024-07-17 10:07:46 -04:00
Peter Tripp
d1300b7a66 Linux Keybinding Improvements (#14600)
- Fixed ctrl-shift-d (duplicate line) conflict.
- Fixes #14458
- Remove some duplicated definitions
- Fix Expand/Shrink selection (was ctrl-shift-up/down, now alt-shift-left/right)
- Add `ctrl-f2` / `cmd-f2` as alias for `editor::SelectAllMatches` matching VSCode
- Moved multi-cursor (Insert cursor above / below) from ctrl-shift-up/down to alt-shift-up/down
2024-07-17 10:06:47 -04:00
Mathias
62f5503e93 svelte: Fix typo in LSP config for inlayHints (#14614)
9b51c5b177/packages/service/configuration.schema.json (L158)

Before:
![Screenshot from 2024-07-17
02-45-37](https://github.com/user-attachments/assets/15f67b7b-ed29-418a-b6a5-23601c1ad820)

After:
![Screenshot from 2024-07-17
02-38-37](https://github.com/user-attachments/assets/c6cb71fc-70f4-4ea3-91d4-a184342163b4)

Release Notes:

- N/A
2024-07-17 09:59:21 -04:00
Kyle Kelley
738d079aa7 docs: Remove extra backtick within repl docs (#14611) 2024-07-17 06:59:05 -07:00
Marshall Bowers
4feb994ad8 php: Bump to v0.1.1 (#14647)
This PR bumps the PHP extension to v0.1.1.

Changes:

- #14643

Release Notes:

- N/A
2024-07-17 09:40:00 -04:00
Marshall Bowers
40ed3b6977 php: Allow using intelephense from PATH (#14643)
This PR updates the PHP extension to use `intelephense` from the PATH,
if it exists.

Tested using the following Nix shell:

```sh
NIXPKGS_ALLOW_UNFREE=1 nix-shell -p php nodePackages_latest.intelephense
```

Resolves #11994.

Release Notes:

- N/A
2024-07-17 09:20:31 -04:00
Mathias
bd02f4fe28 Respect user preference for JS/TS on_type formatting (#14536)
Release Notes:

- Fix user preferences for JS/TS on_type formatting not being respected by VTSLS.
([#13733](https://github.com/zed-industries/zed/issues/13733),
[#14499](https://github.com/zed-industries/zed/issues/14499))
2024-07-17 11:11:00 +02:00
Conrad Irwin
85bc233920 vim: Add :bd/:bp/:bn (#14623)
Also refactor command to be less wierd

Release Notes:

- vim: Added :bd/:bn/:bp (#14457)
2024-07-16 23:06:08 -06:00
Conrad Irwin
33f68882c1 vim: Fix ctrl-d/u going to top bottom (#14620)
Release Notes:

- vim: Fixed ctrl-d/ctrl-u getting to top/bottom of buffer (#13250)
2024-07-16 22:56:44 -06:00
Conrad Irwin
acc9c2421b Vim rename via menu too? (#14617)
Follow up to #14320

Release Notes:

- N/A
2024-07-16 21:00:00 -06:00
Conrad Irwin
2cdfae9ce3 Show an initial empty keymap (#14609)
Release Notes:

- Added default content for the user keymap file.
2024-07-16 20:24:13 -06:00
Soroush Mirzaei
cf92b83c04 Update install CLI message for linux (#14616)
This PR updates the `cli: install` message for Linux. It initially threw
me off thinking that because `path_for_auxiliary_executable` is not
implemented for Linux it's failing and I thought it's a bug. Turns out
the CLI gets installed by the package manager and it's just named
something else.

I ended up only updating the message so it's more clear. If you don't
like the message, let me know :)

The old message:

![image](https://github.com/zed-industries/zed/assets/829535/1a02d08d-2c7a-452a-bfee-dc55d29c0c10)

The new message:

![image](https://github.com/user-attachments/assets/82052a43-1cf5-4b86-88e8-1c1f01a0ae3c)

@ConradIrwin thank you for taking the time and explaining it to me.

closes: #14118

Release Notes:

- N/A
2024-07-16 20:23:10 -06:00
Kyle Kelley
252737aef4 Provide installation instructions for the R extension (#14601)
Release Notes:

- N/A
2024-07-16 15:51:40 -07:00
Kyle Kelley
9c43450fef repl: Don't send KernelInfoRequest on launch (#14608)
Closes #14146. This is just for the time being before a networking
refactoring to split reads and writes on the ROUTER/DEALER ZeroMQ
sockets. Some kernels have not been responding with `kernel_info_reply`,
which ends up hanging our shell socket.

Release Notes:

- N/A

Release notes for the REPL feature will be part of its official launch.
2024-07-16 15:51:23 -07:00
Marshall Bowers
f8cfb50bb4 php: Bump to v0.1.0 (#14607)
This PR bumps the PHP extension to v0.1.0.

Changes:

- #14603
- #14604

Release Notes:

- N/A
2024-07-16 18:49:58 -04:00
Danilo Leal
9499adf50d docs: Adjust the note and warning callout design (#14605)
So they're more consistent and polished. Felt like they could be a bit more refined.

---

Release Notes:

- N/A
2024-07-16 19:48:02 -03:00
Marshall Bowers
696591ca55 php: Add Phpactor support (#14604)
This PR extends the PHP extension with
[Phpactor](https://github.com/phpactor/phpactor) support.

Phpactor seems to provide a better feature set out-of-the-box for free,
so it has been made the default PHP language server.

Thank you to @xtrasmal for informing us of Phpactor's existence!

Release Notes:

- N/A
2024-07-16 18:39:13 -04:00
Alexander Mankuta
f9b0792aa0 Update Configuration docs (#14029)
Release Notes:

- N/A

---

I'd like to help improve Configuration documentation.

Currently I'm often confused by the configuration doc. It's incomplete:
not all settings are documented. It's disorganized: some options are
grouped together but overall there's not much structure or logic to it.
It's inconsistent: some examples show only key and value, some—just the
closes object, and others full nesting. It's confusing: individual keys
are listed but it's hard to understand where in the config structure
they belong.

I suggest the following changes:

- Always specify the full path of the setting
- Document all settings Zed recognises
- List settings in alphabetical order of their full path
- Always use full nesting in examples 

This is an example, of what it might look like. It's first draft, too.
So I'm open to suggestions.

Please let me know if you're interested in this. The whole thing might
need a bit of effort so I'd like to know if this is something you might
want before doing all the work.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-07-16 16:11:55 -06:00
Marshall Bowers
60b22cf029 php: Upgrade zed_extension_api to v0.0.6 (#14603)
This PR upgrades the PHP extension to use v0.0.6 of the
`zed_extension_api`.

Release Notes:

- N/A
2024-07-16 17:53:43 -04:00
Conrad Irwin
1fe16f42ea Fix context in command palette from application menu (#14599)
Supercedes #14468

Release Notes:

- linux: Fixed the command palette when opened from the application menu
2024-07-16 15:14:18 -06:00
Peter Tripp
448ef538b3 More ignorable commits (#14596)
- More of https://github.com/zed-industries/zed/pull/13889
2024-07-16 17:09:50 -04:00
Kyle Kelley
f612c40bee repl: Don't run empty code submission (#14598)
Closes #14565.

Release Notes:

- N/A
2024-07-16 13:51:46 -07:00
Cappy Ishihara
0c6105992c Open URIs from the CLI, support for the zed:// URI scheme on Linux (#14104)
Allows Zed to open custom `zed://` links (redirects from
https://zed.dev/channels) on Linux used XDG MIME types.

This PR also allows the CLI to be able to open Zed (`zed://`) URIs
directly instead of executing the main executable in
`/usr/libexec/zed-editor`.


Release Notes:

- Linux: Allow `zed.dev/channel` (`zed://`) URIs to open on Linux
- CLI: Ability to open URIs from the command line

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-07-16 14:49:15 -06:00
Congyu
64a796d436 Fix renaming sometimes not working in vim mode (#14320)
Disable vim key contexts during renaming, to fix renaming being
interfered with vim commands.

Release Notes:

- Fixed renaming sometimes not working in vim mode
[#14292](https://github.com/zed-industries/zed/issues/14292)
[#11882](https://github.com/zed-industries/zed/issues/11882).
- Fixed inline assistant sometimes not working in vim mode #11559

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-07-16 14:46:03 -06:00
Conrad Irwin
76594ae5cd vim: fix escape while waiting (#14595)
Follow up from #14233

Release Notes:

- N/A
2024-07-16 14:08:05 -06:00
Robert Sturla
05de1dc8d2 Rename Linux desktop icon to match application name (#14437)
Release Notes:

- Updated the Linux manual installation docs to fix windows not matching
with desktop icons
([#14435](https://github.com/zed-industries/zed/issues/14435)).

The automated `curl | bash` installation script already renames the
`zed.desktop` file to match the window, so most users will not be facing
this issue. This is only affecting users who have downloaded and
extracted the files following the manual instructions.

Since the app ID and the desktop file name are not the same, open
windows are not being matched with the desktop icons, therefore showing
a default one. This PR changes the documentation to tell users they
should rename the `.desktop` file to `dev.zed.Zed.desktop`, and
therefore match the automated install script.

Before:

![image](https://github.com/user-attachments/assets/324b91e6-dae1-4902-b59c-b4f124a29820)

After:

![image](https://github.com/user-attachments/assets/169a4e8c-5c84-4400-8f0d-30cf478ca272)
2024-07-16 13:42:16 -06:00
Marshall Bowers
ef5305869f collab: Fix exact extension filtering (#14591)
This PR fixes the exact extension filtering introduced in #14588.

As we traversed the extensions we were always updating `exact_match`,
regardless of whether it matched the extension ID from the filter.

Release Notes:

- N/A
2024-07-16 15:18:48 -04:00
Kyle Kelley
8028e7f1b6 Refactor repl context menu (#14587) 2024-07-16 12:18:06 -07:00
Conrad Irwin
cb6fc11abc Rank exact extension ID matches higher in search results (#14588)
Release Notes:

- Improved relevance of extension search results

Co-authored-by: Marshall <marshall@zed.dev>
2024-07-16 12:33:28 -06:00
Conrad Irwin
cf8bd4a90a Remoting public alpha (#14541)
Release Notes:

- remoting: An alpha version of remote development is now available to
everyone. For more information on how to use it, and limitations see
https://zed.dev/docs/remote-development.
2024-07-16 12:05:55 -06:00
Conrad Irwin
62ab6e1a11 remoting: Allow Add/Remove remote folder (#14532)
Release Notes:

- remoting (alpha only): Allow add/remove folders to projects

---------

Co-authored-by: Max <max@zed.dev>
2024-07-16 12:01:59 -06:00
aohanhongzhi
be1387fee6 Fix text appearing twice after Chinese character input (#14558)
Release Notes:

- Fixed the issue where text appears twice in the editor after Chinese
Character input.([linux: Fix IME on
fcitx](https://github.com/zed-industries/zed/pull/14508)).

Before:

![zed2](https://github.com/user-attachments/assets/e387d70b-ca91-49c8-93e4-850f9e3ef227)

After Fixed:

![zed](https://github.com/user-attachments/assets/8307c12f-30a7-4e82-8c65-d0b53bb8cf44)
2024-07-16 10:54:53 -07:00
apricotbucket28
09459fa3a4 wayland: Fix drag and drop for paths with spaces (#14574)
This wasn't doing any proper parsing before, so `%20` or similar encoded
characters weren't handled correctly.

Release Notes:

- N/A
2024-07-16 10:42:57 -07:00
Marshall Bowers
400ae9c650 extensions_ui: Add feature upsell for Go (#14586)
This PR adds a feature upsell for Go when searching for it in the
extensions view.

Release Notes:

- N/A
2024-07-16 13:33:03 -04:00
Marshall Bowers
f9472ce90b extensions_ui: Add telemetry for docs click-throughs from feature upsells (#14583)
This PR adds some telemetry when the "View docs" button is clicked on a
feature upsell.

The goal here is to get a sense for how effective these upsells are at
getting users to click through if they can't find what they're looking
for.

Release Notes:

- N/A
2024-07-16 12:59:51 -04:00
Marshall Bowers
0556eddc21 docs: Add Git page (#14582)
This PR adds a basic Git page to the docs so that we have somewhere to
link to from the Git upsell within Zed.

Release Notes:

- N/A
2024-07-16 12:47:18 -04:00
Piotr Osiewicz
09c497f744 editor: Ensure allocation reuse (#14577)
In #14567 I claimed that the underlying allocation is reused. And it
was. At the time I've submitted a PR I was using `.filter_map(|x| x)`,
which got flagged by clippy as something that could be simplified to
`.flatten()` - that however broke the allocation reuse promise.

Thus, this PR goes back to using `filter_map` and additionally in debug
builds it performs checks for allocation reuse. With .flatten in place,
a bunch of unit test fail on that branch, so the checks do work.



Release Notes:

- N/A
2024-07-16 18:29:45 +02:00
Marshall Bowers
1b85438df9 docs: Fix casing of "REPL" in sidebar (#14579)
This PR fixes the casing of the REPL link in the sidebar.

Release Notes:

- N/A
2024-07-16 12:17:45 -04:00
Kyle Kelley
b3d0ac3e3c Show how to switch out kernels and discover them (#14531)
A few new doc changes for the repl feature.

Release Notes:

- N/A
2024-07-16 09:12:50 -07:00
Mag Mell
a51a9b0354 docs: Add AOSC OS Installation (#14575)
Added AOSC OS Installation docs

Release Notes:

- N/A
2024-07-16 12:07:17 -04:00
Conrad Irwin
6cf6614c48 Fix xkbcommon overflow more (#14571)
Release Notes:

- linux: Fixed overflow in xkbcommon-rs
2024-07-16 09:06:38 -06:00
Peter Tripp
0bf0152b74 Fix GoForward shortcut on non-US keyboard layouts (#14570)
- Fixes #14418. Originally broken by #13946
2024-07-16 11:00:15 -04:00
Piotr Osiewicz
d338e4a8a6 editor: Improve performance of edit coalescing (#14567)
# Background
In https://github.com/zed-industries/zed/issues/14408 we received a
repro for "Replace all" being slow, even after the work I did
https://github.com/zed-industries/zed/pull/13654. Admittedly #13654 was
a pretty straightforward change.
Under the profiler it turned out that we're spending *10 seconds* in
`memmove` on main thread. Ugh. Not great. The direct ancestor of the
memmove call was
66f0c390a8/crates/editor/src/display_map/tab_map.rs (L108-L119)

What?

# Accidental O(n^2)
We have a bunch of `consolidate_*_edits` functions which take a list of
Fold/Tab/Inlay/Wrap edits and merge consecutive edits if their ranges
overlap/are next to one another. The loop usually goes as follows:
```
while ix < edits.len() {
    let (prev_edits, next_edits) = edits.split_at_mut(ix);
    let prev_edit = prev_edits.last_mut().unwrap();
    let edit = &next_edits[0];
    if PREV_EDIT_CAN_BE_MERGED_WITH_CURRENT_ONE {
        MERGE_EDITS(prev_edit, edit);
        edits.remove(ix); // !!
    } else {
        ix += 1;
    }
}
```
The problem is the call to `.remove` - it has to shift all of the
consecutive elements in the `edits` vector! Thus, when processing the
edits from the original repro (where consolidation shrinks the edit list
from 210k entries to 30k), we mostly spend time moving entries in memory
around.

Thus, the original repro isn't really an issue with replace_all; it's
just that replace_all is one of the few tools available to the end user
that can apply large # of edits in a single transaction.

# Solution
In this PR I address the issue by rewriting the loop in a way that does
not throw items away via `.remove`. Instead, `Iterator::scan` is used,
which lets us achieve the same logic without having the pitfalls of
`.remove`s.
Crucially, **this code does not allocate a new backing buffer for
edits** (see [this article for
rationale](https://blog.polybdenum.com/2024/01/17/identifying-the-collect-vec-memory-leak-footgun.html));
with `vec.into_iter().scan().filter_map().collect()` we still use the
same underlying buffer as the one that's passed into `consolidate_*`
functions. In development I verified that by checking whether the
pointers to backing storage of a Vec are the same before and after the
consolidation.

# Results

### Before
Nightly 0.145.0
[66f0c390a8](66f0c390a8)


https://github.com/user-attachments/assets/8b0ad3bc-86d6-4f8a-850c-ebb86e8b3bfc

(~13s end-to-end)
### After


https://github.com/user-attachments/assets/366835db-1d84-4f95-8c74-b1506a9fabec

(~2s end-to-end)

The remaining lag is (I think) lies in `TextSummary` calculation and not
the consolidation itself. Thus, for the purposes of scoping this PR,
I'll tackle it separately.

Release Notes:

- Significantly improved performance of applying large quantities of
concurrent edits (e.g. when running "Replace all").
2024-07-16 15:53:29 +02:00
Thorsten Ball
751508b6a2 linux: Install dependencies when bundling nightly (#14566)
Release Notes:

- N/A
2024-07-16 15:44:24 +02:00
Thorsten Ball
f54f5dff65 linux: Build Nightly for ARM too (#14562)
Release Notes:

- N/A
2024-07-16 15:19:38 +02:00
Thorsten Ball
66f0c390a8 linux: Fix missing licenses in binary causing panics (#14561)
Turns out that the existing CI step for Nightly did create the licenses
and they have been baked into X86 builds ever since, because our
builders are stateful.

On ARM machines, the licenses wouldn't exist in the binary because we
called `script/generate-licenses` too late in `scripts/bundle-linux`,
after the binary had been created.

This removes the duplication and generates the licenses once, before the
binary is created.

Fixes #14302.

Release Notes:

- Fixed "View Dependency Licenses" (or `zed: open licenses`) crashing on
Linux ARM machines.
([#14302](https://github.com/zed-industries/zed/issues/14302)
2024-07-16 14:55:30 +02:00
Danilo Leal
fdd233eea9 Change the context menu and Copilot settings icon (#14501)
Felt the link we were using for menu items that open a browser page was
not the best. That one is most typically used for attachments within
scope, as opposed to opening external links. Noticed that via the
"Copilot Settings" menu, which also felt like it could have a bit more
descriptive label. Also reduced the size of the rendered icon in this
component.

---

Release Notes:

- N/A
2024-07-16 09:40:40 -03:00
Kirill Bulatov
bf7e474bbc Properly fix the tab icons 2024-07-16 14:46:16 +03:00
Kirill Bulatov
2a8cee57c9 Keep initial tab config without the icons (#14553)
Based on
https://zed-industries.slack.com/archives/C04S5TU0RSN/p1721125498461089

Release Notes:
- N/A
2024-07-16 13:43:17 +03:00
CharlesChen0823
ef20afa9a4 project_panel: Fixed open in split not working in project panel (#14535)
Release Notes:

- Fixed `cmd-double click` in project panel not opening a split view ([14465](https://github.com/zed-industries/zed/issues/14465))
2024-07-16 09:17:58 +03:00
Conrad Irwin
e413823ae7 Headless extensions (#14538)
Release Notes:

- remoting (alpha only): Fix extension installation
2024-07-16 00:08:56 -06:00
Kyle Kelley
e68d9f4625 Switch to muted color for kernel output labels (#14529)
Sets the text for "Executing...", "Queued", etc. to be `Color::Muted`

<img width="442" alt="image"
src="https://github.com/user-attachments/assets/10c27ce2-b804-41a3-a50e-0778b7e6cd09">


Release Notes:

- N/A
2024-07-15 18:20:45 -07:00
apricotbucket28
3407256aa3 linux: Tweak file chooser dialogs (#14526)
Mostly some small tweaks to the file chooser dialogs.

Fixes https://github.com/zed-industries/zed/issues/14127 (along with the
`ashpd` update in https://github.com/zed-industries/zed/pull/14401)

Also included a fix
(971d67c994)
for an issue that made multiple file chooser dialogs pop up on Wayland
when doing CTRL + O and quickly pressing the escape key.

Release Notes:

- N/A
2024-07-15 17:27:46 -07:00
Conrad Irwin
abc5abcd8b open picker (#14524)
Release Notes:

- linux: Added a fallback Open picker for when XDG is not working
- Added a new setting `use_system_path_prompts` (default true) that can
be disabled to use Zed's builtin keyboard-driven prompts.

---------

Co-authored-by: Max <max@zed.dev>
2024-07-15 17:04:15 -06:00
Marshall Bowers
da33aac156 extensions_ui: Remove commented-out code (#14525)
This PR removes some commented-out code from the `extensions_ui`.

Release Notes:

- N/A
2024-07-15 19:00:49 -04:00
Marshall Bowers
1818fef32f Display file icons in tabs (#14523)
This PR adds support for displaying file icons in tabs.

The `tabs.file_icons` setting controls whether the icons are displayed:

```json
{
  "tabs": {
    "file_icons": false
  }
}
```

This setting defaults to `true`.

<img width="1566" alt="Screenshot 2024-07-15 at 6 17 26 PM"
src="https://github.com/user-attachments/assets/86dfc8c9-764c-453d-95e4-2ec95d6fe715">

<img width="1566" alt="Screenshot 2024-07-15 at 6 24 26 PM"
src="https://github.com/user-attachments/assets/4b4e8489-49d3-41bf-b4cb-59365bdd3e9d">

Release Notes:

- Added file icons to buffer tabs
([#12138](https://github.com/zed-industries/zed/issues/12138)).
- If desired, these icons can be removed using `"tabs": { "file_icons":
false }`.
2024-07-15 18:33:08 -04:00
Marshall Bowers
2ae1a472e4 Upsell built-in features on the extensions page (#14516)
This PR extends the extensions page with support for upselling built-in
Zed features when certain keywords are searched for.

This should help inform users about features that Zed has out-of-the-box
when they go looking for them as extensions.

For example, when someone searches "vim":

<img width="1341" alt="Screenshot 2024-07-15 at 4 58 44 PM"
src="https://github.com/user-attachments/assets/b256d07a-559a-43c2-b491-3eca5bff436e">

Here are more examples of what the upsells can look like:

<img width="1341" alt="Screenshot 2024-07-15 at 4 54 39 PM"
src="https://github.com/user-attachments/assets/1f453132-ac14-4884-afc4-7c12db47ad1d">

Release Notes:

- Added banners for built-in Zed features when corresponding keywords
are used in the extension search.
2024-07-15 17:10:01 -04:00
Kirill Bulatov
d7a25c1696 Add an experimental, WIP diagnostics grouping panel (#14515)
Provide a current, broken state as an experimental way to browse
diagnostics.
The diagnostics are grouped by lines and reduced into a block that, in
case of multiple diagnostics per line, could be toggled back and forth
to show more diagnostics on the line.
Use `grouped_diagnostics::Deploy` to show the panel.

Issues remaining:
* panic on warnings toggle due to incorrect excerpt manipulation
* badly styled blocks
* no key bindings to navigate between blocks and toggle them
* overall odd usability gains for certain groups of people

Due to all above, the thing is feature-gated and not exposed to regular
people.


Release Notes:

- N/A
2024-07-15 22:58:18 +03:00
Eric Skogen
2c6cb4ec16 Fix Cmd+\ for workspace::ToggleLeftDock for Atom base keymap (#14098)
Release Notes:

- Fixed Left Dock in Atom keymap on Mac/Linux (`cmd-\`, `ctrl-\`) 
 ([#14098](https://github.com/zed-industries/zed/pull/14098), thanks [@audionerd](https://github.com/audionerd)).

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-07-15 15:56:10 -04:00
Marshall Bowers
143035b1ed gpui_macros: Extract border_style_methods macro (#14514)
This PR extracts a separate `border_style_methods` macro so that it can
be used independently from `style_helpers!`.

Release Notes:

- N/A
2024-07-15 15:51:29 -04:00
Nate Butler
fa3d29087d Add REPL dropdown menu to toolbar (#14493)
TODO: 


- [x] Actions run from menu not firing
- [x] Menu differentiates idle and busy for running kernel

Menu States:
- [x] No session && no support known

No session && no kernel installed for languages of known support
- (TODO after) Intro to REPL
- [x] Link to docs

No session but can start one
- [x] Start REPL
- (TODO after) More info -> Docs?

Yes Session

- [x] Info: Kernel name, language
  example: chatlab-3.7-adsf87fsa (Python)
  example: condapy-3.7 (Python)
- [x] Change Kernel -> https://zed.dev/docs/repl#change-kernel
- ---
- [x] Run
- [x] Interrupt
- [x] Clear Outputs
- ---
- [x] Shutdown


(Release notes left empty as the change will be documented in the REPL
release!)

Reserved for a follow on PR:

```
- [ ] Status should update when the menu is open (missing `cx.notify`?)
- [ ] Shutdown all kernels action
- [ ] Restart action
- [ ] [Default kernel changed - restart (this kernel) to apply] // todo!(kyle): need some kind of state thing that says if this has happened
```


Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-07-15 11:55:49 -07:00
Conrad Irwin
1856320516 Add mouse handling to gpui input example (#14350)
Release Notes:

- N/A

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
2024-07-15 12:10:25 -06:00
Conrad Irwin
b58abb171f linux: Hide Install CLI from welcome (#14506)
Release Notes:

- linux: Remove "Install CLI" from welcome, it is not necessary
2024-07-15 12:08:37 -06:00
Conrad Irwin
868455f978 linux: Fix IME on fcitx5 (#14508)
Release Notes:

- linux: Fix IME under fcitx5 (#14192)
2024-07-15 12:07:19 -06:00
Conrad Irwin
c27c41274a linux: re-add open fallback (#14359)
Release Notes:

- linux: Fixed opening urls/directories on systems where the xdg desktop
portal doesn't handle those requests.
2024-07-15 11:11:38 -06:00
makeProjectGreatAgain
0b0de8ca83 Display hint to add PATH for Fish shell too (#14504)
tested on `fish 3.7.1 (released March 19, 2024)`
___

Release Notes:

- N/A
2024-07-15 11:11:09 -06:00
apricotbucket28
f3ddd18201 linux: Show warning if file picker portal is missing (#14401)
This PR adds a warning when the file chooser couldn't be opened on Linux

It's quite confusing when trying to open a file and apparently nothing
happens:

fixes https://github.com/zed-industries/zed/issues/11089,
https://github.com/zed-industries/zed/issues/14328,
https://github.com/zed-industries/zed/issues/13753#issuecomment-2225812703,
https://github.com/zed-industries/zed/issues/13766,
https://github.com/zed-industries/zed/issues/14384,
https://github.com/zed-industries/zed/issues/14353,
https://github.com/zed-industries/zed/issues/9209


![image](https://github.com/user-attachments/assets/5acabdaa-7a9d-4225-9480-e371d20387c3)


Release Notes:

- N/A
2024-07-15 09:36:39 -07:00
Ephram
5d860e2286 Fix selectable popover dismissing on key press (#14368)
Release Notes:

- Fixed dismissal bug included in #12918
2024-07-15 09:11:14 -06:00
Peter Tripp
e26dbe2c5b Add linux Zed log location to crash report github issue template (#14373)
Release Notes:

- N/A
2024-07-15 09:27:12 -04:00
Peter Tripp
3c38be59b5 Add keyboard shortcuts to center scrolling around current line (#14385)
- MacOS: Center the cursor in the visible area. `ctrl-l` (matches MacOS)
- Linux JetBrains: Scroll so cursor is at the Middle `ctrl-m`
- `editor::NextScreen` is not longer bound in any keymap by default (was
`ctrl-l` on MacOS)

Fixes #5247
2024-07-15 09:26:53 -04:00
Danilo Leal
e50811c425 Adjust list item & pickers spacing (#14250) 2024-07-15 10:23:18 -03:00
Piotr Osiewicz
c1aa4d939c rust: Expose import names in completions for modules and functions (#14490)
Release Notes:

- Improved accuracy of completion lists for Rust functions and modules.
2024-07-15 14:26:39 +02:00
Antonio Scandurra
e8d674dc04 Show cursors for shared contexts (#14484)
Release Notes:

- N/A

Co-authored-by: Nathan <nathan@zed.dev>
2024-07-15 12:42:10 +02:00
Nathan Sobo
f0279e672a Add Gemini models to cloud enum (#14482)
Release Notes:

- N/A
2024-07-15 12:21:58 +02:00
Max Brunsfeld
98b95d9a51 Introduce /symbols command in assistant panel (#14360)
Release Notes:

- Added `/symbols` command in assistant panel.

---------

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Nathan <nathan@zed.dev>
2024-07-15 11:51:32 +02:00
Antonio Scandurra
decdd3b6ac Introduce following for assistant panel (#14479)
Release Notes:

- Added support for following into the assistant panel.

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Nathan <nathan@zed.dev>
2024-07-15 11:36:27 +02:00
Kirill Bulatov
977a1b7a82 Support dynamic formatting capabilities [un]registration (#14478)
Closes https://github.com/zed-industries/zed/issues/12661

Release Notes:

- Added dynamic [un]registration for LSP formatting capabilities
([#12661](https://github.com/zed-industries/zed/issues/12661))
2024-07-15 12:27:33 +03:00
llogick
684d9dde56 zig: Wire up LSP settings and binary.{path/arguments} for zls (#14379)
Enables the  LSP `settings` and `binary.{path/arguments}` functionality

Example:
```
"lsp": {
    "zls": {
      "settings": {
        "semantic_tokens": "none"
      },
      "binary": {
        "path": "/home/user/zls/zig-out/bin/zls",
        "arguments": ["--enable-debug-log"]
      }
    }
  },
```

Release Notes:

- N/A
2024-07-15 09:35:03 +02:00
张小白
315692d112 windows: Refactor clipboard implementation (#14347)
This PR provides a similar implementation to the macOS clipboard
implementation, adds support for metadata and includes tests.

Release Notes:

- N/A
2024-07-14 19:40:41 -07:00
张小白
ba09eabfba windows: Make window creation failable (#14395)
Release Notes:

- N/A
2024-07-14 19:37:46 -07:00
lea
70d983abe3 Include stable package in docs, mention UM, and link to sources for the Fedora package (#14432)
Hello, I'm one of the maintainers of the Zed package on Terra. I made
the following changes:

- Mention the Terra stable package, instead of only preview and nightly.
- Link to sources for Terra packages instead of pkgs.org.
- Mention Ultramarine in addition to Fedora (one of Terra's targets).

Release Notes:

- N/A
2024-07-14 19:34:08 -07:00
Fernando Tagawa
4a3097d4dd x11: Fix capitalization with neo 2 (#14466)
Fixed #14282

Release Notes:

- N/A
2024-07-14 18:19:20 -07:00
Kirill Bulatov
59ce3535d3 Turn off use_on_type_format too, for languages that have format_on_save disabled (#14413)
Based on the discussion in
https://github.com/zed-industries/zed/issues/14400


Release Notes:

- N/A
2024-07-13 22:04:15 +03:00
Kirill Bulatov
f8b5e42070 Do not send textDocument/didSave message if server does not declare its support (#14412)
Release Notes:

- Improved Zed logic for sending `textDocument/didSave` request
([14286](https://github.com/zed-industries/zed/issues/14286))
2024-07-13 21:59:21 +03:00
Kirill Bulatov
88c5eb550e Lookup prettier more leniently (#14403)
Do not require the `prettier` dependency name to be in package.json's
[dev]Dependencies, instead just checking the `node_modules` contents.

Release Notes:

- Improved `prettier` detection to pick up its installation from
transitive dependencies
([12731](https://github.com/zed-industries/zed/issues/12731)
2024-07-13 21:59:14 +03:00
Bedis Nbiba
e5dc6beace deno: wire up LSP settings (#14410)
Currently deno lsp only works because deno have a workaround when it
detects deno.json it gets activated, but without a deno.json it won't
work
With this change now it works correctly regardless of a deno.json
presence, it only require enable:true:


```json
{
  "lsp": {
    "deno": {
      "settings": {
        "deno": {
          "enable": true
        }
      }
    }
  }
}
```


Release Notes:

- Improved initial Deno set-up to enable it without explicit deno.json present in the file system
2024-07-13 21:10:36 +03:00
Zak Johnson
3a410942b4 Apply terminal.foreground and terminal.background from theme (#14281)
Release Notes:

- Fixed terminal colors not respecting the theme
([#11418](https://github.com/zed-industries/zed/discussions/11418)).
2024-07-13 14:41:44 +03:00
Kirill Bulatov
89fbd6528f Do not fold excerpts by default in the outline panel (#14378)
Release Notes:

- N/A
2024-07-13 04:08:21 +03:00
Kirill Bulatov
9ce989a704 Tidy up collab-related signature help data (#14377)
Follow-up of https://github.com/zed-industries/zed/pull/12909

* Fully preserve LSP data when sending it via collab, and only strip it
on the client.
* Avoid extra custom request handlers, and extend multi LSP server query
protocol instead.


Release Notes:

- N/A
2024-07-13 04:06:01 +03:00
Kirill Bulatov
dd63e25f23 Revert hold: true for macOS tasks (#14376)
Otherwise, ctrl-c makes them stuck being held from time to time

Follow-up of https://github.com/zed-industries/zed/pull/13898 that
reverts the macOS-related part of the PR.

Release Notes:

- N/A
2024-07-13 04:02:38 +03:00
Max Brunsfeld
489077befc Extract a BufferStore object from Project (#14037)
This is a ~small~ pure refactor that's a step toward SSH remoting. I've
extracted the Project's buffer state management into a smaller, separate
struct called `BufferStore`, currently in the same crate. I did this as
a separate PR to reduce conflicts between main and `remoting-over-ssh`.

The idea is to make use of this struct (and other smaller structs that
make up `Project`) in a dedicated, simpler `HeadlessProject` type that
we will use in the SSH server to model the remote end of a project. With
this approach, as we develop the headless project, we can avoid adding
more conditional logic to `Project` itself (which is already very
complex), and actually make `Project` a bit smaller by extracting out
helper objects.

Release Notes:

- N/A
2024-07-12 15:25:54 -07:00
FilipeBisinella
21c5ce2bbd Add pyright workspace configuration (#14265)
Release Notes:

- Added support for pyright workspace configuration, as described in
https://microsoft.github.io/pyright/#/settings .
2024-07-12 15:13:09 -07:00
Marshall Bowers
3deb000f70 assistant: Add basic glob support for expanding items in /docs (#14370)
This PR updates the `/docs` slash command with basic globbing support
for expanding docs.

A `*` can be added to the item path to signify the end of a prefix
match.

For example:

```
# This will match any documentation items starting with `auk::`.
# In this case, it will pull in the docs for each item in the crate.
/docs docs-rs auk::*

# This will match any documentation items starting with `auk::visitor::`,
# which will pull in docs for the `visitor` module.
/docs docs-rs auk::visitor::*
```


https://github.com/user-attachments/assets/5e1e21f1-241b-483f-9cd1-facc3aa76365

Release Notes:

- N/A
2024-07-12 17:57:50 -04:00
Mikayla Maki
fe3fe945a9 linux: Indicate when the window is focused (#14266)
fixes #14202

Release Notes:

- Added a representation of the current focus state to Zed's window
style ([#14202](https://github.com/zed-industries/zed/issues/14202))
2024-07-12 14:20:58 -07:00
Stanislav Alekseev
11178eacc7 Fix diagnostic popover not overflowing when necessary (#14322)
It was broken after #13996 moved rendering text one level deeper,
causing `max_h` and `overflow_y_scroll` to apply to different widgets
Release Notes:

- Fixed large diagnostic popovers not overflowing when nessesary

Before:
<img width="814" alt="Screenshot 2024-07-12 at 15 25 46"
src="https://github.com/user-attachments/assets/4f615600-2857-4470-8b77-864e3a9e38d5">

After:
<img width="813" alt="Screenshot 2024-07-12 at 15 26 10"
src="https://github.com/user-attachments/assets/83c1f344-b3b1-4929-8197-4b24a0e9c65e">
2024-07-12 14:14:11 -07:00
Stanislav Alekseev
59bc027750 Fix direnv option being named direnv and not load_direnv in the docs (#14309)
This is a quick followup to #13902 that fixes a mistake with the setting
naming in the docs, I accidentally made
Release Notes:

- N/A
2024-07-12 14:12:02 -07:00
张小白
0a718c65e2 windows: Return client size and position from window_bounds (#14228)
This is a follow up of #14218 , since we open the window based on the
size of the client area, `window_bounds` should also return the size of
the client area to maintain consistency.

Release Notes:

- N/A
2024-07-12 13:19:36 -07:00
Marshall Bowers
85d77a3eec Clarify /docs error message when target/doc does not exist (#14364)
This PR improves the error message shown by the `/docs` slash command
when indexing fails due to the absence of `target/doc`.

We now distinguish between the overall `target/doc` directory missing
and an individual crate directory missing beneath it.

Release Notes:

- N/A
2024-07-12 16:09:16 -04:00
Marshall Bowers
ca80343486 assistant: Add docs provider for docs.rs (#14356)
This PR adds an indexed docs provider for retrieving docs from `docs.rs`
using the `/docs` slash command.

Release Notes:

- N/A
2024-07-12 13:22:52 -04:00
Semen Fomchenkov
739038ddaf docs: Add ALT Linux (Sisyphus) (#14351)
Added ALT Linux (Sisyphus) as one of the ways to install via the package
manager in linux.md.

Release Notes:

- N/A
2024-07-12 12:59:17 -04:00
Peter Tripp
106e0623dd PlainText language: Default to SoftWrap::EditorWidth (#14331)
- Remove wrap guide / vertical ruler in untitled buffers
- Fixes https://github.com/zed-industries/zed/issues/12473
2024-07-12 11:10:59 -04:00
Peter Tripp
607ad6de3c zig: Improve indentation (#14332)
- Fixes https://github.com/zed-industries/zed/issues/14140
2024-07-12 10:24:07 -04:00
Kirill Bulatov
ea26a01f5f Do not render a signature popover when its location is before the visible range (#14307)
Follow-up of https://github.com/zed-industries/zed/pull/12909

Release Notes:

- N/A
2024-07-12 11:31:52 +03:00
Stanislav Alekseev
8abc000553 Fix nushell local env detection by using direnv export (#13902)
I don't intend fully on getting this merged, this is just an experiment
on using `direnv` directly without relying on shell-specific behaviours.
It works though, so this finally closes #8633
Release Notes:

- Fixed nushell not picking up `direnv` environments by directly
interfacing with it using `direnv export`

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-07-12 10:29:32 +02:00
Kirill Bulatov
9f5309cedd Remove non-default wrap setting for markdown (#14304)
With this setting, markdown files are one of the few that get a line
wrap indicator, a vertical line on the right, which confuses people.

Release Notes:

- N/A
2024-07-12 11:15:57 +03:00
Conrad Irwin
adf74fdc14 linux: Fix panic handling unknown keys (#14274)
Pulls in https://github.com/rust-x-bindings/xkbcommon-rs/pull/54 to
avoid
panicking.

Release Notes:

- linux: Fix a panic in keyboard handling
2024-07-11 17:03:19 -06:00
sherwyn
e402d7e96a vim: Add support for vim::PreviousLineStart motion (#14193)
Release Notes:

- vim: Added `-`/`+` to go to beginning of line above/below
([#14183](https://github.com/zed-industries/zed/issues/14183)).
- vim: (Breaking) Removed non-standard builtin binding from `-` to open
the project panel. You can re-add it to your keymap file with:
`{"context":"VimControl", "bindings":{ "-":
"pane::RevealInProjectPanel"}}`


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


https://github.com/zed-industries/zed/assets/32429059/0e9e9348-265e-4a81-a45a-4739034dc5d9

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-07-11 16:36:07 -06:00
Conrad Irwin
12dfd4a2c2 Don't panic on unknown cursor style on x11 (#14264)
Release Notes:

- linux: Fixed a panic if we request a cursor style your system doesn't
support
2024-07-11 16:05:01 -06:00
Conrad Irwin
b87d1eabcc linux: Panic less on window init (#14255)
This change pulls in https://github.com/kvark/blade/pull/135 and updates
the simplelog dependency for compatibility with that.


Release Notes:

- linux: Show link to troubleshooting docs when we can't open a window
2024-07-11 16:04:46 -06:00
Max Brunsfeld
ac528dda64 Fix panic when evaluating a code snippet containing multi-byte characters (#14269)
Also, don't retrieve code snippets when rendering the repl quick action
button

Release Notes:

- N/A

---------

Co-authored-by: Kyle Kelley <kylek@zed.dev>
Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
2024-07-11 15:04:13 -07:00
Marshall Bowers
906688f012 assistant: Show a warning indicator when the user needs to run cargo doc (#14262)
This PR updates the `/docs` slash command to show a warning to the user
if a crate's docs cannot be indexed due to the target directory not
containing docs:

<img width="782" alt="Screenshot 2024-07-11 at 5 11 46 PM"
src="https://github.com/user-attachments/assets/2f54f7a1-97f4-4d2d-b51f-57ba31e50a2f">

Release Notes:

- N/A
2024-07-11 17:37:31 -04:00
Nate Butler
c18e9aedcd Add items_baseline to Styled (#14238)
Add support for aligning items to the baseline.

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2024-07-11 16:38:21 -04:00
Marshall Bowers
cd4847ca22 assistant: Use a more generic icon for the /docs command (#14247)
This PR updates the `/docs` slash command to use a more generic icon to
convey docs.

It was still using the Rust icon, a relic of when it was still
`/rustdoc`.

Release Notes:

- N/A
2024-07-11 15:46:33 -04:00
Brad Pitcher
4c63e8b203 docs: Fix Linux aarch64 tarball links (#14245)
Fixed tarball documentation links for linux aarch64 (they were pointing
at x86_64 tarballs)

Release Notes:

- N/A
2024-07-11 15:43:10 -04:00
TC
d9d8c1f6d9 assistant: Handle http:// links in /fetch (#14243)
Previously http://google.com would get modified to
https://http://google.com which doesn't work. I assume http links should
be supported.

Release Notes:

- N/A
2024-07-11 15:30:45 -04:00
Conrad Irwin
b0dbc80575 vim: (BREAKING) clean up keymap contexts (#14233)
Release Notes:

- vim: (BREAKING) Improved vim keymap contexts.

Previously `vim_mode == normal` was true even when operators were
pending, which led to bugs like #13789 and a requirement for custom
keymaps to exclude various conditions like (`!VimObject` and
`!VimWaiting`) to avoid bugs.

Now `vim_mode` will be set to `operator` or `waiting` in these cases as
described in [the docs](https://zed.dev/docs/vim#keybindings). For most
custom keymaps this change will be a no-op or an improvement, but if you
were deliberately relying on the old behaviour (if you were relying on
`VimObject` or `VimWaiting` becoming true) you will need to update your
keymap.

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-07-11 13:16:26 -06:00
Mikayla Maki
8e853e2b56 Update linux.md 2024-07-11 12:05:59 -07:00
Mikayla Maki
47a78907d6 Update system-requirements.md 2024-07-11 11:59:52 -07:00
Mikayla Maki
0c1a3db87d Update getting-started.md 2024-07-11 11:50:36 -07:00
Omer Tuchfeld
3541a1175f Disrupt blink for immediate feedback on cursor shape changes (#14177)
# Issue

When a user does something that changes the cursor shape, such as when
switching between vim modes, there may be an up to 500ms (cursor blink
interval) delay until the user receives feedback for their action. This
happens when the shape change happens during the invisible phase of a
blink - the user will not see the cursor shape change until the next
phase, which could be 500ms away.

# Solution

Cursor shape changes should disrupt blinking by forcing the cursor to be
shown, this results in immediate feedback for shape changes. This is in
line with the behavior of other editors I've tried.

Release Notes:

- Improved visual feedback when changing cursor shape
2024-07-11 12:47:10 -06:00
Kyle Kelley
e51d469025 Invalidate anchors when they get deleted (#14116)
Allows deleting the outputs directly within the editor. This also fixes
the overlap logic to make sure that the ends and the starts are
compared.


https://github.com/zed-industries/zed/assets/836375/84f5f582-95f3-4c6a-a3c9-54da6009e34d

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-07-11 11:21:41 -07:00
Conrad Irwin
018a2a29ea vim: Fix c when range ends in a multibyte character (#14139)
Release Notes:

- vim: Fixed `c <motion>` omitting trailing multibyte characters
([#13909](https://github.com/zed-industries/zed/issues/13909)).
2024-07-11 12:01:56 -06:00
Donough Liu
d49727ff10 terminal: Set TERM_PROGRAM and TERM_PROGRAM_VERSION environment variables in integrated terminal (#14213)
![image](https://github.com/zed-industries/zed/assets/31354274/9d1c5410-897b-40a1-8256-2d7e207f69ff)

These two environment variables are essential when people need to detect
terminal type and do something. Many popular terminals set them.

fixes https://github.com/zed-industries/zed/issues/4571

Release Notes:

- Set `TERM_PROGRAM` and `TERM_PROGRAM_VERSION` environment variables in
the integrated terminal
([#4571](https://github.com/zed-industries/zed/issues/4571)).
2024-07-11 20:48:46 +03:00
oliverpool
c195c4ddff docs: Document buffer_line_height (#14168)
`buffer_line_height` has been requested in #5590 and implemented in
#2718, however the documentation was still lacking.

Release Notes:

- N/A
2024-07-11 13:42:27 -04:00
Stanislav Alekseev
fd03454540 Fix reverse selections always being cleared (#14150)
When I implemented #13701, I kinda messed up with the reversed
selections, thinking that their anchors are flipped, so I flipped them
again. This caused the reverse selections to always be cleared

Release Notes:

- Fix reverse selections always being cleared, even if the right click
was performed inside
2024-07-11 11:35:18 -06:00
张小白
6eeec9b403 windows: Create window with correct size (#14218)
The `Bounds<DevicePixels>` we use to create a window represents the size
of the drawable area.

### Before:



https://github.com/zed-industries/zed/assets/14981363/52f0d196-b113-4b64-a0d1-407972674990

### After



https://github.com/zed-industries/zed/assets/14981363/83298b6c-5e5f-4a47-b051-35b4a02404ac



Release Notes:

- N/A
2024-07-11 09:54:59 -07:00
Marshall Bowers
b558e8da1e svelte: Bump to v0.0.2 (#14220)
This PR bumps the Svelte extension to v0.0.2.

Changes:

- https://github.com/zed-industries/zed/pull/12788

Release Notes:

- N/A
2024-07-11 11:47:44 -04:00
Peter Tripp
1d7b28c658 Add Upper/LowerCase binds to Linux Sublime Text keybinds (#14155) 2024-07-11 11:15:49 -04:00
Peter Tripp
de78eb44b1 Keymap changes for editor::JoinLines (#14136)
- Linux (default) add ctrl-shift-j
- Linux (default) remove ctrl-j
  - Conflicted with: `"ctrl-j": "workspace::ToggleBottomDock",`
- MacOS (sublime) add cmd-shift-j
2024-07-11 11:14:25 -04:00
Danilo Leal
c071e19899 docs: Add stray design tweaks (#14205)
- Mostly just tweaking some design (colors & spacing) stuff
- Some small accessibility things—e.g., underline decoration for links
and one h1 only per page
- Most of the other captured changes are really just Prettier indenting
stuff

Release Notes:

- N/A
2024-07-11 11:57:22 -03:00
Marshall Bowers
37fc4ce09d Allow Zed Nightly to use v0.0.7 of the Zed extension API (#14209)
This PR updates the Wasm API compatibility check to allow Nightly to
load extensions using v0.0.7 of the Zed extension API.

Release Notes:

- N/A
2024-07-11 10:54:15 -04:00
Danilo Leal
99f56252be docs: Tiny formatting tweaks on the Linux page (#14208)
Release Notes:

- N/A
2024-07-11 11:53:34 -03:00
Aleksei Gusev
f61abe0247 Pass hold: true to Alacritty for tasks (#13898)
It seems `hold: false` causes alacritty to close the channel earlier,
without waiting for the output from the child command to go to Zed.

Fixes [#13683](https://github.com/zed-industries/zed/issues/13683)

Release Notes:

- Fixed loosing output of a spawned task
([#13683](https://github.com/zed-industries/zed/issues/13683)).

[Screencast from 2024-07-06
18-28-56.webm](https://github.com/zed-industries/zed/assets/39293/4ebef8b5-7c0d-46be-9341-4ac0d809458d)
2024-07-11 17:50:00 +03:00
Marshall Bowers
45c54d189a assistant: Show a message when no docs providers are available (#14207)
This PR updates the `/docs` slash command to show a message to more
clearly indicate when there are no available docs providers.

<img width="379" alt="Screenshot 2024-07-11 at 10 31 53 AM"
src="https://github.com/zed-industries/zed/assets/1486634/d079f87c-4933-4da9-ad82-34dbfe6a284c">

Release Notes:

- N/A
2024-07-11 10:49:13 -04:00
Piotr Osiewicz
2727f55772 Add support for projects managed with Yarn (#13644)
TODO:
- [ ] File a PR with Yarn to add Zed to the list of supported IDEs.

Fixes: https://github.com/zed-industries/zed/issues/10107
Fixes: https://github.com/zed-industries/zed/issues/13706
Release Notes:

- Improved experience in projects using Yarn. Run `yarn dlx
@yarnpkg/sdks base` in the root of your project in order to elevate your
experience.

---------

Co-authored-by: Saurabh <79586784+m4saurabh@users.noreply.github.com>
2024-07-11 14:56:07 +02:00
tomoikey
291d64c803 lsp: Implement textDocument/signatureHelp for ProjectClientState::Local environment (#12909)
Closes https://github.com/zed-industries/zed/issues/5155
Closes https://github.com/zed-industries/zed/issues/4879


# Purpose
There was no way to know what to put in function signatures or struct
fields other than hovering at the moment. Therefore, it was necessary to
implement LSP's `textDocument/signatureHelp`.

I tried my best to match the surrounding coding style, but since this is
my first contribution, I believe there are various aspects that may be
lacking. I would greatly appreciate your code review.

# Description
When the window is displayed, the current argument or field at the
cursor's position is automatically bolded. If the cursor moves and there
is nothing to display, the window closes automatically.
To minimize changes and reduce the burden of review and debugging, the
SignatureHelp feature is implemented only when `is_local` is `true`.
Some `unimplemented!()` macros are embedded, but rest assured that they
are not called in this implementation.

# How to try it out
Press `cmd + i` (MacOS), `ctrl + i` (Linux).

# Enable auto signature help (2 ways)
### Add `"auto_signature_help": true` to `settings.json`
<img width="426" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/61310c39-47f9-4586-94b0-ae519dc3b37c">

Or

### Press `Auto Signature Help`. (Default `false`)
<img width="226" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/34155215-1eb5-4621-b09b-55df2f1ab6a8">

# Disable to show signature help after completion
### Add `"show_signature_help_after_completion": false` to
`settings.json`
<img width="438" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/5e5bacac-62e0-4921-9243-17e1e72d5eb6">

# Movie

https://github.com/zed-industries/zed/assets/55743826/77c12d51-b0a5-415d-8901-f93ef92098e7

# Screen Shot
<img width="628" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/3ebcf4b6-2b94-4dea-97f9-ac4f33e0291e">

<img width="637" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/6dc3eb4d-beee-460b-8dbe-d6eec6379b76">

Release Notes:

- Show function signature popovers
([4879](https://github.com/zed-industries/zed/issues/4879),
[5155](https://github.com/zed-industries/zed/issues/5155))

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-07-11 13:38:33 +03:00
Piotr Osiewicz
6a11184ea3 dart: Release 0.0.3 (#14176)
Includes: https://github.com/zed-industries/zed/pull/13686

Release Notes:

- Added Flutter tasks to Dart extension.
2024-07-11 11:48:04 +02:00
Thorsten Ball
ff1dcff2fb gpui example: Add reset button to Input example (#14163)
Extracted from #14051 which I don't want to merge in its current state.


Release Notes:

- N/A
2024-07-11 11:46:47 +02:00
张小白
bef2586eed windows: Fix rust tasks (#13413)
https://github.com/zed-industries/zed/assets/14981363/56c208da-132c-438a-92b3-e31505859262


Release Notes:

- N/A
2024-07-11 10:47:25 +02:00
Abdullah Alsigar
bdba8b23fa dart: Add Flutter runnables and tasks (#13686)
Release Notes:

- Added runnable tasks for Flutter
2024-07-11 10:46:53 +02:00
Piotr Osiewicz
22900554d5 Project panel: Prevent scrollbar size from scaling with rem size (#14167)
The underlying container had width of 0.75 rem, which was equal to 12px
at default ui_font_size. However, with larger values of ui_font_size the
scrollbar would drift towards the center of a project panel, as the
scrollbar itself has a fixed width of 12 pixels. This commit moves
towards using a fixed width of 12px for scrollbar container. The
alternative was to make the scrollbar scale with ui_font_size, but that
isn't what the Editor scrollbar does, so I decided against it.



Release Notes:

- Fixed position of scrollbar in project panel with non-default
`ui_font_size` values.
2024-07-11 10:34:15 +02:00
Denis Washington
6db0b6c5ad terminal: Prevent extra character on handled meta keystrokes (#14151)
On macOS, when `terminal.option_as_meta` is enabled, pressing key
combinations like `option+b` and `option+f` would lead to both an escape
sequence being sent to the terminal (the expected behavior with
`option_as_meta == true`) AND a character being inserted (the behavior
when `option_as_meta == false`). Prevent the latter by stopping
propagation of the key-down event if it corresponds to a terminal escape
sequence and `option_as_meta` is enabled.

Fixes #7728

Release Notes:

- Fixed insertion of extra characters for some keystrokes if
`terminal.option_as_meta` is enabled
([#7728](https://github.com/zed-industries/zed/issues/7728)).
2024-07-11 11:20:54 +03:00
Aaron Cunnington
ba11e9a9a8 Fix SystemUIFont typo in default settings (#14158)
Release Notes:

- N/A
2024-07-11 11:20:38 +03:00
Peter Tripp
de570133ff Docs: Fix theme.mode default settings (#14153)
- Updated docs to match the changes made in
https://github.com/zed-industries/zed/pull/13621
- Fixes: #14084.
2024-07-11 03:20:12 -04:00
Peter Tripp
f1b1a9fd5e Ignore whitespace commits (#13889)
This let's GitHub and the Git cli optionally "skip" certain revs when
generating `git blame`.

Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>
2024-07-11 02:45:40 -04:00
Chris​‌​‮ ‬Hayes‌​​​
1b08f14c54 Document how to enable vim_mode in /docs/vim (#14138)
## Documents:

- **Added** instructions on how to enable "Vim mode" to the
["Settings"](https://zed.dev/docs/vim#settings) of
[/docs/vim](https://zed.dev/docs/vim).

While [/docs/configuring-zed](https://zed.dev/docs/configuring-zed)
_does_ mention the `vim_mode` setting,
[/docs/vim](https://zed.dev/docs/vim) does not.

This can be confusing for users like me who went straight to the vim
doc, and could not figure out how to enable vim.

## Release Notes:

- N/A
2024-07-10 21:59:03 -06:00
Sensational Code
36d3b16279 Add toggle hunk diff and expand all hunk diffs key bindings (#14130)
Noticed these were missing when I was reading through the docs.

Release Notes:

- Add toggle hunk diff and expand all hunk diffs key bindings
2024-07-10 21:22:52 -06:00
Ephram
945764e409 Selectable popover text (#12918)
Release Notes:

- Fixed #5236
- Added the ability to select and copy text from information popovers



https://github.com/zed-industries/zed/assets/50590465/d5c86623-342b-474b-913e-d07cc3f76de4

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Antonio <ascii@zed.dev>
2024-07-10 21:14:34 -06:00
Jason Lee
f1281c14dd Revert Windows normal window title style to WS_EX_APPWINDOW (#14132)
Release Notes:

- N/A

@ConradIrwin we must revert this little change.

https://github.com/zed-industries/zed/pull/14063#issuecomment-2221867379
2024-07-10 20:49:55 -06:00
Hans
3b823d4a0b Add simple support for wrapscan (#13497)
For: #13417 

This is a simple version, I'm not sure if we just need to limit this
feature to vim mode, or maybe in normal editor mode, which involves
other logic like the location of the setting

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-07-10 20:42:37 -06:00
Xiaoguang Wang
46645b552f Remove unused variable query_editor_was_focused (#14128)
Fix
https://github.com/zed-industries/zed/pull/14128

Release Notes:

- N/A
2024-07-10 20:40:36 -06:00
Jason Lee
5bc3846d59 Fix #14106 Windows title bar broken (#14122)
Release Notes:

- N/A

----

Fix #14106 

Sorry, the previous change in #14063 I have made a mistake. I shouldn't
have changed the previous logic.

```diff
- if !state_ptr.hide_title_bar {
+ if state_ptr.hide_title_bar {
```

## Test


https://github.com/zed-industries/zed/assets/5518/e03fbcac-be6b-4a9d-8937-d3b5e236b564

And the popup window limit is still works.
2024-07-10 18:55:51 -06:00
jansol
e6d608fa05 linux: Add NewWorkspace to the Actions list in .desktop (#14097)
Fix
https://github.com/zed-industries/zed/pull/13807#issuecomment-2221324262


Release Notes:

- N/A
2024-07-10 16:16:21 -06:00
Peter Tripp
e106a39620 AlpineLinux: Fix install.sh and docs typo (#14105)
- AlpineLinux uses busybox `mktemp` which requires `mktemp -d` end with six XXXXXX (not five).
- Fixes #14082
2024-07-10 17:41:43 -04:00
Sören Meier
d32e9f759c svelte: Improve syntax highlighting (#12788)
This PR fixes `<script context="module">` not being highlighted.  
It also adds support for scss.

Release Notes:

- N/A
2024-07-10 15:42:50 -04:00
Jason Lee
15662f105e gpui: Fix TextStyle default font_family crash on Windows, use Segoe UI for Windows (#14040)
Release Notes:

- Fixed default font_family crash on Windows, use `Segoe UI`.

## Crash error message

```
thread 'main' panicked at crates\gpui\src\text_system.rs:150:9:
failed to resolve font 'Helvetica' or any of the fallbacks: 
Zed Plex Mono, Helvetica, Cantarell, Ubuntu, Noto Sans, DejaVu Sans
```
2024-07-10 13:40:42 -06:00
Jason Lee
1887a6db53 gpui: Fix popup kind window support on Windows (#14063)
Release Notes:

- N/A

----

Continue #14044 for Windows 

## The problem

The `cx.open_window` method has provided us a `window_kind` option to
allows creating a Popup kind. This behavior can work on macOS, the popup
kind window have no-border, no-shadow, no-resize, and followed the
`is_movable` if present true it can't move.

This PR to fix those supports on Windows.

The border and shadow still exist, I have tried to use WS_POPUP
window_style, but it will crash:

> This is looks like complex, it is out of my known.

```
    Blocking waiting for file lock on build directory
   Compiling gpui v0.1.0 (F:\work\zed\crates\gpui)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 13.96s
     Running `target\debug\examples\window_positioning.exe`
thread 'main' panicked at F:\Users\jason\.cargo\git\checkouts\blade-b2bcd1de1cf7ab6a\21a56f7\blade-graphics\src\vulkan\init.rs:864:18:
called `Result::unwrap()` on an `Err` value: ERROR_OUT_OF_DEVICE_MEMORY
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\examples\window_positioning.exe` (exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN)
```

So I just make a simple change, to use `WS_EX_TOOLWINDOW` this can
disable resize, and connect `is_movable` to `handle_hit_test_msg` to
disable move, and also no Status Bar icon.

## Before



https://github.com/zed-industries/zed/assets/5518/76740a71-e0ba-401f-958d-f4afdeb417c6

## After



https://github.com/zed-industries/zed/assets/5518/dca49f13-914c-425a-b8b6-b9fc15f8d208
2024-07-10 13:39:54 -06:00
张小白
fa9360f78d windows: Work around font rendering clipping issue (#14075)
Release Notes:

- N/A
2024-07-10 13:38:49 -06:00
Conrad Irwin
3ff738fa03 Fix panic clicking on multibyte chars (#14086)
Fixes: #12011

When hovering over a multibyte character in a debug build, Zed would
panic.
Follow up to #11296 

Release Notes:

- N/A
2024-07-10 13:38:26 -06:00
257 changed files with 13584 additions and 5089 deletions

28
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,28 @@
# .git-blame-ignore-revs
#
# This file consists of a list of commits that should be ignored for
# `git blame` purposes. This is useful for ignoring commits that only
# changed whitespace / indentation / formatting, but did not change
# the underlying syntax tree.
#
# GitHub will pick this up automatically for blame views:
# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
# To use this file locally, run:
# git blame --ignore-revs-file .git-blame-ignore-revs
# To always use this file by default, run:
# git config --local blame.ignoreRevsFile .git-blame-ignore-revs
# To disable this functionality, run:
# git config --local blame.ignoreRevsFile ""
# Comments are optional, but may provide helpful context.
# 2023-04-20 Set default tab_size for JSON to 2 and apply new formatting
# https://github.com/zed-industries/zed/pull/2394
eca93c124a488b4e538946cd2d313bd571aa2b86
# 2024-02-25 Format JSON files in assets/
# https://github.com/zed-industries/zed/pull/8405
ffdda588b41f7d9d270ffe76cab116f828ad545e
# 2024-07-05 Improved formatting of default keymaps (single line per bind)
# https://github.com/zed-industries/zed/pull/13887
813cc3f5e537372fc86720b5e71b6e1c815440ab

View File

@@ -27,7 +27,8 @@ body:
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
Drag Zed.log into the text input below.
macOS: `~/Library/Logs/Zed/Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary><pre>

View File

@@ -93,9 +93,9 @@ jobs:
- name: Upload Zed Nightly
run: script/upload-nightly macos
bundle-deb:
bundle-linux-x86:
timeout-minutes: 60
name: Create a Linux *.tar.gz bundle
name: Create a Linux *.tar.gz bundle for x86
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
@@ -121,8 +121,57 @@ jobs:
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Generate license file
run: script/generate-licenses
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
- name: Upload Zed Nightly
run: script/upload-nightly linux-targz
bundle-linux-arm:
timeout-minutes: 60
name: Create a Linux *.tar.gz bundle for ARM
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- hosted-linux-arm-1
needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
clean: false
- name: "Setup jq"
uses: dcarbone/install-jq-action@v2
- name: Set up Clang
run: |
sudo apt-get update
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@v1
with:
mold-version: 2.32.0
- name: rustup
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Set release channel to nightly
run: |
set -euo pipefail
version=$(git rev-parse --short HEAD)
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Create Linux .tar.gz bundle
run: script/bundle-linux

236
Cargo.lock generated
View File

@@ -87,7 +87,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6d1ea4484c8676f295307a4892d478c70ac8da1dbd8c7c10830a504b7f1022f"
dependencies = [
"base64 0.22.0",
"bitflags 2.4.2",
"bitflags 2.6.0",
"home",
"libc",
"log",
@@ -110,7 +110,7 @@ version = "0.24.1-dev"
source = "git+https://github.com/alacritty/alacritty?rev=cacdb5bb3b72bad2c729227537979d95af75978f#cacdb5bb3b72bad2c729227537979d95af75978f"
dependencies = [
"base64 0.22.0",
"bitflags 2.4.2",
"bitflags 2.6.0",
"home",
"libc",
"log",
@@ -341,8 +341,9 @@ dependencies = [
[[package]]
name = "ashpd"
version = "0.9.0"
source = "git+https://github.com/bilelmoussaoui/ashpd?rev=29f2e1a#29f2e1a6f4b0911f504658f5f4630c02e01b13f2"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a"
dependencies = [
"async-fs 2.1.1",
"async-net 2.0.0",
@@ -1583,7 +1584,16 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
"bit-vec 0.6.3",
]
[[package]]
name = "bit-set"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
dependencies = [
"bit-vec 0.7.0",
]
[[package]]
@@ -1592,6 +1602,12 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit-vec"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bit_field"
version = "0.10.2"
@@ -1606,9 +1622,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
dependencies = [
"serde",
]
@@ -1634,11 +1650,11 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.4.0"
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
dependencies = [
"ash",
"ash-window",
"bitflags 2.4.2",
"bitflags 2.6.0",
"block",
"bytemuck",
"codespan-reporting",
@@ -1664,7 +1680,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.2.1"
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
dependencies = [
"proc-macro2",
"quote",
@@ -1674,7 +1690,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7#21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7"
source = "git+https://github.com/zed-industries/blade?rev=a477c2008db27db0b9f745715e119b3ee7ab7818#a477c2008db27db0b9f745715e119b3ee7ab7818"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -1922,7 +1938,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"log",
"polling 3.3.2",
"rustix 0.38.32",
@@ -2391,16 +2407,6 @@ dependencies = [
"worktree",
]
[[package]]
name = "clipboard-win"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342"
dependencies = [
"lazy-bytes-cast",
"winapi",
]
[[package]]
name = "clock"
version = "0.1.0"
@@ -2859,7 +2865,7 @@ name = "cosmic-text"
version = "0.11.2"
source = "git+https://github.com/pop-os/cosmic-text?rev=542b20c#542b20ca4376a3b5de5fa629db1a4ace44e18e0c"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"fontdb",
"log",
"rangemap",
@@ -3396,11 +3402,13 @@ dependencies = [
"ctor",
"editor",
"env_logger",
"feature_flags",
"futures 0.3.28",
"gpui",
"language",
"log",
"lsp",
"multi_buffer",
"pretty_assertions",
"project",
"rand 0.8.5",
@@ -3605,6 +3613,7 @@ dependencies = [
"linkify",
"log",
"lsp",
"markdown",
"multi_buffer",
"ordered-float 2.10.0",
"parking_lot",
@@ -3978,6 +3987,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"collections",
"db",
"editor",
"extension",
@@ -3997,6 +4007,7 @@ dependencies = [
"theme_selector",
"ui",
"util",
"vim",
"workspace",
]
@@ -4012,7 +4023,7 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05"
dependencies = [
"bit-set",
"bit-set 0.5.3",
"regex",
]
@@ -4069,7 +4080,7 @@ name = "feedback"
version = "0.1.0"
dependencies = [
"anyhow",
"bitflags 2.4.2",
"bitflags 2.6.0",
"client",
"db",
"editor",
@@ -4269,7 +4280,7 @@ checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770"
dependencies = [
"fontconfig-parser",
"log",
"memmap2 0.9.4",
"memmap2",
"slotmap",
"tinyvec",
"ttf-parser",
@@ -4403,7 +4414,7 @@ dependencies = [
name = "fsevent"
version = "0.1.0"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"core-foundation",
"fsevent-sys 3.1.0",
"parking_lot",
@@ -4714,7 +4725,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"libc",
"libgit2-sys",
"log",
@@ -4825,7 +4836,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"gpu-alloc-types",
]
@@ -4846,7 +4857,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
]
[[package]]
@@ -4867,7 +4878,6 @@ dependencies = [
"calloop",
"calloop-wayland-source",
"cbindgen",
"clipboard-win",
"cocoa",
"collections",
"core-foundation",
@@ -4896,6 +4906,7 @@ dependencies = [
"num_cpus",
"objc",
"oo7",
"open",
"parking",
"parking_lot",
"pathfinder_geometry",
@@ -5058,6 +5069,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"extension",
"fs",
"futures 0.3.28",
"gpui",
@@ -5103,7 +5115,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f7acb9683d7c7068aa46d47557bfa4e35a277964b350d9504a87b03610163fd"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"byteorder",
"heed-traits",
"heed-types",
@@ -5707,6 +5719,25 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]]
name = "isahc"
version = "1.7.2"
@@ -6052,12 +6083,6 @@ dependencies = [
"workspace",
]
[[package]]
name = "lazy-bytes-cast"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b"
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -6524,15 +6549,6 @@ dependencies = [
"rustix 0.38.32",
]
[[package]]
name = "memmap2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
dependencies = [
"libc",
]
[[package]]
name = "memmap2"
version = "0.9.4"
@@ -6666,17 +6682,17 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "naga"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae585df4b6514cf8842ac0f1ab4992edc975892704835b549cf818dc0191249e"
version = "0.20.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=425526828f738c95ec50b016c6a761bc00d2fb25#425526828f738c95ec50b016c6a761bc00d2fb25"
dependencies = [
"bit-set",
"bitflags 2.4.2",
"arrayvec",
"bit-set 0.6.0",
"bitflags 2.6.0",
"cfg_aliases",
"codespan-reporting",
"hexf-parse",
"indexmap 2.2.6",
"log",
"num-traits",
"rustc-hash",
"spirv",
"termcolor",
@@ -6772,7 +6788,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"cfg-if",
"libc",
"memoffset",
@@ -6784,7 +6800,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -6853,7 +6869,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"crossbeam-channel",
"filetime",
"fsevent-sys 4.1.0",
@@ -7066,6 +7082,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "nvim-rs"
version = "0.6.0-pre"
@@ -7195,6 +7220,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "open"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3"
dependencies = [
"is-wsl",
"libc",
"pathdiff",
]
[[package]]
name = "open_ai"
version = "0.1.0"
@@ -7215,7 +7251,7 @@ version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
@@ -8042,6 +8078,7 @@ dependencies = [
"serde_json",
"settings",
"sha2 0.10.7",
"shellexpand 2.1.2",
"shlex",
"similar",
"smol",
@@ -8183,6 +8220,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"futures 0.3.28",
"prost",
"prost-build",
"serde",
@@ -8229,7 +8267,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"memchr",
"unicase",
]
@@ -8278,7 +8316,9 @@ dependencies = [
"search",
"settings",
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -8485,7 +8525,6 @@ dependencies = [
"client",
"dev_server_projects",
"editor",
"feature_flags",
"fuzzy",
"gpui",
"language",
@@ -8663,6 +8702,7 @@ dependencies = [
"image",
"language",
"log",
"multi_buffer",
"project",
"runtimelib",
"schemars",
@@ -9041,7 +9081,7 @@ version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"errno 0.3.8",
"itoa",
"libc",
@@ -9116,7 +9156,7 @@ version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"bytemuck",
"libm",
"smallvec",
@@ -9339,7 +9379,7 @@ version = "0.1.0"
dependencies = [
"any_vec",
"anyhow",
"bitflags 2.4.2",
"bitflags 2.6.0",
"client",
"collections",
"editor",
@@ -9767,13 +9807,13 @@ dependencies = [
[[package]]
name = "simplelog"
version = "0.9.0"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
dependencies = [
"chrono",
"log",
"termcolor",
"time",
]
[[package]]
@@ -9926,12 +9966,11 @@ dependencies = [
[[package]]
name = "spirv"
version = "0.2.0+1.5.4"
version = "0.3.0+sdk-1.3.268.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
dependencies = [
"bitflags 1.3.2",
"num-traits",
"bitflags 2.6.0",
]
[[package]]
@@ -10107,7 +10146,7 @@ dependencies = [
"atoi",
"base64 0.21.7",
"bigdecimal",
"bitflags 2.4.2",
"bitflags 2.6.0",
"byteorder",
"bytes 1.5.0",
"chrono",
@@ -10154,7 +10193,7 @@ dependencies = [
"atoi",
"base64 0.21.7",
"bigdecimal",
"bitflags 2.4.2",
"bitflags 2.6.0",
"byteorder",
"chrono",
"crc",
@@ -10576,7 +10615,7 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aef1f9d4c1dbdd1cb3a63be9efd2f04d8ddbc919d46112982c76818ffc2f1a7"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"cap-fs-ext",
"cap-std",
"fd-lock",
@@ -10717,9 +10756,9 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.1.3"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
@@ -10736,6 +10775,7 @@ dependencies = [
"gpui",
"libc",
"rand 0.8.5",
"release_channel",
"schemars",
"serde",
"serde_derive",
@@ -10879,18 +10919,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.60"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.60"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
@@ -10941,7 +10981,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
@@ -11300,7 +11342,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"bytes 1.5.0",
"futures-core",
"futures-util",
@@ -12071,7 +12113,7 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"cursor-icon",
"log",
"serde",
@@ -12223,7 +12265,7 @@ version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"indexmap 2.2.6",
"semver",
]
@@ -12490,7 +12532,7 @@ checksum = "371d828b6849ea06d598ae7dd1c316e8dd9e99b76f77d93d5886cb25c7f8e188"
dependencies = [
"anyhow",
"async-trait",
"bitflags 2.4.2",
"bitflags 2.6.0",
"bytes 1.5.0",
"cap-fs-ext",
"cap-net-ext",
@@ -12577,7 +12619,7 @@ version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"rustix 0.38.32",
"wayland-backend",
"wayland-scanner",
@@ -12600,7 +12642,7 @@ version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"wayland-backend",
"wayland-client",
"wayland-scanner",
@@ -12612,7 +12654,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"wayland-backend",
"wayland-client",
"wayland-protocols",
@@ -12731,7 +12773,7 @@ checksum = "ae1136a209614ace00b0c11f04dc7cf42540773be3b22eff6ad165110aba29c1"
dependencies = [
"anyhow",
"async-trait",
"bitflags 2.4.2",
"bitflags 2.6.0",
"thiserror",
"tracing",
"wasmtime",
@@ -13161,7 +13203,7 @@ version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"windows-sys 0.52.0",
]
@@ -13180,7 +13222,7 @@ version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
"wit-bindgen-rt",
"wit-bindgen-rust-macro",
]
@@ -13236,7 +13278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
dependencies = [
"anyhow",
"bitflags 2.4.2",
"bitflags 2.6.0",
"indexmap 2.2.6",
"log",
"serde",
@@ -13294,6 +13336,7 @@ dependencies = [
"derive_more",
"dev_server_projects",
"env_logger",
"file_icons",
"fs",
"futures 0.3.28",
"gpui",
@@ -13442,18 +13485,17 @@ name = "xim-parser"
version = "0.2.1"
source = "git+https://github.com/npmania/xim-rs?rev=27132caffc5b9bc9c432ca4afad184ab6e7c16af#27132caffc5b9bc9c432ca4afad184ab6e7c16af"
dependencies = [
"bitflags 2.4.2",
"bitflags 2.6.0",
]
[[package]]
name = "xkbcommon"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
source = "git+https://github.com/ConradIrwin/xkbcommon-rs?rev=fcbb4612185cc129ceeff51d22f7fb51810a03b2#fcbb4612185cc129ceeff51d22f7fb51810a03b2"
dependencies = [
"as-raw-xcb-connection",
"libc",
"memmap2 0.8.0",
"memmap2",
"xkeysym",
]
@@ -13714,7 +13756,7 @@ dependencies = [
[[package]]
name = "zed_dart"
version = "0.0.2"
version = "0.0.3"
dependencies = [
"zed_extension_api 0.0.6",
]
@@ -13828,9 +13870,9 @@ dependencies = [
[[package]]
name = "zed_php"
version = "0.0.6"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.0.4",
"zed_extension_api 0.0.6",
]
[[package]]
@@ -13864,7 +13906,7 @@ dependencies = [
[[package]]
name = "zed_svelte"
version = "0.0.1"
version = "0.0.3"
dependencies = [
"zed_extension_api 0.0.6",
]

View File

@@ -274,7 +274,7 @@ zed_actions = { path = "crates/zed_actions" }
alacritty_terminal = "0.23"
any_vec = "0.13"
anyhow = "1.0.57"
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "29f2e1a" }
ashpd = "0.9.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = { version = "0.1" }
async-fs = "1.6"
@@ -284,10 +284,10 @@ async-trait = "0.1"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.13"
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
blade-util = { git = "https://github.com/kvark/blade", rev = "21a56f780e21e4cb42c70a1dcf4b59842d1ad7f7" }
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
blade-macros = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
blade-util = { git = "https://github.com/zed-industries/blade", rev = "a477c2008db27db0b9f745715e119b3ee7ab7818" }
cap-std = "3.0"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
@@ -365,6 +365,7 @@ shellexpand = "2.1.0"
shlex = "1.3.0"
signal-hook = "0.3.17"
similar = "1.3"
simplelog = "0.12.2"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
strum = { version = "0.25.0", features = ["derive"] }
@@ -451,6 +452,7 @@ features = [
"Win32_System_Com_StructuredStorage",
"Win32_System_DataExchange",
"Win32_System_LibraryLoader",
"Win32_System_Memory",
"Win32_System_Ole",
"Win32_System_SystemInformation",
"Win32_System_SystemServices",

View File

@@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.49574 4.74787L5.99574 7.25214L8.49574 4.74787" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@@ -1,14 +1,13 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_32_58)">
<path d="M19 7C20.1046 7 21 6.10457 21 5C21 3.89543 20.1046 3 19 3C17.8954 3 17 3.89543 17 5C17 6.10457 17.8954 7 19 7Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 21C6.10457 21 7 20.1046 7 19C7 17.8954 6.10457 17 5 17C3.89543 17 3 17.8954 3 19C3 20.1046 3.89543 21 5 21Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3999 21.9C12.3227 22.2159 14.2958 21.9632 16.0769 21.173C17.858 20.3827 19.3694 19.0893 20.4254 17.4517C21.4814 15.8142 22.036 13.9037 22.021 11.9553C22.006 10.0068 21.422 8.10512 20.3409 6.48401" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.4998 2.10002C11.5849 1.8076 9.62631 2.07763 7.86198 2.87732C6.09765 3.677 4.60356 4.9719 3.56126 6.60468C2.51896 8.23745 1.97332 10.1378 1.99063 12.0748C2.00795 14.0118 2.58749 15.9021 3.65882 17.516" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_62_95)">
<path d="M4.5 6C3.67157 6 3 5.32843 3 4.5C3 3.67157 3.67157 3 4.5 3C5.32843 3 6 3.67157 6 4.5C6 5.32843 5.32843 6 4.5 6Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.54433 13.4334C6.87775 13.5227 7.22046 13.3249 7.3098 12.9914C7.39914 12.658 7.20127 12.3153 6.86786 12.226L6.54433 13.4334ZM3.77426 6.86772L3.93603 6.26401L2.72862 5.94049L2.56686 6.54419L3.77426 6.86772ZM6.86786 12.226C4.53394 11.6006 3.14889 9.20163 3.77426 6.86772L2.56686 6.54419C1.76281 9.54494 3.54359 12.6293 6.54433 13.4334L6.86786 12.226Z" fill="white"/>
<path d="M11.5 13C10.6716 13 10 12.3284 10 11.5C10 10.6716 10.6716 10 11.5 10C12.3284 10 13 10.6716 13 11.5C13 12.3284 12.3284 13 11.5 13Z" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.1875 4.21113C9.88852 4.03854 9.7861 3.65629 9.95869 3.35736C10.1313 3.05843 10.5135 2.95601 10.8125 3.12859L10.1875 4.21113ZM11.7888 10.1875C12.9969 8.09496 12.28 5.41925 10.1875 4.21113L10.8125 3.12859C13.5028 4.6819 14.4246 8.12209 12.8713 10.8125L11.7888 10.1875Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_32_58">
<rect width="24" height="24" fill="white"/>
<clipPath id="clip0_62_95">
<rect width="16" height="16" fill="white" transform="matrix(-1 0 0 1 16 0)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -19,7 +19,6 @@
"escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"shift-enter": "picker::UseSelectedQuery",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
"ctrl-shift-w": "workspace::CloseWindow",
@@ -79,10 +78,8 @@
"shift-down": "editor::SelectDown",
"shift-left": "editor::SelectLeft",
"shift-right": "editor::SelectRight",
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::AddSelectionAbove",
"ctrl-shift-down": "editor::AddSelectionBelow",
"ctrl-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
"ctrl-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"ctrl-shift-home": "editor::SelectToBeginning",
"ctrl-shift-end": "editor::SelectToEnd",
"ctrl-a": "editor::SelectAll",
@@ -100,6 +97,7 @@
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
"ctrl-'": "editor::ToggleHunkDiff",
"ctrl-\"": "editor::ExpandAllHunkDiffs",
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame"
}
},
@@ -260,20 +258,19 @@
"bindings": {
"ctrl-[": "editor::Outdent",
"ctrl-]": "editor::Indent",
"shift-alt-up": "editor::AddSelectionAbove",
"shift-alt-down": "editor::AddSelectionBelow",
"shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
"shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
"ctrl-alt-shift-up": "editor::DuplicateLineUp",
"ctrl-alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
"ctrl-shift-down": "editor::SelectSmallerSyntaxNode", //todo(linux) tmp keybinding
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }],
"ctrl-shift-l": "editor::SelectAllMatches",
"ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": false }],
"alt-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection
"alt-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection
"ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
"ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word
"ctrl-shift-down": ["editor::SelectNext", { "replace_newest": false }], // Add selection to Next Find Match
"ctrl-shift-up": ["editor::SelectPrevious", { "replace_newest": false }],
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }],
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }],
"ctrl-k ctrl-i": "editor::Hover",
@@ -326,7 +323,7 @@
"alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem",
"ctrl-alt--": "pane::GoBack",
"ctrl-alt-shift--": "pane::GoForward",
"ctrl-alt-_": "pane::GoForward",
"ctrl-shift-t": "pane::ReopenClosedItem",
"f3": "search::SelectNextMatch",
"shift-f3": "search::SelectPrevMatch",
@@ -397,7 +394,7 @@
"bindings": {
"ctrl-shift-k": "editor::DeleteLine",
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-j": "editor::JoinLines",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
@@ -566,6 +563,13 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "Picker > Editor",
"bindings": {
"tab": "picker::ConfirmCompletion",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }]
}
},
{
"context": "ChannelModal > Picker > Editor",
"bindings": {

View File

@@ -83,7 +83,7 @@
"ctrl-n": "editor::MoveDown",
"ctrl-b": "editor::MoveLeft",
"ctrl-f": "editor::MoveRight",
"ctrl-l": "editor::NextScreen",
"ctrl-l": "editor::ScrollCursorCenter",
"alt-left": "editor::MoveToPreviousWordStart",
"alt-b": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd",
@@ -102,9 +102,9 @@
"ctrl-shift-b": "editor::SelectLeft",
"shift-right": "editor::SelectRight",
"ctrl-shift-f": "editor::SelectRight",
"alt-shift-left": "editor::SelectToPreviousWordStart",
"alt-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
"alt-shift-b": "editor::SelectToPreviousWordStart",
"alt-shift-right": "editor::SelectToNextWordEnd",
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"alt-shift-f": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
@@ -126,7 +126,8 @@
"cmd-alt-z": "editor::RevertSelectedHunks",
"cmd-'": "editor::ToggleHunkDiff",
"cmd-\"": "editor::ExpandAllHunkDiffs",
"cmd-alt-g b": "editor::ToggleGitBlame"
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp"
}
},
{
@@ -301,19 +302,20 @@
"bindings": {
"cmd-[": "editor::Outdent",
"cmd-]": "editor::Indent",
"cmd-alt-up": "editor::AddSelectionAbove",
"cmd-alt-up": "editor::AddSelectionAbove", // Insert cursor above
"cmd-ctrl-p": "editor::AddSelectionAbove",
"cmd-alt-down": "editor::AddSelectionBelow",
"cmd-alt-down": "editor::AddSelectionBelow", // Insert cursor below
"cmd-ctrl-n": "editor::AddSelectionBelow",
"cmd-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
"alt-shift-up": "editor::DuplicateLineUp",
"alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-right": "editor::SelectLargerSyntaxNode",
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode",
"cmd-d": ["editor::SelectNext", { "replace_newest": false }],
"cmd-shift-l": "editor::SelectAllMatches",
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // Add selection to Next Find Match
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
"cmd-f2": "editor::SelectAllMatches", // Select all occurrences of current word
"ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }],
"cmd-k cmd-d": ["editor::SelectNext", { "replace_newest": true }],
"cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }],
@@ -593,6 +595,14 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "Picker > Editor",
"bindings": {
"tab": "picker::ConfirmCompletion",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }]
}
},
{
"context": "ChannelModal > Picker > Editor",
"bindings": {
@@ -612,14 +622,6 @@
"ctrl-backspace": "tab_switcher::CloseSelectedItem"
}
},
{
"context": "Picker",
"bindings": {
"f2": "picker::UseSelectedQuery",
"alt-enter": ["picker::ConfirmInput", { "secondary": false }],
"cmd-alt-enter": ["picker::ConfirmInput", { "secondary": true }]
}
},
{
"context": "Terminal",
"bindings": {

View File

@@ -0,0 +1,21 @@
// Zed keymap
//
// For information on binding keys, see the Zed
// documentation: https://zed.dev/docs/key-bindings
//
// To see the default key bindings run `zed: Open Default Keymap`
// from the command palette.
[
{
"context": "Workspace",
"bindings": {
// "shift shift": "file_finder::Toggle"
}
},
{
"context": "Editor",
"bindings": {
// "j k": ["workspace::SendKeystrokes", "escape"]
}
}
]

View File

@@ -25,6 +25,7 @@
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
"ctrl-shift-m": "markdown::OpenPreviewToTheSide" // markdown-preview:toggle
}
},

View File

@@ -13,6 +13,7 @@
"ctrl-shift-j": "editor::JoinLines",
"ctrl-d": "editor::DuplicateLineDown",
"ctrl-y": "editor::DeleteLine",
"ctrl-m": "editor::ScrollCursorCenter",
"ctrl-pagedown": "editor::MovePageDown",
"ctrl-pageup": "editor::MovePageUp",
// "ctrl-alt-shift-b": "editor::SelectToPreviousWordStart",

View File

@@ -3,10 +3,10 @@
"bindings": {
"ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-shift-]": "pane::ActivateNextItem",
"ctrl-pagedown": "pane::ActivatePrevItem",
"ctrl-pageup": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivatePrevItem"
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePrevItem"
}
},
{
@@ -24,6 +24,8 @@
"ctrl-shift-f12": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPrevHunk",
"ctrl-k ctrl-u": "editor::ConvertToUpperCase",
"ctrl-k ctrl-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"

View File

@@ -26,6 +26,7 @@
"cmd-shift-d": "editor::DuplicateLineDown",
"ctrl-cmd-up": "editor::MoveLineUp",
"ctrl-cmd-down": "editor::MoveLineDown",
"cmd-\\": "workspace::ToggleLeftDock",
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
}
},

View File

@@ -3,10 +3,10 @@
"bindings": {
"cmd-shift-[": "pane::ActivatePrevItem",
"cmd-shift-]": "pane::ActivateNextItem",
"ctrl-pagedown": "pane::ActivatePrevItem",
"ctrl-pageup": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivatePrevItem"
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePrevItem"
}
},
{
@@ -27,6 +27,7 @@
"ctrl-,": "editor::GoToPrevHunk",
"cmd-k cmd-u": "editor::ConvertToUpperCase",
"cmd-k cmd-l": "editor::ConvertToLowerCase",
"cmd-shift-j": "editor::JoinLines",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"

View File

@@ -1,12 +1,6 @@
[
{
"context": "ProjectPanel || Editor",
"bindings": {
"ctrl-6": "pane::AlternateFile"
}
},
{
"context": "Editor && VimControl && !VimWaiting && !menu",
"context": "VimControl && !menu",
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
@@ -18,6 +12,8 @@
"down": "vim::Down",
"enter": "vim::NextLineStart",
"ctrl-m": "vim::NextLineStart",
"+": "vim::NextLineStart",
"-": "vim::PreviousLineStart",
"tab": "vim::Tab",
"shift-tab": "vim::Tab",
"k": "vim::Up",
@@ -198,20 +194,20 @@
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w space": "editor::OpenExcerptsSplit",
"ctrl-w g space": "editor::OpenExcerptsSplit",
"-": "pane::RevealInProjectPanel"
"ctrl-6": "pane::AlternateFile"
}
},
{
// escape is in its own section so that it cancels a pending count.
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"context": "VimControl && VimCount",
"bindings": {
"0": ["vim::Number", 0]
}
},
{
"context": "vim_mode == normal",
"bindings": {
"escape": "editor::Cancel",
"ctrl-[": "editor::Cancel"
}
},
{
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
"ctrl-[": "editor::Cancel",
".": "vim::Repeat",
"c": ["vim::PushOperator", "Change"],
"shift-c": "vim::ChangeToEndOfLine",
@@ -255,127 +251,12 @@
"] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",
"[ c": "editor::GoToPrevHunk"
"[ c": "editor::GoToPrevHunk",
"g c c": "vim::ToggleComments"
}
},
{
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
"bindings": {
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
}
},
{
"context": "Editor && VimCount && vim_mode != insert",
"bindings": {
"0": ["vim::Number", 0]
}
},
{
"context": "Editor && vim_operator == c",
"bindings": {
"c": "vim::CurrentLine",
"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_operator == gu",
"bindings": {
"g u": "vim::CurrentLine",
"u": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == gU",
"bindings": {
"g shift-u": "vim::CurrentLine",
"shift-u": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == g~",
"bindings": {
"g ~": "vim::CurrentLine",
"~": "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 && vim_operator == ys",
"bindings": {
"s": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == >",
"bindings": {
">": "vim::CurrentLine"
}
},
{
"context": "Editor && vim_operator == <",
"bindings": {
"<": "vim::CurrentLine"
}
},
{
"context": "Editor && VimObject",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"{": "vim::CurlyBrackets",
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::Argument"
}
},
{
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
"context": "vim_mode == visual",
"bindings": {
"u": "vim::ConvertToLowerCase",
"U": "vim::ConvertToUpperCase",
@@ -410,23 +291,16 @@
">": "vim::Indent",
"<": "vim::Outdent",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }]
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"g c": "vim::ToggleComments",
"\"": ["vim::PushOperator", "Register"],
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
}
},
{
"context": "Editor && vim_mode == normal && !VimWaiting",
"bindings": {
"g c c": "vim::ToggleComments"
}
},
{
"context": "Editor && vim_mode == visual",
"bindings": {
"g c": "vim::ToggleComments"
}
},
{
"context": "Editor && vim_mode == insert",
"context": "vim_mode == insert",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
@@ -445,30 +319,118 @@
}
},
{
"context": "Editor && vim_mode == replace",
"context": "vim_mode == replace",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"backspace": "vim::UndoReplace",
"tab": "vim::Tab",
"enter": "vim::Enter",
"backspace": "vim::UndoReplace"
"enter": "vim::Enter"
}
},
{
"context": "Editor && vim_mode != replace && VimWaiting",
"context": "vim_mode == waiting",
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter",
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"]
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators"
}
},
{
"context": "Editor && vim_mode == insert && VimWaiting",
"context": "vim_mode == operator",
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore"
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators"
}
},
{
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"{": "vim::CurlyBrackets",
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::Argument"
}
},
{
"context": "vim_operator == c",
"bindings": {
"c": "vim::CurrentLine",
"d": "editor::Rename", // zed specific
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
}
},
{
"context": "vim_operator == d",
"bindings": {
"d": "vim::CurrentLine",
"s": ["vim::PushOperator", "DeleteSurrounds"]
}
},
{
"context": "vim_operator == gu",
"bindings": {
"g u": "vim::CurrentLine",
"u": "vim::CurrentLine"
}
},
{
"context": "vim_operator == gU",
"bindings": {
"g shift-u": "vim::CurrentLine",
"shift-u": "vim::CurrentLine"
}
},
{
"context": "vim_operator == g~",
"bindings": {
"g ~": "vim::CurrentLine",
"~": "vim::CurrentLine"
}
},
{
"context": "vim_operator == y",
"bindings": {
"y": "vim::CurrentLine",
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
}
},
{
"context": "vim_operator == ys",
"bindings": {
"s": "vim::CurrentLine"
}
},
{
"context": "vim_operator == >",
"bindings": {
">": "vim::CurrentLine"
}
},
{
"context": "vim_operator == <",
"bindings": {
"<": "vim::CurrentLine"
}
},
{
@@ -508,7 +470,8 @@
"x": "project_panel::RevealInFileManager",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent"
"-": "project_panel::SelectParent",
"ctrl-6": "pane::AlternateFile"
}
},
{

View File

@@ -47,7 +47,7 @@
// },
"buffer_line_height": "comfortable",
// The name of a font to use for rendering text in the UI
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
// (On macOS) You can set this to ".SystemUIFont" to use the system font
"ui_font_family": "Zed Plex Sans",
// The OpenType features to enable for text in the UI
"ui_font_features": {
@@ -94,6 +94,9 @@
// 3. Never close the window
// "when_closing_with_no_tabs": "keep_window_open",
"when_closing_with_no_tabs": "platform_default",
// Whether to use the system provided dialogs for Open and Save As.
// When set to false, Zed will use the built-in keyboard-first pickers.
"use_system_path_prompts": true,
// Whether the cursor blinks in the editor.
"cursor_blink": true,
// How to highlight the current line in the editor.
@@ -116,6 +119,11 @@
// The debounce delay before re-querying the language server for completion
// documentation when not included in original completion list.
"completion_documentation_secondary_query_debounce": 300,
// Show method signatures in the editor, when inside parentheses.
"auto_signature_help": false,
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": true,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if softwrap is set to 'preferred_line_length', and will show any
@@ -128,14 +136,7 @@
// The default number of lines to expand excerpts in the multibuffer by.
"expand_excerpt_lines": 3,
// Globs to match against file paths to determine if a file is private.
"private_files": [
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
"private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
@@ -262,6 +263,8 @@
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
//
@@ -431,7 +434,9 @@
// Show git status colors in the editor tabs.
"git_status": false,
// Position of the close button on the editor tabs.
"close_position": "right"
"close_position": "right",
// Whether to show the file icon for a tab.
"file_icons": false
},
// Settings related to preview tabs.
"preview_tabs": {
@@ -539,6 +544,14 @@
// "delay_ms": 600
}
},
// Configuration for how direnv configuration should be loaded. May take 2 values:
// 1. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
// 2. Load direnv configuration using `direnv export json` directly.
// This can help with some shells that otherwise would not detect
// the direnv environment, such as nushell or elvish.
// "load_direnv": "direct"
"load_direnv": "shell_hook",
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"disabled_globs": [".env"]
@@ -714,10 +727,12 @@
}
},
"C": {
"format_on_save": "off"
"format_on_save": "off",
"use_on_type_format": false
},
"C++": {
"format_on_save": "off"
"format_on_save": "off",
"use_on_type_format": false
},
"CSS": {
"prettier": {
@@ -769,11 +784,13 @@
},
"Markdown": {
"format_on_save": "off",
"use_on_type_format": false,
"prettier": {
"allowed": true
}
},
"PHP": {
"language_servers": ["phpactor", "!intelephense", "..."],
"prettier": {
"allowed": true,
"plugins": ["@prettier/plugin-php"],

View File

@@ -4,8 +4,8 @@
// documentation: https://zed.dev/docs/configuring-zed
//
// To see all of Zed's default settings without changing your
// custom settings, run the `open default settings` command
// from the command palette or from `Zed` application menu.
// custom settings, run the `zed: Open Default Settings` command
// from the command palette
{
"ui_font_size": 16,
"buffer_font_size": 16,

View File

@@ -38,6 +38,7 @@
"icon.accent": "#10a793ff",
"status_bar.background": "#262933ff",
"title_bar.background": "#262933ff",
"title_bar.inactive_background": "#21242bff",
"toolbar.background": "#1e2025ff",
"tab_bar.background": "#21242bff",
"tab.inactive_background": "#21242bff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#566ddaff",
"status_bar.background": "#3a353fff",
"title_bar.background": "#3a353fff",
"title_bar.inactive_background": "#221f26ff",
"toolbar.background": "#19171cff",
"tab_bar.background": "#221f26ff",
"tab.inactive_background": "#221f26ff",
@@ -422,6 +423,7 @@
"icon.accent": "#586cdaff",
"status_bar.background": "#bfbcc5ff",
"title_bar.background": "#bfbcc5ff",
"title_bar.inactive_background": "#e6e3ebff",
"toolbar.background": "#efecf4ff",
"tab_bar.background": "#e6e3ebff",
"tab.inactive_background": "#e6e3ebff",
@@ -806,6 +808,7 @@
"icon.accent": "#6684e0ff",
"status_bar.background": "#45433bff",
"title_bar.background": "#45433bff",
"title_bar.inactive_background": "#262622ff",
"toolbar.background": "#20201dff",
"tab_bar.background": "#262622ff",
"tab.inactive_background": "#262622ff",
@@ -1190,6 +1193,7 @@
"icon.accent": "#6684dfff",
"status_bar.background": "#cecab4ff",
"title_bar.background": "#cecab4ff",
"title_bar.inactive_background": "#eeebd7ff",
"toolbar.background": "#fefbecff",
"tab_bar.background": "#eeebd7ff",
"tab.inactive_background": "#eeebd7ff",
@@ -1574,6 +1578,7 @@
"icon.accent": "#36a165ff",
"status_bar.background": "#424136ff",
"title_bar.background": "#424136ff",
"title_bar.inactive_background": "#2c2b23ff",
"toolbar.background": "#22221bff",
"tab_bar.background": "#2c2b23ff",
"tab.inactive_background": "#2c2b23ff",
@@ -1958,6 +1963,7 @@
"icon.accent": "#37a165ff",
"status_bar.background": "#c5c4b9ff",
"title_bar.background": "#c5c4b9ff",
"title_bar.inactive_background": "#ebeae3ff",
"toolbar.background": "#f4f3ecff",
"tab_bar.background": "#ebeae3ff",
"tab.inactive_background": "#ebeae3ff",
@@ -2342,6 +2348,7 @@
"icon.accent": "#407ee6ff",
"status_bar.background": "#443c39ff",
"title_bar.background": "#443c39ff",
"title_bar.inactive_background": "#27211eff",
"toolbar.background": "#1b1918ff",
"tab_bar.background": "#27211eff",
"tab.inactive_background": "#27211eff",
@@ -2726,6 +2733,7 @@
"icon.accent": "#407ee6ff",
"status_bar.background": "#ccc7c5ff",
"title_bar.background": "#ccc7c5ff",
"title_bar.inactive_background": "#e9e6e4ff",
"toolbar.background": "#f0eeedff",
"tab_bar.background": "#e9e6e4ff",
"tab.inactive_background": "#e9e6e4ff",
@@ -3110,6 +3118,7 @@
"icon.accent": "#5169ebff",
"status_bar.background": "#433a43ff",
"title_bar.background": "#433a43ff",
"title_bar.inactive_background": "#252025ff",
"toolbar.background": "#1b181bff",
"tab_bar.background": "#252025ff",
"tab.inactive_background": "#252025ff",
@@ -3494,6 +3503,7 @@
"icon.accent": "#5169ebff",
"status_bar.background": "#c6b8c6ff",
"title_bar.background": "#c6b8c6ff",
"title_bar.inactive_background": "#e0d5e0ff",
"toolbar.background": "#f7f3f7ff",
"tab_bar.background": "#e0d5e0ff",
"tab.inactive_background": "#e0d5e0ff",
@@ -3878,6 +3888,7 @@
"icon.accent": "#267eadff",
"status_bar.background": "#33444dff",
"title_bar.background": "#33444dff",
"title_bar.inactive_background": "#1c2529ff",
"toolbar.background": "#161b1dff",
"tab_bar.background": "#1c2529ff",
"tab.inactive_background": "#1c2529ff",
@@ -4262,6 +4273,7 @@
"icon.accent": "#267eadff",
"status_bar.background": "#a6cadcff",
"title_bar.background": "#a6cadcff",
"title_bar.inactive_background": "#cdeaf9ff",
"toolbar.background": "#ebf8ffff",
"tab_bar.background": "#cdeaf9ff",
"tab.inactive_background": "#cdeaf9ff",
@@ -4646,6 +4658,7 @@
"icon.accent": "#7272caff",
"status_bar.background": "#3b3535ff",
"title_bar.background": "#3b3535ff",
"title_bar.inactive_background": "#252020ff",
"toolbar.background": "#1b1818ff",
"tab_bar.background": "#252020ff",
"tab.inactive_background": "#252020ff",
@@ -5030,6 +5043,7 @@
"icon.accent": "#7272caff",
"status_bar.background": "#c1bbbbff",
"title_bar.background": "#c1bbbbff",
"title_bar.inactive_background": "#ebe3e3ff",
"toolbar.background": "#f4ececff",
"tab_bar.background": "#ebe3e3ff",
"tab.inactive_background": "#ebe3e3ff",
@@ -5414,6 +5428,7 @@
"icon.accent": "#468b8fff",
"status_bar.background": "#353f39ff",
"title_bar.background": "#353f39ff",
"title_bar.inactive_background": "#1f2621ff",
"toolbar.background": "#171c19ff",
"tab_bar.background": "#1f2621ff",
"tab.inactive_background": "#1f2621ff",
@@ -5798,6 +5813,7 @@
"icon.accent": "#488b90ff",
"status_bar.background": "#bcc5bfff",
"title_bar.background": "#bcc5bfff",
"title_bar.inactive_background": "#e3ebe6ff",
"toolbar.background": "#ecf4eeff",
"tab_bar.background": "#e3ebe6ff",
"tab.inactive_background": "#e3ebe6ff",
@@ -6182,6 +6198,7 @@
"icon.accent": "#3e62f4ff",
"status_bar.background": "#3b453bff",
"title_bar.background": "#3b453bff",
"title_bar.inactive_background": "#1f231fff",
"toolbar.background": "#131513ff",
"tab_bar.background": "#1f231fff",
"tab.inactive_background": "#1f231fff",
@@ -6566,6 +6583,7 @@
"icon.accent": "#3e61f4ff",
"status_bar.background": "#b4ceb4ff",
"title_bar.background": "#b4ceb4ff",
"title_bar.inactive_background": "#daeedaff",
"toolbar.background": "#f3faf3ff",
"tab_bar.background": "#daeedaff",
"tab.inactive_background": "#daeedaff",
@@ -6950,6 +6968,7 @@
"icon.accent": "#3e8ed0ff",
"status_bar.background": "#3e4769ff",
"title_bar.background": "#3e4769ff",
"title_bar.inactive_background": "#262f51ff",
"toolbar.background": "#202646ff",
"tab_bar.background": "#262f51ff",
"tab.inactive_background": "#262f51ff",
@@ -7334,6 +7353,7 @@
"icon.accent": "#3e8fd0ff",
"status_bar.background": "#c1c5d8ff",
"title_bar.background": "#c1c5d8ff",
"title_bar.inactive_background": "#e5e8f5ff",
"toolbar.background": "#f5f7ffff",
"tab_bar.background": "#e5e8f5ff",
"tab.inactive_background": "#e5e8f5ff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#5ac1feff",
"status_bar.background": "#313337ff",
"title_bar.background": "#313337ff",
"title_bar.inactive_background": "#1f2127ff",
"toolbar.background": "#0d1016ff",
"tab_bar.background": "#1f2127ff",
"tab.inactive_background": "#1f2127ff",
@@ -407,6 +408,7 @@
"icon.accent": "#3b9ee5ff",
"status_bar.background": "#dcdddeff",
"title_bar.background": "#dcdddeff",
"title_bar.inactive_background": "#ececedff",
"toolbar.background": "#fcfcfcff",
"tab_bar.background": "#ececedff",
"tab.inactive_background": "#ececedff",
@@ -776,6 +778,7 @@
"icon.accent": "#72cffeff",
"status_bar.background": "#464a52ff",
"title_bar.background": "#464a52ff",
"title_bar.inactive_background": "#353944ff",
"toolbar.background": "#242835ff",
"tab_bar.background": "#353944ff",
"tab.inactive_background": "#353944ff",

View File

@@ -47,6 +47,7 @@
"icon.accent": "#83a598ff",
"status_bar.background": "#4c4642ff",
"title_bar.background": "#4c4642ff",
"title_bar.inactive_background": "#3a3735ff",
"toolbar.background": "#282828ff",
"tab_bar.background": "#3a3735ff",
"tab.inactive_background": "#3a3735ff",
@@ -430,6 +431,7 @@
"icon.accent": "#83a598ff",
"status_bar.background": "#4c4642ff",
"title_bar.background": "#4c4642ff",
"title_bar.inactive_background": "#393634ff",
"toolbar.background": "#1d2021ff",
"tab_bar.background": "#393634ff",
"tab.inactive_background": "#393634ff",
@@ -813,6 +815,7 @@
"icon.accent": "#83a598ff",
"status_bar.background": "#4c4642ff",
"title_bar.background": "#4c4642ff",
"title_bar.inactive_background": "#3b3735ff",
"toolbar.background": "#32302fff",
"tab_bar.background": "#3b3735ff",
"tab.inactive_background": "#3b3735ff",
@@ -1196,6 +1199,7 @@
"icon.accent": "#0b6678ff",
"status_bar.background": "#d9c8a4ff",
"title_bar.background": "#d9c8a4ff",
"title_bar.inactive_background": "#ecddb4ff",
"toolbar.background": "#fbf1c7ff",
"tab_bar.background": "#ecddb4ff",
"tab.inactive_background": "#ecddb4ff",
@@ -1579,6 +1583,7 @@
"icon.accent": "#0b6678ff",
"status_bar.background": "#d9c8a4ff",
"title_bar.background": "#d9c8a4ff",
"title_bar.inactive_background": "#ecddb5ff",
"toolbar.background": "#f9f5d7ff",
"tab_bar.background": "#ecddb5ff",
"tab.inactive_background": "#ecddb5ff",
@@ -1962,6 +1967,7 @@
"icon.accent": "#0b6678ff",
"status_bar.background": "#d9c8a4ff",
"title_bar.background": "#d9c8a4ff",
"title_bar.inactive_background": "#ecdcb3ff",
"toolbar.background": "#f2e5bcff",
"tab_bar.background": "#ecdcb3ff",
"tab.inactive_background": "#ecdcb3ff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#74ade8ff",
"status_bar.background": "#3b414dff",
"title_bar.background": "#3b414dff",
"title_bar.inactive_background": "#2e343eff",
"toolbar.background": "#282c33ff",
"tab_bar.background": "#2f343eff",
"tab.inactive_background": "#2f343eff",
@@ -412,6 +413,7 @@
"icon.accent": "#5c78e2ff",
"status_bar.background": "#dcdcddff",
"title_bar.background": "#dcdcddff",
"title_bar.inactive_background": "#ebebecff",
"toolbar.background": "#fafafaff",
"tab_bar.background": "#ebebecff",
"tab.inactive_background": "#ebebecff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#9bced6ff",
"status_bar.background": "#292738ff",
"title_bar.background": "#292738ff",
"title_bar.inactive_background": "#1c1b2aff",
"toolbar.background": "#191724ff",
"tab_bar.background": "#1c1b2aff",
"tab.inactive_background": "#1c1b2aff",
@@ -417,6 +418,7 @@
"icon.accent": "#57949fff",
"status_bar.background": "#dcd8d8ff",
"title_bar.background": "#dcd8d8ff",
"title_bar.inactive_background": "#fef9f2ff",
"toolbar.background": "#faf4edff",
"tab_bar.background": "#fef9f2ff",
"tab.inactive_background": "#fef9f2ff",
@@ -796,6 +798,7 @@
"icon.accent": "#9bced6ff",
"status_bar.background": "#38354eff",
"title_bar.background": "#38354eff",
"title_bar.inactive_background": "#28253cff",
"toolbar.background": "#232136ff",
"tab_bar.background": "#28253cff",
"tab.inactive_background": "#28253cff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#518b8bff",
"status_bar.background": "#333944ff",
"title_bar.background": "#333944ff",
"title_bar.inactive_background": "#2b3038ff",
"toolbar.background": "#282c33ff",
"tab_bar.background": "#2b3038ff",
"tab.inactive_background": "#2b3038ff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#278ad1ff",
"status_bar.background": "#073743ff",
"title_bar.background": "#073743ff",
"title_bar.inactive_background": "#04313bff",
"toolbar.background": "#002a35ff",
"tab_bar.background": "#04313bff",
"tab.inactive_background": "#04313bff",
@@ -407,6 +408,7 @@
"icon.accent": "#288bd1ff",
"status_bar.background": "#cfd0c4ff",
"title_bar.background": "#cfd0c4ff",
"title_bar.inactive_background": "#f3eddaff",
"toolbar.background": "#fdf6e3ff",
"tab_bar.background": "#f3eddaff",
"tab.inactive_background": "#f3eddaff",

View File

@@ -38,6 +38,7 @@
"icon.accent": "#499befff",
"status_bar.background": "#2a261cff",
"title_bar.background": "#2a261cff",
"title_bar.inactive_background": "#231f16ff",
"toolbar.background": "#1b1810ff",
"tab_bar.background": "#231f16ff",
"tab.inactive_background": "#231f16ff",

View File

@@ -30,8 +30,8 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use slash_command::{
active_command, default_command, diagnostics_command, docs_command, fetch_command,
file_command, now_command, project_command, prompt_command, search_command, tabs_command,
term_command,
file_command, now_command, project_command, prompt_command, search_command, symbols_command,
tabs_command, term_command,
};
use std::{
fmt::{self, Display},
@@ -367,6 +367,7 @@ fn register_slash_commands(cx: &mut AppContext) {
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(file_command::FileSlashCommand, true);
slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
slash_command_registry.register_command(project_command::ProjectSlashCommand, true);
slash_command_registry.register_command(search_command::SearchSlashCommand, true);

View File

@@ -18,6 +18,7 @@ use crate::{
use anyhow::{anyhow, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
use breadcrumbs::Breadcrumbs;
use client::proto;
use collections::{BTreeSet, HashMap, HashSet};
use editor::{
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
@@ -58,7 +59,7 @@ use ui::{
use util::ResultExt;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
item::{BreadcrumbText, Item, ItemHandle},
item::{self, BreadcrumbText, FollowableItem, Item, ItemHandle},
pane,
searchable::{SearchEvent, SearchableItem},
Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -66,6 +67,7 @@ use workspace::{
use workspace::{searchable::SearchableItemHandle, NewFile};
pub fn init(cx: &mut AppContext) {
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace
@@ -374,7 +376,7 @@ impl AssistantPanel {
fn handle_pane_event(
&mut self,
_pane: View<Pane>,
pane: View<Pane>,
event: &pane::Event,
cx: &mut ViewContext<Self>,
) {
@@ -384,14 +386,25 @@ impl AssistantPanel {
pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
pane::Event::AddItem { item } => {
if let Some(workspace) = self.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
self.workspace
.update(cx, |workspace, cx| {
item.added_to_pane(workspace, self.pane.clone(), cx)
});
}
})
.ok();
}
pane::Event::RemoveItem { .. } | pane::Event::ActivateItem { .. } => {
pane::Event::ActivateItem { local } => {
if *local {
self.workspace
.update(cx, |workspace, cx| {
workspace.unfollow_in_pane(&pane, cx);
})
.ok();
}
cx.emit(AssistantPanelEvent::ContextEdited);
}
pane::Event::RemoveItem { .. } => {
cx.emit(AssistantPanelEvent::ContextEdited);
}
@@ -582,7 +595,8 @@ impl AssistantPanel {
let mut editor = ContextEditor::for_context(
context,
self.fs.clone(),
workspace,
workspace.clone(),
self.project.clone(),
lsp_adapter_delegate,
cx,
);
@@ -613,12 +627,13 @@ impl AssistantPanel {
fn handle_context_editor_event(
&mut self,
_: View<ContextEditor>,
event: &ContextEditorEvent,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
ContextEditorEvent::TabContentChanged => cx.notify(),
ContextEditorEvent::Edited => cx.emit(AssistantPanelEvent::ContextEdited),
EditorEvent::TitleChanged { .. } => cx.notify(),
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
_ => {}
}
}
@@ -693,6 +708,7 @@ impl AssistantPanel {
.context_store
.update(cx, |store, cx| store.open_local_context(path.clone(), cx));
let fs = self.fs.clone();
let project = self.project.clone();
let workspace = self.workspace.clone();
let lsp_adapter_delegate = workspace
@@ -709,7 +725,14 @@ impl AssistantPanel {
.upgrade()
.ok_or_else(|| anyhow!("workspace dropped"))?;
let editor = cx.new_view(|cx| {
ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
ContextEditor::for_context(
context,
fs,
workspace,
project,
lsp_adapter_delegate,
cx,
)
});
this.show_context(editor, cx);
anyhow::Ok(())
@@ -722,14 +745,17 @@ impl AssistantPanel {
&mut self,
id: ContextId,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
) -> Task<Result<View<ContextEditor>>> {
let existing_context = self.pane.read(cx).items().find_map(|item| {
item.downcast::<ContextEditor>()
.filter(|editor| *editor.read(cx).context.read(cx).id() == id)
});
if let Some(existing_context) = existing_context {
return cx.spawn(|this, mut cx| async move {
this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
this.update(&mut cx, |this, cx| {
this.show_context(existing_context.clone(), cx)
})?;
Ok(existing_context)
});
}
@@ -753,12 +779,18 @@ impl AssistantPanel {
.upgrade()
.ok_or_else(|| anyhow!("workspace dropped"))?;
let editor = cx.new_view(|cx| {
ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
ContextEditor::for_context(
context,
fs,
workspace,
this.project.clone(),
lsp_adapter_delegate,
cx,
)
});
this.show_context(editor, cx);
anyhow::Ok(())
})??;
Ok(())
this.show_context(editor.clone(), cx);
anyhow::Ok(editor)
})?
})
}
@@ -878,6 +910,14 @@ impl Panel for AssistantPanel {
}
}
fn pane(&self) -> Option<View<Pane>> {
Some(self.pane.clone())
}
fn remote_id() -> Option<proto::PanelId> {
Some(proto::PanelId::AssistantPanel)
}
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
let settings = AssistantSettings::get_global(cx);
if !settings.enabled || !settings.button {
@@ -924,6 +964,7 @@ pub struct ContextEditor {
editor: View<Editor>,
blocks: HashSet<BlockId>,
scroll_position: Option<ScrollPosition>,
remote_id: Option<workspace::ViewId>,
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
_subscriptions: Vec<Subscription>,
@@ -936,6 +977,7 @@ impl ContextEditor {
context: Model<Context>,
fs: Arc<dyn Fs>,
workspace: View<Workspace>,
project: Model<Project>,
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut ViewContext<Self>,
) -> Self {
@@ -954,6 +996,7 @@ impl ContextEditor {
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_completion_provider(Box::new(completion_provider));
editor.set_collaboration_hub(Box::new(project));
editor
});
@@ -971,6 +1014,7 @@ impl ContextEditor {
lsp_adapter_delegate,
blocks: Default::default(),
scroll_position: None,
remote_id: None,
fs,
workspace: workspace.downgrade(),
pending_slash_command_creases: HashMap::default(),
@@ -1213,7 +1257,7 @@ impl ContextEditor {
});
}
ContextEvent::SummaryChanged => {
cx.emit(ContextEditorEvent::TabContentChanged);
cx.emit(EditorEvent::TitleChanged);
self.context.update(cx, |context, cx| {
context.save(None, self.fs.clone(), cx);
});
@@ -1472,9 +1516,9 @@ impl ContextEditor {
EditorEvent::SelectionsChanged { .. } => {
self.scroll_position = self.cursor_scroll_position(cx);
}
EditorEvent::BufferEdited => cx.emit(ContextEditorEvent::Edited),
_ => {}
}
cx.emit(event.clone());
}
fn handle_editor_search_event(
@@ -1935,7 +1979,7 @@ impl ContextEditor {
}
}
impl EventEmitter<ContextEditorEvent> for ContextEditor {}
impl EventEmitter<EditorEvent> for ContextEditor {}
impl EventEmitter<SearchEvent> for ContextEditor {}
impl Render for ContextEditor {
@@ -1977,13 +2021,9 @@ impl FocusableView for ContextEditor {
}
impl Item for ContextEditor {
type Event = ContextEditorEvent;
type Event = editor::EditorEvent;
fn tab_content(
&self,
params: workspace::item::TabContentParams,
cx: &WindowContext,
) -> AnyElement {
fn tab_content(&self, params: item::TabContentParams, cx: &WindowContext) -> AnyElement {
let color = if params.selected {
Color::Default
} else {
@@ -1997,15 +2037,16 @@ impl Item for ContextEditor {
.into_any_element()
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
match event {
ContextEditorEvent::Edited => {
f(workspace::item::ItemEvent::Edit);
f(workspace::item::ItemEvent::UpdateBreadcrumbs);
EditorEvent::Edited { .. } => {
f(item::ItemEvent::Edit);
f(item::ItemEvent::UpdateBreadcrumbs);
}
ContextEditorEvent::TabContentChanged => {
f(workspace::item::ItemEvent::UpdateTab);
EditorEvent::TitleChanged => {
f(item::ItemEvent::UpdateTab);
}
_ => {}
}
}
@@ -2021,7 +2062,7 @@ impl Item for ContextEditor {
&self,
theme: &theme::Theme,
cx: &AppContext,
) -> Option<Vec<workspace::item::BreadcrumbText>> {
) -> Option<Vec<item::BreadcrumbText>> {
let editor = self.editor.read(cx);
let cursor = editor.selections.newest_anchor().head();
let multibuffer = &editor.buffer().read(cx);
@@ -2133,6 +2174,127 @@ impl SearchableItem for ContextEditor {
}
}
impl FollowableItem for ContextEditor {
fn remote_id(&self) -> Option<workspace::ViewId> {
self.remote_id
}
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
let context = self.context.read(cx);
Some(proto::view::Variant::ContextEditor(
proto::view::ContextEditor {
context_id: context.id().to_proto(),
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx)
{
Some(proto)
} else {
None
},
},
))
}
fn from_state_proto(
workspace: View<Workspace>,
id: workspace::ViewId,
state: &mut Option<proto::view::Variant>,
cx: &mut WindowContext,
) -> Option<Task<Result<View<Self>>>> {
let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
return None;
};
let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
unreachable!()
};
let context_id = ContextId::from_proto(state.context_id);
let editor_state = state.editor?;
let (project, panel) = workspace.update(cx, |workspace, cx| {
Some((
workspace.project().clone(),
workspace.panel::<AssistantPanel>(cx)?,
))
})?;
let context_editor =
panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
Some(cx.spawn(|mut cx| async move {
let context_editor = context_editor.await?;
context_editor
.update(&mut cx, |context_editor, cx| {
context_editor.remote_id = Some(id);
context_editor.editor.update(cx, |editor, cx| {
editor.apply_update_proto(
&project,
proto::update_view::Variant::Editor(proto::update_view::Editor {
selections: editor_state.selections,
pending_selection: editor_state.pending_selection,
scroll_top_anchor: editor_state.scroll_top_anchor,
scroll_x: editor_state.scroll_y,
scroll_y: editor_state.scroll_y,
..Default::default()
}),
cx,
)
})
})?
.await?;
Ok(context_editor)
}))
}
fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
Editor::to_follow_event(event)
}
fn add_event_to_update_proto(
&self,
event: &Self::Event,
update: &mut Option<proto::update_view::Variant>,
cx: &WindowContext,
) -> bool {
self.editor
.read(cx)
.add_event_to_update_proto(event, update, cx)
}
fn apply_update_proto(
&mut self,
project: &Model<Project>,
message: proto::update_view::Variant,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.editor.update(cx, |editor, cx| {
editor.apply_update_proto(project, message, cx)
})
}
fn is_project_item(&self, _cx: &WindowContext) -> bool {
true
}
fn set_leader_peer_id(
&mut self,
leader_peer_id: Option<proto::PeerId>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
editor.set_leader_peer_id(leader_peer_id, cx)
})
}
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
if existing.context.read(cx).id() == self.context.read(cx).id() {
Some(item::Dedup::KeepExisting)
} else {
None
}
}
}
pub struct ContextEditorToolbarItem {
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
@@ -2369,11 +2531,7 @@ impl EventEmitter<()> for ContextHistory {}
impl Item for ContextHistory {
type Event = ();
fn tab_content(
&self,
params: workspace::item::TabContentParams,
_: &WindowContext,
) -> AnyElement {
fn tab_content(&self, params: item::TabContentParams, _: &WindowContext) -> AnyElement {
let color = if params.selected {
Color::Default
} else {
@@ -2391,10 +2549,13 @@ fn render_slash_command_output_toggle(
fold: ToggleFold,
_cx: &mut WindowContext,
) -> AnyElement {
Disclosure::new(("slash-command-output-fold-indicator", row.0), !is_folded)
.selected(is_folded)
.on_click(move |_e, cx| fold(!is_folded, cx))
.into_any_element()
Disclosure::new(
("slash-command-output-fold-indicator", row.0 as u64),
!is_folded,
)
.selected(is_folded)
.on_click(move |_e, cx| fold(!is_folded, cx))
.into_any_element()
}
fn render_pending_slash_command_gutter_decoration(
@@ -2445,19 +2606,43 @@ fn render_docs_slash_command_trailer(
return Empty.into_any();
};
if !store.is_indexing(&package) {
let mut children = Vec::new();
if store.is_indexing(&package) {
children.push(
div()
.id(("crates-being-indexed", row.0))
.child(Icon::new(IconName::ArrowCircle).with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
))
.tooltip({
let package = package.clone();
move |cx| Tooltip::text(format!("Indexing {package}"), cx)
})
.into_any_element(),
);
}
if let Some(latest_error) = store.latest_error_for_package(&package) {
children.push(
div()
.id(("latest-error", row.0))
.child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
.tooltip(move |cx| Tooltip::text(format!("failed to index: {latest_error}"), cx))
.into_any_element(),
)
}
let is_indexing = store.is_indexing(&package);
let latest_error = store.latest_error_for_package(&package);
if !is_indexing && latest_error.is_none() {
return Empty.into_any();
}
div()
.id(("crates-being-indexed", row.0))
.child(Icon::new(IconName::ArrowCircle).with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(4)).repeat(),
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
))
.tooltip(move |cx| Tooltip::text(format!("Indexing {package}"), cx))
.into_any_element()
h_flex().gap_2().children(children).into_any_element()
}
fn make_lsp_adapter_delegate(

View File

@@ -27,6 +27,8 @@ pub enum CloudModel {
Claude3Opus,
Claude3Sonnet,
Claude3Haiku,
Gemini15Pro,
Gemini15Flash,
Custom(String),
}
@@ -109,6 +111,8 @@ impl CloudModel {
Self::Claude3Opus => "claude-3-opus",
Self::Claude3Sonnet => "claude-3-sonnet",
Self::Claude3Haiku => "claude-3-haiku",
Self::Gemini15Pro => "gemini-1.5-pro",
Self::Gemini15Flash => "gemini-1.5-flash",
Self::Custom(id) => id,
}
}
@@ -123,6 +127,8 @@ impl CloudModel {
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
Self::Gemini15Pro => "Gemini 1.5 Pro",
Self::Gemini15Flash => "Gemini 1.5 Flash",
Self::Custom(id) => id.as_str(),
}
}
@@ -136,6 +142,8 @@ impl CloudModel {
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200000,
Self::Gemini15Pro => 128000,
Self::Gemini15Flash => 32000,
Self::Custom(_) => 4096, // TODO: Make this configurable
}
}

View File

@@ -268,7 +268,7 @@ impl PickerDelegate for PromptPickerDelegate {
.flex_none()
.py_1()
.px_2()
.mx_2()
.mx_1()
.child(editor.clone())
}
}
@@ -766,6 +766,7 @@ impl PromptLibrary {
.capture_action(cx.listener(Self::focus_active_prompt))
.bg(cx.theme().colors().panel_background)
.h_full()
.px_1()
.w_1_3()
.overflow_x_hidden()
.child(

View File

@@ -27,6 +27,7 @@ pub mod now_command;
pub mod project_command;
pub mod prompt_command;
pub mod search_command;
pub mod symbols_command;
pub mod tabs_command;
pub mod term_command;

View File

@@ -8,7 +8,8 @@ use assistant_slash_command::{
};
use gpui::{AppContext, Model, Task, WeakView};
use indexed_docs::{
IndexedDocsRegistry, IndexedDocsStore, LocalProvider, PackageName, ProviderId, RustdocIndexer,
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
};
use language::LspAdapterDelegate;
use project::{Project, ProjectPath};
@@ -34,22 +35,22 @@ impl DocsSlashCommand {
))
}
/// Ensures that the rustdoc provider is registered.
/// Ensures that the indexed doc providers for Rust are registered.
///
/// Ideally we would do this sooner, but we need to wait until we're able to
/// access the workspace so we can read the project.
fn ensure_rustdoc_provider_is_registered(
fn ensure_rust_doc_providers_are_registered(
&self,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) {
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
if indexed_docs_registry
.get_provider_store(ProviderId::rustdoc())
.get_provider_store(LocalRustdocProvider::id())
.is_none()
{
let index_provider_deps = maybe!({
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace.clone().ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was dropped"))?;
@@ -63,9 +64,29 @@ impl DocsSlashCommand {
});
if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() {
indexed_docs_registry.register_provider(Box::new(RustdocIndexer::new(Box::new(
LocalProvider::new(fs, cargo_workspace_root),
))));
indexed_docs_registry.register_provider(Box::new(LocalRustdocProvider::new(
fs,
cargo_workspace_root,
)));
}
}
if indexed_docs_registry
.get_provider_store(DocsDotRsProvider::id())
.is_none()
{
let http_client = maybe!({
let workspace = workspace.ok_or_else(|| anyhow!("no workspace"))?;
let workspace = workspace
.upgrade()
.ok_or_else(|| anyhow!("workspace was dropped"))?;
let project = workspace.read(cx).project().clone();
anyhow::Ok(project.read(cx).client().http_client().clone())
});
if let Some(http_client) = http_client.log_err() {
indexed_docs_registry
.register_provider(Box::new(DocsDotRsProvider::new(http_client)));
}
}
}
@@ -95,7 +116,7 @@ impl SlashCommand for DocsSlashCommand {
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
self.ensure_rustdoc_provider_is_registered(workspace, cx);
self.ensure_rust_doc_providers_are_registered(workspace, cx);
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
let args = DocsSlashCommandArgs::parse(&query);
@@ -121,6 +142,14 @@ impl SlashCommand for DocsSlashCommand {
match args {
DocsSlashCommandArgs::NoProvider => {
let providers = indexed_docs_registry.list_providers();
if providers.is_empty() {
return Ok(vec![ArgumentCompletion {
label: "No available docs providers.".to_string(),
new_text: String::new(),
run_command: false,
}]);
}
Ok(providers
.into_iter()
.map(|provider| ArgumentCompletion {
@@ -171,46 +200,65 @@ impl SlashCommand for DocsSlashCommand {
};
let args = DocsSlashCommandArgs::parse(argument);
let text = cx.background_executor().spawn({
let task = cx.background_executor().spawn({
let store = args
.provider()
.ok_or_else(|| anyhow!("no docs provider specified"))
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
async move {
match args {
let (provider, key) = match args {
DocsSlashCommandArgs::NoProvider => bail!("no docs provider specified"),
DocsSlashCommandArgs::SearchPackageDocs {
provider, package, ..
} => {
let store = store?;
let item_docs = store.load(package.clone()).await?;
anyhow::Ok((provider, package, item_docs.to_string()))
}
} => (provider, package),
DocsSlashCommandArgs::SearchItemDocs {
provider,
item_path,
..
} => {
let store = store?;
let item_docs = store.load(item_path.clone()).await?;
} => (provider, item_path),
};
anyhow::Ok((provider, item_path, item_docs.to_string()))
let store = store?;
let (text, ranges) = if let Some((prefix, _)) = key.split_once('*') {
let docs = store.load_many_by_prefix(prefix.to_string()).await?;
let mut text = String::new();
let mut ranges = Vec::new();
for (key, docs) in docs {
let prev_len = text.len();
text.push_str(&docs.0);
text.push_str("\n");
ranges.push((key, prev_len..text.len()));
text.push_str("\n");
}
}
(text, ranges)
} else {
let item_docs = store.load(key.clone()).await?;
let text = item_docs.to_string();
let range = 0..text.len();
(text, vec![(key, range)])
};
anyhow::Ok((provider, text, ranges))
}
});
cx.foreground_executor().spawn(async move {
let (provider, path, text) = text.await?;
let range = 0..text.len();
let (provider, text, ranges) = task.await?;
Ok(SlashCommandOutput {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::FileRust,
label: format!("docs ({provider}): {path}",).into(),
}],
sections: ranges
.into_iter()
.map(|(key, range)| SlashCommandOutputSection {
range,
icon: IconName::FileDoc,
label: format!("docs ({provider}): {key}",).into(),
})
.collect(),
run_commands_in_text: false,
})
})

View File

@@ -27,7 +27,7 @@ pub(crate) struct FetchSlashCommand;
impl FetchSlashCommand {
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
let mut url = url.to_owned();
if !url.starts_with("https://") {
if !url.starts_with("https://") && !url.starts_with("http://") {
url = format!("https://{url}");
}

View File

@@ -23,7 +23,7 @@ impl SlashCommand for NowSlashCommand {
}
fn menu_text(&self) -> String {
"Insert current date and time".into()
"Insert Current Date and Time".into()
}
fn requires_argument(&self) -> bool {

View File

@@ -0,0 +1,89 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use editor::Editor;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool};
use ui::{IconName, WindowContext};
use workspace::Workspace;
pub(crate) struct OutlineSlashCommand;
impl SlashCommand for OutlineSlashCommand {
fn name(&self) -> String {
"symbols".into()
}
fn description(&self) -> String {
"insert symbols for active tab".into()
}
fn menu_text(&self) -> String {
"Insert Symbols for Active Tab".into()
}
fn complete_argument(
self: Arc<Self>,
_query: String,
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
fn requires_argument(&self) -> bool {
false
}
fn run(
self: Arc<Self>,
_argument: Option<&str>,
workspace: WeakView<Workspace>,
_delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else {
return Task::ready(Err(anyhow!("no active tab")));
};
let Some(buffer) = active_item
.downcast::<Editor>()
.and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
else {
return Task::ready(Err(anyhow!("active tab is not an editor")));
};
let snapshot = buffer.read(cx).snapshot();
let path = snapshot.resolve_file_path(cx, true);
cx.background_executor().spawn(async move {
let outline = snapshot
.outline(None)
.context("no symbols for active tab")?;
let path = path.as_deref().unwrap_or(Path::new("untitled"));
let mut outline_text = format!("Symbols for {}:\n", path.display());
for item in &outline.path_candidates {
outline_text.push_str("- ");
outline_text.push_str(&item.string);
outline_text.push('\n');
}
Ok(SlashCommandOutput {
sections: vec![SlashCommandOutputSection {
range: 0..outline_text.len(),
icon: IconName::ListTree,
label: path.to_string_lossy().to_string().into(),
}],
text: outline_text,
run_commands_in_text: false,
})
})
});
output.unwrap_or_else(|error| Task::ready(Err(error)))
}
}

View File

@@ -31,7 +31,7 @@ impl SlashCommand for TermSlashCommand {
}
fn menu_text(&self) -> String {
"Insert terminal output".into()
"Insert Terminal Output".into()
}
fn requires_argument(&self) -> bool {

View File

@@ -11,6 +11,7 @@ pub struct IpcHandshake {
pub enum CliRequest {
Open {
paths: Vec<String>,
urls: Vec<String>,
wait: bool,
open_new_workspace: Option<bool>,
dev_server_token: Option<String>,

View File

@@ -5,6 +5,7 @@ use clap::Parser;
use cli::{ipc::IpcOneShotServer, CliRequest, CliResponse, IpcHandshake};
use parking_lot::Mutex;
use std::{
convert::Infallible,
env, fs, io,
path::{Path, PathBuf},
process::ExitStatus,
@@ -37,8 +38,7 @@ struct Args {
///
/// Use `path:line:row` syntax to open a file at a specific location.
/// Non-existing paths and directories will ignore `:line:row` suffix.
#[arg(value_parser = parse_path_with_position)]
paths_with_position: Vec<PathLikeWithPosition<PathBuf>>,
paths_with_position: Vec<String>,
/// Print Zed's version and the app path.
#[arg(short, long)]
version: bool,
@@ -53,12 +53,30 @@ struct Args {
dev_server_token: Option<String>,
}
fn parse_path_with_position(
argument_str: &str,
) -> Result<PathLikeWithPosition<PathBuf>, std::convert::Infallible> {
PathLikeWithPosition::parse_str(argument_str, |_, path_str| {
fn parse_path_with_position(argument_str: &str) -> Result<String, std::io::Error> {
let path_like = PathLikeWithPosition::parse_str::<Infallible>(argument_str, |_, path_str| {
Ok(Path::new(path_str).to_path_buf())
})
.unwrap();
let curdir = env::current_dir()?;
let canonicalized = path_like.map_path_like(|path| match fs::canonicalize(&path) {
Ok(path) => Ok(path),
Err(e) => {
if let Some(mut parent) = path.parent() {
if parent == Path::new("") {
parent = &curdir
}
match fs::canonicalize(parent) {
Ok(parent) => Ok(parent.join(path.file_name().unwrap())),
Err(_) => Err(e),
}
} else {
Err(e)
}
}
})?;
Ok(canonicalized.to_string(|path| path.display().to_string()))
}
fn main() -> Result<()> {
@@ -91,28 +109,6 @@ fn main() -> Result<()> {
return Ok(());
}
let curdir = env::current_dir()?;
let mut paths = vec![];
for path in args.paths_with_position {
let canonicalized = path.map_path_like(|path| match fs::canonicalize(&path) {
Ok(path) => Ok(path),
Err(e) => {
if let Some(mut parent) = path.parent() {
if parent == Path::new("") {
parent = &curdir;
}
match fs::canonicalize(parent) {
Ok(parent) => Ok(parent.join(path.file_name().unwrap())),
Err(_) => Err(e),
}
} else {
Err(e)
}
}
})?;
paths.push(canonicalized.to_string(|path| path.display().to_string()))
}
let (server, server_name) =
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
let url = format!("zed-cli://{server_name}");
@@ -126,6 +122,19 @@ fn main() -> Result<()> {
};
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
for path in args.paths_with_position.iter() {
if path.starts_with("zed://")
|| path.starts_with("http://")
|| path.starts_with("https://")
|| path.starts_with("file://")
{
urls.push(path.to_string());
} else {
paths.push(parse_path_with_position(path)?)
}
}
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
let exit_status = exit_status.clone();
@@ -134,6 +143,7 @@ fn main() -> Result<()> {
let (tx, rx) = (handshake.requests, handshake.responses);
tx.send(CliRequest::Open {
paths,
urls,
wait: args.wait,
open_new_workspace,
dev_server_token: args.dev_server_token,

View File

@@ -13,8 +13,9 @@ use async_tungstenite::tungstenite::{
use clock::SystemClock;
use collections::HashMap;
use futures::{
channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt,
TryFutureExt as _, TryStreamExt,
channel::oneshot,
future::{BoxFuture, LocalBoxFuture},
AsyncReadExt, FutureExt, SinkExt, Stream, StreamExt, TryFutureExt as _, TryStreamExt,
};
use gpui::{
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
@@ -23,6 +24,7 @@ use http::{HttpClient, HttpClientWithUrl};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use proto::ProtoClient;
use rand::prelude::*;
use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
@@ -1408,6 +1410,11 @@ impl Client {
self.peer.send(self.connection_id()?, message)
}
fn send_dynamic(&self, envelope: proto::Envelope) -> Result<()> {
let connection_id = self.connection_id()?;
self.peer.send_dynamic(connection_id, envelope)
}
pub fn request<T: RequestMessage>(
&self,
request: T,
@@ -1606,6 +1613,20 @@ impl Client {
}
}
impl ProtoClient for Client {
fn request(
&self,
envelope: proto::Envelope,
request_type: &'static str,
) -> BoxFuture<'static, Result<proto::Envelope>> {
self.request_dynamic(envelope, request_type).boxed()
}
fn send(&self, envelope: proto::Envelope) -> Result<()> {
self.send_dynamic(envelope)
}
}
#[derive(Serialize, Deserialize)]
struct DevelopmentCredentials {
user_id: u64,

View File

@@ -414,5 +414,5 @@ CREATE TABLE dev_servers (
CREATE TABLE dev_server_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dev_server_id INTEGER NOT NULL REFERENCES dev_servers(id),
path TEXT NOT NULL
paths TEXT NOT NULL
);

View File

@@ -0,0 +1,4 @@
ALTER TABLE dev_server_projects ADD COLUMN paths JSONB NULL;
UPDATE dev_server_projects SET paths = to_json(ARRAY[path]);
ALTER TABLE dev_server_projects ALTER COLUMN paths SET NOT NULL;
ALTER TABLE dev_server_projects ALTER COLUMN path DROP NOT NULL;

View File

@@ -43,11 +43,36 @@ async fn get_extensions(
Extension(app): Extension<Arc<AppState>>,
Query(params): Query<GetExtensionsParams>,
) -> Result<Json<GetExtensionsResponse>> {
let extensions = app
let mut extensions = app
.db
.get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
.await?;
if let Some(filter) = params.filter.as_deref() {
let extension_id = filter.to_lowercase();
let mut exact_match = None;
extensions.retain(|extension| {
if extension.id.as_ref() == &extension_id {
exact_match = Some(extension.clone());
false
} else {
true
}
});
if exact_match.is_none() {
exact_match = app
.db
.get_extensions_by_ids(&[&extension_id], None)
.await?
.first()
.cloned();
}
if let Some(exact_match) = exact_match {
extensions.insert(0, exact_match);
}
};
if let Some(query) = params.filter.as_deref() {
let count = extensions.len();
tracing::info!(query, count, "extension_search")

View File

@@ -5,7 +5,7 @@ use rpc::{
};
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait,
ModelTrait, QueryFilter,
IntoActiveModel, ModelTrait, QueryFilter,
};
use crate::db::ProjectId;
@@ -56,12 +56,7 @@ impl Database {
.await?;
Ok(servers
.into_iter()
.map(|(dev_server_project, project)| proto::DevServerProject {
id: dev_server_project.id.to_proto(),
project_id: project.map(|p| p.id.to_proto()),
dev_server_id: dev_server_project.dev_server_id.to_proto(),
path: dev_server_project.path,
})
.map(|(dev_server_project, project)| dev_server_project.to_proto(project))
.collect())
}
@@ -134,7 +129,7 @@ impl Database {
let project = dev_server_project::Entity::insert(dev_server_project::ActiveModel {
id: ActiveValue::NotSet,
dev_server_id: ActiveValue::Set(dev_server_id),
path: ActiveValue::Set(path.to_string()),
paths: ActiveValue::Set(dev_server_project::JSONPaths(vec![path.to_string()])),
})
.exec_with_returning(&*tx)
.await?;
@@ -148,6 +143,38 @@ impl Database {
.await
}
pub async fn update_dev_server_project(
&self,
id: DevServerProjectId,
paths: &Vec<String>,
user_id: UserId,
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
self.transaction(move |tx| async move {
let paths = paths.clone();
let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
.find_also_related(dev_server::Entity)
.one(&*tx)
.await?
else {
return Err(anyhow!("no such dev server project"))?;
};
if dev_server.user_id != user_id {
return Err(anyhow!("not your dev server"))?;
}
let mut project = project.into_active_model();
project.paths = ActiveValue::Set(dev_server_project::JSONPaths(paths));
let project = project.update(&*tx).await?;
let status = self
.dev_server_projects_update_internal(user_id, &tx)
.await?;
Ok((project, status))
})
.await
}
pub async fn delete_dev_server_project(
&self,
dev_server_project_id: DevServerProjectId,
@@ -258,7 +285,6 @@ impl Database {
dev_server_id: DevServerId,
connection: ConnectionId,
) -> crate::Result<Vec<ResharedProject>> {
// todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
self.transaction(|tx| async move {
let mut ret = Vec::new();
for reshared_project in reshared_projects {
@@ -322,7 +348,6 @@ impl Database {
user_id: UserId,
connection_id: ConnectionId,
) -> crate::Result<Vec<RejoinedProject>> {
// todo!() project_transaction? (maybe we can make the lock per-dev-server instead of per-project?)
self.transaction(|tx| async move {
let mut ret = Vec::new();
for rejoined_project in rejoined_projects {

View File

@@ -19,6 +19,28 @@ impl Database {
.await
}
pub async fn get_dev_server_for_user(
&self,
dev_server_id: DevServerId,
user_id: UserId,
) -> crate::Result<dev_server::Model> {
self.transaction(|tx| async move {
let server = 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))?;
if server.user_id != user_id {
return Err(anyhow::anyhow!(
"dev server {} is not owned by user {}",
dev_server_id,
user_id
))?;
}
Ok(server)
})
.await
}
pub async fn get_dev_servers(&self, user_id: UserId) -> crate::Result<Vec<dev_server::Model>> {
self.transaction(|tx| async move {
Ok(dev_server::Entity::find()

View File

@@ -1,7 +1,8 @@
use super::project;
use crate::db::{DevServerId, DevServerProjectId};
use rpc::proto;
use sea_orm::entity::prelude::*;
use sea_orm::{entity::prelude::*, FromJsonQueryResult};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "dev_server_projects")]
@@ -9,9 +10,12 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: DevServerProjectId,
pub dev_server_id: DevServerId,
pub path: String,
pub paths: JSONPaths,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JSONPaths(pub Vec<String>);
impl ActiveModelBehavior for ActiveModel {}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -44,7 +48,12 @@ impl Model {
id: self.id.to_proto(),
project_id: project.map(|p| p.id.to_proto()),
dev_server_id: self.dev_server_id.to_proto(),
path: self.path.clone(),
path: self.paths().get(0).cloned().unwrap_or_default(),
paths: self.paths().clone(),
}
}
pub fn paths(&self) -> &Vec<String> {
&self.paths.0
}
}

View File

@@ -431,11 +431,13 @@ impl Server {
.add_request_handler(user_handler(join_hosted_project))
.add_request_handler(user_handler(rejoin_dev_server_projects))
.add_request_handler(user_handler(create_dev_server_project))
.add_request_handler(user_handler(update_dev_server_project))
.add_request_handler(user_handler(delete_dev_server_project))
.add_request_handler(user_handler(create_dev_server))
.add_request_handler(user_handler(regenerate_dev_server_token))
.add_request_handler(user_handler(rename_dev_server))
.add_request_handler(user_handler(delete_dev_server))
.add_request_handler(user_handler(list_remote_directory))
.add_request_handler(dev_server_handler(share_dev_server_project))
.add_request_handler(dev_server_handler(shutdown_dev_server))
.add_request_handler(dev_server_handler(reconnect_dev_server))
@@ -2313,6 +2315,69 @@ async fn join_hosted_project(
join_project_internal(response, session, &mut project, &replica_id)
}
async fn list_remote_directory(
request: proto::ListRemoteDirectory,
response: Response<proto::ListRemoteDirectory>,
session: UserSession,
) -> Result<()> {
let dev_server_id = DevServerId(request.dev_server_id as i32);
let dev_server_connection_id = session
.connection_pool()
.await
.dev_server_connection_id_supporting(dev_server_id, ZedVersion::with_list_directory())?;
session
.db()
.await
.get_dev_server_for_user(dev_server_id, session.user_id())
.await?;
response.send(
session
.peer
.forward_request(session.connection_id, dev_server_connection_id, request)
.await?,
)?;
Ok(())
}
async fn update_dev_server_project(
request: proto::UpdateDevServerProject,
response: Response<proto::UpdateDevServerProject>,
session: UserSession,
) -> Result<()> {
let dev_server_project_id = DevServerProjectId(request.dev_server_project_id as i32);
let (dev_server_project, update) = session
.db()
.await
.update_dev_server_project(dev_server_project_id, &request.paths, session.user_id())
.await?;
let projects = session
.db()
.await
.get_projects_for_dev_server(dev_server_project.dev_server_id)
.await?;
let dev_server_connection_id = session
.connection_pool()
.await
.dev_server_connection_id_supporting(
dev_server_project.dev_server_id,
ZedVersion::with_list_directory(),
)?;
session.peer.send(
dev_server_connection_id,
proto::DevServerInstructions { projects },
)?;
send_dev_server_projects_update(session.user_id(), update, &session).await;
response.send(proto::Ack {})
}
async fn create_dev_server_project(
request: proto::CreateDevServerProject,
response: Response<proto::CreateDevServerProject>,

View File

@@ -38,6 +38,10 @@ impl ZedVersion {
pub fn with_save_as() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 134, 0))
}
pub fn with_list_directory() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 145, 0))
}
}
pub trait VersionedMessage {
@@ -187,6 +191,18 @@ impl ConnectionPool {
self.connected_dev_servers.get(&dev_server_id).copied()
}
pub fn dev_server_connection_id_supporting(
&self,
dev_server_id: DevServerId,
required: ZedVersion,
) -> Result<ConnectionId> {
match self.connected_dev_servers.get(&dev_server_id) {
Some(cid) if self.connections[cid].zed_version >= required => Ok(*cid),
Some(_) => Err(anyhow!(proto::ErrorCode::RemoteUpgradeRequired)),
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
}
}
pub fn channel_user_ids(
&self,
channel_id: ChannelId,

View File

@@ -66,7 +66,7 @@ async fn test_dev_server(cx: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppC
.update(cx, |store, cx| {
let projects = store.dev_server_projects();
assert_eq!(projects.len(), 1);
assert_eq!(projects[0].path, "/remote");
assert_eq!(projects[0].paths, vec!["/remote"]);
workspace::join_dev_server_project(
projects[0].id,
projects[0].project_id.unwrap(),
@@ -206,7 +206,7 @@ async fn create_dev_server_project(
.update(cx, |store, cx| {
let projects = store.dev_server_projects();
assert_eq!(projects.len(), 1);
assert_eq!(projects[0].path, "/remote");
assert_eq!(projects[0].paths, vec!["/remote"]);
workspace::join_dev_server_project(
projects[0].id,
projects[0].project_id.unwrap(),

View File

@@ -135,7 +135,7 @@ async fn test_basic_following(
assert_eq!(editor.selections.ranges(cx), vec![2..1]);
});
// When client B starts following client A, all visible view states are replicated to client B.
// When client B starts following client A, only the active view state is replicated to client B.
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
cx_c.executor().run_until_parked();
@@ -156,7 +156,7 @@ async fn test_basic_following(
);
assert_eq!(
editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
vec![3..2]
vec![3..3]
);
executor.run_until_parked();
@@ -194,7 +194,7 @@ async fn test_basic_following(
// Client C unfollows client A.
workspace_c.update(cx_c, |workspace, cx| {
workspace.unfollow(&workspace.active_pane().clone(), cx);
workspace.unfollow(peer_id_a, cx).unwrap();
});
// All clients see that clients B is following client A.
@@ -398,7 +398,7 @@ async fn test_basic_following(
// After unfollowing, client B stops receiving updates from client A.
workspace_b.update(cx_b, |workspace, cx| {
workspace.unfollow(&workspace.active_pane().clone(), cx)
workspace.unfollow(peer_id_a, cx).unwrap()
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a2, cx)

View File

@@ -1528,7 +1528,7 @@ async fn test_project_reconnect(
});
let (worktree_a2, _) = project_a1
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root-1/dir2", true, cx)
p.find_or_create_worktree("/root-1/dir2", true, cx)
})
.await
.unwrap();
@@ -1601,7 +1601,7 @@ async fn test_project_reconnect(
});
let (worktree_a3, _) = project_a1
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root-1/dir3", true, cx)
p.find_or_create_worktree("/root-1/dir3", true, cx)
})
.await
.unwrap();
@@ -1725,7 +1725,7 @@ async fn test_project_reconnect(
// While client B is disconnected, add and remove worktrees from client A's project.
let (worktree_a4, _) = project_a1
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root-1/dir4", true, cx)
p.find_or_create_worktree("/root-1/dir4", true, cx)
})
.await
.unwrap();
@@ -3327,7 +3327,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.read(cx).id().to_usize())
.local_settings(worktree_b.entity_id().as_u64() as _)
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
@@ -3346,7 +3346,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.read(cx).id().to_usize())
.local_settings(worktree_b.entity_id().as_u64() as _)
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{}"#.to_string()),
@@ -3375,7 +3375,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.read(cx).id().to_usize())
.local_settings(worktree_b.entity_id().as_u64() as _)
.collect::<Vec<_>>(),
&[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
@@ -3407,7 +3407,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.read(cx).id().to_usize())
.local_settings(worktree_b.entity_id().as_u64() as _)
.collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
)
@@ -4887,7 +4887,7 @@ async fn test_project_search(
let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
let (worktree_2, _) = project_a
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/root/dir-2", true, cx)
p.find_or_create_worktree("/root/dir-2", true, cx)
})
.await
.unwrap();

View File

@@ -581,7 +581,7 @@ impl RandomizedTest for ProjectCollaborationTest {
}
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree(&new_root_path, true, cx)
project.find_or_create_worktree(&new_root_path, true, cx)
})
.await
.unwrap();
@@ -1237,7 +1237,7 @@ impl RandomizedTest for ProjectCollaborationTest {
}
}
for buffer in guest_project.opened_buffers() {
for buffer in guest_project.opened_buffers(cx) {
let buffer = buffer.read(cx);
assert_eq!(
buffer.deferred_ops_len(),
@@ -1287,8 +1287,8 @@ impl RandomizedTest for ProjectCollaborationTest {
for guest_buffer in guest_buffers {
let buffer_id =
guest_buffer.read_with(client_cx, |buffer, _| buffer.remote_id());
let host_buffer = host_project.read_with(host_cx, |project, _| {
project.buffer_for_id(buffer_id).unwrap_or_else(|| {
let host_buffer = host_project.read_with(host_cx, |project, cx| {
project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
panic!(
"host does not have buffer for guest:{}, peer:{:?}, id:{}",
client.username,

View File

@@ -805,9 +805,7 @@ impl TestClient {
) -> (Model<Project>, WorktreeId) {
let project = self.build_empty_local_project(cx);
let (worktree, _) = project
.update(cx, |p, cx| {
p.find_or_create_local_worktree(root_path, true, cx)
})
.update(cx, |p, cx| p.find_or_create_worktree(root_path, true, cx))
.await
.unwrap();
worktree

View File

@@ -22,10 +22,9 @@ use std::{
};
use ui::{prelude::*, Label};
use util::ResultExt;
use workspace::notifications::NotificationId;
use workspace::{item::Dedup, notifications::NotificationId};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, TabContentParams},
register_followable_item,
searchable::SearchableItemHandle,
ItemNavHistory, Pane, SaveIntent, Toast, ViewId, Workspace, WorkspaceId,
};
@@ -33,7 +32,7 @@ use workspace::{
actions!(collab, [CopyLink]);
pub fn init(cx: &mut AppContext) {
register_followable_item::<ChannelView>(cx)
workspace::FollowableViewRegistry::register::<ChannelView>(cx)
}
pub struct ChannelView {
@@ -83,6 +82,56 @@ impl ChannelView {
pane: View<Pane>,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let channel_view = Self::load(channel_id, workspace, cx);
cx.spawn(|mut cx| async move {
let channel_view = channel_view.await?;
pane.update(&mut cx, |pane, cx| {
let buffer_id = channel_view.read(cx).channel_buffer.read(cx).remote_id(cx);
let existing_view = pane
.items_of_type::<Self>()
.find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
// If this channel buffer is already open in this pane, just return it.
if let Some(existing_view) = existing_view.clone() {
if existing_view.read(cx).channel_buffer == channel_view.read(cx).channel_buffer
{
if let Some(link_position) = link_position {
existing_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, cx)
});
}
return existing_view;
}
}
// If the pane contained a disconnected view for this channel buffer,
// replace that.
if let Some(existing_item) = existing_view {
if let Some(ix) = pane.index_for_item(&existing_item) {
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
.detach();
pane.add_item(Box::new(channel_view.clone()), true, true, Some(ix), cx);
}
}
if let Some(link_position) = link_position {
channel_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, cx)
});
}
channel_view
})
})
}
pub fn load(
channel_id: ChannelId,
workspace: View<Workspace>,
cx: &mut WindowContext,
) -> Task<Result<View<Self>>> {
let weak_workspace = workspace.downgrade();
let workspace = workspace.read(cx);
@@ -107,49 +156,11 @@ impl ChannelView {
})
})?;
pane.update(&mut cx, |pane, cx| {
let buffer_id = channel_buffer.read(cx).remote_id(cx);
let existing_view = pane
.items_of_type::<Self>()
.find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
// If this channel buffer is already open in this pane, just return it.
if let Some(existing_view) = existing_view.clone() {
if existing_view.read(cx).channel_buffer == channel_buffer {
if let Some(link_position) = link_position {
existing_view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, cx)
});
}
return existing_view;
}
}
let view = cx.new_view(|cx| {
let mut this =
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
this.acknowledge_buffer_version(cx);
this
});
// If the pane contained a disconnected view for this channel buffer,
// replace that.
if let Some(existing_item) = existing_view {
if let Some(ix) = pane.index_for_item(&existing_item) {
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
.detach();
pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
}
}
if let Some(link_position) = link_position {
view.update(cx, |channel_view, cx| {
channel_view.focus_position_from_link(link_position, true, cx)
});
}
view
cx.new_view(|cx| {
let mut this =
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
this.acknowledge_buffer_version(cx);
this
})
})
}
@@ -478,7 +489,6 @@ impl FollowableItem for ChannelView {
}
fn from_state_proto(
pane: View<workspace::Pane>,
workspace: View<workspace::Workspace>,
remote_id: workspace::ViewId,
state: &mut Option<proto::view::Variant>,
@@ -491,8 +501,7 @@ impl FollowableItem for ChannelView {
unreachable!()
};
let open =
ChannelView::open_in_pane(ChannelId(state.channel_id), None, pane, workspace, cx);
let open = ChannelView::load(ChannelId(state.channel_id), workspace, cx);
Some(cx.spawn(|mut cx| async move {
let this = open.await?;
@@ -563,6 +572,19 @@ impl FollowableItem for ChannelView {
fn to_follow_event(event: &Self::Event) -> Option<workspace::item::FollowEvent> {
Editor::to_follow_event(event)
}
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
let existing = existing.channel_buffer.read(cx);
if self.channel_buffer.read(cx).channel_id == existing.channel_id {
if existing.is_connected() {
Some(Dedup::KeepExisting)
} else {
Some(Dedup::ReplaceExisting)
}
} else {
None
}
}
}
struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);

View File

@@ -20,7 +20,7 @@ pub struct Store {
pub struct DevServerProject {
pub id: DevServerProjectId,
pub project_id: Option<ProjectId>,
pub path: SharedString,
pub paths: Vec<SharedString>,
pub dev_server_id: DevServerId,
}
@@ -29,7 +29,7 @@ impl From<proto::DevServerProject> for DevServerProject {
Self {
id: DevServerProjectId(project.id),
project_id: project.project_id.map(|id| ProjectId(id)),
path: project.path.into(),
paths: project.paths.into_iter().map(|path| path.into()).collect(),
dev_server_id: DevServerId(project.dev_server_id),
}
}
@@ -85,7 +85,7 @@ impl Store {
.filter(|project| project.dev_server_id == id)
.cloned()
.collect();
projects.sort_by_key(|p| (p.path.clone(), p.id));
projects.sort_by_key(|p| (p.paths.clone(), p.id));
projects
}
@@ -108,7 +108,7 @@ impl Store {
pub fn dev_server_projects(&self) -> Vec<DevServerProject> {
let mut projects: Vec<DevServerProject> =
self.dev_server_projects.values().cloned().collect();
projects.sort_by_key(|p| (p.path.clone(), p.id));
projects.sort_by_key(|p| (p.paths.clone(), p.id));
projects
}

View File

@@ -18,11 +18,13 @@ collections.workspace = true
ctor.workspace = true
editor.workspace = true
env_logger.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
multi_buffer.workspace = true
project.workspace = true
rand.workspace = true
schemars.workspace = true

View File

@@ -4,6 +4,7 @@ mod toolbar_controls;
#[cfg(test)]
mod diagnostics_tests;
mod grouped_diagnostics;
use anyhow::Result;
use collections::{BTreeSet, HashSet};
@@ -14,6 +15,7 @@ use editor::{
scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
};
use feature_flags::FeatureFlagAppExt;
use futures::{
channel::mpsc::{self, UnboundedSender},
StreamExt as _,
@@ -52,6 +54,9 @@ pub fn init(cx: &mut AppContext) {
ProjectDiagnosticsSettings::register(cx);
cx.observe_new_views(ProjectDiagnosticsEditor::register)
.detach();
if !cx.has_flag::<feature_flags::GroupedDiagnostics>() {
grouped_diagnostics::init(cx);
}
}
struct ProjectDiagnosticsEditor {
@@ -466,7 +471,9 @@ impl ProjectDiagnosticsEditor {
position: (excerpt_id, entry.range.start),
height: diagnostic.message.matches('\n').count() as u8 + 1,
style: BlockStyle::Fixed,
render: diagnostic_block_renderer(diagnostic, true),
render: diagnostic_block_renderer(
diagnostic, None, true, true,
),
disposition: BlockDisposition::Below,
});
}
@@ -798,7 +805,7 @@ impl Item for ProjectDiagnosticsEditor {
const DIAGNOSTIC_HEADER: &'static str = "diagnostic header";
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
let message: SharedString = message;
Box::new(move |cx| {
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();

View File

@@ -973,8 +973,8 @@ fn editor_blocks(
blocks.extend(
snapshot
.blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
.enumerate()
.filter_map(|(ix, (row, block))| {
.filter_map(|(row, block)| {
let transform_block_id = block.id();
let name: SharedString = match block {
TransformBlock::Custom(block) => {
let mut element = block.render(&mut BlockContext {
@@ -984,7 +984,7 @@ fn editor_blocks(
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
transform_block_id,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();

File diff suppressed because it is too large Load Diff

View File

@@ -49,6 +49,7 @@ lazy_static.workspace = true
linkify.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
multi_buffer.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true

View File

@@ -286,12 +286,14 @@ gpui::actions!(
SelectPageUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShowSignatureHelp,
ShuffleLines,
SortLinesCaseInsensitive,
SortLinesCaseSensitive,
SplitSelectionIntoLines,
Tab,
TabPrev,
ToggleAutoSignatureHelp,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleSelectionMenu,

View File

@@ -30,6 +30,7 @@ use crate::{
pub use block_map::{
BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
BlockMap, BlockPoint, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
TransformBlockId,
};
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};

View File

@@ -4,7 +4,7 @@ use super::{
};
use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, Pixels, WindowContext};
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
use language::{BufferSnapshot, Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
use parking_lot::Mutex;
@@ -20,6 +20,7 @@ use std::{
};
use sum_tree::{Bias, SumTree};
use text::Edit;
use ui::ElementId;
const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
@@ -53,6 +54,12 @@ pub struct BlockSnapshot {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BlockId(usize);
impl Into<ElementId> for BlockId {
fn into(self) -> ElementId {
ElementId::Integer(self.0)
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct BlockPoint(pub Point);
@@ -62,7 +69,7 @@ pub struct BlockRow(pub(super) u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32);
pub type RenderBlock = Box<dyn Send + Fn(&mut BlockContext) -> AnyElement>;
pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
pub struct Block {
id: BlockId,
@@ -77,11 +84,22 @@ pub struct BlockProperties<P> {
pub position: P,
pub height: u8,
pub style: BlockStyle,
pub render: Box<dyn Send + Fn(&mut BlockContext) -> AnyElement>,
pub render: RenderBlock,
pub disposition: BlockDisposition,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
impl<P: Debug> Debug for BlockProperties<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlockProperties")
.field("position", &self.position)
.field("height", &self.height)
.field("style", &self.style)
.field("disposition", &self.disposition)
.finish()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum BlockStyle {
Fixed,
Flex,
@@ -95,10 +113,47 @@ pub struct BlockContext<'a, 'b> {
pub gutter_dimensions: &'b GutterDimensions,
pub em_width: Pixels,
pub line_height: Pixels,
pub block_id: usize,
pub transform_block_id: TransformBlockId,
pub editor_style: &'b EditorStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransformBlockId {
Block(BlockId),
ExcerptHeader(ExcerptId),
ExcerptFooter(ExcerptId),
}
impl From<TransformBlockId> for EntityId {
fn from(value: TransformBlockId) -> Self {
match value {
TransformBlockId::Block(BlockId(id)) => EntityId::from(id as u64),
TransformBlockId::ExcerptHeader(id) => id.into(),
TransformBlockId::ExcerptFooter(id) => id.into(),
}
}
}
impl Into<ElementId> for TransformBlockId {
fn into(self) -> ElementId {
match self {
Self::Block(BlockId(id)) => ("Block", id).into(),
Self::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
Self::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
}
}
}
impl std::fmt::Display for TransformBlockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Block(id) => write!(f, "Block({id:?})"),
Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
}
}
}
/// Whether the block should be considered above or below the anchor line
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum BlockDisposition {
@@ -157,6 +212,14 @@ impl BlockLike for TransformBlock {
}
impl TransformBlock {
pub fn id(&self) -> TransformBlockId {
match self {
TransformBlock::Custom(block) => TransformBlockId::Block(block.id),
TransformBlock::ExcerptHeader { id, .. } => TransformBlockId::ExcerptHeader(*id),
TransformBlock::ExcerptFooter { id, .. } => TransformBlockId::ExcerptFooter(*id),
}
}
fn disposition(&self) -> BlockDisposition {
match self {
TransformBlock::Custom(block) => block.disposition,

View File

@@ -164,7 +164,7 @@ impl<'a> FoldMapWriter<'a> {
new_tree
};
consolidate_inlay_edits(&mut edits);
let edits = consolidate_inlay_edits(edits);
let edits = self.0.sync(snapshot.clone(), edits);
(self.0.snapshot.clone(), edits)
}
@@ -212,7 +212,7 @@ impl<'a> FoldMapWriter<'a> {
folds
};
consolidate_inlay_edits(&mut edits);
let edits = consolidate_inlay_edits(edits);
let edits = self.0.sync(snapshot.clone(), edits);
(self.0.snapshot.clone(), edits)
}
@@ -513,7 +513,7 @@ impl FoldMap {
});
}
consolidate_fold_edits(&mut fold_edits);
fold_edits = consolidate_fold_edits(fold_edits);
}
self.snapshot.transforms = new_transforms;
@@ -809,7 +809,7 @@ where
cursor
}
fn consolidate_inlay_edits(edits: &mut Vec<InlayEdit>) {
fn consolidate_inlay_edits(mut edits: Vec<InlayEdit>) -> Vec<InlayEdit> {
edits.sort_unstable_by(|a, b| {
a.old
.start
@@ -817,42 +817,68 @@ fn consolidate_inlay_edits(edits: &mut Vec<InlayEdit>) {
.then_with(|| b.old.end.cmp(&a.old.end))
});
let mut i = 1;
while i < edits.len() {
let edit = edits[i].clone();
let prev_edit = &mut edits[i - 1];
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
edits.remove(i);
continue;
}
i += 1;
}
let _old_alloc_ptr = edits.as_ptr();
let mut inlay_edits = edits.into_iter();
let inlay_edits = if let Some(mut first_edit) = inlay_edits.next() {
// This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
#[allow(clippy::filter_map_identity)]
let mut v: Vec<_> = inlay_edits
.scan(&mut first_edit, |prev_edit, edit| {
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
Some(None) // Skip this edit, it's merged
} else {
let prev = std::mem::replace(*prev_edit, edit);
Some(Some(prev)) // Yield the previous edit
}
})
.filter_map(|x| x)
.collect();
v.push(first_edit.clone());
debug_assert_eq!(_old_alloc_ptr, v.as_ptr(), "Inlay edits were reallocated");
v
} else {
vec![]
};
inlay_edits
}
fn consolidate_fold_edits(edits: &mut Vec<FoldEdit>) {
fn consolidate_fold_edits(mut edits: Vec<FoldEdit>) -> Vec<FoldEdit> {
edits.sort_unstable_by(|a, b| {
a.old
.start
.cmp(&b.old.start)
.then_with(|| b.old.end.cmp(&a.old.end))
});
let _old_alloc_ptr = edits.as_ptr();
let mut fold_edits = edits.into_iter();
let fold_edits = if let Some(mut first_edit) = fold_edits.next() {
// This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
#[allow(clippy::filter_map_identity)]
let mut v: Vec<_> = fold_edits
.scan(&mut first_edit, |prev_edit, edit| {
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
Some(None) // Skip this edit, it's merged
} else {
let prev = std::mem::replace(*prev_edit, edit);
Some(Some(prev)) // Yield the previous edit
}
})
.filter_map(|x| x)
.collect();
v.push(first_edit.clone());
v
} else {
vec![]
};
let mut i = 1;
while i < edits.len() {
let edit = edits[i].clone();
let prev_edit = &mut edits[i - 1];
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = prev_edit.old.end.max(edit.old.end);
prev_edit.new.start = prev_edit.new.start.min(edit.new.start);
prev_edit.new.end = prev_edit.new.end.max(edit.new.end);
edits.remove(i);
continue;
}
i += 1;
}
fold_edits
}
#[derive(Clone, Debug, Default)]

View File

@@ -103,20 +103,33 @@ impl TabMap {
}
}
let _old_alloc_ptr = fold_edits.as_ptr();
// Combine any edits that overlap due to the expansion.
let mut ix = 1;
while ix < fold_edits.len() {
let (prev_edits, next_edits) = fold_edits.split_at_mut(ix);
let prev_edit = prev_edits.last_mut().unwrap();
let edit = &next_edits[0];
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = edit.old.end;
prev_edit.new.end = edit.new.end;
fold_edits.remove(ix);
} else {
ix += 1;
}
}
let mut fold_edits = fold_edits.into_iter();
let fold_edits = if let Some(mut first_edit) = fold_edits.next() {
// This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
#[allow(clippy::filter_map_identity)]
let mut v: Vec<_> = fold_edits
.scan(&mut first_edit, |state, edit| {
if state.old.end >= edit.old.start {
state.old.end = edit.old.end;
state.new.end = edit.new.end;
Some(None) // Skip this edit, it's merged
} else {
let new_state = edit.clone();
let result = Some(Some(state.clone())); // Yield the previous edit
**state = new_state;
result
}
})
.filter_map(|x| x)
.collect();
v.push(first_edit);
debug_assert_eq!(v.as_ptr(), _old_alloc_ptr, "Fold edits were reallocated");
v
} else {
vec![]
};
for fold_edit in fold_edits {
let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot);

View File

@@ -567,7 +567,7 @@ impl WrapSnapshot {
});
}
consolidate_wrap_edits(&mut wrap_edits);
wrap_edits = consolidate_wrap_edits(wrap_edits);
Patch::new(wrap_edits)
}
@@ -1008,19 +1008,33 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
}
}
fn consolidate_wrap_edits(edits: &mut Vec<WrapEdit>) {
let mut i = 1;
while i < edits.len() {
let edit = edits[i].clone();
let prev_edit = &mut edits[i - 1];
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = edit.old.end;
prev_edit.new.end = edit.new.end;
edits.remove(i);
continue;
}
i += 1;
}
fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
let _old_alloc_ptr = edits.as_ptr();
let mut wrap_edits = edits.into_iter();
let wrap_edits = if let Some(mut first_edit) = wrap_edits.next() {
// This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
#[allow(clippy::filter_map_identity)]
let mut v: Vec<_> = wrap_edits
.scan(&mut first_edit, |prev_edit, edit| {
if prev_edit.old.end >= edit.old.start {
prev_edit.old.end = edit.old.end;
prev_edit.new.end = edit.new.end;
Some(None) // Skip this edit, it's merged
} else {
let prev = std::mem::replace(*prev_edit, edit);
Some(Some(prev)) // Yield the previous edit
}
})
.filter_map(|x| x)
.collect();
v.push(first_edit.clone());
debug_assert_eq!(v.as_ptr(), _old_alloc_ptr, "Wrap edits were reallocated");
v
} else {
vec![]
};
wrap_edits
}
#[cfg(test)]

View File

@@ -39,8 +39,10 @@ pub mod tasks;
#[cfg(test)]
mod editor_tests;
mod signature_help;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use ::git::diff::{DiffHunk, DiffHunkStatus};
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
pub(crate) use actions::*;
@@ -66,12 +68,12 @@ use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, FocusableView,
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
WeakView, WhiteSpace, WindowContext,
Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText,
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext,
WeakFocusHandle, WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -154,6 +156,7 @@ use workspace::{
use workspace::{OpenInTerminal, OpenTerminal, TabBarSettings, Toast};
use crate::hover_links::find_url;
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
pub const FILE_HEADER_HEIGHT: u8 = 1;
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u8 = 1;
@@ -268,7 +271,7 @@ pub fn init(cx: &mut AppContext) {
init_settings(cx);
workspace::register_project_item::<Editor>(cx);
workspace::register_followable_item::<Editor>(cx);
workspace::FollowableViewRegistry::register::<Editor>(cx);
workspace::register_deserializable_item::<Editor>(cx);
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
@@ -501,6 +504,8 @@ pub struct Editor {
context_menu: RwLock<Option<ContextMenu>>,
mouse_context_menu: Option<MouseContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
signature_help_state: SignatureHelpState,
auto_signature_help: Option<bool>,
find_all_references_task_sources: Vec<Anchor>,
next_completion_id: CompletionId,
completion_documentation_pre_resolve_debounce: DebouncedDelay,
@@ -1765,6 +1770,8 @@ impl Editor {
);
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, Self::handle_focus).detach();
cx.on_focus_in(&focus_handle, Self::handle_focus_in)
.detach();
cx.on_focus_out(&focus_handle, Self::handle_focus_out)
.detach();
cx.on_blur(&focus_handle, Self::handle_blur).detach();
@@ -1819,6 +1826,8 @@ impl Editor {
context_menu: RwLock::new(None),
mouse_context_menu: None,
completion_tasks: Default::default(),
signature_help_state: SignatureHelpState::default(),
auto_signature_help: None,
find_all_references_task_sources: Vec::new(),
next_completion_id: 0,
completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
@@ -2156,6 +2165,10 @@ impl Editor {
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
self.cursor_shape = cursor_shape;
// Disrupt blink for immediate user feedback that the cursor shape has changed
self.blink_manager.update(cx, BlinkManager::show_cursor);
cx.notify();
}
@@ -2411,6 +2424,15 @@ impl Editor {
self.request_autoscroll(autoscroll, cx);
}
self.selections_did_change(true, &old_cursor_position, request_completions, cx);
if self.should_open_signature_help_automatically(
&old_cursor_position,
self.signature_help_state.backspace_pressed(),
cx,
) {
self.show_signature_help(&ShowSignatureHelp, cx);
}
self.signature_help_state.set_backspace_pressed(false);
}
result
@@ -2866,6 +2888,10 @@ impl Editor {
return true;
}
if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
return true;
}
if self.hide_context_menu(cx).is_some() {
return true;
}
@@ -2942,7 +2968,7 @@ impl Editor {
}
let selections = self.selections.all_adjusted(cx);
let mut brace_inserted = false;
let mut bracket_inserted = false;
let mut edits = Vec::new();
let mut linked_edits = HashMap::<_, Vec<_>>::default();
let mut new_selections = Vec::with_capacity(selections.len());
@@ -3004,6 +3030,7 @@ impl Editor {
),
&bracket_pair.start[..prefix_len],
));
if autoclose
&& bracket_pair.close
&& following_text_allows_autoclose
@@ -3021,7 +3048,7 @@ impl Editor {
selection.range(),
format!("{}{}", text, bracket_pair.end).into(),
));
brace_inserted = true;
bracket_inserted = true;
continue;
}
}
@@ -3067,7 +3094,7 @@ impl Editor {
selection.end..selection.end,
bracket_pair.end.as_str().into(),
));
brace_inserted = true;
bracket_inserted = true;
new_selections.push((
Selection {
id: selection.id,
@@ -3224,7 +3251,7 @@ impl Editor {
s.select(new_selections)
});
if !brace_inserted && EditorSettings::get_global(cx).use_on_type_format {
if !bracket_inserted && EditorSettings::get_global(cx).use_on_type_format {
if let Some(on_type_format_task) =
this.trigger_on_type_formatting(text.to_string(), cx)
{
@@ -3232,6 +3259,14 @@ impl Editor {
}
}
let editor_settings = EditorSettings::get_global(cx);
if bracket_inserted
&& (editor_settings.auto_signature_help
|| editor_settings.show_signature_help_after_edits)
{
this.show_signature_help(&ShowSignatureHelp, cx);
}
let trigger_in_words = !had_active_inline_completion;
this.trigger_completion_on_input(&text, trigger_in_words, cx);
linked_editing_ranges::refresh_linked_ranges(this, cx);
@@ -4305,6 +4340,14 @@ impl Editor {
true,
cx,
);
let editor_settings = EditorSettings::get_global(cx);
if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
// After the code completion is finished, users often want to know what signatures are needed.
// so we should automatically call signature_help
self.show_signature_help(&ShowSignatureHelp, cx);
}
Some(cx.foreground_executor().spawn(async move {
apply_edits.await?;
Ok(())
@@ -5328,6 +5371,7 @@ impl Editor {
}
}
this.signature_help_state.set_backspace_pressed(true);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
this.insert("", cx);
let empty_str: Arc<str> = Arc::from("");
@@ -8487,7 +8531,7 @@ impl Editor {
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
let (worktree_id, file) = project
.buffer_for_id(runnable.buffer)
.buffer_for_id(runnable.buffer, cx)
.and_then(|buffer| buffer.read(cx).file())
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
.unzip();
@@ -9447,6 +9491,11 @@ impl Editor {
}
editor
});
cx.subscribe(&rename_editor, |_, _, e, cx| match e {
EditorEvent::Focused => cx.emit(EditorEvent::FocusedIn),
_ => {}
})
.detach();
let write_highlights =
this.clear_background_highlights::<DocumentHighlightWrite>(cx);
@@ -9720,7 +9769,7 @@ impl Editor {
*block_id,
(
None,
diagnostic_block_renderer(diagnostic.clone(), is_valid),
diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid),
),
);
}
@@ -9773,7 +9822,7 @@ impl Editor {
style: BlockStyle::Fixed,
position: buffer.anchor_after(entry.range.start),
height: message_height,
render: diagnostic_block_renderer(diagnostic, true),
render: diagnostic_block_renderer(diagnostic, None, true, true),
disposition: BlockDisposition::Below,
}
}),
@@ -11590,6 +11639,10 @@ impl Editor {
}
}
fn handle_focus_in(&mut self, cx: &mut ViewContext<Self>) {
cx.emit(EditorEvent::FocusedIn)
}
fn handle_focus_out(&mut self, event: FocusOutEvent, _cx: &mut ViewContext<Self>) {
if event.blurred != self.focus_handle {
self.last_focused_descendant = Some(event.blurred);
@@ -11604,8 +11657,11 @@ impl Editor {
if let Some(blame) = self.blame.as_ref() {
blame.update(cx, GitBlame::blur)
}
if !self.hover_state.focused(cx) {
hide_hover(self, cx);
}
self.hide_context_menu(cx);
hide_hover(self, cx);
cx.emit(EditorEvent::Blurred);
cx.notify();
}
@@ -12191,6 +12247,7 @@ pub enum EditorEvent {
},
Reparsed(BufferId),
Focused,
FocusedIn,
Blurred,
DirtyChanged,
Saved,
@@ -12639,11 +12696,17 @@ impl InvalidationRegion for SnippetState {
}
}
pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock {
let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic);
pub fn diagnostic_block_renderer(
diagnostic: Diagnostic,
max_message_rows: Option<u8>,
allow_closing: bool,
_is_valid: bool,
) -> RenderBlock {
let (text_without_backticks, code_ranges) =
highlight_diagnostic_message(&diagnostic, max_message_rows);
Box::new(move |cx: &mut BlockContext| {
let group_id: SharedString = cx.block_id.to_string().into();
let group_id: SharedString = cx.transform_block_id.to_string().into();
let mut text_style = cx.text_style().clone();
text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
@@ -12655,23 +12718,25 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
let multi_line_diagnostic = diagnostic.message.contains('\n');
let buttons = |diagnostic: &Diagnostic, block_id: usize| {
let buttons = |diagnostic: &Diagnostic, block_id: TransformBlockId| {
if multi_line_diagnostic {
v_flex()
} else {
h_flex()
}
.children(diagnostic.is_primary.then(|| {
IconButton::new(("close-block", block_id), IconName::XCircle)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
.visible_on_hover(group_id.clone())
.on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
.tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
}))
.when(allow_closing, |div| {
div.children(diagnostic.is_primary.then(|| {
IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
.visible_on_hover(group_id.clone())
.on_click(move |_click, cx| cx.dispatch_action(Box::new(Cancel)))
.tooltip(|cx| Tooltip::for_action("Close Diagnostics", &Cancel, cx))
}))
})
.child(
IconButton::new(("copy-block", block_id), IconName::Copy)
IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
@@ -12684,12 +12749,12 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
)
};
let icon_size = buttons(&diagnostic, cx.block_id)
let icon_size = buttons(&diagnostic, cx.transform_block_id)
.into_any_element()
.layout_as_root(AvailableSpace::min_size(), cx);
h_flex()
.id(cx.block_id)
.id(cx.transform_block_id)
.group(group_id.clone())
.relative()
.size_full()
@@ -12701,7 +12766,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
.w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
.flex_shrink(),
)
.child(buttons(&diagnostic, cx.block_id))
.child(buttons(&diagnostic, cx.transform_block_id))
.child(div().flex().flex_shrink_0().child(
StyledText::new(text_without_backticks.clone()).with_highlights(
&text_style,
@@ -12720,7 +12785,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
})
}
pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, Vec<Range<usize>>) {
pub fn highlight_diagnostic_message(
diagnostic: &Diagnostic,
mut max_message_rows: Option<u8>,
) -> (SharedString, Vec<Range<usize>>) {
let mut text_without_backticks = String::new();
let mut code_ranges = Vec::new();
@@ -12732,18 +12800,45 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V
let mut prev_offset = 0;
let mut in_code_block = false;
let mut newline_indices = diagnostic
.message
.match_indices('\n')
.map(|(ix, _)| ix)
.fuse()
.peekable();
for (ix, _) in diagnostic
.message
.match_indices('`')
.chain([(diagnostic.message.len(), "")])
{
let mut trimmed_ix = ix;
while let Some(newline_index) = newline_indices.peek() {
if *newline_index < ix {
if let Some(rows_left) = &mut max_message_rows {
if *rows_left == 0 {
trimmed_ix = newline_index.saturating_sub(1);
break;
} else {
*rows_left -= 1;
}
}
let _ = newline_indices.next();
} else {
break;
}
}
let prev_len = text_without_backticks.len();
text_without_backticks.push_str(&diagnostic.message[prev_offset..ix]);
prev_offset = ix + 1;
let new_text = &diagnostic.message[prev_offset..trimmed_ix];
text_without_backticks.push_str(new_text);
if in_code_block {
code_ranges.push(prev_len..text_without_backticks.len());
}
prev_offset = trimmed_ix + 1;
in_code_block = !in_code_block;
if trimmed_ix != ix {
text_without_backticks.push_str("...");
break;
}
}
(text_without_backticks.into(), code_ranges)

View File

@@ -25,6 +25,9 @@ pub struct EditorSettings {
pub expand_excerpt_lines: u32,
#[serde(default)]
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
pub search_wrap: bool,
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
#[serde(default)]
pub jupyter: Jupyter,
}
@@ -228,6 +231,20 @@ pub struct EditorSettingsContent {
///
/// Default: select
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
/// Whether the editor search results will loop
///
/// Default: true
pub search_wrap: Option<bool>,
/// Whether to automatically show a signature help pop-up or not.
///
/// Default: false
pub auto_signature_help: Option<bool>,
/// Whether to show the signature help pop-up after completions or bracket pairs inserted.
///
/// Default: true
pub show_signature_help_after_edits: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<Jupyter>,

View File

@@ -21,13 +21,16 @@ use language::{
BracketPairConfig,
Capability::ReadWrite,
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
Point,
ParsedMarkdown, Point,
};
use language_settings::IndentGuideSettings;
use multi_buffer::MultiBufferIndentGuide;
use parking_lot::Mutex;
use project::project_settings::{LspSettings, ProjectSettings};
use project::FakeFs;
use project::{
lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
project_settings::{LspSettings, ProjectSettings},
};
use serde_json::{self, json};
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
@@ -6831,6 +6834,626 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
);
}
#[gpui::test]
async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
cx: &mut gpui::TestAppContext,
) {
init_test(cx, |_| {});
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
});
});
});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/*".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "\"".to_string(),
end: "\"".to_string(),
close: true,
surround: true,
newline: false,
},
BracketPair {
start: "<".to_string(),
end: ">".to_string(),
close: false,
surround: true,
newline: true,
},
],
..Default::default()
},
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
);
let language = Arc::new(language);
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language(Some(language), cx);
});
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
});
}
#[gpui::test]
async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(false);
settings.show_signature_help_after_edits = Some(false);
});
});
});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/*".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "\"".to_string(),
end: "\"".to_string(),
close: true,
surround: true,
newline: false,
},
BracketPair {
start: "<".to_string(),
end: ">".to_string(),
close: false,
surround: true,
newline: true,
},
],
..Default::default()
},
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
);
let language = Arc::new(language);
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language(Some(language), cx);
});
// Ensure that signature_help is not called when no signature help is enabled.
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
cx.editor(|editor, _| {
assert!(editor.signature_help_state.task().is_none());
});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
// Ensure that signature_help is called when enabled afte edits
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(false);
settings.show_signature_help_after_edits = Some(true);
});
});
});
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.update_editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
editor.signature_help_state = SignatureHelpState::default();
});
// Ensure that signature_help is called when auto signature help override is enabled
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
settings.show_signature_help_after_edits = Some(false);
});
});
});
cx.set_state(
&r#"
fn main() {
sampleˇ
}
"#
.unindent(),
);
cx.update_editor(|view, cx| {
view.handle_input("(", cx);
});
cx.assert_editor_state(
&"
fn main() {
sample(ˇ)
}
"
.unindent(),
);
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
});
}
#[gpui::test]
async fn test_signature_help(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
});
});
});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
signature_help_provider: Some(lsp::SignatureHelpOptions {
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
// A test that directly calls `show_signature_help`
cx.update_editor(|editor, cx| {
editor.show_signature_help(&ShowSignatureHelp, cx);
});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
let signature_help_state = editor.signature_help_state.popover().cloned();
assert!(signature_help_state.is_some());
let ParsedMarkdown {
text, highlights, ..
} = signature_help_state.unwrap().parsed_content;
assert_eq!(text, "param1: u8, param2: u8");
assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
});
// When exiting outside from inside the brackets, `signature_help` is closed.
cx.set_state(indoc! {"
fn main() {
sample(ˇ);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
});
let mocked_response = lsp::SignatureHelp {
signatures: Vec::new(),
active_signature: None,
active_parameter: None,
};
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| !editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
assert!(!editor.signature_help_state.is_shown());
});
// When entering inside the brackets from outside, `show_signature_help` is automatically called.
cx.set_state(indoc! {"
fn main() {
sample(ˇ);
}
fn sample(param1: u8, param2: u8) {}
"});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
assert!(editor.signature_help_state.is_shown());
});
// Restore the popover with more parameter input
cx.set_state(indoc! {"
fn main() {
sample(param1, param2ˇ);
}
fn sample(param1: u8, param2: u8) {}
"});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(1),
};
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
// When selecting a range, the popover is gone.
// Avoid using `cx.set_state` to not actually edit the document, just change its selections.
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, «ˇparam2»);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.editor(|editor, _| {
assert!(!editor.signature_help_state.is_shown());
});
// When unselecting again, the popover is back if within the brackets.
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, ˇparam2);
}
fn sample(param1: u8, param2: u8) {}
"});
handle_signature_help_request(&mut cx, mocked_response).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.editor(|editor, _| {
assert!(editor.signature_help_state.is_shown());
});
// Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, ˇparam2);
}
fn sample(param1: u8, param2: u8) {}
"});
let mocked_response = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn sample(param1: u8, param2: u8)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(1),
};
handle_signature_help_request(&mut cx, mocked_response.clone()).await;
cx.condition(|editor, _| editor.signature_help_state.is_shown())
.await;
cx.update_editor(|editor, cx| {
editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
});
cx.condition(|editor, _| !editor.signature_help_state.is_shown())
.await;
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, «ˇparam2»);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.update_editor(|editor, cx| {
editor.change_selections(None, cx, |s| {
s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
})
});
cx.assert_editor_state(indoc! {"
fn main() {
sample(param1, ˇparam2);
}
fn sample(param1: u8, param2: u8) {}
"});
cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
.await;
}
#[gpui::test]
async fn test_completion(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -8189,7 +8812,6 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
let follower_1 = cx
.update_window(*workspace.deref(), |_, cx| {
Editor::from_state_proto(
pane.clone(),
workspace.root_view(cx).unwrap(),
ViewId {
creator: Default::default(),
@@ -8281,7 +8903,6 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
let follower_2 = cx
.update_window(*workspace.deref(), |_, cx| {
Editor::from_state_proto(
pane.clone(),
workspace.root_view(cx).unwrap().clone(),
ViewId {
creator: Default::default(),
@@ -12450,6 +13071,21 @@ fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewCo
);
}
pub fn handle_signature_help_request(
cx: &mut EditorLspTestContext,
mocked_response: lsp::SignatureHelp,
) -> impl Future<Output = ()> {
let mut request =
cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
let mocked_response = mocked_response.clone();
async move { Ok(Some(mocked_response)) }
});
async move {
request.next().await;
}
}
/// Handle completion request passing a marked string specifying where the completion
/// should be triggered from using '|' character, what range should be replaced, and what completions
/// should be returned using '<' and '>' to delimit the range

View File

@@ -1,4 +1,5 @@
use crate::editor_settings::ScrollBeyondLastLine;
use crate::TransformBlockId;
use crate::{
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
display_map::{
@@ -31,7 +32,7 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
EntityId, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
@@ -382,6 +383,7 @@ impl EditorElement {
cx.propagate();
}
});
register_action(view, cx, Editor::show_signature_help);
register_action(view, cx, Editor::next_inline_completion);
register_action(view, cx, Editor::previous_inline_completion);
register_action(view, cx, Editor::show_inline_completion);
@@ -407,8 +409,10 @@ impl EditorElement {
if phase != DispatchPhase::Bubble {
return;
}
editor.update(cx, |editor, cx| {
if editor.hover_state.focused(cx) {
return;
}
Self::modifiers_changed(editor, event, &position_map, &text_hitbox, cx)
})
}
@@ -709,18 +713,24 @@ impl EditorElement {
let Some(hub) = editor.collaboration_hub() else {
return;
};
let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
..DisplayPoint::new(
let start = snapshot.display_snapshot.clip_point(
DisplayPoint::new(point.row(), point.column().saturating_sub(1)),
Bias::Left,
);
let end = snapshot.display_snapshot.clip_point(
DisplayPoint::new(
point.row(),
(point.column() + 1).min(snapshot.line_len(point.row())),
);
),
Bias::Right,
);
let range = snapshot
.buffer_snapshot
.anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
.anchor_at(start.to_point(&snapshot.display_snapshot), Bias::Left)
..snapshot
.buffer_snapshot
.anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
.anchor_at(end.to_point(&snapshot.display_snapshot), Bias::Right);
let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
return;
@@ -1930,7 +1940,6 @@ impl EditorElement {
line_layouts: &[LineWithInvisibles],
cx: &mut WindowContext,
) -> Vec<BlockLayout> {
let mut block_id = 0;
let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone())
.partition::<Vec<_>, _>(|(_, block)| match block {
@@ -1941,7 +1950,7 @@ impl EditorElement {
let render_block = |block: &TransformBlock,
available_space: Size<AvailableSpace>,
block_id: usize,
block_id: TransformBlockId,
block_row_start: DisplayRow,
cx: &mut WindowContext| {
let mut element = match block {
@@ -1965,7 +1974,7 @@ impl EditorElement {
gutter_dimensions,
line_height,
em_width,
block_id,
transform_block_id: block_id,
max_width: text_hitbox.size.width.max(*scroll_width),
editor_style: &self.style,
})
@@ -2049,7 +2058,7 @@ impl EditorElement {
let header_padding = px(6.0);
v_flex()
.id(("path excerpt header", block_id))
.id(("path excerpt header", EntityId::from(block_id)))
.size_full()
.p(header_padding)
.child(
@@ -2157,7 +2166,7 @@ impl EditorElement {
}))
} else {
v_flex()
.id(("excerpt header", block_id))
.id(("excerpt header", EntityId::from(block_id)))
.size_full()
.child(
div()
@@ -2305,49 +2314,54 @@ impl EditorElement {
}
TransformBlock::ExcerptFooter { id, .. } => {
let element = v_flex().id(("excerpt footer", block_id)).size_full().child(
h_flex()
.justify_end()
.flex_none()
.w(gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin))
.h_full()
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowDownFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| {
style.text_color(
cx.theme().colors().editor_active_line_number,
let element = v_flex()
.id(("excerpt footer", EntityId::from(block_id)))
.size_full()
.child(
h_flex()
.justify_end()
.flex_none()
.w(gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin))
.h_full()
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowDownFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| {
style.text_color(
cx.theme()
.colors()
.editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Down,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Down,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
}),
),
);
}
}),
),
);
element.into_any()
}
};
@@ -2363,8 +2377,8 @@ impl EditorElement {
AvailableSpace::MinContent,
AvailableSpace::Definite(block.height() as f32 * line_height),
);
let block_id = block.id();
let (element, element_size) = render_block(block, available_space, block_id, row, cx);
block_id += 1;
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
blocks.push(BlockLayout {
row,
@@ -2392,8 +2406,8 @@ impl EditorElement {
AvailableSpace::Definite(width),
AvailableSpace::Definite(block.height() as f32 * line_height),
);
let block_id = block.id();
let (element, _) = render_block(block, available_space, block_id, row, cx);
block_id += 1;
blocks.push(BlockLayout {
row,
element,
@@ -2629,6 +2643,73 @@ impl EditorElement {
}
}
#[allow(clippy::too_many_arguments)]
fn layout_signature_help(
&self,
hitbox: &Hitbox,
content_origin: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<Pixels>,
newest_selection_head: Option<DisplayPoint>,
start_row: DisplayRow,
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
em_width: Pixels,
cx: &mut WindowContext,
) {
let Some(newest_selection_head) = newest_selection_head else {
return;
};
let selection_row = newest_selection_head.row();
if selection_row < start_row {
return;
}
let Some(cursor_row_layout) = line_layouts.get(selection_row.minus(start_row) as usize)
else {
return;
};
let start_x = cursor_row_layout.x_for_index(newest_selection_head.column() as usize)
- scroll_pixel_position.x
+ content_origin.x;
let start_y =
selection_row.as_f32() * line_height + content_origin.y - scroll_pixel_position.y;
let max_size = size(
(120. * em_width) // Default size
.min(hitbox.size.width / 2.) // Shrink to half of the editor width
.max(MIN_POPOVER_CHARACTER_WIDTH * em_width), // Apply minimum width of 20 characters
(16. * line_height) // Default size
.min(hitbox.size.height / 2.) // Shrink to half of the editor height
.max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
);
let maybe_element = self.editor.update(cx, |editor, cx| {
if let Some(popover) = editor.signature_help_state.popover_mut() {
let element = popover.render(
&self.style,
max_size,
editor.workspace.as_ref().map(|(w, _)| w.clone()),
cx,
);
Some(element)
} else {
None
}
});
if let Some(mut element) = maybe_element {
let window_size = cx.viewport_size();
let size = element.layout_as_root(Size::<AvailableSpace>::default(), cx);
let mut point = point(start_x, start_y - size.height);
// Adjusting to ensure the popover does not overflow in the X-axis direction.
if point.x + size.width >= window_size.width {
point.x = window_size.width - size.width;
}
cx.defer_draw(element, point, 1)
}
}
fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) {
cx.paint_layer(layout.hitbox.bounds, |cx| {
let scroll_top = layout.position_map.snapshot.scroll_position().y;
@@ -3734,6 +3815,9 @@ impl EditorElement {
move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
editor.update(cx, |editor, cx| {
if editor.hover_state.focused(cx) {
return;
}
if event.pressed_button == Some(MouseButton::Left)
|| event.pressed_button == Some(MouseButton::Middle)
{
@@ -5063,6 +5147,18 @@ impl Element for EditorElement {
vec![]
};
self.layout_signature_help(
&hitbox,
content_origin,
scroll_pixel_position,
newest_selection_head,
start_row,
&line_layouts,
line_height,
em_width,
cx,
);
if !cx.has_active_drag() {
self.layout_hover_popovers(
&snapshot,

View File

@@ -5,24 +5,26 @@ use crate::{
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
EditorStyle, Hover, RangeToAnchorExt,
};
use futures::{stream::FuturesUnordered, FutureExt};
use gpui::{
div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
ParentElement, Pixels, ScrollHandle, SharedString, Size, StatefulInteractiveElement, Styled,
Task, ViewContext, WeakView,
div, px, AnyElement, AsyncWindowContext, CursorStyle, FontWeight, Hsla, InteractiveElement,
IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle, SharedString, Size,
StatefulInteractiveElement, StyleRefinement, Styled, Task, TextStyleRefinement, View,
ViewContext, WeakView,
};
use language::{markdown, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
use itertools::Itertools;
use language::{DiagnosticEntry, Language, LanguageRegistry};
use lsp::DiagnosticSeverity;
use markdown::{Markdown, MarkdownStyle};
use multi_buffer::ToOffset;
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
use project::{HoverBlock, InlayHintLabelPart};
use settings::Settings;
use smol::stream::StreamExt;
use std::rc::Rc;
use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings;
use ui::{prelude::*, window_is_transparent, Tooltip};
use util::TryFutureExt;
use workspace::Workspace;
pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@@ -40,6 +42,9 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
/// depending on whether a point to hover over is provided.
pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContext<Editor>) {
if EditorSettings::get_global(cx).hover_popover_enabled {
if show_keyboard_hover(editor, cx) {
return;
}
if let Some(anchor) = anchor {
show_hover(editor, anchor, false, cx);
} else {
@@ -48,6 +53,20 @@ pub fn hover_at(editor: &mut Editor, anchor: Option<Anchor>, cx: &mut ViewContex
}
}
pub fn show_keyboard_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
let info_popovers = editor.hover_state.info_popovers.clone();
for p in info_popovers {
let keyboard_grace = p.keyboard_grace.borrow();
if *keyboard_grace {
if let Some(anchor) = p.anchor {
show_hover(editor, anchor, false, cx);
return true;
}
}
}
return false;
}
pub struct InlayHover {
pub range: InlayHighlight,
pub tooltip: HoverBlock,
@@ -113,12 +132,14 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let language_registry = project.update(&mut cx, |p, _| p.languages().clone())?;
let blocks = vec![inlay_hover.tooltip];
let parsed_content = parse_blocks(&blocks, &language_registry, None).await;
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
scroll_handle: ScrollHandle::new(),
keyboard_grace: Rc::new(RefCell::new(false)),
anchor: None,
};
this.update(&mut cx, |this, cx| {
@@ -291,39 +312,40 @@ fn show_hover(
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
let mut info_popovers = Vec::with_capacity(hovers_response.len());
let mut info_popover_tasks = hovers_response
.into_iter()
.map(|hover_result| async {
// Create symbol range of anchors for highlighting and filtering of future requests.
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
let mut info_popover_tasks = Vec::with_capacity(hovers_response.len());
Some(start..end)
})
.unwrap_or_else(|| anchor..anchor);
for hover_result in hovers_response {
// Create symbol range of anchors for highlighting and filtering of future requests.
let range = hover_result
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
let blocks = hover_result.contents;
let language = hover_result.language;
let parsed_content = parse_blocks(&blocks, &language_registry, language).await;
Some(start..end)
})
.unwrap_or_else(|| anchor..anchor);
(
range.clone(),
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scroll_handle: ScrollHandle::new(),
},
)
})
.collect::<FuturesUnordered<_>>();
while let Some((highlight_range, info_popover)) = info_popover_tasks.next().await {
let blocks = hover_result.contents;
let language = hover_result.language;
let parsed_content =
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
info_popover_tasks.push((
range.clone(),
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scroll_handle: ScrollHandle::new(),
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
},
));
}
for (highlight_range, info_popover) in info_popover_tasks {
hover_highlights.push(highlight_range);
info_popovers.push(info_popover);
}
@@ -357,72 +379,81 @@ async fn parse_blocks(
blocks: &[HoverBlock],
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
) -> markdown::ParsedMarkdown {
let mut text = String::new();
let mut highlights = Vec::new();
let mut region_ranges = Vec::new();
let mut regions = Vec::new();
cx: &mut AsyncWindowContext,
) -> Option<View<Markdown>> {
let fallback_language_name = if let Some(ref l) = language {
let l = Arc::clone(l);
Some(l.lsp_id().clone())
} else {
None
};
for block in blocks {
match &block.kind {
HoverBlockKind::PlainText => {
markdown::new_paragraph(&mut text, &mut Vec::new());
text.push_str(&block.text.replace("\\n", "\n"));
let combined_text = blocks
.iter()
.map(|block| match &block.kind {
project::HoverBlockKind::PlainText | project::HoverBlockKind::Markdown => {
Cow::Borrowed(block.text.trim())
}
HoverBlockKind::Markdown => {
markdown::parse_markdown_block(
&block.text.replace("\\n", "\n"),
language_registry,
language.clone(),
&mut text,
&mut highlights,
&mut region_ranges,
&mut regions,
)
.await
project::HoverBlockKind::Code { language } => {
Cow::Owned(format!("```{}\n{}\n```", language, block.text.trim()))
}
})
.join("\n\n");
HoverBlockKind::Code { language } => {
if let Some(language) = language_registry
.language_for_name(language)
.now_or_never()
.and_then(Result::ok)
{
markdown::highlight_code(&mut text, &mut highlights, &block.text, &language);
} else {
text.push_str(&block.text);
}
}
}
}
let rendered_block = cx
.new_view(|cx| {
let settings = ThemeSettings::get_global(cx);
let buffer_font_family = settings.buffer_font.family.clone();
let mut base_style = cx.text_style();
base_style.refine(&TextStyleRefinement {
font_family: Some(buffer_font_family.clone()),
color: Some(cx.theme().colors().editor_foreground),
..Default::default()
});
let leading_space = text.chars().take_while(|c| c.is_whitespace()).count();
if leading_space > 0 {
highlights = highlights
.into_iter()
.map(|(range, style)| {
(
range.start.saturating_sub(leading_space)
..range.end.saturating_sub(leading_space),
style,
)
})
.collect();
region_ranges = region_ranges
.into_iter()
.map(|range| {
range.start.saturating_sub(leading_space)..range.end.saturating_sub(leading_space)
})
.collect();
}
let markdown_style = MarkdownStyle {
base_text_style: base_style,
code_block: StyleRefinement::default().mt(rems(1.)).mb(rems(1.)),
inline_code: TextStyleRefinement {
background_color: Some(cx.theme().colors().background),
..Default::default()
},
rule_color: Color::Muted.color(cx),
block_quote_border_color: Color::Muted.color(cx),
block_quote: TextStyleRefinement {
color: Some(Color::Muted.color(cx)),
..Default::default()
},
link: TextStyleRefinement {
color: Some(cx.theme().colors().editor_foreground),
underline: Some(gpui::UnderlineStyle {
thickness: px(1.),
color: Some(cx.theme().colors().editor_foreground),
wavy: false,
}),
..Default::default()
},
syntax: cx.theme().syntax().clone(),
selection_background_color: { cx.theme().players().local().selection },
break_style: Default::default(),
heading: StyleRefinement::default()
.font_weight(FontWeight::BOLD)
.text_base()
.mt(rems(1.))
.mb_0(),
};
ParsedMarkdown {
text: text.trim().to_string(),
highlights,
region_ranges,
regions,
}
Markdown::new(
combined_text,
markdown_style.clone(),
Some(language_registry.clone()),
cx,
fallback_language_name,
)
})
.ok();
rendered_block
}
#[derive(Default, Debug)]
@@ -444,7 +475,7 @@ impl HoverState {
style: &EditorStyle,
visible_rows: Range<DisplayRow>,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<(DisplayPoint, Vec<AnyElement>)> {
// If there is a diagnostic, position the popovers based on that.
@@ -482,29 +513,39 @@ impl HoverState {
elements.push(diagnostic_popover.render(style, max_size, cx));
}
for info_popover in &mut self.info_popovers {
elements.push(info_popover.render(style, max_size, workspace.clone(), cx));
elements.push(info_popover.render(max_size, cx));
}
Some((point, elements))
}
pub fn focused(&self, cx: &mut ViewContext<Editor>) -> bool {
let mut hover_popover_is_focused = false;
for info_popover in &self.info_popovers {
for markdown_view in &info_popover.parsed_content {
if markdown_view.focus_handle(cx).is_focused(cx) {
hover_popover_is_focused = true;
}
}
}
return hover_popover_is_focused;
}
}
#[derive(Clone, Debug)]
#[derive(Debug, Clone)]
pub struct InfoPopover {
pub symbol_range: RangeInEditor,
pub parsed_content: ParsedMarkdown,
pub parsed_content: Option<View<Markdown>>,
pub scroll_handle: ScrollHandle,
pub keyboard_grace: Rc<RefCell<bool>>,
pub anchor: Option<Anchor>,
}
impl InfoPopover {
pub fn render(
&mut self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
div()
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
let keyboard_grace = Rc::clone(&self.keyboard_grace);
let mut d = div()
.id("info_popover")
.elevation_2(cx)
.overflow_y_scroll()
@@ -514,15 +555,17 @@ impl InfoPopover {
// Prevent a mouse down/move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(div().p_2().child(crate::render_parsed_markdown(
"content",
&self.parsed_content,
style,
workspace,
cx,
)))
.into_any_element()
.on_mouse_down(MouseButton::Left, move |_, cx| {
let mut keyboard_grace = keyboard_grace.borrow_mut();
*keyboard_grace = false;
cx.stop_propagation();
})
.p_2();
if let Some(markdown) = &self.parsed_content {
d = d.child(markdown.clone());
}
d.into_any_element()
}
pub fn scroll(&self, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
@@ -593,8 +636,6 @@ impl DiagnosticPopover {
.when(window_is_transparent(cx), |this| {
this.bg(gpui::transparent_black())
})
.max_w(max_size.width)
.max_h(max_size.height)
.cursor(CursorStyle::PointingHand)
.tooltip(move |cx| Tooltip::for_action("Go To Diagnostic", &crate::GoToDiagnostic, cx))
// Prevent a mouse move on the popover from being propagated to the editor,
@@ -608,6 +649,8 @@ impl DiagnosticPopover {
div()
.id("diagnostic-inner")
.overflow_y_scroll()
.max_w(max_size.width)
.max_h(max_size.height)
.px_2()
.py_1()
.bg(diagnostic_colors.background)
@@ -642,17 +685,33 @@ mod tests {
InlayId, PointForPosition,
};
use collections::BTreeSet;
use gpui::{FontWeight, HighlightStyle, UnderlineStyle};
use indoc::indoc;
use language::{language_settings::InlayHintSettings, Diagnostic, DiagnosticSet};
use lsp::LanguageServerId;
use project::{HoverBlock, HoverBlockKind};
use markdown::parser::MarkdownEvent;
use smol::stream::StreamExt;
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use text::Bias;
use unindent::Unindent;
use util::test::marked_text_ranges;
impl InfoPopover {
fn get_rendered_text(&self, cx: &gpui::AppContext) -> String {
let mut rendered_text = String::new();
if let Some(parsed_content) = self.parsed_content.clone() {
let markdown = parsed_content.read(cx);
let text = markdown.parsed_markdown().source().to_string();
let data = markdown.parsed_markdown().events();
let slice = data;
for (range, event) in slice.iter() {
if [MarkdownEvent::Text, MarkdownEvent::Code].contains(event) {
rendered_text.push_str(&text[range.clone()])
}
}
}
rendered_text
}
}
#[gpui::test]
async fn test_mouse_hover_info_popover_with_autocomplete_popover(
@@ -736,7 +795,7 @@ mod tests {
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
cx.editor(|editor, _| {
cx.editor(|editor, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -744,14 +803,13 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered = editor
let rendered_text = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.parsed_content;
assert_eq!(rendered.text, "some basic docs".to_string())
.get_rendered_text(cx);
assert_eq!(rendered_text, "some basic docs".to_string())
});
// check that the completion menu is still visible and that there still has only been 1 completion request
@@ -777,7 +835,7 @@ mod tests {
assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
//verify the information popover is still visible and unchanged
cx.editor(|editor, _| {
cx.editor(|editor, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -785,14 +843,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered = editor
let rendered_text = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.parsed_content;
assert_eq!(rendered.text, "some basic docs".to_string())
.get_rendered_text(cx);
assert_eq!(rendered_text, "some basic docs".to_string())
});
// Mouse moved with no hover response dismisses
@@ -870,7 +928,7 @@ mod tests {
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
requests.next().await;
cx.editor(|editor, _| {
cx.editor(|editor, cx| {
assert!(editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
@@ -878,14 +936,14 @@ mod tests {
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered = editor
let rendered_text = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.parsed_content;
assert_eq!(rendered.text, "some basic docs".to_string())
.get_rendered_text(cx);
assert_eq!(rendered_text, "some basic docs".to_string())
});
// Mouse moved with no hover response dismisses
@@ -931,34 +989,49 @@ mod tests {
let symbol_range = cx.lsp_range(indoc! {"
«fn» test() { println!(); }
"});
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: "some other basic docs".to_string(),
}),
range: Some(symbol_range),
}))
})
.next()
.await;
cx.editor(|editor, _cx| {
assert!(!editor.hover_state.visible());
assert_eq!(
editor.hover_state.info_popovers.len(),
0,
"Expected no hovers but got but got: {:?}",
editor.hover_state.info_popovers
);
});
let mut requests =
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: "some other basic docs".to_string(),
}),
range: Some(symbol_range),
}))
});
requests.next().await;
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, _| {
cx.editor(|editor, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered = editor
let rendered_text = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.parsed_content;
assert_eq!(rendered.text, "some other basic docs".to_string())
.get_rendered_text(cx);
assert_eq!(rendered_text, "some other basic docs".to_string())
});
}
@@ -998,24 +1071,25 @@ mod tests {
})
.next()
.await;
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, _| {
cx.editor(|editor, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered = editor
let rendered_text = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.parsed_content;
.get_rendered_text(cx);
assert_eq!(
rendered.text,
rendered_text,
"regular text for hover to show".to_string(),
"No empty string hovers should be shown"
);
@@ -1063,24 +1137,25 @@ mod tests {
.next()
.await;
cx.dispatch_action(Hover);
cx.condition(|editor, _| editor.hover_state.visible()).await;
cx.editor(|editor, _| {
cx.editor(|editor, cx| {
assert_eq!(
editor.hover_state.info_popovers.len(),
1,
"Expected exactly one hover but got: {:?}",
editor.hover_state.info_popovers
);
let rendered = editor
let rendered_text = editor
.hover_state
.info_popovers
.first()
.cloned()
.unwrap()
.parsed_content;
.get_rendered_text(cx);
assert_eq!(
rendered.text,
code_str.trim(),
rendered_text, code_str,
"Should not have extra line breaks at end of rendered hover"
);
});
@@ -1156,153 +1231,6 @@ mod tests {
});
}
#[gpui::test]
fn test_render_blocks(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let languages = Arc::new(LanguageRegistry::test(cx.executor()));
let editor = cx.add_window(|cx| Editor::single_line(cx));
editor
.update(cx, |editor, _cx| {
let style = editor.style.clone().unwrap();
struct Row {
blocks: Vec<HoverBlock>,
expected_marked_text: String,
expected_styles: Vec<HighlightStyle>,
}
let rows = &[
// Strong emphasis
Row {
blocks: vec![HoverBlock {
text: "one **two** three".to_string(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "one «two» three".to_string(),
expected_styles: vec![HighlightStyle {
font_weight: Some(FontWeight::BOLD),
..Default::default()
}],
},
// Links
Row {
blocks: vec![HoverBlock {
text: "one [two](https://the-url) three".to_string(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "one «two» three".to_string(),
expected_styles: vec![HighlightStyle {
underline: Some(UnderlineStyle {
thickness: 1.0.into(),
..Default::default()
}),
..Default::default()
}],
},
// Lists
Row {
blocks: vec![HoverBlock {
text: "
lists:
* one
- a
- b
* two
- [c](https://the-url)
- d"
.unindent(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "
lists:
- one
- a
- b
- two
- «c»
- d"
.unindent(),
expected_styles: vec![HighlightStyle {
underline: Some(UnderlineStyle {
thickness: 1.0.into(),
..Default::default()
}),
..Default::default()
}],
},
// Multi-paragraph list items
Row {
blocks: vec![HoverBlock {
text: "
* one two
three
* four five
* six seven
eight
nine
* ten
* six"
.unindent(),
kind: HoverBlockKind::Markdown,
}],
expected_marked_text: "
- one two three
- four five
- six seven eight
nine
- ten
- six"
.unindent(),
expected_styles: vec![HighlightStyle {
underline: Some(UnderlineStyle {
thickness: 1.0.into(),
..Default::default()
}),
..Default::default()
}],
},
];
for Row {
blocks,
expected_marked_text,
expected_styles,
} in &rows[0..]
{
let rendered = smol::block_on(parse_blocks(&blocks, &languages, None));
let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
let expected_highlights = ranges
.into_iter()
.zip(expected_styles.iter().cloned())
.collect::<Vec<_>>();
assert_eq!(
rendered.text, expected_text,
"wrong text for input {blocks:?}"
);
let rendered_highlights: Vec<_> = rendered
.highlights
.iter()
.filter_map(|(range, highlight)| {
let highlight = highlight.to_highlight_style(&style.syntax)?;
Some((range.clone(), highlight))
})
.collect();
assert_eq!(
rendered_highlights, expected_highlights,
"wrong highlights for input {blocks:?}"
);
}
})
.unwrap();
}
#[gpui::test]
async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
@@ -1546,9 +1474,8 @@ mod tests {
"Popover range should match the new type label part"
);
assert_eq!(
popover.parsed_content.text,
format!("A tooltip for `{new_type_label}`"),
"Rendered text should not anyhow alter backticks"
popover.get_rendered_text(cx),
format!("A tooltip for {new_type_label}"),
);
});
@@ -1602,7 +1529,7 @@ mod tests {
"Popover range should match the struct label part"
);
assert_eq!(
popover.parsed_content.text,
popover.get_rendered_text(cx),
format!("A tooltip for {struct_label}"),
"Rendered markdown element should remove backticks from text"
);

View File

@@ -19,7 +19,7 @@ use multi_buffer::AnchorRangeExt;
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use workspace::item::{ItemSettings, TabContentParams};
use workspace::item::{Dedup, ItemSettings, TabContentParams};
use std::{
any::TypeId,
@@ -34,7 +34,7 @@ use text::{BufferId, Selection};
use theme::{Theme, ThemeSettings};
use ui::{h_flex, prelude::*, Label};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableItemHandle};
use workspace::item::{BreadcrumbText, FollowEvent};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -49,7 +49,6 @@ impl FollowableItem for Editor {
}
fn from_state_proto(
pane: View<workspace::Pane>,
workspace: View<Workspace>,
remote_id: ViewId,
state: &mut Option<proto::view::Variant>,
@@ -63,7 +62,6 @@ impl FollowableItem for Editor {
unreachable!()
};
let client = project.read(cx).client();
let replica_id = project.read(cx).replica_id();
let buffer_ids = state
.excerpts
@@ -77,72 +75,55 @@ impl FollowableItem for Editor {
.collect::<Result<Vec<_>>>()
});
let pane = pane.downgrade();
Some(cx.spawn(|mut cx| async move {
let mut buffers = futures::future::try_join_all(buffers?)
.await
.debug_assert_ok("leaders don't share views for unshared buffers")?;
let editor = pane.update(&mut cx, |pane, cx| {
let mut editors = pane.items_of_type::<Self>();
editors.find(|editor| {
let ids_match = editor.remote_id(&client, cx) == Some(remote_id);
let singleton_buffer_matches = state.singleton
&& buffers.first()
== editor.read(cx).buffer.read(cx).as_singleton().as_ref();
ids_match || singleton_buffer_matches
let editor = cx.update(|cx| {
let multibuffer = cx.new_model(|cx| {
let mut multibuffer;
if state.singleton && buffers.len() == 1 {
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
} else {
multibuffer = MultiBuffer::new(replica_id, project.read(cx).capability());
let mut excerpts = state.excerpts.into_iter().peekable();
while let Some(excerpt) = excerpts.peek() {
let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
continue;
};
let buffer_excerpts = iter::from_fn(|| {
let excerpt = excerpts.peek()?;
(excerpt.buffer_id == u64::from(buffer_id))
.then(|| excerpts.next().unwrap())
});
let buffer =
buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
if let Some(buffer) = buffer {
multibuffer.push_excerpts(
buffer.clone(),
buffer_excerpts.filter_map(deserialize_excerpt_range),
cx,
);
}
}
};
if let Some(title) = &state.title {
multibuffer = multibuffer.with_title(title.clone())
}
multibuffer
});
cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx);
editor.remote_id = Some(remote_id);
editor
})
})?;
let editor = if let Some(editor) = editor {
editor
} else {
pane.update(&mut cx, |_, cx| {
let multibuffer = cx.new_model(|cx| {
let mut multibuffer;
if state.singleton && buffers.len() == 1 {
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
} else {
multibuffer =
MultiBuffer::new(replica_id, project.read(cx).capability());
let mut excerpts = state.excerpts.into_iter().peekable();
while let Some(excerpt) = excerpts.peek() {
let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
continue;
};
let buffer_excerpts = iter::from_fn(|| {
let excerpt = excerpts.peek()?;
(excerpt.buffer_id == u64::from(buffer_id))
.then(|| excerpts.next().unwrap())
});
let buffer =
buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
if let Some(buffer) = buffer {
multibuffer.push_excerpts(
buffer.clone(),
buffer_excerpts.filter_map(deserialize_excerpt_range),
cx,
);
}
}
};
if let Some(title) = &state.title {
multibuffer = multibuffer.with_title(title.clone())
}
multibuffer
});
cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx);
editor.remote_id = Some(remote_id);
editor
})
})?
};
update_editor_from_message(
editor.downgrade(),
project,
@@ -327,6 +308,16 @@ impl FollowableItem for Editor {
fn is_project_item(&self, _cx: &WindowContext) -> bool {
true
}
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
let self_singleton = self.buffer.read(cx).as_singleton()?;
let other_singleton = existing.buffer.read(cx).as_singleton()?;
if self_singleton == other_singleton {
Some(Dedup::KeepExisting)
} else {
None
}
}
}
async fn update_editor_from_message(
@@ -371,7 +362,7 @@ async fn update_editor_from_message(
continue;
};
let buffer_id = BufferId::new(excerpt.buffer_id)?;
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id) else {
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
continue;
};
@@ -944,7 +935,7 @@ impl Item for Editor {
.context("No path stored for this editor")?;
let (worktree, path) = project
.find_local_worktree(&path, cx)
.find_worktree(&path, cx)
.with_context(|| format!("No worktree for path: {path:?}"))?;
let project_path = ProjectPath {
worktree_id: worktree.read(cx).id(),

View File

@@ -49,13 +49,11 @@ fn display_ranges<'a>(
.pending
.as_ref()
.map(|pending| &pending.selection);
selections.disjoint.iter().chain(pending).map(move |s| {
if s.reversed {
s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
} else {
s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
}
})
selections
.disjoint
.iter()
.chain(pending)
.map(move |s| s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map))
}
pub fn deploy_context_menu(

View File

@@ -476,7 +476,10 @@ impl Editor {
}
let cur_position = self.scroll_position(cx);
let new_pos = cur_position + point(0., amount.lines(self));
let Some(visible_line_count) = self.visible_line_count() else {
return;
};
let new_pos = cur_position + point(0., amount.lines(visible_line_count));
self.set_scroll_position(new_pos, cx);
}

View File

@@ -1,4 +1,3 @@
use crate::Editor;
use serde::Deserialize;
use ui::{px, Pixels};
@@ -11,19 +10,16 @@ pub enum ScrollAmount {
}
impl ScrollAmount {
pub fn lines(&self, editor: &mut Editor) -> f32 {
pub fn lines(&self, mut visible_line_count: f32) -> f32 {
match self {
Self::Line(count) => *count,
Self::Page(count) => editor
.visible_line_count()
.map(|mut l| {
// for full pages subtract one to leave an anchor line
if count.abs() == 1.0 {
l -= 1.0
}
(l * count).trunc()
})
.unwrap_or(0.),
Self::Page(count) => {
// for full pages subtract one to leave an anchor line
if count.abs() == 1.0 {
visible_line_count -= 1.0
}
(visible_line_count * count).trunc()
}
}
}

View File

@@ -0,0 +1,225 @@
mod popover;
mod state;
use crate::actions::ShowSignatureHelp;
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp};
use gpui::{AppContext, ViewContext};
use language::markdown::parse_markdown;
use multi_buffer::{Anchor, ToOffset};
use settings::Settings;
use std::ops::Range;
pub use popover::SignatureHelpPopover;
pub use state::SignatureHelpState;
// Language-specific settings may define quotes as "brackets", so filter them out separately.
const QUOTE_PAIRS: [(&'static str, &'static str); 3] = [("'", "'"), ("\"", "\""), ("`", "`")];
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SignatureHelpHiddenBy {
AutoClose,
Escape,
Selection,
}
impl Editor {
pub fn toggle_auto_signature_help_menu(
&mut self,
_: &ToggleAutoSignatureHelp,
cx: &mut ViewContext<Self>,
) {
self.auto_signature_help = self
.auto_signature_help
.map(|auto_signature_help| !auto_signature_help)
.or_else(|| Some(!EditorSettings::get_global(cx).auto_signature_help));
match self.auto_signature_help {
Some(auto_signature_help) if auto_signature_help => {
self.show_signature_help(&ShowSignatureHelp, cx);
}
Some(_) => {
self.hide_signature_help(cx, SignatureHelpHiddenBy::AutoClose);
}
None => {}
}
cx.notify();
}
pub(super) fn hide_signature_help(
&mut self,
cx: &mut ViewContext<Self>,
signature_help_hidden_by: SignatureHelpHiddenBy,
) -> bool {
if self.signature_help_state.is_shown() {
self.signature_help_state.kill_task();
self.signature_help_state.hide(signature_help_hidden_by);
cx.notify();
true
} else {
false
}
}
pub fn auto_signature_help_enabled(&self, cx: &AppContext) -> bool {
if let Some(auto_signature_help) = self.auto_signature_help {
auto_signature_help
} else {
EditorSettings::get_global(cx).auto_signature_help
}
}
pub(super) fn should_open_signature_help_automatically(
&mut self,
old_cursor_position: &Anchor,
backspace_pressed: bool,
cx: &mut ViewContext<Self>,
) -> bool {
if !(self.signature_help_state.is_shown() || self.auto_signature_help_enabled(cx)) {
return false;
}
let newest_selection = self.selections.newest::<usize>(cx);
let head = newest_selection.head();
// There are two cases where the head and tail of a selection are different: selecting multiple ranges and using backspace.
// If we dont exclude the backspace case, signature_help will blink every time backspace is pressed, so we need to prevent this.
if !newest_selection.is_empty() && !backspace_pressed && head != newest_selection.tail() {
self.signature_help_state
.hide(SignatureHelpHiddenBy::Selection);
return false;
}
let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
let bracket_range = |position: usize| match (position, position + 1) {
(0, b) if b <= buffer_snapshot.len() => 0..b,
(0, b) => 0..b - 1,
(a, b) if b <= buffer_snapshot.len() => a - 1..b,
(a, b) => a - 1..b - 1,
};
let not_quote_like_brackets = |start: Range<usize>, end: Range<usize>| {
let text = buffer_snapshot.text();
let (text_start, text_end) = (text.get(start), text.get(end));
QUOTE_PAIRS
.into_iter()
.all(|(start, end)| text_start != Some(start) && text_end != Some(end))
};
let previous_position = old_cursor_position.to_offset(&buffer_snapshot);
let previous_brackets_range = bracket_range(previous_position);
let previous_brackets_surround = buffer_snapshot
.innermost_enclosing_bracket_ranges(
previous_brackets_range,
Some(&not_quote_like_brackets),
)
.filter(|(start_bracket_range, end_bracket_range)| {
start_bracket_range.start != previous_position
&& end_bracket_range.end != previous_position
});
let current_brackets_range = bracket_range(head);
let current_brackets_surround = buffer_snapshot
.innermost_enclosing_bracket_ranges(
current_brackets_range,
Some(&not_quote_like_brackets),
)
.filter(|(start_bracket_range, end_bracket_range)| {
start_bracket_range.start != head && end_bracket_range.end != head
});
match (previous_brackets_surround, current_brackets_surround) {
(None, None) => {
self.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
false
}
(Some(_), None) => {
self.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
false
}
(None, Some(_)) => true,
(Some(previous), Some(current)) => {
let condition = self.signature_help_state.hidden_by_selection()
|| previous != current
|| (previous == current && self.signature_help_state.is_shown());
if !condition {
self.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
}
condition
}
}
}
pub fn show_signature_help(&mut self, _: &ShowSignatureHelp, cx: &mut ViewContext<Self>) {
if self.pending_rename.is_some() {
return;
}
let position = self.selections.newest_anchor().head();
let Some((buffer, buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(position, cx)
else {
return;
};
self.signature_help_state
.set_task(cx.spawn(move |editor, mut cx| async move {
let signature_help = editor
.update(&mut cx, |editor, cx| {
let language = editor.language_at(position, cx);
let project = editor.project.clone()?;
let (markdown, language_registry) = {
project.update(cx, |project, mut cx| {
let language_registry = project.languages().clone();
(
project.signature_help(&buffer, buffer_position, &mut cx),
language_registry,
)
})
};
Some((markdown, language_registry, language))
})
.ok()
.flatten();
let signature_help_popover = if let Some((
signature_help_task,
language_registry,
language,
)) = signature_help
{
// TODO allow multiple signature helps inside the same popover
if let Some(mut signature_help) = signature_help_task.await.into_iter().next() {
let mut parsed_content = parse_markdown(
signature_help.markdown.as_str(),
&language_registry,
language,
)
.await;
parsed_content
.highlights
.append(&mut signature_help.highlights);
Some(SignatureHelpPopover { parsed_content })
} else {
None
}
} else {
None
};
editor
.update(&mut cx, |editor, cx| {
let previous_popover = editor.signature_help_state.popover();
if previous_popover != signature_help_popover.as_ref() {
if let Some(signature_help_popover) = signature_help_popover {
editor
.signature_help_state
.set_popover(signature_help_popover);
} else {
editor
.signature_help_state
.hide(SignatureHelpHiddenBy::AutoClose);
}
cx.notify();
}
})
.ok();
}));
}
}

View File

@@ -0,0 +1,48 @@
use crate::{Editor, EditorStyle};
use gpui::{
div, AnyElement, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, Size,
StatefulInteractiveElement, Styled, ViewContext, WeakView,
};
use language::ParsedMarkdown;
use ui::StyledExt;
use workspace::Workspace;
#[derive(Clone, Debug)]
pub struct SignatureHelpPopover {
pub parsed_content: ParsedMarkdown,
}
impl PartialEq for SignatureHelpPopover {
fn eq(&self, other: &Self) -> bool {
let str_equality = self.parsed_content.text.as_str() == other.parsed_content.text.as_str();
let highlight_equality = self.parsed_content.highlights == other.parsed_content.highlights;
str_equality && highlight_equality
}
}
impl SignatureHelpPopover {
pub fn render(
&mut self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
div()
.id("signature_help_popover")
.elevation_2(cx)
.overflow_y_scroll()
.max_w(max_size.width)
.max_h(max_size.height)
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.child(div().p_2().child(crate::render_parsed_markdown(
"signature_help_popover_content",
&self.parsed_content,
style,
workspace,
cx,
)))
.into_any_element()
}
}

View File

@@ -0,0 +1,65 @@
use crate::signature_help::popover::SignatureHelpPopover;
use crate::signature_help::SignatureHelpHiddenBy;
use gpui::Task;
#[derive(Default, Debug)]
pub struct SignatureHelpState {
task: Option<Task<()>>,
popover: Option<SignatureHelpPopover>,
hidden_by: Option<SignatureHelpHiddenBy>,
backspace_pressed: bool,
}
impl SignatureHelpState {
pub fn set_task(&mut self, task: Task<()>) {
self.task = Some(task);
self.hidden_by = None;
}
pub fn kill_task(&mut self) {
self.task = None;
}
pub fn popover(&self) -> Option<&SignatureHelpPopover> {
self.popover.as_ref()
}
pub fn popover_mut(&mut self) -> Option<&mut SignatureHelpPopover> {
self.popover.as_mut()
}
pub fn backspace_pressed(&self) -> bool {
self.backspace_pressed
}
pub fn set_backspace_pressed(&mut self, backspace_pressed: bool) {
self.backspace_pressed = backspace_pressed;
}
pub fn set_popover(&mut self, popover: SignatureHelpPopover) {
self.popover = Some(popover);
self.hidden_by = None;
}
pub fn hide(&mut self, hidden_by: SignatureHelpHiddenBy) {
if self.hidden_by.is_none() {
self.popover = None;
self.hidden_by = Some(hidden_by);
}
}
pub fn hidden_by_selection(&self) -> bool {
self.hidden_by == Some(SignatureHelpHiddenBy::Selection)
}
pub fn is_shown(&self) -> bool {
self.popover.is_some()
}
}
#[cfg(test)]
impl SignatureHelpState {
pub fn task(&self) -> Option<&Task<()>> {
self.task.as_ref()
}
}

View File

@@ -79,7 +79,7 @@ impl EditorLspTestContext {
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
project
.update(&mut cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
project.find_or_create_worktree("/root", true, cx)
})
.await
.unwrap();

View File

@@ -49,10 +49,9 @@ pub fn is_supported_wasm_api_version(
/// Returns the Wasm API version range that is supported by the Wasm host.
#[inline(always)]
pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive<SemanticVersion> {
let max_version = if release_channel == ReleaseChannel::Dev {
latest::MAX_VERSION
} else {
since_v0_0_6::MAX_VERSION
let max_version = match release_channel {
ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_0_6::MAX_VERSION,
};
since_v0_0_1::MIN_VERSION..=max_version

View File

@@ -17,6 +17,7 @@ test-support = []
[dependencies]
anyhow.workspace = true
client.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
extension.workspace = true
@@ -36,6 +37,7 @@ theme.workspace = true
theme_selector.workspace = true
ui.workspace = true
util.workspace = true
vim.workspace = true
workspace.workspace = true
[dev-dependencies]

View File

@@ -1,3 +1,5 @@
mod extension_card;
mod feature_upsell;
pub use extension_card::*;
pub use feature_upsell::*;

View File

@@ -0,0 +1,83 @@
use std::sync::Arc;
use client::telemetry::Telemetry;
use gpui::{AnyElement, Div, StyleRefinement};
use smallvec::SmallVec;
use ui::{prelude::*, ButtonLike};
#[derive(IntoElement)]
pub struct FeatureUpsell {
base: Div,
telemetry: Arc<Telemetry>,
text: SharedString,
docs_url: Option<SharedString>,
children: SmallVec<[AnyElement; 2]>,
}
impl FeatureUpsell {
pub fn new(telemetry: Arc<Telemetry>, text: impl Into<SharedString>) -> Self {
Self {
base: h_flex(),
telemetry,
text: text.into(),
docs_url: None,
children: SmallVec::new(),
}
}
pub fn docs_url(mut self, docs_url: impl Into<SharedString>) -> Self {
self.docs_url = Some(docs_url.into());
self
}
}
impl ParentElement for FeatureUpsell {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements)
}
}
// Style methods.
impl FeatureUpsell {
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
gpui::border_style_methods!({
visibility: pub
});
}
impl RenderOnce for FeatureUpsell {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
self.base
.p_4()
.justify_between()
.border_color(cx.theme().colors().border)
.child(v_flex().overflow_hidden().child(Label::new(self.text)))
.child(h_flex().gap_2().children(self.children).when_some(
self.docs_url,
|el, docs_url| {
el.child(
ButtonLike::new("open_docs")
.child(
h_flex()
.gap_2()
.child(Label::new("View docs"))
.child(Icon::new(IconName::ArrowUpRight)),
)
.on_click({
let telemetry = self.telemetry.clone();
let docs_url = docs_url.clone();
move |_event, cx| {
telemetry.report_app_event(format!(
"feature upsell: viewed docs ({docs_url})"
));
cx.open_url(&docs_url)
}
}),
)
},
))
}
}

View File

@@ -2,35 +2,40 @@ mod components;
mod extension_suggest;
mod extension_version_selector;
use crate::components::ExtensionCard;
use crate::extension_version_selector::{
ExtensionVersionSelector, ExtensionVersionSelectorDelegate,
};
use std::ops::DerefMut;
use std::sync::OnceLock;
use std::time::Duration;
use std::{ops::Range, sync::Arc};
use client::telemetry::Telemetry;
use client::ExtensionMetadata;
use collections::{BTreeMap, BTreeSet};
use editor::{Editor, EditorElement, EditorStyle};
use extension::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle,
actions, uniform_list, AnyElement, AppContext, EventEmitter, Flatten, FocusableView, FontStyle,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
};
use num_format::{Locale, ToFormattedString};
use project::DirectoryLister;
use release_channel::ReleaseChannel;
use settings::Settings;
use std::ops::DerefMut;
use std::time::Duration;
use std::{ops::Range, sync::Arc};
use theme::ThemeSettings;
use ui::{prelude::*, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use util::ResultExt as _;
use ui::{prelude::*, CheckboxWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use vim::VimModeSetting;
use workspace::item::TabContentParams;
use workspace::{
item::{Item, ItemEvent},
Workspace, WorkspaceId,
};
use crate::components::{ExtensionCard, FeatureUpsell};
use crate::extension_version_selector::{
ExtensionVersionSelector, ExtensionVersionSelectorDelegate,
};
actions!(zed, [Extensions, InstallDevExtension]);
pub fn init(cx: &mut AppContext) {
@@ -50,17 +55,35 @@ pub fn init(cx: &mut AppContext) {
workspace.add_item_to_active_pane(Box::new(extensions_page), None, cx)
}
})
.register_action(move |_, _: &InstallDevExtension, cx| {
.register_action(move |workspace, _: &InstallDevExtension, cx| {
let store = ExtensionStore::global(cx);
let prompt = cx.prompt_for_paths(gpui::PathPromptOptions {
files: false,
directories: true,
multiple: false,
});
let prompt = workspace.prompt_for_open_path(
gpui::PathPromptOptions {
files: false,
directories: true,
multiple: false,
},
DirectoryLister::Local(workspace.app_state().fs.clone()),
cx,
);
let workspace_handle = cx.view().downgrade();
cx.deref_mut()
.spawn(|mut cx| async move {
let extension_path = prompt.await.log_err()??.pop()?;
let extension_path =
match Flatten::flatten(prompt.await.map_err(|e| e.into())) {
Ok(Some(mut paths)) => paths.pop()?,
Ok(None) => return None,
Err(err) => {
workspace_handle
.update(&mut cx, |workspace, cx| {
workspace.show_portal_error(err.to_string(), cx);
})
.ok();
return None;
}
};
store
.update(&mut cx, |store, cx| {
store
@@ -109,6 +132,41 @@ impl ExtensionFilter {
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
enum Feature {
Git,
Vim,
LanguageBash,
LanguageC,
LanguageCpp,
LanguageGo,
LanguagePython,
LanguageReact,
LanguageRust,
LanguageTypescript,
}
fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
static KEYWORDS_BY_FEATURE: OnceLock<BTreeMap<Feature, Vec<&'static str>>> = OnceLock::new();
KEYWORDS_BY_FEATURE.get_or_init(|| {
BTreeMap::from_iter([
(Feature::Git, vec!["git"]),
(Feature::Vim, vec!["vim"]),
(Feature::LanguageBash, vec!["sh", "bash"]),
(Feature::LanguageC, vec!["c", "clang"]),
(Feature::LanguageCpp, vec!["c++", "cpp", "clang"]),
(Feature::LanguageGo, vec!["go", "golang"]),
(Feature::LanguagePython, vec!["python", "py"]),
(Feature::LanguageReact, vec!["react"]),
(Feature::LanguageRust, vec!["rust", "rs"]),
(
Feature::LanguageTypescript,
vec!["type", "typescript", "ts"],
),
])
})
}
pub struct ExtensionsPage {
workspace: WeakView<Workspace>,
list: UniformListScrollHandle,
@@ -122,6 +180,7 @@ pub struct ExtensionsPage {
query_contains_error: bool,
_subscriptions: [gpui::Subscription; 2],
extension_fetch_task: Option<Task<()>>,
upsells: BTreeSet<Feature>,
}
impl ExtensionsPage {
@@ -160,6 +219,7 @@ impl ExtensionsPage {
extension_fetch_task: None,
_subscriptions: subscriptions,
query_editor,
upsells: BTreeSet::default(),
};
this.fetch_extensions(None, cx);
this
@@ -718,25 +778,19 @@ impl ExtensionsPage {
cx.theme().colors().border
};
h_flex()
.w_full()
.gap_2()
.key_context(key_context)
// .capture_action(cx.listener(Self::tab))
// .on_action(cx.listener(Self::dismiss))
.child(
h_flex()
.flex_1()
.px_2()
.py_1()
.gap_2()
.border_1()
.border_color(editor_border)
.min_w(rems_from_px(384.))
.rounded_lg()
.child(Icon::new(IconName::MagnifyingGlass))
.child(self.render_text_input(&self.query_editor, cx)),
)
h_flex().w_full().gap_2().key_context(key_context).child(
h_flex()
.flex_1()
.px_2()
.py_1()
.gap_2()
.border_1()
.border_color(editor_border)
.min_w(rems_from_px(384.))
.rounded_lg()
.child(Icon::new(IconName::MagnifyingGlass))
.child(self.render_text_input(&self.query_editor, cx)),
)
}
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
@@ -779,6 +833,7 @@ impl ExtensionsPage {
if let editor::EditorEvent::Edited { .. } = event {
self.query_contains_error = false;
self.fetch_extensions_debounced(cx);
self.refresh_feature_upsells(cx);
}
}
@@ -850,6 +905,120 @@ impl ExtensionsPage {
Label::new(message)
}
fn update_settings<T: Settings>(
&mut self,
selection: &Selection,
cx: &mut ViewContext<Self>,
callback: impl 'static + Send + Fn(&mut T::FileContent, bool),
) {
if let Some(workspace) = self.workspace.upgrade() {
let fs = workspace.read(cx).app_state().fs.clone();
let selection = *selection;
settings::update_settings_file::<T>(fs, cx, move |settings| {
let value = match selection {
Selection::Unselected => false,
Selection::Selected => true,
_ => return,
};
callback(settings, value)
});
}
}
fn refresh_feature_upsells(&mut self, cx: &mut ViewContext<Self>) {
let Some(search) = self.search_query(cx) else {
self.upsells.clear();
return;
};
let search = search.to_lowercase();
let search_terms = search
.split_whitespace()
.map(|term| term.trim())
.collect::<Vec<_>>();
for (feature, keywords) in keywords_by_feature() {
if keywords
.iter()
.any(|keyword| search_terms.contains(keyword))
{
self.upsells.insert(*feature);
} else {
self.upsells.remove(&feature);
}
}
}
fn render_feature_upsells(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let upsells_count = self.upsells.len();
v_flex().children(self.upsells.iter().enumerate().map(|(ix, feature)| {
let telemetry = self.telemetry.clone();
let upsell = match feature {
Feature::Git => FeatureUpsell::new(
telemetry,
"Zed comes with basic Git support. More Git features are coming in the future.",
)
.docs_url("https://zed.dev/docs/git"),
Feature::Vim => FeatureUpsell::new(telemetry, "Vim support is built-in to Zed!")
.docs_url("https://zed.dev/docs/vim")
.child(CheckboxWithLabel::new(
"enable-vim",
Label::new("Enable vim mode"),
if VimModeSetting::get_global(cx).0 {
ui::Selection::Selected
} else {
ui::Selection::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry
.report_app_event("feature upsell: toggle vim".to_string());
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
);
}),
)),
Feature::LanguageBash => {
FeatureUpsell::new(telemetry, "Shell support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/bash")
}
Feature::LanguageC => {
FeatureUpsell::new(telemetry, "C support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/c")
}
Feature::LanguageCpp => {
FeatureUpsell::new(telemetry, "C++ support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/cpp")
}
Feature::LanguageGo => {
FeatureUpsell::new(telemetry, "Go support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/go")
}
Feature::LanguagePython => {
FeatureUpsell::new(telemetry, "Python support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/python")
}
Feature::LanguageReact => {
FeatureUpsell::new(telemetry, "React support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript")
}
Feature::LanguageRust => {
FeatureUpsell::new(telemetry, "Rust support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/rust")
}
Feature::LanguageTypescript => {
FeatureUpsell::new(telemetry, "Typescript support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript")
}
};
upsell.when(ix < upsells_count, |upsell| upsell.border_b_1())
}))
}
}
impl Render for ExtensionsPage {
@@ -932,6 +1101,7 @@ impl Render for ExtensionsPage {
),
),
)
.child(self.render_feature_upsells(cx))
.child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
let mut count = self.filtered_remote_extension_indices.len();
if self.filter.include_dev_extensions() {

View File

@@ -34,6 +34,11 @@ impl FeatureFlag for TerminalInlineAssist {
const NAME: &'static str = "terminal-inline-assist";
}
pub struct GroupedDiagnostics {}
impl FeatureFlag for GroupedDiagnostics {
const NAME: &'static str = "grouped-diagnostics";
}
pub trait FeatureFlagViewExt<V: 'static> {
fn observe_flag<T: FeatureFlag, F>(&mut self, callback: F) -> Subscription
where

View File

@@ -2,6 +2,7 @@
mod file_finder_tests;
mod new_path_prompt;
mod open_path_prompt;
use collections::{BTreeSet, HashMap};
use editor::{scroll::Autoscroll, Bias, Editor};
@@ -13,6 +14,7 @@ use gpui::{
};
use itertools::Itertools;
use new_path_prompt::NewPathPrompt;
use open_path_prompt::OpenPathPrompt;
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
use settings::Settings;
@@ -41,6 +43,7 @@ pub struct FileFinder {
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(FileFinder::register).detach();
cx.observe_new_views(NewPathPrompt::register).detach();
cx.observe_new_views(OpenPathPrompt::register).detach();
}
impl FileFinder {
@@ -676,7 +679,7 @@ impl FileFinderDelegate {
let update_result = project
.update(&mut cx, |project, cx| {
if let Some((worktree, relative_path)) =
project.find_local_worktree(query_path, cx)
project.find_worktree(query_path, cx)
{
path_matches.push(ProjectPanelOrdMatch(PathMatch {
score: 1.0,

View File

@@ -767,7 +767,7 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
cx.update(|cx| {
project.update(cx, |project, cx| {
project.find_or_create_local_worktree("/external-src", false, cx)
project.find_or_create_worktree("/external-src", false, cx)
})
})
.detach();
@@ -1513,7 +1513,7 @@ async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
project
.update(cx, |project, cx| {
project
.find_or_create_local_worktree("/test/project_2", true, cx)
.find_or_create_worktree("/test/project_2", true, cx)
.into_future()
})
.await

View File

@@ -197,14 +197,12 @@ pub struct NewPathDelegate {
}
impl NewPathPrompt {
pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
if workspace.project().read(cx).is_remote() {
workspace.set_prompt_for_new_path(Box::new(|workspace, cx| {
let (tx, rx) = futures::channel::oneshot::channel();
Self::prompt_for_new_path(workspace, tx, cx);
rx
}));
}
pub(crate) fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
workspace.set_prompt_for_new_path(Box::new(|workspace, cx| {
let (tx, rx) = futures::channel::oneshot::channel();
Self::prompt_for_new_path(workspace, tx, cx);
rx
}));
}
fn prompt_for_new_path(

View File

@@ -0,0 +1,287 @@
use futures::channel::oneshot;
use fuzzy::StringMatchCandidate;
use picker::{Picker, PickerDelegate};
use project::{compare_paths, DirectoryLister};
use std::{
path::{Path, PathBuf},
sync::{
atomic::{self, AtomicBool},
Arc,
},
};
use ui::{prelude::*, LabelLike, ListItemSpacing};
use ui::{ListItem, ViewContext};
use util::maybe;
use workspace::Workspace;
pub(crate) struct OpenPathPrompt;
pub struct OpenPathDelegate {
tx: Option<oneshot::Sender<Option<Vec<PathBuf>>>>,
lister: DirectoryLister,
selected_index: usize,
directory_state: Option<DirectoryState>,
matches: Vec<usize>,
cancel_flag: Arc<AtomicBool>,
should_dismiss: bool,
}
struct DirectoryState {
path: String,
match_candidates: Vec<StringMatchCandidate>,
error: Option<SharedString>,
}
impl OpenPathPrompt {
pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.set_prompt_for_open_path(Box::new(|workspace, lister, cx| {
let (tx, rx) = futures::channel::oneshot::channel();
Self::prompt_for_open_path(workspace, lister, tx, cx);
rx
}));
}
fn prompt_for_open_path(
workspace: &mut Workspace,
lister: DirectoryLister,
tx: oneshot::Sender<Option<Vec<PathBuf>>>,
cx: &mut ViewContext<Workspace>,
) {
workspace.toggle_modal(cx, |cx| {
let delegate = OpenPathDelegate {
tx: Some(tx),
lister: lister.clone(),
selected_index: 0,
directory_state: None,
matches: Vec::new(),
cancel_flag: Arc::new(AtomicBool::new(false)),
should_dismiss: true,
};
let picker = Picker::uniform_list(delegate, cx).width(rems(34.));
let query = lister.default_query(cx);
picker.set_query(query, cx);
picker
});
}
}
impl PickerDelegate for OpenPathDelegate {
type ListItem = ui::ListItem;
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
cx.notify();
}
fn update_matches(
&mut self,
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let lister = self.lister.clone();
let (mut dir, suffix) = if let Some(index) = query.rfind('/') {
(query[..index].to_string(), query[index + 1..].to_string())
} else {
(query, String::new())
};
if dir == "" {
dir = "/".to_string();
}
let query = if self
.directory_state
.as_ref()
.map_or(false, |s| s.path == dir)
{
None
} else {
Some(lister.list_directory(dir.clone(), cx))
};
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone();
cx.spawn(|this, mut cx| async move {
if let Some(query) = query {
let paths = query.await;
if cancel_flag.load(atomic::Ordering::Relaxed) {
return;
}
this.update(&mut cx, |this, _| {
this.delegate.directory_state = Some(match paths {
Ok(mut paths) => {
paths.sort_by(|a, b| compare_paths((a, true), (b, true)));
let match_candidates = paths
.iter()
.enumerate()
.map(|(ix, path)| {
StringMatchCandidate::new(ix, path.to_string_lossy().into())
})
.collect::<Vec<_>>();
DirectoryState {
match_candidates,
path: dir,
error: None,
}
}
Err(err) => DirectoryState {
match_candidates: vec![],
path: dir,
error: Some(err.to_string().into()),
},
});
})
.ok();
}
let match_candidates = this
.update(&mut cx, |this, cx| {
let directory_state = this.delegate.directory_state.as_ref()?;
if directory_state.error.is_some() {
this.delegate.matches.clear();
this.delegate.selected_index = 0;
cx.notify();
return None;
}
Some(directory_state.match_candidates.clone())
})
.unwrap_or(None);
let Some(mut match_candidates) = match_candidates else {
return;
};
if !suffix.starts_with('.') {
match_candidates.retain(|m| !m.string.starts_with('.'));
}
if suffix == "" {
this.update(&mut cx, |this, cx| {
this.delegate.matches.clear();
this.delegate
.matches
.extend(match_candidates.iter().map(|m| m.id));
cx.notify();
})
.ok();
return;
}
let matches = fuzzy::match_strings(
&match_candidates.as_slice(),
&suffix,
false,
100,
&cancel_flag,
cx.background_executor().clone(),
)
.await;
if cancel_flag.load(atomic::Ordering::Relaxed) {
return;
}
this.update(&mut cx, |this, cx| {
this.delegate.matches.clear();
this.delegate
.matches
.extend(matches.into_iter().map(|m| m.candidate_id));
this.delegate.matches.sort_by_key(|m| {
(
this.delegate.directory_state.as_ref().and_then(|d| {
d.match_candidates
.get(*m)
.map(|c| !c.string.starts_with(&suffix))
}),
*m,
)
});
cx.notify();
})
.ok();
})
}
fn confirm_completion(&self, query: String) -> Option<String> {
Some(
maybe!({
let m = self.matches.get(self.selected_index)?;
let directory_state = self.directory_state.as_ref()?;
let candidate = directory_state.match_candidates.get(*m)?;
Some(format!("{}/{}", directory_state.path, candidate.string))
})
.unwrap_or(query),
)
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(m) = self.matches.get(self.selected_index) else {
return;
};
let Some(directory_state) = self.directory_state.as_ref() else {
return;
};
let Some(candidate) = directory_state.match_candidates.get(*m) else {
return;
};
let result = Path::new(&directory_state.path).join(&candidate.string);
if let Some(tx) = self.tx.take() {
tx.send(Some(vec![result])).ok();
}
cx.emit(gpui::DismissEvent);
}
fn should_dismiss(&self) -> bool {
self.should_dismiss
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
if let Some(tx) = self.tx.take() {
tx.send(None).ok();
}
cx.emit(gpui::DismissEvent)
}
fn render_match(
&self,
ix: usize,
selected: bool,
_: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let m = self.matches.get(ix)?;
let directory_state = self.directory_state.as_ref()?;
let candidate = directory_state.match_candidates.get(*m)?;
Some(
ListItem::new(ix)
.spacing(ListItemSpacing::Sparse)
.inset(true)
.selected(selected)
.child(LabelLike::new().child(candidate.string.clone())),
)
}
fn no_matches_text(&self, _cx: &mut WindowContext) -> SharedString {
if let Some(error) = self.directory_state.as_ref().and_then(|s| s.error.clone()) {
error
} else {
"No such file or directory".into()
}
}
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
Arc::from("[directory/]filename.ext")
}
}

View File

@@ -67,7 +67,10 @@ pub trait Fs: Send + Sync {
self.remove_file(path, options).await
}
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
async fn load(&self, path: &Path) -> Result<String>;
async fn load(&self, path: &Path) -> Result<String> {
Ok(String::from_utf8(self.load_bytes(path).await?)?)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>>;
async fn atomic_write(&self, path: PathBuf, text: String) -> Result<()>;
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()>;
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
@@ -318,6 +321,11 @@ impl Fs for RealFs {
let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
Ok(text)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
let path = path.to_path_buf();
let bytes = smol::unblock(|| std::fs::read(path)).await?;
Ok(bytes)
}
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
smol::unblock(move || {
@@ -1433,6 +1441,10 @@ impl Fs for FakeFs {
Ok(String::from_utf8(content.clone())?)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
self.load_internal(path).await
}
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
self.simulate_random_delay().await;
let path = normalize_path(path.as_path());

View File

@@ -376,11 +376,12 @@ mod tests {
#[test]
fn test_event_stream_simple() {
for _ in 0..3 {
for iteration in 0..3 {
let dir = tempfile::Builder::new()
.prefix("test-event-stream")
.tempdir()
.unwrap();
println!("iteration: {}, dir: {:?}", iteration, dir);
let path = dir.path().canonicalize().unwrap();
for i in 0..10 {
fs::write(path.join(format!("existing-file-{}", i)), "").unwrap();
@@ -389,17 +390,33 @@ mod tests {
let (tx, rx) = mpsc::channel();
let (stream, handle) = EventStream::new(&[&path], Duration::from_millis(50));
thread::spawn(move || stream.run(move |events| tx.send(events.to_vec()).is_ok()));
thread::spawn(move || {
stream.run(move |events| {
println!("sending events:");
for event in events.iter() {
println!("- {:?}", event);
}
tx.send(events.to_vec()).is_ok()
})
});
println!("--------- creating new file");
fs::write(path.join("new-file"), "").unwrap();
let events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
let event = events.last().unwrap();
assert_eq!(event.path, path.join("new-file"));
assert!(event.flags.contains(StreamFlags::ITEM_CREATED));
println!("---------- removing existing file");
fs::remove_file(path.join("existing-file-5")).unwrap();
let events = rx.recv_timeout(Duration::from_secs(2)).unwrap();
let event = events.last().unwrap();
// thread 'tests::test_event_stream_simple' panicked at crates/fsevent/src/fsevent.rs:403:13:
// assertion `left == right` failed
// left: "/private/var/folders/cf/sdy1nn_542g798vjgnxxz8140000gn/T/test-event-streamW1SkQ2/new-file"
// right: "/private/var/folders/cf/sdy1nn_542g798vjgnxxz8140000gn/T/test-event-streamW1SkQ2/existing-file-5"
assert_eq!(event.path, path.join("existing-file-5"));
assert!(event.flags.contains(StreamFlags::ITEM_REMOVED));
drop(handle);

View File

@@ -124,6 +124,7 @@ wayland-protocols = { version = "0.31.2", features = [
] }
wayland-protocols-plasma = { version = "0.2.0", features = ["client"] }
oo7 = "0.3.0"
open = "5.2.0"
filedescriptor = "0.8.2"
x11rb = { version = "0.13.0", features = [
"allow-unsafe-code",
@@ -134,18 +135,22 @@ x11rb = { version = "0.13.0", features = [
"resource_manager",
"sync",
] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "fcbb4612185cc129ceeff51d22f7fb51810a03b2", features = [
"wayland",
"x11",
] }
xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
"x11rb-xcb",
"x11rb-client",
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = [
"source-fontconfig-dlopen",
] }
x11-clipboard = "0.9.2"
[target.'cfg(windows)'.dependencies]
windows.workspace = true
windows-core = "0.57"
clipboard-win = "3.1.1"
[[example]]
name = "hello_world"

View File

@@ -22,6 +22,7 @@ actions!(
struct TextInput {
focus_handle: FocusHandle,
content: SharedString,
placeholder: SharedString,
selected_range: Range<usize>,
selection_reversed: bool,
marked_range: Option<Range<usize>>,
@@ -84,7 +85,12 @@ impl TextInput {
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
self.is_selecting = true;
self.move_to(self.index_for_mouse_position(event.position), cx)
if event.modifiers.shift {
self.select_to(self.index_for_mouse_position(event.position), cx);
} else {
self.move_to(self.index_for_mouse_position(event.position), cx)
}
}
fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) {
@@ -115,6 +121,10 @@ impl TextInput {
}
fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {
if self.content.is_empty() {
return 0;
}
let (Some(bounds), Some(line)) = (self.last_bounds.as_ref(), self.last_layout.as_ref())
else {
return 0;
@@ -193,6 +203,16 @@ impl TextInput {
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
fn reset(&mut self) {
self.content = "".into();
self.selected_range = 0..0;
self.selection_reversed = false;
self.marked_range = None;
self.last_layout = None;
self.last_bounds = None;
self.is_selecting = false;
}
}
impl ViewInputHandler for TextInput {
@@ -338,10 +358,17 @@ impl Element for TextElement {
let selected_range = input.selected_range.clone();
let cursor = input.cursor_offset();
let style = cx.text_style();
let (display_text, text_color) = if content.is_empty() {
(input.placeholder.clone(), hsla(0., 0., 0., 0.2))
} else {
(content.clone(), style.color)
};
let run = TextRun {
len: input.content.len(),
len: display_text.len(),
font: style.font(),
color: style.color,
color: text_color,
background_color: None,
underline: None,
strikethrough: None,
@@ -362,7 +389,7 @@ impl Element for TextElement {
..run.clone()
},
TextRun {
len: input.content.len() - marked_range.end,
len: display_text.len() - marked_range.end,
..run.clone()
},
]
@@ -376,7 +403,7 @@ impl Element for TextElement {
let font_size = style.font_size.to_pixels(cx.rem_size());
let line = cx
.text_system()
.shape_line(content, font_size, &runs)
.shape_line(display_text, font_size, &runs)
.unwrap();
let cursor_pos = line.x_for_index(cursor);
@@ -494,13 +521,47 @@ struct InputExample {
recent_keystrokes: Vec<Keystroke>,
}
impl InputExample {
fn on_reset_click(&mut self, _: &MouseUpEvent, cx: &mut ViewContext<Self>) {
self.recent_keystrokes.clear();
self.text_input
.update(cx, |text_input, _cx| text_input.reset());
cx.notify();
}
}
impl Render for InputExample {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let num_keystrokes = self.recent_keystrokes.len();
div()
.bg(rgb(0xaaaaaa))
.flex()
.flex_col()
.size_full()
.child(
div()
.bg(white())
.border_b_1()
.border_color(black())
.flex()
.flex_row()
.justify_between()
.child(format!("Keystrokes: {}", num_keystrokes))
.child(
div()
.border_1()
.border_color(black())
.px_2()
.bg(yellow())
.child("Reset")
.hover(|style| {
style
.bg(yellow().blend(opaque_grey(0.5, 0.5)))
.cursor_pointer()
})
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_reset_click)),
),
)
.child(self.text_input.clone())
.children(self.recent_keystrokes.iter().rev().map(|ks| {
format!(
@@ -541,6 +602,7 @@ fn main() {
let text_input = cx.new_view(|cx| TextInput {
focus_handle: cx.focus_handle(),
content: "".into(),
placeholder: "Type here...".into(),
selected_range: 0..0,
selection_reversed: false,
marked_range: None,

View File

@@ -612,10 +612,11 @@ impl AppContext {
/// Displays a platform modal for selecting paths.
/// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel.
/// If cancelled, a `None` will be relayed instead.
/// May return an error on Linux if the file picker couldn't be opened.
pub fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
self.platform.prompt_for_paths(options)
}
@@ -623,7 +624,11 @@ impl AppContext {
/// The provided directory will be used to set the initial location.
/// When a path is selected, it is relayed asynchronously via the returned oneshot channel.
/// If cancelled, a `None` will be relayed instead.
pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
/// May return an error on Linux if the file picker couldn't be opened.
pub fn prompt_for_new_path(
&self,
directory: &Path,
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
self.platform.prompt_for_new_path(directory)
}

View File

@@ -291,6 +291,10 @@ pub trait BorrowAppContext {
fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where
G: Global;
/// Updates the global state of the given type, creating a default if it didn't exist before.
fn update_default_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where
G: Global + Default;
}
impl<C> BorrowAppContext for C
@@ -310,6 +314,14 @@ where
self.borrow_mut().end_global_lease(global);
result
}
fn update_default_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where
G: Global + Default,
{
self.borrow_mut().default_global::<G>();
self.update_global(f)
}
}
/// A flatten equivalent for anyhow `Result`s.

View File

@@ -137,8 +137,8 @@ pub(crate) trait Platform: 'static {
fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
fn reveal_path(&self, path: &Path);
fn on_quit(&self, callback: Box<dyn FnMut()>);
@@ -406,6 +406,8 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
raster_bounds: Bounds<DevicePixels>,
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
#[cfg(target_os = "windows")]
fn destroy(&self);
}
#[derive(PartialEq, Eq, Hash, Clone)]

View File

@@ -177,6 +177,9 @@ impl PlatformTextSystem for CosmicTextSystem {
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
self.0.write().layout_line(text, font_size, runs)
}
#[cfg(target_os = "windows")]
fn destroy(&self) {}
}
impl CosmicTextSystemState {

View File

@@ -21,6 +21,7 @@ use std::{
use anyhow::anyhow;
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest};
use ashpd::desktop::ResponseError;
use ashpd::{url, ActivationToken};
use async_task::Runnable;
use calloop::channel::Channel;
@@ -54,6 +55,9 @@ pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
const FILE_PICKER_PORTAL_MISSING: &str =
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
pub trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
@@ -256,75 +260,84 @@ impl<P: LinuxClient + 'static> Platform for P {
fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
let (done_tx, done_rx) = oneshot::channel();
self.foreground_executor()
.spawn(async move {
let title = if options.multiple {
if !options.files {
"Open folders"
} else {
"Open files"
}
let title = if options.directories {
"Open Folder"
} else {
if !options.files {
"Open folder"
} else {
"Open file"
}
"Open File"
};
let result = OpenFileRequest::default()
let request = match OpenFileRequest::default()
.modal(true)
.title(title)
.accept_label("Select")
.multiple(options.multiple)
.directory(options.directories)
.send()
.await
.ok()
.and_then(|request| request.response().ok())
.and_then(|response| {
{
Ok(request) => request,
Err(err) => {
let result = match err {
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
err => err.into(),
};
done_tx.send(Err(result));
return;
}
};
let result = match request.response() {
Ok(response) => Ok(Some(
response
.uris()
.iter()
.map(|uri| uri.to_file_path().ok())
.collect()
});
.filter_map(|uri| uri.to_file_path().ok())
.collect::<Vec<_>>(),
)),
Err(ashpd::Error::Response(ResponseError::Cancelled)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result);
})
.detach();
done_rx
}
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
let (done_tx, done_rx) = oneshot::channel();
let directory = directory.to_owned();
self.foreground_executor()
.spawn(async move {
let request = SaveFileRequest::default()
let request = match SaveFileRequest::default()
.modal(true)
.title("Select new path")
.accept_label("Accept")
.current_folder(directory);
let result = if let Ok(request) = request {
request
.send()
.await
.ok()
.and_then(|request| request.response().ok())
.and_then(|response| {
response
.uris()
.first()
.and_then(|uri| uri.to_file_path().ok())
})
} else {
None
.title("Save File")
.current_folder(directory)
.expect("pathbuf should not be nul terminated")
.send()
.await
{
Ok(request) => request,
Err(err) => {
let result = match err {
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
err => err.into(),
};
done_tx.send(Err(result));
return;
}
};
let result = match request.response() {
Ok(response) => Ok(response
.uris()
.first()
.and_then(|uri| uri.to_file_path().ok())),
Err(ashpd::Error::Response(ResponseError::Cancelled)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result);
})
.detach();
@@ -502,11 +515,26 @@ pub(super) fn open_uri_internal(
if let Some(uri) = url::Url::parse(uri).log_err() {
executor
.spawn(async move {
OpenUriRequest::default()
.activation_token(activation_token.map(ActivationToken::from))
match OpenUriRequest::default()
.activation_token(activation_token.clone().map(ActivationToken::from))
.send_uri(&uri)
.await
.log_err();
{
Ok(_) => return,
Err(e) => log::error!("Failed to open with dbus: {}", e),
}
for mut command in open::commands(uri.to_string()) {
if let Some(token) = activation_token.as_ref() {
command.env("XDG_ACTIVATION_TOKEN", token);
}
match command.spawn() {
Ok(_) => return,
Err(e) => {
log::error!("Failed to open with {:?}: {}", command.get_program(), e)
}
}
}
})
.detach();
}
@@ -519,12 +547,20 @@ pub(super) fn reveal_path_internal(
) {
executor
.spawn(async move {
if let Some(dir) = File::open(path).log_err() {
OpenDirectoryRequest::default()
if let Some(dir) = File::open(path.clone()).log_err() {
match OpenDirectoryRequest::default()
.activation_token(activation_token.map(ActivationToken::from))
.send(&dir.as_fd())
.await
.log_err();
{
Ok(_) => return,
Err(e) => log::error!("Failed to open with dbus: {}", e),
}
if path.is_dir() {
open::that_detached(path).log_err();
} else {
open::that_detached(path.parent().unwrap_or(Path::new(""))).log_err();
}
}
})
.detach();

View File

@@ -11,6 +11,7 @@ use calloop_wayland_source::WaylandSource;
use collections::HashMap;
use filedescriptor::Pipe;
use http::Url;
use smallvec::SmallVec;
use util::ResultExt;
use wayland_backend::client::ObjectId;
@@ -1119,7 +1120,10 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
let keyboard_focused_window = get_window(&mut state, &surface.id());
state.keyboard_focused_window = None;
state.enter_token.take();
// Prevent keyboard events from repeating after opening e.g. a file chooser and closing it quickly
state.repeat.current_id += 1;
state.clipboard.set_offer(None);
state.clipboard.set_primary_offer(None);
if let Some(window) = keyboard_focused_window {
if let Some(ref mut compose) = state.compose_state {
@@ -1792,7 +1796,8 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
let paths: SmallVec<[_; 2]> = file_list
.lines()
.map(|path| PathBuf::from(path.replace("file://", "")))
.filter_map(|path| Url::parse(path).log_err())
.filter_map(|url| url.to_file_path().log_err())
.collect();
let position = Point::new(x.into(), y.into());

View File

@@ -24,7 +24,7 @@ use x11rb::xcb_ffi::XCBConnection;
use xim::{x11rb::X11rbClient, Client};
use xim::{AttributeName, InputStyle};
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb as xkbc;
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
@@ -94,6 +94,13 @@ impl From<xim::ClientError> for EventHandlerError {
}
}
#[derive(Debug, Default, Clone)]
struct XKBStateNotiy {
depressed_layout: LayoutIndex,
latched_layout: LayoutIndex,
locked_layout: LayoutIndex,
}
pub struct X11ClientState {
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
@@ -113,6 +120,7 @@ pub struct X11ClientState {
pub(crate) mouse_focused_window: Option<xproto::Window>,
pub(crate) keyboard_focused_window: Option<xproto::Window>,
pub(crate) xkb: xkbc::State,
previous_xkb_state: XKBStateNotiy,
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
pub(crate) xim_handler: Option<XimHandler>,
pub modifiers: Modifiers,
@@ -350,6 +358,7 @@ impl X11Client {
mouse_focused_window: None,
keyboard_focused_window: None,
xkb: xkb_state,
previous_xkb_state: XKBStateNotiy::default(),
ximc,
xim_handler,
@@ -622,7 +631,11 @@ impl X11Client {
event.latched_group as u32,
event.locked_group.into(),
);
state.previous_xkb_state = XKBStateNotiy {
depressed_layout: event.base_group as u32,
latched_layout: event.latched_group as u32,
locked_layout: event.locked_group.into(),
};
let modifiers = Modifiers::from_xkb(&state.xkb);
if state.modifiers == modifiers {
drop(state);
@@ -644,11 +657,18 @@ impl X11Client {
let modifiers = modifiers_from_state(event.state);
state.modifiers = modifiers;
state.pre_ime_key_down.take();
let keystroke = {
let code = event.detail.into();
let xkb_state = state.previous_xkb_state.clone();
state.xkb.update_mask(
event.state.bits() as ModMask,
0,
0,
xkb_state.depressed_layout,
xkb_state.latched_layout,
xkb_state.locked_layout,
);
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkbc::KeyDirection::Down);
let keysym = state.xkb.key_get_one_sym(code);
if keysym.is_modifier_key() {
return Some(());
@@ -707,8 +727,16 @@ impl X11Client {
let keystroke = {
let code = event.detail.into();
let xkb_state = state.previous_xkb_state.clone();
state.xkb.update_mask(
event.state.bits() as ModMask,
0,
0,
xkb_state.depressed_layout,
xkb_state.latched_layout,
xkb_state.locked_layout,
);
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkbc::KeyDirection::Up);
let keysym = state.xkb.key_get_one_sym(code);
if keysym.is_modifier_key() {
return Some(());
@@ -962,20 +990,17 @@ impl X11Client {
fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
let window = self.get_window(window).unwrap();
let mut state = self.0.borrow_mut();
if !state.composing {
if let Some(keystroke) = state.pre_ime_key_down.take() {
drop(state);
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
}));
return Some(());
}
}
let keystroke = state.pre_ime_key_down.take();
state.composing = false;
drop(state);
if let Some(mut keystroke) = keystroke {
keystroke.ime_key = Some(text.clone());
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
}));
}
window.handle_ime_commit(text);
Some(())
}
@@ -1169,10 +1194,13 @@ impl LinuxClient for X11Client {
let cursor = match state.cursor_cache.get(&style) {
Some(cursor) => *cursor,
None => {
let cursor = state
let Some(cursor) = state
.cursor_handle
.load_cursor(&state.xcb_connection, &style.to_icon_name())
.expect("failed to load cursor");
.log_err()
else {
return;
};
state.cursor_cache.insert(style, cursor);
cursor
}

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