Compare commits

...

49 Commits

Author SHA1 Message Date
Piotr Osiewicz
6486bb7cc4 Add elided lifetime to make merging with code-actions seamless 2024-04-26 17:29:56 +02:00
Piotr Osiewicz
de0c8bc368 Remove moot import 2024-04-26 17:20:33 +02:00
Piotr Osiewicz
af555a6189 Get rid of cx and TaskSource trait 2024-04-26 17:16:27 +02:00
Piotr Osiewicz
e4781ce5fa Remove TestTaskSource 2024-04-26 15:31:39 +02:00
Piotr Osiewicz
c128c3ac79 fix clippy lint 2024-04-26 13:22:51 +02:00
Piotr Osiewicz
893b0eb371 Strip unused deps 2024-04-26 13:21:49 +02:00
Piotr Osiewicz
8d05ee91b3 Fix up build and a bunch of warnings 2024-04-26 13:12:46 +02:00
Mikayla
4abd55d340 Finish collecting data to be able to spawn tasks 2024-04-25 16:52:01 -07:00
Piotr Osiewicz
8aac108d5a fixup! Wire through inventory querying 2024-04-25 16:52:01 -07:00
Piotr Osiewicz
3d41adfc53 Wire through inventory querying
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-04-25 16:52:01 -07:00
Piotr Osiewicz
dee6b5629f fixup! Fix up CI 2024-04-25 16:52:01 -07:00
Piotr Osiewicz
266949d56b Fix up CI 2024-04-25 16:52:01 -07:00
Mikayla
fade2f4843 WIP: Get git blame menu rendering 2024-04-25 16:52:01 -07:00
Mikayla
e5f9780c05 add tree sitter query implementation 2024-04-25 16:52:01 -07:00
Mikayla
a60785c423 Wire in new language query
Restore live updating language queries
2024-04-25 16:52:01 -07:00
Kyle Kelley
cf2272a949 Always submit function definitions in Simple mode too (#11016)
Switches Assistant2 to always provide functions. It's up to the model to
choose to use them. At a later point, the `Submit::Codebase` should
change the `tool_choice` to `query_codebase` rather than `auto`. For
now, I think this will improve the experience for folks testing.

Release Notes:

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

Release Notes:

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

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

By adding an entry in the keymap file

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

both of these issues are solved...

Release Notes:

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

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

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

Release Notes:

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

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

Release Notes:

- N/A

---------

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

New languages should be added as extensions.

Release Notes:

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

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

Release Notes:

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

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

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

#### Elixir LS

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

#### Next LS

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

#### Lexical

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

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

Release Notes:

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

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

Ref issue: #10898 

@bennetbo 

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


7dccbd8e3b (diff-c8d1735cb347ea08d03198df112343ec50a74de8d50414a6f3be6c6d674c6d19R161)

And this also used in other place:

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

## Test demo after updated

Looks like it is no side effect

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

Release Notes:

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


Demo: 


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

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

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

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

Release Notes:

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

Release Notes:

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


Release Notes:

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

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

Release Notes:

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

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

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

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

This reverts commit 5102e37a5b.

Release Notes:

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

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

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

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

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

Release Notes:

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



Release Notes:

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

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

Release Notes:

- N/A

---------

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

## Before


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


## After


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

## Release notes

Release Notes:

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


Release Notes:

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

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

Release Notes:

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

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

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

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

Release Notes:

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

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

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

### Before

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

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

### After

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

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

Release Notes:

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

- N/A

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

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

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

Release Notes:

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

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

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

Changes:

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

Release Notes:

- N/A
2024-04-24 13:03:56 -04:00
Joseph T. Lyons
facd04c902 v0.134.x dev 2024-04-24 12:46:30 -04:00
126 changed files with 4858 additions and 4290 deletions

View File

@@ -173,6 +173,11 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v4
with:
# We need to fetch more than one commit so that `script/draft-release-notes`
# is able to diff between the current and previous tag.
#
# 25 was chosen arbitrarily.
fetch-depth: 25
clean: false
submodules: "recursive"
@@ -206,7 +211,8 @@ jobs:
exit 1
fi
mkdir -p target/
script/draft-release-notes "$version" "$channel" > target/release-notes.md
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
- name: Generate license file
run: script/generate-licenses

61
Cargo.lock generated
View File

@@ -284,21 +284,21 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "ash"
version = "0.37.3+1.3.251"
version = "0.38.0+1.3.281"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
dependencies = [
"libloading 0.7.4",
"libloading 0.8.0",
]
[[package]]
name = "ash-window"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b912285a7c29f3a8f87ca6f55afc48768624e5e33ec17dbd2f2075903f5e35ab"
checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82"
dependencies = [
"ash",
"raw-window-handle 0.5.2",
"raw-window-handle 0.6.0",
"raw-window-metal",
]
@@ -1479,7 +1479,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.4.0"
source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d25292b2ce4#810ec594358aafea29a4a3d8ab601d25292b2ce4"
source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
dependencies = [
"ash",
"ash-window",
@@ -1500,7 +1500,7 @@ dependencies = [
"mint",
"naga",
"objc",
"raw-window-handle 0.5.2",
"raw-window-handle 0.6.0",
"slab",
"wasm-bindgen",
"web-sys",
@@ -1509,7 +1509,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.2.1"
source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d25292b2ce4#810ec594358aafea29a4a3d8ab601d25292b2ce4"
source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
dependencies = [
"proc-macro2",
"quote",
@@ -3390,6 +3390,7 @@ dependencies = [
"smol",
"snippet",
"sum_tree",
"task",
"text",
"theme",
"time",
@@ -3433,12 +3434,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "embed-manifest"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae"
[[package]]
name = "emojis"
version = "0.6.1"
@@ -4492,9 +4487,9 @@ dependencies = [
[[package]]
name = "gpu-alloc-ash"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2424bc9be88170e1a56e57c25d3d0e2dfdd22e8f328e892786aeb4da1415732"
checksum = "cbda7a18a29bc98c2e0de0435c347df935bf59489935d0cbd0b73f1679b6f79a"
dependencies = [
"ash",
"gpu-alloc-types",
@@ -4561,7 +4556,6 @@ dependencies = [
"postage",
"profiling",
"rand 0.8.5",
"raw-window-handle 0.5.2",
"raw-window-handle 0.6.0",
"refineable",
"resvg",
@@ -4724,6 +4718,7 @@ dependencies = [
"project",
"rpc",
"settings",
"shellexpand",
"util",
]
@@ -5552,12 +5547,9 @@ dependencies = [
"regex",
"rope",
"rust-embed",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"shellexpand",
"smol",
"task",
"text",
@@ -5568,12 +5560,10 @@ dependencies = [
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-elixir",
"tree-sitter-embedded-template",
"tree-sitter-go",
"tree-sitter-gomod",
"tree-sitter-gowork",
"tree-sitter-heex",
"tree-sitter-jsdoc",
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
@@ -7731,14 +7721,14 @@ checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
[[package]]
name = "raw-window-metal"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac4ea493258d54c24cb46aa9345d099e58e2ea3f30dd63667fc54fc892f18e76"
checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1"
dependencies = [
"cocoa",
"core-graphics",
"objc",
"raw-window-handle 0.5.2",
"raw-window-handle 0.6.0",
]
[[package]]
@@ -9488,7 +9478,6 @@ dependencies = [
"ctrlc",
"dialoguer",
"editor",
"embed-manifest",
"fuzzy",
"gpui",
"indoc",
@@ -9504,6 +9493,7 @@ dependencies = [
"strum",
"theme",
"ui",
"winresource",
]
[[package]]
@@ -9805,6 +9795,7 @@ dependencies = [
"futures 0.3.28",
"gpui",
"hex",
"parking_lot",
"schemars",
"serde",
"serde_json_lenient",
@@ -9817,7 +9808,6 @@ dependencies = [
name = "tasks_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"editor",
"file_icons",
"fuzzy",
@@ -10519,7 +10509,7 @@ dependencies = [
[[package]]
name = "tree-sitter"
version = "0.20.100"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=7f21c3b98c0749ac192da67a0d65dfe3eabc4a63#7f21c3b98c0749ac192da67a0d65dfe3eabc4a63"
source = "git+https://github.com/tree-sitter/tree-sitter?rev=528bcd2274814ca53711a57d71d1e3cf7abd73fe#528bcd2274814ca53711a57d71d1e3cf7abd73fe"
dependencies = [
"cc",
"regex",
@@ -11101,6 +11091,7 @@ dependencies = [
"futures 0.3.28",
"gpui",
"indoc",
"itertools 0.11.0",
"language",
"log",
"lsp",
@@ -12607,7 +12598,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.133.0"
version = "0.134.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -12633,7 +12624,6 @@ dependencies = [
"db",
"diagnostics",
"editor",
"embed-manifest",
"env_logger",
"extension",
"extensions_ui",
@@ -12724,7 +12714,7 @@ dependencies = [
[[package]]
name = "zed_dart"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12736,6 +12726,13 @@ dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_elixir"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_elm"
version = "0.0.1"

View File

@@ -110,6 +110,7 @@ members = [
"extensions/csharp",
"extensions/dart",
"extensions/deno",
"extensions/elixir",
"extensions/elm",
"extensions/emmet",
"extensions/erlang",
@@ -249,9 +250,8 @@ async-recursion = "1.0.0"
async-tar = "0.4.2"
async-trait = "0.1"
bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "810ec594358aafea29a4a3d8ab601d25292b2ce4" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "810ec594358aafea29a4a3d8ab601d25292b2ce4" }
blade-rwh = { package = "raw-window-handle", version = "0.5" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
cap-std = "3.0"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
@@ -407,7 +407,7 @@ features = [
]
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f21c3b98c0749ac192da67a0d65dfe3eabc4a63" }
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "528bcd2274814ca53711a57d71d1e3cf7abd73fe" }
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }

View File

@@ -297,13 +297,8 @@
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
"ctrl-alt-shift-up": [
"editor::DuplicateLine",
{
"move_upwards": true
}
],
"ctrl-alt-shift-down": "editor::DuplicateLine",
"ctrl-alt-shift-up": "editor::DuplicateLineUp",
"ctrl-alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
@@ -593,12 +588,6 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "ChatPanel > MessageEditor",
"bindings": {
"escape": "chat_panel::CloseReplyPreview"
}
},
{
"context": "FileFinder",
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }

View File

@@ -212,7 +212,8 @@
"context": "AssistantChat > Editor", // Used in the assistant2 crate
"bindings": {
"enter": ["assistant2::Submit", "Simple"],
"cmd-enter": ["assistant2::Submit", "Codebase"]
"cmd-enter": ["assistant2::Submit", "Codebase"],
"escape": "assistant2::Cancel"
}
},
{

View File

@@ -17,7 +17,11 @@
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"cmd-q": "storybook::Quit"
"cmd-q": "storybook::Quit",
"backspace": "editor::Backspace",
"delete": "editor::Delete",
"left": "editor::MoveLeft",
"right": "editor::MoveRight"
}
}
]

View File

@@ -555,27 +555,6 @@
// Existing terminals will not pick up this change until they are recreated.
// "max_scroll_history_lines": 10000,
},
// Settings specific to our elixir integration
"elixir": {
// Change the LSP zed uses for elixir.
// Note that changing this setting requires a restart of Zed
// to take effect.
//
// May take 3 values:
// 1. Use the standard ElixirLS, this is the default
// "lsp": "elixir_ls"
// 2. Use the experimental NextLs
// "lsp": "next_ls",
// 3. Use a language server installed locally on your machine:
// "lsp": {
// "local": {
// "path": "~/next-ls/bin/start",
// "arguments": ["--stdio"]
// }
// },
//
"lsp": "elixir_ls"
},
"code_actions_on_format": {},
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should

View File

@@ -1,4 +1,5 @@
use anyhow::Context as _;
/// This example creates a basic Chat UI with a function for rolling a die.
use anyhow::{Context as _, Result};
use assets::Assets;
use assistant2::AssistantPanel;
use assistant_tooling::{LanguageModelTool, ToolRegistry};
@@ -83,9 +84,32 @@ struct DiceRoll {
rolls: Vec<DieRoll>,
}
pub struct DiceView {
result: Result<DiceRoll>,
}
impl Render for DiceView {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let output = match &self.result {
Ok(output) => output,
Err(_) => return "Somehow dice failed 🎲".into_any_element(),
};
h_flex()
.children(
output
.rolls
.iter()
.map(|roll| div().p_2().child(roll.render())),
)
.into_any_element()
}
}
impl LanguageModelTool for RollDiceTool {
type Input = DiceParams;
type Output = DiceRoll;
type View = DiceView;
fn name(&self) -> String {
"roll_dice".to_string()
@@ -110,23 +134,21 @@ impl LanguageModelTool for RollDiceTool {
return Task::ready(Ok(DiceRoll { rolls }));
}
fn render(
_tool_call_id: &str,
_input: &Self::Input,
output: &Self::Output,
_cx: &mut WindowContext,
) -> gpui::AnyElement {
h_flex()
.children(
output
.rolls
.iter()
.map(|roll| div().p_2().child(roll.render())),
)
.into_any_element()
fn new_view(
_tool_call_id: String,
_input: Self::Input,
result: Result<Self::Output>,
cx: &mut WindowContext,
) -> gpui::View<Self::View> {
cx.new_view(|_cx| DiceView { result })
}
fn format(_input: &Self::Input, output: &Self::Output) -> String {
fn format(_: &Self::Input, output: &Result<Self::Output>) -> String {
let output = match output {
Ok(output) => output,
Err(_) => return "Somehow dice failed 🎲".to_string(),
};
let mut result = String::new();
for roll in &output.rolls {
let die = &roll.die;

View File

@@ -34,8 +34,6 @@ pub use assistant_settings::AssistantSettings;
const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
// gpui::actions!(assistant, [Submit]);
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
pub struct Submit(SubmitMode);
@@ -50,7 +48,7 @@ pub enum SubmitMode {
Codebase,
}
gpui::actions!(assistant2, [ToggleFocus]);
gpui::actions!(assistant2, [Cancel, ToggleFocus]);
gpui::impl_actions!(assistant2, [Submit]);
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
@@ -256,6 +254,21 @@ impl AssistantChat {
})
}
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
if self.pending_completion.take().is_none() {
cx.propagate();
return;
}
if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
if message.body.text.is_empty() {
self.pop_message(cx);
} else {
self.push_new_user_message(false, cx);
}
}
}
fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
let Some(focused_message_id) = self.focused_message_id(cx) else {
log::error!("unexpected state: no user message editor is focused.");
@@ -282,6 +295,7 @@ impl AssistantChat {
.focus_handle(cx)
.contains_focused(cx);
this.push_new_user_message(focus, cx);
this.pending_completion = None;
})
.context("Failed to push new user message")
.log_err();
@@ -300,7 +314,8 @@ impl AssistantChat {
let completion = this.update(cx, |this, cx| {
this.push_new_assistant_message(cx);
let definitions = if call_count < limit && matches!(mode, SubmitMode::Codebase)
let definitions = if call_count < limit
&& matches!(mode, SubmitMode::Codebase | SubmitMode::Simple)
{
this.tool_registry.definitions()
} else {
@@ -308,9 +323,11 @@ impl AssistantChat {
};
call_count += 1;
let messages = this.completion_messages(cx);
CompletionProvider::get(cx).complete(
this.model.clone(),
this.completion_messages(cx),
messages,
Vec::new(),
1.0,
definitions,
@@ -393,6 +410,10 @@ impl AssistantChat {
}
let tools = join_all(tool_tasks.into_iter()).await;
// If the WindowContext went away for any tool's view we don't include it
// especially since the below call would fail for the same reason.
let tools = tools.into_iter().filter_map(|tool| tool.ok()).collect();
this.update(cx, |this, cx| {
if let Some(ChatMessage::Assistant(AssistantMessage { tool_calls, .. })) =
this.messages.last_mut()
@@ -453,6 +474,17 @@ impl AssistantChat {
cx.notify();
}
fn pop_message(&mut self, cx: &mut ViewContext<Self>) {
if self.messages.is_empty() {
return;
}
self.messages.pop();
self.list_state
.splice(self.messages.len()..self.messages.len() + 1, 0);
cx.notify();
}
fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
if let Some(index) = self.messages.iter().position(|message| match message {
ChatMessage::User(message) => message.id == last_message_id,
@@ -536,10 +568,9 @@ impl AssistantChat {
let result = &tool_call.result;
let name = tool_call.name.clone();
match result {
Some(result) => div()
.p_2()
.child(result.render(&name, &tool_call.id, cx))
.into_any(),
Some(result) => {
div().p_2().child(result.into_any_element(&name)).into_any()
}
None => div()
.p_2()
.child(Label::new(name).color(Color::Modified))
@@ -552,7 +583,7 @@ impl AssistantChat {
}
}
fn completion_messages(&self, cx: &WindowContext) -> Vec<CompletionMessage> {
fn completion_messages(&self, cx: &mut WindowContext) -> Vec<CompletionMessage> {
let mut completion_messages = Vec::new();
for message in &self.messages {
@@ -677,6 +708,7 @@ impl Render for AssistantChat {
.flex_1()
.v_flex()
.key_context("AssistantChat")
.on_action(cx.listener(Self::cancel))
.text_color(Color::Default.color(cx))
.child(self.render_model_dropdown(cx))
.child(list(self.list_state.clone()).flex_1())

View File

@@ -1,10 +1,10 @@
use anyhow::Result;
use assistant_tooling::LanguageModelTool;
use gpui::{prelude::*, AnyElement, AppContext, Model, Task};
use gpui::{prelude::*, AppContext, Model, Task};
use project::Fs;
use schemars::JsonSchema;
use semantic_index::ProjectIndex;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use std::sync::Arc;
use ui::{
div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, SharedString,
@@ -14,11 +14,13 @@ use util::ResultExt as _;
const DEFAULT_SEARCH_LIMIT: usize = 20;
#[derive(Serialize, Clone)]
#[derive(Clone)]
pub struct CodebaseExcerpt {
path: SharedString,
text: SharedString,
score: f32,
element_id: ElementId,
expanded: bool,
}
// Note: Comments on a `LanguageModelTool::Input` become descriptions on the generated JSON schema as shown to the language model.
@@ -32,6 +34,79 @@ pub struct CodebaseQuery {
limit: Option<usize>,
}
pub struct ProjectIndexView {
input: CodebaseQuery,
output: Result<Vec<CodebaseExcerpt>>,
}
impl ProjectIndexView {
fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext<Self>) {
if let Ok(excerpts) = &mut self.output {
if let Some(excerpt) = excerpts
.iter_mut()
.find(|excerpt| excerpt.element_id == element_id)
{
excerpt.expanded = !excerpt.expanded;
cx.notify();
}
}
}
}
impl Render for ProjectIndexView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let query = self.input.query.clone();
let result = &self.output;
let excerpts = match result {
Err(err) => {
return div().child(Label::new(format!("Error: {}", err)).color(Color::Error));
}
Ok(excerpts) => excerpts,
};
div()
.v_flex()
.gap_2()
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.child(Label::new("Query: ").color(Color::Modified))
.child(Label::new(query).color(Color::Muted)),
),
)
.children(excerpts.iter().map(|excerpt| {
let element_id = excerpt.element_id.clone();
let expanded = excerpt.expanded;
CollapsibleContainer::new(element_id.clone(), expanded)
.start_slot(
h_flex()
.gap_1()
.child(Icon::new(IconName::File).color(Color::Muted))
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
)
.on_click(cx.listener(move |this, _, cx| {
this.toggle_expanded(element_id.clone(), cx);
}))
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(
excerpt.text.clone(), // todo!(): Show as an editor block
),
)
}))
}
}
pub struct ProjectIndexTool {
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
@@ -47,6 +122,7 @@ impl ProjectIndexTool {
impl LanguageModelTool for ProjectIndexTool {
type Input = CodebaseQuery;
type Output = Vec<CodebaseExcerpt>;
type View = ProjectIndexView;
fn name(&self) -> String {
"query_codebase".to_string()
@@ -90,6 +166,8 @@ impl LanguageModelTool for ProjectIndexTool {
}
anyhow::Ok(CodebaseExcerpt {
element_id: ElementId::Name(nanoid::nanoid!().into()),
expanded: false,
path: path.to_string_lossy().to_string().into(),
text: SharedString::from(text[start..end].to_string()),
score: result.score,
@@ -106,71 +184,37 @@ impl LanguageModelTool for ProjectIndexTool {
})
}
fn render(
_tool_call_id: &str,
input: &Self::Input,
excerpts: &Self::Output,
fn new_view(
_tool_call_id: String,
input: Self::Input,
output: Result<Self::Output>,
cx: &mut WindowContext,
) -> AnyElement {
let query = input.query.clone();
div()
.v_flex()
.gap_2()
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.child(Label::new("Query: ").color(Color::Modified))
.child(Label::new(query).color(Color::Muted)),
),
)
.children(excerpts.iter().map(|excerpt| {
// This render doesn't have state/model, so we can't use the listener
// let expanded = excerpt.expanded;
// let element_id = excerpt.element_id.clone();
let element_id = ElementId::Name(nanoid::nanoid!().into());
let expanded = false;
CollapsibleContainer::new(element_id.clone(), expanded)
.start_slot(
h_flex()
.gap_1()
.child(Icon::new(IconName::File).color(Color::Muted))
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
)
// .on_click(cx.listener(move |this, _, cx| {
// this.toggle_expanded(element_id.clone(), cx);
// }))
.child(
div()
.p_2()
.rounded_md()
.bg(cx.theme().colors().editor_background)
.child(
excerpt.text.clone(), // todo!(): Show as an editor block
),
)
}))
.into_any_element()
) -> gpui::View<Self::View> {
cx.new_view(|_cx| ProjectIndexView { input, output })
}
fn format(_input: &Self::Input, excerpts: &Self::Output) -> String {
let mut body = "Semantic search results:\n".to_string();
fn format(_input: &Self::Input, output: &Result<Self::Output>) -> String {
match &output {
Ok(excerpts) => {
if excerpts.len() == 0 {
return "No results found".to_string();
}
for excerpt in excerpts {
body.push_str("Excerpt from ");
body.push_str(excerpt.path.as_ref());
body.push_str(", score ");
body.push_str(&excerpt.score.to_string());
body.push_str(":\n");
body.push_str("~~~\n");
body.push_str(excerpt.text.as_ref());
body.push_str("~~~\n");
let mut body = "Semantic search results:\n".to_string();
for excerpt in excerpts {
body.push_str("Excerpt from ");
body.push_str(excerpt.path.as_ref());
body.push_str(", score ");
body.push_str(&excerpt.score.to_string());
body.push_str(":\n");
body.push_str("~~~\n");
body.push_str(excerpt.text.as_ref());
body.push_str("~~~\n");
}
body
}
Err(err) => format!("Error: {}", err),
}
body
}
}

View File

@@ -1,13 +1,16 @@
use anyhow::{anyhow, Result};
use gpui::{AnyElement, AppContext, Task, WindowContext};
use std::{any::Any, collections::HashMap};
use gpui::{Task, WindowContext};
use std::collections::HashMap;
use crate::tool::{
LanguageModelTool, ToolFunctionCall, ToolFunctionCallResult, ToolFunctionDefinition,
};
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Fn(&ToolFunctionCall, &AppContext) -> Task<ToolFunctionCall>>>,
tools: HashMap<
String,
Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>,
>,
definitions: Vec<ToolFunctionDefinition>,
}
@@ -24,77 +27,45 @@ impl ToolRegistry {
}
pub fn register<T: 'static + LanguageModelTool>(&mut self, tool: T) -> Result<()> {
fn render<T: 'static + LanguageModelTool>(
tool_call_id: &str,
input: &Box<dyn Any>,
output: &Box<dyn Any>,
cx: &mut WindowContext,
) -> AnyElement {
T::render(
tool_call_id,
input.as_ref().downcast_ref::<T::Input>().unwrap(),
output.as_ref().downcast_ref::<T::Output>().unwrap(),
cx,
)
}
fn format<T: 'static + LanguageModelTool>(
input: &Box<dyn Any>,
output: &Box<dyn Any>,
) -> String {
T::format(
input.as_ref().downcast_ref::<T::Input>().unwrap(),
output.as_ref().downcast_ref::<T::Output>().unwrap(),
)
}
self.definitions.push(tool.definition());
let name = tool.name();
let previous = self.tools.insert(
name.clone(),
Box::new(move |tool_call: &ToolFunctionCall, cx: &AppContext| {
let name = tool_call.name.clone();
let arguments = tool_call.arguments.clone();
let id = tool_call.id.clone();
// registry.call(tool_call, cx)
Box::new(
move |tool_call: &ToolFunctionCall, cx: &mut WindowContext| {
let name = tool_call.name.clone();
let arguments = tool_call.arguments.clone();
let id = tool_call.id.clone();
let Ok(input) = serde_json::from_str::<T::Input>(arguments.as_str()) else {
return Task::ready(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::ParsingFailed),
});
};
let result = tool.execute(&input, cx);
cx.spawn(move |_cx| async move {
match result.await {
Ok(result) => {
let result: T::Output = result;
ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::Finished {
input: Box::new(input),
output: Box::new(result),
render_fn: render::<T>,
format_fn: format::<T>,
}),
}
}
Err(_error) => ToolFunctionCall {
let Ok(input) = serde_json::from_str::<T::Input>(arguments.as_str()) else {
return Task::ready(Ok(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::ExecutionFailed {
input: Box::new(input),
result: Some(ToolFunctionCallResult::ParsingFailed),
}));
};
let result = tool.execute(&input, cx);
cx.spawn(move |mut cx| async move {
let result: Result<T::Output> = result.await;
let for_model = T::format(&input, &result);
let view = cx.update(|cx| T::new_view(id.clone(), input, result, cx))?;
Ok(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::Finished {
view: view.into(),
for_model,
}),
},
}
})
}),
})
})
},
),
);
if previous.is_some() {
@@ -104,7 +75,12 @@ impl ToolRegistry {
Ok(())
}
pub fn call(&self, tool_call: &ToolFunctionCall, cx: &AppContext) -> Task<ToolFunctionCall> {
/// Task yields an error if the window for the given WindowContext is closed before the task completes.
pub fn call(
&self,
tool_call: &ToolFunctionCall,
cx: &mut WindowContext,
) -> Task<Result<ToolFunctionCall>> {
let name = tool_call.name.clone();
let arguments = tool_call.arguments.clone();
let id = tool_call.id.clone();
@@ -113,12 +89,12 @@ impl ToolRegistry {
Some(tool) => tool,
None => {
let name = name.clone();
return Task::ready(ToolFunctionCall {
return Task::ready(Ok(ToolFunctionCall {
id,
name: name.clone(),
arguments,
result: Some(ToolFunctionCallResult::NoSuchTool),
});
}));
}
};
@@ -128,12 +104,10 @@ impl ToolRegistry {
#[cfg(test)]
mod test {
use super::*;
use gpui::View;
use gpui::{div, prelude::*, Render, TestAppContext};
use schemars::schema_for;
use gpui::{div, AnyElement, Element, ParentElement, TestAppContext, WindowContext};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
@@ -155,9 +129,20 @@ mod test {
unit: String,
}
struct WeatherView {
result: WeatherResult,
}
impl Render for WeatherView {
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
div().child(format!("temperature: {}", self.result.temperature))
}
}
impl LanguageModelTool for WeatherTool {
type Input = WeatherQuery;
type Output = WeatherResult;
type View = WeatherView;
fn name(&self) -> String {
"get_current_weather".to_string()
@@ -167,7 +152,11 @@ mod test {
"Fetches the current weather for a given location.".to_string()
}
fn execute(&self, input: &WeatherQuery, _cx: &AppContext) -> Task<Result<Self::Output>> {
fn execute(
&self,
input: &Self::Input,
_cx: &gpui::AppContext,
) -> Task<Result<Self::Output>> {
let _location = input.location.clone();
let _unit = input.unit.clone();
@@ -176,25 +165,20 @@ mod test {
Task::ready(Ok(weather))
}
fn render(
_tool_call_id: &str,
_input: &Self::Input,
output: &Self::Output,
_cx: &mut WindowContext,
) -> AnyElement {
div()
.child(format!(
"The current temperature in {} is {} {}",
output.location, output.temperature, output.unit
))
.into_any()
fn new_view(
_tool_call_id: String,
_input: Self::Input,
result: Result<Self::Output>,
cx: &mut WindowContext,
) -> View<Self::View> {
cx.new_view(|_cx| {
let result = result.unwrap();
WeatherView { result }
})
}
fn format(_input: &Self::Input, output: &Self::Output) -> String {
format!(
"The current temperature in {} is {} {}",
output.location, output.temperature, output.unit
)
fn format(_: &Self::Input, output: &Result<Self::Output>) -> String {
serde_json::to_string(&output.as_ref().unwrap()).unwrap()
}
}
@@ -214,20 +198,20 @@ mod test {
registry.register(tool).unwrap();
let _result = cx
.update(|cx| {
registry.call(
&ToolFunctionCall {
name: "get_current_weather".to_string(),
arguments: r#"{ "location": "San Francisco", "unit": "Celsius" }"#
.to_string(),
id: "test-123".to_string(),
result: None,
},
cx,
)
})
.await;
// let _result = cx
// .update(|cx| {
// registry.call(
// &ToolFunctionCall {
// name: "get_current_weather".to_string(),
// arguments: r#"{ "location": "San Francisco", "unit": "Celsius" }"#
// .to_string(),
// id: "test-123".to_string(),
// result: None,
// },
// cx,
// )
// })
// .await;
// assert!(result.is_ok());
// let result = result.unwrap();

View File

@@ -1,11 +1,8 @@
use anyhow::Result;
use gpui::{div, AnyElement, AppContext, Element, ParentElement as _, Task, WindowContext};
use gpui::{AnyElement, AnyView, AppContext, IntoElement as _, Render, Task, View, WindowContext};
use schemars::{schema::RootSchema, schema_for, JsonSchema};
use serde::Deserialize;
use std::{
any::Any,
fmt::{Debug, Display},
};
use std::fmt::Display;
#[derive(Default, Deserialize)]
pub struct ToolFunctionCall {
@@ -19,71 +16,29 @@ pub struct ToolFunctionCall {
pub enum ToolFunctionCallResult {
NoSuchTool,
ParsingFailed,
ExecutionFailed {
input: Box<dyn Any>,
},
Finished {
input: Box<dyn Any>,
output: Box<dyn Any>,
render_fn: fn(
// tool_call_id
&str,
// LanguageModelTool::Input
&Box<dyn Any>,
// LanguageModelTool::Output
&Box<dyn Any>,
&mut WindowContext,
) -> AnyElement,
format_fn: fn(
// LanguageModelTool::Input
&Box<dyn Any>,
// LanguageModelTool::Output
&Box<dyn Any>,
) -> String,
},
Finished { for_model: String, view: AnyView },
}
impl ToolFunctionCallResult {
pub fn render(
&self,
tool_name: &str,
tool_call_id: &str,
cx: &mut WindowContext,
) -> AnyElement {
pub fn format(&self, name: &String) -> String {
match self {
ToolFunctionCallResult::NoSuchTool => {
div().child(format!("no such tool {tool_name}")).into_any()
ToolFunctionCallResult::NoSuchTool => format!("No tool for {name}"),
ToolFunctionCallResult::ParsingFailed => {
format!("Unable to parse arguments for {name}")
}
ToolFunctionCallResult::ParsingFailed => div()
.child(format!("failed to parse input for tool {tool_name}"))
.into_any(),
ToolFunctionCallResult::ExecutionFailed { .. } => div()
.child(format!("failed to execute tool {tool_name}"))
.into_any(),
ToolFunctionCallResult::Finished {
input,
output,
render_fn,
..
} => render_fn(tool_call_id, input, output, cx),
ToolFunctionCallResult::Finished { for_model, .. } => for_model.clone(),
}
}
pub fn format(&self, tool: &str) -> String {
pub fn into_any_element(&self, name: &String) -> AnyElement {
match self {
ToolFunctionCallResult::NoSuchTool => format!("no such tool {tool}"),
ToolFunctionCallResult::NoSuchTool => {
format!("Language Model attempted to call {name}").into_any_element()
}
ToolFunctionCallResult::ParsingFailed => {
format!("failed to parse input for tool {tool}")
format!("Language Model called {name} with bad arguments").into_any_element()
}
ToolFunctionCallResult::ExecutionFailed { input: _input } => {
format!("failed to execute tool {tool}")
}
ToolFunctionCallResult::Finished {
input,
output,
format_fn,
..
} => format_fn(input, output),
ToolFunctionCallResult::Finished { view, .. } => view.clone().into_any_element(),
}
}
}
@@ -105,19 +60,6 @@ impl Display for ToolFunctionDefinition {
}
}
impl Debug for ToolFunctionDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let schema = serde_json::to_string(&self.parameters).ok();
let schema = schema.unwrap_or("None".to_string());
f.debug_struct("ToolFunctionDefinition")
.field("name", &self.name)
.field("description", &self.description)
.field("parameters", &schema)
.finish()
}
}
pub trait LanguageModelTool {
/// The input type that will be passed in to `execute` when the tool is called
/// by the language model.
@@ -126,6 +68,8 @@ pub trait LanguageModelTool {
/// The output returned by executing the tool.
type Output: 'static;
type View: Render;
/// The name of the tool is exposed to the language model to allow
/// the model to pick which tools to use. As this name is used to
/// identify the tool within a tool registry, it should be unique.
@@ -149,12 +93,12 @@ pub trait LanguageModelTool {
/// Execute the tool
fn execute(&self, input: &Self::Input, cx: &AppContext) -> Task<Result<Self::Output>>;
fn render(
tool_call_id: &str,
input: &Self::Input,
output: &Self::Output,
cx: &mut WindowContext,
) -> AnyElement;
fn format(input: &Self::Input, output: &Result<Self::Output>) -> String;
fn format(input: &Self::Input, output: &Self::Output) -> String;
fn new_view(
tool_call_id: String,
input: Self::Input,
output: Result<Self::Output>,
cx: &mut WindowContext,
) -> View<Self::View>;
}

View File

@@ -33,7 +33,7 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex().text_ui();
let element = h_flex().text_ui(cx);
let Some(active_item) = self.active_item.as_ref() else {
return element;
};

View File

@@ -36,6 +36,9 @@ struct Args {
/// Custom Zed.app path
#[arg(short, long)]
bundle_path: Option<PathBuf>,
/// Run zed in dev-server mode
#[arg(long)]
dev_server_token: Option<String>,
}
fn parse_path_with_position(
@@ -67,6 +70,10 @@ fn main() -> Result<()> {
let bundle = Bundle::detect(args.bundle_path.as_deref()).context("Bundle detection")?;
if let Some(dev_server_token) = args.dev_server_token {
return bundle.spawn(vec!["--dev-server-token".into(), dev_server_token]);
}
if args.version {
println!("{}", bundle.zed_version_string());
return Ok(());
@@ -169,6 +176,10 @@ mod linux {
unimplemented!()
}
pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
unimplemented!()
}
pub fn zed_version_string(&self) -> String {
unimplemented!()
}
@@ -202,6 +213,10 @@ mod windows {
unimplemented!()
}
pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
unimplemented!()
}
pub fn zed_version_string(&self) -> String {
unimplemented!()
}
@@ -217,7 +232,7 @@ mod mac_os {
url::{CFURLCreateWithBytes, CFURL},
};
use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
use std::{fs, path::Path, ptr};
use std::{fs, path::Path, process::Command, ptr};
use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME};
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
@@ -278,6 +293,15 @@ mod mac_os {
}
}
pub fn spawn(&self, args: Vec<String>) -> Result<()> {
let path = match self {
Self::App { app_bundle, .. } => app_bundle.join("Contents/MacOS/zed"),
Self::LocalPath { executable, .. } => executable.clone(),
};
Command::new(path).args(args).status()?;
Ok(())
}
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
let (server, server_name) =
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
@@ -358,12 +382,12 @@ mod mac_os {
)
}
}
pub(super) fn spawn_channel_cli(
channel: release_channel::ReleaseChannel,
leftover_args: Vec<String>,
) -> Result<()> {
use anyhow::bail;
use std::process::Command;
let app_id_prompt = format!("id of app \"{}\"", channel.display_name());
let app_id_output = Command::new("osascript")

View File

@@ -315,7 +315,7 @@ impl ChatPanel {
None => {
return div().child(
h_flex()
.text_ui_xs()
.text_ui_xs(cx)
.my_0p5()
.px_0p5()
.gap_x_1()
@@ -350,7 +350,7 @@ impl ChatPanel {
div().child(
h_flex()
.id(message_element_id)
.text_ui_xs()
.text_ui_xs(cx)
.my_0p5()
.px_0p5()
.gap_x_1()
@@ -495,7 +495,7 @@ impl ChatPanel {
|this| {
this.child(
h_flex()
.text_ui_sm()
.text_ui_sm(cx)
.child(
div().absolute().child(
Avatar::new(message.sender.avatar_uri.clone())
@@ -539,7 +539,7 @@ impl ChatPanel {
el.child(
v_flex()
.w_full()
.text_ui_sm()
.text_ui_sm(cx)
.id(element_id)
.child(text.element("body".into(), cx)),
)
@@ -562,7 +562,7 @@ impl ChatPanel {
div()
.px_1()
.rounded_md()
.text_ui_xs()
.text_ui_xs(cx)
.bg(cx.theme().colors().background)
.child("New messages"),
)
@@ -1003,7 +1003,7 @@ impl Render for ChatPanel {
el.child(
h_flex()
.px_2()
.text_ui_xs()
.text_ui_xs(cx)
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border)

View File

@@ -18,7 +18,7 @@ use project::{search::SearchQuery, Completion};
use settings::Settings;
use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings;
use ui::{prelude::*, UiTextSize};
use ui::{prelude::*, TextSize};
use crate::panel_settings::MessageEditorSettings;
@@ -523,7 +523,7 @@ impl Render for MessageEditor {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
font_size: UiTextSize::Small.rems().into(),
font_size: TextSize::Small.rems(cx).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3),

View File

@@ -34,7 +34,7 @@ impl ParentElement for CollabNotification {
impl RenderOnce for CollabNotification {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
h_flex()
.text_ui()
.text_ui(cx)
.justify_between()
.size_full()
.overflow_hidden()

View File

@@ -912,7 +912,7 @@ mod tests {
display_map::{BlockContext, TransformBlock},
DisplayPoint, GutterDimensions,
};
use gpui::{px, Stateful, TestAppContext, VisualTestContext, WindowContext};
use gpui::{px, AvailableSpace, Stateful, TestAppContext, VisualTestContext};
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
use project::FakeFs;
use serde_json::json;
@@ -1049,67 +1049,66 @@ mod tests {
cx,
)
});
let editor = view.update(cx, |view, _| view.editor.clone());
view.next_notification(cx).await;
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(15, "collapsed context".into()),
(16, "diagnostic header".into()),
(25, "collapsed context".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// main.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
"\n", // padding
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(15, "collapsed context".into()),
(16, "diagnostic header".into()),
(25, "collapsed context".into()),
]
);
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// main.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
"\n", // padding
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
// Cursor is at the first diagnostic
view.editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
);
});
// Cursor is at the first diagnostic
editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
);
});
// Diagnostics are added for another earlier path.
@@ -1138,78 +1137,77 @@ mod tests {
});
view.next_notification(cx).await;
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "path header block".into()),
(9, "diagnostic header".into()),
(22, "collapsed context".into()),
(23, "diagnostic header".into()),
(32, "collapsed context".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// consts.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
//
// main.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // filename
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "path header block".into()),
(9, "diagnostic header".into()),
(22, "collapsed context".into()),
(23, "diagnostic header".into()),
(32, "collapsed context".into()),
]
);
// Cursor keeps its position.
view.editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
);
});
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// consts.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
//
// main.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // filename
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
// Cursor keeps its position.
editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
);
});
// Diagnostics are added to the first path
@@ -1254,80 +1252,79 @@ mod tests {
});
view.next_notification(cx).await;
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "collapsed context".into()),
(8, "diagnostic header".into()),
(13, "path header block".into()),
(15, "diagnostic header".into()),
(28, "collapsed context".into()),
(29, "diagnostic header".into()),
(38, "collapsed context".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// consts.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
"\n", // padding
"const a: i32 = 'a';\n",
"const b: i32 = c;\n",
"\n", // supporting diagnostic
//
// main.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
"\n", // filename
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
});
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "collapsed context".into()),
(8, "diagnostic header".into()),
(13, "path header block".into()),
(15, "diagnostic header".into()),
(28, "collapsed context".into()),
(29, "diagnostic header".into()),
(38, "collapsed context".into()),
]
);
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
//
// consts.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
"\n", // padding
"const a: i32 = 'a';\n",
"const b: i32 = c;\n",
"\n", // supporting diagnostic
//
// main.rs
//
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
" a(x);\n",
" b(y);\n",
"\n", // supporting diagnostic
" // comment 1\n",
" // comment 2\n",
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
"\n", // filename
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
" let y = vec![];\n",
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
)
);
}
#[gpui::test]
@@ -1364,6 +1361,7 @@ mod tests {
cx,
)
});
let editor = view.update(cx, |view, _| view.editor.clone());
// Two language servers start updating diagnostics
project.update(cx, |project, cx| {
@@ -1397,27 +1395,25 @@ mod tests {
// Only the first language server's diagnostics are shown.
cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"a();\n", //
"b();",
)
);
});
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
]
);
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"a();\n", //
"b();",
)
);
// The second language server finishes
project.update(cx, |project, cx| {
@@ -1445,36 +1441,34 @@ mod tests {
// Both language server's diagnostics are shown.
cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(6, "collapsed context".into()),
(7, "diagnostic header".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"a();\n", // location
"b();\n", //
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // padding
"a();\n", // context
"b();\n", //
"c();", // context
)
);
});
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(6, "collapsed context".into()),
(7, "diagnostic header".into()),
]
);
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"a();\n", // location
"b();\n", //
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // padding
"a();\n", // context
"b();\n", //
"c();", // context
)
);
// Both language servers start updating diagnostics, and the first server finishes.
project.update(cx, |project, cx| {
@@ -1513,37 +1507,35 @@ mod tests {
// Only the first language server's diagnostics are updated.
cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "collapsed context".into()),
(8, "diagnostic header".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"a();\n", // location
"b();\n", //
"c();\n", // context
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // padding
"b();\n", // context
"c();\n", //
"d();", // context
)
);
});
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "collapsed context".into()),
(8, "diagnostic header".into()),
]
);
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"a();\n", // location
"b();\n", //
"c();\n", // context
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // padding
"b();\n", // context
"c();\n", //
"d();", // context
)
);
// The second language server finishes.
project.update(cx, |project, cx| {
@@ -1571,37 +1563,35 @@ mod tests {
// Both language servers' diagnostics are updated.
cx.executor().run_until_parked();
view.update(cx, |view, cx| {
assert_eq!(
editor_blocks(&view.editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "collapsed context".into()),
(8, "diagnostic header".into()),
]
);
assert_eq!(
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"b();\n", // location
"c();\n", //
"d();\n", // context
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // padding
"c();\n", // context
"d();\n", //
"e();", // context
)
);
});
assert_eq!(
editor_blocks(&editor, cx),
[
(0, "path header block".into()),
(2, "diagnostic header".into()),
(7, "collapsed context".into()),
(8, "diagnostic header".into()),
]
);
assert_eq!(
editor.update(cx, |editor, cx| editor.display_text(cx)),
concat!(
"\n", // filename
"\n", // padding
// diagnostic group 1
"\n", // primary message
"\n", // padding
"b();\n", // location
"c();\n", //
"d();\n", // context
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
"\n", // padding
"c();\n", // context
"d();\n", //
"e();", // context
)
);
}
fn init_test(cx: &mut TestAppContext) {
@@ -1618,45 +1608,58 @@ mod tests {
});
}
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
snapshot
.blocks_in_range(0..snapshot.max_point().row())
.enumerate()
.filter_map(|(ix, (row, block))| {
let name: SharedString = match block {
TransformBlock::Custom(block) => cx.with_element_context({
|cx| -> Option<SharedString> {
let mut element = block.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
element.interactivity().element_id.clone()?.try_into().ok()
}
})?,
fn editor_blocks(
editor: &View<Editor>,
cx: &mut VisualTestContext,
) -> Vec<(u32, SharedString)> {
let mut blocks = Vec::new();
cx.draw(gpui::Point::default(), AvailableSpace::min_size(), |cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
blocks.extend(
snapshot
.blocks_in_range(0..snapshot.max_point().row())
.enumerate()
.filter_map(|(ix, (row, block))| {
let name: SharedString = match block {
TransformBlock::Custom(block) => {
let mut element = block.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
element
.interactivity()
.element_id
.clone()?
.try_into()
.ok()?
}
TransformBlock::ExcerptHeader {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
"path header block".into()
} else {
"collapsed context".into()
}
}
};
TransformBlock::ExcerptHeader {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
"path header block".into()
} else {
"collapsed context".into()
}
}
};
Some((row, name))
})
.collect()
})
Some((row, name))
}),
)
});
div().into_any()
});
blocks
}
}

