Compare commits

...

66 Commits

Author SHA1 Message Date
Kyle Kelley
983e878cff pass through remote kernel's language on legacy selection 2024-11-22 16:18:18 -08:00
Marshall Bowers
1cfcdfa7ac Overhaul extension registration (#21083)
This PR overhauls extension registration in order to make it more
modular.

The `extension` crate now contains an `ExtensionHostProxy` that can be
used to register various proxies that the extension host can use to
interact with the rest of the system.

There are now a number of different proxy traits representing the
various pieces of functionality that can be provided by an extension.
The respective crates that provide this functionality can implement
their corresponding proxy trait in order to register a proxy that the
extension host will use to register the bits of functionality provided
by the extension.

Release Notes:

- N/A
2024-11-22 19:02:32 -05:00
Michael Sloan
c9f2c2792c Improve error handling and resource cleanup in linux/x11/window.rs (#21079)
* Fixes registration of event handler for xinput-2 device changes,
revealed by this improvement.

* Pushes `.unwrap()` panic-ing outwards to callers.

* Includes a description of what the X11 call was doing when a failure
was encountered.

* Fixes a variety of places where the X11 reply wasn't being inspected
for failures.

* Destroys windows on failure during setup. New structure makes it
possible for the caller of `open_window` to carry on despite failures,
and so partially initialized window should be removed (though all calls
I looked at also panic currently).

Considered pushing this through `linux/x11/client.rs` too but figured
it'd be nice to minimize merge conflicts with #20853.

Release Notes:

- N/A
2024-11-22 16:03:46 -07:00
Mikayla Maki
8240a52a39 Prevent panels from being resized past the edge of the workspace (#20637)
Closes #20593

Release Notes:

- Fixed a bug where it is possible to get in near-unrecoverable panel
state by resizing the panel past the edge of the workspace.

Co-authored-by: Trace <violet.white.batt@gmail.com>
2024-11-22 14:59:40 -08:00
teapo
c28f5b11f8 Allow overrides for json-language-server settings (#20748)
Closes #20739

The JSON LSP adapter now merges user settings with cached settings, and
util::merge_json_value_into pushes array contents from source to target.
2024-11-22 17:50:25 -05:00
Mikayla Maki
96854c68ea Markdown preview image rendering (#21082)
Closes https://github.com/zed-industries/zed/issues/13246

Supersedes: https://github.com/zed-industries/zed/pull/16192

I couldn't push to the git fork this user was using, so here's the exact
same PR but with some style nits implemented.


Release Notes:

- Added image rendering to the Markdown preview

---------

Co-authored-by: dovakin0007 <dovakin0007@gmail.com>
Co-authored-by: dovakin0007 <73059450+dovakin0007@users.noreply.github.com>
2024-11-22 14:49:26 -08:00
Peter Tripp
becc36380f Cleanup file_scan_inclusions in default.json (#21073) 2024-11-22 17:46:14 -05:00
Peter Tripp
1a0a8a9559 Fix picker new_path_prompt throwing "file exists" when saving (#21080)
Fix for getting File exists "os error 17" with `"use_system_path_prompts": false,`

This was reproducible when the first worktree is a non-folder worktree
(e.g. setting.json) so we were trying to create the new file with a path
under ~/.config/zed/settings.json/newfile.ext

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-22 17:45:03 -05:00
Peter Tripp
2fd210bc9a Fix stale Discord invite links (#21074) 2024-11-22 21:10:51 +00:00
Peter Tripp
23321be2ce docs: Improve Dart language docs (#21071) 2024-11-22 13:58:24 -05:00
Hugo Cardante
659b1c9dcf Add the option to hide both the task and command lines in the task output (#20920)
The goal is to be able to hide these lines from a task output:

```sh
⏵ Task `...` finished successfully
⏵ Command: ...
```

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-22 13:45:42 -05:00
Marshall Bowers
cb8028c092 Use Extension trait when registering extension context servers (#21070)
This PR updates the extension context server registration to go through
the `Extension` trait for interacting with extensions rather than going
through the `WasmHost` directly.

Release Notes:

- N/A
2024-11-22 13:21:30 -05:00
william341
ca76948044 gpui: Add drop_image (#19772)
This PR adds a function, WindowContext::drop_image, to manually remove a
RenderImage from the sprite atlas. In addition, PlatformAtlas::remove
was added to support this behavior. Previously, there was no way to
request a RenderImage to be removed from the sprite atlas, and since
they are not removed automatically the sprite would remain in video
memory once added until the window was closed. This PR allows a
developer to request the image be dropped from memory manually, however
it does not add automatic removal.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-11-22 09:51:26 -08:00
Conrad Irwin
852fb51528 Canonicalize paths when opening workspaces (#21039)
Closes #17161

Release Notes:

- Added symlink resolution when opening projects from within Zed.
Previously this only happened within zed's cli, but that broke file
watching on Linux when opening a symlinked directory.
2024-11-22 09:20:49 -07:00
Marshall Bowers
d489f96aef Don't name ExtensionLspAdapter in ExtensionRegistrationHooks (#21064)
This PR updates the `ExtensionRegistrationHooks` trait to not name the
`ExtensionLspAdapter` type.

This helps decouple the two.

Release Notes:

- N/A
2024-11-22 10:58:11 -05:00
Peter Tripp
b4659bb44e Fix inaccurate Ollama context length for qwen2.5 models (#20933)
Since Ollama/llama.cpp do not currently YARN for context length
extension, the context length is limited to `32768`. This can be
confirmed by the Ollama model card.
See corresponding issue on Ollama repo : 
https://github.com/ollama/ollama/issues/6865

Co-authored-by: Patrick Samson <1416027+patricksamson@users.noreply.github.com>
2024-11-22 10:10:01 -05:00
Techatrix
d5f2bca382 Filter LSP code actions based on the requested kinds (#20847)
I've observed that Zed's implementation of [Code Actions On
Format](https://zed.dev/docs/configuring-zed#code-actions-on-format)
uses the
[CodeActionContext.only](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext)
parameter to request specific code action kinds from the server. The
issue is that it does not filter out code actions from the response,
believing that the server will do it.

The [LSP
specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext)
says that the client is responsible for filtering out unwanted code
actions:

```js
/**
* Requested kind of actions to return.
*
* Actions not of this kind are filtered out by the client before being
* shown. So servers can omit computing them.
*/
only?: CodeActionKind[];
```

This PR will filter out unwanted code action on the client side.

I have initially encountered this issue because the [ZLS language
server](https://github.com/zigtools/zls) (until
https://github.com/zigtools/zls/pull/2087) does not filter code action
based on `CodeActionContext.only` so Zed runs all received code actions
even if they are explicitly disabled in the `code_actions_on_format`
setting.

Release Notes:

- Fix the `code_actions_on_format` setting when used with a language
server like ZLS

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-11-22 13:01:00 +01:00
tims
114c462143 Maintain selection on file/dir deletion in project panel (#20577)
Closes #20444

- Focus on next file/dir on deletion.
- Focus on prev file/dir in case where it's last item in worktree.
- Tested when multiple files/dirs are being deleted.

Release Notes:

- Maintain selection on file/dir deletion in project panel.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-11-22 12:59:04 +02:00
Joseph T. Lyons
933c11a9b2 Remove dead snowflake code (#21041)
Co-authored-by: Nathan Sobo <1789+nathansobo@users.noreply.github.com>

Release Notes:

- N/A
2024-11-21 23:06:03 -05:00
Michael Sloan
14ea4621ab Add fs::MTime newtype to encourage != instead of > (#20830)
See ["mtime comparison considered
harmful"](https://apenwarr.ca/log/20181113) for details of why
comparators other than equality/inequality should not be used with
mtime.

Release Notes:

- N/A
2024-11-21 19:21:18 -07:00
Piotr Osiewicz
477c6e6833 pylsp: Update mypy plugin name (#21031)
Follow-up to #21025

Release Notes:

- N/A
2024-11-22 01:13:48 +01:00
Marshall Bowers
6c470748ac zed: Remove unnecessary #[allow(non_snake_case)] attribute (#21030)
This PR removes the `#[allow(non_snake_case)]` attribute from the `zed`
crate, as it wasn't actually doing anything.

Release Notes:

- N/A
2024-11-21 18:47:44 -05:00
Marshall Bowers
e0245b3f30 Merge quick_action_bar into zed (#21026)
This PR merges the `quick_action_bar` crate into the `zed` crate.

We weren't really gaining anything by having it be a separate crate, and
it was introducing an additional step in the dependency graph that was
getting in the way.

It's only ~850 LOC, so the impact on the compilation speed of the `zed`
crate itself is negligible.

Release Notes:

- N/A
2024-11-21 18:33:11 -05:00
张小白
9211e699ee Follow-up on #18447: Unintentional deletion during merge-conflicts resolution (#20991)
After #18447 was merged, I reviewed the PR code as usual. During this
review, I realized that some code was unintentionally removed when I was
resolving merge conflicts in #18447.

Sorry!

Release Notes:

- N/A
2024-11-21 15:32:49 -08:00
Piotr Osiewicz
0663bf2a53 pylsp: Tweak default user settings (#21025)
I've also looked into not creating temp dirs in project directories and
succeeded at that for Mypy; no dice for rope though, I'll have to send a
patch to pylsp to fix that.
Closes #20646

Release Notes:

- Python: tweaked default pylsp settings to be less noisy (mypy and
pycodestyle are no longer enabled by default).
2024-11-22 00:25:30 +01:00
Marshall Bowers
9d95da56c3 welcome: Remove dependency on theme_selector (#21024)
This PR removes the dependency on `theme_selector` from `welcome`, as we
can just dispatch the action instead.

Release Notes:

- N/A
2024-11-21 17:50:22 -05:00
Piotr Osiewicz
5ee5a1a51e chore: Do not produce universal binaries for our releases (#21014)
Closes #ISSUE

Release Notes:

- We no longer provide universal binaries for our releases on macOS.
2024-11-21 23:16:49 +01:00
Kyle Kelley
72613b7668 Implement RunningKernel trait for native and remote kernels (#20934)
This PR introduces a unified interface for both native and remote
kernels through the `RunningKernel` trait. When either the native kernel
or the remote kernels are started, they return a `Box<dyn
RunningKernel>` to make it easier to work with the session. As a bonus
of this refactor, I've dropped some of the mpsc channels to instead opt
for passing messages directly to `session.route(message)`. There was a
lot of simplification of `Session` by moving responsibilities to
`NativeRunningKernel`.

No release notes yet until this is finalized.

* [x] Detect remote kernelspecs from configured remote servers
* [x] Launch kernel on demand

For now, this allows you to set env vars `JUPYTER_SERVER` and
`JUPYTER_TOKEN` to access a remote server. `JUPYTER_SERVER` should be a
base path like `http://localhost:8888` or
`https://notebooks.gesis.org/binder/jupyter/user/rubydata-binder-w6igpy4l/`

Release Notes:

- N/A
2024-11-21 14:00:19 -08:00
Michael Sloan
f74f670865 Fix panics from spawn_local tasks dropped on other threads in remote server (#21022)
Closes #21020

Release Notes:

- Fixed remote server panic of "local task dropped by a thread that
didn't spawn it"
2024-11-21 14:50:38 -07:00
Marshall Bowers
af34953bc3 extensions_ui: Remove dependency on theme_selector (#21023)
This PR removes the dependency on `theme_selector` from `extensions_ui`,
as we can just dispatch the action instead.

Release Notes:

- N/A
2024-11-21 16:48:25 -05:00
Marshall Bowers
b102a40e04 Extract VimModeSetting to its own crate (#21019)
This PR extracts the `VimModeSetting` out of the `vim` crate and into
its own `vim_mode_setting` crate.

A number of crates were depending on the entirety of the `vim` crate
just to reference `VimModeSetting`, which was not ideal.

Release Notes:

- N/A
2024-11-21 16:24:38 -05:00
Marshall Bowers
790fdcf737 collab_ui: Remove dependency on vcs_menu (#21016)
This PR removes the `vcs_menu` dependency from `collab_ui`.

We were only depending on this to call `vcs_menu::init`, which isn't
necessary to do here.

Release Notes:

- N/A
2024-11-21 15:48:35 -05:00
Marshall Bowers
2868b67286 title_bar: Remove dependency on feedback (#21013)
This PR removes the `title_bar` crate's dependency on the `feedback`
crate.

The `feedback::GiveFeedback` action now resides at
`zed_actions::feedback::GiveFeedback`.

`title_bar` now no longer depends on `editor` 🥳 

Release Notes:

- N/A
2024-11-21 15:24:04 -05:00
Peter Tripp
614b3b979b macos: Add default keybind for ctrl-home / ctrl-end (#21007)
This matches the default behavior on native macos apps.
ctrl-fn-left == ctrl-home == MoveToBeginning
ctrl-fn-right == ctrl-end == MoveToEnd
2024-11-21 15:03:50 -05:00
Marshall Bowers
4c7b48b35d title_bar: Remove dependency on vcs_menu (#21011)
This PR removes the `title_bar` crate's dependency on the `vcs_menu`.

The `vcs_menu::OpenRecent` action now resides at
`zed_actions::branches::OpenRecent`.

Release Notes:

- N/A
2024-11-21 14:56:02 -05:00
Marshall Bowers
6b2f1cc543 title_bar: Remove dependency on theme_selector (#21009)
This PR removes the `title_bar` crate's dependency on the
`theme_selector`.

The `theme_selector::Toggle` action now resides at
`zed_actions::theme_selector::Toggle`.

Release Notes:

- N/A
2024-11-21 14:33:58 -05:00
Marshall Bowers
f62ccf9c8a Extract auto_update_ui crate (#21008)
This PR extracts an `auto_update_ui` crate out of the `auto_update`
crate.

This allows `auto_update` to not depend on heavier crates like `editor`,
which in turn allows other downstream crates to start building sooner.

Release Notes:

- N/A
2024-11-21 14:11:57 -05:00
Conrad Irwin
841d3221b3 Auto release preview patch releases (#20886)
This should make the process of releasing patch releases to preview less
toilful

Release Notes:

- N/A
2024-11-21 11:59:02 -07:00
Conrad Irwin
02447a8552 Use our own git clone in draft release notes (#20956)
It turns out that messing with the git repo created by the github action
is
tricky, so we'll just clone our own.

On my machine, a shallow tree-less clone takes <500ms

Release Notes:

- N/A
2024-11-21 11:55:22 -07:00
Marshall Bowers
c16dfc1a39 title_bar: Remove dependency on command_palette (#21006)
This PR removes the `title_bar` crate's dependency on the
`command_palette`.

The `command_palette::Toggle` action now resides at
`zed_actions::command_palette::Toggle`.

Release Notes:

- N/A
2024-11-21 13:37:34 -05:00
Peter Tripp
268ac4c047 Implement readline/emacs/macos style ctrl-k cut and ctrl-y yank (#21003)
- Added support for ctrl-k / ctrl-y alternate cut/yank buffer on macos.

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-21 13:10:25 -05:00
Nils Koch
571c7d4f66 Improve project_panel diagnostic icon knockout colors (#20760)
Closes #20572

Release Notes:

- N/A

cc @danilo-leal @WeetHet
2024-11-21 15:03:40 -03:00
Kirill Bulatov
5ff49db92f Only show breadcrumbs for terminals when there's a title (#20997)
Closes https://github.com/zed-industries/zed/issues/20475

Release Notes:

- Fixed terminal title and breadcrumbs behavior

---------

Co-authored-by: Thorsten Ball <thorsten@zed.dev>
2024-11-21 19:57:09 +02:00
Conrad Irwin
395e25be25 Fix keybindings on a Spanish ISO keyboard (#20995)
Co-Authored-By: Peter <peter@zed.dev>

Also reformatted the mappings to be easier to read/edit by hand.

Release Notes:

- Fixed keyboard shortcuts on Spanish ISO keyboards

---------

Co-authored-by: Peter <peter@zed.dev>
2024-11-21 10:18:54 -07:00
Conrad Irwin
74223c1b00 vim: Fix shortcuts that require shift+punct (#20990)
Fixes a bug I introduced in #20953

Release Notes:

- N/A
2024-11-21 09:05:00 -07:00
Piotr Osiewicz
0b373d43dc toolchains: Use language-specific terms in UI (#20985)
Closes #ISSUE

Release Notes:

- N/A
2024-11-21 15:57:22 +01:00
Piotr Osiewicz
75c545aa1e toolchains: Expose raw JSON representation of a toolchain (#20721)
Closes #ISSUE

Release Notes:

- N/A
2024-11-21 13:27:25 +01:00
Adam Richardson
6ab4b46984 rope: Minor optimization for tab indices (#20911)
This is a follow up on https://github.com/zed-industries/zed/pull/20289
and optimises the tabs by replacing branches with an XOR.

I saw this after watching the latest zed decoded episode so thank you
for those videos!

Release Notes:

- N/A
2024-11-21 08:48:13 +01:00
Conrad Irwin
ebaa270baf Clip UTF-16 offsets in text for range (#20968)
When launching the Pinyin keyboard, macOS will sometimes try to peek one
character back in the string.

This caused a panic if the preceding character was an emoji. The docs
say
"don't assume the range is valid", so now we don't.

Release Notes:

- (macOS) Fixed a panic when using the Pinyin keyboard with emojis
2024-11-20 22:04:26 -07:00
Conrad Irwin
7285cdb955 Drop platform lock when setting menu (#20962)
Turns out setting the menu (sometimes) calls `selected_range` on the
input
handler.

https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1732160078058279

Release Notes:

- Fixed a panic when reloading keymaps
2024-11-20 21:24:31 -07:00
Conrad Irwin
e062f30d9e Rename ime_key -> key_char and update behavior (#20953)
As part of the recent changes to keyboard support, ime_key is no longer
populated by the IME; but instead by the keyboard.

As part of #20877 I changed some code to assume that falling back to key
was
ok, but this was not ok; instead we need to populate this more similarly
to how
it was done before #20336.

The alternative fix could be to instead of simulating these events in
our own
code to push a fake native event back to the platform input handler.

Closes #ISSUE

Release Notes:

- Fixed a bug where tapping `shift` coudl type "shift" if you had a
binding on "shift shift"
2024-11-20 20:29:47 -07:00
Conrad Irwin
37a59d6b2e vim: Fix : on welcome screen (#20937)
Release Notes:

- vim: Fixed `:` on the welcome screen
2024-11-20 19:21:22 -07:00
Mikayla Maki
a03770837e Add extensions to the remote server (#20049)
TODO:

- [x] Double check strange PHP env detection
- [x] Clippy & etc.

Release Notes:

- Added support for extension languages on the remote server

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-11-20 19:21:09 -07:00
Ryan Hawkins
0e62b6dddd Add file_scan_inclusions setting to customize Zed file indexing (#16852)
Closes #4745

Release Notes:

- Added a new `file_scan_inclusions` setting to force Zed to index files
that match the provided globs, even if they're gitignored.

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-11-20 17:00:21 -08:00
张小白
95ace03706 windows: Set CREATE_NO_WINDOW for commands (#18447)
- Closes: #18371

Release Notes:

- N/A
2024-11-20 16:52:38 -08:00
张小白
49ed932c1f Fix line truncate crash on Windows (#17271)
Closes #17267

We should update the `len` of `runs` when truncating. cc @huacnlee 

Release Notes:

- N/A
2024-11-20 16:47:55 -08:00
renovate[bot]
33e84da657 Update Rust crate cargo_metadata to 0.19 (#20948)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [cargo_metadata](https://redirect.github.com/oli-obk/cargo_metadata) |
workspace.dependencies | minor | `0.18` -> `0.19` |

---

### Release Notes

<details>
<summary>oli-obk/cargo_metadata (cargo_metadata)</summary>

###
[`v0.19.0`](https://redirect.github.com/oli-obk/cargo_metadata/blob/HEAD/CHANGELOG.md#0190---2024-11-20)

[Compare
Source](https://redirect.github.com/oli-obk/cargo_metadata/compare/0.18.1...0.19.0)

##### Added

-   Re-exported `semver` crate directly.
-   Added implementation of `std::ops::Index<&PackageId>` for `Resolve`.
-   Added `pub fn is_kind(&self, name: TargetKind) -> bool` to `Target`.
- Added derived implementations of `PartialEq`, `Eq` and `Hash` for
`Metadata` and its members' types.
-   Added default fields to `PackageBuilder`.
- Added `pub fn new(name:version:id:path:) -> Self` to `PackageBuilder`
for providing all required fields upfront.

##### Changed

-   Bumped MSRV from `1.42.0` to `1.56.0`.
- Made `parse_stream` more versatile by accepting anything that
implements `Read`.
-   Converted `TargetKind` and `CrateType` to an enum representation.

##### Removed

- Removed re-exports for `BuildMetadata` and `Prerelease` from `semver`
crate.
- Removed `.is_lib(…)`, `.is_bin(…)`, `.is_example(…)`, `.is_test(…)`,
`.is_bench(…)`, `.is_custom_build(…)`, and `.is_proc_macro(…)` from
`Target` (in favor of adding `.is_kind(…)`).

##### Fixed

- Added missing `manifest_path` field to `Artifact`. Fixes
[#&#8203;187](https://redirect.github.com/oli-obk/cargo_metadata/issues/187).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOS4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 19:39:49 -05:00
Piotr Osiewicz
536d7e5355 chore: Sever terminal_view <-> tasks_ui dependency (#20946)
Closes #ISSUE

Release Notes:

- N/A
2024-11-21 01:07:14 +01:00
Marshall Bowers
cbba44900d Add language_models crate to house language model providers (#20945)
This PR adds a new `language_models` crate to house the various language
model providers.

By extracting the provider definitions out of `language_model`, we're
able to remove `language_model`'s dependency on `editor`, which improves
incremental compilation when changing `editor`.

Release Notes:

- N/A
2024-11-20 18:49:34 -05:00
Piotr Osiewicz
335b112abd title_bar: Remove dependency on recent_projects (#20942)
Use actions defined in zed_actions to interface with that crate instead.
One drawback of this is that we now hide call controls when any modal is
visible (we used to hide them just when ssh modal was deployed).

Release Notes:

- N/A
2024-11-21 00:43:03 +01:00
renovate[bot]
33bed8d680 Update Rust crate ctor to v0.2.9 (#20928)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ctor](https://redirect.github.com/mmastrac/rust-ctor) |
workspace.dependencies | patch | `0.2.8` -> `0.2.9` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOS4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:36:27 -07:00
renovate[bot]
6d4a5f9ad2 Update Rust crate libc to v0.2.164 (#20931)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [libc](https://redirect.github.com/rust-lang/libc) |
workspace.dependencies | patch | `0.2.162` -> `0.2.164` |

---

### Release Notes

<details>
<summary>rust-lang/libc (libc)</summary>

###
[`v0.2.164`](https://redirect.github.com/rust-lang/libc/blob/HEAD/CHANGELOG.md#02164---2024-11-16)

[Compare
Source](https://redirect.github.com/rust-lang/libc/compare/0.2.163...0.2.164)

##### MSRV

This release increases the MSRV of `libc` to 1.63.

##### Other

- CI: remove tests with rust < 1.63
[#&#8203;4051](https://redirect.github.com/rust-lang/libc/pull/4051)
- MSRV: document the MSRV of the stable channel to be 1.63
[#&#8203;4040](https://redirect.github.com/rust-lang/libc/pull/4040)
- MacOS: move ifconf to s_no_extra_traits
[#&#8203;4051](https://redirect.github.com/rust-lang/libc/pull/4051)

###
[`v0.2.163`](https://redirect.github.com/rust-lang/libc/blob/HEAD/CHANGELOG.md#02163---2024-11-16)

[Compare
Source](https://redirect.github.com/rust-lang/libc/compare/0.2.162...0.2.163)

##### Added

- Aix: add more `dlopen` flags
[#&#8203;4044](https://redirect.github.com/rust-lang/libc/pull/4044)
- Android: add group calls
[#&#8203;3499](https://redirect.github.com/rust-lang/libc/pull/3499)
- FreeBSD: add `TCP_FUNCTION_BLK` and `TCP_FUNCTION_ALIAS`
[#&#8203;4047](https://redirect.github.com/rust-lang/libc/pull/4047)
- Linux: add `confstr`
[#&#8203;3612](https://redirect.github.com/rust-lang/libc/pull/3612)
- Solarish: add `aio`
[#&#8203;4033](https://redirect.github.com/rust-lang/libc/pull/4033)
- Solarish: add `arc4random*`
[#&#8203;3944](https://redirect.github.com/rust-lang/libc/pull/3944)

##### Changed

- Emscripten: upgrade emsdk to 3.1.68
[#&#8203;3962](https://redirect.github.com/rust-lang/libc/pull/3962)
- Hurd: use more standard types
[#&#8203;3733](https://redirect.github.com/rust-lang/libc/pull/3733)
- Hurd: use the standard `ssize_t = isize`
[#&#8203;4029](https://redirect.github.com/rust-lang/libc/pull/4029)
- Solaris: fix `confstr` and `ucontext_t`
[#&#8203;4035](https://redirect.github.com/rust-lang/libc/pull/4035)

##### Other

- CI: add Solaris
[#&#8203;4035](https://redirect.github.com/rust-lang/libc/pull/4035)
- CI: add `i686-unknown-freebsd`
[#&#8203;3997](https://redirect.github.com/rust-lang/libc/pull/3997)
- CI: ensure that calls to `sort` do not depend on locale
[#&#8203;4026](https://redirect.github.com/rust-lang/libc/pull/4026)
- Specify `rust-version` in `Cargo.toml`
[#&#8203;4041](https://redirect.github.com/rust-lang/libc/pull/4041)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOS4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:35:08 -07:00
renovate[bot]
427c2017c3 Update Rust crate serde_json to v1.0.133 (#20932)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [serde_json](https://redirect.github.com/serde-rs/json) | dependencies
| patch | `1.0.132` -> `1.0.133` |
| [serde_json](https://redirect.github.com/serde-rs/json) |
workspace.dependencies | patch | `1.0.132` -> `1.0.133` |

---

### Release Notes

<details>
<summary>serde-rs/json (serde_json)</summary>

###
[`v1.0.133`](https://redirect.github.com/serde-rs/json/releases/tag/v1.0.133)

[Compare
Source](https://redirect.github.com/serde-rs/json/compare/v1.0.132...v1.0.133)

- Implement From<\[T; N]> for serde_json::Value
([#&#8203;1215](https://redirect.github.com/serde-rs/json/issues/1215))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xOS4wIiwidXBkYXRlZEluVmVyIjoiMzkuMTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:34:59 -07:00
Conrad Irwin
ebca6a8f3d Send os_version and country to amplitude (#20936)
Release Notes:

- N/A
2024-11-20 15:34:24 -07:00
Marshall Bowers
29c9f0f6a1 Extract InlineCompletionProvider to its own crate (#20935)
This PR extracts the `InlineCompletionProvider` trait and its related
types out of `editor` and into a new `inline_completion` crate.

By doing so we're able to remove a dependency on `editor` from the
`copilot` and `supermaven` crates.

We did have to move `editor::Direction` into the `inline_completion`
crate, as it is referenced by the `InlineCompletionProvider`. This
should find a better home, at some point.

Release Notes:

- N/A
2024-11-20 16:51:13 -05:00
Marshall Bowers
e076f55d78 language_model: Remove dependency on inline_completion_button (#20930)
This PR removes a dependency on the `inline_completion_button` crate
from the `language_model` crate.

We were taking on this dependency solely to call `initiate_sign_in`,
which can easily be moved to the `copilot` crate.

This allows `language_model` to move up in the crate dependency graph.

Release Notes:

- N/A
2024-11-20 16:19:20 -05:00
248 changed files with 7762 additions and 2949 deletions

View File

@@ -272,18 +272,12 @@ jobs:
- name: Create macOS app bundle
run: script/bundle-mac
- name: Rename single-architecture binaries
- name: Rename binaries
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
run: |
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (universal) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
@@ -309,7 +303,6 @@ jobs:
target/zed-remote-server-macos-aarch64.gz
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -404,3 +397,16 @@ jobs:
target/release/zed-linux-aarch64.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto-release-preview:
name: Auto release preview
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
needs: [bundle-mac, bundle-linux, bundle-linux-aarch64]
runs-on:
- self-hosted
- bundle
steps:
- name: gh release
run: gh release edit $GITHUB_REF_NAME --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

221
Cargo.lock generated
View File

@@ -402,6 +402,7 @@ dependencies = [
"indoc",
"language",
"language_model",
"language_models",
"languages",
"log",
"lsp",
@@ -1013,26 +1014,41 @@ dependencies = [
"anyhow",
"client",
"db",
"editor",
"gpui",
"http_client",
"log",
"markdown_preview",
"menu",
"paths",
"release_channel",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"smol",
"tempfile",
"util",
"which 6.0.3",
"workspace",
]
[[package]]
name = "auto_update_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"auto_update",
"client",
"editor",
"gpui",
"http_client",
"markdown_preview",
"menu",
"release_channel",
"serde",
"serde_json",
"smol",
"util",
"workspace",
]
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -2107,9 +2123,9 @@ dependencies = [
[[package]]
name = "cargo_metadata"
version = "0.18.1"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
checksum = "afc309ed89476c8957c50fb818f56fe894db857866c3e163335faa91dc34eb85"
dependencies = [
"camino",
"cargo-platform",
@@ -2585,6 +2601,7 @@ dependencies = [
"editor",
"env_logger 0.11.5",
"envy",
"extension",
"file_finder",
"fs",
"futures 0.3.31",
@@ -2696,7 +2713,6 @@ dependencies = [
"tree-sitter-md",
"ui",
"util",
"vcs_menu",
"workspace",
]
@@ -2827,6 +2843,7 @@ dependencies = [
"anyhow",
"collections",
"command_palette_hooks",
"extension",
"futures 0.3.31",
"gpui",
"log",
@@ -2876,6 +2893,7 @@ dependencies = [
"gpui",
"http_client",
"indoc",
"inline_completion",
"language",
"lsp",
"menu",
@@ -3350,9 +3368,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [
"quote",
"syn 2.0.87",
@@ -3715,12 +3733,14 @@ dependencies = [
"emojis",
"env_logger 0.11.5",
"file_icons",
"fs",
"futures 0.3.31",
"fuzzy",
"git",
"gpui",
"http_client",
"indoc",
"inline_completion",
"itertools 0.13.0",
"language",
"linkify",
@@ -4027,6 +4047,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"util",
]
[[package]]
@@ -4108,10 +4129,12 @@ dependencies = [
"language",
"log",
"lsp",
"parking_lot",
"semantic_version",
"serde",
"serde_json",
"toml 0.8.19",
"util",
"wasm-encoder 0.215.0",
"wasmparser 0.215.0",
"wit-component",
@@ -4158,6 +4181,7 @@ dependencies = [
"gpui",
"http_client",
"language",
"language_extension",
"log",
"lsp",
"node_runtime",
@@ -4165,6 +4189,7 @@ dependencies = [
"paths",
"project",
"release_channel",
"remote",
"reqwest_client",
"schemars",
"semantic_version",
@@ -4173,7 +4198,9 @@ dependencies = [
"serde_json_lenient",
"settings",
"task",
"tempfile",
"theme",
"theme_extension",
"toml 0.8.19",
"url",
"util",
@@ -4187,21 +4214,15 @@ name = "extensions_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"assistant_slash_command",
"client",
"collections",
"context_servers",
"db",
"editor",
"extension",
"extension_host",
"fs",
"fuzzy",
"gpui",
"indexed_docs",
"language",
"log",
"lsp",
"num-format",
"picker",
"project",
@@ -4210,13 +4231,10 @@ dependencies = [
"serde",
"settings",
"smallvec",
"snippet_provider",
"theme",
"theme_selector",
"ui",
"util",
"vim",
"wasmtime-wasi",
"vim_mode_setting",
"workspace",
"zed_actions",
]
@@ -4325,6 +4343,7 @@ dependencies = [
"urlencoding",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -4600,6 +4619,7 @@ dependencies = [
"objc",
"parking_lot",
"paths",
"proto",
"rope",
"serde",
"serde_json",
@@ -4933,7 +4953,6 @@ dependencies = [
"unindent",
"url",
"util",
"windows 0.58.0",
]
[[package]]
@@ -6056,6 +6075,16 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "inline_completion"
version = "0.1.0"
dependencies = [
"gpui",
"language",
"project",
"text",
]
[[package]]
name = "inline_completion_button"
version = "0.1.0"
@@ -6076,7 +6105,6 @@ dependencies = [
"supermaven",
"theme",
"ui",
"util",
"workspace",
"zed_actions",
]
@@ -6458,6 +6486,7 @@ dependencies = [
"ctor",
"ec4rs",
"env_logger 0.11.5",
"fs",
"futures 0.3.31",
"fuzzy",
"git",
@@ -6502,6 +6531,23 @@ dependencies = [
"util",
]
[[package]]
name = "language_extension"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collections",
"extension",
"futures 0.3.31",
"gpui",
"language",
"lsp",
"serde",
"serde_json",
"util",
]
[[package]]
name = "language_model"
version = "0.1.0"
@@ -6509,28 +6555,48 @@ dependencies = [
"anthropic",
"anyhow",
"base64 0.22.1",
"client",
"collections",
"copilot",
"ctor",
"editor",
"env_logger 0.11.5",
"feature_flags",
"futures 0.3.31",
"google_ai",
"gpui",
"http_client",
"image",
"inline_completion_button",
"language",
"log",
"menu",
"ollama",
"open_ai",
"parking_lot",
"proto",
"schemars",
"serde",
"serde_json",
"smol",
"strum 0.25.0",
"ui",
"util",
]
[[package]]
name = "language_models"
version = "0.1.0"
dependencies = [
"anthropic",
"anyhow",
"client",
"collections",
"copilot",
"editor",
"feature_flags",
"fs",
"futures 0.3.31",
"google_ai",
"gpui",
"http_client",
"language_model",
"menu",
"ollama",
"open_ai",
"project",
"proto",
"rand 0.8.5",
"schemars",
"serde",
"serde_json",
@@ -6538,12 +6604,10 @@ dependencies = [
"smol",
"strum 0.25.0",
"telemetry_events",
"text",
"theme",
"thiserror 1.0.69",
"tiktoken-rs",
"ui",
"unindent",
"util",
]
@@ -6670,9 +6734,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.162"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libdbus-sys"
@@ -6921,7 +6985,6 @@ dependencies = [
"serde_json",
"smol",
"util",
"windows 0.58.0",
]
[[package]]
@@ -7460,7 +7523,6 @@ dependencies = [
"util",
"walkdir",
"which 6.0.3",
"windows 0.58.0",
]
[[package]]
@@ -9149,7 +9211,6 @@ dependencies = [
"url",
"util",
"which 6.0.3",
"windows 0.58.0",
"worktree",
]
@@ -9382,24 +9443,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "quick_action_bar"
version = "0.1.0"
dependencies = [
"assistant",
"editor",
"gpui",
"markdown_preview",
"picker",
"repl",
"search",
"settings",
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
name = "quinn"
version = "0.11.6"
@@ -9649,6 +9692,7 @@ dependencies = [
"anyhow",
"auto_update",
"editor",
"extension_host",
"file_finder",
"futures 0.3.31",
"fuzzy",
@@ -9674,6 +9718,7 @@ dependencies = [
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -9823,6 +9868,8 @@ dependencies = [
"client",
"clock",
"env_logger 0.11.5",
"extension",
"extension_host",
"fork",
"fs",
"futures 0.3.31",
@@ -9831,6 +9878,7 @@ dependencies = [
"gpui",
"http_client",
"language",
"language_extension",
"languages",
"libc",
"log",
@@ -9879,6 +9927,7 @@ dependencies = [
"editor",
"env_logger 0.11.5",
"feature_flags",
"file_icons",
"futures 0.3.31",
"gpui",
"http_client",
@@ -9910,7 +9959,6 @@ dependencies = [
"ui",
"util",
"uuid",
"windows 0.58.0",
"workspace",
]
@@ -10854,9 +10902,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"indexmap 2.6.0",
"itoa",
@@ -11273,6 +11321,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"extension",
"fs",
"futures 0.3.31",
"gpui",
@@ -11783,6 +11832,7 @@ dependencies = [
"futures 0.3.31",
"gpui",
"http_client",
"inline_completion",
"language",
"log",
"postage",
@@ -11797,7 +11847,6 @@ dependencies = [
"ui",
"unicode-segmentation",
"util",
"windows 0.58.0",
]
[[package]]
@@ -12164,6 +12213,7 @@ dependencies = [
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -12249,6 +12299,7 @@ name = "terminal_view"
version = "0.1.0"
dependencies = [
"anyhow",
"breadcrumbs",
"client",
"collections",
"db",
@@ -12267,7 +12318,6 @@ dependencies = [
"shellexpand 2.1.2",
"smol",
"task",
"tasks_ui",
"terminal",
"theme",
"ui",
@@ -12325,6 +12375,17 @@ dependencies = [
"uuid",
]
[[package]]
name = "theme_extension"
version = "0.1.0"
dependencies = [
"anyhow",
"extension",
"fs",
"gpui",
"theme",
]
[[package]]
name = "theme_importer"
version = "0.1.0"
@@ -12362,6 +12423,7 @@ dependencies = [
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -12574,16 +12636,12 @@ dependencies = [
"call",
"client",
"collections",
"command_palette",
"editor",
"feature_flags",
"feedback",
"gpui",
"http_client",
"notifications",
"pretty_assertions",
"project",
"recent_projects",
"remote",
"rpc",
"serde",
@@ -12591,11 +12649,9 @@ dependencies = [
"smallvec",
"story",
"theme",
"theme_selector",
"tree-sitter-md",
"ui",
"util",
"vcs_menu",
"windows 0.58.0",
"workspace",
"zed_actions",
@@ -13517,6 +13573,7 @@ dependencies = [
"rust-embed",
"serde",
"serde_json",
"smol",
"take-until",
"tempfile",
"tendril",
@@ -13606,6 +13663,7 @@ dependencies = [
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -13652,10 +13710,20 @@ dependencies = [
"tokio",
"ui",
"util",
"vim_mode_setting",
"workspace",
"zed_actions",
]
[[package]]
name = "vim_mode_setting"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui",
"settings",
]
[[package]]
name = "vscode_theme"
version = "0.2.0"
@@ -14356,21 +14424,20 @@ version = "0.1.0"
dependencies = [
"anyhow",
"client",
"copilot",
"db",
"editor",
"fuzzy",
"gpui",
"inline_completion_button",
"install_cli",
"picker",
"project",
"schemars",
"serde",
"settings",
"theme_selector",
"ui",
"util",
"vim",
"vim_mode_setting",
"workspace",
"zed_actions",
]
@@ -15428,10 +15495,10 @@ dependencies = [
"ashpd",
"assets",
"assistant",
"assistant_slash_command",
"async-watch",
"audio",
"auto_update",
"auto_update_ui",
"backtrace",
"breadcrumbs",
"call",
@@ -15444,12 +15511,12 @@ dependencies = [
"collections",
"command_palette",
"command_palette_hooks",
"context_servers",
"copilot",
"db",
"diagnostics",
"editor",
"env_logger 0.11.5",
"extension",
"extension_host",
"extensions_ui",
"feature_flags",
@@ -15464,12 +15531,13 @@ dependencies = [
"gpui",
"http_client",
"image_viewer",
"indexed_docs",
"inline_completion_button",
"install_cli",
"journal",
"language",
"language_extension",
"language_model",
"language_models",
"language_selector",
"language_tools",
"languages",
@@ -15485,12 +15553,12 @@ dependencies = [
"outline_panel",
"parking_lot",
"paths",
"picker",
"profiling",
"project",
"project_panel",
"project_symbols",
"proto",
"quick_action_bar",
"recent_projects",
"release_channel",
"remote",
@@ -15516,6 +15584,7 @@ dependencies = [
"telemetry_events",
"terminal_view",
"theme",
"theme_extension",
"theme_selector",
"time",
"toolchain_selector",
@@ -15526,7 +15595,9 @@ dependencies = [
"urlencoding",
"util",
"uuid",
"vcs_menu",
"vim",
"vim_mode_setting",
"welcome",
"windows 0.58.0",
"winresource",

View File

@@ -9,6 +9,7 @@ members = [
"crates/assistant_tool",
"crates/audio",
"crates/auto_update",
"crates/auto_update_ui",
"crates/breadcrumbs",
"crates/call",
"crates/channel",
@@ -49,11 +50,14 @@ members = [
"crates/http_client",
"crates/image_viewer",
"crates/indexed_docs",
"crates/inline_completion",
"crates/inline_completion_button",
"crates/install_cli",
"crates/journal",
"crates/language",
"crates/language_extension",
"crates/language_model",
"crates/language_models",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
@@ -78,7 +82,6 @@ members = [
"crates/project_panel",
"crates/project_symbols",
"crates/proto",
"crates/quick_action_bar",
"crates/recent_projects",
"crates/refineable",
"crates/refineable/derive_refineable",
@@ -114,6 +117,7 @@ members = [
"crates/terminal_view",
"crates/text",
"crates/theme",
"crates/theme_extension",
"crates/theme_importer",
"crates/theme_selector",
"crates/time_format",
@@ -126,6 +130,7 @@ members = [
"crates/util",
"crates/vcs_menu",
"crates/vim",
"crates/vim_mode_setting",
"crates/welcome",
"crates/workspace",
"crates/worktree",
@@ -185,6 +190,7 @@ assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_tool = { path = "crates/assistant_tool" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
auto_update_ui = { path = "crates/auto_update_ui" }
breadcrumbs = { path = "crates/breadcrumbs" }
call = { path = "crates/call" }
channel = { path = "crates/channel" }
@@ -221,11 +227,14 @@ html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion = { path = "crates/inline_completion" }
inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_models = { path = "crates/language_models" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
@@ -252,7 +261,6 @@ project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
proto = { path = "crates/proto" }
quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" }
@@ -287,6 +295,7 @@ terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" }
theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
@@ -298,6 +307,7 @@ ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
welcome = { path = "crates/welcome" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
@@ -332,7 +342,7 @@ blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.18"
cargo_metadata = "0.19"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }

View File

@@ -49,8 +49,9 @@
"ctrl-d": "editor::Delete",
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-t": "editor::Transpose",
"ctrl-k": "editor::KillRingCut",
"ctrl-y": "editor::KillRingYank",
"cmd-k q": "editor::Rewrap",
"cmd-k cmd-q": "editor::Rewrap",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
@@ -92,6 +93,8 @@
"ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"ctrl-shift-p": "editor::SelectUp",
"shift-down": "editor::SelectDown",

View File

@@ -577,7 +577,7 @@
}
},
{
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"use_layout_keys": true,
"bindings": {
":": "command_palette::Toggle",

View File

@@ -668,7 +668,7 @@
},
// Add files or globs of files that will be excluded by Zed entirely:
// they will be skipped during FS scan(s), file tree and file search
// will lack the corresponding file entries.
// will lack the corresponding file entries. Overrides `file_scan_inclusions`.
"file_scan_exclusions": [
"**/.git",
"**/.svn",
@@ -679,6 +679,11 @@
"**/.classpath",
"**/.settings"
],
// Add files or globs of files that will be included by Zed, even when
// ignored by git. This is useful for files that are not tracked by git,
// but are still important to your project. Note that globs that are
// overly broad can slow down Zed's file scanning. Overridden by `file_scan_exclusions`.
"file_scan_inclusions": [".env*"],
// Git gutter behavior configuration.
"git": {
// Control whether the git gutter is shown. May take 2 values:
@@ -839,8 +844,12 @@
}
},
"toolbar": {
// Whether to display the terminal title in its toolbar.
"title": true
// Whether to display the terminal title in its toolbar's breadcrumbs.
// Only shown if the terminal title is not empty.
//
// The shell running in the terminal needs to be configured to emit the title.
// Example: `echo -e "\e]2;New Title\007";`
"breadcrumbs": true
}
// Set the terminal's font size. If this option is not included,
// the terminal will default to matching the buffer's font size.

View File

@@ -50,6 +50,7 @@ indexed_docs.workspace = true
indoc.workspace = true
language.workspace = true
language_model.workspace = true
language_models.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true

View File

@@ -33,7 +33,6 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::impl_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use indexed_docs::IndexedDocsRegistry;
pub(crate) use inline_assistant::*;
use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
@@ -275,7 +274,7 @@ pub fn init(
client.telemetry().clone(),
cx,
);
IndexedDocsRegistry::init_global(cx);
indexed_docs::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Assistant::NAMESPACE);

View File

@@ -50,11 +50,11 @@ use indexed_docs::IndexedDocsStore;
use language::{
language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset,
};
use language_model::{
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
LanguageModelRegistry, Role,
};
use language_model::{LanguageModelImage, LanguageModelToolUse};
use language_model::{
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
ZED_CLOUD_PROVIDER_ID,
};
use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate};
use project::lsp_store::LocalLspAdapterDelegate;
@@ -664,7 +664,7 @@ impl AssistantPanel {
// If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
// the provider, we want to show a nudge to sign in.
let show_zed_ai_notice = client_status.is_signed_out()
&& active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
&& active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID);
self.show_zed_ai_notice = show_zed_ai_notice;
cx.notify();

View File

@@ -5,13 +5,12 @@ use anthropic::Model as AnthropicModel;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::{AppContext, Pixels};
use language_model::provider::open_ai;
use language_model::settings::{
AnthropicSettingsContent, AnthropicSettingsContentV1, OllamaSettingsContent,
OpenAiSettingsContent, OpenAiSettingsContentV1, VersionedAnthropicSettingsContent,
VersionedOpenAiSettingsContent,
use language_model::{CloudModel, LanguageModel};
use language_models::{
provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent,
AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent,
OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent,
};
use language_model::{settings::AllLanguageModelSettings, CloudModel, LanguageModel};
use ollama::Model as OllamaModel;
use schemars::{schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};

View File

@@ -25,13 +25,15 @@ use gpui::{
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
logging::report_assistant_event,
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role,
StopReason,
};
use language_models::{
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
report_assistant_event,
};
use open_ai::Model as OpenAiModel;
use paths::contexts_dir;
use project::Project;

View File

@@ -770,7 +770,7 @@ impl ContextStore {
contexts.push(SavedContextMetadata {
title: title.to_string(),
path,
mtime: metadata.mtime.into(),
mtime: metadata.mtime.timestamp_for_user().into(),
});
}
}

View File

@@ -30,9 +30,10 @@ use gpui::{
};
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
use language_model::{
logging::report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role,
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelTextStream, Role,
};
use language_models::report_assistant_event;
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction};

View File

@@ -17,9 +17,9 @@ use gpui::{
};
use language::Buffer;
use language_model::{
logging::report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role,
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use language_models::report_assistant_event;
use settings::Settings;
use std::{
cmp,

View File

@@ -18,6 +18,7 @@ use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
extension_slash_command::init(cx);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View File

@@ -3,17 +3,39 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result;
use async_trait::async_trait;
use extension::{Extension, WorktreeDelegate};
use gpui::{Task, WeakView, WindowContext};
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
use crate::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
SlashCommandRegistry, SlashCommandResult,
};
pub fn init(cx: &mut AppContext) {
let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
slash_command_registry: SlashCommandRegistry::global(cx),
});
}
struct SlashCommandRegistryProxy {
slash_command_registry: Arc<SlashCommandRegistry>,
}
impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
fn register_slash_command(
&self,
extension: Arc<dyn Extension>,
command: extension::SlashCommand,
) {
self.slash_command_registry
.register_command(ExtensionSlashCommand::new(extension, command), false)
}
}
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);

View File

@@ -16,21 +16,16 @@ doctest = false
anyhow.workspace = true
client.workspace = true
db.workspace = true
editor.workspace = true
gpui.workspace = true
http_client.workspace = true
log.workspace = true
markdown_preview.workspace = true
menu.workspace = true
paths.workspace = true
release_channel.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
tempfile.workspace = true
util.workspace = true
which.workspace = true
workspace.workspace = true

View File

@@ -1,27 +1,19 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, TelemetrySettings};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use editor::{Editor, MultiBuffer};
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
SemanticVersion, Task, WindowContext,
};
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use paths::remote_servers_dir;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use smol::{fs, io::AsyncReadExt};
use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs::File, process::Command};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use paths::remote_servers_dir;
use release_channel::{AppCommitSha, ReleaseChannel};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs, io::AsyncReadExt};
use smol::{fs::File, process::Command};
use std::{
env::{
self,
@@ -32,24 +24,13 @@ use std::{
sync::Arc,
time::Duration,
};
use update_notification::UpdateNotification;
use util::ResultExt;
use which::which;
use workspace::notifications::NotificationId;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
actions!(
auto_update,
[
Check,
DismissErrorMessage,
ViewReleaseNotes,
ViewReleaseNotesLocally
]
);
actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes,]);
#[derive(Serialize)]
struct UpdateRequestBody {
@@ -146,12 +127,6 @@ struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
impl Global for GlobalAutoUpdate {}
#[derive(Deserialize)]
struct ReleaseNotesBody {
title: String,
release_notes: String,
}
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
@@ -161,10 +136,6 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
workspace.register_action(|_, action, cx| {
view_release_notes(action, cx);
});
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
view_release_notes_locally(workspace, cx);
});
})
.detach();
@@ -264,121 +235,6 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
None
}
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let url = match release_channel {
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
_ => None,
};
if let Some(url) = url {
cx.open_url(url);
return;
}
let version = AppVersion::global(cx).to_string();
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(
"/api/release_notes/v2/{}/{}",
release_channel.dev_name(),
version
));
let markdown = workspace
.app_state()
.languages
.language_for_name("Markdown");
workspace
.with_local_workspace(cx, move |_, cx| {
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else {
return;
};
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await.ok();
let body: serde_json::Result<ReleaseNotesBody> =
serde_json::from_slice(body.as_slice());
if let Ok(body) = body {
workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx)
});
let language_registry = project.read(cx).languages().clone();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx)
});
let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
language_registry,
Some(tab_description),
cx,
);
workspace.add_item_to_active_pane(
Box::new(view.clone()),
None,
true,
cx,
);
cx.notify();
})
.log_err();
}
})
.detach();
})
.detach();
}
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version;
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn(|workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
let workspace_handle = workspace.weak_handle();
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
);
updater.update(cx, |updater, cx| {
updater
.set_should_show_update_notification(false, cx)
.detach_and_log_err(cx);
});
})?;
}
anyhow::Ok(())
})
.detach();
None
}
impl AutoUpdater {
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
cx.default_global::<GlobalAutoUpdate>().0.clone()
@@ -423,6 +279,10 @@ impl AutoUpdater {
}));
}
pub fn current_version(&self) -> SemanticVersion {
self.current_version
}
pub fn status(&self) -> AutoUpdateStatus {
self.status.clone()
}
@@ -646,7 +506,7 @@ impl AutoUpdater {
Ok(())
}
fn set_should_show_update_notification(
pub fn set_should_show_update_notification(
&self,
should_show: bool,
cx: &AppContext,
@@ -668,7 +528,7 @@ impl AutoUpdater {
})
}
fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
pub fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
cx.background_executor().spawn(async move {
Ok(KEY_VALUE_STORE
.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?

View File

@@ -0,0 +1,28 @@
[package]
name = "auto_update_ui"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/auto_update_ui.rs"
[dependencies]
anyhow.workspace = true
auto_update.workspace = true
client.workspace = true
editor.workspace = true
gpui.workspace = true
http_client.workspace = true
markdown_preview.workspace = true
menu.workspace = true
release_channel.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -0,0 +1,147 @@
mod update_notification;
use auto_update::AutoUpdater;
use editor::{Editor, MultiBuffer};
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
use http_client::HttpClient;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use release_channel::{AppVersion, ReleaseChannel};
use serde::Deserialize;
use smol::io::AsyncReadExt;
use util::ResultExt as _;
use workspace::notifications::NotificationId;
use workspace::Workspace;
use crate::update_notification::UpdateNotification;
actions!(auto_update, [ViewReleaseNotesLocally]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
view_release_notes_locally(workspace, cx);
});
})
.detach();
}
#[derive(Deserialize)]
struct ReleaseNotesBody {
title: String,
release_notes: String,
}
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let url = match release_channel {
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
_ => None,
};
if let Some(url) = url {
cx.open_url(url);
return;
}
let version = AppVersion::global(cx).to_string();
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(
"/api/release_notes/v2/{}/{}",
release_channel.dev_name(),
version
));
let markdown = workspace
.app_state()
.languages
.language_for_name("Markdown");
workspace
.with_local_workspace(cx, move |_, cx| {
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else {
return;
};
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await.ok();
let body: serde_json::Result<ReleaseNotesBody> =
serde_json::from_slice(body.as_slice());
if let Ok(body) = body {
workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx)
});
let language_registry = project.read(cx).languages().clone();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx)
});
let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
language_registry,
Some(tab_description),
cx,
);
workspace.add_item_to_active_pane(
Box::new(view.clone()),
None,
true,
cx,
);
cx.notify();
})
.log_err();
}
})
.detach();
})
.detach();
}
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version();
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn(|workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
let workspace_handle = workspace.weak_handle();
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
);
updater.update(cx, |updater, cx| {
updater
.set_should_show_update_notification(false, cx)
.detach_and_log_err(cx);
});
})?;
}
anyhow::Ok(())
})
.detach();
None
}

View File

@@ -224,6 +224,8 @@ impl Telemetry {
cx.background_executor()
.spawn({
let state = state.clone();
let os_version = os_version();
state.lock().os_version = Some(os_version.clone());
async move {
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
state.lock().log_file = Some(tempfile);

View File

@@ -90,6 +90,7 @@ collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
extension.workspace = true
file_finder.workspace = true
fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] }

View File

@@ -1555,15 +1555,15 @@ fn for_snowflake(
);
map.insert("signed_in".to_string(), event.signed_in.into());
if let Some(country_code) = country_code.as_ref() {
map.insert("country_code".to_string(), country_code.clone().into());
map.insert("country".to_string(), country_code.clone().into());
}
}
// NOTE: most amplitude user properties are read out of our event_properties
// dictionary. See https://app.amplitude.com/data/zed/Zed/sources/detail/production/falcon%3A159998
// for how that is configured.
let user_properties = Some(serde_json::json!({
"is_staff": body.is_staff,
"Country": country_code.clone(),
"OS": format!("{} {}", body.os_name, body.os_version.clone().unwrap_or_default()),
"Version": body.app_version.clone(),
}));
Some(SnowflakeRow {
@@ -1588,48 +1588,3 @@ struct SnowflakeRow {
pub user_properties: Option<serde_json::Value>,
pub insert_id: Option<String>,
}
#[derive(Serialize, Deserialize)]
struct SnowflakeData {
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
pub installation_id: Option<String>,
/// Identifier unique to each logged in Zed user (randomly generated on first sign in)
/// Identifier unique to each Zed session (differs for each time you open Zed)
pub session_id: Option<String>,
pub metrics_id: Option<String>,
/// True for Zed staff, otherwise false
pub is_staff: Option<bool>,
/// Zed version number
pub app_version: String,
pub os_name: String,
pub os_version: Option<String>,
pub architecture: String,
/// Zed release channel (stable, preview, dev)
pub release_channel: Option<String>,
pub signed_in: bool,
#[serde(flatten)]
pub editor_event: Option<EditorEvent>,
#[serde(flatten)]
pub inline_completion_event: Option<InlineCompletionEvent>,
#[serde(flatten)]
pub call_event: Option<CallEvent>,
#[serde(flatten)]
pub assistant_event: Option<AssistantEvent>,
#[serde(flatten)]
pub cpu_event: Option<CpuEvent>,
#[serde(flatten)]
pub memory_event: Option<MemoryEvent>,
#[serde(flatten)]
pub app_event: Option<AppEvent>,
#[serde(flatten)]
pub setting_event: Option<SettingEvent>,
#[serde(flatten)]
pub extension_event: Option<ExtensionEvent>,
#[serde(flatten)]
pub edit_event: Option<EditEvent>,
#[serde(flatten)]
pub repl_event: Option<ReplEvent>,
#[serde(flatten)]
pub action_event: Option<ActionEvent>,
}

View File

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

View File

@@ -1,6 +1,7 @@
use crate::tests::TestServer;
use call::ActiveCall;
use collections::HashSet;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs as _};
use futures::StreamExt as _;
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _};
@@ -81,6 +82,7 @@ async fn test_sharing_an_ssh_remote_project(
http_client: remote_http_client,
node_runtime: node,
languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
},
cx,
)
@@ -243,6 +245,7 @@ async fn test_ssh_collaboration_git_branches(
http_client: remote_http_client,
node_runtime: node,
languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
},
cx,
)
@@ -400,6 +403,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
http_client: remote_http_client,
node_runtime: NodeRuntime::unavailable(),
languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
},
cx,
)

View File

@@ -58,12 +58,11 @@ settings.workspace = true
smallvec.workspace = true
story = { workspace = true, optional = true }
theme.workspace = true
time_format.workspace = true
time.workspace = true
time_format.workspace = true
title_bar.workspace = true
ui.workspace = true
util.workspace = true
vcs_menu.workspace = true
workspace.workspace = true
[dev-dependencies]

View File

@@ -33,7 +33,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_panel::init(cx);
notifications::init(app_state, cx);
title_bar::init(cx);
vcs_menu::init(cx);
}
fn notification_window_options(

View File

@@ -11,7 +11,7 @@ use command_palette_hooks::{
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
ParentElement, Render, Styled, Task, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
@@ -21,9 +21,7 @@ use settings::Settings;
use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace, WorkspaceSettings};
use zed_actions::OpenZedUrl;
actions!(command_palette, [Toggle]);
use zed_actions::{command_palette::Toggle, OpenZedUrl};
pub fn init(cx: &mut AppContext) {
client::init_settings(cx);

View File

@@ -15,6 +15,7 @@ path = "src/context_servers.rs"
anyhow.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
extension.workspace = true
futures.workspace = true
gpui.workspace = true
log.workspace = true

View File

@@ -9,7 +9,7 @@ use serde_json::{value::RawValue, Value};
use smol::{
channel,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
process::{self, Child},
process::Child,
};
use std::{
fmt,
@@ -152,7 +152,7 @@ impl Client {
&binary.args
);
let mut command = process::Command::new(&binary.executable);
let mut command = util::command::new_smol_command(&binary.executable);
command
.args(&binary.args)
.envs(binary.env.unwrap_or_default())

View File

@@ -1,4 +1,5 @@
pub mod client;
mod extension_context_server;
pub mod manager;
pub mod protocol;
mod registry;
@@ -19,6 +20,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
pub fn init(cx: &mut AppContext) {
ContextServerSettings::register(cx);
ContextServerFactoryRegistry::default_global(cx);
extension_context_server::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);

View File

@@ -0,0 +1,78 @@
use std::sync::Arc;
use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate};
use gpui::{AppContext, Model};
use crate::manager::ServerCommand;
use crate::ContextServerFactoryRegistry;
struct ExtensionProject {
worktree_ids: Vec<u64>,
}
impl ProjectDelegate for ExtensionProject {
fn worktree_ids(&self) -> Vec<u64> {
self.worktree_ids.clone()
}
}
pub fn init(cx: &mut AppContext) {
let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_context_server_proxy(ContextServerFactoryRegistryProxy {
context_server_factory_registry: ContextServerFactoryRegistry::global(cx),
});
}
struct ContextServerFactoryRegistryProxy {
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
}
impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy {
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
id: Arc<str>,
cx: &mut AppContext,
) {
self.context_server_factory_registry
.update(cx, |registry, _| {
registry.register_server_factory(
id.clone(),
Arc::new({
move |project, cx| {
log::info!(
"loading command for context server {id} from extension {}",
extension.manifest().id
);
let id = id.clone();
let extension = extension.clone();
cx.spawn(|mut cx| async move {
let extension_project =
project.update(&mut cx, |project, cx| {
Arc::new(ExtensionProject {
worktree_ids: project
.visible_worktrees(cx)
.map(|worktree| worktree.read(cx).id().to_proto())
.collect(),
})
})?;
let command = extension
.context_server_command(id.clone(), extension_project)
.await?;
log::info!("loaded command for context server {id}: {command:?}");
Ok(ServerCommand {
path: command.command,
args: command.args,
env: Some(command.env.into_iter().collect()),
})
})
}
}),
)
});
}
}

View File

@@ -29,14 +29,14 @@ anyhow.workspace = true
async-compression.workspace = true
async-tar.workspace = true
chrono.workspace = true
collections.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
inline_completion.workspace = true
language.workspace = true
lsp.workspace = true
menu.workspace = true
@@ -44,12 +44,12 @@ node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
project.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
schemars = { workspace = true, optional = true }
strum.workspace = true
settings.workspace = true
smol.workspace = true
strum.workspace = true
task.workspace = true
ui.workspace = true
util.workspace = true

View File

@@ -38,8 +38,8 @@ use std::{
};
use util::{fs::remove_matching, maybe, ResultExt};
pub use copilot_completion_provider::CopilotCompletionProvider;
pub use sign_in::CopilotCodeVerification;
pub use crate::copilot_completion_provider::CopilotCompletionProvider;
pub use crate::sign_in::{initiate_sign_in, CopilotCodeVerification};
actions!(
copilot,
@@ -1231,7 +1231,7 @@ mod tests {
fn disk_state(&self) -> language::DiskState {
language::DiskState::Present {
mtime: std::time::UNIX_EPOCH,
mtime: ::fs::MTime::from_seconds_and_nanos(100, 42),
}
}

View File

@@ -1,8 +1,8 @@
use crate::{Completion, Copilot};
use anyhow::Result;
use client::telemetry::Telemetry;
use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
Buffer, OffsetRangeExt, ToOffset,

View File

@@ -5,10 +5,79 @@ use gpui::{
Styled, Subscription, ViewContext,
};
use ui::{prelude::*, Button, Label, Vector, VectorName};
use workspace::ModalView;
use util::ResultExt as _;
use workspace::notifications::NotificationId;
use workspace::{ModalView, Toast, Workspace};
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
struct CopilotStartingToast;
pub fn initiate_sign_in(cx: &mut WindowContext) {
let Some(copilot) = Copilot::global(cx) else {
return;
};
let status = copilot.read(cx).status();
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
match status {
Status::Starting { task } => {
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotStartingToast>(),
"Copilot is starting...",
),
cx,
);
workspace.weak_handle()
}) else {
return;
};
cx.spawn(|mut cx| async move {
task.await;
if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() {
workspace
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
Status::Authorized => workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotStartingToast>(),
"Copilot has started!",
),
cx,
),
_ => {
workspace.dismiss_toast(
&NotificationId::unique::<CopilotStartingToast>(),
cx,
);
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
.detach_and_log_err(cx);
}
})
.log_err();
}
})
.detach();
}
_ => {
copilot.update(cx, |this, cx| this.sign_in(cx)).detach();
workspace
.update(cx, |this, cx| {
this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx));
})
.ok();
}
}
}
pub struct CopilotCodeVerification {
status: Status,
connect_clicked: bool,

View File

@@ -776,7 +776,7 @@ impl Item for ProjectDiagnosticsEditor {
}
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft
}

View File

@@ -42,10 +42,12 @@ emojis.workspace = true
file_icons.workspace = true
futures.workspace = true
fuzzy.workspace = true
fs.workspace = true
git.workspace = true
gpui.workspace = true
http_client.workspace = true
indoc.workspace = true
inline_completion.workspace = true
itertools.workspace = true
language.workspace = true
linkify.workspace = true

View File

@@ -271,6 +271,8 @@ gpui::actions!(
Hover,
Indent,
JoinLines,
KillRingCut,
KillRingYank,
LineDown,
LineUp,
MoveDown,

View File

@@ -28,7 +28,6 @@ mod hover_popover;
mod hunk_diff;
mod indent_guides;
mod inlay_hint_cache;
mod inline_completion_provider;
pub mod items;
mod linked_editing_ranges;
mod lsp_ext;
@@ -75,7 +74,7 @@ use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
FocusableView, FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, ModelContext, MouseButton, PaintQuad, ParentElement, Pixels, Render,
ScrollStrategy, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
@@ -87,7 +86,8 @@ pub(crate) use hunk_diff::HoveredHunk;
use hunk_diff::{diff_hunk_to_display, ExpandedHunks};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion_provider::*;
pub use inline_completion::Direction;
use inline_completion::{InlayProposal, InlineCompletionProvider, InlineCompletionProviderHandle};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
@@ -273,12 +273,6 @@ enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
enum InputComposition {}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Direction {
Prev,
Next,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Navigated {
Yes,
@@ -7370,7 +7364,7 @@ impl Editor {
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
pub fn cut_common(&mut self, cx: &mut ViewContext<Self>) -> ClipboardItem {
let mut text = String::new();
let buffer = self.buffer.read(cx).snapshot(cx);
let mut selections = self.selections.all::<Point>(cx);
@@ -7414,11 +7408,38 @@ impl Editor {
s.select(selections);
});
this.insert("", cx);
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
text,
clipboard_selections,
));
});
ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
}
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
let item = self.cut_common(cx);
cx.write_to_clipboard(item);
}
pub fn kill_ring_cut(&mut self, _: &KillRingCut, cx: &mut ViewContext<Self>) {
self.change_selections(None, cx, |s| {
s.move_with(|snapshot, sel| {
if sel.is_empty() {
sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
}
});
});
let item = self.cut_common(cx);
cx.set_global(KillRing(item))
}
pub fn kill_ring_yank(&mut self, _: &KillRingYank, cx: &mut ViewContext<Self>) {
let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
(kill_ring.text().to_string(), kill_ring.metadata_json())
} else {
return;
}
} else {
return;
};
self.do_paste(&text, metadata, false, cx);
}
pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
@@ -13790,7 +13811,9 @@ impl CodeActionProvider for Model<Project> {
range: Range<text::Anchor>,
cx: &mut WindowContext,
) -> Task<Result<Vec<CodeAction>>> {
self.update(cx, |project, cx| project.code_actions(buffer, range, cx))
self.update(cx, |project, cx| {
project.code_actions(buffer, range, None, cx)
})
}
fn apply_code_action(
@@ -14434,15 +14457,16 @@ impl ViewInputHandler for Editor {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted_range: &mut Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) -> Option<String> {
Some(
self.buffer
.read(cx)
.read(cx)
.text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
.collect(),
)
let snapshot = self.buffer.read(cx).read(cx);
let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
if (start.0..end.0) != range_utf16 {
adjusted_range.replace(start.0..end.0);
}
Some(snapshot.text_for_range(start..end).collect())
}
fn selected_text_range(
@@ -15150,4 +15174,7 @@ fn check_multiline_range(buffer: &Buffer, range: Range<usize>) -> Range<usize> {
}
}
pub struct KillRing(ClipboardItem);
impl Global for KillRing {}
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);

View File

@@ -217,6 +217,8 @@ impl EditorElement {
register_action(view, cx, Editor::transpose);
register_action(view, cx, Editor::rewrap);
register_action(view, cx, Editor::cut);
register_action(view, cx, Editor::kill_ring_cut);
register_action(view, cx, Editor::kill_ring_yank);
register_action(view, cx, Editor::copy);
register_action(view, cx, Editor::paste);
register_action(view, cx, Editor::undo);

View File

@@ -841,7 +841,7 @@ impl Item for Editor {
self.pixel_position_of_newest_cursor
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
if self.show_breadcrumbs {
ToolbarItemLocation::PrimaryLeft
} else {
@@ -1618,15 +1618,14 @@ fn path_for_file<'a>(
#[cfg(test)]
mod tests {
use crate::editor_tests::init_test;
use fs::Fs;
use super::*;
use fs::MTime;
use gpui::{AppContext, VisualTestContext};
use language::{LanguageMatcher, TestFile};
use project::FakeFs;
use std::{
path::{Path, PathBuf},
time::SystemTime,
};
use std::path::{Path, PathBuf};
#[gpui::test]
fn test_path_for_file(cx: &mut AppContext) {
@@ -1679,9 +1678,7 @@ mod tests {
async fn test_deserialize(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let now = SystemTime::now();
let fs = FakeFs::new(cx.executor());
fs.set_next_mtime(now);
fs.insert_file("/file.rs", Default::default()).await;
// Test case 1: Deserialize with path and contents
@@ -1690,12 +1687,18 @@ mod tests {
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
let item_id = 1234 as ItemId;
let mtime = fs
.metadata(Path::new("/file.rs"))
.await
.unwrap()
.unwrap()
.mtime;
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")),
contents: Some("fn main() {}".to_string()),
language: Some("Rust".to_string()),
mtime: Some(now),
mtime: Some(mtime),
};
DB.save_serialized_editor(item_id, workspace_id, serialized_editor.clone())
@@ -1792,9 +1795,7 @@ mod tests {
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
let item_id = 9345 as ItemId;
let old_mtime = now
.checked_sub(std::time::Duration::from_secs(60 * 60 * 24))
.unwrap();
let old_mtime = MTime::from_seconds_and_nanos(0, 50);
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")),
contents: Some("fn main() {}".to_string()),

View File

@@ -1,8 +1,8 @@
use anyhow::Result;
use db::sqlez::bindable::{Bind, Column, StaticColumnCount};
use db::sqlez::statement::Statement;
use fs::MTime;
use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use db::sqlez_macros::sql;
use db::{define_connection, query};
@@ -14,7 +14,7 @@ pub(crate) struct SerializedEditor {
pub(crate) abs_path: Option<PathBuf>,
pub(crate) contents: Option<String>,
pub(crate) language: Option<String>,
pub(crate) mtime: Option<SystemTime>,
pub(crate) mtime: Option<MTime>,
}
impl StaticColumnCount for SerializedEditor {
@@ -29,16 +29,13 @@ impl Bind for SerializedEditor {
let start_index = statement.bind(&self.contents, start_index)?;
let start_index = statement.bind(&self.language, start_index)?;
let mtime = self.mtime.and_then(|mtime| {
mtime
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| (duration.as_secs() as i64, duration.subsec_nanos() as i32))
});
let start_index = match mtime {
let start_index = match self
.mtime
.and_then(|mtime| mtime.to_seconds_and_nanos_for_persistence())
{
Some((seconds, nanos)) => {
let start_index = statement.bind(&seconds, start_index)?;
statement.bind(&nanos, start_index)?
let start_index = statement.bind(&(seconds as i64), start_index)?;
statement.bind(&(nanos as i32), start_index)?
}
None => {
let start_index = statement.bind::<Option<i64>>(&None, start_index)?;
@@ -64,7 +61,7 @@ impl Column for SerializedEditor {
let mtime = mtime_seconds
.zip(mtime_nanos)
.map(|(seconds, nanos)| UNIX_EPOCH + Duration::new(seconds as u64, nanos as u32));
.map(|(seconds, nanos)| MTime::from_seconds_and_nanos(seconds as u64, nanos as u32));
let editor = Self {
abs_path,
@@ -280,12 +277,11 @@ mod tests {
assert_eq!(have, serialized_editor);
// Storing and retrieving mtime
let now = SystemTime::now();
let serialized_editor = SerializedEditor {
abs_path: None,
contents: None,
language: None,
mtime: Some(now),
mtime: Some(MTime::from_seconds_and_nanos(100, 42)),
};
DB.save_serialized_editor(1234, workspace_id, serialized_editor.clone())

View File

@@ -30,9 +30,10 @@ languages.workspace = true
node_runtime.workspace = true
open_ai.workspace = true
project.workspace = true
reqwest_client.workspace = true
semantic_index.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
reqwest_client.workspace = true
util.workspace = true

View File

@@ -27,7 +27,7 @@ use std::time::Duration;
use std::{
fs,
path::Path,
process::{exit, Command, Stdio},
process::{exit, Stdio},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
@@ -667,7 +667,7 @@ async fn fetch_eval_repo(
return;
}
if !repo_dir.join(".git").exists() {
let init_output = Command::new("git")
let init_output = util::command::new_std_command("git")
.current_dir(&repo_dir)
.args(&["init"])
.output()
@@ -682,13 +682,13 @@ async fn fetch_eval_repo(
}
}
let url = format!("https://github.com/{}.git", repo);
Command::new("git")
util::command::new_std_command("git")
.current_dir(&repo_dir)
.args(&["remote", "add", "-f", "origin", &url])
.stdin(Stdio::null())
.output()
.unwrap();
let fetch_output = Command::new("git")
let fetch_output = util::command::new_std_command("git")
.current_dir(&repo_dir)
.args(&["fetch", "--depth", "1", "origin", &sha])
.stdin(Stdio::null())
@@ -703,7 +703,7 @@ async fn fetch_eval_repo(
);
return;
}
let checkout_output = Command::new("git")
let checkout_output = util::command::new_std_command("git")
.current_dir(&repo_dir)
.args(&["checkout", &sha])
.output()

View File

@@ -24,10 +24,12 @@ http_client.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
parking_lot.workspace = true
semantic_version.workspace = true
serde.workspace = true
serde_json.workspace = true
toml.workspace = true
util.workspace = true
wasm-encoder.workspace = true
wasmparser.workspace = true
wit-component.workspace = true

View File

@@ -1,4 +1,5 @@
pub mod extension_builder;
mod extension_host_proxy;
mod extension_manifest;
mod types;
@@ -9,13 +10,19 @@ use ::lsp::LanguageServerName;
use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait;
use fs::normalize_path;
use gpui::Task;
use gpui::{AppContext, Task};
use language::LanguageName;
use semantic_version::SemanticVersion;
pub use crate::extension_host_proxy::*;
pub use crate::extension_manifest::*;
pub use crate::types::*;
/// Initializes the `extension` crate.
pub fn init(cx: &mut AppContext) {
ExtensionHostProxy::default_global(cx);
}
#[async_trait]
pub trait WorktreeDelegate: Send + Sync + 'static {
fn id(&self) -> u64;
@@ -25,6 +32,10 @@ pub trait WorktreeDelegate: Send + Sync + 'static {
async fn shell_env(&self) -> Vec<(String, String)>;
}
pub trait ProjectDelegate: Send + Sync + 'static {
fn worktree_ids(&self) -> Vec<u64>;
}
pub trait KeyValueStoreDelegate: Send + Sync + 'static {
fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
}
@@ -87,6 +98,12 @@ pub trait Extension: Send + Sync + 'static {
worktree: Option<Arc<dyn WorktreeDelegate>>,
) -> Result<SlashCommandOutput>;
async fn context_server_command(
&self,
context_server_id: Arc<str>,
project: Arc<dyn ProjectDelegate>,
) -> Result<Command>;
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
async fn index_docs(

View File

@@ -11,7 +11,7 @@ use serde::Deserialize;
use std::{
env, fs, mem,
path::{Path, PathBuf},
process::{Command, Stdio},
process::Stdio,
sync::Arc,
};
use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _};
@@ -130,7 +130,7 @@ impl ExtensionBuilder {
"compiling Rust crate for extension {}",
extension_dir.display()
);
let output = Command::new("cargo")
let output = util::command::new_std_command("cargo")
.args(["build", "--target", RUST_TARGET])
.args(options.release.then_some("--release"))
.arg("--target-dir")
@@ -237,7 +237,7 @@ impl ExtensionBuilder {
let scanner_path = src_path.join("scanner.c");
log::info!("compiling {grammar_name} parser");
let clang_output = Command::new(&clang_path)
let clang_output = util::command::new_std_command(&clang_path)
.args(["-fPIC", "-shared", "-Os"])
.arg(format!("-Wl,--export=tree_sitter_{grammar_name}"))
.arg("-o")
@@ -264,7 +264,7 @@ impl ExtensionBuilder {
let git_dir = directory.join(".git");
if directory.exists() {
let remotes_output = Command::new("git")
let remotes_output = util::command::new_std_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["remote", "-v"])
@@ -287,7 +287,7 @@ impl ExtensionBuilder {
fs::create_dir_all(directory).with_context(|| {
format!("failed to create grammar directory {}", directory.display(),)
})?;
let init_output = Command::new("git")
let init_output = util::command::new_std_command("git")
.arg("init")
.current_dir(directory)
.output()?;
@@ -298,7 +298,7 @@ impl ExtensionBuilder {
);
}
let remote_add_output = Command::new("git")
let remote_add_output = util::command::new_std_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["remote", "add", "origin", url])
@@ -312,14 +312,14 @@ impl ExtensionBuilder {
}
}
let fetch_output = Command::new("git")
let fetch_output = util::command::new_std_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["fetch", "--depth", "1", "origin", rev])
.output()
.context("failed to execute `git fetch`")?;
let checkout_output = Command::new("git")
let checkout_output = util::command::new_std_command("git")
.arg("--git-dir")
.arg(&git_dir)
.args(["checkout", rev])
@@ -346,7 +346,7 @@ impl ExtensionBuilder {
}
fn install_rust_wasm_target_if_needed(&self) -> Result<()> {
let rustc_output = Command::new("rustc")
let rustc_output = util::command::new_std_command("rustc")
.arg("--print")
.arg("sysroot")
.output()
@@ -363,7 +363,7 @@ impl ExtensionBuilder {
return Ok(());
}
let output = Command::new("rustup")
let output = util::command::new_std_command("rustup")
.args(["target", "add", RUST_TARGET])
.stderr(Stdio::piped())
.stdout(Stdio::inherit())

View File

@@ -0,0 +1,324 @@
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use fs::Fs;
use gpui::{AppContext, Global, ReadGlobal, SharedString, Task};
use language::{LanguageMatcher, LanguageName, LanguageServerBinaryStatus, LoadedLanguage};
use lsp::LanguageServerName;
use parking_lot::RwLock;
use crate::{Extension, SlashCommand};
#[derive(Default)]
struct GlobalExtensionHostProxy(Arc<ExtensionHostProxy>);
impl Global for GlobalExtensionHostProxy {}
/// A proxy for interacting with the extension host.
///
/// This object implements each of the individual proxy types so that their
/// methods can be called directly on it.
#[derive(Default)]
pub struct ExtensionHostProxy {
theme_proxy: RwLock<Option<Arc<dyn ExtensionThemeProxy>>>,
grammar_proxy: RwLock<Option<Arc<dyn ExtensionGrammarProxy>>>,
language_proxy: RwLock<Option<Arc<dyn ExtensionLanguageProxy>>>,
language_server_proxy: RwLock<Option<Arc<dyn ExtensionLanguageServerProxy>>>,
snippet_proxy: RwLock<Option<Arc<dyn ExtensionSnippetProxy>>>,
slash_command_proxy: RwLock<Option<Arc<dyn ExtensionSlashCommandProxy>>>,
context_server_proxy: RwLock<Option<Arc<dyn ExtensionContextServerProxy>>>,
indexed_docs_provider_proxy: RwLock<Option<Arc<dyn ExtensionIndexedDocsProviderProxy>>>,
}
impl ExtensionHostProxy {
/// Returns the global [`ExtensionHostProxy`].
pub fn global(cx: &AppContext) -> Arc<Self> {
GlobalExtensionHostProxy::global(cx).0.clone()
}
/// Returns the global [`ExtensionHostProxy`].
///
/// Inserts a default [`ExtensionHostProxy`] if one does not yet exist.
pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
cx.default_global::<GlobalExtensionHostProxy>().0.clone()
}
pub fn new() -> Self {
Self {
theme_proxy: RwLock::default(),
grammar_proxy: RwLock::default(),
language_proxy: RwLock::default(),
language_server_proxy: RwLock::default(),
snippet_proxy: RwLock::default(),
slash_command_proxy: RwLock::default(),
context_server_proxy: RwLock::default(),
indexed_docs_provider_proxy: RwLock::default(),
}
}
pub fn register_theme_proxy(&self, proxy: impl ExtensionThemeProxy) {
self.theme_proxy.write().replace(Arc::new(proxy));
}
pub fn register_grammar_proxy(&self, proxy: impl ExtensionGrammarProxy) {
self.grammar_proxy.write().replace(Arc::new(proxy));
}
pub fn register_language_proxy(&self, proxy: impl ExtensionLanguageProxy) {
self.language_proxy.write().replace(Arc::new(proxy));
}
pub fn register_language_server_proxy(&self, proxy: impl ExtensionLanguageServerProxy) {
self.language_server_proxy.write().replace(Arc::new(proxy));
}
pub fn register_snippet_proxy(&self, proxy: impl ExtensionSnippetProxy) {
self.snippet_proxy.write().replace(Arc::new(proxy));
}
pub fn register_slash_command_proxy(&self, proxy: impl ExtensionSlashCommandProxy) {
self.slash_command_proxy.write().replace(Arc::new(proxy));
}
pub fn register_context_server_proxy(&self, proxy: impl ExtensionContextServerProxy) {
self.context_server_proxy.write().replace(Arc::new(proxy));
}
pub fn register_indexed_docs_provider_proxy(
&self,
proxy: impl ExtensionIndexedDocsProviderProxy,
) {
self.indexed_docs_provider_proxy
.write()
.replace(Arc::new(proxy));
}
}
pub trait ExtensionThemeProxy: Send + Sync + 'static {
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>>;
fn remove_user_themes(&self, themes: Vec<SharedString>);
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>>;
fn reload_current_theme(&self, cx: &mut AppContext);
}
impl ExtensionThemeProxy for ExtensionHostProxy {
fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
let Some(proxy) = self.theme_proxy.read().clone() else {
return Task::ready(Ok(Vec::new()));
};
proxy.list_theme_names(theme_path, fs)
}
fn remove_user_themes(&self, themes: Vec<SharedString>) {
let Some(proxy) = self.theme_proxy.read().clone() else {
return;
};
proxy.remove_user_themes(themes)
}
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
let Some(proxy) = self.theme_proxy.read().clone() else {
return Task::ready(Ok(()));
};
proxy.load_user_theme(theme_path, fs)
}
fn reload_current_theme(&self, cx: &mut AppContext) {
let Some(proxy) = self.theme_proxy.read().clone() else {
return;
};
proxy.reload_current_theme(cx)
}
}
pub trait ExtensionGrammarProxy: Send + Sync + 'static {
fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>);
}
impl ExtensionGrammarProxy for ExtensionHostProxy {
fn register_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
let Some(proxy) = self.grammar_proxy.read().clone() else {
return;
};
proxy.register_grammars(grammars)
}
}
pub trait ExtensionLanguageProxy: Send + Sync + 'static {
fn register_language(
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
);
fn remove_languages(
&self,
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
);
}
impl ExtensionLanguageProxy for ExtensionHostProxy {
fn register_language(
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
let Some(proxy) = self.language_proxy.read().clone() else {
return;
};
proxy.register_language(language, grammar, matcher, load)
}
fn remove_languages(
&self,
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
let Some(proxy) = self.language_proxy.read().clone() else {
return;
};
proxy.remove_languages(languages_to_remove, grammars_to_remove)
}
}
pub trait ExtensionLanguageServerProxy: Send + Sync + 'static {
fn register_language_server(
&self,
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language: LanguageName,
);
fn remove_language_server(
&self,
language: &LanguageName,
language_server_id: &LanguageServerName,
);
fn update_language_server_status(
&self,
language_server_id: LanguageServerName,
status: LanguageServerBinaryStatus,
);
}
impl ExtensionLanguageServerProxy for ExtensionHostProxy {
fn register_language_server(
&self,
extension: Arc<dyn Extension>,
language_server_id: LanguageServerName,
language: LanguageName,
) {
let Some(proxy) = self.language_server_proxy.read().clone() else {
return;
};
proxy.register_language_server(extension, language_server_id, language)
}
fn remove_language_server(
&self,
language: &LanguageName,
language_server_id: &LanguageServerName,
) {
let Some(proxy) = self.language_server_proxy.read().clone() else {
return;
};
proxy.remove_language_server(language, language_server_id)
}
fn update_language_server_status(
&self,
language_server_id: LanguageServerName,
status: LanguageServerBinaryStatus,
) {
let Some(proxy) = self.language_server_proxy.read().clone() else {
return;
};
proxy.update_language_server_status(language_server_id, status)
}
}
pub trait ExtensionSnippetProxy: Send + Sync + 'static {
fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>;
}
impl ExtensionSnippetProxy for ExtensionHostProxy {
fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
let Some(proxy) = self.snippet_proxy.read().clone() else {
return Ok(());
};
proxy.register_snippet(path, snippet_contents)
}
}
pub trait ExtensionSlashCommandProxy: Send + Sync + 'static {
fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand);
}
impl ExtensionSlashCommandProxy for ExtensionHostProxy {
fn register_slash_command(&self, extension: Arc<dyn Extension>, command: SlashCommand) {
let Some(proxy) = self.slash_command_proxy.read().clone() else {
return;
};
proxy.register_slash_command(extension, command)
}
}
pub trait ExtensionContextServerProxy: Send + Sync + 'static {
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
server_id: Arc<str>,
cx: &mut AppContext,
);
}
impl ExtensionContextServerProxy for ExtensionHostProxy {
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
server_id: Arc<str>,
cx: &mut AppContext,
) {
let Some(proxy) = self.context_server_proxy.read().clone() else {
return;
};
proxy.register_context_server(extension, server_id, cx)
}
}
pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static {
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>);
}
impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else {
return;
};
proxy.register_indexed_docs_provider(extension, provider_id)
}
}

View File

@@ -10,6 +10,7 @@ pub use slash_command::*;
pub type EnvVars = Vec<(String, String)>;
/// A command.
#[derive(Debug)]
pub struct Command {
/// The command to execute.
pub command: String,

View File

@@ -34,6 +34,7 @@ lsp.workspace = true
node_runtime.workspace = true
paths.workspace = true
project.workspace = true
remote.workspace = true
release_channel.workspace = true
schemars.workspace = true
semantic_version.workspace = true
@@ -42,6 +43,7 @@ serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
task.workspace = true
tempfile.workspace = true
toml.workspace = true
url.workspace = true
util.workspace = true
@@ -55,7 +57,9 @@ env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
language_extension.workspace = true
parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true
theme = { workspace = true, features = ["test-support"] }
theme_extension.workspace = true

View File

@@ -1,19 +1,22 @@
pub mod extension_lsp_adapter;
pub mod extension_settings;
pub mod headless_host;
pub mod wasm_host;
#[cfg(test)]
mod extension_store_test;
use crate::extension_lsp_adapter::ExtensionLspAdapter;
use anyhow::{anyhow, bail, Context as _, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
use collections::{btree_map, BTreeMap, HashSet};
use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
use collections::{btree_map, BTreeMap, HashMap, HashSet};
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use extension::Extension;
pub use extension::ExtensionManifest;
use extension::{
ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy,
ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy,
};
use fs::{Fs, RemoveOptions};
use futures::{
channel::{
@@ -24,18 +27,18 @@ use futures::{
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
};
use gpui::{
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext,
SharedString, Task, WeakModel,
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
WeakModel,
};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
QUERY_FILENAME_PREFIXES,
};
use lsp::LanguageServerName;
use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks;
use release_channel::ReleaseChannel;
use remote::SshRemoteClient;
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use settings::Settings;
@@ -94,76 +97,8 @@ pub fn is_version_compatible(
true
}
pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
fn remove_user_themes(&self, _themes: Vec<SharedString>) {}
fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc<dyn Fs>) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn list_theme_names(
&self,
_theme_path: PathBuf,
_fs: Arc<dyn Fs>,
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
}
fn reload_current_theme(&self, _cx: &mut AppContext) {}
fn register_language(
&self,
_language: LanguageName,
_grammar: Option<Arc<str>>,
_matcher: language::LanguageMatcher,
_load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
}
fn register_lsp_adapter(&self, _language: LanguageName, _adapter: ExtensionLspAdapter) {}
fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {}
fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
fn remove_languages(
&self,
_languages_to_remove: &[LanguageName],
_grammars_to_remove: &[Arc<str>],
) {
}
fn register_slash_command(
&self,
_extension: Arc<dyn Extension>,
_command: extension::SlashCommand,
) {
}
fn register_context_server(
&self,
_id: Arc<str>,
_extension: WasmExtension,
_cx: &mut AppContext,
) {
}
fn register_docs_provider(&self, _extension: Arc<dyn Extension>, _provider_id: Arc<str>) {}
fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> {
Ok(())
}
fn update_lsp_status(
&self,
_server_name: lsp::LanguageServerName,
_status: language::LanguageServerBinaryStatus,
) {
}
}
pub struct ExtensionStore {
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
pub proxy: Arc<ExtensionHostProxy>,
pub builder: Arc<ExtensionBuilder>,
pub extension_index: ExtensionIndex,
pub fs: Arc<dyn Fs>,
@@ -178,6 +113,8 @@ pub struct ExtensionStore {
pub wasm_host: Arc<WasmHost>,
pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
pub tasks: Vec<Task<()>>,
pub ssh_clients: HashMap<String, WeakModel<SshRemoteClient>>,
pub ssh_registered_tx: UnboundedSender<()>,
}
#[derive(Clone, Copy)]
@@ -231,7 +168,7 @@ pub struct ExtensionIndexLanguageEntry {
actions!(zed, [ReloadExtensions]);
pub fn init(
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
extension_host_proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>,
client: Arc<Client>,
node_runtime: NodeRuntime,
@@ -243,7 +180,7 @@ pub fn init(
ExtensionStore::new(
paths::extensions_dir().clone(),
None,
registration_hooks,
extension_host_proxy,
fs,
client.http_client().clone(),
client.http_client().clone(),
@@ -275,7 +212,7 @@ impl ExtensionStore {
pub fn new(
extensions_dir: PathBuf,
build_dir: Option<PathBuf>,
extension_api: Arc<dyn ExtensionRegistrationHooks>,
extension_host_proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>,
http_client: Arc<HttpClientWithUrl>,
builder_client: Arc<dyn HttpClient>,
@@ -289,8 +226,9 @@ impl ExtensionStore {
let index_path = extensions_dir.join("index.json");
let (reload_tx, mut reload_rx) = unbounded();
let (connection_registered_tx, mut connection_registered_rx) = unbounded();
let mut this = Self {
registration_hooks: extension_api.clone(),
proxy: extension_host_proxy.clone(),
extension_index: Default::default(),
installed_dir,
index_path,
@@ -302,7 +240,7 @@ impl ExtensionStore {
fs.clone(),
http_client.clone(),
node_runtime,
extension_api,
extension_host_proxy,
work_dir,
cx,
),
@@ -312,6 +250,9 @@ impl ExtensionStore {
telemetry,
reload_tx,
tasks: Vec::new(),
ssh_clients: HashMap::default(),
ssh_registered_tx: connection_registered_tx,
};
// The extensions store maintains an index file, which contains a complete
@@ -337,7 +278,10 @@ impl ExtensionStore {
if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) =
(index_metadata, extensions_metadata)
{
if index_metadata.mtime > extensions_metadata.mtime {
if index_metadata
.mtime
.bad_is_greater_than(extensions_metadata.mtime)
{
extension_index_needs_rebuild = false;
}
}
@@ -386,6 +330,14 @@ impl ExtensionStore {
.await;
index_changed = false;
}
Self::update_ssh_clients(&this, &mut cx).await?;
}
_ = connection_registered_rx.next() => {
debounce_timer = cx
.background_executor()
.timer(RELOAD_DEBOUNCE_DURATION)
.fuse();
}
extension_id = reload_rx.next() => {
let Some(extension_id) = extension_id else { break; };
@@ -1089,16 +1041,16 @@ impl ExtensionStore {
grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
for (language_server_name, config) in extension.manifest.language_servers.iter() {
for language in config.languages() {
self.registration_hooks
.remove_lsp_adapter(&language, language_server_name);
self.proxy
.remove_language_server(&language, language_server_name);
}
}
}
self.wasm_extensions
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
self.registration_hooks.remove_user_themes(themes_to_remove);
self.registration_hooks
self.proxy.remove_user_themes(themes_to_remove);
self.proxy
.remove_languages(&languages_to_remove, &grammars_to_remove);
let languages_to_add = new_index
@@ -1133,8 +1085,7 @@ impl ExtensionStore {
}));
}
self.registration_hooks
.register_wasm_grammars(grammars_to_add);
self.proxy.register_grammars(grammars_to_add);
for (language_name, language) in languages_to_add {
let mut language_path = self.installed_dir.clone();
@@ -1142,7 +1093,7 @@ impl ExtensionStore {
Path::new(language.extension.as_ref()),
language.path.as_path(),
]);
self.registration_hooks.register_language(
self.proxy.register_language(
language_name.clone(),
language.grammar.clone(),
language.matcher.clone(),
@@ -1172,7 +1123,7 @@ impl ExtensionStore {
let fs = self.fs.clone();
let wasm_host = self.wasm_host.clone();
let root_dir = self.installed_dir.clone();
let api = self.registration_hooks.clone();
let proxy = self.proxy.clone();
let extension_entries = extensions_to_load
.iter()
.filter_map(|name| new_index.extensions.get(name).cloned())
@@ -1188,13 +1139,17 @@ impl ExtensionStore {
let fs = fs.clone();
async move {
for theme_path in themes_to_add.into_iter() {
api.load_user_theme(theme_path, fs.clone()).await.log_err();
proxy
.load_user_theme(theme_path, fs.clone())
.await
.log_err();
}
for snippets_path in &snippets_to_add {
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
{
api.register_snippets(snippets_path, &snippets_contents)
proxy
.register_snippet(snippets_path, &snippets_contents)
.log_err();
}
}
@@ -1235,19 +1190,16 @@ impl ExtensionStore {
for (language_server_id, language_server_config) in &manifest.language_servers {
for language in language_server_config.languages() {
this.registration_hooks.register_lsp_adapter(
this.proxy.register_language_server(
extension.clone(),
language_server_id.clone(),
language.clone(),
ExtensionLspAdapter {
extension: extension.clone(),
language_server_id: language_server_id.clone(),
language_name: language.clone(),
},
);
}
}
for (slash_command_name, slash_command) in &manifest.slash_commands {
this.registration_hooks.register_slash_command(
this.proxy.register_slash_command(
extension.clone(),
extension::SlashCommand {
name: slash_command_name.to_string(),
@@ -1262,21 +1214,18 @@ impl ExtensionStore {
}
for (id, _context_server_entry) in &manifest.context_servers {
this.registration_hooks.register_context_server(
id.clone(),
wasm_extension.clone(),
cx,
);
this.proxy
.register_context_server(extension.clone(), id.clone(), cx);
}
for (provider_id, _provider) in &manifest.indexed_docs_providers {
this.registration_hooks
.register_docs_provider(extension.clone(), provider_id.clone());
this.proxy
.register_indexed_docs_provider(extension.clone(), provider_id.clone());
}
}
this.wasm_extensions.extend(wasm_extensions);
this.registration_hooks.reload_current_theme(cx);
this.proxy.reload_current_theme(cx);
})
.ok();
})
@@ -1287,7 +1236,7 @@ impl ExtensionStore {
let work_dir = self.wasm_host.work_dir.clone();
let extensions_dir = self.installed_dir.clone();
let index_path = self.index_path.clone();
let extension_api = self.registration_hooks.clone();
let proxy = self.proxy.clone();
cx.background_executor().spawn(async move {
let start_time = Instant::now();
let mut index = ExtensionIndex::default();
@@ -1313,7 +1262,7 @@ impl ExtensionStore {
fs.clone(),
extension_dir,
&mut index,
extension_api.clone(),
proxy.clone(),
)
.await
.log_err();
@@ -1336,7 +1285,7 @@ impl ExtensionStore {
fs: Arc<dyn Fs>,
extension_dir: PathBuf,
index: &mut ExtensionIndex,
extension_api: Arc<dyn ExtensionRegistrationHooks>,
proxy: Arc<ExtensionHostProxy>,
) -> Result<()> {
let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
let extension_id = extension_manifest.id.clone();
@@ -1388,7 +1337,7 @@ impl ExtensionStore {
continue;
};
let Some(theme_families) = extension_api
let Some(theme_families) = proxy
.list_theme_names(theme_path.clone(), fs.clone())
.await
.log_err()
@@ -1431,6 +1380,144 @@ impl ExtensionStore {
Ok(())
}
fn prepare_remote_extension(
&mut self,
extension_id: Arc<str>,
tmp_dir: PathBuf,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let src_dir = self.extensions_dir().join(extension_id.as_ref());
let Some(loaded_extension) = self.extension_index.extensions.get(&extension_id).cloned()
else {
return Task::ready(Err(anyhow!("extension no longer installed")));
};
let fs = self.fs.clone();
cx.background_executor().spawn(async move {
for well_known_path in ["extension.toml", "extension.json", "extension.wasm"] {
if fs.is_file(&src_dir.join(well_known_path)).await {
fs.copy_file(
&src_dir.join(well_known_path),
&tmp_dir.join(well_known_path),
fs::CopyOptions::default(),
)
.await?
}
}
for language_path in loaded_extension.manifest.languages.iter() {
if fs
.is_file(&src_dir.join(language_path).join("config.toml"))
.await
{
fs.create_dir(&tmp_dir.join(language_path)).await?;
fs.copy_file(
&src_dir.join(language_path).join("config.toml"),
&tmp_dir.join(language_path).join("config.toml"),
fs::CopyOptions::default(),
)
.await?
}
}
Ok(())
})
}
async fn sync_extensions_over_ssh(
this: &WeakModel<Self>,
client: WeakModel<SshRemoteClient>,
cx: &mut AsyncAppContext,
) -> Result<()> {
let extensions = this.update(cx, |this, _cx| {
this.extension_index
.extensions
.iter()
.filter_map(|(id, entry)| {
if entry.manifest.language_servers.is_empty() {
return None;
}
Some(proto::Extension {
id: id.to_string(),
version: entry.manifest.version.to_string(),
dev: entry.dev,
})
})
.collect()
})?;
let response = client
.update(cx, |client, _cx| {
client
.proto_client()
.request(proto::SyncExtensions { extensions })
})?
.await?;
for missing_extension in response.missing_extensions.into_iter() {
let tmp_dir = tempfile::tempdir()?;
this.update(cx, |this, cx| {
this.prepare_remote_extension(
missing_extension.id.clone().into(),
tmp_dir.path().to_owned(),
cx,
)
})?
.await?;
let dest_dir = PathBuf::from(&response.tmp_dir).join(missing_extension.clone().id);
log::info!("Uploading extension {}", missing_extension.clone().id);
client
.update(cx, |client, cx| {
client.upload_directory(tmp_dir.path().to_owned(), dest_dir.clone(), cx)
})?
.await?;
client
.update(cx, |client, _cx| {
client.proto_client().request(proto::InstallExtension {
tmp_dir: dest_dir.to_string_lossy().to_string(),
extension: Some(missing_extension),
})
})?
.await?;
}
anyhow::Ok(())
}
pub async fn update_ssh_clients(
this: &WeakModel<Self>,
cx: &mut AsyncAppContext,
) -> Result<()> {
let clients = this.update(cx, |this, _cx| {
this.ssh_clients.retain(|_k, v| v.upgrade().is_some());
this.ssh_clients.values().cloned().collect::<Vec<_>>()
})?;
for client in clients {
Self::sync_extensions_over_ssh(&this, client, cx)
.await
.log_err();
}
anyhow::Ok(())
}
pub fn register_ssh_client(
&mut self,
client: Model<SshRemoteClient>,
cx: &mut ModelContext<Self>,
) {
let connection_options = client.read(cx).connection_options();
if self.ssh_clients.contains_key(&connection_options.ssh_url()) {
return;
}
self.ssh_clients
.insert(connection_options.ssh_url(), client.downgrade());
self.ssh_registered_tx.unbounded_send(()).ok();
}
}
fn load_plugin_queries(root_path: &Path) -> LanguageQueries {

View File

@@ -1,17 +1,16 @@
use crate::extension_lsp_adapter::ExtensionLspAdapter;
use crate::{
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore,
GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION,
};
use anyhow::Result;
use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{BackgroundExecutor, Context, SemanticVersion, SharedString, Task, TestAppContext};
use gpui::{Context, SemanticVersion, TestAppContext};
use http_client::{FakeHttpClient, Response};
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage};
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus};
use lsp::LanguageServerName;
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
@@ -28,84 +27,6 @@ use std::{
use theme::ThemeRegistry;
use util::test::temp_tree;
use crate::ExtensionRegistrationHooks;
struct TestExtensionRegistrationHooks {
executor: BackgroundExecutor,
language_registry: Arc<LanguageRegistry>,
theme_registry: Arc<ThemeRegistry>,
}
impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks {
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
self.executor.spawn(async move {
let themes = theme::read_user_theme(&path, fs).await?;
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
})
}
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
let theme_registry = self.theme_registry.clone();
self.executor
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
}
fn remove_user_themes(&self, themes: Vec<SharedString>) {
self.theme_registry.remove_user_themes(&themes);
}
fn register_language(
&self,
language: language::LanguageName,
grammar: Option<Arc<str>>,
matcher: language::LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
self.language_registry
.register_language(language, grammar, matcher, load)
}
fn remove_languages(
&self,
languages_to_remove: &[language::LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.language_registry
.remove_languages(&languages_to_remove, &grammars_to_remove);
}
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
self.language_registry.register_wasm_grammars(grammars)
}
fn register_lsp_adapter(
&self,
language_name: language::LanguageName,
adapter: ExtensionLspAdapter,
) {
self.language_registry
.register_lsp_adapter(language_name, Arc::new(adapter));
}
fn update_lsp_status(
&self,
server_name: lsp::LanguageServerName,
status: LanguageServerBinaryStatus,
) {
self.language_registry
.update_lsp_status(server_name, status);
}
fn remove_lsp_adapter(
&self,
language_name: &language::LanguageName,
server_name: &lsp::LanguageServerName,
) {
self.language_registry
.remove_lsp_adapter(language_name, server_name);
}
}
#[cfg(test)]
#[ctor::ctor]
fn init_logger() {
@@ -337,20 +258,18 @@ async fn test_extension_store(cx: &mut TestAppContext) {
.collect(),
};
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
let proxy = Arc::new(ExtensionHostProxy::new());
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let registration_hooks = Arc::new(TestExtensionRegistrationHooks {
executor: cx.executor(),
language_registry: language_registry.clone(),
theme_registry: theme_registry.clone(),
});
theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
language_extension::init(proxy.clone(), language_registry.clone());
let node_runtime = NodeRuntime::unavailable();
let store = cx.new_model(|cx| {
ExtensionStore::new(
PathBuf::from("/the-extension-dir"),
None,
registration_hooks.clone(),
proxy.clone(),
fs.clone(),
http_client.clone(),
http_client.clone(),
@@ -475,7 +394,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
ExtensionStore::new(
PathBuf::from("/the-extension-dir"),
None,
registration_hooks,
proxy,
fs.clone(),
http_client.clone(),
http_client.clone(),
@@ -558,13 +477,11 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
let project = Project::test(fs.clone(), [project_dir.as_path()], cx).await;
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
let proxy = Arc::new(ExtensionHostProxy::new());
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let registration_hooks = Arc::new(TestExtensionRegistrationHooks {
executor: cx.executor(),
language_registry: language_registry.clone(),
theme_registry: theme_registry.clone(),
});
theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor());
let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
language_extension::init(proxy.clone(), language_registry.clone());
let node_runtime = NodeRuntime::unavailable();
let mut status_updates = language_registry.language_server_binary_statuses();
@@ -658,7 +575,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
ExtensionStore::new(
extensions_dir.clone(),
Some(cache_dir),
registration_hooks,
proxy,
fs.clone(),
extension_client.clone(),
builder_client,

View File

@@ -0,0 +1,319 @@
use std::{path::PathBuf, sync::Arc};
use anyhow::{anyhow, Context as _, Result};
use client::{proto, TypedEnvelope};
use collections::{HashMap, HashSet};
use extension::{
Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
ExtensionManifest,
};
use fs::{Fs, RemoveOptions, RenameOptions};
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel};
use http_client::HttpClient;
use language::{LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage};
use lsp::LanguageServerName;
use node_runtime::NodeRuntime;
use crate::wasm_host::{WasmExtension, WasmHost};
#[derive(Clone, Debug)]
pub struct ExtensionVersion {
pub id: String,
pub version: String,
pub dev: bool,
}
pub struct HeadlessExtensionStore {
pub fs: Arc<dyn Fs>,
pub extension_dir: PathBuf,
pub proxy: Arc<ExtensionHostProxy>,
pub wasm_host: Arc<WasmHost>,
pub loaded_extensions: HashMap<Arc<str>, Arc<str>>,
pub loaded_languages: HashMap<Arc<str>, Vec<LanguageName>>,
pub loaded_language_servers: HashMap<Arc<str>, Vec<(LanguageServerName, LanguageName)>>,
}
impl HeadlessExtensionStore {
pub fn new(
fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>,
extension_dir: PathBuf,
extension_host_proxy: Arc<ExtensionHostProxy>,
node_runtime: NodeRuntime,
cx: &mut AppContext,
) -> Model<Self> {
cx.new_model(|cx| Self {
fs: fs.clone(),
wasm_host: WasmHost::new(
fs.clone(),
http_client.clone(),
node_runtime,
extension_host_proxy.clone(),
extension_dir.join("work"),
cx,
),
extension_dir,
proxy: extension_host_proxy,
loaded_extensions: Default::default(),
loaded_languages: Default::default(),
loaded_language_servers: Default::default(),
})
}
pub fn sync_extensions(
&mut self,
extensions: Vec<ExtensionVersion>,
cx: &ModelContext<Self>,
) -> Task<Result<Vec<ExtensionVersion>>> {
let on_client = HashSet::from_iter(extensions.iter().map(|e| e.id.as_str()));
let to_remove: Vec<Arc<str>> = self
.loaded_extensions
.keys()
.filter(|id| !on_client.contains(id.as_ref()))
.cloned()
.collect();
let to_load: Vec<ExtensionVersion> = extensions
.into_iter()
.filter(|e| {
if e.dev {
return true;
}
!self
.loaded_extensions
.get(e.id.as_str())
.is_some_and(|loaded| loaded.as_ref() == e.version.as_str())
})
.collect();
cx.spawn(|this, mut cx| async move {
let mut missing = Vec::new();
for extension_id in to_remove {
log::info!("removing extension: {}", extension_id);
this.update(&mut cx, |this, cx| {
this.uninstall_extension(&extension_id, cx)
})?
.await?;
}
for extension in to_load {
if let Err(e) = Self::load_extension(this.clone(), extension.clone(), &mut cx).await
{
log::info!("failed to load extension: {}, {:?}", extension.id, e);
missing.push(extension)
} else if extension.dev {
missing.push(extension)
}
}
Ok(missing)
})
}
pub async fn load_extension(
this: WeakModel<Self>,
extension: ExtensionVersion,
cx: &mut AsyncAppContext,
) -> Result<()> {
let (fs, wasm_host, extension_dir) = this.update(cx, |this, _cx| {
this.loaded_extensions.insert(
extension.id.clone().into(),
extension.version.clone().into(),
);
(
this.fs.clone(),
this.wasm_host.clone(),
this.extension_dir.join(&extension.id),
)
})?;
let manifest = Arc::new(ExtensionManifest::load(fs.clone(), &extension_dir).await?);
debug_assert!(!manifest.languages.is_empty() || !manifest.language_servers.is_empty());
if manifest.version.as_ref() != extension.version.as_str() {
anyhow::bail!(
"mismatched versions: ({}) != ({})",
manifest.version,
extension.version
)
}
for language_path in &manifest.languages {
let language_path = extension_dir.join(language_path);
let config = fs.load(&language_path.join("config.toml")).await?;
let mut config = ::toml::from_str::<LanguageConfig>(&config)?;
this.update(cx, |this, _cx| {
this.loaded_languages
.entry(manifest.id.clone())
.or_default()
.push(config.name.clone());
config.grammar = None;
this.proxy.register_language(
config.name.clone(),
None,
config.matcher.clone(),
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: LanguageQueries::default(),
context_provider: None,
toolchain_provider: None,
})
}),
);
})?;
}
if manifest.language_servers.is_empty() {
return Ok(());
}
let wasm_extension: Arc<dyn Extension> =
Arc::new(WasmExtension::load(extension_dir, &manifest, wasm_host.clone(), &cx).await?);
for (language_server_id, language_server_config) in &manifest.language_servers {
for language in language_server_config.languages() {
this.update(cx, |this, _cx| {
this.loaded_language_servers
.entry(manifest.id.clone())
.or_default()
.push((language_server_id.clone(), language.clone()));
this.proxy.register_language_server(
wasm_extension.clone(),
language_server_id.clone(),
language.clone(),
);
})?;
}
}
Ok(())
}
fn uninstall_extension(
&mut self,
extension_id: &Arc<str>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
self.loaded_extensions.remove(extension_id);
let languages_to_remove = self
.loaded_languages
.remove(extension_id)
.unwrap_or_default();
self.proxy.remove_languages(&languages_to_remove, &[]);
for (language_server_name, language) in self
.loaded_language_servers
.remove(extension_id)
.unwrap_or_default()
{
self.proxy
.remove_language_server(&language, &language_server_name);
}
let path = self.extension_dir.join(&extension_id.to_string());
let fs = self.fs.clone();
cx.spawn(|_, _| async move {
fs.remove_dir(
&path,
RemoveOptions {
recursive: true,
ignore_if_not_exists: true,
},
)
.await
})
}
pub fn install_extension(
&mut self,
extension: ExtensionVersion,
tmp_path: PathBuf,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let path = self.extension_dir.join(&extension.id);
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {
if fs.is_dir(&path).await {
this.update(&mut cx, |this, cx| {
this.uninstall_extension(&extension.id.clone().into(), cx)
})?
.await?;
}
fs.rename(&tmp_path, &path, RenameOptions::default())
.await?;
Self::load_extension(this, extension, &mut cx).await
})
}
pub async fn handle_sync_extensions(
extension_store: Model<HeadlessExtensionStore>,
envelope: TypedEnvelope<proto::SyncExtensions>,
mut cx: AsyncAppContext,
) -> Result<proto::SyncExtensionsResponse> {
let requested_extensions =
envelope
.payload
.extensions
.into_iter()
.map(|p| ExtensionVersion {
id: p.id,
version: p.version,
dev: p.dev,
});
let missing_extensions = extension_store
.update(&mut cx, |extension_store, cx| {
extension_store.sync_extensions(requested_extensions.collect(), cx)
})?
.await?;
Ok(proto::SyncExtensionsResponse {
missing_extensions: missing_extensions
.into_iter()
.map(|e| proto::Extension {
id: e.id,
version: e.version,
dev: e.dev,
})
.collect(),
tmp_dir: paths::remote_extensions_uploads_dir()
.to_string_lossy()
.to_string(),
})
}
pub async fn handle_install_extension(
extensions: Model<HeadlessExtensionStore>,
envelope: TypedEnvelope<proto::InstallExtension>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let extension = envelope
.payload
.extension
.with_context(|| anyhow!("Invalid InstallExtension request"))?;
extensions
.update(&mut cx, |extensions, cx| {
extensions.install_extension(
ExtensionVersion {
id: extension.id,
version: extension.version,
dev: extension.dev,
},
PathBuf::from(envelope.payload.tmp_dir),
cx,
)
})?
.await?;
Ok(proto::Ack {})
}
}

View File

@@ -1,11 +1,11 @@
pub mod wit;
use crate::{ExtensionManifest, ExtensionRegistrationHooks};
use crate::ExtensionManifest;
use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait;
use extension::{
CodeLabel, Command, Completion, KeyValueStoreDelegate, SlashCommand,
SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
CodeLabel, Command, Completion, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate,
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
};
use fs::{normalize_path, Fs};
use futures::future::LocalBoxFuture;
@@ -34,14 +34,13 @@ use wasmtime::{
};
use wasmtime_wasi::{self as wasi, WasiView};
use wit::Extension;
pub use wit::ExtensionProject;
pub struct WasmHost {
engine: Engine,
release_channel: ReleaseChannel,
http_client: Arc<dyn HttpClient>,
node_runtime: NodeRuntime,
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
pub(crate) proxy: Arc<ExtensionHostProxy>,
fs: Arc<dyn Fs>,
pub work_dir: PathBuf,
_main_thread_message_task: Task<()>,
@@ -238,6 +237,25 @@ impl extension::Extension for WasmExtension {
.await
}
async fn context_server_command(
&self,
context_server_id: Arc<str>,
project: Arc<dyn ProjectDelegate>,
) -> Result<Command> {
self.call(|extension, store| {
async move {
let project_resource = store.data_mut().table().push(project)?;
let command = extension
.call_context_server_command(store, context_server_id.clone(), project_resource)
.await?
.map_err(|err| anyhow!("{err}"))?;
anyhow::Ok(command.into())
}
.boxed()
})
.await
}
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> {
self.call(|extension, store| {
async move {
@@ -312,7 +330,7 @@ impl WasmHost {
fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>,
node_runtime: NodeRuntime,
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
proxy: Arc<ExtensionHostProxy>,
work_dir: PathBuf,
cx: &mut AppContext,
) -> Arc<Self> {
@@ -328,7 +346,7 @@ impl WasmHost {
work_dir,
http_client,
node_runtime,
registration_hooks,
proxy,
release_channel: ReleaseChannel::global(cx),
_main_thread_message_task: task,
main_thread_message_tx: tx,

View File

@@ -3,7 +3,7 @@ use crate::wasm_host::wit::since_v0_0_4;
use crate::wasm_host::WasmState;
use anyhow::Result;
use async_trait::async_trait;
use extension::WorktreeDelegate;
use extension::{ExtensionLanguageServerProxy, WorktreeDelegate};
use language::LanguageServerBinaryStatus;
use semantic_version::SemanticVersion;
use std::sync::{Arc, OnceLock};
@@ -149,8 +149,9 @@ impl ExtensionImports for WasmState {
};
self.host
.registration_hooks
.update_lsp_status(lsp::LanguageServerName(server_name.into()), status);
.proxy
.update_language_server_status(lsp::LanguageServerName(server_name.into()), status);
Ok(())
}

View File

@@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use extension::{KeyValueStoreDelegate, WorktreeDelegate};
use extension::{ExtensionLanguageServerProxy, KeyValueStoreDelegate, WorktreeDelegate};
use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use language::LanguageName;
@@ -495,8 +495,9 @@ impl ExtensionImports for WasmState {
};
self.host
.registration_hooks
.update_lsp_status(::lsp::LanguageServerName(server_name.into()), status);
.proxy
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
Ok(())
}

View File

@@ -8,7 +8,9 @@ use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use context_servers::manager::ContextServerSettings;
use extension::{KeyValueStoreDelegate, WorktreeDelegate};
use extension::{
ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate,
};
use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus};
@@ -44,13 +46,10 @@ mod settings {
}
pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
pub type ExtensionProject = Arc<dyn ProjectDelegate>;
pub type ExtensionKeyValueStore = Arc<dyn KeyValueStoreDelegate>;
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
pub struct ExtensionProject {
pub worktree_ids: Vec<u64>,
}
pub fn linker() -> &'static Linker<WasmState> {
static LINKER: OnceLock<Linker<WasmState>> = OnceLock::new();
LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
@@ -273,7 +272,7 @@ impl HostProject for WasmState {
project: Resource<ExtensionProject>,
) -> wasmtime::Result<Vec<u64>> {
let project = self.table.get(&project)?;
Ok(project.worktree_ids.clone())
Ok(project.worktree_ids())
}
fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
@@ -685,8 +684,9 @@ impl ExtensionImports for WasmState {
};
self.host
.registration_hooks
.update_lsp_status(::lsp::LanguageServerName(server_name.into()), status);
.proxy
.update_language_server_status(::lsp::LanguageServerName(server_name.into()), status);
Ok(())
}

View File

@@ -13,21 +13,15 @@ path = "src/extensions_ui.rs"
[dependencies]
anyhow.workspace = true
assistant_slash_command.workspace = true
client.workspace = true
collections.workspace = true
context_servers.workspace = true
db.workspace = true
editor.workspace = true
extension.workspace = true
extension_host.workspace = true
fs.workspace = true
fuzzy.workspace = true
gpui.workspace = true
indexed_docs.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
num-format.workspace = true
picker.workspace = true
project.workspace = true
@@ -36,13 +30,10 @@ semantic_version.workspace = true
serde.workspace = true
settings.workspace = true
smallvec.workspace = true
snippet_provider.workspace = true
theme.workspace = true
theme_selector.workspace = true
ui.workspace = true
util.workspace = true
vim.workspace = true
wasmtime-wasi.workspace = true
vim_mode_setting.workspace = true
workspace.workspace = true
zed_actions.workspace = true

View File

@@ -1,212 +0,0 @@
use std::{path::PathBuf, sync::Arc};
use anyhow::{anyhow, Result};
use assistant_slash_command::{ExtensionSlashCommand, SlashCommandRegistry};
use context_servers::manager::ServerCommand;
use context_servers::ContextServerFactoryRegistry;
use db::smol::future::FutureExt as _;
use extension::Extension;
use extension_host::wasm_host::ExtensionProject;
use extension_host::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host};
use fs::Fs;
use gpui::{AppContext, BackgroundExecutor, Model, Task};
use indexed_docs::{ExtensionIndexedDocsProvider, IndexedDocsRegistry, ProviderId};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage};
use snippet_provider::SnippetRegistry;
use theme::{ThemeRegistry, ThemeSettings};
use ui::SharedString;
use wasmtime_wasi::WasiView as _;
pub struct ConcreteExtensionRegistrationHooks {
slash_command_registry: Arc<SlashCommandRegistry>,
theme_registry: Arc<ThemeRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
language_registry: Arc<LanguageRegistry>,
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
executor: BackgroundExecutor,
}
impl ConcreteExtensionRegistrationHooks {
pub fn new(
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
language_registry: Arc<LanguageRegistry>,
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
cx: &AppContext,
) -> Arc<dyn extension_host::ExtensionRegistrationHooks> {
Arc::new(Self {
theme_registry,
slash_command_registry,
indexed_docs_registry,
snippet_registry,
language_registry,
context_server_factory_registry,
executor: cx.background_executor().clone(),
})
}
}
impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistrationHooks {
fn remove_user_themes(&self, themes: Vec<SharedString>) {
self.theme_registry.remove_user_themes(&themes);
}
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
let theme_registry = self.theme_registry.clone();
self.executor
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
}
fn register_slash_command(
&self,
extension: Arc<dyn Extension>,
command: extension::SlashCommand,
) {
self.slash_command_registry
.register_command(ExtensionSlashCommand::new(extension, command), false)
}
fn register_context_server(
&self,
id: Arc<str>,
extension: wasm_host::WasmExtension,
cx: &mut AppContext,
) {
self.context_server_factory_registry
.update(cx, |registry, _| {
registry.register_server_factory(
id.clone(),
Arc::new({
move |project, cx| {
log::info!(
"loading command for context server {id} from extension {}",
extension.manifest.id
);
let id = id.clone();
let extension = extension.clone();
cx.spawn(|mut cx| async move {
let extension_project =
project.update(&mut cx, |project, cx| ExtensionProject {
worktree_ids: project
.visible_worktrees(cx)
.map(|worktree| worktree.read(cx).id().to_proto())
.collect(),
})?;
let command = extension
.call({
let id = id.clone();
|extension, store| {
async move {
let project = store
.data_mut()
.table()
.push(extension_project)?;
let command = extension
.call_context_server_command(
store,
id.clone(),
project,
)
.await?
.map_err(|e| anyhow!("{}", e))?;
anyhow::Ok(command)
}
.boxed()
}
})
.await?;
log::info!("loaded command for context server {id}: {command:?}");
Ok(ServerCommand {
path: command.command,
args: command.args,
env: Some(command.env.into_iter().collect()),
})
})
}
}),
)
});
}
fn register_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
self.indexed_docs_registry
.register_provider(Box::new(ExtensionIndexedDocsProvider::new(
extension,
ProviderId(provider_id),
)));
}
fn register_snippets(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
self.snippet_registry
.register_snippets(path, snippet_contents)
}
fn update_lsp_status(
&self,
server_name: lsp::LanguageServerName,
status: LanguageServerBinaryStatus,
) {
self.language_registry
.update_lsp_status(server_name, status);
}
fn register_lsp_adapter(
&self,
language_name: language::LanguageName,
adapter: ExtensionLspAdapter,
) {
self.language_registry
.register_lsp_adapter(language_name, Arc::new(adapter));
}
fn remove_lsp_adapter(
&self,
language_name: &language::LanguageName,
server_name: &lsp::LanguageServerName,
) {
self.language_registry
.remove_lsp_adapter(language_name, server_name);
}
fn remove_languages(
&self,
languages_to_remove: &[language::LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.language_registry
.remove_languages(&languages_to_remove, &grammars_to_remove);
}
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
self.language_registry.register_wasm_grammars(grammars)
}
fn register_language(
&self,
language: language::LanguageName,
grammar: Option<Arc<str>>,
matcher: language::LanguageMatcher,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
self.language_registry
.register_language(language, grammar, matcher, load)
}
fn reload_current_theme(&self, cx: &mut AppContext) {
ThemeSettings::reload_current_theme(cx)
}
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
self.executor.spawn(async move {
let themes = theme::read_user_theme(&path, fs).await?;
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
})
}
}

View File

@@ -1,10 +1,7 @@
mod components;
mod extension_registration_hooks;
mod extension_suggest;
mod extension_version_selector;
pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks;
use std::ops::DerefMut;
use std::sync::OnceLock;
use std::time::Duration;
@@ -17,9 +14,9 @@ use editor::{Editor, EditorElement, EditorStyle};
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, uniform_list, AppContext, EventEmitter, Flatten, FocusableView, InteractiveElement,
KeyContext, ParentElement, Render, Styled, Task, TextStyle, UniformListScrollHandle, View,
ViewContext, VisualContext, WeakView, WindowContext,
actions, uniform_list, Action, AppContext, EventEmitter, Flatten, FocusableView,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use num_format::{Locale, ToFormattedString};
use project::DirectoryLister;
@@ -27,7 +24,7 @@ use release_channel::ReleaseChannel;
use settings::Settings;
use theme::ThemeSettings;
use ui::{prelude::*, CheckboxWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use vim::VimModeSetting;
use vim_mode_setting::VimModeSetting;
use workspace::{
item::{Item, ItemEvent},
Workspace, WorkspaceId,
@@ -254,14 +251,13 @@ impl ExtensionsPage {
.collect::<Vec<_>>();
if !themes.is_empty() {
workspace
.update(cx, |workspace, cx| {
theme_selector::toggle(
workspace,
&theme_selector::Toggle {
.update(cx, |_workspace, cx| {
cx.dispatch_action(
zed_actions::theme_selector::Toggle {
themes_filter: Some(themes),
},
cx,
)
}
.boxed_clone(),
);
})
.ok();
}

View File

@@ -22,8 +22,8 @@ db.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
human_bytes = "0.4.1"
http_client.workspace = true
human_bytes = "0.4.1"
language.workspace = true
log.workspace = true
menu.workspace = true
@@ -39,6 +39,7 @@ ui.workspace = true
urlencoding = "2.1.2"
util.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }

View File

@@ -5,8 +5,6 @@ use workspace::Workspace;
pub mod feedback_modal;
actions!(feedback, [GiveFeedback, SubmitFeedback]);
mod system_specs;
actions!(

View File

@@ -18,8 +18,9 @@ use serde_derive::Serialize;
use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
use util::ResultExt;
use workspace::{DismissDecision, ModalView, Workspace};
use zed_actions::feedback::GiveFeedback;
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo};
use crate::{system_specs::SystemSpecs, OpenZedRepo};
// For UI testing purposes
const SEND_SUCCESS_IN_DEV_MODE: bool = true;

View File

@@ -71,8 +71,16 @@ impl Match {
fn project_path(&self, project: &Project, cx: &WindowContext) -> Option<ProjectPath> {
let worktree_id = if let Some(path_match) = &self.path_match {
WorktreeId::from_usize(path_match.worktree_id)
} else if let Some(worktree) = project.visible_worktrees(cx).find(|worktree| {
worktree
.read(cx)
.root_entry()
.is_some_and(|entry| entry.is_dir())
}) {
worktree.read(cx).id()
} else {
project.worktrees(cx).next()?.read(cx).id()
// todo(): we should find_or_create a workspace.
return None;
};
let path = PathBuf::from(self.relative_path());

View File

@@ -24,6 +24,7 @@ libc.workspace = true
parking_lot.workspace = true
paths.workspace = true
rope.workspace = true
proto.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true

View File

@@ -27,13 +27,14 @@ use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt};
use git::repository::{GitRepository, RealGitRepository};
use gpui::{AppContext, Global, ReadGlobal};
use rope::Rope;
use serde::{Deserialize, Serialize};
use smol::io::AsyncWriteExt;
use std::{
io::{self, Write},
path::{Component, Path, PathBuf},
pin::Pin,
sync::Arc,
time::{Duration, SystemTime},
time::{Duration, SystemTime, UNIX_EPOCH},
};
use tempfile::{NamedTempFile, TempDir};
use text::LineEnding;
@@ -179,13 +180,62 @@ pub struct RemoveOptions {
#[derive(Copy, Clone, Debug)]
pub struct Metadata {
pub inode: u64,
pub mtime: SystemTime,
pub mtime: MTime,
pub is_symlink: bool,
pub is_dir: bool,
pub len: u64,
pub is_fifo: bool,
}
/// Filesystem modification time. The purpose of this newtype is to discourage use of operations
/// that do not make sense for mtimes. In particular, it is not always valid to compare mtimes using
/// `<` or `>`, as there are many things that can cause the mtime of a file to be earlier than it
/// was. See ["mtime comparison considered harmful" - apenwarr](https://apenwarr.ca/log/20181113).
///
/// Do not derive Ord, PartialOrd, or arithmetic operation traits.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(transparent)]
pub struct MTime(SystemTime);
impl MTime {
/// Conversion intended for persistence and testing.
pub fn from_seconds_and_nanos(secs: u64, nanos: u32) -> Self {
MTime(UNIX_EPOCH + Duration::new(secs, nanos))
}
/// Conversion intended for persistence.
pub fn to_seconds_and_nanos_for_persistence(self) -> Option<(u64, u32)> {
self.0
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| (duration.as_secs(), duration.subsec_nanos()))
}
/// Returns the value wrapped by this `MTime`, for presentation to the user. The name including
/// "_for_user" is to discourage misuse - this method should not be used when making decisions
/// about file dirtiness.
pub fn timestamp_for_user(self) -> SystemTime {
self.0
}
/// Temporary method to split out the behavior changes from introduction of this newtype.
pub fn bad_is_greater_than(self, other: MTime) -> bool {
self.0 > other.0
}
}
impl From<proto::Timestamp> for MTime {
fn from(timestamp: proto::Timestamp) -> Self {
MTime(timestamp.into())
}
}
impl From<MTime> for proto::Timestamp {
fn from(mtime: MTime) -> Self {
mtime.0.into()
}
}
#[derive(Default)]
pub struct RealFs {
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
@@ -558,7 +608,7 @@ impl Fs for RealFs {
Ok(Some(Metadata {
inode,
mtime: metadata.modified().unwrap(),
mtime: MTime(metadata.modified().unwrap()),
len: metadata.len(),
is_symlink,
is_dir: metadata.file_type().is_dir(),
@@ -818,13 +868,13 @@ struct FakeFsState {
enum FakeFsEntry {
File {
inode: u64,
mtime: SystemTime,
mtime: MTime,
len: u64,
content: Vec<u8>,
},
Dir {
inode: u64,
mtime: SystemTime,
mtime: MTime,
len: u64,
entries: BTreeMap<String, Arc<Mutex<FakeFsEntry>>>,
git_repo_state: Option<Arc<Mutex<git::repository::FakeGitRepositoryState>>>,
@@ -836,6 +886,18 @@ enum FakeFsEntry {
#[cfg(any(test, feature = "test-support"))]
impl FakeFsState {
fn get_and_increment_mtime(&mut self) -> MTime {
let mtime = self.next_mtime;
self.next_mtime += FakeFs::SYSTEMTIME_INTERVAL;
MTime(mtime)
}
fn get_and_increment_inode(&mut self) -> u64 {
let inode = self.next_inode;
self.next_inode += 1;
inode
}
fn read_path(&self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
Ok(self
.try_read_path(target, true)
@@ -959,7 +1021,7 @@ pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> =
impl FakeFs {
/// We need to use something large enough for Windows and Unix to consider this a new file.
/// https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html#platform-specific-behavior
const SYSTEMTIME_INTERVAL: u64 = 100;
const SYSTEMTIME_INTERVAL: Duration = Duration::from_nanos(100);
pub fn new(executor: gpui::BackgroundExecutor) -> Arc<Self> {
let (tx, mut rx) = smol::channel::bounded::<PathBuf>(10);
@@ -969,13 +1031,13 @@ impl FakeFs {
state: Mutex::new(FakeFsState {
root: Arc::new(Mutex::new(FakeFsEntry::Dir {
inode: 0,
mtime: SystemTime::UNIX_EPOCH,
mtime: MTime(UNIX_EPOCH),
len: 0,
entries: Default::default(),
git_repo_state: None,
})),
git_event_tx: tx,
next_mtime: SystemTime::UNIX_EPOCH,
next_mtime: UNIX_EPOCH + Self::SYSTEMTIME_INTERVAL,
next_inode: 1,
event_txs: Default::default(),
buffered_events: Vec::new(),
@@ -1007,13 +1069,16 @@ impl FakeFs {
state.next_mtime = next_mtime;
}
pub fn get_and_increment_mtime(&self) -> MTime {
let mut state = self.state.lock();
state.get_and_increment_mtime()
}
pub async fn touch_path(&self, path: impl AsRef<Path>) {
let mut state = self.state.lock();
let path = path.as_ref();
let new_mtime = state.next_mtime;
let new_inode = state.next_inode;
state.next_inode += 1;
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
let new_mtime = state.get_and_increment_mtime();
let new_inode = state.get_and_increment_inode();
state
.write_path(path, move |entry| {
match entry {
@@ -1062,19 +1127,14 @@ impl FakeFs {
fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
let mut state = self.state.lock();
let path = path.as_ref();
let inode = state.next_inode;
let mtime = state.next_mtime;
state.next_inode += 1;
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
let file = Arc::new(Mutex::new(FakeFsEntry::File {
inode,
mtime,
inode: state.get_and_increment_inode(),
mtime: state.get_and_increment_mtime(),
len: content.len() as u64,
content,
}));
let mut kind = None;
state.write_path(path, {
state.write_path(path.as_ref(), {
let kind = &mut kind;
move |entry| {
match entry {
@@ -1090,7 +1150,7 @@ impl FakeFs {
Ok(())
}
})?;
state.emit_event([(path, kind)]);
state.emit_event([(path.as_ref(), kind)]);
Ok(())
}
@@ -1383,16 +1443,6 @@ impl FakeFsEntry {
}
}
fn set_file_content(&mut self, path: &Path, new_content: Vec<u8>) -> Result<()> {
if let Self::File { content, mtime, .. } = self {
*mtime = SystemTime::now();
*content = new_content;
Ok(())
} else {
Err(anyhow!("not a file: {}", path.display()))
}
}
fn dir_entries(
&mut self,
path: &Path,
@@ -1456,10 +1506,8 @@ impl Fs for FakeFs {
}
let mut state = self.state.lock();
let inode = state.next_inode;
let mtime = state.next_mtime;
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
state.next_inode += 1;
let inode = state.get_and_increment_inode();
let mtime = state.get_and_increment_mtime();
state.write_path(&cur_path, |entry| {
entry.or_insert_with(|| {
created_dirs.push((cur_path.clone(), Some(PathEventKind::Created)));
@@ -1482,10 +1530,8 @@ impl Fs for FakeFs {
async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> {
self.simulate_random_delay().await;
let mut state = self.state.lock();
let inode = state.next_inode;
let mtime = state.next_mtime;
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
state.next_inode += 1;
let inode = state.get_and_increment_inode();
let mtime = state.get_and_increment_mtime();
let file = Arc::new(Mutex::new(FakeFsEntry::File {
inode,
mtime,
@@ -1625,13 +1671,12 @@ impl Fs for FakeFs {
let source = normalize_path(source);
let target = normalize_path(target);
let mut state = self.state.lock();
let mtime = state.next_mtime;
let inode = util::post_inc(&mut state.next_inode);
state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL);
let mtime = state.get_and_increment_mtime();
let inode = state.get_and_increment_inode();
let source_entry = state.read_path(&source)?;
let content = source_entry.lock().file_content(&source)?.clone();
let mut kind = Some(PathEventKind::Created);
let entry = state.write_path(&target, |e| match e {
state.write_path(&target, |e| match e {
btree_map::Entry::Occupied(e) => {
if options.overwrite {
kind = Some(PathEventKind::Changed);
@@ -1647,14 +1692,11 @@ impl Fs for FakeFs {
inode,
mtime,
len: content.len() as u64,
content: Vec::new(),
content,
})))
.clone(),
)),
})?;
if let Some(entry) = entry {
entry.lock().set_file_content(&target, content)?;
}
state.emit_event([(target, kind)]);
Ok(())
}

View File

@@ -31,10 +31,6 @@ time.workspace = true
url.workspace = true
util.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
[dev-dependencies]
unindent.workspace = true
serde_json.workspace = true

View File

@@ -4,7 +4,7 @@ use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::process::{Command, Stdio};
use std::process::Stdio;
use std::sync::Arc;
use std::{ops::Range, path::Path};
use text::Rope;
@@ -80,9 +80,7 @@ fn run_git_blame(
path: &Path,
contents: &Rope,
) -> Result<String> {
let mut child = Command::new(git_binary);
child
let child = util::command::new_std_command(git_binary)
.current_dir(working_directory)
.arg("blame")
.arg("--incremental")
@@ -91,15 +89,7 @@ fn run_git_blame(
.arg(path.as_os_str())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
}
let child = child
.stderr(Stdio::piped())
.spawn()
.map_err(|e| anyhow!("Failed to start git blame process: {}", e))?;

View File

@@ -2,10 +2,6 @@ use crate::Oid;
use anyhow::{anyhow, Result};
use collections::HashMap;
use std::path::Path;
use std::process::Command;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
pub fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result<HashMap<Oid, String>> {
if shas.is_empty() {
@@ -14,19 +10,12 @@ pub fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result<HashMap<Oi
const MARKER: &str = "<MARKER>";
let mut command = Command::new("git");
command
let output = util::command::new_std_command("git")
.current_dir(working_directory)
.arg("show")
.arg("-s")
.arg(format!("--format=%B{}", MARKER))
.args(shas.iter().map(ToString::to_string));
#[cfg(windows)]
command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
let output = command
.args(shas.iter().map(ToString::to_string))
.output()
.map_err(|e| anyhow!("Failed to start git blame process: {}", e))?;

View File

@@ -2,7 +2,7 @@ use crate::repository::{GitFileStatus, RepoPath};
use anyhow::{anyhow, Result};
use std::{
path::{Path, PathBuf},
process::{Command, Stdio},
process::Stdio,
sync::Arc,
};
@@ -17,9 +17,7 @@ impl GitStatus {
working_directory: &Path,
path_prefixes: &[PathBuf],
) -> Result<Self> {
let mut child = Command::new(git_binary);
child
let child = util::command::new_std_command(git_binary)
.current_dir(working_directory)
.args([
"--no-optional-locks",
@@ -37,15 +35,7 @@ impl GitStatus {
}))
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
}
let child = child
.stderr(Stdio::piped())
.spawn()
.map_err(|e| anyhow!("Failed to start git status process: {}", e))?;

View File

@@ -61,4 +61,4 @@ In addition to the systems above, GPUI provides a range of smaller services that
- The `[gpui::test]` macro provides a convenient way to write tests for your GPUI applications. Tests also have their own kind of context, a `TestAppContext` which provides ways of simulating common platform input. See `app::test_context` and `test` modules for more details.
Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://discord.gg/zed-community). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://zed.dev/community-links). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).

View File

@@ -15,7 +15,10 @@ actions!(
SelectAll,
Home,
End,
ShowCharacterPalette
ShowCharacterPalette,
Paste,
Cut,
Copy,
]
);
@@ -107,6 +110,28 @@ impl TextInput {
cx.show_character_palette();
}
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
self.replace_text_in_range(None, &text.replace("\n", " "), cx);
}
}
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
if !self.selected_range.is_empty() {
cx.write_to_clipboard(ClipboardItem::new_string(
(&self.content[self.selected_range.clone()]).to_string(),
));
}
}
fn cut(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
if !self.selected_range.is_empty() {
cx.write_to_clipboard(ClipboardItem::new_string(
(&self.content[self.selected_range.clone()]).to_string(),
));
self.replace_text_in_range(None, "", cx)
}
}
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
self.selected_range = offset..offset;
cx.notify()
@@ -219,9 +244,11 @@ impl ViewInputHandler for TextInput {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
actual_range: &mut Option<Range<usize>>,
_cx: &mut ViewContext<Self>,
) -> Option<String> {
let range = self.range_from_utf16(&range_utf16);
actual_range.replace(self.range_to_utf16(&range));
Some(self.content[range].to_string())
}
@@ -497,6 +524,9 @@ impl Render for TextInput {
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::show_character_palette))
.on_action(cx.listener(Self::paste))
.on_action(cx.listener(Self::cut))
.on_action(cx.listener(Self::copy))
.on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
.on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
@@ -581,8 +611,8 @@ impl Render for InputExample {
format!(
"{:} {}",
ks.unparse(),
if let Some(ime_key) = ks.ime_key.as_ref() {
format!("-> {:?}", ime_key)
if let Some(key_char) = ks.key_char.as_ref() {
format!("-> {:?}", key_char)
} else {
"".to_owned()
}
@@ -602,6 +632,9 @@ fn main() {
KeyBinding::new("shift-left", SelectLeft, None),
KeyBinding::new("shift-right", SelectRight, None),
KeyBinding::new("cmd-a", SelectAll, None),
KeyBinding::new("cmd-v", Paste, None),
KeyBinding::new("cmd-c", Copy, None),
KeyBinding::new("cmd-x", Cut, None),
KeyBinding::new("home", Home, None),
KeyBinding::new("end", End, None),
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),

View File

@@ -263,7 +263,7 @@ impl TextLayout {
.line_height
.to_pixels(font_size.into(), cx.rem_size());
let runs = if let Some(runs) = runs {
let mut runs = if let Some(runs) = runs {
runs
} else {
vec![text_style.to_run(text.len())]
@@ -306,7 +306,7 @@ impl TextLayout {
let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
let text = if let Some(truncate_width) = truncate_width {
line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis)
line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis, &mut runs)
} else {
text.clone()
};

View File

@@ -56,7 +56,7 @@
//! and [`test`] modules for more details.
//!
//! Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop
//! a question in the [Zed Discord](https://discord.gg/zed-community). We're working on improving the documentation, creating more examples,
//! a question in the [Zed Discord](https://zed.dev/community-links). We're working on improving the documentation, creating more examples,
//! and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog).
#![deny(missing_docs)]

View File

@@ -9,8 +9,12 @@ use std::ops::Range;
/// See [`InputHandler`] for details on how to implement each method.
pub trait ViewInputHandler: 'static + Sized {
/// See [`InputHandler::text_for_range`] for details
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
-> Option<String>;
fn text_for_range(
&mut self,
range: Range<usize>,
adjusted_range: &mut Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) -> Option<String>;
/// See [`InputHandler::selected_text_range`] for details
fn selected_text_range(
@@ -89,10 +93,12 @@ impl<V: ViewInputHandler> InputHandler for ElementInputHandler<V> {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted_range: &mut Option<Range<usize>>,
cx: &mut WindowContext,
) -> Option<String> {
self.view
.update(cx, |view, cx| view.text_for_range(range_utf16, cx))
self.view.update(cx, |view, cx| {
view.text_for_range(range_utf16, adjusted_range, cx)
})
}
fn replace_text_in_range(

View File

@@ -46,6 +46,7 @@ use smallvec::SmallVec;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use std::io::Cursor;
use std::ops;
use std::time::{Duration, Instant};
use std::{
fmt::{self, Debug},
@@ -561,6 +562,42 @@ pub(crate) trait PlatformAtlas: Send + Sync {
key: &AtlasKey,
build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
) -> Result<Option<AtlasTile>>;
fn remove(&self, key: &AtlasKey);
}
struct AtlasTextureList<T> {
textures: Vec<Option<T>>,
free_list: Vec<usize>,
}
impl<T> Default for AtlasTextureList<T> {
fn default() -> Self {
Self {
textures: Vec::default(),
free_list: Vec::default(),
}
}
}
impl<T> ops::Index<usize> for AtlasTextureList<T> {
type Output = Option<T>;
fn index(&self, index: usize) -> &Self::Output {
&self.textures[index]
}
}
impl<T> AtlasTextureList<T> {
#[allow(unused)]
fn drain(&mut self) -> std::vec::Drain<Option<T>> {
self.free_list.clear();
self.textures.drain(..)
}
#[allow(dead_code)]
fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> {
self.textures.iter_mut().flatten()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -643,9 +680,13 @@ impl PlatformInputHandler {
}
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted: &mut Option<Range<usize>>,
) -> Option<String> {
self.cx
.update(|cx| self.handler.text_for_range(range_utf16, cx))
.update(|cx| self.handler.text_for_range(range_utf16, adjusted, cx))
.ok()
.flatten()
}
@@ -712,6 +753,7 @@ impl PlatformInputHandler {
/// A struct representing a selection in a text buffer, in UTF16 characters.
/// This is different from a range because the head may be before the tail.
#[derive(Debug)]
pub struct UTF16Selection {
/// The range of text in the document this selection corresponds to
/// in UTF16 characters.
@@ -749,6 +791,7 @@ pub trait InputHandler: 'static {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted_range: &mut Option<Range<usize>>,
cx: &mut WindowContext,
) -> Option<String>;

View File

@@ -1,6 +1,6 @@
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size,
platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
DevicePixels, PlatformAtlas, Point, Size,
};
use anyhow::Result;
use blade_graphics as gpu;
@@ -67,7 +67,7 @@ impl BladeAtlas {
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
let mut lock = self.0.lock();
let textures = &mut lock.storage[texture_kind];
for texture in textures {
for texture in textures.iter_mut() {
texture.clear();
}
}
@@ -130,19 +130,48 @@ impl PlatformAtlas for BladeAtlas {
Ok(Some(tile))
}
}
fn remove(&self, key: &AtlasKey) {
let mut lock = self.0.lock();
let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
return;
};
let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
return;
};
if let Some(mut texture) = texture_slot.take() {
texture.decrement_ref_count();
if texture.is_unreferenced() {
lock.storage[id.kind]
.free_list
.push(texture.id.index as usize);
texture.destroy(&lock.gpu);
} else {
*texture_slot = Some(texture);
}
}
}
}
impl BladeAtlasState {
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
let textures = &mut self.storage[texture_kind];
textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
.unwrap_or_else(|| {
let texture = self.push_texture(size, texture_kind);
texture.allocate(size).unwrap()
})
{
let textures = &mut self.storage[texture_kind];
if let Some(tile) = textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
{
return tile;
}
}
let texture = self.push_texture(size, texture_kind);
texture.allocate(size).unwrap()
}
fn push_texture(
@@ -198,21 +227,30 @@ impl BladeAtlasState {
},
);
let textures = &mut self.storage[kind];
let texture_list = &mut self.storage[kind];
let index = texture_list.free_list.pop();
let atlas_texture = BladeAtlasTexture {
id: AtlasTextureId {
index: textures.len() as u32,
index: index.unwrap_or(texture_list.textures.len()) as u32,
kind,
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
format,
raw,
raw_view,
live_atlas_keys: 0,
};
self.initializations.push(atlas_texture.id);
textures.push(atlas_texture);
textures.last_mut().unwrap()
if let Some(ix) = index {
texture_list.textures[ix] = Some(atlas_texture);
texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
} else {
texture_list.textures.push(Some(atlas_texture));
texture_list.textures.last_mut().unwrap().as_mut().unwrap()
}
}
fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
@@ -258,13 +296,13 @@ impl BladeAtlasState {
#[derive(Default)]
struct BladeAtlasStorage {
monochrome_textures: Vec<BladeAtlasTexture>,
polychrome_textures: Vec<BladeAtlasTexture>,
path_textures: Vec<BladeAtlasTexture>,
monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
path_textures: AtlasTextureList<BladeAtlasTexture>,
}
impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
type Output = Vec<BladeAtlasTexture>;
type Output = AtlasTextureList<BladeAtlasTexture>;
fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
match kind {
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
@@ -292,19 +330,19 @@ impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
};
&textures[id.index as usize]
textures[id.index as usize].as_ref().unwrap()
}
}
impl BladeAtlasStorage {
fn destroy(&mut self, gpu: &gpu::Context) {
for mut texture in self.monochrome_textures.drain(..) {
for mut texture in self.monochrome_textures.drain().flatten() {
texture.destroy(gpu);
}
for mut texture in self.polychrome_textures.drain(..) {
for mut texture in self.polychrome_textures.drain().flatten() {
texture.destroy(gpu);
}
for mut texture in self.path_textures.drain(..) {
for mut texture in self.path_textures.drain().flatten() {
texture.destroy(gpu);
}
}
@@ -316,6 +354,7 @@ struct BladeAtlasTexture {
raw: gpu::Texture,
raw_view: gpu::TextureView,
format: gpu::TextureFormat,
live_atlas_keys: u32,
}
impl BladeAtlasTexture {
@@ -334,6 +373,7 @@ impl BladeAtlasTexture {
size,
},
};
self.live_atlas_keys += 1;
Some(tile)
}
@@ -345,6 +385,14 @@ impl BladeAtlasTexture {
fn bytes_per_pixel(&self) -> u8 {
self.format.block_info().size
}
fn decrement_ref_count(&mut self) {
self.live_atlas_keys -= 1;
}
fn is_unreferenced(&mut self) -> bool {
self.live_atlas_keys == 0
}
}
impl From<Size<DevicePixels>> for etagere::Size {

View File

@@ -12,14 +12,15 @@ pub struct Keystroke {
/// e.g. for option-s, key is "s"
pub key: String,
/// ime_key is the character inserted by the IME engine when that key was pressed.
/// e.g. for option-s, ime_key is "ß"
pub ime_key: Option<String>,
/// key_char is the character that could have been typed when
/// this binding was pressed.
/// e.g. for s this is "s", for option-s "ß", and cmd-s None
pub key_char: Option<String>,
}
impl Keystroke {
/// When matching a key we cannot know whether the user intended to type
/// the ime_key or the key itself. On some non-US keyboards keys we use in our
/// the key_char or the key itself. On some non-US keyboards keys we use in our
/// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
/// and on some keyboards the IME handler converts a sequence of keys into a
/// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
@@ -27,10 +28,10 @@ impl Keystroke {
/// This method assumes that `self` was typed and `target' is in the keymap, and checks
/// both possibilities for self against the target.
pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
if let Some(ime_key) = self
.ime_key
if let Some(key_char) = self
.key_char
.as_ref()
.filter(|ime_key| ime_key != &&self.key)
.filter(|key_char| key_char != &&self.key)
{
let ime_modifiers = Modifiers {
control: self.modifiers.control,
@@ -38,7 +39,7 @@ impl Keystroke {
..Default::default()
};
if &target.key == ime_key && target.modifiers == ime_modifiers {
if &target.key == key_char && target.modifiers == ime_modifiers {
return true;
}
}
@@ -47,9 +48,9 @@ impl Keystroke {
}
/// key syntax is:
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
/// ime_key syntax is only used for generating test events,
/// when matching a key with an ime_key set will be matched without it.
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
/// key_char syntax is only used for generating test events,
/// when matching a key with an key_char set will be matched without it.
pub fn parse(source: &str) -> anyhow::Result<Self> {
let mut control = false;
let mut alt = false;
@@ -57,7 +58,7 @@ impl Keystroke {
let mut platform = false;
let mut function = false;
let mut key = None;
let mut ime_key = None;
let mut key_char = None;
let mut components = source.split('-').peekable();
while let Some(component) = components.next() {
@@ -74,7 +75,7 @@ impl Keystroke {
break;
} else if next.len() > 1 && next.starts_with('>') {
key = Some(String::from(component));
ime_key = Some(String::from(&next[1..]));
key_char = Some(String::from(&next[1..]));
components.next();
} else {
return Err(anyhow!("Invalid keystroke `{}`", source));
@@ -118,7 +119,7 @@ impl Keystroke {
function,
},
key,
ime_key,
key_char: key_char,
})
}
@@ -154,7 +155,7 @@ impl Keystroke {
/// Returns true if this keystroke left
/// the ime system in an incomplete state.
pub fn is_ime_in_progress(&self) -> bool {
self.ime_key.is_none()
self.key_char.is_none()
&& (is_printable_key(&self.key) || self.key.is_empty())
&& !(self.modifiers.platform
|| self.modifiers.control
@@ -162,17 +163,17 @@ impl Keystroke {
|| self.modifiers.alt)
}
/// Returns a new keystroke with the ime_key filled.
/// Returns a new keystroke with the key_char filled.
/// This is used for dispatch_keystroke where we want users to
/// be able to simulate typing "space", etc.
pub fn with_simulated_ime(mut self) -> Self {
if self.ime_key.is_none()
if self.key_char.is_none()
&& !self.modifiers.platform
&& !self.modifiers.control
&& !self.modifiers.function
&& !self.modifiers.alt
{
self.ime_key = match self.key.as_str() {
self.key_char = match self.key.as_str() {
"space" => Some(" ".into()),
"tab" => Some("\t".into()),
"enter" => Some("\n".into()),

View File

@@ -742,14 +742,14 @@ impl Keystroke {
}
}
// Ignore control characters (and DEL) for the purposes of ime_key
let ime_key =
// Ignore control characters (and DEL) for the purposes of key_char
let key_char =
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
Keystroke {
modifiers,
key,
ime_key,
key_char,
}
}

View File

@@ -1208,7 +1208,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
compose.feed(keysym);
match compose.status() {
xkb::Status::Composing => {
keystroke.ime_key = None;
keystroke.key_char = None;
state.pre_edit_text =
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
let pre_edit =
@@ -1220,7 +1220,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
xkb::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose.utf8();
keystroke.key_char = compose.utf8();
if let Some(keysym) = compose.keysym() {
keystroke.key = xkb::keysym_get_name(keysym);
}
@@ -1340,7 +1340,7 @@ impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
keystroke: Keystroke {
modifiers: Modifiers::default(),
key: commit_text.clone(),
ime_key: Some(commit_text),
key_char: Some(commit_text),
},
is_held: false,
}));

View File

@@ -687,11 +687,11 @@ impl WaylandWindowStatePtr {
}
}
if let PlatformInput::KeyDown(event) = input {
if let Some(ime_key) = &event.keystroke.ime_key {
if let Some(key_char) = &event.keystroke.key_char {
let mut state = self.state.borrow_mut();
if let Some(mut input_handler) = state.input_handler.take() {
drop(state);
input_handler.replace_text_in_range(None, ime_key);
input_handler.replace_text_in_range(None, key_char);
self.state.borrow_mut().input_handler = Some(input_handler);
}
}

View File

@@ -178,7 +178,7 @@ pub struct X11ClientState {
pub(crate) compose_state: Option<xkbc::compose::State>,
pub(crate) pre_edit_text: Option<String>,
pub(crate) composing: bool,
pub(crate) pre_ime_key_down: Option<Keystroke>,
pub(crate) pre_key_char_down: Option<Keystroke>,
pub(crate) cursor_handle: cursor::Handle,
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
@@ -446,7 +446,7 @@ impl X11Client {
compose_state,
pre_edit_text: None,
pre_ime_key_down: None,
pre_key_char_down: None,
composing: false,
cursor_handle,
@@ -776,11 +776,11 @@ impl X11Client {
},
};
let window = self.get_window(event.window)?;
window.configure(bounds);
window.configure(bounds).unwrap();
}
Event::PropertyNotify(event) => {
let window = self.get_window(event.window)?;
window.property_notify(event);
window.property_notify(event).unwrap();
}
Event::FocusIn(event) => {
let window = self.get_window(event.event)?;
@@ -858,7 +858,7 @@ impl X11Client {
let modifiers = modifiers_from_state(event.state);
state.modifiers = modifiers;
state.pre_ime_key_down.take();
state.pre_key_char_down.take();
let keystroke = {
let code = event.detail.into();
let xkb_state = state.previous_xkb_state.clone();
@@ -880,13 +880,13 @@ impl X11Client {
match compose_state.status() {
xkbc::Status::Composed => {
state.pre_edit_text.take();
keystroke.ime_key = compose_state.utf8();
keystroke.key_char = compose_state.utf8();
if let Some(keysym) = compose_state.keysym() {
keystroke.key = xkbc::keysym_get_name(keysym);
}
}
xkbc::Status::Composing => {
keystroke.ime_key = None;
keystroke.key_char = None;
state.pre_edit_text = compose_state
.utf8()
.or(crate::Keystroke::underlying_dead_key(keysym));
@@ -1156,7 +1156,7 @@ impl X11Client {
match event {
Event::KeyPress(event) | Event::KeyRelease(event) => {
let mut state = self.0.borrow_mut();
state.pre_ime_key_down = Some(Keystroke::from_xkb(
state.pre_key_char_down = Some(Keystroke::from_xkb(
&state.xkb,
state.modifiers,
event.detail.into(),
@@ -1187,11 +1187,11 @@ 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();
let keystroke = state.pre_ime_key_down.take();
let keystroke = state.pre_key_char_down.take();
state.composing = false;
drop(state);
if let Some(mut keystroke) = keystroke {
keystroke.ime_key = Some(text.clone());
keystroke.key_char = Some(text.clone());
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
@@ -1258,11 +1258,9 @@ impl LinuxClient for X11Client {
.iter()
.enumerate()
.filter_map(|(root_id, _)| {
Some(Rc::new(X11Display::new(
&state.xcb_connection,
state.scale_factor,
root_id,
)?) as Rc<dyn PlatformDisplay>)
Some(Rc::new(
X11Display::new(&state.xcb_connection, state.scale_factor, root_id).ok()?,
) as Rc<dyn PlatformDisplay>)
})
.collect()
}
@@ -1283,11 +1281,9 @@ impl LinuxClient for X11Client {
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
let state = self.0.borrow();
Some(Rc::new(X11Display::new(
&state.xcb_connection,
state.scale_factor,
id.0 as usize,
)?))
Some(Rc::new(
X11Display::new(&state.xcb_connection, state.scale_factor, id.0 as usize).ok()?,
))
}
fn open_window(

View File

@@ -13,12 +13,17 @@ pub(crate) struct X11Display {
impl X11Display {
pub(crate) fn new(
xc: &XCBConnection,
xcb: &XCBConnection,
scale_factor: f32,
x_screen_index: usize,
) -> Option<Self> {
let screen = xc.setup().roots.get(x_screen_index).unwrap();
Some(Self {
) -> anyhow::Result<Self> {
let Some(screen) = xcb.setup().roots.get(x_screen_index) else {
return Err(anyhow::anyhow!(
"No screen found with index {}",
x_screen_index
));
};
Ok(Self {
x_screen_index,
bounds: Bounds {
origin: Default::default(),

File diff suppressed because it is too large Load Diff

View File

@@ -245,7 +245,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
.charactersIgnoringModifiers()
.to_str()
.to_string();
let mut ime_key = None;
let mut key_char = None;
let first_char = characters.chars().next().map(|ch| ch as u16);
let modifiers = native_event.modifierFlags();
@@ -261,13 +261,19 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
#[allow(non_upper_case_globals)]
let key = match first_char {
Some(SPACE_KEY) => {
ime_key = Some(" ".to_string());
key_char = Some(" ".to_string());
"space".to_string()
}
Some(TAB_KEY) => {
key_char = Some("\t".to_string());
"tab".to_string()
}
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => {
key_char = Some("\n".to_string());
"enter".to_string()
}
Some(BACKSPACE_KEY) => "backspace".to_string(),
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
Some(ESCAPE_KEY) => "escape".to_string(),
Some(TAB_KEY) => "tab".to_string(),
Some(SHIFT_TAB_KEY) => "tab".to_string(),
Some(NSUpArrowFunctionKey) => "up".to_string(),
Some(NSDownArrowFunctionKey) => "down".to_string(),
@@ -335,6 +341,18 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
chars_ignoring_modifiers = chars_with_cmd;
}
if !control && !command && !function {
let mut mods = NO_MOD;
if shift {
mods |= SHIFT_MOD;
}
if alt {
mods |= OPTION_MOD;
}
key_char = Some(chars_for_modified_key(native_event.keyCode(), mods));
}
let mut key = if shift
&& chars_ignoring_modifiers
.chars()
@@ -348,20 +366,6 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
chars_ignoring_modifiers
};
if always_use_cmd_layout || alt {
let mut mods = NO_MOD;
if shift {
mods |= SHIFT_MOD;
}
if alt {
mods |= OPTION_MOD;
}
let alt_key = chars_for_modified_key(native_event.keyCode(), mods);
if alt_key != key {
ime_key = Some(alt_key);
}
};
key
}
};
@@ -375,7 +379,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
function,
},
key,
ime_key,
key_char,
}
}

View File

@@ -1,6 +1,6 @@
use crate::{
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
Point, Size,
platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,
DevicePixels, PlatformAtlas, Point, Size,
};
use anyhow::{anyhow, Result};
use collections::FxHashMap;
@@ -42,7 +42,7 @@ impl MetalAtlas {
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
AtlasTextureKind::Path => &mut lock.path_textures,
};
for texture in textures {
for texture in textures.iter_mut() {
texture.clear();
}
}
@@ -50,9 +50,9 @@ impl MetalAtlas {
struct MetalAtlasState {
device: AssertSend<Device>,
monochrome_textures: Vec<MetalAtlasTexture>,
polychrome_textures: Vec<MetalAtlasTexture>,
path_textures: Vec<MetalAtlasTexture>,
monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
path_textures: AtlasTextureList<MetalAtlasTexture>,
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
}
@@ -78,6 +78,38 @@ impl PlatformAtlas for MetalAtlas {
Ok(Some(tile))
}
}
fn remove(&self, key: &AtlasKey) {
let mut lock = self.0.lock();
let Some(id) = lock.tiles_by_key.get(key).map(|v| v.texture_id) else {
return;
};
let textures = match id.kind {
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
AtlasTextureKind::Path => &mut lock.polychrome_textures,
};
let Some(texture_slot) = textures
.textures
.iter_mut()
.find(|texture| texture.as_ref().is_some_and(|v| v.id == id))
else {
return;
};
if let Some(mut texture) = texture_slot.take() {
texture.decrement_ref_count();
if texture.is_unreferenced() {
textures.free_list.push(id.index as usize);
lock.tiles_by_key.remove(key);
} else {
*texture_slot = Some(texture);
}
}
}
}
impl MetalAtlasState {
@@ -86,20 +118,24 @@ impl MetalAtlasState {
size: Size<DevicePixels>,
texture_kind: AtlasTextureKind,
) -> Option<AtlasTile> {
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
{
let textures = match texture_kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
.or_else(|| {
let texture = self.push_texture(size, texture_kind);
texture.allocate(size)
})
if let Some(tile) = textures
.iter_mut()
.rev()
.find_map(|texture| texture.allocate(size))
{
return Some(tile);
}
}
let texture = self.push_texture(size, texture_kind);
texture.allocate(size)
}
fn push_texture(
@@ -140,21 +176,31 @@ impl MetalAtlasState {
texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor);
let textures = match kind {
let texture_list = match kind {
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
AtlasTextureKind::Path => &mut self.path_textures,
};
let index = texture_list.free_list.pop();
let atlas_texture = MetalAtlasTexture {
id: AtlasTextureId {
index: textures.len() as u32,
index: index.unwrap_or(texture_list.textures.len()) as u32,
kind,
},
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
metal_texture: AssertSend(metal_texture),
live_atlas_keys: 0,
};
textures.push(atlas_texture);
textures.last_mut().unwrap()
if let Some(ix) = index {
texture_list.textures[ix] = Some(atlas_texture);
texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
} else {
texture_list.textures.push(Some(atlas_texture));
texture_list.textures.last_mut().unwrap().as_mut().unwrap()
}
}
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
@@ -163,7 +209,7 @@ impl MetalAtlasState {
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
crate::AtlasTextureKind::Path => &self.path_textures,
};
&textures[id.index as usize]
textures[id.index as usize].as_ref().unwrap()
}
}
@@ -171,6 +217,7 @@ struct MetalAtlasTexture {
id: AtlasTextureId,
allocator: BucketedAtlasAllocator,
metal_texture: AssertSend<metal::Texture>,
live_atlas_keys: u32,
}
impl MetalAtlasTexture {
@@ -189,6 +236,7 @@ impl MetalAtlasTexture {
},
padding: 0,
};
self.live_atlas_keys += 1;
Some(tile)
}
@@ -215,6 +263,14 @@ impl MetalAtlasTexture {
_ => unimplemented!(),
}
}
fn decrement_ref_count(&mut self) {
self.live_atlas_keys -= 1;
}
fn is_unreferenced(&mut self) -> bool {
self.live_atlas_keys == 0
}
}
impl From<Size<DevicePixels>> for etagere::Size {

View File

@@ -844,7 +844,9 @@ impl Platform for MacPlatform {
let app: id = msg_send![APP_CLASS, sharedApplication];
let mut state = self.0.lock();
let actions = &mut state.menu_actions;
app.setMainMenu_(self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap));
let menu = self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap);
drop(state);
app.setMainMenu_(menu);
}
}

View File

@@ -38,6 +38,7 @@ use std::{
cell::Cell,
ffi::{c_void, CStr},
mem,
ops::Range,
path::PathBuf,
ptr::{self, NonNull},
rc::Rc,
@@ -1283,18 +1284,17 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
}
if event.is_held {
let handled = with_input_handler(&this, |input_handler| {
if !input_handler.apple_press_and_hold_enabled() {
input_handler.replace_text_in_range(
None,
&event.keystroke.ime_key.unwrap_or(event.keystroke.key),
);
if let Some(key_char) = event.keystroke.key_char.as_ref() {
let handled = with_input_handler(&this, |input_handler| {
if !input_handler.apple_press_and_hold_enabled() {
input_handler.replace_text_in_range(None, &key_char);
return YES;
}
NO
});
if handled == Some(YES) {
return YES;
}
NO
});
if handled == Some(YES) {
return YES;
}
}
@@ -1437,7 +1437,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
let keystroke = Keystroke {
modifiers: Default::default(),
key: ".".into(),
ime_key: None,
key_char: None,
};
let event = PlatformInput::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
@@ -1755,15 +1755,21 @@ extern "C" fn attributed_substring_for_proposed_range(
this: &Object,
_: Sel,
range: NSRange,
_actual_range: *mut c_void,
actual_range: *mut c_void,
) -> id {
with_input_handler(this, |input_handler| {
let range = range.to_range()?;
if range.is_empty() {
return None;
}
let mut adjusted: Option<Range<usize>> = None;
let selected_text = input_handler.text_for_range(range.clone())?;
let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?;
if let Some(adjusted) = adjusted {
if adjusted != range {
unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) };
}
}
unsafe {
let string: id = msg_send![class!(NSAttributedString), alloc];
let string: id = msg_send![string, initWithString: ns_string(&selected_text)];

View File

@@ -339,4 +339,9 @@ impl PlatformAtlas for TestAtlas {
Ok(Some(state.tiles[key].clone()))
}
fn remove(&self, key: &AtlasKey) {
let mut state = self.0.lock();
state.tiles.remove(key);
}
}

View File

@@ -386,7 +386,7 @@ fn handle_char_msg(
return Some(1);
};
drop(lock);
let ime_key = keystroke.ime_key.clone();
let key_char = keystroke.key_char.clone();
let event = KeyDownEvent {
keystroke,
is_held: lparam.0 & (0x1 << 30) > 0,
@@ -397,7 +397,7 @@ fn handle_char_msg(
if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
return Some(0);
}
let Some(ime_char) = ime_key else {
let Some(ime_char) = key_char else {
return Some(1);
};
with_input_handler(&state_ptr, |input_handler| {
@@ -1172,7 +1172,7 @@ fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
Some(Keystroke {
modifiers,
key,
ime_key: None,
key_char: None,
})
}
@@ -1220,7 +1220,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
return Some(KeystrokeOrModifier::Keystroke(Keystroke {
modifiers,
key: format!("f{}", offset + 1),
ime_key: None,
key_char: None,
}));
};
return None;
@@ -1231,7 +1231,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
Some(KeystrokeOrModifier::Keystroke(Keystroke {
modifiers,
key,
ime_key: None,
key_char: None,
}))
}
@@ -1253,7 +1253,7 @@ fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
Some(Keystroke {
modifiers,
key,
ime_key: Some(first_char.to_string()),
key_char: Some(first_char.to_string()),
})
}
}
@@ -1327,7 +1327,7 @@ fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke>
Some(Keystroke {
modifiers,
key,
ime_key: None,
key_char: None,
})
}

View File

@@ -292,7 +292,7 @@ impl Platform for WindowsPlatform {
pid,
app_path.display(),
);
let restart_process = std::process::Command::new("powershell.exe")
let restart_process = util::command::new_std_command("powershell.exe")
.arg("-command")
.arg(script)
.spawn();

View File

@@ -1,4 +1,4 @@
use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem, SharedString};
use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem, SharedString, TextRun};
use collections::HashMap;
use std::{iter, sync::Arc};
@@ -104,6 +104,7 @@ impl LineWrapper {
line: SharedString,
truncate_width: Pixels,
ellipsis: Option<&str>,
runs: &mut Vec<TextRun>,
) -> SharedString {
let mut width = px(0.);
let mut ellipsis_width = px(0.);
@@ -124,15 +125,15 @@ impl LineWrapper {
width += char_width;
if width.floor() > truncate_width {
return SharedString::from(format!(
"{}{}",
&line[..truncate_ix],
ellipsis.unwrap_or("")
));
let ellipsis = ellipsis.unwrap_or("");
let result = SharedString::from(format!("{}{}", &line[..truncate_ix], ellipsis));
update_runs_after_truncation(&result, ellipsis, runs);
return result;
}
}
line.clone()
line
}
pub(crate) fn is_word_char(c: char) -> bool {
@@ -195,6 +196,23 @@ impl LineWrapper {
}
}
fn update_runs_after_truncation(result: &str, ellipsis: &str, runs: &mut Vec<TextRun>) {
let mut truncate_at = result.len() - ellipsis.len();
let mut run_end = None;
for (run_index, run) in runs.iter_mut().enumerate() {
if run.len <= truncate_at {
truncate_at -= run.len;
} else {
run.len = truncate_at + ellipsis.len();
run_end = Some(run_index + 1);
break;
}
}
if let Some(run_end) = run_end {
runs.truncate(run_end);
}
}
/// A boundary between two lines of text.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Boundary {
@@ -213,7 +231,9 @@ impl Boundary {
#[cfg(test)]
mod tests {
use super::*;
use crate::{font, TestAppContext, TestDispatcher};
use crate::{
font, Font, FontFeatures, FontStyle, FontWeight, Hsla, TestAppContext, TestDispatcher,
};
#[cfg(target_os = "macos")]
use crate::{TextRun, WindowTextSystem, WrapBoundary};
use rand::prelude::*;
@@ -232,6 +252,26 @@ mod tests {
LineWrapper::new(id, px(16.), cx.text_system().platform_text_system.clone())
}
fn generate_test_runs(input_run_len: &[usize]) -> Vec<TextRun> {
input_run_len
.iter()
.map(|run_len| TextRun {
len: *run_len,
font: Font {
family: "Dummy".into(),
features: FontFeatures::default(),
fallbacks: None,
weight: FontWeight::default(),
style: FontStyle::Normal,
},
color: Hsla::default(),
background_color: None,
underline: None,
strikethrough: None,
})
.collect()
}
#[test]
fn test_wrap_line() {
let mut wrapper = build_wrapper();
@@ -293,28 +333,135 @@ mod tests {
fn test_truncate_line() {
let mut wrapper = build_wrapper();
assert_eq!(
wrapper.truncate_line("aa bbb cccc ddddd eeee ffff gggg".into(), px(220.), None),
"aa bbb cccc ddddd eeee"
fn perform_test(
wrapper: &mut LineWrapper,
text: &'static str,
result: &'static str,
ellipsis: Option<&str>,
) {
let dummy_run_lens = vec![text.len()];
let mut dummy_runs = generate_test_runs(&dummy_run_lens);
assert_eq!(
wrapper.truncate_line(text.into(), px(220.), ellipsis, &mut dummy_runs),
result
);
assert_eq!(dummy_runs.first().unwrap().len, result.len());
}
perform_test(
&mut wrapper,
"aa bbb cccc ddddd eeee ffff gggg",
"aa bbb cccc ddddd eeee",
None,
);
assert_eq!(
wrapper.truncate_line(
"aa bbb cccc ddddd eeee ffff gggg".into(),
px(220.),
Some("")
),
"aa bbb cccc ddddd eee…"
perform_test(
&mut wrapper,
"aa bbb cccc ddddd eeee ffff gggg",
"aa bbb cccc ddddd eee…",
Some(""),
);
assert_eq!(
wrapper.truncate_line(
"aa bbb cccc ddddd eeee ffff gggg".into(),
px(220.),
Some("......")
),
"aa bbb cccc dddd......"
perform_test(
&mut wrapper,
"aa bbb cccc ddddd eeee ffff gggg",
"aa bbb cccc dddd......",
Some("......"),
);
}
#[test]
fn test_truncate_multiple_runs() {
let mut wrapper = build_wrapper();
fn perform_test(
wrapper: &mut LineWrapper,
text: &'static str,
result: &str,
run_lens: &[usize],
result_run_len: &[usize],
line_width: Pixels,
) {
let mut dummy_runs = generate_test_runs(run_lens);
assert_eq!(
wrapper.truncate_line(text.into(), line_width, Some(""), &mut dummy_runs),
result
);
for (run, result_len) in dummy_runs.iter().zip(result_run_len) {
assert_eq!(run.len, *result_len);
}
}
// Case 0: Normal
// Text: abcdefghijkl
// Runs: Run0 { len: 12, ... }
//
// Truncate res: abcd… (truncate_at = 4)
// Run res: Run0 { string: abcd…, len: 7, ... }
perform_test(&mut wrapper, "abcdefghijkl", "abcd…", &[12], &[7], px(50.));
// Case 1: Drop some runs
// Text: abcdefghijkl
// Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... }
//
// Truncate res: abcdef… (truncate_at = 6)
// Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: ef…, len:
// 5, ... }
perform_test(
&mut wrapper,
"abcdefghijkl",
"abcdef…",
&[4, 4, 4],
&[4, 5],
px(70.),
);
// Case 2: Truncate at start of some run
// Text: abcdefghijkl
// Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... }
//
// Truncate res: abcdefgh… (truncate_at = 8)
// Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: efgh, len:
// 4, ... }, Run2 { string: …, len: 3, ... }
perform_test(
&mut wrapper,
"abcdefghijkl",
"abcdefgh…",
&[4, 4, 4],
&[4, 4, 3],
px(90.),
);
}
#[test]
fn test_update_run_after_truncation() {
fn perform_test(result: &str, run_lens: &[usize], result_run_lens: &[usize]) {
let mut dummy_runs = generate_test_runs(run_lens);
update_runs_after_truncation(result, "", &mut dummy_runs);
for (run, result_len) in dummy_runs.iter().zip(result_run_lens) {
assert_eq!(run.len, *result_len);
}
}
// Case 0: Normal
// Text: abcdefghijkl
// Runs: Run0 { len: 12, ... }
//
// Truncate res: abcd… (truncate_at = 4)
// Run res: Run0 { string: abcd…, len: 7, ... }
perform_test("abcd…", &[12], &[7]);
// Case 1: Drop some runs
// Text: abcdefghijkl
// Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... }
//
// Truncate res: abcdef… (truncate_at = 6)
// Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: ef…, len:
// 5, ... }
perform_test("abcdef…", &[4, 4, 4], &[4, 5]);
// Case 2: Truncate at start of some run
// Text: abcdefghijkl
// Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... }
//
// Truncate res: abcdefgh… (truncate_at = 8)
// Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: efgh, len:
// 4, ... }, Run2 { string: …, len: 3, ... }
perform_test("abcdefgh…", &[4, 4, 4], &[4, 4, 3]);
}
#[test]
fn test_is_word_char() {
#[track_caller]

View File

@@ -2685,6 +2685,20 @@ impl<'a> WindowContext<'a> {
});
}
/// Removes an image from the sprite atlas.
pub fn drop_image(&mut self, data: Arc<RenderImage>) -> Result<()> {
for frame_index in 0..data.frame_count() {
let params = RenderImageParams {
image_id: data.id,
frame_index,
};
self.window.sprite_atlas.remove(&params.clone().into());
}
Ok(())
}
#[must_use]
/// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which
/// layout is being requested, along with the layout ids of any children. This method is called during
@@ -3038,7 +3052,7 @@ impl<'a> WindowContext<'a> {
return true;
}
if let Some(input) = keystroke.with_simulated_ime().ime_key {
if let Some(input) = keystroke.key_char {
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
input_handler.dispatch_input(&input, self);
self.window.platform_window.set_input_handler(input_handler);
@@ -3267,7 +3281,7 @@ impl<'a> WindowContext<'a> {
if let Some(key) = key {
keystroke = Some(Keystroke {
key: key.to_string(),
ime_key: None,
key_char: None,
modifiers: Modifiers::default(),
});
}
@@ -3482,13 +3496,7 @@ impl<'a> WindowContext<'a> {
if !self.propagate_event {
continue 'replay;
}
if let Some(input) = replay
.keystroke
.with_simulated_ime()
.ime_key
.as_ref()
.cloned()
{
if let Some(input) = replay.keystroke.key_char.as_ref().cloned() {
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
input_handler.dispatch_input(&input, self);
self.window.platform_window.set_input_handler(input_handler)

View File

@@ -116,7 +116,7 @@ impl Item for ImageView {
.map(Icon::from_path)
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft
}

View File

@@ -3,9 +3,33 @@ use std::sync::Arc;
use anyhow::Result;
use async_trait::async_trait;
use extension::Extension;
use extension::{Extension, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy};
use gpui::AppContext;
use crate::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
use crate::{
IndexedDocsDatabase, IndexedDocsProvider, IndexedDocsRegistry, PackageName, ProviderId,
};
pub fn init(cx: &mut AppContext) {
let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_indexed_docs_provider_proxy(IndexedDocsRegistryProxy {
indexed_docs_registry: IndexedDocsRegistry::global(cx),
});
}
struct IndexedDocsRegistryProxy {
indexed_docs_registry: Arc<IndexedDocsRegistry>,
}
impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy {
fn register_indexed_docs_provider(&self, extension: Arc<dyn Extension>, provider_id: Arc<str>) {
self.indexed_docs_registry
.register_provider(Box::new(ExtensionIndexedDocsProvider::new(
extension,
ProviderId(provider_id),
)));
}
}
pub struct ExtensionIndexedDocsProvider {
extension: Arc<dyn Extension>,

View File

@@ -3,7 +3,14 @@ mod providers;
mod registry;
mod store;
use gpui::AppContext;
pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
pub use crate::providers::rustdoc::*;
pub use crate::registry::*;
pub use crate::store::*;
pub fn init(cx: &mut AppContext) {
IndexedDocsRegistry::init_global(cx);
extension_indexed_docs_provider::init(cx);
}

View File

@@ -20,7 +20,7 @@ impl IndexedDocsRegistry {
GlobalIndexedDocsRegistry::global(cx).0.clone()
}
pub fn init_global(cx: &mut AppContext) {
pub(crate) fn init_global(cx: &mut AppContext) {
GlobalIndexedDocsRegistry::set_global(
cx,
GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))),

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