View File

@@ -60,6 +60,7 @@ smallvec.workspace = true
smol.workspace = true
snippet.workspace = true
sum_tree.workspace = true
task.workspace = true
text.workspace = true
time.workspace = true
time_format.workspace = true

View File

@@ -43,6 +43,12 @@ pub struct ToggleCodeActions {
pub deployed_from_indicator: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default)]
pub struct ToggleTestRunner {
#[serde(default)]
pub deployed_from_indicator: Option<u32>,
}
#[derive(PartialEq, Clone, Deserialize, Default)]
pub struct ConfirmCompletion {
#[serde(default)]

View File

@@ -39,17 +39,15 @@ impl<'a> CommitAvatar<'a> {
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
let element = cx.with_element_context(|cx| {
match cx.use_cached_asset::<CommitAvatarAsset>(&avatar_url) {
// Loading or no avatar found
None | Some(None) => Icon::new(IconName::Person)
.color(Color::Muted)
.into_element()
.into_any(),
// Found
Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(),
}
});
let element = match cx.use_cached_asset::<CommitAvatarAsset>(&avatar_url) {
// Loading or no avatar found
None | Some(None) => Icon::new(IconName::Person)
.color(Color::Muted)
.into_element()
.into_any(),
// Found
Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(),
};
Some(element)
}
}

View File

@@ -4,7 +4,7 @@ use super::{
};
use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, ElementContext, Pixels};
use gpui::{AnyElement, Pixels, WindowContext};
use language::{BufferSnapshot, Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
use parking_lot::Mutex;
@@ -82,7 +82,7 @@ pub enum BlockStyle {
}
pub struct BlockContext<'a, 'b> {
pub context: &'b mut ElementContext<'a>,
pub context: &'b mut WindowContext<'a>,
pub anchor_x: Pixels,
pub max_width: Pixels,
pub gutter_dimensions: &'b GutterDimensions,
@@ -934,7 +934,7 @@ impl BlockDisposition {
}
impl<'a> Deref for BlockContext<'a, '_> {
type Target = ElementContext<'a>;
type Target = WindowContext<'a>;
fn deref(&self) -> &Self::Target {
self.context

View File

@@ -33,6 +33,7 @@ mod persistence;
mod rust_analyzer_ext;
pub mod scroll;
mod selections_collection;
pub mod tasks;
#[cfg(test)]
mod editor_tests;
@@ -75,6 +76,7 @@ use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion_provider::*;
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::Runnable;
use language::{
char_kind,
language_settings::{self, all_language_settings, InlayHintSettings},
@@ -82,6 +84,7 @@ use language::{
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId,
};
use task::TaskTemplate;
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
use lsp::{DiagnosticSeverity, LanguageServerId};
@@ -96,7 +99,8 @@ use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock};
use project::project_settings::{GitGutterSetting, ProjectSettings};
use project::{
CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath, ProjectTransaction,
CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath,
ProjectTransaction, WorktreeId,
};
use rand::prelude::*;
use rpc::proto::*;
@@ -388,6 +392,13 @@ impl Default for ScrollbarMarkerState {
}
}
struct RunnableTasks {
templates: SmallVec<[TaskTemplate; 1]>,
match_range: Range<usize>, // The equivalent of the newest selection,
language: Arc<Language>, // For getting a context provider
worktree: Option<WorktreeId>,
}
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
///
/// See the [module level documentation](self) for more information.
@@ -3677,6 +3688,47 @@ impl Editor {
.detach_and_log_err(cx);
}
pub fn toggle_test_runner(&mut self, action: &ToggleTestRunner, cx: &mut ViewContext<Self>) {
unimplemented!()
// let mut context_menu = self.context_menu.write();
// if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
// *context_menu = None;
// cx.notify();
// return;
// }
// drop(context_menu);
// let deployed_from_indicator = action.deployed_from_indicator;
// let mut task = self.code_actions_task.take();
// cx.spawn(|this, mut cx| async move {
// while let Some(prev_task) = task {
// prev_task.await;
// task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
// }
// this.update(&mut cx, |this, cx| {
// if this.focus_handle.is_focused(cx) {
// if let Some((buffer, actions)) = this.available_code_actions.clone() {
// this.completion_tasks.clear();
// this.discard_inline_completion(cx);
// *this.context_menu.write() =
// Some(ContextMenu::CodeActions(CodeActionsMenu {
// buffer,
// actions,
// selected_item: Default::default(),
// scroll_handle: UniformListScrollHandle::default(),
// deployed_from_indicator,
// }));
// cx.notify();
// }
// }
// })?;
// Ok::<_, anyhow::Error>(())
// })
// .detach_and_log_err(cx);
}
pub fn confirm_code_action(
&mut self,
action: &ConfirmCodeAction,
@@ -4199,6 +4251,28 @@ impl Editor {
}
}
pub fn render_test_run_indicator(
&self,
_style: &EditorStyle,
is_active: bool,
indicator: u32,
cx: &mut ViewContext<Self>,
) -> IconButton {
IconButton::new("code_actions_indicator", ui::IconName::Play)
.icon_size(IconSize::XSmall)
.size(ui::ButtonSize::None)
.icon_color(Color::Muted)
.selected(is_active)
.on_click(cx.listener(move |editor, _e, cx| {
editor.toggle_test_runner(
&ToggleTestRunner {
deployed_from_indicator: Some(indicator),
},
cx,
);
}))
}
pub fn render_fold_indicators(
&mut self,
fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
@@ -7375,6 +7449,72 @@ impl Editor {
self.select_larger_syntax_node_stack = stack;
}
fn runnable_display_rows(
&self,
range: Range<Anchor>,
snapshot: &DisplaySnapshot,
cx: &WindowContext,
) -> Vec<(u32, RunnableTasks)> {
snapshot
.buffer_snapshot
.runnable_ranges(range)
.filter_map(|(multi_buffer_range, mut runnable)| {
let (tasks, worktree_id) = self.resolve_runnable(&mut runnable, cx);
if tasks.is_empty() {
return None;
}
Some((
multi_buffer_range.start.to_display_point(&snapshot).row(),
RunnableTasks {
templates: tasks,
match_range: multi_buffer_range,
language: runnable.language,
worktree: worktree_id,
},
))
})
.collect()
}
fn resolve_runnable(
&self,
runnable: &mut Runnable,
cx: &WindowContext<'_>,
) -> (SmallVec<[TaskTemplate; 1]>, Option<WorktreeId>) {
let Some(project) = self.project.as_ref() else {
return Default::default();
};
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
let worktree_id = project
.buffer_for_id(runnable.buffer)
.and_then(|buffer| buffer.read(cx).file())
.map(|file| WorktreeId::from_usize(file.worktree_id()));
(project.task_inventory().clone(), worktree_id)
});
let inventory = inventory.read(cx);
let tags = mem::take(&mut runnable.tags);
(
SmallVec::from_iter(
tags.into_iter()
.flat_map(|tag| {
let tag = tag.0.clone();
inventory
.list_tasks(Some(runnable.language.clone()), worktree_id)
.into_iter()
.filter(move |(_, template)| {
template.tags.iter().any(|source_tag| source_tag == &tag)
})
})
.sorted_by_key(|(kind, _)| kind.to_owned())
.map(|(_, template)| template),
),
worktree_id,
)
}
pub fn move_to_enclosing_bracket(
&mut self,
_: &MoveToEnclosingBracket,
@@ -7745,7 +7885,13 @@ impl Editor {
.update(&mut cx, |editor, cx| {
editor.navigate_to_hover_links(
Some(kind),
definitions.into_iter().map(HoverLink::Text).collect(),
definitions
.into_iter()
.filter(|location| {
hover_links::exclude_link_to_position(&buffer, &head, location, cx)
})
.map(HoverLink::Text)
.collect::<Vec<_>>(),
split,
cx,
)
@@ -8982,6 +9128,10 @@ impl Editor {
return;
};
if buffer.read(cx).file().is_none() {
return;
}
let project = project.clone();
let blame = cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, cx));
self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
@@ -10323,7 +10473,19 @@ impl Render for Editor {
let settings = ThemeSettings::get_global(cx);
let text_style = match self.mode {
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => cx.text_style(),
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(settings.buffer_line_height.value()),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
},
EditorMode::Full => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),

View File

@@ -15,7 +15,7 @@ use crate::{
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, GutterDimensions, HalfPageDown,
HalfPageUp, HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
RunnableTasks, SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
};
use anyhow::Result;
use collections::{BTreeMap, HashMap};
@@ -23,12 +23,11 @@ use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementContext,
ElementInputHandler, Entity, Hitbox, Hsla, InteractiveElement, IntoElement,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
ViewContext, WeakView, WindowContext,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful, StatefulInteractiveElement, Style,
Styled, TextRun, TextStyle, TextStyleRefinement, View, ViewContext, WeakView, WindowContext,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -50,6 +49,7 @@ use std::{
sync::Arc,
};
use sum_tree::Bias;
use task::{RunnableTag, TaskTemplate};
use theme::{ActiveTheme, PlayerColor};
use ui::prelude::*;
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
@@ -367,7 +367,7 @@ impl EditorElement {
register_action(view, cx, Editor::open_active_item_in_terminal)
}
fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) {
fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) {
let position_map = layout.position_map.clone();
cx.on_key_event({
let editor = self.editor.clone();
@@ -691,7 +691,7 @@ impl EditorElement {
snapshot: &EditorSnapshot,
start_row: u32,
end_row: u32,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> (
Vec<(PlayerColor, Vec<SelectionLayout>)>,
BTreeMap<u32, bool>,
@@ -819,7 +819,7 @@ impl EditorElement {
scroll_pixel_position: gpui::Point<Pixels>,
line_height: Pixels,
line_layouts: &[LineWithInvisibles],
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Vec<FoldLayout> {
snapshot
.folds_in_range(visible_anchor_range.clone())
@@ -887,7 +887,7 @@ impl EditorElement {
line_height: Pixels,
em_width: Pixels,
autoscroll_containing_element: bool,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Vec<CursorLayout> {
let mut autoscroll_bounds = None;
let cursor_layouts = self.editor.update(cx, |editor, cx| {
@@ -993,7 +993,7 @@ impl EditorElement {
color: self.style.background,
is_top_row: cursor_position.row() == 0,
});
cx.with_element_context(|cx| cursor.layout(content_origin, cursor_name, cx));
cursor.layout(content_origin, cursor_name, cx);
cursors.push(cursor);
}
}
@@ -1013,7 +1013,7 @@ impl EditorElement {
bounds: Bounds<Pixels>,
scroll_position: gpui::Point<f32>,
rows_per_page: f32,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<ScrollbarLayout> {
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
let show_scrollbars = match scrollbar_settings.show {
@@ -1082,7 +1082,7 @@ impl EditorElement {
gutter_settings: crate::editor_settings::Gutter,
scroll_pixel_position: gpui::Point<Pixels>,
gutter_hitbox: &Hitbox,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Vec<Option<AnyElement>> {
let mut indicators = self.editor.update(cx, |editor, cx| {
editor.render_fold_indicators(
@@ -1155,7 +1155,7 @@ impl EditorElement {
content_origin: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<Pixels>,
line_height: Pixels,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<AnyElement> {
if !self
.editor
@@ -1200,7 +1200,7 @@ impl EditorElement {
.map(|col| self.column_pixels(col as usize, cx))
.unwrap_or(px(0.));
content_origin.x + max(padded_line_width, min_column)
(content_origin.x - scroll_pixel_position.x) + max(padded_line_width, min_column)
};
let absolute_offset = point(start_x, start_y);
@@ -1220,7 +1220,7 @@ impl EditorElement {
line_height: Pixels,
gutter_hitbox: &Hitbox,
max_width: Option<Pixels>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<Vec<AnyElement>> {
if !self
.editor
@@ -1278,6 +1278,37 @@ impl EditorElement {
Some(shaped_lines)
}
fn layout_test_run_indicators(
&self,
test_lines: Vec<(u32, RunnableTasks)>,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
gutter_dimensions: &GutterDimensions,
gutter_hitbox: &Hitbox,
cx: &mut WindowContext,
) -> Vec<AnyElement> {
test_lines
.into_iter()
.filter_map(|(line, tags)| {
let button = self.editor.update(cx, |editor, cx| {
// active = todo check if the run menu is open or something
editor.render_test_run_indicator(&self.style, false, line, cx)
});
let button = prepaint_gutter_button(
button,
line,
line_height,
gutter_dimensions,
scroll_pixel_position,
gutter_hitbox,
cx,
);
Some(button)
})
.collect_vec()
}
fn layout_code_actions_indicator(
&self,
line_height: Pixels,
@@ -1285,7 +1316,7 @@ impl EditorElement {
scroll_pixel_position: gpui::Point<Pixels>,
gutter_dimensions: &GutterDimensions,
gutter_hitbox: &Hitbox,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<AnyElement> {
let mut active = false;
let mut button = None;
@@ -1297,27 +1328,16 @@ impl EditorElement {
button = editor.render_code_actions_indicator(&self.style, active, cx);
});
let mut button = button?.into_any_element();
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height),
let button = prepaint_gutter_button(
button?,
newest_selection_head.row(),
line_height,
gutter_dimensions,
scroll_pixel_position,
gutter_hitbox,
cx,
);
let indicator_size = button.layout_as_root(available_space, cx);
let blame_width = gutter_dimensions
.git_blame_entries_width
.unwrap_or(Pixels::ZERO);
let mut x = blame_width;
let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
- indicator_size.width
- blame_width;
x += available_width / 2.;
let mut y = newest_selection_head.row() as f32 * line_height - scroll_pixel_position.y;
y += (line_height - indicator_size.height) / 2.;
button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
Some(button)
}
@@ -1372,7 +1392,7 @@ impl EditorElement {
active_rows: &BTreeMap<u32, bool>,
newest_selection_head: Option<DisplayPoint>,
snapshot: &EditorSnapshot,
cx: &ElementContext,
cx: &WindowContext,
) -> (
Vec<Option<ShapedLine>>,
Vec<Option<(FoldStatus, BufferRow, bool)>>,
@@ -1465,7 +1485,7 @@ impl EditorElement {
rows: Range<u32>,
line_number_layouts: &[Option<ShapedLine>],
snapshot: &EditorSnapshot,
cx: &ElementContext,
cx: &WindowContext,
) -> Vec<LineWithInvisibles> {
if rows.start >= rows.end {
return Vec::new();
@@ -1530,7 +1550,7 @@ impl EditorElement {
text_x: Pixels,
line_height: Pixels,
line_layouts: &[LineWithInvisibles],
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Vec<BlockLayout> {
let mut block_id = 0;
let (fixed_blocks, non_fixed_blocks) = snapshot
@@ -1544,7 +1564,7 @@ impl EditorElement {
available_space: Size<AvailableSpace>,
block_id: usize,
block_row_start: u32,
cx: &mut ElementContext| {
cx: &mut WindowContext| {
let mut element = match block {
TransformBlock::Custom(block) => {
let align_to = block
@@ -1865,7 +1885,7 @@ impl EditorElement {
hitbox: &Hitbox,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
for block in blocks {
let mut origin = hitbox.origin
@@ -1893,7 +1913,7 @@ impl EditorElement {
scroll_pixel_position: gpui::Point<Pixels>,
line_layouts: &[LineWithInvisibles],
newest_selection_head: DisplayPoint,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> bool {
let max_height = cmp::min(
12. * line_height,
@@ -1933,7 +1953,7 @@ impl EditorElement {
true
}
fn layout_mouse_context_menu(&self, cx: &mut ElementContext) -> Option<AnyElement> {
fn layout_mouse_context_menu(&self, cx: &mut WindowContext) -> Option<AnyElement> {
let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
let mut element = deferred(
anchored()
@@ -1961,7 +1981,7 @@ impl EditorElement {
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
em_width: Pixels,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
struct MeasuredHoverPopover {
element: AnyElement,
@@ -2021,7 +2041,7 @@ impl EditorElement {
}
overall_height += HOVER_POPOVER_GAP;
fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
let mut occlusion = div()
.size_full()
.occlude()
@@ -2067,7 +2087,7 @@ impl EditorElement {
}
}
fn paint_background(&self, layout: &EditorLayout, cx: &mut ElementContext) {
fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) {
cx.paint_layer(layout.hitbox.bounds, |cx| {
let scroll_top = layout.position_map.snapshot.scroll_position().y;
let gutter_bg = cx.theme().colors().editor_gutter_background;
@@ -2188,7 +2208,7 @@ impl EditorElement {
})
}
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
let line_height = layout.position_map.line_height;
let scroll_position = layout.position_map.snapshot.scroll_position();
@@ -2230,13 +2250,19 @@ impl EditorElement {
}
});
cx.with_element_id(Some("gutter_test_indicators"), |cx| {
for test_indicators in layout.test_indicators.iter_mut() {
test_indicators.paint(cx);
}
});
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
indicator.paint(cx);
}
})
}
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut ElementContext) {
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
if layout.display_hunks.is_empty() {
return;
}
@@ -2342,7 +2368,7 @@ impl EditorElement {
})
}
fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
return;
};
@@ -2354,7 +2380,7 @@ impl EditorElement {
})
}
fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
cx.with_content_mask(
Some(ContentMask {
bounds: layout.text_hitbox.bounds,
@@ -2386,7 +2412,7 @@ impl EditorElement {
fn paint_highlights(
&mut self,
layout: &mut EditorLayout,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> SmallVec<[Range<DisplayPoint>; 32]> {
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
@@ -2428,7 +2454,7 @@ impl EditorElement {
&mut self,
invisible_display_ranges: &[Range<DisplayPoint>],
layout: &EditorLayout,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let whitespace_setting = self
.editor
@@ -2451,7 +2477,7 @@ impl EditorElement {
}
}
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
if layout.redacted_ranges.is_empty() {
return;
}
@@ -2475,13 +2501,13 @@ impl EditorElement {
});
}
fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
for cursor in &mut layout.cursors {
cursor.paint(layout.content_origin, cx);
}
}
fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
return;
};
@@ -2617,7 +2643,7 @@ impl EditorElement {
&self,
layout: &EditorLayout,
scrollbar_layout: &ScrollbarLayout,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.editor.update(cx, |editor, cx| {
if !editor.is_singleton(cx)
@@ -2775,7 +2801,7 @@ impl EditorElement {
corner_radius: Pixels,
line_end_overshoot: Pixels,
layout: &EditorLayout,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let start_row = layout.visible_display_row_range.start;
let end_row = layout.visible_display_row_range.end;
@@ -2824,7 +2850,7 @@ impl EditorElement {
}
}
fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
if layout.folds.is_empty() {
return;
}
@@ -2855,7 +2881,7 @@ impl EditorElement {
})
}
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
if let Some(mut inline_blame) = layout.inline_blame.take() {
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
inline_blame.paint(cx);
@@ -2863,19 +2889,19 @@ impl EditorElement {
}
}
fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
for mut block in layout.blocks.drain(..) {
block.element.paint(cx);
}
}
fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
mouse_context_menu.paint(cx);
}
}
fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
cx.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
@@ -2925,7 +2951,7 @@ impl EditorElement {
});
}
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
self.paint_scroll_wheel_listener(layout, cx);
cx.on_mouse_event({
@@ -3037,12 +3063,45 @@ impl EditorElement {
}
}
fn prepaint_gutter_button(
button: IconButton,
row: u32,
line_height: Pixels,
gutter_dimensions: &GutterDimensions,
scroll_pixel_position: gpui::Point<Pixels>,
gutter_hitbox: &Hitbox,
cx: &mut WindowContext<'_>,
) -> AnyElement {
let mut button = button.into_any_element();
let available_space = size(
AvailableSpace::MinContent,
AvailableSpace::Definite(line_height),
);
let indicator_size = button.layout_as_root(available_space, cx);
let blame_width = gutter_dimensions
.git_blame_entries_width
.unwrap_or(Pixels::ZERO);
let mut x = blame_width;
let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
- indicator_size.width
- blame_width;
x += available_width / 2.;
let mut y = row as f32 * line_height - scroll_pixel_position.y;
y += (line_height - indicator_size.height) / 2.;
button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
button
}
fn render_inline_blame_entry(
blame: &gpui::Model<GitBlame>,
blame_entry: BlameEntry,
style: &EditorStyle,
workspace: Option<WeakView<Workspace>>,
cx: &mut ElementContext<'_>,
cx: &mut WindowContext<'_>,
) -> AnyElement {
let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
@@ -3073,7 +3132,7 @@ fn render_blame_entry(
style: &EditorStyle,
last_used_color: &mut Option<(PlayerColor, Oid)>,
editor: View<Editor>,
cx: &mut ElementContext<'_>,
cx: &mut WindowContext<'_>,
) -> AnyElement {
let mut sha_color = cx
.theme()
@@ -3286,7 +3345,7 @@ impl LineWithInvisibles {
content_origin: gpui::Point<Pixels>,
whitespace_setting: ShowWhitespaceSetting,
selection_ranges: &[Range<DisplayPoint>],
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let line_height = layout.position_map.line_height;
let line_y =
@@ -3318,7 +3377,7 @@ impl LineWithInvisibles {
row: u32,
line_height: Pixels,
whitespace_setting: ShowWhitespaceSetting,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let allowed_invisibles_regions = match whitespace_setting {
ShowWhitespaceSetting::None => return,
@@ -3365,7 +3424,7 @@ impl Element for EditorElement {
type RequestLayoutState = ();
type PrepaintState = EditorLayout;
fn request_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, ()) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (gpui::LayoutId, ()) {
self.editor.update(cx, |editor, cx| {
editor.set_style(self.style.clone(), cx);
@@ -3375,36 +3434,31 @@ impl Element for EditorElement {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
cx.with_element_context(|cx| cx.request_layout(&style, None))
cx.request_layout(&style, None)
}
EditorMode::AutoHeight { max_lines } => {
let editor_handle = cx.view().clone();
let max_line_number_width =
self.max_line_number_width(&editor.snapshot(cx), cx);
cx.with_element_context(|cx| {
cx.request_measured_layout(
Style::default(),
move |known_dimensions, _, cx| {
editor_handle
.update(cx, |editor, cx| {
compute_auto_height_layout(
editor,
max_lines,
max_line_number_width,
known_dimensions,
cx,
)
})
.unwrap_or_default()
},
)
cx.request_measured_layout(Style::default(), move |known_dimensions, _, cx| {
editor_handle
.update(cx, |editor, cx| {
compute_auto_height_layout(
editor,
max_lines,
max_line_number_width,
known_dimensions,
cx,
)
})
.unwrap_or_default()
})
}
EditorMode::Full => {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = relative(1.).into();
cx.with_element_context(|cx| cx.request_layout(&style, None))
cx.request_layout(&style, None)
}
};
@@ -3416,13 +3470,14 @@ impl Element for EditorElement {
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Self::PrepaintState {
let text_style = TextStyleRefinement {
font_size: Some(self.style.text.font_size),
line_height: Some(self.style.text.line_height),
..Default::default()
};
cx.set_view_id(self.editor.entity_id());
cx.with_text_style(Some(text_style), |cx| {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
let mut snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
@@ -3555,6 +3610,12 @@ impl Element for EditorElement {
cx,
);
let test_lines = self.editor.read(cx).runnable_display_rows(
start_anchor..end_anchor,
&snapshot.display_snapshot,
cx,
);
let (selections, active_rows, newest_selection_head) = self.layout_selections(
start_anchor,
end_anchor,
@@ -3722,18 +3783,34 @@ impl Element for EditorElement {
cx,
);
if gutter_settings.code_actions {
code_actions_indicator = self.layout_code_actions_indicator(
line_height,
newest_selection_head,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
cx,
);
let has_test_indicator = test_lines
.iter()
.any(|(line, _)| *line == newest_selection_head.row());
if !has_test_indicator {
code_actions_indicator = self.layout_code_actions_indicator(
line_height,
newest_selection_head,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
cx,
);
}
}
}
}
let test_indicators = cx.with_element_id(Some("test-run"), |cx| {
self.layout_test_run_indicators(
test_lines,
line_height,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
cx,
)
});
if !context_menu_visible && !cx.has_active_drag() {
self.layout_hover_popovers(
&snapshot,
@@ -3833,6 +3910,7 @@ impl Element for EditorElement {
cursors,
selections,
mouse_context_menu,
test_indicators,
code_actions_indicator,
fold_indicators,
tab_invisible,
@@ -3847,13 +3925,12 @@ impl Element for EditorElement {
bounds: Bounds<gpui::Pixels>,
_: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let focus_handle = self.editor.focus_handle(cx);
let key_context = self.editor.read(cx).key_context(cx);
cx.set_focus_handle(&focus_handle);
cx.set_key_context(key_context);
cx.set_view_id(self.editor.entity_id());
cx.handle_input(
&focus_handle,
ElementInputHandler::new(bounds, self.editor.clone()),
@@ -3924,6 +4001,7 @@ pub struct EditorLayout {
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
max_row: u32,
code_actions_indicator: Option<AnyElement>,
test_indicators: Vec<AnyElement>,
fold_indicators: Vec<Option<AnyElement>>,
mouse_context_menu: Option<AnyElement>,
tab_invisible: ShapedLine,
@@ -4206,7 +4284,7 @@ impl CursorLayout {
&mut self,
origin: gpui::Point<Pixels>,
cursor_name: Option<CursorName>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
if let Some(cursor_name) = cursor_name {
let bounds = self.bounds(origin);
@@ -4236,7 +4314,7 @@ impl CursorLayout {
}
}
pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
let bounds = self.bounds(origin);
//Draw background or border quad
@@ -4280,7 +4358,7 @@ pub struct HighlightedRangeLine {
}
impl HighlightedRange {
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ElementContext) {
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
self.paint_lines(
@@ -4299,7 +4377,7 @@ impl HighlightedRange {
start_y: Pixels,
lines: &[HighlightedRangeLine],
_bounds: Bounds<Pixels>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
if lines.is_empty() {
return;
@@ -4416,7 +4494,7 @@ mod tests {
editor_tests::{init_test, update_test_language_settings},
Editor, MultiBuffer,
};
use gpui::TestAppContext;
use gpui::{TestAppContext, VisualTestContext};
use language::language_settings;
use log::info;
use std::num::NonZeroU32;
@@ -4437,18 +4515,16 @@ mod tests {
let layouts = cx
.update_window(*window, |_, cx| {
cx.with_element_context(|cx| {
element
.layout_line_numbers(
0..6,
(0..6).map(Some),
&Default::default(),
Some(DisplayPoint::new(0, 0)),
&snapshot,
cx,
)
.0
})
element
.layout_line_numbers(
0..6,
(0..6).map(Some),
&Default::default(),
Some(DisplayPoint::new(0, 0)),
&snapshot,
cx,
)
.0
})
.unwrap();
assert_eq!(layouts.len(), 6);
@@ -4487,9 +4563,9 @@ mod tests {
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
Editor::new(EditorMode::Full, buffer, None, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
let mut element = EditorElement::new(&editor, style);
window
.update(cx, |editor, cx| {
@@ -4503,20 +4579,10 @@ mod tests {
});
})
.unwrap();
let state = cx
.update_window(window.into(), |_view, cx| {
cx.with_element_context(|cx| {
element.prepaint(
Bounds {
origin: point(px(500.), px(500.)),
size: size(px(500.), px(500.)),
},
&mut (),
cx,
)
})
})
.unwrap();
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
EditorElement::new(&editor, style)
});
assert_eq!(state.selections.len(), 1);
let local_selections = &state.selections[0].1;
@@ -4587,7 +4653,6 @@ mod tests {
});
let editor = window.root(cx).unwrap();
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
let mut element = EditorElement::new(&editor, style);
let _state = window.update(cx, |editor, cx| {
editor.cursor_shape = CursorShape::Block;
editor.change_selections(None, cx, |s| {
@@ -4598,20 +4663,9 @@ mod tests {
});
});
let state = cx
.update_window(window.into(), |_view, cx| {
cx.with_element_context(|cx| {
element.prepaint(
Bounds {
origin: point(px(500.), px(500.)),
size: size(px(500.), px(500.)),
},
&mut (),
cx,
)
})
})
.unwrap();
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
EditorElement::new(&editor, style)
});
assert_eq!(state.selections.len(), 1);
let local_selections = &state.selections[0].1;
assert_eq!(local_selections.len(), 2);
@@ -4640,6 +4694,7 @@ mod tests {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
window
@@ -4662,22 +4717,9 @@ mod tests {
})
.unwrap();
let mut element = EditorElement::new(&editor, style);
let state = cx
.update_window(window.into(), |_view, cx| {
cx.with_element_context(|cx| {
element.prepaint(
Bounds {
origin: point(px(500.), px(500.)),
size: size(px(500.), px(500.)),
},
&mut (),
cx,
)
})
})
.unwrap();
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
EditorElement::new(&editor, style)
});
assert_eq!(state.position_map.line_layouts.len(), 4);
assert_eq!(
state
@@ -4850,31 +4892,19 @@ mod tests {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
let mut element = EditorElement::new(&editor, style);
window
.update(cx, |editor, cx| {
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
editor.set_wrap_width(Some(editor_width), cx);
})
.unwrap();
let layout_state = cx
.update_window(window.into(), |_, cx| {
cx.with_element_context(|cx| {
element.prepaint(
Bounds {
origin: point(px(500.), px(500.)),
size: size(px(500.), px(500.)),
},
&mut (),
cx,
)
})
})
.unwrap();
layout_state
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
EditorElement::new(&editor, style)
});
state
.position_map
.line_layouts
.iter()

View File

@@ -3,7 +3,7 @@ use crate::{
Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition, InlayId,
PointForPosition, SelectPhase,
};
use gpui::{px, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
use language::{Bias, ToOffset};
use linkify::{LinkFinder, LinkKind};
use lsp::LanguageServerId;
@@ -11,8 +11,7 @@ use project::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
ResolveState,
};
use std::{cmp, ops::Range};
use text::Point;
use std::ops::Range;
use theme::ActiveTheme as _;
use util::{maybe, ResultExt, TryFutureExt};
@@ -85,6 +84,25 @@ impl TriggerPoint {
}
}
pub fn exclude_link_to_position(
buffer: &Model<language::Buffer>,
current_position: &text::Anchor,
location: &LocationLink,
cx: &AppContext,
) -> bool {
// Exclude definition links that points back to cursor position.
// (i.e., currently cursor upon definition).
let snapshot = buffer.read(cx).snapshot();
!(buffer == &location.target.buffer
&& current_position
.bias_right(&snapshot)
.cmp(&location.target.range.start, &snapshot)
.is_ge()
&& current_position
.cmp(&location.target.range.end, &snapshot)
.is_le())
}
impl Editor {
pub(crate) fn update_hovered_link(
&mut self,
@@ -132,28 +150,12 @@ impl Editor {
modifiers: Modifiers,
cx: &mut ViewContext<Editor>,
) {
let selection_before_revealing = self.selections.newest::<Point>(cx);
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
let before_revealing_head = selection_before_revealing.head();
let before_revealing_tail = selection_before_revealing.tail();
let before_revealing = match before_revealing_tail.cmp(&before_revealing_head) {
cmp::Ordering::Equal | cmp::Ordering::Less => {
multi_buffer_snapshot.anchor_after(before_revealing_head)
..multi_buffer_snapshot.anchor_before(before_revealing_tail)
}
cmp::Ordering::Greater => {
multi_buffer_snapshot.anchor_before(before_revealing_tail)
..multi_buffer_snapshot.anchor_after(before_revealing_head)
}
};
drop(multi_buffer_snapshot);
let reveal_task = self.cmd_click_reveal_task(point, modifiers, cx);
cx.spawn(|editor, mut cx| async move {
let definition_revealed = reveal_task.await.log_err().unwrap_or(false);
let find_references = editor
.update(&mut cx, |editor, cx| {
if definition_revealed && revealed_elsewhere(editor, before_revealing, cx) {
if definition_revealed {
return None;
}
editor.find_all_references(&FindAllReferences, cx)
@@ -180,12 +182,30 @@ impl Editor {
cx.focus(&self.focus_handle);
}
return self.navigate_to_hover_links(
None,
hovered_link_state.links,
modifiers.alt,
cx,
);
// exclude links pointing back to the current anchor
let current_position = point
.next_valid
.to_point(&self.snapshot(cx).display_snapshot);
let Some((buffer, anchor)) = self
.buffer()
.read(cx)
.text_anchor_for_position(current_position, cx)
else {
return Task::ready(Ok(false));
};
let links = hovered_link_state
.links
.into_iter()
.filter(|link| {
if let HoverLink::Text(location) = link {
exclude_link_to_position(&buffer, &anchor, location, cx)
} else {
true
}
})
.collect();
return self.navigate_to_hover_links(None, links, modifiers.alt, cx);
}
}
@@ -212,46 +232,6 @@ impl Editor {
}
}
fn revealed_elsewhere(
editor: &mut Editor,
before_revealing: Range<Anchor>,
cx: &mut ViewContext<'_, Editor>,
) -> bool {
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let selection_after_revealing = editor.selections.newest::<Point>(cx);
let after_revealing_head = selection_after_revealing.head();
let after_revealing_tail = selection_after_revealing.tail();
let after_revealing = match after_revealing_tail.cmp(&after_revealing_head) {
cmp::Ordering::Equal | cmp::Ordering::Less => {
multi_buffer_snapshot.anchor_after(after_revealing_tail)
..multi_buffer_snapshot.anchor_before(after_revealing_head)
}
cmp::Ordering::Greater => {
multi_buffer_snapshot.anchor_after(after_revealing_head)
..multi_buffer_snapshot.anchor_before(after_revealing_tail)
}
};
let before_intersects_after_range = (before_revealing
.start
.cmp(&after_revealing.start, &multi_buffer_snapshot)
.is_ge()
&& before_revealing
.start
.cmp(&after_revealing.end, &multi_buffer_snapshot)
.is_le())
|| (before_revealing
.end
.cmp(&after_revealing.start, &multi_buffer_snapshot)
.is_ge()
&& before_revealing
.end
.cmp(&after_revealing.end, &multi_buffer_snapshot)
.is_le());
!before_intersects_after_range
}
pub fn update_inlay_link_and_hover_points(
snapshot: &EditorSnapshot,
point_for_position: PointForPosition,

View File

@@ -0,0 +1,99 @@
use crate::Editor;
use std::{path::Path, sync::Arc};
use anyhow::Context;
use gpui::WindowContext;
use language::{BasicContextProvider, ContextProvider};
use project::{Location, WorktreeId};
use task::{TaskContext, TaskVariables};
use util::ResultExt;
use workspace::Workspace;
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
fn task_context_impl(workspace: &Workspace, cx: &mut WindowContext<'_>) -> Option<TaskContext> {
let cwd = workspace::tasks::task_cwd(workspace, cx)
.log_err()
.flatten();
let editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))?;
let (selection, buffer, editor_snapshot) = editor.update(cx, |editor, cx| {
let selection = editor.selections.newest::<usize>(cx);
let (buffer, _, _) = editor
.buffer()
.read(cx)
.point_to_buffer_offset(selection.start, cx)?;
let snapshot = editor.snapshot(cx);
Some((selection, buffer, snapshot))
})?;
let language_context_provider = buffer
.read(cx)
.language()
.and_then(|language| language.context_provider())
.unwrap_or_else(|| Arc::new(BasicContextProvider));
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let worktree_abs_path = buffer
.read(cx)
.file()
.map(|file| WorktreeId::from_usize(file.worktree_id()))
.and_then(|worktree_id| {
workspace
.project()
.read(cx)
.worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
});
let location = Location {
buffer,
range: start..end,
};
let task_variables = combine_task_variables(
worktree_abs_path.as_deref(),
location,
language_context_provider.as_ref(),
cx,
)
.log_err()?;
Some(TaskContext {
cwd,
task_variables,
})
}
task_context_impl(workspace, cx).unwrap_or_default()
}
fn combine_task_variables(
worktree_abs_path: Option<&Path>,
location: Location,
context_provider: &dyn ContextProvider,
cx: &mut WindowContext<'_>,
) -> anyhow::Result<TaskVariables> {
if context_provider.is_basic() {
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building basic provider context")
} else {
let mut basic_context = BasicContextProvider
.build_context(worktree_abs_path, &location, cx)
.context("building basic default context")?;
basic_context.extend(
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building provider context ")?,
);
Ok(basic_context)
}
}

View File

@@ -21,6 +21,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("dart", &["dart"]),
("dockerfile", &["Dockerfile"]),
("elisp", &["el"]),
("elixir", &["ex", "exs", "heex"]),
("elm", &["elm"]),
("erlang", &["erl", "hrl"]),
("fish", &["fish"]),

View File

@@ -14,7 +14,7 @@ workspace = true
default = []
test-support = ["backtrace", "collections/test-support", "util/test-support"]
runtime_shaders = []
macos-blade = ["blade-graphics", "blade-macros", "blade-rwh", "bytemuck"]
macos-blade = ["blade-graphics", "blade-macros", "bytemuck"]
[lib]
path = "src/gpui.rs"
@@ -26,7 +26,6 @@ async-task = "4.7"
backtrace = { version = "0.3", optional = true }
blade-graphics = { workspace = true, optional = true }
blade-macros = { workspace = true, optional = true }
blade-rwh = { workspace = true, optional = true }
bytemuck = { version = "1", optional = true }
collections.workspace = true
ctor.workspace = true
@@ -95,7 +94,6 @@ flume = "0.11"
#TODO: use these on all platforms
blade-graphics.workspace = true
blade-macros.workspace = true
blade-rwh.workspace = true
bytemuck = "1"
cosmic-text = "0.11.2"
copypasta = "0.10.1"

View File

@@ -1,10 +1,11 @@
use crate::{
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
AvailableSpace, BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, Empty,
Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext,
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, AvailableSpace,
BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, DrawPhase, Drawable,
Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model,
ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher,
TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowContext,
WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{channel::oneshot, Stream, StreamExt};
@@ -725,21 +726,28 @@ impl VisualTestContext {
}
/// Draw an element to the window. Useful for simulating events or actions
pub fn draw(
pub fn draw<E>(
&mut self,
origin: Point<Pixels>,
space: Size<AvailableSpace>,
f: impl FnOnce(&mut WindowContext) -> AnyElement,
) {
space: impl Into<Size<AvailableSpace>>,
f: impl FnOnce(&mut WindowContext) -> E,
) -> (E::RequestLayoutState, E::PrepaintState)
where
E: Element,
{
self.update(|cx| {
cx.with_element_context(|cx| {
let mut element = f(cx);
element.layout_as_root(space, cx);
cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx));
element.paint(cx);
});
cx.window.draw_phase = DrawPhase::Prepaint;
let mut element = Drawable::new(f(cx));
element.layout_as_root(space.into(), cx);
cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx));
cx.window.draw_phase = DrawPhase::Paint;
let (request_layout_state, prepaint_state) = element.paint(cx);
cx.window.draw_phase = DrawPhase::None;
cx.refresh();
(request_layout_state, prepaint_state)
})
}

View File

@@ -32,12 +32,12 @@
//! your own custom layout algorithm or rendering a code editor.
use crate::{
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementId, LayoutId,
Pixels, Point, Size, Style, ViewContext, WindowContext, ELEMENT_ARENA,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
use std::{any::Any, fmt::Debug, mem};
/// Implemented by types that participate in laying out and painting the contents of a window.
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
@@ -54,7 +54,7 @@ pub trait Element: 'static + IntoElement {
/// Before an element can be painted, we need to know where it's going to be and how big it is.
/// Use this method to request a layout from Taffy and initialize the element's state.
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState);
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState);
/// After laying out an element, we need to commit its bounds to the current frame for hitbox
/// purposes. The state argument is the same state that was returned from [`Element::request_layout()`].
@@ -62,7 +62,7 @@ pub trait Element: 'static + IntoElement {
&mut self,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Self::PrepaintState;
/// Once layout has been completed, this method will be called to paint the element to the screen.
@@ -72,7 +72,7 @@ pub trait Element: 'static + IntoElement {
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
);
/// Convert this element into a dynamically-typed [`AnyElement`].
@@ -164,18 +164,13 @@ impl<C: RenderOnce> Element for Component<C> {
type RequestLayoutState = AnyElement;
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
let mut element = self
.0
.take()
.unwrap()
.render(cx.deref_mut())
.into_any_element();
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let mut element = self.0.take().unwrap().render(cx).into_any_element();
let layout_id = element.request_layout(cx);
(layout_id, element)
}
fn prepaint(&mut self, _: Bounds<Pixels>, element: &mut AnyElement, cx: &mut ElementContext) {
fn prepaint(&mut self, _: Bounds<Pixels>, element: &mut AnyElement, cx: &mut WindowContext) {
element.prepaint(cx);
}
@@ -184,7 +179,7 @@ impl<C: RenderOnce> Element for Component<C> {
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
element.paint(cx)
}
@@ -205,16 +200,16 @@ pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
trait ElementObject {
fn inner_element(&mut self) -> &mut dyn Any;
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId;
fn prepaint(&mut self, cx: &mut ElementContext);
fn prepaint(&mut self, cx: &mut WindowContext);
fn paint(&mut self, cx: &mut ElementContext);
fn paint(&mut self, cx: &mut WindowContext);
fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Size<Pixels>;
}
@@ -249,14 +244,14 @@ enum ElementDrawPhase<RequestLayoutState, PrepaintState> {
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
impl<E: Element> Drawable<E> {
fn new(element: E) -> Self {
pub(crate) fn new(element: E) -> Self {
Drawable {
element,
phase: ElementDrawPhase::Start,
}
}
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
match mem::take(&mut self.phase) {
ElementDrawPhase::Start => {
let (layout_id, request_layout) = self.element.request_layout(cx);
@@ -270,7 +265,7 @@ impl<E: Element> Drawable<E> {
}
}
fn prepaint(&mut self, cx: &mut ElementContext) {
pub(crate) fn prepaint(&mut self, cx: &mut WindowContext) {
match mem::take(&mut self.phase) {
ElementDrawPhase::RequestLayoutState {
layout_id,
@@ -296,7 +291,10 @@ impl<E: Element> Drawable<E> {
}
}
fn paint(&mut self, cx: &mut ElementContext) -> E::RequestLayoutState {
pub(crate) fn paint(
&mut self,
cx: &mut WindowContext,
) -> (E::RequestLayoutState, E::PrepaintState) {
match mem::take(&mut self.phase) {
ElementDrawPhase::PrepaintState {
node_id,
@@ -309,16 +307,16 @@ impl<E: Element> Drawable<E> {
self.element
.paint(bounds, &mut request_layout, &mut prepaint, cx);
self.phase = ElementDrawPhase::Painted;
request_layout
(request_layout, prepaint)
}
_ => panic!("must call prepaint before paint"),
}
}
fn layout_as_root(
pub(crate) fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.request_layout(cx);
@@ -368,22 +366,22 @@ where
&mut self.element
}
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
Drawable::request_layout(self, cx)
}
fn prepaint(&mut self, cx: &mut ElementContext) {
fn prepaint(&mut self, cx: &mut WindowContext) {
Drawable::prepaint(self, cx);
}
fn paint(&mut self, cx: &mut ElementContext) {
fn paint(&mut self, cx: &mut WindowContext) {
Drawable::paint(self, cx);
}
fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Size<Pixels> {
Drawable::layout_as_root(self, available_space, cx)
}
@@ -411,18 +409,18 @@ impl AnyElement {
/// Request the layout ID of the element stored in this `AnyElement`.
/// Used for laying out child elements in a parent element.
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
pub fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
self.0.request_layout(cx)
}
/// Prepares the element to be painted by storing its bounds, giving it a chance to draw hitboxes and
/// request autoscroll before the final paint pass is confirmed.
pub fn prepaint(&mut self, cx: &mut ElementContext) {
pub fn prepaint(&mut self, cx: &mut WindowContext) {
self.0.prepaint(cx)
}
/// Paints the element stored in this `AnyElement`.
pub fn paint(&mut self, cx: &mut ElementContext) {
pub fn paint(&mut self, cx: &mut WindowContext) {
self.0.paint(cx)
}
@@ -430,13 +428,13 @@ impl AnyElement {
pub fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Size<Pixels> {
self.0.layout_as_root(available_space, cx)
}
/// Prepaints this element at the given absolute origin.
pub fn prepaint_at(&mut self, origin: Point<Pixels>, cx: &mut ElementContext) {
pub fn prepaint_at(&mut self, origin: Point<Pixels>, cx: &mut WindowContext) {
cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
}
@@ -445,7 +443,7 @@ impl AnyElement {
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.layout_as_root(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
@@ -456,7 +454,7 @@ impl Element for AnyElement {
type RequestLayoutState = ();
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let layout_id = self.request_layout(cx);
(layout_id, ())
}
@@ -465,7 +463,7 @@ impl Element for AnyElement {
&mut self,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.prepaint(cx)
}
@@ -475,7 +473,7 @@ impl Element for AnyElement {
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.paint(cx)
}
@@ -508,15 +506,15 @@ impl Element for Empty {
type RequestLayoutState = ();
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
(cx.request_layout(&crate::Style::default(), None), ())
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
(cx.request_layout(&Style::default(), None), ())
}
fn prepaint(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::RequestLayoutState,
_cx: &mut ElementContext,
_cx: &mut WindowContext,
) {
}
@@ -525,7 +523,7 @@ impl Element for Empty {
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
_cx: &mut ElementContext,
_cx: &mut WindowContext,
) {
}
}

View File

@@ -2,8 +2,8 @@ use smallvec::SmallVec;
use taffy::style::{Display, Position};
use crate::{
point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement,
Pixels, Point, Size, Style,
point, AnyElement, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels, Point, Size,
Style, WindowContext,
};
/// The state that the anchored element element uses to track its children.
@@ -74,7 +74,7 @@ impl Element for Anchored {
fn request_layout(
&mut self,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> (crate::LayoutId, Self::RequestLayoutState) {
let child_layout_ids = self
.children
@@ -97,7 +97,7 @@ impl Element for Anchored {
&mut self,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
if request_layout.child_layout_ids.is_empty() {
return;
@@ -180,7 +180,7 @@ impl Element for Anchored {
_bounds: crate::Bounds<crate::Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
for child in &mut self.children {
child.paint(cx);

View File

@@ -91,7 +91,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
fn request_layout(
&mut self,
cx: &mut crate::ElementContext,
cx: &mut crate::WindowContext,
) -> (crate::LayoutId, Self::RequestLayoutState) {
cx.with_element_state(Some(self.id.clone()), |state, cx| {
let state = state.unwrap().unwrap_or_else(|| AnimationState {
@@ -138,7 +138,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
&mut self,
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::RequestLayoutState,
cx: &mut crate::ElementContext,
cx: &mut crate::WindowContext,
) -> Self::PrepaintState {
element.prepaint(cx);
}
@@ -148,7 +148,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut crate::ElementContext,
cx: &mut crate::WindowContext,
) {
element.paint(cx);
}

View File

@@ -1,12 +1,12 @@
use refineable::Refineable as _;
use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRefinement, Styled};
use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
/// Construct a canvas element with the given paint callback.
/// Useful for adding short term custom drawing to a view.
pub fn canvas<T>(
prepaint: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
prepaint: impl 'static + FnOnce(Bounds<Pixels>, &mut WindowContext) -> T,
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut WindowContext),
) -> Canvas<T> {
Canvas {
prepaint: Some(Box::new(prepaint)),
@@ -18,8 +18,8 @@ pub fn canvas<T>(
/// A canvas element, meant for accessing the low level paint API without defining a whole
/// custom element
pub struct Canvas<T> {
prepaint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
prepaint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut WindowContext) -> T>>,
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut WindowContext)>>,
style: StyleRefinement,
}
@@ -37,7 +37,7 @@ impl<T: 'static> Element for Canvas<T> {
fn request_layout(
&mut self,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> (crate::LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.refine(&self.style);
@@ -49,7 +49,7 @@ impl<T: 'static> Element for Canvas<T> {
&mut self,
bounds: Bounds<Pixels>,
_request_layout: &mut Style,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<T> {
Some(self.prepaint.take().unwrap()(bounds, cx))
}
@@ -59,7 +59,7 @@ impl<T: 'static> Element for Canvas<T> {
bounds: Bounds<Pixels>,
style: &mut Style,
prepaint: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let prepaint = prepaint.take().unwrap();
style.paint(bounds, cx, |cx| {

View File

@@ -1,4 +1,4 @@
use crate::{AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, Pixels};
use crate::{AnyElement, Bounds, Element, IntoElement, LayoutId, Pixels, WindowContext};
/// Builds a `Deferred` element, which delays the layout and paint of its child.
pub fn deferred(child: impl IntoElement) -> Deferred {
@@ -29,7 +29,7 @@ impl Element for Deferred {
type RequestLayoutState = ();
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, ()) {
let layout_id = self.child.as_mut().unwrap().request_layout(cx);
(layout_id, ())
}
@@ -38,7 +38,7 @@ impl Element for Deferred {
&mut self,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let child = self.child.take().unwrap();
let element_offset = cx.element_offset();
@@ -50,7 +50,7 @@ impl Element for Deferred {
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
_cx: &mut ElementContext,
_cx: &mut WindowContext,
) {
}
}

View File

@@ -17,11 +17,11 @@
use crate::{
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global, Hitbox,
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, Hitbox, HitboxId,
IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, ModifiersChangedEvent,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId,
View, Visibility, WindowContext,
};
use collections::HashMap;
use refineable::Refineable;
@@ -1123,7 +1123,7 @@ impl Element for Div {
type RequestLayoutState = DivFrameState;
type PrepaintState = Option<Hitbox>;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let mut child_layout_ids = SmallVec::new();
let layout_id = self.interactivity.request_layout(cx, |style, cx| {
cx.with_text_style(style.text_style().cloned(), |cx| {
@@ -1142,7 +1142,7 @@ impl Element for Div {
&mut self,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<Hitbox> {
let mut child_min = point(Pixels::MAX, Pixels::MAX);
let mut child_max = Point::default();
@@ -1197,7 +1197,7 @@ impl Element for Div {
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.interactivity
.paint(bounds, hitbox.as_ref(), cx, |_style, cx| {
@@ -1276,8 +1276,8 @@ impl Interactivity {
/// Layout this element according to this interactivity state's configured styles
pub fn request_layout(
&mut self,
cx: &mut ElementContext,
f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
cx: &mut WindowContext,
f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
) -> LayoutId {
cx.with_element_state::<InteractiveElementState, _>(
self.element_id.clone(),
@@ -1341,8 +1341,8 @@ impl Interactivity {
&mut self,
bounds: Bounds<Pixels>,
content_size: Size<Pixels>,
cx: &mut ElementContext,
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut ElementContext) -> R,
cx: &mut WindowContext,
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut WindowContext) -> R,
) -> R {
self.content_size = content_size;
cx.with_element_state::<InteractiveElementState, _>(
@@ -1406,7 +1406,7 @@ impl Interactivity {
&mut self,
bounds: Bounds<Pixels>,
style: &Style,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Point<Pixels> {
if let Some(scroll_offset) = self.scroll_offset.as_ref() {
if let Some(scroll_handle) = &self.tracked_scroll_handle {
@@ -1456,8 +1456,8 @@ impl Interactivity {
&mut self,
bounds: Bounds<Pixels>,
hitbox: Option<&Hitbox>,
cx: &mut ElementContext,
f: impl FnOnce(&Style, &mut ElementContext),
cx: &mut WindowContext,
f: impl FnOnce(&Style, &mut WindowContext),
) {
self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(cx));
cx.with_element_state::<InteractiveElementState, _>(
@@ -1482,7 +1482,7 @@ impl Interactivity {
return ((), element_state);
}
style.paint(bounds, cx, |cx: &mut ElementContext| {
style.paint(bounds, cx, |cx: &mut WindowContext| {
cx.with_text_style(style.text_style().cloned(), |cx| {
cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
if let Some(hitbox) = hitbox {
@@ -1521,7 +1521,7 @@ impl Interactivity {
}
#[cfg(debug_assertions)]
fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) {
fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) {
if self.element_id.is_some()
&& (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
&& hitbox.is_hovered(cx)
@@ -1530,7 +1530,7 @@ impl Interactivity {
let element_id = format!("{:?}", self.element_id.as_ref().unwrap());
let str_len = element_id.len();
let render_debug_text = |cx: &mut ElementContext| {
let render_debug_text = |cx: &mut WindowContext| {
if let Some(text) = cx
.text_system()
.shape_text(
@@ -1629,7 +1629,7 @@ impl Interactivity {
&mut self,
hitbox: &Hitbox,
element_state: Option<&mut InteractiveElementState>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
// If this element can be focused, register a mouse down listener
// that will automatically transfer focus when hitting the element.
@@ -1712,11 +1712,11 @@ impl Interactivity {
let mut can_drop = true;
if let Some(predicate) = &can_drop_predicate {
can_drop = predicate(drag.value.as_ref(), cx.deref_mut());
can_drop = predicate(drag.value.as_ref(), cx);
}
if can_drop {
listener(drag.value.as_ref(), cx.deref_mut());
listener(drag.value.as_ref(), cx);
cx.refresh();
cx.stop_propagation();
}
@@ -1840,7 +1840,7 @@ impl Interactivity {
*was_hovered = is_hovered;
drop(was_hovered);
hover_listener(&is_hovered, cx.deref_mut());
hover_listener(&is_hovered, cx);
}
});
}
@@ -1969,7 +1969,7 @@ impl Interactivity {
}
}
fn paint_keyboard_listeners(&mut self, cx: &mut ElementContext) {
fn paint_keyboard_listeners(&mut self, cx: &mut WindowContext) {
let key_down_listeners = mem::take(&mut self.key_down_listeners);
let key_up_listeners = mem::take(&mut self.key_up_listeners);
let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
@@ -2004,7 +2004,7 @@ impl Interactivity {
}
}
fn paint_hover_group_handler(&self, cx: &mut ElementContext) {
fn paint_hover_group_handler(&self, cx: &mut WindowContext) {
let group_hitbox = self
.group_hover_style
.as_ref()
@@ -2021,7 +2021,7 @@ impl Interactivity {
}
}
fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) {
fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) {
if let Some(scroll_offset) = self.scroll_offset.clone() {
let overflow = style.overflow;
let line_height = cx.line_height();
@@ -2064,7 +2064,7 @@ impl Interactivity {
}
/// Compute the visual style for this element, based on the current bounds and the element's state.
pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut ElementContext) -> Style {
pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut WindowContext) -> Style {
cx.with_element_state(self.element_id.clone(), |element_state, cx| {
let mut element_state =
element_state.map(|element_state| element_state.unwrap_or_default());
@@ -2078,7 +2078,7 @@ impl Interactivity {
&self,
hitbox: Option<&Hitbox>,
element_state: Option<&mut InteractiveElementState>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Style {
let mut style = Style::default();
style.refine(&self.base_style);
@@ -2119,7 +2119,7 @@ impl Interactivity {
if let Some(drag) = cx.active_drag.take() {
let mut can_drop = true;
if let Some(can_drop_predicate) = &self.can_drop_predicate {
can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
can_drop = can_drop_predicate(drag.value.as_ref(), cx);
}
if can_drop {
@@ -2264,7 +2264,7 @@ where
type RequestLayoutState = E::RequestLayoutState;
type PrepaintState = E::PrepaintState;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
self.element.request_layout(cx)
}
@@ -2272,7 +2272,7 @@ where
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> E::PrepaintState {
self.element.prepaint(bounds, state, cx)
}
@@ -2282,7 +2282,7 @@ where
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.element.paint(bounds, request_layout, prepaint, cx)
}
@@ -2347,7 +2347,7 @@ where
type RequestLayoutState = E::RequestLayoutState;
type PrepaintState = E::PrepaintState;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
self.element.request_layout(cx)
}
@@ -2355,7 +2355,7 @@ where
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> E::PrepaintState {
self.element.prepaint(bounds, state, cx)
}
@@ -2365,7 +2365,7 @@ where
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.element.paint(bounds, request_layout, prepaint, cx);
}

View File

@@ -3,9 +3,9 @@ use std::path::PathBuf;
use std::sync::Arc;
use crate::{
point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element,
ElementContext, Hitbox, ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId,
Length, Pixels, SharedUri, Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext,
point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element, Hitbox,
ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId, Length, Pixels, SharedUri,
Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext,
};
use futures::{AsyncReadExt, Future};
use image::{ImageBuffer, ImageError};
@@ -232,7 +232,7 @@ impl Element for Img {
type RequestLayoutState = ();
type PrepaintState = Option<Hitbox>;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
if let Some(data) = self.source.data(cx) {
let image_size = data.size();
@@ -260,7 +260,7 @@ impl Element for Img {
&mut self,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<Hitbox> {
self.interactivity
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
@@ -271,7 +271,7 @@ impl Element for Img {
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let source = self.source.clone();
self.interactivity
@@ -319,7 +319,7 @@ impl InteractiveElement for Img {
}
impl ImageSource {
fn data(&self, cx: &mut ElementContext) -> Option<Arc<ImageData>> {
fn data(&self, cx: &mut WindowContext) -> Option<Arc<ImageData>> {
match self {
ImageSource::Uri(_) | ImageSource::File(_) => {
let uri_or_path: UriOrPath = match self {

View File

@@ -8,8 +8,8 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
Element, ElementContext, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent,
Size, Style, StyleRefinement, Styled, WindowContext,
Element, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
StyleRefinement, Styled, WindowContext,
};
use collections::VecDeque;
use refineable::Refineable as _;
@@ -434,7 +434,7 @@ impl StateInner {
available_width: Option<Pixels>,
available_height: Pixels,
padding: &Edges<Pixels>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> LayoutItemsResponse {
let old_items = self.items.clone();
let mut measured_items = VecDeque::new();
@@ -609,7 +609,7 @@ impl StateInner {
bounds: Bounds<Pixels>,
padding: Edges<Pixels>,
autoscroll: bool,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Result<LayoutItemsResponse, ListOffset> {
cx.transact(|cx| {
let mut layout_response =
@@ -706,7 +706,7 @@ impl Element for List {
fn request_layout(
&mut self,
cx: &mut crate::ElementContext,
cx: &mut crate::WindowContext,
) -> (crate::LayoutId, Self::RequestLayoutState) {
let layout_id = match self.sizing_behavior {
ListSizingBehavior::Infer => {
@@ -772,7 +772,7 @@ impl Element for List {
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> ListPrepaintState {
let state = &mut *self.state.0.borrow_mut();
state.reset = false;
@@ -815,7 +815,7 @@ impl Element for List {
bounds: Bounds<crate::Pixels>,
_: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
cx: &mut crate::ElementContext,
cx: &mut crate::WindowContext,
) {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
for item in &mut prepaint.layout.item_layouts {
@@ -951,11 +951,9 @@ mod test {
});
// Paint
cx.draw(
point(px(0.), px(0.)),
size(px(100.), px(20.)).into(),
|_| list(state.clone()).w_full().h_full().into_any(),
);
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_| {
list(state.clone()).w_full().h_full()
});
// Reset
state.reset(5);

View File

@@ -1,7 +1,7 @@
use crate::{
geometry::Negate as _, point, px, radians, size, Bounds, Element, ElementContext, Hitbox,
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString,
Size, StyleRefinement, Styled, TransformationMatrix,
geometry::Negate as _, point, px, radians, size, Bounds, Element, Hitbox, InteractiveElement,
Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size,
StyleRefinement, Styled, TransformationMatrix, WindowContext,
};
use util::ResultExt;
@@ -40,7 +40,7 @@ impl Element for Svg {
type RequestLayoutState = ();
type PrepaintState = Option<Hitbox>;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let layout_id = self
.interactivity
.request_layout(cx, |style, cx| cx.request_layout(&style, None));
@@ -51,7 +51,7 @@ impl Element for Svg {
&mut self,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<Hitbox> {
self.interactivity
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
@@ -62,7 +62,7 @@ impl Element for Svg {
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) where
Self: Sized,
{

View File

@@ -1,8 +1,7 @@
use crate::{
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
TOOLTIP_DELAY,
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, HighlightStyle,
Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point,
SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
@@ -20,7 +19,7 @@ impl Element for &'static str {
type RequestLayoutState = TextState;
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let mut state = TextState::default();
let layout_id = state.layout(SharedString::from(*self), None, cx);
(layout_id, state)
@@ -30,7 +29,7 @@ impl Element for &'static str {
&mut self,
_bounds: Bounds<Pixels>,
_text_state: &mut Self::RequestLayoutState,
_cx: &mut ElementContext,
_cx: &mut WindowContext,
) {
}
@@ -39,7 +38,7 @@ impl Element for &'static str {
bounds: Bounds<Pixels>,
text_state: &mut TextState,
_: &mut (),
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
text_state.paint(bounds, self, cx)
}
@@ -65,7 +64,7 @@ impl Element for SharedString {
type RequestLayoutState = TextState;
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let mut state = TextState::default();
let layout_id = state.layout(self.clone(), None, cx);
(layout_id, state)
@@ -75,7 +74,7 @@ impl Element for SharedString {
&mut self,
_bounds: Bounds<Pixels>,
_text_state: &mut Self::RequestLayoutState,
_cx: &mut ElementContext,
_cx: &mut WindowContext,
) {
}
@@ -84,7 +83,7 @@ impl Element for SharedString {
bounds: Bounds<Pixels>,
text_state: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let text_str: &str = self.as_ref();
text_state.paint(bounds, text_str, cx)
@@ -151,7 +150,7 @@ impl Element for StyledText {
type RequestLayoutState = TextState;
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let mut state = TextState::default();
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
(layout_id, state)
@@ -161,7 +160,7 @@ impl Element for StyledText {
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::RequestLayoutState,
_cx: &mut ElementContext,
_cx: &mut WindowContext,
) {
}
@@ -170,7 +169,7 @@ impl Element for StyledText {
bounds: Bounds<Pixels>,
text_state: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
text_state.paint(bounds, &self.text, cx)
}
@@ -204,7 +203,7 @@ impl TextState {
&mut self,
text: SharedString,
runs: Option<Vec<TextRun>>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> LayoutId {
let text_style = cx.text_style();
let font_size = text_style.font_size.to_pixels(cx.rem_size());
@@ -279,7 +278,7 @@ impl TextState {
layout_id
}
fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut ElementContext) {
fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
let element_state = self.lock();
let element_state = element_state
.as_ref()
@@ -405,7 +404,7 @@ impl Element for InteractiveText {
type RequestLayoutState = TextState;
type PrepaintState = Hitbox;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
self.text.request_layout(cx)
}
@@ -413,7 +412,7 @@ impl Element for InteractiveText {
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Hitbox {
cx.with_element_state::<InteractiveTextState, _>(
Some(self.element_id.clone()),
@@ -442,7 +441,7 @@ impl Element for InteractiveText {
bounds: Bounds<Pixels>,
text_state: &mut Self::RequestLayoutState,
hitbox: &mut Hitbox,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
cx.with_element_state::<InteractiveTextState, _>(
Some(self.element_id.clone()),

View File

@@ -5,9 +5,9 @@
//! elements with uniform height.
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId, Hitbox,
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render, ScrollHandle, Size,
StyleRefinement, Styled, View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -107,7 +107,7 @@ impl Element for UniformList {
type RequestLayoutState = UniformListFrameState;
type PrepaintState = Option<Hitbox>;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let max_items = self.item_count;
let item_size = self.measure_item(None, cx);
let layout_id = self.interactivity.request_layout(cx, |style, cx| {
@@ -141,7 +141,7 @@ impl Element for UniformList {
&mut self,
bounds: Bounds<Pixels>,
frame_state: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<Hitbox> {
let style = self.interactivity.compute_style(None, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
@@ -239,7 +239,7 @@ impl Element for UniformList {
bounds: Bounds<crate::Pixels>,
request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.interactivity
.paint(bounds, hitbox.as_ref(), cx, |_, cx| {
@@ -265,7 +265,7 @@ impl UniformList {
self
}
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut ElementContext) -> Size<Pixels> {
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
if self.item_count == 0 {
return Size::default();
}

View File

@@ -50,9 +50,8 @@
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
///
use crate::{
Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent,
WindowContext,
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent, WindowContext,
};
use collections::FxHashMap;
use smallvec::SmallVec;
@@ -107,8 +106,8 @@ impl ReusedSubtree {
}
}
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut ElementContext)>;
type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut ElementContext)>;
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut WindowContext)>;
#[derive(Clone)]
pub(crate) struct DispatchActionListener {

View File

@@ -12,7 +12,7 @@ use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
#[cfg(target_os = "macos")]
use std::ffi::c_void;
use std::{ffi::c_void, ptr::NonNull};
use blade_graphics as gpu;
use std::{mem, sync::Arc};
@@ -25,35 +25,32 @@ pub type Renderer = BladeRenderer;
#[cfg(target_os = "macos")]
pub unsafe fn new_renderer(
_context: self::Context,
native_window: *mut c_void,
_native_window: *mut c_void,
native_view: *mut c_void,
bounds: crate::Size<f32>,
) -> Renderer {
use raw_window_handle as rwh;
struct RawWindow {
window: *mut c_void,
view: *mut c_void,
}
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
let mut wh = blade_rwh::AppKitWindowHandle::empty();
wh.ns_window = self.window;
wh.ns_view = self.view;
wh.into()
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let view = NonNull::new(self.view).unwrap();
let handle = rwh::AppKitWindowHandle::new(view);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
let dh = blade_rwh::AppKitDisplayHandle::empty();
dh.into()
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let handle = rwh::AppKitDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
let gpu = Arc::new(
gpu::Context::init_windowed(
&RawWindow {
window: native_window as *mut _,
view: native_view as *mut _,
},
gpu::ContextDesc {
@@ -184,7 +181,7 @@ struct BladePipelines {
}
impl BladePipelines {
fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo) -> Self {
use gpu::ShaderData as _;
let shader = gpu.create_shader(gpu::ShaderDesc {
@@ -216,7 +213,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_quad"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -233,7 +230,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_shadow"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -267,7 +264,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_path"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -284,7 +281,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_underline"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -301,7 +298,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_mono_sprite"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -318,7 +315,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_poly_sprite"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -335,7 +332,7 @@ impl BladePipelines {
depth_stencil: None,
fragment: shader.at("fs_surface"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
format: surface_info.format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
@@ -367,16 +364,18 @@ impl BladeRenderer {
//Note: this matches the original logic of the Metal backend,
// but ultimaterly we need to switch to `Linear`.
color_space: gpu::ColorSpace::Srgb,
allow_exclusive_full_screen: false,
transparent: false,
}
}
pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
let surface_format = gpu.resize(Self::make_surface_config(size));
let surface_info = gpu.resize(Self::make_surface_config(size));
let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
name: "main",
buffer_count: 2,
});
let pipelines = BladePipelines::new(&gpu, surface_format);
let pipelines = BladePipelines::new(&gpu, surface_info);
let instance_belt = BladeBelt::new(BladeBeltDescriptor {
memory: gpu::Memory::Shared,
min_chunk_size: 0x1000,

View File

@@ -2,16 +2,14 @@ use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::c_void;
use std::num::NonZeroU32;
use std::ptr::NonNull;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use blade_graphics as gpu;
use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
use collections::{HashMap, HashSet};
use futures::channel::oneshot::Receiver;
use raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
};
use raw_window_handle as rwh;
use wayland_backend::client::ObjectId;
use wayland_client::WEnum;
use wayland_client::{protocol::wl_surface, Proxy};
@@ -49,19 +47,18 @@ struct RawWindow {
display: *mut c_void,
}
unsafe impl HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut wh = blade_rwh::WaylandWindowHandle::empty();
wh.surface = self.window;
wh.into()
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
let window = NonNull::new(self.window).unwrap();
let handle = rwh::WaylandWindowHandle::new(window);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
unsafe impl HasRawDisplayHandle for RawWindow {
fn raw_display_handle(&self) -> RawDisplayHandle {
let mut dh = blade_rwh::WaylandDisplayHandle::empty();
dh.display = self.display;
dh.into()
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
let display = NonNull::new(self.display).unwrap();
let handle = rwh::WaylandDisplayHandle::new(display);
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
@@ -520,14 +517,13 @@ impl WaylandWindowStatePtr {
}
}
impl HasWindowHandle for WaylandWindow {
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
impl rwh::HasWindowHandle for WaylandWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
unimplemented!()
}
}
impl HasDisplayHandle for WaylandWindow {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
impl rwh::HasDisplayHandle for WaylandWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
unimplemented!()
}
}

View File

@@ -77,8 +77,8 @@ pub struct Callbacks {
}
pub(crate) struct X11WindowState {
raw: RawWindow,
atoms: XcbAtoms,
raw: RawWindow,
bounds: Bounds<i32>,
scale_factor: f32,
renderer: BladeRenderer,
@@ -96,40 +96,29 @@ pub(crate) struct X11Window {
}
// todo(linux): Remove other RawWindowHandle implementation
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
let mut wh = blade_rwh::XcbWindowHandle::empty();
wh.window = self.window_id;
wh.visual_id = self.visual_id;
wh.into()
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let non_zero = NonZeroU32::new(self.window_id).unwrap();
let handle = rwh::XcbWindowHandle::new(non_zero);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
let mut dh = blade_rwh::XcbDisplayHandle::empty();
dh.connection = self.connection;
dh.screen = self.screen_id as i32;
dh.into()
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let non_zero = NonNull::new(self.connection).unwrap();
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasWindowHandle for X11Window {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
Ok(unsafe {
let non_zero = NonZeroU32::new(self.state.borrow().raw.window_id).unwrap();
let handle = rwh::XcbWindowHandle::new(non_zero);
rwh::WindowHandle::borrow_raw(handle.into())
})
unimplemented!()
}
}
impl rwh::HasDisplayHandle for X11Window {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
Ok(unsafe {
let this = self.state.borrow();
let non_zero = NonNull::new(this.raw.connection).unwrap();
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), this.raw.screen_id as i32);
rwh::DisplayHandle::borrow_raw(handle.into())
})
unimplemented!()
}
}

View File

@@ -31,10 +31,7 @@ use objc::{
sel, sel_impl,
};
use parking_lot::Mutex;
use raw_window_handle::{
AppKitDisplayHandle, AppKitWindowHandle, DisplayHandle, HasDisplayHandle, HasWindowHandle,
RawWindowHandle, WindowHandle,
};
use raw_window_handle as rwh;
use smallvec::SmallVec;
use std::{
any::Any,
@@ -1141,25 +1138,25 @@ impl PlatformWindow for MacWindow {
}
}
impl HasWindowHandle for MacWindow {
fn window_handle(
&self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
impl rwh::HasWindowHandle for MacWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
// SAFETY: The AppKitWindowHandle is a wrapper around a pointer to an NSView
unsafe {
Ok(WindowHandle::borrow_raw(RawWindowHandle::AppKit(
AppKitWindowHandle::new(self.0.lock().native_view.cast()),
Ok(rwh::WindowHandle::borrow_raw(rwh::RawWindowHandle::AppKit(
rwh::AppKitWindowHandle::new(self.0.lock().native_view.cast()),
)))
}
}
}
impl HasDisplayHandle for MacWindow {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
impl rwh::HasDisplayHandle for MacWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
// SAFETY: This is a no-op on macOS
unsafe { Ok(DisplayHandle::borrow_raw(AppKitDisplayHandle::new().into())) }
unsafe {
Ok(rwh::DisplayHandle::borrow_raw(
rwh::AppKitDisplayHandle::new().into(),
))
}
}
}

View File

@@ -16,9 +16,11 @@ use windows::{
Direct2D::{Common::*, *},
DirectWrite::*,
Dxgi::Common::*,
Gdi::LOGFONTW,
Imaging::{D2D::IWICImagingFactory2, *},
},
System::{Com::*, SystemServices::LOCALE_NAME_MAX_LENGTH},
UI::WindowsAndMessaging::*,
},
};
@@ -51,6 +53,7 @@ unsafe impl Send for DirectWriteComponent {}
struct DirectWriteState {
components: DirectWriteComponent,
system_ui_font_name: SharedString,
system_font_collection: IDWriteFontCollection1,
custom_font_collection: IDWriteFontCollection1,
fonts: Vec<FontInfo>,
@@ -106,9 +109,11 @@ impl DirectWriteTextSystem {
.factory
.CreateFontCollectionFromFontSet(&custom_font_set)?
};
let system_ui_font_name = get_system_ui_font_name();
Ok(Self(RwLock::new(DirectWriteState {
components,
system_ui_font_name,
system_font_collection,
custom_font_collection,
fonts: Vec::new(),
@@ -309,53 +314,55 @@ impl DirectWriteState {
}
fn select_font(&mut self, target_font: &Font) -> FontId {
let family_name = if target_font.family == ".SystemUIFont" {
// https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
// Segoe UI is the Windows font intended for user interface text strings.
"Segoe UI"
} else {
target_font.family.as_ref()
};
unsafe {
// try to find target font in custom font collection first
self.get_font_id_from_font_collection(
family_name,
target_font.weight,
target_font.style,
&target_font.features,
false,
)
.or_else(|| {
self.get_font_id_from_font_collection(
family_name,
if target_font.family == ".SystemUIFont" {
let family = self.system_ui_font_name.clone();
self.find_font_id(
family.as_ref(),
target_font.weight,
target_font.style,
&target_font.features,
true,
)
.unwrap()
} else {
self.find_font_id(
target_font.family.as_ref(),
target_font.weight,
target_font.style,
&target_font.features,
)
.unwrap_or_else(|| {
let family = self.system_ui_font_name.clone();
log::error!("{} not found, use {} instead.", target_font.family, family);
self.get_font_id_from_font_collection(
family.as_ref(),
target_font.weight,
target_font.style,
&target_font.features,
true,
)
.unwrap()
})
}
}
}
unsafe fn find_font_id(
&mut self,
family_name: &str,
weight: FontWeight,
style: FontStyle,
features: &FontFeatures,
) -> Option<FontId> {
// try to find target font in custom font collection first
self.get_font_id_from_font_collection(family_name, weight, style, features, false)
.or_else(|| {
self.get_font_id_from_font_collection(family_name, weight, style, features, true)
})
.or_else(|| {
self.update_system_font_collection();
self.get_font_id_from_font_collection(
family_name,
target_font.weight,
target_font.style,
&target_font.features,
true,
)
self.get_font_id_from_font_collection(family_name, weight, style, features, true)
})
.or_else(|| {
log::error!("{} not found, use Arial instead.", family_name);
self.get_font_id_from_font_collection(
"Arial",
target_font.weight,
target_font.style,
&target_font.features,
false,
)
})
.unwrap()
}
}
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
@@ -1271,6 +1278,29 @@ fn translate_color(color: &DWRITE_COLOR_F) -> D2D1_COLOR_F {
}
}
fn get_system_ui_font_name() -> SharedString {
unsafe {
let mut info: LOGFONTW = std::mem::zeroed();
let font_family = if SystemParametersInfoW(
SPI_GETICONTITLELOGFONT,
std::mem::size_of::<LOGFONTW>() as u32,
Some(&mut info as *mut _ as _),
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
)
.log_err()
.is_none()
{
// https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
// Segoe UI is the Windows font intended for user interface text strings.
"Segoe UI".into()
} else {
String::from_utf16_lossy(&info.lfFaceName).into()
};
log::info!("Use {} as UI font.", font_family);
font_family
}
}
const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
r: 1.0,

View File

@@ -3,7 +3,6 @@
use std::{
any::Any,
cell::{Cell, RefCell},
ffi::c_void,
iter::once,
num::NonZeroIsize,
path::PathBuf,
@@ -18,7 +17,7 @@ use anyhow::Context;
use blade_graphics as gpu;
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use raw_window_handle as rwh;
use smallvec::SmallVec;
use std::result::Result;
use windows::{
@@ -77,20 +76,24 @@ impl WindowsWindowInner {
let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
let input_handler = Cell::new(None);
struct RawWindow {
hwnd: *mut c_void,
hwnd: isize,
}
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
let mut handle = blade_rwh::Win32WindowHandle::empty();
handle.hwnd = self.hwnd;
handle.into()
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
Ok(unsafe {
let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
let handle = rwh::Win32WindowHandle::new(hwnd);
rwh::WindowHandle::borrow_raw(handle.into())
})
}
}
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
blade_rwh::WindowsDisplayHandle::empty().into()
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
let handle = rwh::WindowsDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
let raw = RawWindow { hwnd: hwnd.0 as _ };
let gpu = Arc::new(
unsafe {
@@ -698,12 +701,13 @@ impl WindowsWindowInner {
if let Some(callback) = callbacks.input.as_mut() {
let x = lparam.signed_loword() as f32;
let y = lparam.signed_hiword() as f32;
let click_count = self.click_state.borrow().current_count;
let scale_factor = self.scale_factor.get();
let event = MouseUpEvent {
button,
position: logical_point(x, y, scale_factor),
modifiers: self.current_modifiers(),
click_count: 1,
click_count,
};
if callback(PlatformInput::MouseUp(event)).default_prevented {
return Some(0);
@@ -1316,23 +1320,18 @@ impl WindowsWindow {
}
}
impl HasWindowHandle for WindowsWindow {
fn window_handle(
&self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
NonZeroIsize::new_unchecked(self.inner.hwnd.0)
})
.into();
Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
impl rwh::HasWindowHandle for WindowsWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
let raw =
rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.inner.hwnd.0) })
.into();
Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
}
}
// todo(windows)
impl HasDisplayHandle for WindowsWindow {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
impl rwh::HasDisplayHandle for WindowsWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
unimplemented!()
}
}

View File

@@ -2,9 +2,9 @@ use std::{iter, mem, ops::Range};
use crate::{
black, phi, point, quad, rems, AbsoluteLength, Bounds, ContentMask, Corners, CornersRefinement,
CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font, FontFeatures,
FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size,
SizeRefinement, Styled, TextRun,
CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight,
Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, SizeRefinement, Styled,
TextRun, WindowContext,
};
use collections::HashSet;
use refineable::Refineable;
@@ -391,8 +391,8 @@ impl Style {
pub fn paint(
&self,
bounds: Bounds<Pixels>,
cx: &mut ElementContext,
continuation: impl FnOnce(&mut ElementContext),
cx: &mut WindowContext,
continuation: impl FnOnce(&mut WindowContext),
) {
#[cfg(debug_assertions)]
if self.debug_below {

View File

@@ -1,6 +1,6 @@
use crate::{
black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result,
SharedString, StrikethroughStyle, UnderlineStyle, WrapBoundary, WrappedLineLayout,
black, fill, point, px, size, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,
StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
};
use derive_more::{Deref, DerefMut};
use smallvec::SmallVec;
@@ -48,7 +48,7 @@ impl ShapedLine {
&self,
origin: Point<Pixels>,
line_height: Pixels,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Result<()> {
paint_line(
origin,
@@ -86,7 +86,7 @@ impl WrappedLine {
&self,
origin: Point<Pixels>,
line_height: Pixels,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Result<()> {
paint_line(
origin,
@@ -107,7 +107,7 @@ fn paint_line(
line_height: Pixels,
decoration_runs: &[DecorationRun],
wrap_boundaries: &[WrapBoundary],
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Result<()> {
let line_bounds = Bounds::new(origin, size(layout.width, line_height));
cx.paint_layer(line_bounds, |cx| {

View File

@@ -1,8 +1,8 @@
use crate::{
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds, ContentMask, Element,
ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement,
LayoutId, Model, PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement,
TextStyle, ViewContext, VisualContext, WeakModel,
ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, LayoutId, Model,
PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, ViewContext,
VisualContext, WeakModel, WindowContext,
};
use anyhow::{Context, Result};
use refineable::Refineable;
@@ -93,7 +93,7 @@ impl<V: Render> Element for View<V> {
type RequestLayoutState = AnyElement;
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
let layout_id = element.request_layout(cx);
@@ -105,7 +105,7 @@ impl<V: Render> Element for View<V> {
&mut self,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
cx.set_view_id(self.entity_id());
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
@@ -118,7 +118,7 @@ impl<V: Render> Element for View<V> {
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.paint(cx)
@@ -220,7 +220,7 @@ impl<V> Eq for WeakView<V> {}
#[derive(Clone, Debug)]
pub struct AnyView {
model: AnyModel,
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
render: fn(&AnyView, &mut WindowContext) -> AnyElement,
cached_style: Option<StyleRefinement>,
}
@@ -279,7 +279,7 @@ impl Element for AnyView {
type RequestLayoutState = Option<AnyElement>;
type PrepaintState = Option<AnyElement>;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
if let Some(style) = self.cached_style.as_ref() {
let mut root_style = Style::default();
root_style.refine(style);
@@ -298,7 +298,7 @@ impl Element for AnyView {
&mut self,
bounds: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<AnyElement> {
cx.set_view_id(self.entity_id());
if self.cached_style.is_some() {
@@ -359,7 +359,7 @@ impl Element for AnyView {
_bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
element: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
if self.cached_style.is_some() {
cx.with_element_state::<AnyViewState, _>(
@@ -408,7 +408,7 @@ impl IntoElement for AnyView {
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
pub struct AnyWeakView {
model: AnyWeakModel,
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
render: fn(&AnyView, &mut WindowContext) -> AnyElement,
}
impl AnyWeakView {
@@ -447,11 +447,11 @@ impl std::fmt::Debug for AnyWeakView {
}
mod any_view {
use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render};
use crate::{AnyElement, AnyView, IntoElement, Render, WindowContext};
pub(crate) fn render<V: 'static + Render>(
view: &AnyView,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> AnyElement {
let view = view.clone().downcast::<V>().unwrap();
view.update(cx, |view, cx| view.render(cx).into_any_element())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ project.workspace = true
fs.workspace = true
futures.workspace = true
settings.workspace = true
shellexpand.workspace = true
postage.workspace = true
[dev-dependencies]

View File

@@ -180,7 +180,8 @@ impl DevServer {
_: Arc<Client>,
cx: AsyncAppContext,
) -> Result<proto::Ack> {
let path = std::path::Path::new(&envelope.payload.path);
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
let path = std::path::Path::new(&expanded);
let fs = cx.read_model(&this, |this, _| this.app_state.fs.clone())?;
let path_exists = fs.is_dir(path).await;
@@ -232,9 +233,11 @@ impl DevServer {
(this.client.clone(), project)
})?;
let path = shellexpand::tilde(&remote_project.path).to_string();
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree(&remote_project.path, true, cx)
project.find_or_create_local_worktree(&path, true, cx)
})?
.await?;

View File

@@ -155,7 +155,7 @@ impl FocusableView for ImageView {
impl Render for ImageView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut ElementContext| {
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut WindowContext| {
let square_size = 32.0;
let start_y = bounds.origin.y.0;

View File

@@ -13,7 +13,7 @@ use crate::{
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
SyntaxSnapshot, ToTreeSitterPoint,
},
LanguageScope, Outline,
LanguageScope, Outline, RunnableTag,
};
use anyhow::{anyhow, Context, Result};
pub use clock::ReplicaId;
@@ -498,6 +498,13 @@ pub enum CharKind {
Word,
}
/// A runnable is a set of data about a region that could be resolved into a task
pub struct Runnable {
pub tags: SmallVec<[RunnableTag; 1]>,
pub language: Arc<Language>,
pub buffer: BufferId,
}
impl Buffer {
/// Create a new buffer with the given base text.
pub fn local<T: Into<String>>(base_text: T, cx: &mut ModelContext<Self>) -> Self {
@@ -2948,6 +2955,53 @@ impl BufferSnapshot {
})
}
pub fn runnable_ranges(
&self,
range: Range<Anchor>,
) -> impl Iterator<Item = (Range<usize>, Runnable)> + '_ {
let offset_range = range.start.to_offset(self)..range.end.to_offset(self);
let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| {
grammar.runnable_config.as_ref().map(|config| &config.query)
});
let test_configs = syntax_matches
.grammars()
.iter()
.map(|grammar| grammar.runnable_config.as_ref())
.collect::<Vec<_>>();
iter::from_fn(move || {
let test_range = syntax_matches
.peek()
.and_then(|mat| {
test_configs[mat.grammar_index].and_then(|test_configs| {
let tags = SmallVec::from_iter(mat.captures.iter().filter_map(|capture| {
test_configs.runnable_tags.get(&capture.index).cloned()
}));
if tags.is_empty() {
return None;
}
Some((
mat.captures
.iter()
.find(|capture| capture.index == test_configs.run_capture_ix)?,
Runnable {
tags,
language: mat.language,
buffer: self.remote_id(),
},
))
})
})
.map(|(mat, test_tags)| (mat.node.byte_range(), test_tags));
syntax_matches.advance();
test_range
})
}
/// Returns selections for remote peers intersecting the given range.
#[allow(clippy::type_complexity)]
pub fn remote_selections_in_range(

View File

@@ -56,6 +56,7 @@ use std::{
},
};
use syntax_map::SyntaxSnapshot;
use task::RunnableTag;
pub use task_context::{BasicContextProvider, ContextProvider, ContextProviderWithTasks};
use theme::SyntaxTheme;
use tree_sitter::{self, wasmtime, Query, WasmStore};
@@ -828,6 +829,7 @@ pub struct Grammar {
pub(crate) highlights_query: Option<Query>,
pub(crate) brackets_config: Option<BracketConfig>,
pub(crate) redactions_config: Option<RedactionConfig>,
pub(crate) runnable_config: Option<RunnableConfig>,
pub(crate) indents_config: Option<IndentConfig>,
pub outline_config: Option<OutlineConfig>,
pub embedding_config: Option<EmbeddingConfig>,
@@ -874,6 +876,14 @@ struct RedactionConfig {
pub redaction_capture_ix: u32,
}
struct RunnableConfig {
pub query: Query,
/// A mapping from captures indices to known test tags
pub runnable_tags: HashMap<u32, RunnableTag>,
/// index of the capture that corresponds to @run
pub run_capture_ix: u32,
}
struct OverrideConfig {
query: Query,
values: HashMap<u32, (String, LanguageConfigOverride)>,
@@ -915,6 +925,7 @@ impl Language {
injection_config: None,
override_config: None,
redactions_config: None,
runnable_config: None,
error_query: Query::new(&ts_language, "(ERROR) @error").unwrap(),
ts_language,
highlight_map: Default::default(),
@@ -970,6 +981,11 @@ impl Language {
.with_redaction_query(query.as_ref())
.context("Error loading redaction query")?;
}
if let Some(query) = queries.runnables {
self = self
.with_runnable_query(query.as_ref())
.context("Error loading tests query")?;
}
Ok(self)
}
@@ -981,6 +997,33 @@ impl Language {
Ok(self)
}
pub fn with_runnable_query(mut self, source: &str) -> Result<Self> {
let grammar = self
.grammar_mut()
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;
let query = Query::new(&grammar.ts_language, source)?;
let mut run_capture_index = None;
let mut runnable_tags = HashMap::default();
for (ix, name) in query.capture_names().iter().enumerate() {
if *name == "run" {
run_capture_index = Some(ix as u32);
} else if !name.starts_with('_') {
runnable_tags.insert(ix as u32, RunnableTag(name.to_string().into()));
}
}
if let Some(run_capture_ix) = run_capture_index {
grammar.runnable_config = Some(RunnableConfig {
query,
run_capture_ix,
runnable_tags,
});
}
Ok(self)
}
pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
let grammar = self
.grammar_mut()

View File

@@ -46,6 +46,8 @@ struct LanguageRegistryState {
available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
subscription: (watch::Sender<()>, watch::Receiver<()>),
theme: Option<Arc<Theme>>,
@@ -122,6 +124,7 @@ pub const QUERY_FILENAME_PREFIXES: &[(
("injections", |q| &mut q.injections),
("overrides", |q| &mut q.overrides),
("redactions", |q| &mut q.redactions),
("runnables", |q| &mut q.runnables),
];
/// Tree-sitter language queries for a given language.
@@ -135,6 +138,7 @@ pub struct LanguageQueries {
pub injections: Option<Cow<'static, str>>,
pub overrides: Option<Cow<'static, str>>,
pub redactions: Option<Cow<'static, str>>,
pub runnables: Option<Cow<'static, str>>,
}
#[derive(Clone, Default)]
@@ -153,6 +157,7 @@ impl LanguageRegistry {
language_settings: Default::default(),
loading_languages: Default::default(),
lsp_adapters: Default::default(),
available_lsp_adapters: HashMap::default(),
subscription: watch::channel(),
theme: Default::default(),
version: 0,
@@ -213,6 +218,38 @@ impl LanguageRegistry {
)
}
/// Registers an available language server adapter.
///
/// The language server is registered under the language server name, but
/// not bound to a particular language.
///
/// When a language wants to load this particular language server, it will
/// invoke the `load` function.
pub fn register_available_lsp_adapter(
&self,
name: LanguageServerName,
load: impl Fn() -> Arc<dyn LspAdapter> + 'static + Send + Sync,
) {
self.state.write().available_lsp_adapters.insert(
name,
Arc::new(move || {
let lsp_adapter = load();
CachedLspAdapter::new(lsp_adapter, true)
}),
);
}
/// Loads the language server adapter for the language server with the given name.
pub fn load_available_lsp_adapter(
&self,
name: &LanguageServerName,
) -> Option<Arc<CachedLspAdapter>> {
let state = self.state.read();
let load_lsp_adapter = state.available_lsp_adapters.get(name)?;
Some(load_lsp_adapter())
}
pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
self.state
.write()

View File

@@ -789,5 +789,24 @@ mod tests {
),
language_server_names(&["deno", "eslint", "tailwind"])
);
// Adding a language server not in the list of available language servers adds it to the list.
assert_eq!(
LanguageSettings::resolve_language_servers(
&[
"my-cool-language-server".into(),
LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
],
&available_language_servers
),
language_server_names(&[
"my-cool-language-server",
"typescript-language-server",
"biome",
"deno",
"eslint",
"tailwind",
])
);
}
}

View File

@@ -59,6 +59,7 @@ pub struct SyntaxMapCapture<'a> {
#[derive(Debug)]
pub struct SyntaxMapMatch<'a> {
pub language: Arc<Language>,
pub depth: usize,
pub pattern_index: usize,
pub captures: &'a [QueryCapture<'a>],
@@ -74,6 +75,7 @@ struct SyntaxMapCapturesLayer<'a> {
}
struct SyntaxMapMatchesLayer<'a> {
language: Arc<Language>,
depth: usize,
next_pattern_index: usize,
next_captures: Vec<QueryCapture<'a>>,
@@ -1004,6 +1006,7 @@ impl<'a> SyntaxMapMatches<'a> {
result.grammars.len() - 1
});
let mut layer = SyntaxMapMatchesLayer {
language: layer.language.clone(),
depth: layer.depth,
grammar_index,
matches,
@@ -1036,10 +1039,13 @@ impl<'a> SyntaxMapMatches<'a> {
pub fn peek(&self) -> Option<SyntaxMapMatch> {
let layer = self.layers.first()?;
if !layer.has_next {
return None;
}
Some(SyntaxMapMatch {
language: layer.language.clone(),
depth: layer.depth,
grammar_index: layer.grammar_index,
pattern_index: layer.next_pattern_index,

View File

@@ -26,12 +26,9 @@ project.workspace = true
regex.workspace = true
rope.workspace = true
rust-embed = "8.2.0"
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
shellexpand.workspace = true
smol.workspace = true
task.workspace = true
toml.workspace = true
@@ -39,12 +36,10 @@ tree-sitter-bash.workspace = true
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-css.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-embedded-template.workspace = true
tree-sitter-go.workspace = true
tree-sitter-gomod.workspace = true
tree-sitter-gowork.workspace = true
tree-sitter-heex.workspace = true
tree-sitter-jsdoc.workspace = true
tree-sitter-json.workspace = true
tree-sitter-markdown.workspace = true

View File

@@ -1,7 +1,7 @@
name = "Shell Script"
code_fence_block_name = "bash"
grammar = "bash"
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env"]
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env", "PKGBUILD"]
line_comments = ["# "]
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
brackets = [

View File

@@ -1,607 +0,0 @@
use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task};
pub use language::*;
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
use project::project_settings::ProjectSettings;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
use settings::{Settings, SettingsSources};
use smol::fs::{self, File};
use std::{
any::Any,
env::consts,
ops::Deref,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
},
};
use task::{TaskTemplate, TaskTemplates, VariableName};
use util::{
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
maybe, ResultExt,
};
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub struct ElixirSettings {
pub lsp: ElixirLspSetting,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ElixirLspSetting {
ElixirLs,
NextLs,
Local {
path: String,
arguments: Vec<String>,
},
}
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
pub struct ElixirSettingsContent {
lsp: Option<ElixirLspSetting>,
}
impl Settings for ElixirSettings {
const KEY: Option<&'static str> = Some("elixir");
type FileContent = ElixirSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()
}
}
pub struct ElixirLspAdapter;
#[async_trait(?Send)]
impl LspAdapter for ElixirLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("elixir-ls".into())
}
fn will_start_server(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Result<()>>> {
static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
const NOTIFICATION_MESSAGE: &str = "Could not run the elixir language server, `elixir-ls`, because `elixir` was not found.";
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
let elixir_output = smol::process::Command::new("elixir")
.args(["--version"])
.output()
.await;
if elixir_output.is_err() {
if DID_SHOW_NOTIFICATION
.compare_exchange(false, true, SeqCst, SeqCst)
.is_ok()
{
cx.update(|cx| {
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
})?
}
return Err(anyhow!("cannot run elixir-ls"));
}
Ok(())
}))
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let http = delegate.http_client();
let release = latest_github_release("elixir-lsp/elixir-ls", true, false, http).await?;
let asset_name = format!("elixir-ls-{}.zip", &release.tag_name);
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.ok_or_else(|| anyhow!("no asset found matching {asset_name:?}"))?;
let version = GitHubLspBinaryVersion {
name: release.tag_name.clone(),
url: asset.browser_download_url.clone(),
};
Ok(Box::new(version) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
let folder_path = container_dir.join("elixir-ls");
let binary_path = folder_path.join("language_server.sh");
if fs::metadata(&binary_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.context("error downloading release")?;
let mut file = File::create(&zip_path)
.await
.with_context(|| format!("failed to create file {}", zip_path.display()))?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response.body_mut(), &mut file).await?;
fs::create_dir_all(&folder_path)
.await
.with_context(|| format!("failed to create directory {}", folder_path.display()))?;
let unzip_status = smol::process::Command::new("unzip")
.arg(&zip_path)
.arg("-d")
.arg(&folder_path)
.output()
.await?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip elixir-ls archive"))?;
}
remove_matching(&container_dir, |entry| entry != folder_path).await;
}
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec![],
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_elixir_ls(container_dir).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_elixir_ls(container_dir).await
}
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
match completion.kind.zip(completion.detail.as_ref()) {
Some((_, detail)) if detail.starts_with("(function)") => {
let text = detail.strip_prefix("(function) ")?;
let filter_range = 0..text.find('(').unwrap_or(text.len());
let source = Rope::from(format!("def {text}").as_str());
let runs = language.highlight_text(&source, 4..4 + text.len());
return Some(CodeLabel {
text: text.to_string(),
runs,
filter_range,
});
}
Some((_, detail)) if detail.starts_with("(macro)") => {
let text = detail.strip_prefix("(macro) ")?;
let filter_range = 0..text.find('(').unwrap_or(text.len());
let source = Rope::from(format!("defmacro {text}").as_str());
let runs = language.highlight_text(&source, 9..9 + text.len());
return Some(CodeLabel {
text: text.to_string(),
runs,
filter_range,
});
}
Some((
CompletionItemKind::CLASS
| CompletionItemKind::MODULE
| CompletionItemKind::INTERFACE
| CompletionItemKind::STRUCT,
_,
)) => {
let filter_range = 0..completion
.label
.find(" (")
.unwrap_or(completion.label.len());
let text = &completion.label[filter_range.clone()];
let source = Rope::from(format!("defmodule {text}").as_str());
let runs = language.highlight_text(&source, 10..10 + text.len());
return Some(CodeLabel {
text: completion.label.clone(),
runs,
filter_range,
});
}
_ => {}
}
None
}
async fn label_for_symbol(
&self,
name: &str,
kind: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
SymbolKind::METHOD | SymbolKind::FUNCTION => {
let text = format!("def {}", name);
let filter_range = 4..4 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => {
let text = format!("defmodule {}", name);
let filter_range = 10..10 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
_ => return None,
};
Some(CodeLabel {
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
text: text[display_range].to_string(),
filter_range,
})
}
async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Result<Value> {
let settings = cx.update(|cx| {
ProjectSettings::get_global(cx)
.lsp
.get("elixir-ls")
.and_then(|s| s.settings.clone())
.unwrap_or_default()
})?;
Ok(serde_json::json!({
"elixirLS": settings
}))
}
}
async fn get_cached_server_binary_elixir_ls(
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
let server_path = container_dir.join("elixir-ls/language_server.sh");
if server_path.exists() {
Some(LanguageServerBinary {
path: server_path,
env: None,
arguments: vec![],
})
} else {
log::error!("missing executable in directory {:?}", server_path);
None
}
}
pub struct NextLspAdapter;
#[async_trait(?Send)]
impl LspAdapter for NextLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("next-ls".into())
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let platform = match consts::ARCH {
"x86_64" => "darwin_amd64",
"aarch64" => "darwin_arm64",
other => bail!("Running on unsupported platform: {other}"),
};
let release =
latest_github_release("elixir-tools/next-ls", true, false, delegate.http_client())
.await?;
let version = release.tag_name;
let asset_name = format!("next_ls_{platform}");
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.with_context(|| format!("no asset found matching {asset_name:?}"))?;
let version = GitHubLspBinaryVersion {
name: version,
url: asset.browser_download_url.clone(),
};
Ok(Box::new(version) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let binary_path = container_dir.join("next-ls");
if fs::metadata(&binary_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
let mut file = smol::fs::File::create(&binary_path).await?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response.body_mut(), &mut file).await?;
// todo("windows")
#[cfg(not(windows))]
{
fs::set_permissions(
&binary_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
}
}
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: vec!["--stdio".into()],
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_next(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--stdio".into()];
binary
})
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_next(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--help".into()];
binary
})
}
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_completion_elixir(completion, language)
}
async fn label_for_symbol(
&self,
name: &str,
symbol_kind: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_elixir(name, symbol_kind, language)
}
}
async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<LanguageServerBinary> {
maybe!(async {
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_file()
&& entry
.file_name()
.to_str()
.map_or(false, |name| name == "next-ls")
{
last_binary_path = Some(entry.path());
}
}
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
env: None,
arguments: Vec::new(),
})
} else {
Err(anyhow!("no cached binary"))
}
})
.await
.log_err()
}
pub struct LocalLspAdapter {
pub path: String,
pub arguments: Vec<String>,
}
#[async_trait(?Send)]
impl LspAdapter for LocalLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("local-ls".into())
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(()) as Box<_>)
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let path = shellexpand::full(&self.path)?;
Ok(LanguageServerBinary {
path: PathBuf::from(path.deref()),
env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
env: None,
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn label_for_completion(
&self,
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_completion_elixir(completion, language)
}
async fn label_for_symbol(
&self,
name: &str,
symbol: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_elixir(name, symbol, language)
}
}
fn label_for_completion_elixir(
completion: &lsp::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
return Some(CodeLabel {
runs: language.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
text: completion.label.clone(),
filter_range: 0..completion.label.len(),
});
}
fn label_for_symbol_elixir(
name: &str,
_: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
Some(CodeLabel {
runs: language.highlight_text(&name.into(), 0..name.len()),
text: name.to_string(),
filter_range: 0..name.len(),
})
}
pub(super) fn elixir_task_context() -> ContextProviderWithTasks {
// Taken from https://gist.github.com/josevalim/2e4f60a14ccd52728e3256571259d493#gistcomment-4995881
ContextProviderWithTasks::new(TaskTemplates(vec![
TaskTemplate {
label: "mix test".to_owned(),
command: "mix".to_owned(),
args: vec!["test".to_owned()],
..TaskTemplate::default()
},
TaskTemplate {
label: "mix test --failed".to_owned(),
command: "mix".to_owned(),
args: vec!["test".to_owned(), "--failed".to_owned()],
..TaskTemplate::default()
},
TaskTemplate {
label: format!("mix test {}", VariableName::Symbol.template_value()),
command: "mix".to_owned(),
args: vec!["test".to_owned(), VariableName::Symbol.template_value()],
..TaskTemplate::default()
},
TaskTemplate {
label: format!(
"mix test {}:{}",
VariableName::File.template_value(),
VariableName::Row.template_value()
),
command: "mix".to_owned(),
args: vec![
"test".to_owned(),
format!(
"{}:{}",
VariableName::File.template_value(),
VariableName::Row.template_value()
),
],
..TaskTemplate::default()
},
TaskTemplate {
label: "Elixir: break line".to_owned(),
command: "iex".to_owned(),
args: vec![
"-S".to_owned(),
"mix".to_owned(),
"test".to_owned(),
"-b".to_owned(),
format!(
"{}:{}",
VariableName::File.template_value(),
VariableName::Row.template_value()
),
],
..TaskTemplate::default()
},
]))
}

View File

@@ -3,22 +3,16 @@ use gpui::{AppContext, BorrowAppContext};
pub use language::*;
use node_runtime::NodeRuntime;
use rust_embed::RustEmbed;
use settings::{Settings, SettingsStore};
use settings::SettingsStore;
use smol::stream::StreamExt;
use std::{str, sync::Arc};
use util::{asset_str, ResultExt};
use crate::{
bash::bash_task_context, elixir::elixir_task_context, python::python_task_context,
rust::RustContextProvider,
};
use self::elixir::ElixirSettings;
use crate::{bash::bash_task_context, python::python_task_context, rust::RustContextProvider};
mod bash;
mod c;
mod css;
mod elixir;
mod go;
mod json;
mod python;
@@ -28,15 +22,6 @@ mod tailwind;
mod typescript;
mod yaml;
// 1. Add tree-sitter-{language} parser to zed crate
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
// 3. Add config.toml to the newly created language directory using existing languages as a template
// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
// Note: github highlights take the last match while zed takes the first
// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
// and autoclosing brackets respectively
// 6. If the language has injections add an injections.scm query file
#[derive(RustEmbed)]
#[folder = "src/"]
#[exclude = "*.rs"]
@@ -47,14 +32,11 @@ pub fn init(
node_runtime: Arc<dyn NodeRuntime>,
cx: &mut AppContext,
) {
ElixirSettings::register(cx);
languages.register_native_grammars([
("bash", tree_sitter_bash::language()),
("c", tree_sitter_c::language()),
("cpp", tree_sitter_cpp::language()),
("css", tree_sitter_css::language()),
("elixir", tree_sitter_elixir::language()),
(
"embedded_template",
tree_sitter_embedded_template::language(),
@@ -62,7 +44,6 @@ pub fn init(
("go", tree_sitter_go::language()),
("gomod", tree_sitter_gomod::language()),
("gowork", tree_sitter_gowork::language()),
("heex", tree_sitter_heex::language()),
("jsdoc", tree_sitter_jsdoc::language()),
("json", tree_sitter_json::language()),
("markdown", tree_sitter_markdown::language()),
@@ -126,51 +107,11 @@ pub fn init(
language!("cpp", vec![Arc::new(c::CLspAdapter)]);
language!(
"css",
vec![
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),]
);
match &ElixirSettings::get(None, cx).lsp {
elixir::ElixirLspSetting::ElixirLs => {
language!(
"elixir",
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
elixir_task_context()
);
}
elixir::ElixirLspSetting::NextLs => {
language!(
"elixir",
vec![Arc::new(elixir::NextLspAdapter)],
elixir_task_context()
);
}
elixir::ElixirLspSetting::Local { path, arguments } => {
language!(
"elixir",
vec![Arc::new(elixir::LocalLspAdapter {
path: path.clone(),
arguments: arguments.clone(),
})],
elixir_task_context()
);
}
}
language!("go", vec![Arc::new(go::GoLspAdapter)]);
language!("gomod");
language!("gowork");
language!(
"heex",
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
);
language!(
"json",
vec![Arc::new(json::JsonLspAdapter::new(
@@ -193,24 +134,21 @@ pub fn init(
);
language!(
"tsx",
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
]
vec![Arc::new(typescript::TypeScriptLspAdapter::new(
node_runtime.clone()
))]
);
language!(
"typescript",
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
]
vec![Arc::new(typescript::TypeScriptLspAdapter::new(
node_runtime.clone()
))]
);
language!(
"javascript",
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
]
vec![Arc::new(typescript::TypeScriptLspAdapter::new(
node_runtime.clone()
))]
);
language!(
"jsdoc",
@@ -219,13 +157,7 @@ pub fn init(
))]
);
language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
language!(
"erb",
vec![
Arc::new(ruby::RubyLanguageServer),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
]
);
language!("erb", vec![Arc::new(ruby::RubyLanguageServer),]);
language!("regex");
language!(
"yaml",
@@ -233,13 +165,42 @@ pub fn init(
);
language!("proto");
// Register Tailwind globally as an available language server.
//
// This will allow users to add Tailwind support for a given language via
// the `language_servers` setting:
//
// ```json
// {
// "languages": {
// "My Language": {
// "language_servers": ["tailwindcss-language-server", "..."]
// }
// }
// }
// ```
languages.register_available_lsp_adapter(
LanguageServerName("tailwindcss-language-server".into()),
{
let node_runtime = node_runtime.clone();
move || Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone()))
},
);
// Register Tailwind for the existing languages that should have it by default.
//
// This can be driven by the `language_servers` setting once we have a way for
// extensions to provide their own default value for that setting.
let tailwind_languages = [
"Astro",
"CSS",
"ERB",
"HEEX",
"HTML",
"JavaScript",
"PHP",
"Svelte",
"TSX",
"JavaScript",
"Vue.js",
];
@@ -250,6 +211,14 @@ pub fn init(
);
}
let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js"];
for language in eslint_languages {
languages.register_secondary_lsp_adapter(
language.into(),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
);
}
let mut subscription = languages.subscribe();
let mut prev_language_settings = languages.language_settings();

View File

@@ -389,6 +389,7 @@ impl ContextProvider for RustContextProvider {
"--".into(),
"--nocapture".into(),
],
tags: vec!["rust-test".to_owned()],
..TaskTemplate::default()
},
TaskTemplate {

View File

@@ -64,6 +64,16 @@
"<" @punctuation.bracket
">" @punctuation.bracket)
[
";"
","
"::"
] @punctuation.delimiter
[
"#"
] @punctuation.special
[
"as"
"async"
@@ -122,3 +132,50 @@
(line_comment)
(block_comment)
] @comment
[
"!"
"!="
"%"
"%="
"&"
"&="
"&&"
"*"
"*="
"*"
"+"
"+="
","
"-"
"-="
"->"
"."
".."
"..="
"..."
"/"
"/="
":"
";"
"<<"
"<<="
"<"
"<="
"="
"=="
"=>"
">"
">="
">>"
">>="
"@"
"^"
"^="
"|"
"|="
"||"
"?"
] @operator
(lifetime) @lifetime

View File

@@ -0,0 +1,7 @@
(
(attribute_item (attribute) @_attribute
(#match? @_attribute ".*test.*"))
.
(function_item
name: (_) @run)
) @rust-test

View File

@@ -13,7 +13,7 @@ use language::{
language_settings::{language_settings, LanguageSettings},
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape,
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
Outline, OutlineItem, Point, PointUtf16, Runnable, Selection, TextDimension, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
};
use smallvec::SmallVec;
@@ -3132,6 +3132,31 @@ impl MultiBufferSnapshot {
.flatten()
}
pub fn runnable_ranges(
&self,
range: Range<Anchor>,
) -> impl Iterator<Item = (Range<usize>, Runnable)> + '_ {
let range = range.start.to_offset(self)..range.end.to_offset(self);
self.excerpts_for_range(range.clone())
.flat_map(move |(excerpt, excerpt_offset)| {
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
excerpt
.buffer
.runnable_ranges(excerpt.range.context.clone())
.map(move |(mut match_range, runnable)| {
// Re-base onto the excerpts coordinates in the multibuffer
match_range.start =
excerpt_offset + (match_range.start - excerpt_buffer_start);
match_range.end = excerpt_offset + (match_range.end - excerpt_buffer_start);
(match_range, runnable)
})
.skip_while(move |(match_range, _)| match_range.end < range.start)
.take_while(move |(match_range, _)| match_range.start < range.end)
})
}
pub fn diagnostics_update_count(&self) -> usize {
self.diagnostics_update_count
}

View File

@@ -306,7 +306,7 @@ impl PickerDelegate for OutlineViewDelegate {
.selected(selected)
.child(
div()
.text_ui()
.text_ui(cx)
.pl(rems(outline_item.depth as f32))
.child(styled_text),
),

View File

@@ -3066,12 +3066,34 @@ impl Project {
.map(|lsp_adapter| lsp_adapter.name.clone())
.collect::<Vec<_>>();
let enabled_language_servers =
let desired_language_servers =
settings.customized_language_servers(&available_language_servers);
let enabled_lsp_adapters = available_lsp_adapters
.into_iter()
.filter(|adapter| enabled_language_servers.contains(&adapter.name))
.collect::<Vec<_>>();
let mut enabled_lsp_adapters: Vec<Arc<CachedLspAdapter>> = Vec::new();
for desired_language_server in desired_language_servers {
if let Some(adapter) = available_lsp_adapters
.iter()
.find(|adapter| adapter.name == desired_language_server)
{
enabled_lsp_adapters.push(adapter.clone());
continue;
}
if let Some(adapter) = self
.languages
.load_available_lsp_adapter(&desired_language_server)
{
self.languages()
.register_lsp_adapter(language.name(), adapter.adapter.clone());
enabled_lsp_adapters.push(adapter);
continue;
}
log::warn!(
"no language server found matching '{}'",
desired_language_server.0
);
}
log::info!(
"starting language servers for {language}: {adapters}",
@@ -3083,7 +3105,7 @@ impl Project {
);
for adapter in enabled_lsp_adapters {
self.start_language_server(worktree, adapter.clone(), language.clone(), cx);
self.start_language_server(worktree, adapter, language.clone(), cx);
}
}
@@ -7574,17 +7596,15 @@ impl Project {
} else {
let fs = self.fs.clone();
let task_abs_path = abs_path.clone();
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
task_inventory.add_source(
TaskSourceKind::Worktree {
id: remote_worktree_id,
abs_path,
id_base: "local_tasks_for_worktree",
},
|cx| {
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
StaticSource::new(TrackedFile::new(tasks_file_rx, cx), cx)
},
StaticSource::new(TrackedFile::new(tasks_file_rx, cx)),
cx,
);
}
@@ -7596,23 +7616,20 @@ impl Project {
} else {
let fs = self.fs.clone();
let task_abs_path = abs_path.clone();
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
task_inventory.add_source(
TaskSourceKind::Worktree {
id: remote_worktree_id,
abs_path,
id_base: "local_vscode_tasks_for_worktree",
},
|cx| {
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
StaticSource::new(
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
tasks_file_rx,
cx,
),
StaticSource::new(
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
tasks_file_rx,
cx,
)
},
),
),
cx,
);
}

View File

@@ -14,7 +14,7 @@ use serde_json::json;
#[cfg(not(windows))]
use std::os;
use std::task::Poll;
use task::{TaskContext, TaskSource, TaskTemplate, TaskTemplates};
use task::{TaskContext, TaskTemplate, TaskTemplates};
use unindent::Unindent as _;
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
use worktree::WorktreeModelHandle as _;
@@ -168,12 +168,11 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
let all_tasks = project
.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
project.task_inventory().update(cx, |inventory, _| {
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
old.extend(new);
old
@@ -215,13 +214,9 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
project.update(cx, |project, cx| {
let inventory = project.task_inventory();
inventory.update(cx, |inventory, cx| {
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
inventory.update(cx, |inventory, _| {
let (mut old, new) =
inventory.used_and_current_resolved_tasks(None, Some(workree_id), &task_context);
old.extend(new);
let (_, resolved_task) = old
.into_iter()
@@ -231,41 +226,39 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
})
});
let tasks = serde_json::to_string(&TaskTemplates(vec![TaskTemplate {
label: "cargo check".to_string(),
command: "cargo".to_string(),
args: vec![
"check".to_string(),
"--all".to_string(),
"--all-targets".to_string(),
],
env: HashMap::from_iter(Some((
"RUSTFLAGS".to_string(),
"-Zunstable-options".to_string(),
))),
..TaskTemplate::default()
}]))
.unwrap();
let (tx, rx) = futures::channel::mpsc::unbounded();
let templates = cx.update(|cx| TrackedFile::new(rx, cx));
tx.unbounded_send(tasks).unwrap();
let source = StaticSource::new(templates);
cx.run_until_parked();
cx.update(|cx| {
let all_tasks = project
.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
inventory.add_source(
global_task_source_kind.clone(),
|cx| {
cx.new_model(|_| {
let source = TestTaskSource {
tasks: TaskTemplates(vec![TaskTemplate {
label: "cargo check".to_string(),
command: "cargo".to_string(),
args: vec![
"check".to_string(),
"--all".to_string(),
"--all-targets".to_string(),
],
env: HashMap::from_iter(Some((
"RUSTFLAGS".to_string(),
"-Zunstable-options".to_string(),
))),
..TaskTemplate::default()
}]),
};
Box::new(source) as Box<_>
})
},
cx,
);
inventory.add_source(global_task_source_kind.clone(), source, cx);
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
old.extend(new);
old
@@ -317,20 +310,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
});
}
struct TestTaskSource {
tasks: TaskTemplates,
}
impl TaskSource for TestTaskSource {
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
self.tasks.clone()
}
}
#[gpui::test]
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
init_test(cx);

View File

@@ -1,17 +1,18 @@
//! Project-wide storage of the tasks available, capable of updating itself from the sources set.
use std::{
any::TypeId,
cmp::{self, Reverse},
path::{Path, PathBuf},
sync::Arc,
};
use collections::{hash_map, HashMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use gpui::{AppContext, Context, Model, ModelContext};
use itertools::{Either, Itertools};
use language::Language;
use task::{ResolvedTask, TaskContext, TaskId, TaskSource, TaskTemplate, VariableName};
use task::{
static_source::StaticSource, ResolvedTask, TaskContext, TaskId, TaskTemplate, VariableName,
};
use util::{post_inc, NumericPrefixWithSuffix};
use worktree::WorktreeId;
@@ -22,14 +23,12 @@ pub struct Inventory {
}
struct SourceInInventory {
source: Model<Box<dyn TaskSource>>,
_subscription: Subscription,
type_id: TypeId,
source: StaticSource,
kind: TaskSourceKind,
}
/// Kind of a source the tasks are fetched from, used to display more source information in the UI.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TaskSourceKind {
/// bash-like commands spawned by users, not associated with any path
UserInput,
@@ -95,7 +94,7 @@ impl Inventory {
pub fn add_source(
&mut self,
kind: TaskSourceKind,
create_source: impl FnOnce(&mut ModelContext<Self>) -> Model<Box<dyn TaskSource>>,
source: StaticSource,
cx: &mut ModelContext<Self>,
) {
let abs_path = kind.abs_path();
@@ -106,16 +105,7 @@ impl Inventory {
}
}
let source = create_source(cx);
let type_id = source.read(cx).type_id();
let source = SourceInInventory {
_subscription: cx.observe(&source, |_, _, cx| {
cx.notify();
}),
source,
type_id,
kind,
};
let source = SourceInInventory { source, kind };
self.sources.push(source);
cx.notify();
}
@@ -136,31 +126,12 @@ impl Inventory {
self.sources.retain(|s| s.kind.worktree() != Some(worktree));
}
pub fn source<T: TaskSource>(&self) -> Option<(Model<Box<dyn TaskSource>>, TaskSourceKind)> {
let target_type_id = std::any::TypeId::of::<T>();
self.sources.iter().find_map(
|SourceInInventory {
type_id,
source,
kind,
..
}| {
if &target_type_id == type_id {
Some((source.clone(), kind.clone()))
} else {
None
}
},
)
}
/// Pulls its task sources relevant to the worktree and the language given,
/// returns all task templates with their source kinds, in no specific order.
pub fn list_tasks(
&self,
language: Option<Arc<Language>>,
worktree: Option<WorktreeId>,
cx: &mut AppContext,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name(),
@@ -180,7 +151,7 @@ impl Inventory {
.flat_map(|source| {
source
.source
.update(cx, |source, cx| source.tasks_to_schedule(cx))
.tasks_to_schedule()
.0
.into_iter()
.map(|task| (&source.kind, task))
@@ -199,7 +170,6 @@ impl Inventory {
language: Option<Arc<Language>>,
worktree: Option<WorktreeId>,
task_context: &TaskContext,
cx: &mut AppContext,
) -> (
Vec<(TaskSourceKind, ResolvedTask)>,
Vec<(TaskSourceKind, ResolvedTask)>,
@@ -246,7 +216,7 @@ impl Inventory {
.flat_map(|source| {
source
.source
.update(cx, |source, cx| source.tasks_to_schedule(cx))
.tasks_to_schedule()
.0
.into_iter()
.map(|task| (&source.kind, task))
@@ -387,9 +357,12 @@ fn task_variables_preference(task: &ResolvedTask) -> Reverse<usize> {
#[cfg(test)]
mod test_inventory {
use gpui::{AppContext, Context as _, Model, ModelContext, TestAppContext};
use gpui::{AppContext, Model, TestAppContext};
use itertools::Itertools;
use task::{TaskContext, TaskId, TaskSource, TaskTemplate, TaskTemplates};
use task::{
static_source::{StaticSource, TrackedFile},
TaskContext, TaskTemplate, TaskTemplates,
};
use worktree::WorktreeId;
use crate::Inventory;
@@ -398,55 +371,28 @@ mod test_inventory {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestTask {
id: task::TaskId,
name: String,
}
pub struct StaticTestSource {
pub tasks: Vec<TestTask>,
}
impl StaticTestSource {
pub(super) fn new(
task_names: impl IntoIterator<Item = String>,
cx: &mut AppContext,
) -> Model<Box<dyn TaskSource>> {
cx.new_model(|_| {
Box::new(Self {
tasks: task_names
.into_iter()
.enumerate()
.map(|(i, name)| TestTask {
id: TaskId(format!("task_{i}_{name}")),
name,
})
.collect(),
}) as Box<dyn TaskSource>
})
}
}
impl TaskSource for StaticTestSource {
fn tasks_to_schedule(
&mut self,
_cx: &mut ModelContext<Box<dyn TaskSource>>,
) -> TaskTemplates {
TaskTemplates(
self.tasks
.clone()
.into_iter()
.map(|task| TaskTemplate {
label: task.name,
command: "test command".to_string(),
..TaskTemplate::default()
})
.collect(),
)
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
}
pub(super) fn static_test_source(
task_names: impl IntoIterator<Item = String>,
cx: &mut AppContext,
) -> StaticSource {
let tasks = TaskTemplates(
task_names
.into_iter()
.map(|name| TaskTemplate {
label: name,
command: "test command".to_owned(),
..TaskTemplate::default()
})
.collect(),
);
let (tx, rx) = futures::channel::mpsc::unbounded();
let file = TrackedFile::new(rx, cx);
tx.unbounded_send(serde_json::to_string(&tasks).unwrap())
.unwrap();
StaticSource::new(file)
}
pub(super) fn task_template_names(
@@ -454,9 +400,9 @@ mod test_inventory {
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
inventory.update(cx, |inventory, _| {
inventory
.list_tasks(None, worktree, cx)
.list_tasks(None, worktree)
.into_iter()
.map(|(_, task)| task.label)
.sorted()
@@ -469,13 +415,9 @@ mod test_inventory {
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
let (used, current) = inventory.used_and_current_resolved_tasks(
None,
worktree,
&TaskContext::default(),
cx,
);
inventory.update(cx, |inventory, _| {
let (used, current) =
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
used.into_iter()
.chain(current)
.map(|(_, task)| task.original_task().label.clone())
@@ -488,9 +430,9 @@ mod test_inventory {
task_name: &str,
cx: &mut TestAppContext,
) {
inventory.update(cx, |inventory, cx| {
inventory.update(cx, |inventory, _| {
let (task_source_kind, task) = inventory
.list_tasks(None, None, cx)
.list_tasks(None, None)
.into_iter()
.find(|(_, task)| task.label == task_name)
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
@@ -508,13 +450,9 @@ mod test_inventory {
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<(TaskSourceKind, String)> {
inventory.update(cx, |inventory, cx| {
let (used, current) = inventory.used_and_current_resolved_tasks(
None,
worktree,
&TaskContext::default(),
cx,
);
inventory.update(cx, |inventory, _| {
let (used, current) =
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
let mut all = used;
all.extend(current);
all.into_iter()
@@ -549,27 +487,25 @@ mod tests {
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| StaticTestSource::new(vec!["3_task".to_string()], cx),
static_test_source(vec!["3_task".to_string()], cx),
cx,
);
});
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| {
StaticTestSource::new(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
cx,
)
},
static_test_source(
vec![
"1_task".to_string(),
"2_task".to_string(),
"1_a_task".to_string(),
],
cx,
),
cx,
);
});
cx.run_until_parked();
let expected_initial_state = [
"1_a_task".to_string(),
"1_task".to_string(),
@@ -622,12 +558,11 @@ mod tests {
inventory.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| {
StaticTestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx)
},
static_test_source(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
cx,
);
});
cx.run_until_parked();
let expected_updated_state = [
"10_hello".to_string(),
"11_hello".to_string(),
@@ -680,15 +615,11 @@ mod tests {
let worktree_path_1 = Path::new("worktree_path_1");
let worktree_2 = WorktreeId::from_usize(2);
let worktree_path_2 = Path::new("worktree_path_2");
inventory_with_statics.update(cx, |inventory, cx| {
inventory.add_source(
TaskSourceKind::UserInput,
|cx| {
StaticTestSource::new(
vec!["user_input".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(vec!["user_input".to_string(), common_name.to_string()], cx),
cx,
);
inventory.add_source(
@@ -696,12 +627,10 @@ mod tests {
id_base: "test source",
abs_path: path_1.to_path_buf(),
},
|cx| {
StaticTestSource::new(
vec!["static_source_1".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(
vec!["static_source_1".to_string(), common_name.to_string()],
cx,
),
cx,
);
inventory.add_source(
@@ -709,12 +638,10 @@ mod tests {
id_base: "test source",
abs_path: path_2.to_path_buf(),
},
|cx| {
StaticTestSource::new(
vec!["static_source_2".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(
vec!["static_source_2".to_string(), common_name.to_string()],
cx,
),
cx,
);
inventory.add_source(
@@ -723,12 +650,7 @@ mod tests {
abs_path: worktree_path_1.to_path_buf(),
id_base: "test_source",
},
|cx| {
StaticTestSource::new(
vec!["worktree_1".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(vec!["worktree_1".to_string(), common_name.to_string()], cx),
cx,
);
inventory.add_source(
@@ -737,16 +659,11 @@ mod tests {
abs_path: worktree_path_2.to_path_buf(),
id_base: "test_source",
},
|cx| {
StaticTestSource::new(
vec!["worktree_2".to_string(), common_name.to_string()],
cx,
)
},
static_test_source(vec!["worktree_2".to_string(), common_name.to_string()], cx),
cx,
);
});
cx.run_until_parked();
let worktree_independent_tasks = vec![
(
TaskSourceKind::AbsPath {

View File

@@ -36,7 +36,7 @@ theme.workspace = true
ui = { workspace = true, features = ["stories"] }
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-manifest = "1.4.0"
winresource = "0.1"
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }

View File

@@ -12,7 +12,13 @@ fn main() {
let manifest = std::path::Path::new("../zed/resources/windows/manifest.xml");
println!("cargo:rerun-if-changed={}", manifest.display());
embed_manifest::embed_manifest(embed_manifest::new_manifest(manifest.to_str().unwrap()))
.unwrap();
let mut res = winresource::WindowsResource::new();
res.set_manifest_file(manifest.to_str().unwrap());
if let Err(e) = res.compile() {
eprintln!("{}", e);
std::process::exit(1);
}
}
}

View File

@@ -14,6 +14,7 @@ collections.workspace = true
futures.workspace = true
gpui.workspace = true
hex.workspace = true
parking_lot.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json_lenient.workspace = true

View File

@@ -6,9 +6,8 @@ mod task_template;
mod vscode_format;
use collections::{HashMap, HashSet};
use gpui::ModelContext;
use gpui::SharedString;
use serde::Serialize;
use std::any::Any;
use std::borrow::Cow;
use std::path::PathBuf;
@@ -103,6 +102,8 @@ pub enum VariableName {
Column,
/// Text from the latest selection.
SelectedText,
/// The symbol selected by the symbol tagging system, specifically the @run capture in a runnables.scm
RunnableSymbol,
/// Custom variable, provided by the plugin or other external source.
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
Custom(Cow<'static, str>),
@@ -132,6 +133,7 @@ impl std::fmt::Display for VariableName {
Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
Self::Custom(s) => write!(f, "{ZED_VARIABLE_NAME_PREFIX}CUSTOM_{s}"),
}
}
@@ -169,13 +171,6 @@ pub struct TaskContext {
pub task_variables: TaskVariables,
}
/// [`Source`] produces tasks that can be scheduled.
///
/// Implementations of this trait could be e.g. [`StaticSource`] that parses tasks from a .json files and provides process templates to be spawned;
/// another one could be a language server providing lenses with tests or build server listing all targets for a given project.
pub trait TaskSource: Any {
/// A way to erase the type of the source, processing and storing them generically.
fn as_any(&mut self) -> &mut dyn Any;
/// Collects all tasks available for scheduling.
fn tasks_to_schedule(&mut self, cx: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates;
}
/// This is a new type representing a 'tag' on a 'runnable symbol', typically a test of main() function, found via treesitter.
#[derive(Clone, Debug)]
pub struct RunnableTag(pub SharedString);

View File

@@ -1,134 +1,110 @@
//! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file.
use std::sync::Arc;
use futures::StreamExt;
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use gpui::AppContext;
use parking_lot::RwLock;
use serde::Deserialize;
use util::ResultExt;
use crate::{TaskSource, TaskTemplates};
use crate::TaskTemplates;
use futures::channel::mpsc::UnboundedReceiver;
/// The source of tasks defined in a tasks config file.
pub struct StaticSource {
tasks: TaskTemplates,
_templates: Model<TrackedFile<TaskTemplates>>,
_subscription: Subscription,
tasks: TrackedFile<TaskTemplates>,
}
/// A Wrapper around deserializable T that keeps track of its contents
/// via a provided channel. Once T value changes, the observers of [`TrackedFile`] are
/// notified.
pub struct TrackedFile<T> {
parsed_contents: T,
parsed_contents: Arc<RwLock<T>>,
}
impl<T: PartialEq + 'static> TrackedFile<T> {
impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
/// Initializes new [`TrackedFile`] with a type that's deserializable.
pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Model<Self>
pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Self
where
T: for<'a> Deserialize<'a> + Default,
T: for<'a> Deserialize<'a> + Default + Send,
{
cx.new_model(move |cx| {
cx.spawn(|tracked_file, mut cx| async move {
while let Some(new_contents) = tracker.next().await {
if !new_contents.trim().is_empty() {
// String -> T (ZedTaskFormat)
// String -> U (VsCodeFormat) -> Into::into T
let Some(new_contents) =
serde_json_lenient::from_str(&new_contents).log_err()
else {
continue;
};
tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {
if tracked_file.parsed_contents != new_contents {
tracked_file.parsed_contents = new_contents;
cx.notify();
let parsed_contents: Arc<RwLock<T>> = Arc::default();
cx.background_executor()
.spawn({
let parsed_contents = parsed_contents.clone();
async move {
while let Some(new_contents) = tracker.next().await {
if Arc::strong_count(&parsed_contents) == 1 {
// We're no longer being observed. Stop polling.
break;
}
if !new_contents.trim().is_empty() {
let Some(new_contents) =
serde_json_lenient::from_str::<T>(&new_contents).log_err()
else {
continue;
};
})?;
let mut contents = parsed_contents.write();
*contents = new_contents;
}
}
anyhow::Ok(())
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
Self {
parsed_contents: Default::default(),
}
})
Self { parsed_contents }
}
/// Initializes new [`TrackedFile`] with a type that's convertible from another deserializable type.
pub fn new_convertible<U: for<'a> Deserialize<'a> + TryInto<T, Error = anyhow::Error>>(
mut tracker: UnboundedReceiver<String>,
cx: &mut AppContext,
) -> Model<Self>
) -> Self
where
T: Default,
T: Default + Send,
{
cx.new_model(move |cx| {
cx.spawn(|tracked_file, mut cx| async move {
while let Some(new_contents) = tracker.next().await {
if !new_contents.trim().is_empty() {
let Some(new_contents) =
serde_json_lenient::from_str::<U>(&new_contents).log_err()
else {
continue;
};
let Some(new_contents) = new_contents.try_into().log_err() else {
continue;
};
tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {
if tracked_file.parsed_contents != new_contents {
tracked_file.parsed_contents = new_contents;
cx.notify();
let parsed_contents: Arc<RwLock<T>> = Arc::default();
cx.background_executor()
.spawn({
let parsed_contents = parsed_contents.clone();
async move {
while let Some(new_contents) = tracker.next().await {
if Arc::strong_count(&parsed_contents) == 1 {
// We're no longer being observed. Stop polling.
break;
}
if !new_contents.trim().is_empty() {
let Some(new_contents) =
serde_json_lenient::from_str::<U>(&new_contents).log_err()
else {
continue;
};
})?;
let Some(new_contents) = new_contents.try_into().log_err() else {
continue;
};
let mut contents = parsed_contents.write();
*contents = new_contents;
}
}
anyhow::Ok(())
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
Self {
parsed_contents: Default::default(),
}
})
}
fn get(&self) -> &T {
&self.parsed_contents
Self {
parsed_contents: Default::default(),
}
}
}
impl StaticSource {
/// Initializes the static source, reacting on tasks config changes.
pub fn new(
templates: Model<TrackedFile<TaskTemplates>>,
cx: &mut AppContext,
) -> Model<Box<dyn TaskSource>> {
cx.new_model(|cx| {
let _subscription = cx.observe(
&templates,
move |source: &mut Box<(dyn TaskSource + 'static)>, new_templates, cx| {
if let Some(static_source) = source.as_any().downcast_mut::<Self>() {
static_source.tasks = new_templates.read(cx).get().clone();
cx.notify();
}
},
);
Box::new(Self {
tasks: TaskTemplates::default(),
_templates: templates,
_subscription,
})
})
}
}
impl TaskSource for StaticSource {
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
self.tasks.clone()
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
self
pub fn new(tasks: TrackedFile<TaskTemplates>) -> Self {
Self { tasks }
}
/// Returns current list of tasks
pub fn tasks_to_schedule(&self) -> TaskTemplates {
self.tasks.parsed_contents.read().clone()
}
}

View File

@@ -58,6 +58,10 @@ pub struct TaskTemplate {
/// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
#[serde(default)]
pub reveal: RevealStrategy,
/// Represents the tags which this template attaches to. Adding this removes this task from other UI.
#[serde(default)]
pub tags: Vec<String>,
}
/// What to do with the terminal pane and tab, after the command was started.

View File

@@ -9,7 +9,6 @@ license = "GPL-3.0-or-later"
workspace = true
[dependencies]
anyhow.workspace = true
editor.workspace = true
file_icons.workspace = true
fuzzy.workspace = true

View File

@@ -1,18 +1,13 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use std::sync::Arc;
use ::settings::Settings;
use anyhow::Context;
use editor::Editor;
use editor::{tasks::task_context, Editor};
use gpui::{AppContext, ViewContext, WindowContext};
use language::{BasicContextProvider, ContextProvider, Language};
use language::Language;
use modal::TasksModal;
use project::{Location, TaskSourceKind, WorktreeId};
use task::{ResolvedTask, TaskContext, TaskTemplate, TaskVariables};
use util::ResultExt;
use workspace::Workspace;
use project::WorktreeId;
use workspace::tasks::schedule_task;
use workspace::{tasks::schedule_resolved_task, Workspace};
mod modal;
mod settings;
@@ -93,9 +88,9 @@ fn spawn_task_with_name(name: String, cx: &mut ViewContext<Workspace>) {
.update(&mut cx, |workspace, cx| {
let (worktree, language) = active_item_selection_properties(workspace, cx);
let tasks = workspace.project().update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.list_tasks(language, worktree, cx)
})
project
.task_inventory()
.update(cx, |inventory, _| inventory.list_tasks(language, worktree))
});
let (task_source_kind, target_task) =
tasks.into_iter().find(|(_, task)| task.label == name)?;
@@ -148,168 +143,6 @@ fn active_item_selection_properties(
(worktree_id, language)
}
fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
fn task_context_impl(workspace: &Workspace, cx: &mut WindowContext<'_>) -> Option<TaskContext> {
let cwd = task_cwd(workspace, cx).log_err().flatten();
let editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))?;
let (selection, buffer, editor_snapshot) = editor.update(cx, |editor, cx| {
let selection = editor.selections.newest::<usize>(cx);
let (buffer, _, _) = editor
.buffer()
.read(cx)
.point_to_buffer_offset(selection.start, cx)?;
let snapshot = editor.snapshot(cx);
Some((selection, buffer, snapshot))
})?;
let language_context_provider = buffer
.read(cx)
.language()
.and_then(|language| language.context_provider())
.unwrap_or_else(|| Arc::new(BasicContextProvider));
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = editor_snapshot
.display_snapshot
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let worktree_abs_path = buffer
.read(cx)
.file()
.map(|file| WorktreeId::from_usize(file.worktree_id()))
.and_then(|worktree_id| {
workspace
.project()
.read(cx)
.worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
});
let location = Location {
buffer,
range: start..end,
};
let task_variables = combine_task_variables(
worktree_abs_path.as_deref(),
location,
language_context_provider.as_ref(),
cx,
)
.log_err()?;
Some(TaskContext {
cwd,
task_variables,
})
}
task_context_impl(workspace, cx).unwrap_or_default()
}
fn combine_task_variables(
worktree_abs_path: Option<&Path>,
location: Location,
context_provider: &dyn ContextProvider,
cx: &mut WindowContext<'_>,
) -> anyhow::Result<TaskVariables> {
if context_provider.is_basic() {
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building basic provider context")
} else {
let mut basic_context = BasicContextProvider
.build_context(worktree_abs_path, &location, cx)
.context("building basic default context")?;
basic_context.extend(
context_provider
.build_context(worktree_abs_path, &location, cx)
.context("building provider context ")?,
);
Ok(basic_context)
}
}
fn schedule_task(
workspace: &Workspace,
task_source_kind: TaskSourceKind,
task_to_resolve: &TaskTemplate,
task_cx: &TaskContext,
omit_history: bool,
cx: &mut ViewContext<'_, Workspace>,
) {
if let Some(spawn_in_terminal) =
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
{
schedule_resolved_task(
workspace,
task_source_kind,
spawn_in_terminal,
omit_history,
cx,
);
}
}
fn schedule_resolved_task(
workspace: &Workspace,
task_source_kind: TaskSourceKind,
mut resolved_task: ResolvedTask,
omit_history: bool,
cx: &mut ViewContext<'_, Workspace>,
) {
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
if !omit_history {
resolved_task.resolved = Some(spawn_in_terminal.clone());
workspace.project().update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, _| {
inventory.task_scheduled(task_source_kind, resolved_task);
})
});
}
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
}
}
fn task_cwd(workspace: &Workspace, cx: &mut WindowContext) -> anyhow::Result<Option<PathBuf>> {
let project = workspace.project().read(cx);
let available_worktrees = project
.worktrees()
.filter(|worktree| {
let worktree = worktree.read(cx);
worktree.is_visible()
&& worktree.is_local()
&& worktree.root_entry().map_or(false, |e| e.is_dir())
})
.collect::<Vec<_>>();
let cwd = match available_worktrees.len() {
0 => None,
1 => Some(available_worktrees[0].read(cx).abs_path()),
_ => {
let cwd_for_active_entry = project.active_entry().and_then(|entry_id| {
available_worktrees.into_iter().find_map(|worktree| {
let worktree = worktree.read(cx);
if worktree.contains_entry(entry_id) {
Some(worktree.abs_path())
} else {
None
}
})
});
anyhow::ensure!(
cwd_for_active_entry.is_some(),
"Cannot determine task cwd for multiple worktrees"
);
cwd_for_active_entry
}
};
Ok(cwd.map(|path| path.to_path_buf()))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::{active_item_selection_properties, schedule_resolved_task};
use crate::active_item_selection_properties;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, Global,
@@ -16,7 +16,7 @@ use ui::{
Tooltip, WindowContext,
};
use util::ResultExt;
use workspace::{ModalView, Workspace};
use workspace::{tasks::schedule_resolved_task, ModalView, Workspace};
use serde::Deserialize;
@@ -209,12 +209,11 @@ impl PickerDelegate for TasksModalDelegate {
return Vec::new();
};
let (used, current) =
picker.delegate.inventory.update(cx, |inventory, cx| {
picker.delegate.inventory.update(cx, |inventory, _| {
inventory.used_and_current_resolved_tasks(
language,
worktree,
&picker.delegate.task_context,
cx,
)
});
picker.delegate.last_used_candidate_index = if used.is_empty() {

View File

@@ -1,11 +1,11 @@
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};
use gpui::{
div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementContext,
FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler,
InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext,
ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine,
StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle,
WeakView, WhiteSpace, WindowContext, WindowTextSystem,
div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, FocusHandle, Font,
FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement,
Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton,
MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle,
Styled, TextRun, TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext,
WindowTextSystem,
};
use itertools::Itertools;
use language::CursorShape;
@@ -85,7 +85,7 @@ impl LayoutCell {
origin: Point<Pixels>,
layout: &LayoutState,
_visible_bounds: Bounds<Pixels>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let pos = {
let point = self.point;
@@ -124,7 +124,7 @@ impl LayoutRect {
}
}
fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
let position = {
let alac_point = self.point;
point(
@@ -418,7 +418,7 @@ impl TerminalElement {
origin: Point<Pixels>,
mode: TermMode,
hitbox: &Hitbox,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
let focus = self.focus.clone();
let terminal = self.terminal.clone();
@@ -544,7 +544,7 @@ impl Element for TerminalElement {
type RequestLayoutState = ();
type PrepaintState = LayoutState;
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
self.interactivity.occlude_mouse();
let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
style.size.width = relative(1.).into();
@@ -560,7 +560,7 @@ impl Element for TerminalElement {
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Self::PrepaintState {
self.interactivity
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, cx| {
@@ -777,7 +777,7 @@ impl Element for TerminalElement {
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
cx: &mut ElementContext<'_>,
cx: &mut WindowContext<'_>,
) {
cx.paint_quad(fill(bounds, layout.background_color));
let origin = bounds.origin + Point::new(layout.gutter, px(0.));

View File

@@ -160,7 +160,7 @@ impl RenderOnce for Key {
}
})
.h(rems_from_px(14.))
.text_ui()
.text_ui(cx)
.line_height(relative(1.))
.text_color(cx.theme().colors().text_muted)
.child(self.key.clone())

View File

@@ -108,10 +108,10 @@ impl RenderOnce for LabelLike {
)
})
.map(|this| match self.size {
LabelSize::Large => this.text_ui_lg(),
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
LabelSize::XSmall => this.text_ui_xs(),
LabelSize::Large => this.text_ui_lg(cx),
LabelSize::Default => this.text_ui(cx),
LabelSize::Small => this.text_ui_sm(cx),
LabelSize::XSmall => this.text_ui_xs(cx),
})
.when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
this.line_height(relative(1.))

View File

@@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
anchored, deferred, div, point, prelude::FluentBuilder, px, AnchorCorner, AnyElement, Bounds,
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, InteractiveElement,
IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
VisualContext, WindowContext,
DismissEvent, DispatchPhase, Element, ElementId, HitboxId, InteractiveElement, IntoElement,
LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
WindowContext,
};
use crate::prelude::*;
@@ -112,8 +112,8 @@ impl<M: ManagedView> PopoverMenu<M> {
fn with_element_state<R>(
&mut self,
cx: &mut ElementContext,
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut ElementContext) -> R,
cx: &mut WindowContext,
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut WindowContext) -> R,
) -> R {
cx.with_element_state::<PopoverMenuElementState<M>, _>(
Some(self.id.clone()),
@@ -173,7 +173,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
fn request_layout(
&mut self,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
self.with_element_state(cx, |this, element_state, cx| {
let mut menu_layout_id = None;
@@ -221,7 +221,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
&mut self,
_bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> Option<HitboxId> {
self.with_element_state(cx, |_this, element_state, cx| {
if let Some(child) = request_layout.child_element.as_mut() {
@@ -245,7 +245,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
_: Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
child_hitbox: &mut Option<HitboxId>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
self.with_element_state(cx, |_this, _element_state, cx| {
if let Some(mut child) = request_layout.child_element.take() {

View File

@@ -2,9 +2,8 @@ use std::{cell::RefCell, rc::Rc};
use gpui::{
anchored, deferred, div, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase,
Element, ElementContext, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId,
ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
WindowContext,
Element, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, ManagedView,
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
};
pub struct RightClickMenu<M: ManagedView> {
@@ -41,8 +40,8 @@ impl<M: ManagedView> RightClickMenu<M> {
fn with_element_state<R>(
&mut self,
cx: &mut ElementContext,
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut ElementContext) -> R,
cx: &mut WindowContext,
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut WindowContext) -> R,
) -> R {
cx.with_element_state::<MenuHandleElementState<M>, _>(
Some(self.id.clone()),
@@ -89,19 +88,24 @@ impl<M> Default for MenuHandleElementState<M> {
}
}
pub struct MenuHandleFrameState {
pub struct RequestLayoutState {
child_layout_id: Option<LayoutId>,
child_element: Option<AnyElement>,
menu_element: Option<AnyElement>,
}
pub struct PrepaintState {
hitbox: Hitbox,
child_bounds: Option<Bounds<Pixels>>,
}
impl<M: ManagedView> Element for RightClickMenu<M> {
type RequestLayoutState = MenuHandleFrameState;
type PrepaintState = Hitbox;
type RequestLayoutState = RequestLayoutState;
type PrepaintState = PrepaintState;
fn request_layout(
&mut self,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
self.with_element_state(cx, |this, element_state, cx| {
let mut menu_layout_id = None;
@@ -137,7 +141,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
(
layout_id,
MenuHandleFrameState {
RequestLayoutState {
child_element,
child_layout_id,
menu_element,
@@ -150,8 +154,8 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
&mut self,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
) -> Hitbox {
cx: &mut WindowContext,
) -> PrepaintState {
cx.with_element_id(Some(self.id.clone()), |cx| {
let hitbox = cx.insert_hitbox(bounds, false);
@@ -163,7 +167,12 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
menu.prepaint(cx);
}
hitbox
PrepaintState {
hitbox,
child_bounds: request_layout
.child_layout_id
.map(|layout_id| cx.layout_bounds(layout_id)),
}
})
}
@@ -171,8 +180,8 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
&mut self,
_bounds: Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
cx: &mut ElementContext,
prepaint_state: &mut Self::PrepaintState,
cx: &mut WindowContext,
) {
self.with_element_state(cx, |this, element_state, cx| {
if let Some(mut child) = request_layout.child_element.take() {
@@ -191,10 +200,9 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
let attach = this.attach;
let menu = element_state.menu.clone();
let position = element_state.position.clone();
let child_layout_id = request_layout.child_layout_id;
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
let child_bounds = prepaint_state.child_bounds;
let hitbox_id = hitbox.id;
let hitbox_id = prepaint_state.hitbox.id;
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& event.button == MouseButton::Right
@@ -219,7 +227,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
.detach();
cx.focus_view(&new_menu);
*menu.borrow_mut() = Some(new_menu);
*position.borrow_mut() = if child_layout_id.is_some() {
*position.borrow_mut() = if let Some(child_bounds) = child_bounds {
if let Some(attach) = attach {
attach.corner(child_bounds)
} else {

View File

@@ -96,7 +96,7 @@ pub fn tooltip_container<V>(
v_flex()
.elevation_2(cx)
.font_family(ui_font)
.text_ui()
.text_ui(cx)
.text_color(cx.theme().colors().text)
.py_1()
.px_2()

View File

@@ -2,16 +2,16 @@
pub use gpui::prelude::*;
pub use gpui::{
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementContext,
ElementId, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled,
ViewContext, WindowContext,
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext,
WindowContext,
};
pub use crate::clickable::*;
pub use crate::disableable::*;
pub use crate::fixed::*;
pub use crate::selectable::*;
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle};
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography};
pub use crate::visible_on_hover::*;
pub use crate::{h_flex, v_flex};
pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};

View File

@@ -1,9 +1,7 @@
use gpui::{hsla, px, Styled, WindowContext};
use settings::Settings;
use theme::ThemeSettings;
use crate::prelude::*;
use crate::{ElevationIndex, UiTextSize};
use crate::ElevationIndex;
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
this.bg(cx.theme().colors().elevated_surface_background)
@@ -29,66 +27,6 @@ pub trait StyledExt: Styled + Sized {
self.flex().flex_col()
}
/// Sets the text size using a [`UiTextSize`].
fn text_ui_size(self, size: UiTextSize) -> Self {
self.text_size(size.rems())
}
/// The large size for UI text.
///
/// `1rem` or `16px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_lg(self) -> Self {
self.text_size(UiTextSize::Large.rems())
}
/// The default size for UI text.
///
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui_sm` for smaller text.
fn text_ui(self) -> Self {
self.text_size(UiTextSize::default().rems())
}
/// The small size for UI text.
///
/// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_sm(self) -> Self {
self.text_size(UiTextSize::Small.rems())
}
/// The extra small size for UI text.
///
/// `0.625rem` or `10px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_xs(self) -> Self {
self.text_size(UiTextSize::XSmall.rems())
}
/// The font size for buffer text.
///
/// Retrieves the default font size, or the user's custom font size if set.
///
/// This should only be used for text that is displayed in a buffer,
/// or other places that text needs to match the user's buffer font size.
fn text_buffer(self, cx: &mut WindowContext) -> Self {
let settings = ThemeSettings::get_global(cx);
self.text_size(settings.buffer_font_size(cx))
}
/// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`

View File

@@ -6,8 +6,73 @@ use theme::{ActiveTheme, ThemeSettings};
use crate::rems_from_px;
/// Extends [`gpui::Styled`] with typography-related styling methods.
pub trait StyledTypography: Styled + Sized {
/// Sets the text size using a [`UiTextSize`].
fn text_ui_size(self, size: TextSize, cx: &WindowContext) -> Self {
self.text_size(size.rems(cx))
}
/// The large size for UI text.
///
/// `1rem` or `16px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_lg(self, cx: &WindowContext) -> Self {
self.text_size(TextSize::Large.rems(cx))
}
/// The default size for UI text.
///
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui_sm` for smaller text.
fn text_ui(self, cx: &WindowContext) -> Self {
self.text_size(TextSize::default().rems(cx))
}
/// The small size for UI text.
///
/// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_sm(self, cx: &WindowContext) -> Self {
self.text_size(TextSize::Small.rems(cx))
}
/// The extra small size for UI text.
///
/// `0.625rem` or `10px` at the default scale of `1rem` = `16px`.
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use `text_ui` for regular-sized text.
fn text_ui_xs(self, cx: &WindowContext) -> Self {
self.text_size(TextSize::XSmall.rems(cx))
}
/// The font size for buffer text.
///
/// Retrieves the default font size, or the user's custom font size if set.
///
/// This should only be used for text that is displayed in a buffer,
/// or other places that text needs to match the user's buffer font size.
fn text_buffer(self, cx: &mut WindowContext) -> Self {
let settings = ThemeSettings::get_global(cx);
self.text_size(settings.buffer_font_size(cx))
}
}
impl<E: Styled> StyledTypography for E {}
#[derive(Debug, Default, Clone)]
pub enum UiTextSize {
pub enum TextSize {
/// The default size for UI text.
///
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
@@ -35,15 +100,28 @@ pub enum UiTextSize {
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
XSmall,
/// The `ui_font_size` set by the user.
UI,
/// The `buffer_font_size` set by the user.
Editor,
// TODO: The terminal settings will need to be passed to
// ThemeSettings before we can enable this.
//// The `terminal.font_size` set by the user.
// Terminal,
}
impl UiTextSize {
pub fn rems(self) -> Rems {
impl TextSize {
pub fn rems(self, cx: &WindowContext) -> Rems {
let theme_settings = ThemeSettings::get_global(cx);
match self {
Self::Large => rems_from_px(16.),
Self::Default => rems_from_px(14.),
Self::Small => rems_from_px(12.),
Self::XSmall => rems_from_px(10.),
Self::UI => rems_from_px(theme_settings.ui_font_size.into()),
Self::Editor => rems_from_px(theme_settings.buffer_font_size.into()),
}
}
}

View File

@@ -23,6 +23,7 @@ collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = [

View File

@@ -42,6 +42,9 @@ fn focused(editor: View<Editor>, cx: &mut WindowContext) {
fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
Vim::update(cx, |vim, cx| {
if !vim.enabled {
return;
}
if let Some(previous_editor) = vim.active_editor.clone() {
vim.stop_recording_immediately(NormalBefore.boxed_clone());
if previous_editor
@@ -51,6 +54,9 @@ fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
vim.clear_operator(cx);
}
}
editor.update(cx, |editor, cx| {
editor.set_cursor_shape(language::CursorShape::Hollow, cx);
});
});
}

View File

@@ -55,6 +55,7 @@ impl Render for ModeIndicator {
Label::new(format!("{} -- {} --", self.operators, mode))
.size(LabelSize::Small)
.line_height_style(LineHeightStyle::UiLabel)
.into_any_element()
}
}

View File

@@ -9,6 +9,9 @@ use editor::{
movement::{self, FindRange},
Bias, DisplayPoint,
};
use itertools::Itertools;
use gpui::{actions, impl_actions, ViewContext, WindowContext};
use language::{char_kind, BufferSnapshot, CharKind, Point, Selection};
use serde::Deserialize;
@@ -801,15 +804,20 @@ fn surrounding_markers(
let mut matched_closes = 0;
let mut opening = None;
let mut before_ch = match movement::chars_before(map, point).next() {
Some((ch, _)) => ch,
_ => '\0',
};
if let Some((ch, range)) = movement::chars_after(map, point).next() {
if ch == open_marker {
if ch == open_marker && before_ch != '\\' {
if open_marker == close_marker {
let mut total = 0;
for (ch, _) in movement::chars_before(map, point) {
for ((ch, _), (before_ch, _)) in movement::chars_before(map, point).tuple_windows()
{
if ch == '\n' {
break;
}
if ch == open_marker {
if ch == open_marker && before_ch != '\\' {
total += 1;
}
}
@@ -823,11 +831,15 @@ fn surrounding_markers(
}
if opening.is_none() {
for (ch, range) in movement::chars_before(map, point) {
for ((ch, range), (before_ch, _)) in movement::chars_before(map, point).tuple_windows() {
if ch == '\n' && !search_across_lines {
break;
}
if before_ch == '\\' {
continue;
}
if ch == open_marker {
if matched_closes == 0 {
opening = Some(range);
@@ -839,15 +851,18 @@ fn surrounding_markers(
}
}
}
if opening.is_none() {
for (ch, range) in movement::chars_after(map, point) {
if ch == open_marker {
opening = Some(range);
break;
} else if ch == close_marker {
break;
if before_ch != '\\' {
if ch == open_marker {
opening = Some(range);
break;
} else if ch == close_marker {
break;
}
}
before_ch = ch;
}
}
@@ -857,21 +872,28 @@ fn surrounding_markers(
let mut matched_opens = 0;
let mut closing = None;
before_ch = match movement::chars_before(map, opening.end).next() {
Some((ch, _)) => ch,
_ => '\0',
};
for (ch, range) in movement::chars_after(map, opening.end) {
if ch == '\n' && !search_across_lines {
break;
}
if ch == close_marker {
if matched_opens == 0 {
closing = Some(range);
break;
if before_ch != '\\' {
if ch == close_marker {
if matched_opens == 0 {
closing = Some(range);
break;
}
matched_opens -= 1;
} else if ch == open_marker {
matched_opens += 1;
}
matched_opens -= 1;
} else if ch == open_marker {
matched_opens += 1;
}
before_ch = ch;
}
let Some(mut closing) = closing else {
@@ -1467,6 +1489,32 @@ mod test {
.await;
}
#[gpui::test]
async fn test_singleline_surrounding_character_objects_with_escape(
cx: &mut gpui::TestAppContext,
) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_shared_state(indoc! {
"h\"e\\\"lˇlo \\\"world\"!"
})
.await;
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
cx.assert_shared_state(indoc! {
"h\"«e\\\"llo \\\"worldˇ»\"!"
})
.await;
cx.set_shared_state(indoc! {
"hello \"teˇst \\\"inside\\\" world\""
})
.await;
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
cx.assert_shared_state(indoc! {
"hello \"«test \\\"inside\\\" worldˇ»\""
})
.await;
}
#[gpui::test]
async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;

View File

@@ -0,0 +1,10 @@
{"Put":{"state":"h\"e\\\"lˇlo \\\"world\"!"}}
{"Key":"v"}
{"Key":"i"}
{"Key":"\""}
{"Get":{"state":"h\"«e\\\"llo \\\"worldˇ»\"!","mode":"Visual"}}
{"Put":{"state":"hello \"teˇst \\\"inside\\\" world\""}}
{"Key":"v"}
{"Key":"i"}
{"Key":"\""}
{"Get":{"state":"hello \"«test \\\"inside\\\" worldˇ»\"","mode":"Visual"}}

View File

@@ -5,7 +5,7 @@ use crate::{
},
toolbar::Toolbar,
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
CloseWindow, NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible,
NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible,
SplitDirection, ToggleZoom, Workspace,
};
use anyhow::Result;
@@ -879,8 +879,6 @@ impl Pane {
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
if self.items.is_empty() {
// Close the window when there's no active items to close.
cx.dispatch_action(Box::new(CloseWindow));
return None;
}
let active_item_id = self.items[self.active_item_index].item_id();

View File

@@ -759,7 +759,7 @@ mod element {
fn layout_handle(
axis: Axis,
pane_bounds: Bounds<Pixels>,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> PaneAxisHandleLayout {
let handle_bounds = Bounds {
origin: pane_bounds.origin.apply_along(axis, |origin| {
@@ -797,7 +797,7 @@ mod element {
fn request_layout(
&mut self,
cx: &mut ui::prelude::ElementContext,
cx: &mut ui::prelude::WindowContext,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let mut style = Style::default();
style.flex_grow = 1.;
@@ -812,7 +812,7 @@ mod element {
&mut self,
bounds: Bounds<Pixels>,
_state: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) -> PaneAxisLayout {
let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
Some(self.basis.into()),
@@ -900,7 +900,7 @@ mod element {
bounds: gpui::Bounds<ui::prelude::Pixels>,
_: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
cx: &mut ui::prelude::ElementContext,
cx: &mut ui::prelude::WindowContext,
) {
for child in &mut layout.children {
child.element.paint(cx);

View File

@@ -0,0 +1,83 @@
use std::path::PathBuf;
use project::TaskSourceKind;
use task::{ResolvedTask, TaskContext, TaskTemplate};
use ui::{ViewContext, WindowContext};
use crate::Workspace;
pub fn task_cwd(workspace: &Workspace, cx: &mut WindowContext) -> anyhow::Result<Option<PathBuf>> {
let project = workspace.project().read(cx);
let available_worktrees = project
.worktrees()
.filter(|worktree| {
let worktree = worktree.read(cx);
worktree.is_visible()
&& worktree.is_local()
&& worktree.root_entry().map_or(false, |e| e.is_dir())
})
.collect::<Vec<_>>();
let cwd = match available_worktrees.len() {
0 => None,
1 => Some(available_worktrees[0].read(cx).abs_path()),
_ => {
let cwd_for_active_entry = project.active_entry().and_then(|entry_id| {
available_worktrees.into_iter().find_map(|worktree| {
let worktree = worktree.read(cx);
if worktree.contains_entry(entry_id) {
Some(worktree.abs_path())
} else {
None
}
})
});
anyhow::ensure!(
cwd_for_active_entry.is_some(),
"Cannot determine task cwd for multiple worktrees"
);
cwd_for_active_entry
}
};
Ok(cwd.map(|path| path.to_path_buf()))
}
pub fn schedule_task(
workspace: &Workspace,
task_source_kind: TaskSourceKind,
task_to_resolve: &TaskTemplate,
task_cx: &TaskContext,
omit_history: bool,
cx: &mut ViewContext<'_, Workspace>,
) {
if let Some(spawn_in_terminal) =
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
{
schedule_resolved_task(
workspace,
task_source_kind,
spawn_in_terminal,
omit_history,
cx,
);
}
}
pub fn schedule_resolved_task(
workspace: &Workspace,
task_source_kind: TaskSourceKind,
mut resolved_task: ResolvedTask,
omit_history: bool,
cx: &mut ViewContext<'_, Workspace>,
) {
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
if !omit_history {
resolved_task.resolved = Some(spawn_in_terminal.clone());
workspace.project().update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, _| {
inventory.task_scheduled(task_source_kind, resolved_task);
})
});
}
cx.emit(crate::Event::SpawnTask(spawn_in_terminal));
}
}

View File

@@ -8,6 +8,7 @@ mod persistence;
pub mod searchable;
pub mod shared_screen;
mod status_bar;
pub mod tasks;
mod toolbar;
mod workspace_settings;
@@ -78,9 +79,9 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
use ui::{
div, h_flex, Context as _, Div, Element, ElementContext, FluentBuilder,
InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString,
Styled as _, ViewContext, VisualContext as _, WindowContext,
div, h_flex, Context as _, Div, Element, FluentBuilder, InteractiveElement as _, IntoElement,
Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
WindowContext,
};
use util::{maybe, ResultExt};
use uuid::Uuid;
@@ -4991,7 +4992,7 @@ impl Element for DisconnectedOverlay {
type RequestLayoutState = AnyElement;
type PrepaintState = ();
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
let mut background = cx.theme().colors().elevated_surface_background;
background.fade_out(0.2);
let mut overlay = div()
@@ -5016,7 +5017,7 @@ impl Element for DisconnectedOverlay {
&mut self,
bounds: Bounds<Pixels>,
overlay: &mut Self::RequestLayoutState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
cx.insert_hitbox(bounds, true);
overlay.prepaint(cx);
@@ -5027,7 +5028,7 @@ impl Element for DisconnectedOverlay {
_: Bounds<Pixels>,
overlay: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
cx: &mut ElementContext,
cx: &mut WindowContext,
) {
overlay.paint(cx)
}

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.133.0"
version = "0.134.0"
publish = false
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]
@@ -96,7 +96,6 @@ workspace.workspace = true
zed_actions.workspace = true
[target.'cfg(target_os = "windows")'.build-dependencies]
embed-manifest = "1.4.0"
winresource = "0.1"
[dev-dependencies]

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