Compare commits

..

64 Commits

Author SHA1 Message Date
Nate Butler
d0649250a3 Add an initial set of captures for blockquotes 2024-04-17 10:48:51 -04:00
Thorsten Ball
d7becce9aa git: Only show inline git blame when editor is focused (#10680)
Release Notes:

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

It fixes this:

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


Release Notes:

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

Release Notes:

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



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


Release Notes:


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

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

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

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

Release Notes:

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

Release Notes:

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

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

---------

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

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

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

Release Notes:

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

- N/A

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

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

Before: 

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

After:

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

---

Release Notes:

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

---------

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

Release Notes:

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

Changes:

- #10641

Release Notes:

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

Changes:

- #10638

Release Notes:

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

Changes:

- #10636

Release Notes:

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

Changes:

- #10635

Release Notes:

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

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

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

Successful command:

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

Unsuccessful command:

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

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

Release Notes:

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

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

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

Release Notes:

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


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

Related Issues:

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

Changes:

- #10639
- #10642

Release Notes:

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

Changes:

- #10559
- #10634

Release Notes:

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

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

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

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

CC: @maxbrunsfeld

lua.rs before ef4c70c:

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

lua.rs after:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

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

Release Notes:


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

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

Example user settings for `clangd`:

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

Constraints:

* Right now this only allows ABSOLUTE paths.

Release Notes:

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

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

Release Notes:

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


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

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

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

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

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

Release Notes:

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

Fixes #10595

Release Notes:

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


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

Release Notes:

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

Example:

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

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

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



Release Notes:

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

Before:


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

After:

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

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

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

Example Markdown from `clangd`:

    ### instance-method `format`

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

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

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

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



Release Notes:

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

Before:

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

After:

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

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

Release Notes:

- N/A

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

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


Release Notes:

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

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

---------

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

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

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

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

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

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

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

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

Release Notes:

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

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

- N/A

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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


Release Notes:


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

Release Notes:

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

Here is what this looks like:

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


Release Notes:

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

Release Notes:

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

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



Release Notes:

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

Release Notes:

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

Release Notes:

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

Release Notes:

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

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

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

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

Release Notes:

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

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

Release Notes:

- Added an indicator to show the currently-installed extension version
when not on the latest version.
2024-04-15 14:30:53 -04:00
105 changed files with 1723 additions and 2135 deletions

View File

@@ -0,0 +1,49 @@
name: bump_patch_version
on:
workflow_dispatch:
inputs:
branch:
description: "Branch name to run on"
required: true
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.event.input.branch }}
cancel-in-progress: true
jobs:
bump_patch_version:
runs-on:
- self-hosted
- test
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.branch }}
ssh-key: ${{ secrets.ZED_BOT_DEPLOY_KEY }}
- name: Bump Patch Version
run: |
set -eux
channel=$(cat crates/zed/RELEASE_CHANNEL)
tag_suffix=""
case $channel in
stable)
;;
preview)
tag_suffix="-pre"
;;
*)
echo "this must be run on either of stable|preview release branches" >&2
exit 1
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag v${output}${tag_suffix}
git push origin HEAD v${output}${tag_suffix}

View File

@@ -3,10 +3,5 @@
"label": "clippy",
"command": "cargo",
"args": ["xtask", "clippy"]
},
{
"label": "assistant2",
"command": "cargo",
"args": ["run", "-p", "assistant2", "--example", "assistant_example"]
}
]

154
Cargo.lock generated
View File

@@ -371,35 +371,6 @@ dependencies = [
"workspace",
]
[[package]]
name = "assistant2"
version = "0.1.0"
dependencies = [
"anyhow",
"assets",
"client",
"editor",
"env_logger",
"futures 0.3.28",
"gpui",
"language",
"languages",
"log",
"nanoid",
"node_runtime",
"open_ai",
"project",
"release_channel",
"rich_text",
"semantic_index",
"serde",
"settings",
"theme",
"ui",
"util",
"workspace",
]
[[package]]
name = "async-broadcast"
version = "0.7.0"
@@ -549,7 +520,7 @@ dependencies = [
"polling 3.3.2",
"rustix 0.38.32",
"slab",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"windows-sys 0.52.0",
]
@@ -890,7 +861,7 @@ dependencies = [
"ring 0.17.7",
"time",
"tokio",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"zeroize",
]
@@ -926,7 +897,7 @@ dependencies = [
"http-body",
"percent-encoding",
"pin-project-lite",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"uuid",
]
@@ -955,7 +926,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"regex-lite",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"url",
]
@@ -978,7 +949,7 @@ dependencies = [
"http 0.2.9",
"once_cell",
"regex-lite",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1000,7 +971,7 @@ dependencies = [
"http 0.2.9",
"once_cell",
"regex-lite",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1023,7 +994,7 @@ dependencies = [
"http 0.2.9",
"once_cell",
"regex-lite",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1051,7 +1022,7 @@ dependencies = [
"sha2 0.10.7",
"subtle",
"time",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"zeroize",
]
@@ -1084,7 +1055,7 @@ dependencies = [
"pin-project-lite",
"sha1",
"sha2 0.10.7",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1116,7 +1087,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"pin-utils",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1160,7 +1131,7 @@ dependencies = [
"pin-utils",
"rustls",
"tokio",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1175,7 +1146,7 @@ dependencies = [
"http 0.2.9",
"pin-project-lite",
"tokio",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"zeroize",
]
@@ -1223,7 +1194,7 @@ dependencies = [
"aws-smithy-types",
"http 0.2.9",
"rustc_version",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -1546,7 +1517,7 @@ dependencies = [
"futures-io",
"futures-lite 2.2.0",
"piper",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -2304,7 +2275,7 @@ dependencies = [
"toml 0.8.10",
"tower",
"tower-http 0.4.4",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"tracing-subscriber",
"unindent",
"util",
@@ -4360,11 +4331,11 @@ dependencies = [
[[package]]
name = "git2"
version = "0.15.0"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1"
checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.4.2",
"libc",
"libgit2-sys",
"log",
@@ -4602,7 +4573,7 @@ dependencies = [
"slab",
"tokio",
"tokio-util",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -4913,7 +4884,7 @@ dependencies = [
"socket2 0.4.9",
"tokio",
"tower-service",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"want",
]
@@ -5228,7 +5199,7 @@ dependencies = [
"polling 2.8.0",
"slab",
"sluice",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"tracing-futures",
"url",
"waker-fn",
@@ -5592,9 +5563,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libgit2-sys"
version = "0.14.2+1.5.1"
version = "0.16.2+1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
dependencies = [
"cc",
"libc",
@@ -7194,7 +7165,7 @@ dependencies = [
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.32",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"windows-sys 0.52.0",
]
@@ -8098,7 +8069,7 @@ dependencies = [
"serde",
"serde_json",
"strum",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"util",
"zstd",
]
@@ -8465,7 +8436,7 @@ dependencies = [
"strum",
"thiserror",
"time",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"url",
"uuid",
]
@@ -9239,7 +9210,7 @@ dependencies = [
"time",
"tokio",
"tokio-stream",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"url",
"uuid",
"webpki-roots",
@@ -9326,7 +9297,7 @@ dependencies = [
"stringprep",
"thiserror",
"time",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"uuid",
"whoami",
]
@@ -9371,7 +9342,7 @@ dependencies = [
"stringprep",
"thiserror",
"time",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"uuid",
"whoami",
]
@@ -9396,7 +9367,7 @@ dependencies = [
"serde",
"sqlx-core",
"time",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"url",
"uuid",
]
@@ -10229,7 +10200,7 @@ dependencies = [
"futures-sink",
"pin-project-lite",
"tokio",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -10324,7 +10295,7 @@ dependencies = [
"tokio",
"tower-layer",
"tower-service",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -10361,7 +10332,7 @@ dependencies = [
"pin-project-lite",
"tower-layer",
"tower-service",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
@@ -10385,16 +10356,7 @@ dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"pin-project-lite",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing-core",
]
[[package]]
@@ -10413,14 +10375,6 @@ name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
dependencies = [
"once_cell",
"valuable",
@@ -10433,32 +10387,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing-core",
]
[[package]]
name = "tracing-serde"
version = "0.1.3"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
dependencies = [
"serde",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18#8b7a1dde69797b33ecfa20da71e72eb5e61f0b25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -10469,8 +10426,8 @@ dependencies = [
"sharded-slab",
"smallvec",
"thread_local",
"tracing 0.1.40 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing-core 0.1.32 (git+https://github.com/tokio-rs/tracing?rev=tracing-subscriber-0.3.18)",
"tracing",
"tracing-core",
"tracing-log",
"tracing-serde",
]
@@ -10767,7 +10724,6 @@ dependencies = [
"gpui",
"itertools 0.11.0",
"menu",
"nanoid",
"settings",
"smallvec",
"story",
@@ -11325,7 +11281,7 @@ dependencies = [
"anyhow",
"log",
"once_cell",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"wasmtime",
"wasmtime-c-api-macros",
]
@@ -11537,7 +11493,7 @@ dependencies = [
"system-interface",
"thiserror",
"tokio",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"url",
"wasmtime",
"wiggle",
@@ -11774,7 +11730,7 @@ dependencies = [
"async-trait",
"bitflags 2.4.2",
"thiserror",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"wasmtime",
"wiggle-macro",
]
@@ -12530,7 +12486,7 @@ dependencies = [
"serde_repr",
"sha1",
"static_assertions",
"tracing 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing",
"uds_windows",
"windows-sys 0.52.0",
"xdg-home",
@@ -12667,14 +12623,14 @@ dependencies = [
[[package]]
name = "zed_clojure"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.4",
]
[[package]]
name = "zed_csharp"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.4",
]
@@ -12738,7 +12694,7 @@ dependencies = [
[[package]]
name = "zed_gleam"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12759,7 +12715,7 @@ dependencies = [
[[package]]
name = "zed_lua"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12801,7 +12757,7 @@ dependencies = [
[[package]]
name = "zed_terraform"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -12829,7 +12785,7 @@ dependencies = [
[[package]]
name = "zed_zig"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@@ -4,7 +4,6 @@ members = [
"crates/anthropic",
"crates/assets",
"crates/assistant",
"crates/assistant2",
"crates/audio",
"crates/auto_update",
"crates/breadcrumbs",
@@ -206,7 +205,6 @@ rpc = { path = "crates/rpc" }
task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" }
search = { path = "crates/search" }
semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" }
settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
@@ -259,7 +257,7 @@ env_logger = "0.9"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.15", default-features = false }
git2 = { version = "0.18", default-features = false }
globset = "0.4"
heed = { git = "https://github.com/meilisearch/heed", rev = "036ac23f73a021894974b9adc815bc95b3e0482a", features = ["read-txn-no-tls"] }
hex = "0.4.3"

View File

@@ -1,3 +1,3 @@
collab: RUST_LOG=${RUST_LOG:-warn,tower_http=info,collab=info} cargo run --package=collab serve
collab: RUST_LOG=${RUST_LOG:-info} cargo run --package=collab serve
livekit: livekit-server --dev
blob_store: ./script/run-local-minio

View File

@@ -38,6 +38,8 @@ brew install zed-preview
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
## Licensing
License information for third party dependencies must be correctly provided for CI to pass.

3
assets/icons/sliders.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.36667 3.79167C5.53364 3.79167 4.85833 4.46697 4.85833 5.3C4.85833 6.13303 5.53364 6.80833 6.36667 6.80833C7.1997 6.80833 7.875 6.13303 7.875 5.3C7.875 4.46697 7.1997 3.79167 6.36667 3.79167ZM2.1 5.925H3.67944C3.9626 7.14732 5.05824 8.05833 6.36667 8.05833C7.67509 8.05833 8.77073 7.14732 9.05389 5.925H14.9C15.2452 5.925 15.525 5.64518 15.525 5.3C15.525 4.95482 15.2452 4.675 14.9 4.675H9.05389C8.77073 3.45268 7.67509 2.54167 6.36667 2.54167C5.05824 2.54167 3.9626 3.45268 3.67944 4.675H2.1C1.75482 4.675 1.475 4.95482 1.475 5.3C1.475 5.64518 1.75482 5.925 2.1 5.925ZM13.3206 12.325C13.0374 13.5473 11.9418 14.4583 10.6333 14.4583C9.32491 14.4583 8.22927 13.5473 7.94611 12.325H2.1C1.75482 12.325 1.475 12.0452 1.475 11.7C1.475 11.3548 1.75482 11.075 2.1 11.075H7.94611C8.22927 9.85268 9.32491 8.94167 10.6333 8.94167C11.9418 8.94167 13.0374 9.85268 13.3206 11.075H14.9C15.2452 11.075 15.525 11.3548 15.525 11.7C15.525 12.0452 15.2452 12.325 14.9 12.325H13.3206ZM9.125 11.7C9.125 10.867 9.8003 10.1917 10.6333 10.1917C11.4664 10.1917 12.1417 10.867 12.1417 11.7C12.1417 12.533 11.4664 13.2083 10.6333 13.2083C9.8003 13.2083 9.125 12.533 9.125 11.7Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -209,14 +209,7 @@
}
},
{
"context": "AssistantChat > Editor", // Used in the assistant2 crate
"bindings": {
"enter": ["assistant::Submit", "Simple"],
"cmd-enter": ["assistant::Submit", "Codebase"]
}
},
{
"context": "AssistantPanel", // Used in the assistant crate, which we're replacing
"context": "AssistantPanel",
"bindings": {
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch"

View File

@@ -47,11 +47,20 @@
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
// Centered layout related settings.
"centered_layout": {
// The relative width of the left padding of the central pane from the
// workspace when the centered layout is used.
"left_padding": 0.2,
// The relative width of the right padding of the central pane from the
// workspace when the centered layout is used.
"right_padding": 0.2
},
// The key to use for adding multiple cursors
// Currently "alt" or "cmd_or_ctrl" (also aliased as
// "cmd" and "ctrl") are supported.
"multi_cursor_modifier": "alt",
// Whether to enable vim modes and key bindings
// Whether to enable vim modes and key bindings.
"vim_mode": false,
// Whether to show the informational hover box when moving the mouse
// over symbols in the editor.
@@ -92,8 +101,9 @@
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
// Whether to automatically type closing characters for you. For example,
// when you type (, Zed will automatically add a closing ) at the correct position.
// Whether to automatically add matching closing characters when typing
// opening parenthesis, bracket, brace, single or double quote characters.
// For example, when you type (, Zed will add a closing ) at the correct position.
"use_autoclose": true,
// Controls how the editor handles the autoclosed characters.
// When set to `false`(default), skipping over and auto-removing of the closing characters
@@ -145,10 +155,10 @@
"show": "auto",
// Whether to show git diff indicators in the scrollbar.
"git_diff": true,
// Whether to show selections in the scrollbar.
"selections": true,
// Whether to show symbols selections in the scrollbar.
"symbols_selections": true,
// Whether to show buffer search results in the scrollbar.
"search_results": true,
// Whether to show selected symbol occurrences in the scrollbar.
"selected_symbol": true,
// Whether to show diagnostic indicators in the scrollbar.
"diagnostics": true
},
@@ -171,7 +181,7 @@
},
// The number of lines to keep above/below the cursor when scrolling.
"vertical_scroll_margin": 3,
// Scroll sensitivity multiplier. This multiplier is applied
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
"relative_line_numbers": false,
@@ -397,7 +407,7 @@
// Control whether the git blame information is shown inline,
// in the currently focused line.
"inline_blame": {
"enabled": false
"enabled": true
// Sets a delay after which the inline blame information is shown.
// Delay is restarted with every cursor movement.
// "delay_ms": 600
@@ -490,6 +500,8 @@
// Whether or not selecting text in the terminal will automatically
// copy to the system clipboard.
"copy_on_select": false,
// Whether to show the terminal button in the status bar
"button": true,
// Any key-value pairs added to this list will be added to the terminal's
// environment. Use `:` to separate multiple values.
"env": {

View File

@@ -5,9 +5,6 @@ edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/assets.rs"
[lints]
workspace = true

View File

@@ -1,7 +1,7 @@
// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build.
use anyhow::anyhow;
use gpui::{AppContext, AssetSource, Result, SharedString};
use gpui::{AssetSource, Result, SharedString};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
@@ -34,19 +34,3 @@ impl AssetSource for Assets {
.collect())
}
}
impl Assets {
/// Populate the [`TextSystem`] of the given [`AppContext`] with all `.ttf` fonts in the `fonts` directory.
pub fn load_fonts(&self, cx: &AppContext) -> gpui::Result<()> {
let font_paths = self.list("fonts")?;
let mut embedded_fonts = Vec::new();
for font_path in font_paths {
if font_path.ends_with(".ttf") {
let font_bytes = cx.asset_source().load(&font_path)?;
embedded_fonts.push(font_bytes);
}
}
cx.text_system().add_fonts(embedded_fonts)
}
}

View File

@@ -1119,8 +1119,8 @@ impl AssistantPanel {
)
.size_full()
.into_any_element()
} else {
let editor = self.active_conversation_editor().unwrap();
} else if let Some(editor) = self.active_conversation_editor() {
let editor = editor.clone();
let conversation = editor.read(cx).conversation.clone();
div()
.size_full()
@@ -1135,6 +1135,8 @@ impl AssistantPanel {
.children(self.render_remaining_tokens(&conversation, cx)),
)
.into_any_element()
} else {
div().into_any_element()
},
))
}

View File

@@ -1,52 +0,0 @@
[package]
name = "assistant2"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/assistant2.rs"
[[example]]
name = "assistant_example"
path = "examples/assistant_example.rs"
crate-type = ["bin"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
client.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true
open_ai.workspace = true
project.workspace = true
rich_text.workspace = true
semantic_index.workspace = true
serde.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
nanoid = "0.4"
[dev-dependencies]
assets.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
languages.workspace = true
node_runtime.workspace = true
project = { workspace = true, features = ["test-support"] }
release_channel.workspace = true
settings = { workspace = true, features = ["test-support"] }
theme = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }
workspace = { workspace = true, features = ["test-support"] }
[lints]
workspace = true

View File

@@ -1,120 +0,0 @@
use assets::Assets;
use assistant2::AssistantPanel;
use client::Client;
use gpui::{actions, App, AppContext, KeyBinding, Model, Task, View, WindowOptions};
use language::LanguageRegistry;
use project::{Fs, Project};
use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, ProjectIndex, SemanticIndex};
use settings::{KeymapFile, DEFAULT_KEYMAP_PATH};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use theme::LoadThemes;
use ui::{div, prelude::*, Render};
use util::http::HttpClientWithUrl;
actions!(example, [Quit]);
fn main() {
let args: Vec<String> = std::env::args().collect();
env_logger::init();
App::new().with_assets(Assets).run(|cx| {
cx.bind_keys(Some(KeyBinding::new("cmd-q", Quit, None)));
cx.on_action(|_: &Quit, cx: &mut AppContext| {
cx.quit();
});
if args.len() < 2 {
eprintln!(
"Usage: cargo run --example assistant_example -p assistant2 -- <project_path>"
);
cx.quit();
return;
}
settings::init(cx);
language::init(cx);
Project::init_settings(cx);
editor::init(cx);
theme::init(LoadThemes::JustBase, cx);
Assets.load_fonts(cx).unwrap();
KeymapFile::load_asset(DEFAULT_KEYMAP_PATH, cx).unwrap();
client::init_settings(cx);
release_channel::init("0.130.0", cx);
let client = Client::production(cx);
{
let client = client.clone();
cx.spawn(|cx| async move { client.authenticate_and_connect(false, &cx).await })
.detach_and_log_err(cx);
}
assistant2::init(client.clone(), cx);
let language_registry = Arc::new(LanguageRegistry::new(
Task::ready(()),
cx.background_executor().clone(),
));
let node_runtime = node_runtime::RealNodeRuntime::new(client.http_client());
languages::init(language_registry.clone(), node_runtime, cx);
let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434"));
let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set");
let embedding_provider = OpenAiEmbeddingProvider::new(
http.clone(),
OpenAiEmbeddingModel::TextEmbedding3Small,
open_ai::OPEN_AI_API_URL.to_string(),
api_key,
);
let semantic_index = SemanticIndex::new(
PathBuf::from("/tmp/semantic-index-db.mdb"),
Arc::new(embedding_provider),
cx,
);
cx.spawn(|mut cx| async move {
let project_path = Path::new(&args[1]);
dbg!(project_path);
let project = Project::example([project_path], &mut cx).await;
let mut semantic_index = semantic_index.await?;
cx.update(|cx| {
let fs = project.read(cx).fs().clone();
let project_index = semantic_index.project_index(project.clone(), cx);
cx.open_window(WindowOptions::default(), |cx| {
cx.new_view(|cx| Example::new(language_registry, project_index, fs, cx))
});
cx.activate(true);
})
})
.detach_and_log_err(cx);
})
}
struct Example {
assistant_panel: View<AssistantPanel>,
}
impl Example {
fn new(
language_registry: Arc<LanguageRegistry>,
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
assistant_panel: cx
.new_view(|cx| AssistantPanel::new(language_registry, project_index, fs, cx)),
}
}
}
impl Render for Example {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl ui::prelude::IntoElement {
div().size_full().child(self.assistant_panel.clone())
}
}

View File

@@ -1,788 +0,0 @@
mod completion_provider;
use anyhow::Result;
use client::Client;
use completion_provider::*;
use editor::{Editor, EditorEvent};
use futures::{channel::oneshot, Future, FutureExt as _, StreamExt};
use gpui::{
list, prelude::*, AnyElement, AppContext, FocusHandle, Global, ListAlignment, ListState, Model,
Render, Task, View,
};
use language::{language_settings::SoftWrap, LanguageRegistry};
use project::Fs;
use rich_text::RichText;
use semantic_index::ProjectIndex;
use serde::Deserialize;
use settings::Settings;
use std::{cmp, sync::Arc};
use theme::ThemeSettings;
use ui::{popover_menu, prelude::*, ButtonLike, CollapsibleContainer, Color, ContextMenu, Tooltip};
use util::ResultExt;
// gpui::actions!(assistant, [Submit]);
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
pub struct Submit(SubmitMode);
/// There are multiple different ways to submit a model request, represented by this enum.
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
pub enum SubmitMode {
/// Only include the conversation.
Simple,
/// Send the current file as context.
CurrentFile,
/// Search the codebase and send relevant excerpts.
Codebase,
}
gpui::impl_actions!(assistant, [Submit]);
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
cx.set_global(CompletionProvider::new(CloudCompletionProvider::new(
client,
)));
}
pub struct AssistantPanel {
#[allow(dead_code)]
language_registry: Arc<LanguageRegistry>,
#[allow(dead_code)]
project_index: Model<ProjectIndex>,
#[allow(dead_code)]
fs: Arc<dyn Fs>,
chat: View<AssistantChat>,
}
impl AssistantPanel {
pub fn new(
language_registry: Arc<LanguageRegistry>,
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>,
) -> Self {
let chat = cx.new_view(|cx| {
AssistantChat::new(
language_registry.clone(),
project_index.clone(),
fs.clone(),
cx,
)
});
Self {
language_registry,
project_index,
fs,
chat,
}
}
}
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.size_full()
.v_flex()
.p_2()
.bg(cx.theme().colors().background)
.child(self.chat.clone())
}
}
struct AssistantChat {
model: String,
messages: Vec<ChatMessage>,
list_state: ListState,
language_registry: Arc<LanguageRegistry>,
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
next_message_id: MessageId,
pending_completion: Option<Task<()>>,
}
impl AssistantChat {
fn new(
language_registry: Arc<LanguageRegistry>,
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>,
) -> Self {
let model = CompletionProvider::get(cx).default_model();
let view = cx.view().downgrade();
let list_state = ListState::new(
0,
ListAlignment::Bottom,
px(1024.),
move |ix, cx: &mut WindowContext| {
view.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
},
);
let mut this = Self {
model,
messages: Vec::new(),
list_state,
language_registry,
project_index,
fs,
next_message_id: MessageId(0),
pending_completion: None,
};
this.push_new_user_message(true, cx);
this
}
fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
self.messages.iter().find_map(|message| match message {
ChatMessage::User(message) => message
.body
.focus_handle(cx)
.contains_focused(cx)
.then_some(message.id),
ChatMessage::Assistant(_) => None,
})
}
fn 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.");
return;
};
self.truncate_messages(focused_message_id, cx);
self.push_new_assistant_message(cx);
let populate = self.populate_context_on_submit(focused_message_id, mode, cx);
self.pending_completion = Some(cx.spawn(|this, mut cx| async move {
let complete = async {
populate.await?;
let completion = this.update(&mut cx, |this, cx| {
CompletionProvider::get(cx).complete(
this.model.clone(),
this.completion_messages(cx),
Vec::new(),
1.0,
)
});
let mut stream = completion?.await?;
let mut body = String::new();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
this.update(&mut cx, |this, cx| {
if let Some(ChatMessage::Assistant(AssistantMessage {
body: message_body,
..
})) = this.messages.last_mut()
{
body.push_str(&chunk);
*message_body =
RichText::new(body.clone(), &[], &this.language_registry);
cx.notify();
} else {
unreachable!()
}
})?;
}
anyhow::Ok(())
}
.await;
this.update(&mut cx, |this, cx| {
if let Err(error) = complete {
if let Some(ChatMessage::Assistant(AssistantMessage {
error: message_error,
..
})) = this.messages.last_mut()
{
message_error.replace(SharedString::from(error.to_string()));
cx.notify();
} else {
unreachable!()
}
}
let focus = this
.user_message(focused_message_id)
.body
.focus_handle(cx)
.contains_focused(cx);
this.push_new_user_message(focus, cx);
})
.log_err();
}));
}
/// Set up the query designed for the semantic index, based on previous conversation
fn setup_query(&self, cx: &mut ViewContext<Self>) -> Task<Result<String>> {
// Let's try another approach where we take the user's previous messages and turn that into a query
// by calling for a completion.
// For now, we'll set up a summary request message, where we tell the model we need something simple to summarize
let mut query_creation_messages = self.completion_messages(cx);
query_creation_messages.push(CompletionMessage {
role: CompletionRole::System,
body: r#"
Turn the user's query into a single search string that can be used to search for code base snippets relevant to the user's query. Everything you respond with will be fed directly to a semantic index.
## Example
**User**: How can I create a component in GPUI that works like a `<details>` / `<summary>` pair in HTML?
GPUI create component like HTML details summary example
"#.into(),
});
let query = CompletionProvider::get(cx).complete(
self.model.clone(),
query_creation_messages,
Vec::new(),
1.0,
);
cx.spawn(|_, _| async move {
let mut stream = query.await?;
// todo!(): Show the query in the UI as part of the context view
let mut query = String::new();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
query.push_str(&chunk);
}
dbg!(&query);
anyhow::Ok(query)
})
}
// Returns a oneshot channel which resolves to true when the context is successfully populated.
fn populate_context_on_submit(
&mut self,
submitted_id: MessageId,
mode: &SubmitMode,
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<bool> {
let (tx, rx) = oneshot::channel();
match mode {
SubmitMode::Simple => {
tx.send(true).ok();
}
SubmitMode::CurrentFile => {
tx.send(true).ok();
}
SubmitMode::Codebase => {
self.user_message(submitted_id).contexts.clear();
let query = self.setup_query(cx);
let project_index = self.project_index.clone();
let fs = self.fs.clone();
self.user_message(submitted_id)
.contexts
.push(AssistantContext::Codebase(cx.new_view(|cx| {
CodebaseContext::new(query, tx, project_index, fs, cx)
})));
}
}
rx
}
fn user_message(&mut self, message_id: MessageId) -> &mut UserMessage {
self.messages
.iter_mut()
.find_map(|message| match message {
ChatMessage::User(user_message) if user_message.id == message_id => {
Some(user_message)
}
_ => None,
})
.expect("User message not found")
}
fn push_new_user_message(&mut self, focus: bool, cx: &mut ViewContext<Self>) {
let id = self.next_message_id.post_inc();
let body = cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
if focus {
cx.focus_self();
}
editor
});
let _subscription = cx.subscribe(&body, move |this, editor, event, cx| match event {
EditorEvent::SelectionsChanged { .. } => {
if editor.read(cx).is_focused(cx) {
let (message_ix, message) = this
.messages
.iter()
.enumerate()
.find_map(|(ix, message)| match message {
ChatMessage::User(user_message) if user_message.id == id => {
Some((ix, user_message))
}
_ => None,
})
.expect("user message not found");
message.body.update(cx, |body, cx| {
if let Some(editor_style) = body.style() {
let row = body.selections.newest_display(cx).head().row();
let line_height =
editor_style.text.line_height_in_pixels(cx.rem_size());
let row_y = row as f32 * line_height;
this.list_state.scroll_to_fit(
message_ix,
row_y,
row_y + 5. * line_height,
);
}
});
}
}
_ => {}
});
let message = ChatMessage::User(UserMessage {
id,
body,
contexts: Vec::new(),
_subscription,
});
self.push_message(message, cx);
}
fn push_new_assistant_message(&mut self, cx: &mut ViewContext<Self>) {
let message = ChatMessage::Assistant(AssistantMessage {
id: self.next_message_id.post_inc(),
body: RichText::default(),
error: None,
});
self.push_message(message, cx);
}
fn push_message(&mut self, message: ChatMessage, cx: &mut ViewContext<Self>) {
let old_len = self.messages.len();
let focus_handle = Some(message.focus_handle(cx));
self.messages.push(message);
self.list_state
.splice_focusable(old_len..old_len, focus_handle);
cx.notify();
}
fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
if let Some(index) = self.messages.iter().position(|message| match message {
ChatMessage::User(message) => message.id == last_message_id,
ChatMessage::Assistant(message) => message.id == last_message_id,
}) {
self.list_state.splice(index + 1..self.messages.len(), 0);
self.messages.truncate(index + 1);
cx.notify();
}
}
fn render_error(
&self,
error: Option<SharedString>,
_ix: usize,
cx: &mut ViewContext<Self>,
) -> AnyElement {
let theme = cx.theme();
if let Some(error) = error {
div()
.py_1()
.px_2()
.neg_mx_1()
.rounded_md()
.border()
.border_color(theme.status().error_border)
// .bg(theme.status().error_background)
.text_color(theme.status().error)
.child(error.clone())
.into_any_element()
} else {
div().into_any_element()
}
}
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let is_last = ix == self.messages.len() - 1;
match &self.messages[ix] {
ChatMessage::User(UserMessage { body, contexts, .. }) => div()
.when(!is_last, |element| element.mb_2())
.child(div().p_2().child(Label::new("You").color(Color::Default)))
.child(
div()
.on_action(cx.listener(Self::submit))
.p_2()
.text_color(cx.theme().colors().editor_foreground)
.font(ThemeSettings::get_global(cx).buffer_font.clone())
.bg(cx.theme().colors().editor_background)
.child(body.clone())
.children(contexts.iter().map(|context| context.render(cx))),
)
.into_any(),
ChatMessage::Assistant(AssistantMessage { id, body, error }) => div()
.when(!is_last, |element| element.mb_2())
.child(
div()
.p_2()
.child(Label::new("Assistant").color(Color::Modified)),
)
.child(div().p_2().child(body.element(ElementId::from(id.0), cx)))
.child(self.render_error(error.clone(), ix, cx))
.into_any(),
}
}
fn completion_messages(&self, cx: &WindowContext) -> Vec<CompletionMessage> {
let mut completion_messages = Vec::new();
for message in &self.messages {
match message {
ChatMessage::User(UserMessage { body, contexts, .. }) => {
// setup context for model
contexts.iter().for_each(|context| {
completion_messages.extend(context.completion_messages(cx))
});
// Show user's message last so that the assistant is grounded in the user's request
completion_messages.push(CompletionMessage {
role: CompletionRole::User,
body: body.read(cx).text(cx),
});
}
ChatMessage::Assistant(AssistantMessage { body, .. }) => {
completion_messages.push(CompletionMessage {
role: CompletionRole::Assistant,
body: body.text.to_string(),
});
}
}
}
completion_messages
}
fn render_model_dropdown(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let this = cx.view().downgrade();
div().h_flex().justify_end().child(
div().w_32().child(
popover_menu("user-menu")
.menu(move |cx| {
ContextMenu::build(cx, |mut menu, cx| {
for model in CompletionProvider::get(cx).available_models() {
menu = menu.custom_entry(
{
let model = model.clone();
move |_| Label::new(model.clone()).into_any_element()
},
{
let this = this.clone();
move |cx| {
_ = this.update(cx, |this, cx| {
this.model = model.clone();
cx.notify();
});
}
},
);
}
menu
})
.into()
})
.trigger(
ButtonLike::new("active-model")
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(Label::new(self.model.clone())),
)
.child(div().child(
Icon::new(IconName::ChevronDown).color(Color::Muted),
)),
)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Change Model", cx)),
)
.anchor(gpui::AnchorCorner::TopRight),
),
)
}
}
impl Render for AssistantChat {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.relative()
.flex_1()
.v_flex()
.key_context("AssistantChat")
.text_color(Color::Default.color(cx))
.child(self.render_model_dropdown(cx))
.child(list(self.list_state.clone()).flex_1())
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
struct MessageId(usize);
impl MessageId {
fn post_inc(&mut self) -> Self {
let id = *self;
self.0 += 1;
id
}
}
enum ChatMessage {
User(UserMessage),
Assistant(AssistantMessage),
}
impl ChatMessage {
fn focus_handle(&self, cx: &WindowContext) -> Option<FocusHandle> {
match self {
ChatMessage::User(UserMessage { body, .. }) => Some(body.focus_handle(cx)),
ChatMessage::Assistant(_) => None,
}
}
}
struct UserMessage {
id: MessageId,
body: View<Editor>,
contexts: Vec<AssistantContext>,
_subscription: gpui::Subscription,
}
// chain_of_thought: ... -> search -> search_results -> produce_new_message -> send for the real chat message
struct AssistantMessage {
id: MessageId,
body: RichText,
error: Option<SharedString>,
}
enum AssistantContext {
Codebase(View<CodebaseContext>),
}
struct CodebaseExcerpt {
element_id: ElementId,
path: SharedString,
text: SharedString,
score: f32,
expanded: bool,
}
impl AssistantContext {
fn render(&self, _cx: &mut ViewContext<AssistantChat>) -> AnyElement {
match self {
AssistantContext::Codebase(context) => context.clone().into_any_element(),
}
}
fn completion_messages(&self, cx: &WindowContext) -> Vec<CompletionMessage> {
match self {
AssistantContext::Codebase(context) => context.read(cx).completion_messages(),
}
}
}
enum CodebaseContext {
Pending { _task: Task<()> },
Done(Result<Vec<CodebaseExcerpt>>),
}
impl CodebaseContext {
fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext<Self>) {
if let CodebaseContext::Done(Ok(excerpts)) = self {
if let Some(excerpt) = excerpts
.iter_mut()
.find(|excerpt| excerpt.element_id == element_id)
{
excerpt.expanded = !excerpt.expanded;
cx.notify();
}
}
}
}
impl Render for CodebaseContext {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
match self {
CodebaseContext::Pending { .. } => div()
.h_flex()
.items_center()
.gap_1()
.child(Icon::new(IconName::Ai).color(Color::Muted).into_element())
.child("Searching codebase..."),
CodebaseContext::Done(Ok(excerpts)) => {
div()
.v_flex()
.gap_2()
.children(excerpts.iter().map(|excerpt| {
let expanded = excerpt.expanded;
let element_id = excerpt.element_id.clone();
CollapsibleContainer::new(element_id.clone(), expanded.clone())
.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| {
dbg!("listener callback fired");
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
),
)
}))
}
CodebaseContext::Done(Err(error)) => div().child(error.to_string()), // todo!,
}
}
}
impl CodebaseContext {
fn new(
query: impl 'static + Future<Output = Result<String>>,
populated: oneshot::Sender<bool>,
project_index: Model<ProjectIndex>,
fs: Arc<dyn Fs>,
cx: &mut ViewContext<Self>,
) -> Self {
let query = query.boxed_local();
let _task = cx.spawn(|this, mut cx| async move {
let result = async {
let query = query.await?;
let results = this
.update(&mut cx, |_this, cx| {
project_index.read(cx).search(&query, 16, cx)
})?
.await;
let excerpts = results.into_iter().map(|result| {
let abs_path = result
.worktree
.read_with(&cx, |worktree, _| worktree.abs_path().join(&result.path));
let fs = fs.clone();
async move {
let path = result.path.clone();
let text = fs.load(&abs_path?).await?;
// todo!("what should we do with stale ranges?");
let range = cmp::min(result.range.start, text.len())
..cmp::min(result.range.end, text.len());
let text = SharedString::from(text[range].to_string());
anyhow::Ok(CodebaseExcerpt {
element_id: ElementId::Name(nanoid::nanoid!().into()),
path: path.to_string_lossy().to_string().into(),
text,
score: result.score,
expanded: false,
})
}
});
anyhow::Ok(
futures::future::join_all(excerpts)
.await
.into_iter()
.filter_map(|result| result.log_err())
.collect(),
)
}
.await;
this.update(&mut cx, |this, cx| {
this.populate(result, populated, cx);
})
.ok();
});
Self::Pending { _task }
}
fn populate(
&mut self,
result: Result<Vec<CodebaseExcerpt>>,
populated: oneshot::Sender<bool>,
cx: &mut ViewContext<Self>,
) {
let success = result.is_ok();
*self = Self::Done(result);
populated.send(success).ok();
cx.notify();
}
fn completion_messages(&self) -> Vec<CompletionMessage> {
// One system message for the whole batch of excerpts:
// Semantic search results for user query:
//
// Excerpt from $path:
// ~~~
// `text`
// ~~~
//
// Excerpt from $path:
match self {
CodebaseContext::Done(Ok(excerpts)) => {
if excerpts.is_empty() {
return Vec::new();
}
let mut body = "Semantic search reasults for user query:\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");
}
vec![CompletionMessage {
role: CompletionRole::System,
body,
}]
}
_ => vec![],
}
}
}

View File

@@ -1,123 +0,0 @@
use anyhow::Result;
use client::{proto, Client};
use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::Global;
use std::sync::Arc;
pub enum CompletionRole {
User,
Assistant,
System,
}
pub struct CompletionMessage {
pub role: CompletionRole,
pub body: String,
}
#[derive(Clone)]
pub struct CompletionProvider(Arc<dyn CompletionProviderBackend>);
impl CompletionProvider {
pub fn new(backend: impl CompletionProviderBackend) -> Self {
Self(Arc::new(backend))
}
pub fn default_model(&self) -> String {
self.0.default_model()
}
pub fn available_models(&self) -> Vec<String> {
self.0.available_models()
}
pub fn complete(
&self,
model: String,
messages: Vec<CompletionMessage>,
stop: Vec<String>,
temperature: f32,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
self.0.complete(model, messages, stop, temperature)
}
}
impl Global for CompletionProvider {}
pub trait CompletionProviderBackend: 'static {
fn default_model(&self) -> String;
fn available_models(&self) -> Vec<String>;
fn complete(
&self,
model: String,
messages: Vec<CompletionMessage>,
stop: Vec<String>,
temperature: f32,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
}
pub struct CloudCompletionProvider {
client: Arc<Client>,
}
impl CloudCompletionProvider {
pub fn new(client: Arc<Client>) -> Self {
Self { client }
}
}
impl CompletionProviderBackend for CloudCompletionProvider {
fn default_model(&self) -> String {
"gpt-4-turbo".into()
}
fn available_models(&self) -> Vec<String> {
vec!["gpt-4-turbo".into(), "gpt-4".into(), "gpt-3.5-turbo".into()]
}
fn complete(
&self,
model: String,
messages: Vec<CompletionMessage>,
stop: Vec<String>,
temperature: f32,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
let client = self.client.clone();
async move {
let stream = client
.request_stream(proto::CompleteWithLanguageModel {
model,
messages: messages
.into_iter()
.map(|message| proto::LanguageModelRequestMessage {
role: match message.role {
CompletionRole::User => {
proto::LanguageModelRole::LanguageModelUser as i32
}
CompletionRole::Assistant => {
proto::LanguageModelRole::LanguageModelAssistant as i32
}
CompletionRole::System => {
proto::LanguageModelRole::LanguageModelSystem as i32
}
},
content: message.body,
})
.collect(),
stop,
temperature,
})
.await?;
Ok(stream
.filter_map(|response| async move {
match response {
Ok(mut response) => Some(Ok(response.choices.pop()?.delta?.content?)),
Err(error) => Some(Err(error)),
}
})
.boxed())
}
.boxed()
}
}

View File

@@ -132,7 +132,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
move |_: &SignOut, cx| {
if let Some(client) = client.upgrade() {
cx.spawn(|cx| async move {
client.disconnect(&cx);
client.sign_out(&cx).await;
})
.detach();
}
@@ -457,14 +457,6 @@ impl Client {
})
}
pub fn production(cx: &mut AppContext) -> Arc<Self> {
let clock = Arc::new(clock::RealSystemClock);
let http = Arc::new(HttpClientWithUrl::new(
&ClientSettings::get_global(cx).server_url,
));
Self::new(clock, http.clone(), cx)
}
pub fn id(&self) -> u64 {
self.id.load(Ordering::SeqCst)
}
@@ -1124,13 +1116,9 @@ impl Client {
let public_key_string = String::try_from(public_key)
.expect("failed to serialize public key for auth");
dbg!(ADMIN_API_TOKEN.as_ref());
if let Some((login, token)) =
IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref())
{
eprintln!("authenticate as admin {login}, {token}");
return Self::authenticate_as_admin(http, login.clone(), token.clone())
.await;
}
@@ -1262,6 +1250,15 @@ impl Client {
})
}
pub async fn sign_out(self: &Arc<Self>, cx: &AsyncAppContext) {
self.state.write().credentials = None;
self.disconnect(&cx);
if self.has_keychain_credentials(cx).await {
delete_credentials_from_keychain(cx).await.log_err();
}
}
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
self.peer.teardown();
self.set_status(Status::SignedOut, cx);

View File

@@ -64,7 +64,7 @@ toml.workspace = true
tower = "0.4"
tower-http = { workspace = true, features = ["trace"] }
tracing = "0.1.40"
tracing-subscriber = { git = "https://github.com/tokio-rs/tracing", rev = "tracing-subscriber-0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
util.workspace = true
uuid.workspace = true

View File

@@ -18,7 +18,10 @@ use language::{
language_settings::{AllLanguageSettings, InlayHintSettings},
FakeLspAdapter,
};
use project::SERVER_PROGRESS_DEBOUNCE_TIMEOUT;
use project::{
project_settings::{InlineBlameSettings, ProjectSettings},
SERVER_PROGRESS_DEBOUNCE_TIMEOUT,
};
use rpc::RECEIVE_TIMEOUT;
use serde_json::json;
use settings::SettingsStore;
@@ -1999,6 +2002,25 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
cx_a.update(editor::init);
cx_b.update(editor::init);
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
let inline_blame_off_settings = Some(InlineBlameSettings {
enabled: false,
delay_ms: None,
});
cx_a.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<ProjectSettings>(cx, |settings| {
settings.git.inline_blame = inline_blame_off_settings;
});
});
});
cx_b.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<ProjectSettings>(cx, |settings| {
settings.git.inline_blame = inline_blame_off_settings;
});
});
});
client_a
.fs()

View File

@@ -234,11 +234,10 @@ impl ChatPanel {
let channel_id = chat.read(cx).channel_id;
{
self.markdown_data.clear();
let chat = chat.read(cx);
self.message_list.reset(chat.message_count());
let channel_name = chat.channel(cx).map(|channel| channel.name.clone());
let message_count = chat.message_count();
self.message_list.reset(message_count);
self.message_editor.update(cx, |editor, cx| {
editor.set_channel(channel_id, channel_name, cx);
editor.clear_reply_to_message_id();
@@ -767,7 +766,7 @@ impl ChatPanel {
body.push_str(MESSAGE_EDITED);
}
let mut rich_text = RichText::new(body, &mentions, language_registry);
let mut rich_text = rich_text::render_rich_text(body, &mentions, language_registry, None);
if message.edited_at.is_some() {
let range = (rich_text.text.len() - MESSAGE_EDITED.len())..rich_text.text.len();

View File

@@ -3075,7 +3075,7 @@ impl Render for DraggedChannelView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element {
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
h_flex()
.font_family(ui_font)
.font(ui_font)
.bg(cx.theme().colors().background)
.w(self.width)
.p_1()

View File

@@ -125,7 +125,7 @@ impl Render for IncomingCallNotification {
cx.set_rem_size(ui_font_size);
div().size_full().font_family(ui_font).child(
div().size_full().font(ui_font).child(
CollabNotification::new(
self.state.call.calling_user.avatar_uri.clone(),
Button::new("accept", "Accept").on_click({

View File

@@ -129,7 +129,7 @@ impl Render for ProjectSharedNotification {
cx.set_rem_size(ui_font_size);
div().size_full().font_family(ui_font).child(
div().size_full().font(ui_font).child(
CollabNotification::new(
self.owner.avatar_uri.clone(),
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {

View File

@@ -44,8 +44,6 @@ use workspace::{
actions!(diagnostics, [Deploy, ToggleWarnings]);
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut AppContext) {
ProjectDiagnosticsSettings::register(cx);
cx.observe_new_views(ProjectDiagnosticsEditor::register)
@@ -63,6 +61,7 @@ struct ProjectDiagnosticsEditor {
paths_to_update: HashMap<LanguageServerId, HashSet<ProjectPath>>,
current_diagnostics: HashMap<LanguageServerId, HashSet<ProjectPath>>,
include_warnings: bool,
context: u32,
_subscriptions: Vec<Subscription>,
}
@@ -116,7 +115,8 @@ impl ProjectDiagnosticsEditor {
workspace.register_action(Self::deploy);
}
fn new(
fn new_with_context(
context: u32,
project_handle: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
@@ -181,6 +181,7 @@ impl ProjectDiagnosticsEditor {
let summary = project.diagnostic_summary(false, cx);
let mut this = Self {
project: project_handle,
context,
summary,
workspace,
excerpts,
@@ -200,6 +201,19 @@ impl ProjectDiagnosticsEditor {
this
}
fn new(
project_handle: Model<Project>,
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
) -> Self {
Self::new_with_context(
editor::DEFAULT_MULTIBUFFER_CONTEXT,
project_handle,
workspace,
cx,
)
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, cx);
@@ -430,18 +444,16 @@ impl ProjectDiagnosticsEditor {
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
if let Some((range, start_ix)) = &mut pending_range {
if let Some(entry) = resolved_entry.as_ref() {
if entry.range.start.row
<= range.end.row + 1 + CONTEXT_LINE_COUNT * 2
{
if entry.range.start.row <= range.end.row + 1 + self.context * 2 {
range.end = range.end.max(entry.range.end);
continue;
}
}
let excerpt_start =
Point::new(range.start.row.saturating_sub(CONTEXT_LINE_COUNT), 0);
Point::new(range.start.row.saturating_sub(self.context), 0);
let excerpt_end = snapshot.clip_point(
Point::new(range.end.row + CONTEXT_LINE_COUNT, u32::MAX),
Point::new(range.end.row + self.context, u32::MAX),
Bias::Left,
);
let excerpt_id = excerpts
@@ -1030,7 +1042,12 @@ mod tests {
// Open the project diagnostics view while there are already diagnostics.
let view = window.build_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
ProjectDiagnosticsEditor::new_with_context(
1,
project.clone(),
workspace.downgrade(),
cx,
)
});
view.next_notification(cx).await;
@@ -1340,7 +1357,12 @@ mod tests {
let workspace = window.root(cx).unwrap();
let view = window.build_view(cx, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
ProjectDiagnosticsEditor::new_with_context(
1,
project.clone(),
workspace.downgrade(),
cx,
)
});
// Two language servers start updating diagnostics

View File

@@ -60,13 +60,13 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use git::blame::GitBlame;
use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton, PaintQuad,
ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle, Styled, StyledText,
Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, ViewContext,
ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView,
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model,
MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -138,6 +138,7 @@ use workspace::{
use crate::hover_links::find_url;
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const MAX_LINE_LEN: usize = 1024;
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
@@ -466,6 +467,7 @@ pub struct Editor {
show_git_blame_gutter: bool,
show_git_blame_inline: bool,
show_git_blame_inline_delay_task: Option<Task<()>>,
git_blame_inline_enabled: bool,
blame: Option<Model<GitBlame>>,
blame_subscription: Option<Subscription>,
custom_context_menu: Option<
@@ -1503,6 +1505,7 @@ impl Editor {
show_git_blame_gutter: false,
show_git_blame_inline: false,
show_git_blame_inline_delay_task: None,
git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
blame: None,
blame_subscription: None,
_subscriptions: vec![
@@ -1535,7 +1538,8 @@ impl Editor {
let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
if ProjectSettings::get_global(cx).git.inline_blame_enabled() {
if this.git_blame_inline_enabled {
this.git_blame_inline_enabled = true;
this.start_git_blame_inline(false, cx);
}
}
@@ -1920,7 +1924,9 @@ impl Editor {
self.refresh_document_highlights(cx);
refresh_matching_bracket_highlights(self, cx);
self.discard_inline_completion(cx);
self.start_inline_blame_timer(cx);
if self.git_blame_inline_enabled {
self.start_inline_blame_timer(cx);
}
}
self.blink_manager.update(cx, BlinkManager::pause_blinking);
@@ -3738,7 +3744,7 @@ impl Editor {
buffer
.edited_ranges_for_transaction::<usize>(transaction)
.collect(),
1,
DEFAULT_MULTIBUFFER_CONTEXT,
cx,
),
);
@@ -8007,7 +8013,7 @@ impl Editor {
ranges_to_highlight.extend(multibuffer.push_excerpts_with_context_lines(
location.buffer.clone(),
ranges_for_buffer,
1,
DEFAULT_MULTIBUFFER_CONTEXT,
cx,
))
}
@@ -8883,6 +8889,10 @@ impl Editor {
cx.notify();
}
pub fn git_blame_inline_enabled(&self) -> bool {
self.git_blame_inline_enabled
}
fn start_git_blame(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
if let Some(project) = self.project.as_ref() {
let Some(buffer) = self.buffer().read(cx).as_singleton() else {
@@ -8901,10 +8911,12 @@ impl Editor {
user_triggered: bool,
cx: &mut ViewContext<Self>,
) {
if self.show_git_blame_inline || self.show_git_blame_inline_delay_task.is_some() {
if self.git_blame_inline_enabled {
self.git_blame_inline_enabled = false;
self.show_git_blame_inline = false;
self.show_git_blame_inline_delay_task.take();
} else {
self.git_blame_inline_enabled = true;
self.start_git_blame_inline(user_triggered, cx);
}
@@ -8912,16 +8924,16 @@ impl Editor {
}
fn start_git_blame_inline(&mut self, user_triggered: bool, cx: &mut ViewContext<Self>) {
if let Some(inline_blame_settings) = ProjectSettings::get_global(cx).git.inline_blame {
if inline_blame_settings.enabled {
self.start_git_blame(user_triggered, cx);
self.start_git_blame(user_triggered, cx);
if inline_blame_settings.delay_ms.is_some() {
self.start_inline_blame_timer(cx);
} else {
self.show_git_blame_inline = true
}
}
if ProjectSettings::get_global(cx)
.git
.inline_blame_delay()
.is_some()
{
self.start_inline_blame_timer(cx);
} else {
self.show_git_blame_inline = true
}
}
@@ -8934,7 +8946,7 @@ impl Editor {
}
pub fn render_git_blame_inline(&mut self, cx: &mut WindowContext) -> bool {
self.show_git_blame_inline && self.has_blame_entries(cx)
self.focus_handle.is_focused(cx) && self.show_git_blame_inline && self.has_blame_entries(cx)
}
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
@@ -9514,7 +9526,7 @@ impl Editor {
if self.mode == EditorMode::Full {
let inline_blame_enabled = ProjectSettings::get_global(cx).git.inline_blame_enabled();
if self.show_git_blame_inline != inline_blame_enabled {
if self.git_blame_inline_enabled != inline_blame_enabled {
self.toggle_git_blame_inline_internal(false, cx);
}
}
@@ -10226,9 +10238,21 @@ impl FocusableView for Editor {
impl Render for Editor {
fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
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

@@ -58,8 +58,8 @@ pub struct Toolbar {
pub struct Scrollbar {
pub show: ShowScrollbar,
pub git_diff: bool,
pub selections: bool,
pub symbols_selections: bool,
pub selected_symbol: bool,
pub search_results: bool,
pub diagnostics: bool,
}
@@ -194,14 +194,14 @@ pub struct ScrollbarContent {
///
/// Default: true
pub git_diff: Option<bool>,
/// Whether to show buffer search result markers in the scrollbar.
/// Whether to show buffer search result indicators in the scrollbar.
///
/// Default: true
pub selections: Option<bool>,
/// Whether to show symbols highlighted markers in the scrollbar.
pub search_results: Option<bool>,
/// Whether to show selected symbol occurrences in the scrollbar.
///
/// Default: true
pub symbols_selections: Option<bool>,
pub selected_symbol: Option<bool>,
/// Whether to show diagnostic indicators in the scrollbar.
///
/// Default: true

View File

@@ -965,11 +965,11 @@ impl EditorElement {
// Git
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
||
// Selections
(is_singleton && scrollbar_settings.selections && editor.has_background_highlights::<BufferSearchHighlights>())
// Buffer Search Results
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
||
// Symbols Selections
(is_singleton && scrollbar_settings.symbols_selections && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
// Selected Symbol Occurrences
(is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
||
// Diagnostics
(is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
@@ -1099,9 +1099,9 @@ impl EditorElement {
#[allow(clippy::too_many_arguments)]
fn layout_inline_blame(
&self,
start_row: u32,
row: u32,
line_layouts: &[LineWithInvisibles],
display_row: u32,
display_snapshot: &DisplaySnapshot,
line_layout: &LineWithInvisibles,
em_width: Pixels,
content_origin: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<Pixels>,
@@ -1115,29 +1115,33 @@ impl EditorElement {
return None;
}
let blame = self.editor.read(cx).blame.clone()?;
let workspace = self
.editor
.read(cx)
.workspace
.as_ref()
.map(|(w, _)| w.clone());
let display_point = DisplayPoint::new(display_row, 0);
let buffer_row = display_point.to_point(display_snapshot).row;
let blame = self.editor.read(cx).blame.clone()?;
let blame_entry = blame
.update(cx, |blame, cx| blame.blame_for_rows([Some(row)], cx).next())
.update(cx, |blame, cx| {
blame.blame_for_rows([Some(buffer_row)], cx).next()
})
.flatten()?;
let mut element =
render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
let start_y =
content_origin.y + line_height * (row as f32 - scroll_pixel_position.y / line_height);
let start_y = content_origin.y
+ line_height * (display_row as f32 - scroll_pixel_position.y / line_height);
let start_x = {
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
let line_layout = &line_layouts[(row - start_row) as usize];
let line_width = line_layout.line.width;
content_origin.x + line_width + (em_width * INLINE_BLAME_PADDING_EM_WIDTHS)
};
@@ -2620,10 +2624,14 @@ impl EditorElement {
for (background_highlight_id, (_, background_ranges)) in
background_highlights.iter()
{
if (*background_highlight_id
== TypeId::of::<BufferSearchHighlights>()
&& scrollbar_settings.selections)
|| scrollbar_settings.symbols_selections
let is_search_highlights = *background_highlight_id
== TypeId::of::<BufferSearchHighlights>();
let is_symbol_occurrences = *background_highlight_id
== TypeId::of::<DocumentHighlightRead>()
|| *background_highlight_id
== TypeId::of::<DocumentHighlightWrite>();
if (is_search_highlights && scrollbar_settings.search_results)
|| (is_symbol_occurrences && scrollbar_settings.selected_symbol)
{
let marker_row_ranges =
background_ranges.into_iter().map(|range| {
@@ -2989,7 +2997,7 @@ fn render_inline_blame_entry(
h_flex()
.id("inline-blame")
.w_full()
.font_family(style.text.font().family)
.font(style.text.font().family)
.text_color(cx.theme().status().hint)
.line_height(style.text.line_height)
.child(Icon::new(IconName::FileGit).color(Color::Hint))
@@ -3093,7 +3101,9 @@ impl Render for BlameEntryTooltip {
.gap_4()
.child(
h_flex()
.gap_2()
.gap_x_2()
.overflow_x_hidden()
.flex_wrap()
.child(author)
.when_some(author_email, |this, author_email| {
this.child(
@@ -3193,7 +3203,7 @@ fn render_blame_entry(
h_flex()
.w_full()
.font_family(style.text.font().family)
.font(style.text.font().family)
.line_height(style.text.line_height)
.id(("blame", ix))
.children([
@@ -3693,11 +3703,13 @@ impl Element for EditorElement {
let mut inline_blame = None;
if let Some(newest_selection_head) = newest_selection_head {
if (start_row..end_row).contains(&newest_selection_head.row()) {
let display_row = newest_selection_head.row();
if (start_row..end_row).contains(&display_row) {
let line_layout = &line_layouts[(display_row - start_row) as usize];
inline_blame = self.layout_inline_blame(
start_row,
newest_selection_head.row(),
&line_layouts,
display_row,
&snapshot.display_snapshot,
line_layout,
em_width,
content_origin,
scroll_pixel_position,
@@ -3884,20 +3896,6 @@ impl Element for EditorElement {
)
.unwrap();
let focus_handle = self.editor.focus_handle(cx);
let focus_target_bounds = newest_selection_head.and_then(|cursor| {
if (start_row..end_row).contains(&cursor.row()) {
let cursor_y = line_height
* (cursor.row() as f32 - scroll_pixel_position.y / line_height);
let cursor_origin =
content_origin + gpui::point(-scroll_pixel_position.x, cursor_y);
Some(Bounds::new(cursor_origin, size(em_width, line_height)))
} else {
None
}
});
cx.set_focus_target(&focus_handle, focus_target_bounds);
EditorLayout {
mode: snapshot.mode,
position_map: Arc::new(PositionMap {
@@ -3950,6 +3948,7 @@ impl Element for EditorElement {
) {
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(

View File

@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{sync::Arc, time::Duration};
use anyhow::Result;
use collections::HashMap;
@@ -63,7 +63,8 @@ pub struct GitBlame {
task: Task<Result<()>>,
generated: bool,
user_triggered: bool,
_refresh_subscription: Subscription,
regenerate_on_edit_task: Task<Result<()>>,
_regenerate_subscriptions: Vec<Subscription>,
}
impl GitBlame {
@@ -81,7 +82,19 @@ impl GitBlame {
&(),
);
let refresh_subscription = cx.subscribe(&project, {
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
language::Event::DirtyChanged => {
if !buffer.read(cx).is_dirty() {
this.generate(cx);
}
}
language::Event::Edited => {
this.regenerate_on_edit(cx);
}
_ => {}
});
let project_subscription = cx.subscribe(&project, {
let buffer = buffer.clone();
move |this, _, event, cx| match event {
@@ -116,7 +129,8 @@ impl GitBlame {
commit_details: HashMap::default(),
task: Task::ready(Ok(())),
generated: false,
_refresh_subscription: refresh_subscription,
regenerate_on_edit_task: Task::ready(Ok(())),
_regenerate_subscriptions: vec![buffer_subscriptions, project_subscription],
};
this.generate(cx);
this
@@ -310,8 +324,22 @@ impl GitBlame {
})
});
}
fn regenerate_on_edit(&mut self, cx: &mut ModelContext<Self>) {
self.regenerate_on_edit_task = cx.spawn(|this, mut cx| async move {
cx.background_executor()
.timer(REGENERATE_ON_EDIT_DEBOUNCE_INTERVAL)
.await;
this.update(&mut cx, |this, cx| {
this.generate(cx);
})
});
}
}
const REGENERATE_ON_EDIT_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(2);
fn build_blame_entry_sum_tree(entries: Vec<BlameEntry>, max_row: u32) -> SumTree<GitBlameEntry> {
let mut current_row = 0;
let mut entries = SumTree::from_iter(

View File

@@ -4,15 +4,22 @@ use ui::prelude::*;
#[derive(IntoElement)]
pub struct ExtensionCard {
overridden_by_dev_extension: bool,
children: SmallVec<[AnyElement; 2]>,
}
impl ExtensionCard {
pub fn new() -> Self {
Self {
overridden_by_dev_extension: false,
children: SmallVec::new(),
}
}
pub fn overridden_by_dev_extension(mut self, overridden: bool) -> Self {
self.overridden_by_dev_extension = overridden;
self
}
}
impl ParentElement for ExtensionCard {
@@ -34,7 +41,24 @@ impl RenderOnce for ExtensionCard {
.border_1()
.border_color(cx.theme().colors().border)
.rounded_md()
.children(self.children),
.children(self.children)
.when(self.overridden_by_dev_extension, |card| {
card.child(
h_flex()
.absolute()
.top_0()
.left_0()
.occlude()
.size_full()
.items_center()
.justify_center()
.bg(theme::color_alpha(
cx.theme().colors().elevated_surface_background,
0.8,
))
.child(Label::new("Overridden by dev extension.")),
)
}),
)
}
}

View File

@@ -190,6 +190,15 @@ impl ExtensionsPage {
}
}
/// Returns whether a dev extension currently exists for the extension with the given ID.
fn dev_extension_exists(extension_id: &str, cx: &mut ViewContext<Self>) -> bool {
let extension_store = ExtensionStore::global(cx).read(cx);
extension_store
.dev_extensions()
.any(|dev_extension| dev_extension.id.as_ref() == extension_id)
}
fn extension_status(extension_id: &str, cx: &mut ViewContext<Self>) -> ExtensionStatus {
let extension_store = ExtensionStore::global(cx).read(cx);
@@ -417,13 +426,21 @@ impl ExtensionsPage {
) -> ExtensionCard {
let this = cx.view().clone();
let status = Self::extension_status(&extension.id, cx);
let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
let extension_id = extension.id.clone();
let (install_or_uninstall_button, upgrade_button) =
self.buttons_for_entry(extension, &status, cx);
self.buttons_for_entry(extension, &status, has_dev_extension, cx);
let version = extension.manifest.version.clone();
let repository_url = extension.manifest.repository.clone();
let installed_version = match status {
ExtensionStatus::Installed(installed_version) => Some(installed_version),
_ => None,
};
ExtensionCard::new()
.overridden_by_dev_extension(has_dev_extension)
.child(
h_flex()
.justify_between()
@@ -435,9 +452,14 @@ impl ExtensionsPage {
Headline::new(extension.manifest.name.clone())
.size(HeadlineSize::Medium),
)
.child(
Headline::new(format!("v{}", extension.manifest.version))
.size(HeadlineSize::XSmall),
.child(Headline::new(format!("v{version}")).size(HeadlineSize::XSmall))
.children(
installed_version
.filter(|installed_version| *installed_version != version)
.map(|installed_version| {
Headline::new(format!("(v{installed_version} installed)",))
.size(HeadlineSize::XSmall)
}),
),
)
.child(
@@ -577,16 +599,24 @@ impl ExtensionsPage {
&self,
extension: &ExtensionMetadata,
status: &ExtensionStatus,
has_dev_extension: bool,
cx: &mut ViewContext<Self>,
) -> (Button, Option<Button>) {
let is_compatible = extension::is_version_compatible(&extension);
let disabled = !is_compatible;
if has_dev_extension {
// If we have a dev extension for the given extension, just treat it as uninstalled.
// The button here is a placeholder, as it won't be interactable anyways.
return (
Button::new(SharedString::from(extension.id.clone()), "Install"),
None,
);
}
match status.clone() {
ExtensionStatus::NotInstalled => (
Button::new(SharedString::from(extension.id.clone()), "Install")
.disabled(disabled)
.on_click(cx.listener({
Button::new(SharedString::from(extension.id.clone()), "Install").on_click(
cx.listener({
let extension_id = extension.id.clone();
move |this, _, cx| {
this.telemetry
@@ -595,7 +625,8 @@ impl ExtensionsPage {
store.install_latest_extension(extension_id.clone(), cx)
});
}
})),
}),
),
None,
),
ExtensionStatus::Installing => (
@@ -626,7 +657,20 @@ impl ExtensionsPage {
} else {
Some(
Button::new(SharedString::from(extension.id.clone()), "Upgrade")
.disabled(disabled)
.when(!is_compatible, |upgrade_button| {
upgrade_button.disabled(true).tooltip({
let version = extension.manifest.version.clone();
move |cx| {
Tooltip::text(
format!(
"v{version} is not compatible with this version of Zed.",
),
cx,
)
}
})
})
.disabled(!is_compatible)
.on_click(cx.listener({
let extension_id = extension.id.clone();
let version = extension.manifest.version.clone();

View File

@@ -1375,10 +1375,6 @@ impl Interactivity {
None
};
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
cx.set_focus_target(focus_handle, Some(bounds));
}
let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
let result = f(&style, scroll_offset, hitbox, cx);
(result, element_state)
@@ -1981,6 +1977,9 @@ impl Interactivity {
if let Some(context) = self.key_context.clone() {
cx.set_key_context(context);
}
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
cx.set_focus_handle(focus_handle);
}
for listener in key_down_listeners {
cx.on_key_event(move |event: &KeyDownEvent, phase, cx| {

View File

@@ -8,12 +8,12 @@
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, ElementContext, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
StyleRefinement, Styled, WindowContext,
};
use collections::VecDeque;
use refineable::Refineable as _;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
use std::{cell::RefCell, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
use taffy::style::Overflow;
@@ -94,7 +94,6 @@ struct LayoutItemsResponse {
scroll_top: ListOffset,
available_item_space: Size<AvailableSpace>,
item_elements: VecDeque<AnyElement>,
focused_offscreen_element: Option<AnyElement>,
}
/// Frame state used by the [List] element after layout.
@@ -105,41 +104,8 @@ pub struct ListAfterLayoutState {
#[derive(Clone)]
enum ListItem {
Unmeasured {
focus_handle: Option<FocusHandle>,
},
Measured {
size: Size<Pixels>,
focus_handle: Option<FocusHandle>,
},
}
impl ListItem {
fn size(&self) -> Option<Size<Pixels>> {
if let ListItem::Measured { size, .. } = self {
Some(*size)
} else {
None
}
}
fn focus_handle(&self) -> Option<FocusHandle> {
match self {
ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
focus_handle.clone()
}
}
}
fn contains_focused(&self, cx: &WindowContext) -> bool {
match self {
ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
focus_handle
.as_ref()
.is_some_and(|handle| handle.contains_focused(cx))
}
}
}
Unrendered,
Rendered { size: Size<Pixels> },
}
#[derive(Clone, Debug, Default, PartialEq)]
@@ -148,7 +114,6 @@ struct ListItemSummary {
rendered_count: usize,
unrendered_count: usize,
height: Pixels,
has_focus_handles: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
@@ -166,45 +131,45 @@ struct Height(Pixels);
impl ListState {
/// Construct a new list state, for storage on a view.
///
/// The overdraw parameter controls how much extra space is rendered
/// above and below the visible area. Elements within this area will
/// be measured even though they are not visible. This can help ensure
/// that the list doesn't flicker or pop in when scrolling.
pub fn new<R>(
item_count: usize,
alignment: ListAlignment,
/// the overdraw parameter controls how much extra space is rendered
/// above and below the visible area. This can help ensure that the list
/// doesn't flicker or pop in when scrolling.
pub fn new<F>(
element_count: usize,
orientation: ListAlignment,
overdraw: Pixels,
render_item: R,
render_item: F,
) -> Self
where
R: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
{
let this = Self(Rc::new(RefCell::new(StateInner {
let mut items = SumTree::new();
items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
Self(Rc::new(RefCell::new(StateInner {
last_layout_bounds: None,
last_padding: None,
render_item: Box::new(render_item),
items: SumTree::new(),
items,
logical_scroll_top: None,
alignment,
alignment: orientation,
overdraw,
scroll_handler: None,
reset: false,
})));
this.splice(0..0, item_count);
this
})))
}
/// Reset this instantiation of the list state.
///
/// Note that this will cause scroll events to be dropped until the next paint.
pub fn reset(&self, element_count: usize) {
{
let state = &mut *self.0.borrow_mut();
state.reset = true;
state.logical_scroll_top = None;
}
let state = &mut *self.0.borrow_mut();
state.reset = true;
self.splice(0..element_count, element_count);
state.logical_scroll_top = None;
state.items = SumTree::new();
state
.items
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
}
/// The number of items in this list.
@@ -212,39 +177,11 @@ impl ListState {
self.0.borrow().items.summary().count
}
/// Inform the list state that the items in `old_range` have been replaced
/// Register with the list state that the items in `old_range` have been replaced
/// by `count` new items that must be recalculated.
pub fn splice(&self, old_range: Range<usize>, count: usize) {
self.splice_focusable(old_range, (0..count).into_iter().map(|_| None))
}
/// Register with the list state that the items in `old_range` have been replaced
/// by new items. As opposed to [`splice`], this method allows an iterator of optional focus handles
/// to be supplied to properly integrate with items in the list that can be focused. If a focused item
/// is scrolled out of view, the list will continue to render it to allow keyboard interaction.
pub fn splice_focusable(
&self,
old_range: Range<usize>,
focus_handles: impl IntoIterator<Item = Option<FocusHandle>>,
) {
let state = &mut *self.0.borrow_mut();
let mut old_items = state.items.cursor::<Count>();
let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
let mut spliced_count = 0;
new_items.extend(
focus_handles.into_iter().map(|focus_handle| {
spliced_count += 1;
ListItem::Unmeasured { focus_handle }
}),
&(),
);
new_items.append(old_items.suffix(&()), &());
drop(old_items);
state.items = new_items;
if let Some(ListOffset {
item_ix,
offset_in_item,
@@ -254,9 +191,18 @@ impl ListState {
*item_ix = old_range.start;
*offset_in_item = px(0.);
} else if old_range.end <= *item_ix {
*item_ix = *item_ix - (old_range.end - old_range.start) + spliced_count;
*item_ix = *item_ix - (old_range.end - old_range.start) + count;
}
}
let mut old_heights = state.items.cursor::<Count>();
let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
new_heights.append(old_heights.suffix(&()), &());
drop(old_heights);
state.items = new_heights;
}
/// Set a handler that will be called when the list is scrolled.
@@ -284,57 +230,6 @@ impl ListState {
state.logical_scroll_top = Some(scroll_top);
}
pub fn scroll_to_focused(&self) {
// state.requested_autoscroll = Some();
}
/// Scroll the list to the given item index and top/bottom offset.
/// If the given position is already visibile, this method won't scroll.
pub fn scroll_to_fit(
&self,
mut item_ix: usize,
mut top_offset_in_item: Pixels,
mut bottom_offset_in_item: Pixels,
) {
let state = &mut *self.0.borrow_mut();
let Some(bounds) = state.last_layout_bounds else {
return;
};
let item_count = state.items.summary().count;
if item_ix >= item_count {
item_ix = item_count;
top_offset_in_item = Pixels::ZERO;
bottom_offset_in_item = Pixels::ZERO;
}
let logical_scroll_top = state.logical_scroll_top();
let mut cursor = state.items.cursor::<(Count, Height)>();
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
let scroll_top = cursor.start().1 .0 + logical_scroll_top.offset_in_item;
let scroll_bottom = scroll_top + bounds.size.height;
cursor.seek(&Count(item_ix), Bias::Right, &());
let item_scroll_top = cursor.start().1 .0;
let desired_scroll_top = item_scroll_top + top_offset_in_item;
let desired_scroll_bottom = item_scroll_top + bottom_offset_in_item;
if desired_scroll_top < scroll_top {
state.logical_scroll_top = Some(ListOffset {
item_ix,
offset_in_item: desired_scroll_top - item_scroll_top,
});
} else if desired_scroll_bottom > scroll_bottom {
state.logical_scroll_top = Some(ListOffset {
item_ix,
offset_in_item: cmp::max(
Pixels::ZERO,
desired_scroll_bottom - item_scroll_top - bounds.size.height,
),
});
}
}
/// Scroll the list to the given item, such that the item is fully visible.
pub fn scroll_to_reveal_item(&self, ix: usize) {
let state = &mut *self.0.borrow_mut();
@@ -384,7 +279,7 @@ impl ListState {
let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
cursor.seek_forward(&Count(ix), Bias::Right, &());
if let Some(&ListItem::Measured { size, .. }) = cursor.item() {
if let Some(&ListItem::Rendered { size }) = cursor.item() {
let &(Count(count), Height(top)) = cursor.start();
if count == ix {
let top = bounds.top() + top - scroll_top;
@@ -488,7 +383,6 @@ impl StateInner {
let mut rendered_height = padding.top;
let mut max_item_width = px(0.);
let mut scroll_top = self.logical_scroll_top();
let mut rendered_focused_item = false;
let available_item_space = size(
available_width.map_or(AvailableSpace::MinContent, |width| {
@@ -507,8 +401,12 @@ impl StateInner {
break;
}
// Use the previously cached height and focus handle if available
let mut size = item.size();
// Use the previously cached height if available
let mut size = if let ListItem::Rendered { size } = item {
Some(*size)
} else {
None
};
// If we're within the visible area or the height wasn't cached, render and measure the item's element
if visible_height < available_height || size.is_none() {
@@ -517,19 +415,13 @@ impl StateInner {
size = Some(element_size);
if visible_height < available_height {
item_elements.push_back(element);
if item.contains_focused(cx) {
rendered_focused_item = true;
}
}
}
let size = size.unwrap();
rendered_height += size.height;
max_item_width = max_item_width.max(size.width);
measured_items.push_back(ListItem::Measured {
size,
focus_handle: item.focus_handle(),
});
measured_items.push_back(ListItem::Rendered { size });
}
rendered_height += padding.bottom;
@@ -541,19 +433,13 @@ impl StateInner {
if rendered_height - scroll_top.offset_in_item < available_height {
while rendered_height < available_height {
cursor.prev(&());
if let Some(item) = cursor.item() {
if cursor.item().is_some() {
let mut element = (self.render_item)(cursor.start().0, cx);
let element_size = element.measure(available_item_space, cx);
let focus_handle = item.focus_handle();
rendered_height += element_size.height;
measured_items.push_front(ListItem::Measured {
size: element_size,
focus_handle,
});
item_elements.push_front(element);
if item.contains_focused(cx) {
rendered_focused_item = true;
}
measured_items.push_front(ListItem::Rendered { size: element_size });
item_elements.push_front(element)
} else {
break;
}
@@ -584,7 +470,7 @@ impl StateInner {
while leading_overdraw < self.overdraw {
cursor.prev(&());
if let Some(item) = cursor.item() {
let size = if let ListItem::Measured { size, .. } = item {
let size = if let ListItem::Rendered { size } = item {
*size
} else {
let mut element = (self.render_item)(cursor.start().0, cx);
@@ -592,10 +478,7 @@ impl StateInner {
};
leading_overdraw += size.height;
measured_items.push_front(ListItem::Measured {
size,
focus_handle: item.focus_handle(),
});
measured_items.push_front(ListItem::Rendered { size });
} else {
break;
}
@@ -607,36 +490,14 @@ impl StateInner {
new_items.extend(measured_items, &());
cursor.seek(&Count(measured_range.end), Bias::Right, &());
new_items.append(cursor.suffix(&()), &());
self.items = new_items;
// If the none of the visible items are focused, check if an off-screen item is focused
// and include it to be rendered after the visible items so keyboard interaction continues
// to work for it.
let mut focused_offscreen_element = None;
if self.scroll_to_focused_item {
self.scroll_to_focused_item = false;
// Are the focused item and its focus target bounds visible?
// If not, update the scroll position and call this method recursively?
} else if !rendered_focused_item {
let mut cursor = self
.items
.filter::<_, Count>(|summary| summary.has_focus_handles);
cursor.next(&());
while let Some(item) = cursor.item() {
if item.contains_focused(cx) {
focused_offscreen_element = Some((self.render_item)(cursor.start().0, cx));
break;
}
cursor.next(&());
}
}
self.items = new_items;
LayoutItemsResponse {
max_item_width,
scroll_top,
available_item_space,
item_elements,
focused_offscreen_element,
}
}
}
@@ -644,8 +505,8 @@ impl StateInner {
impl std::fmt::Debug for ListItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unmeasured { .. } => write!(f, "Unrendered"),
Self::Measured { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
Self::Unrendered => write!(f, "Unrendered"),
Self::Rendered { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
}
}
}
@@ -746,14 +607,10 @@ impl Element for List {
if state.last_layout_bounds.map_or(true, |last_bounds| {
last_bounds.size.width != bounds.size.width
}) {
let new_items = SumTree::from_iter(
state.items.iter().map(|item| ListItem::Unmeasured {
focus_handle: item.focus_handle(),
}),
state.items = SumTree::from_iter(
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
&(),
);
state.items = new_items;
)
}
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
@@ -762,7 +619,7 @@ impl Element for List {
// Only paint the visible items, if there is actually any space for them (taking padding into account)
if bounds.size.height > padding.top + padding.bottom {
// Layout the visible items followed by the offscreen focused item if it is present
// Paint the visible items
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
item_origin.y -= layout_response.scroll_top.offset_in_item;
@@ -771,9 +628,6 @@ impl Element for List {
item_element.layout(item_origin, layout_response.available_item_space, cx);
item_origin.y += item_size.height;
}
if let Some(focused_element) = layout_response.focused_offscreen_element.as_mut() {
focused_element.layout(item_origin, layout_response.available_item_space, cx);
}
});
}
@@ -796,9 +650,6 @@ impl Element for List {
for item in &mut after_layout.layout.item_elements {
item.paint(cx);
}
if let Some(focused_element) = after_layout.layout.focused_offscreen_element.as_mut() {
focused_element.paint(cx);
}
});
let list_state = self.state.clone();
@@ -837,21 +688,17 @@ impl sum_tree::Item for ListItem {
fn summary(&self) -> Self::Summary {
match self {
ListItem::Unmeasured { focus_handle } => ListItemSummary {
ListItem::Unrendered => ListItemSummary {
count: 1,
rendered_count: 0,
unrendered_count: 1,
height: px(0.),
has_focus_handles: focus_handle.is_some(),
},
ListItem::Measured {
size, focus_handle, ..
} => ListItemSummary {
ListItem::Rendered { size } => ListItemSummary {
count: 1,
rendered_count: 1,
unrendered_count: 0,
height: size.height,
has_focus_handles: focus_handle.is_some(),
},
}
}
@@ -865,7 +712,6 @@ impl sum_tree::Summary for ListItemSummary {
self.rendered_count += summary.rendered_count;
self.unrendered_count += summary.unrendered_count;
self.height += summary.height;
self.has_focus_handles |= summary.has_focus_handles;
}
}

View File

@@ -1257,6 +1257,7 @@ where
/// origin: Point { x: 15.0, y: 15.0 },
/// size: Size { width: 15.0, height: 30.0 },
/// });
/// ```
pub fn map<U>(&self, f: impl Fn(T) -> U) -> Bounds<U>
where
U: Clone + Default + Debug,
@@ -1283,6 +1284,7 @@ where
/// origin: Point { x: 15.0, y: 15.0 },
/// size: Size { width: 10.0, height: 20.0 },
/// });
/// ```
pub fn map_origin(self, f: impl Fn(Point<T>) -> Point<T>) -> Bounds<T> {
Bounds {
origin: f(self.origin),

View File

@@ -50,9 +50,9 @@
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
///
use crate::{
Action, ActionRegistry, Bounds, DispatchPhase, ElementContext, EntityId, FocusTargetId,
KeyBinding, KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher,
ModifiersChangedEvent, Pixels, WindowContext,
Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent,
WindowContext,
};
use collections::FxHashMap;
use smallvec::SmallVec;
@@ -72,7 +72,7 @@ pub(crate) struct DispatchTree {
pub(crate) context_stack: Vec<KeyContext>,
view_stack: Vec<EntityId>,
nodes: Vec<DispatchNode>,
focusable_node_ids: FxHashMap<FocusTargetId, DispatchNodeId>,
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
keystroke_matchers: FxHashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
keymap: Rc<RefCell<Keymap>>,
@@ -85,17 +85,11 @@ pub(crate) struct DispatchNode {
pub action_listeners: Vec<DispatchActionListener>,
pub modifiers_changed_listeners: Vec<ModifiersChangedListener>,
pub context: Option<KeyContext>,
focus_target: Option<FocusTarget>,
pub focus_id: Option<FocusId>,
view_id: Option<EntityId>,
parent: Option<DispatchNodeId>,
}
#[derive(Clone)]
struct FocusTarget {
id: FocusTargetId,
bounds: Option<Bounds<Pixels>>,
}
pub(crate) struct ReusedSubtree {
old_range: Range<usize>,
new_range: Range<usize>,
@@ -205,36 +199,12 @@ impl DispatchTree {
self.context_stack.push(context);
}
pub fn set_focus_target(&mut self, focus_id: FocusTargetId, bounds: Option<Bounds<Pixels>>) {
pub fn set_focus_id(&mut self, focus_id: FocusId) {
let node_id = *self.node_stack.last().unwrap();
self.nodes[node_id.0].focus_target = Some(FocusTarget {
id: focus_id,
bounds,
});
self.nodes[node_id.0].focus_id = Some(focus_id);
self.focusable_node_ids.insert(focus_id, node_id);
}
pub fn focus_target_bounds(&self, focus_id: FocusTargetId) -> Option<Bounds<Pixels>> {
let active_node_id = self.active_node_id()?;
let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied()?;
let focus_target_bounds = self.node(current_node_id).focus_target.as_ref()?.bounds?;
loop {
// Only return the bounds if the focused node is within the active subtree.
if current_node_id == active_node_id {
return Some(focus_target_bounds);
}
if let Some(parent) = self.node(current_node_id).parent {
current_node_id = parent;
} else {
break;
}
}
None
}
pub fn parent_view_id(&mut self) -> Option<EntityId> {
self.view_stack.last().copied()
}
@@ -264,8 +234,8 @@ impl DispatchTree {
if let Some(context) = source.context.clone() {
self.set_key_context(context);
}
if let Some(focus) = source.focus_target.clone() {
self.set_focus_target(focus.id, focus.bounds);
if let Some(focus_id) = source.focus_id {
self.set_focus_id(focus_id);
}
if let Some(view_id) = source.view_id {
self.set_view_id(view_id);
@@ -319,11 +289,7 @@ impl DispatchTree {
/// Preserve keystroke matchers from previous frames to support multi-stroke
/// bindings across multiple frames.
pub fn preserve_pending_keystrokes(
&mut self,
old_tree: &mut Self,
focus_id: Option<FocusTargetId>,
) {
pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
let dispatch_path = self.dispatch_path(node_id);
@@ -367,7 +333,7 @@ impl DispatchTree {
});
}
pub fn focus_contains(&self, parent: FocusTargetId, child: FocusTargetId) -> bool {
pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool {
if parent == child {
return true;
}
@@ -507,13 +473,13 @@ impl DispatchTree {
dispatch_path
}
pub fn focus_path(&self, focus_id: FocusTargetId) -> SmallVec<[FocusTargetId; 8]> {
let mut focus_path: SmallVec<[FocusTargetId; 8]> = SmallVec::new();
pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> {
let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new();
let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied();
while let Some(node_id) = current_node_id {
let node = self.node(node_id);
if let Some(focus) = node.focus_target.as_ref() {
focus_path.push(focus.id);
if let Some(focus_id) = node.focus_id {
focus_path.push(focus_id);
}
current_node_id = node.parent;
}
@@ -544,7 +510,7 @@ impl DispatchTree {
&mut self.nodes[active_node_id.0]
}
pub fn focusable_node_id(&self, target: FocusTargetId) -> Option<DispatchNodeId> {
pub fn focusable_node_id(&self, target: FocusId) -> Option<DispatchNodeId> {
self.focusable_node_ids.get(&target).copied()
}

View File

@@ -95,6 +95,7 @@ impl Globals {
pub(crate) struct WaylandClientState {
globals: Globals,
wl_pointer: Option<wl_pointer::WlPointer>,
// Surface to Window mapping
windows: HashMap<ObjectId, WaylandWindowStatePtr>,
// Output to scale mapping
@@ -240,6 +241,7 @@ impl WaylandClient {
let mut state = Rc::new(RefCell::new(WaylandClientState {
globals,
wl_pointer: None,
output_scales: outputs,
windows: HashMap::default(),
common,
@@ -343,7 +345,15 @@ impl LinuxClient for WaylandClient {
}
.to_string();
self.0.borrow_mut().cursor_icon_name = cursor_icon_name;
let mut state = self.0.borrow_mut();
state.cursor_icon_name = cursor_icon_name.clone();
if state.mouse_focused_window.is_some() {
let wl_pointer = state
.wl_pointer
.clone()
.expect("window is focused by pointer");
state.cursor.set_icon(&wl_pointer, &cursor_icon_name);
}
}
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
@@ -403,6 +413,7 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
version,
} => match &interface[..] {
"wl_seat" => {
state.wl_pointer = None;
registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
}
"wl_output" => {
@@ -582,7 +593,9 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
seat.get_keyboard(qh, ());
}
if capabilities.contains(wl_seat::Capability::Pointer) {
seat.get_pointer(qh, ());
let client = state.get_client();
let mut state = client.borrow_mut();
state.wl_pointer = Some(seat.get_pointer(qh, ()));
}
}
}
@@ -789,10 +802,11 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if let Some(window) = get_window(&mut state, &surface.id()) {
state.enter_token = Some(());
state.mouse_focused_window = Some(window.clone());
state.cursor.mark_dirty();
state.cursor.set_serial_id(serial);
state
.cursor
.set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
.set_icon(&wl_pointer, cursor_icon_name.as_str());
drop(state);
window.set_focused(true);
}
@@ -823,9 +837,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
return;
}
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
state
.cursor
.set_icon(&wl_pointer, Some(cursor_icon_name.as_str()));
if let Some(window) = state.mouse_focused_window.clone() {
let input = PlatformInput::MouseMove(MouseMoveEvent {

View File

@@ -8,7 +8,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme};
pub(crate) struct Cursor {
theme: Option<CursorTheme>,
current_icon_name: String,
current_icon_name: Option<String>,
surface: WlSurface,
serial_id: u32,
}
@@ -24,19 +24,29 @@ impl Cursor {
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
Self {
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
current_icon_name: "default".to_string(),
current_icon_name: None,
surface: globals.compositor.create_surface(&globals.qh, ()),
serial_id: 0,
}
}
pub fn mark_dirty(&mut self) {
self.current_icon_name = None;
}
pub fn set_serial_id(&mut self, serial_id: u32) {
self.serial_id = serial_id;
}
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: Option<&str>) {
let mut cursor_icon_name = cursor_icon_name.unwrap_or("default");
if self.current_icon_name != cursor_icon_name {
pub fn set_icon(&mut self, wl_pointer: &WlPointer, mut cursor_icon_name: &str) {
let need_update = self
.current_icon_name
.as_ref()
.map_or(true, |current_icon_name| {
current_icon_name != cursor_icon_name
});
if need_update {
if let Some(theme) = &mut self.theme {
let mut buffer: Option<&CursorImageBuffer>;
@@ -68,7 +78,7 @@ impl Cursor {
self.surface.damage(0, 0, width as i32, height as i32);
self.surface.commit();
self.current_icon_name = cursor_icon_name.to_string();
self.current_icon_name = Some(cursor_icon_name.to_string());
}
} else {
log::warn!("Linux: Wayland: Unable to load cursor themes");

View File

@@ -52,7 +52,6 @@ pub(crate) struct WindowsWindowInner {
pub(crate) handle: AnyWindowHandle,
hide_title_bar: bool,
display: RefCell<Rc<WindowsDisplay>>,
last_ime_input: RefCell<Option<String>>,
click_state: RefCell<ClickState>,
fullscreen: Cell<Option<StyleAndBounds>>,
}
@@ -114,7 +113,6 @@ impl WindowsWindowInner {
let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
let callbacks = RefCell::new(Callbacks::default());
let display = RefCell::new(display);
let last_ime_input = RefCell::new(None);
let click_state = RefCell::new(ClickState::new());
let fullscreen = Cell::new(None);
Self {
@@ -129,7 +127,6 @@ impl WindowsWindowInner {
handle,
hide_title_bar,
display,
last_ime_input,
click_state,
fullscreen,
}
@@ -816,6 +813,7 @@ impl WindowsWindowInner {
}
fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
let mut ime_input = None;
if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
let Some((string, string_len)) = self.parse_ime_compostion_string() else {
return None;
@@ -829,10 +827,10 @@ impl WindowsWindowInner {
Some(0..string_len),
);
self.input_handler.set(Some(input_handler));
*self.last_ime_input.borrow_mut() = Some(string);
ime_input = Some(string);
}
if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
let Some(ref comp_string) = *self.last_ime_input.borrow() else {
let Some(ref comp_string) = ime_input else {
return None;
};
let caret_pos = self.retrieve_composition_cursor_position();
@@ -863,7 +861,6 @@ impl WindowsWindowInner {
};
input_handler.replace_text_in_range(None, &ime_char);
self.input_handler.set(Some(input_handler));
*self.last_ime_input.borrow_mut() = None;
self.invalidate_client_area();
Some(0)
}

View File

@@ -1,7 +1,7 @@
use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla,
JustifyContent, Length, Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
DefiniteLength, Fill, FlexDirection, FlexWrap, FontStyle, FontWeight, Hsla, JustifyContent,
Length, Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
};
use crate::{BoxShadow, TextStyleRefinement};
use smallvec::{smallvec, SmallVec};
@@ -771,32 +771,14 @@ pub trait Styled: Sized {
self
}
/// Change the font family on this element and its children.
fn font_family(mut self, family_name: impl Into<SharedString>) -> Self {
/// Change the font on this element and its children.
fn font(mut self, family_name: impl Into<SharedString>) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.font_family = Some(family_name.into());
self
}
/// Change the font of this element and its children.
fn font(mut self, font: Font) -> Self {
let Font {
family,
features,
weight,
style,
} = font;
let text_style = self.text_style().get_or_insert_with(Default::default);
text_style.font_family = Some(family);
text_style.font_features = Some(features);
text_style.font_weight = Some(weight);
text_style.font_style = Some(style);
self
}
/// Set the line height on this element and its children.
fn line_height(mut self, line_height: impl Into<DefiniteLength>) -> Self {
self.text_style()

View File

@@ -76,20 +76,20 @@ type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
struct FocusEvent {
previous_focus_path: SmallVec<[FocusTargetId; 8]>,
current_focus_path: SmallVec<[FocusTargetId; 8]>,
previous_focus_path: SmallVec<[FocusId; 8]>,
current_focus_path: SmallVec<[FocusId; 8]>,
}
slotmap::new_key_type! {
/// A globally unique identifier for a focusable element.
pub struct FocusTargetId;
pub struct FocusId;
}
thread_local! {
pub(crate) static ELEMENT_ARENA: RefCell<Arena> = RefCell::new(Arena::new(8 * 1024 * 1024));
}
impl FocusTargetId {
impl FocusId {
/// Obtains whether the element associated with this handle is currently focused.
pub fn is_focused(&self, cx: &WindowContext) -> bool {
cx.window.focus == Some(*self)
@@ -120,8 +120,8 @@ impl FocusTargetId {
/// A handle which can be used to track and manipulate the focused element in a window.
pub struct FocusHandle {
pub(crate) id: FocusTargetId,
handles: Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
pub(crate) id: FocusId,
handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
}
impl std::fmt::Debug for FocusHandle {
@@ -131,7 +131,7 @@ impl std::fmt::Debug for FocusHandle {
}
impl FocusHandle {
pub(crate) fn new(handles: &Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>) -> Self {
pub(crate) fn new(handles: &Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>) -> Self {
let id = handles.write().insert(AtomicUsize::new(1));
Self {
id,
@@ -140,8 +140,8 @@ impl FocusHandle {
}
pub(crate) fn for_id(
id: FocusTargetId,
handles: &Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
id: FocusId,
handles: &Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
) -> Option<Self> {
let lock = handles.read();
let ref_count = lock.get(id)?;
@@ -219,8 +219,8 @@ impl Drop for FocusHandle {
/// A weak reference to a focus handle.
#[derive(Clone, Debug)]
pub struct WeakFocusHandle {
pub(crate) id: FocusTargetId,
handles: Weak<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
pub(crate) id: FocusId,
handles: Weak<RwLock<SlotMap<FocusId, AtomicUsize>>>,
}
impl WeakFocusHandle {
@@ -291,7 +291,7 @@ pub struct Window {
pub(crate) tooltip_bounds: Option<TooltipBounds>,
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
pub(crate) dirty_views: FxHashSet<EntityId>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusTargetId, AtomicUsize>>>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
default_prevented: bool,
@@ -309,7 +309,7 @@ pub struct Window {
pub(crate) refreshing: bool,
pub(crate) draw_phase: DrawPhase,
activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) focus: Option<FocusTargetId>,
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
pending_input: Option<PendingInput>,
prompt: Option<RenderablePromptHandle>,
@@ -327,7 +327,7 @@ pub(crate) enum DrawPhase {
struct PendingInput {
keystrokes: SmallVec<[Keystroke; 1]>,
bindings: SmallVec<[KeyBinding; 1]>,
focus: Option<FocusTargetId>,
focus: Option<FocusId>,
timer: Option<Task<()>>,
}
@@ -2808,7 +2808,7 @@ pub enum ElementId {
/// A string based ID.
Name(SharedString),
/// An ID that's equated with a focus handle.
FocusHandle(FocusTargetId),
FocusHandle(FocusId),
/// A combination of a name and an integer.
NamedInteger(SharedString, usize),
}

View File

@@ -34,9 +34,9 @@ use crate::{
hash, point, prelude::*, px, size, AnyElement, AnyTooltip, AppContext, Asset, AvailableSpace,
Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId,
DispatchPhase, DispatchTree, DrawPhase, ElementId, ElementStateBox, EntityId, FocusHandle,
FocusTargetId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero,
KeyContext, KeyEvent, LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite,
MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext,
KeyEvent, LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite, MouseEvent,
PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size,
StrikethroughStyle, Style, Task, TextStyleRefinement, TransformationMatrix, Underline,
UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS,
@@ -126,7 +126,7 @@ pub(crate) struct DeferredDraw {
}
pub(crate) struct Frame {
pub(crate) focus: Option<FocusTargetId>,
pub(crate) focus: Option<FocusId>,
pub(crate) window_active: bool,
pub(crate) element_states: FxHashMap<(GlobalElementId, TypeId), ElementStateBox>,
accessed_element_states: Vec<(GlobalElementId, TypeId)>,
@@ -214,7 +214,7 @@ impl Frame {
hit_test
}
pub(crate) fn focus_path(&self) -> SmallVec<[FocusTargetId; 8]> {
pub(crate) fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
self.focus
.map(|focus_id| self.dispatch_tree.focus_path(focus_id))
.unwrap_or_default()
@@ -1426,16 +1426,11 @@ impl<'a> ElementContext<'a> {
/// Sets the focus handle for the current element. This handle will be used to manage focus state
/// and keyboard event dispatch for the element.
pub fn set_focus_target(&mut self, focus_handle: &FocusHandle, bounds: Option<Bounds<Pixels>>) {
debug_assert_eq!(
self.window.draw_phase,
DrawPhase::Layout,
"you must set the focus target during after_layout"
);
pub fn set_focus_handle(&mut self, focus_handle: &FocusHandle) {
self.window
.next_frame
.dispatch_tree
.set_focus_target(focus_handle.id, bounds);
.set_focus_id(focus_handle.id);
}
/// Sets the view id for the current element, which will be used to manage view caching.
@@ -1448,15 +1443,6 @@ impl<'a> ElementContext<'a> {
self.window.next_frame.dispatch_tree.parent_view_id()
}
/// Returns the bounds for the focus target if it is a descendant of the current element.
pub fn focus_target_bounds(&self) -> Option<Bounds<Pixels>> {
let focus_target_id = self.window.focus?;
self.window
.next_frame
.dispatch_tree
.focus_target_bounds(focus_target_id)
}
/// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions. This handler will be active for the upcoming frame until the following frame is

View File

@@ -5,7 +5,7 @@ use std::{ops::Range, path::PathBuf};
use crate::{HighlightId, Language, LanguageRegistry};
use gpui::{px, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, UnderlineStyle};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd};
/// Parsed Markdown content.
#[derive(Debug, Clone, Default)]
@@ -165,7 +165,10 @@ pub async fn parse_markdown_block(
let mut current_language = None;
let mut list_stack = Vec::new();
for event in Parser::new_ext(markdown, Options::all()) {
let mut options = pulldown_cmark::Options::all();
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
for event in Parser::new_ext(markdown, options) {
let prev_len = text.len();
match event {
Event::Text(t) => {
@@ -249,7 +252,7 @@ pub async fn parse_markdown_block(
new_paragraph(text, &mut list_stack);
current_language = if let CodeBlockKind::Fenced(language) = kind {
language_registry
.language_for_name(language.as_ref())
.language_for_name_or_extension(language.as_ref())
.await
.ok()
} else {
@@ -357,3 +360,35 @@ pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)
text.push_str(" ");
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_dividers() {
let input = r#"
### instance-method `format`
---
→ `void`
Parameters:
- `const int &`
- `const std::tm &`
- `int & dest`
---
```cpp
// In my_formatter_flag
public: void format(const int &, const std::tm &, int &dest)
```
"#;
let mut options = pulldown_cmark::Options::all();
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
let parser = pulldown_cmark::Parser::new_ext(input, options);
for event in parser.into_iter() {
println!("{:?}", event);
}
}
}

View File

@@ -4,6 +4,8 @@ use futures::StreamExt;
use gpui::AsyncAppContext;
pub use language::*;
use lsp::LanguageServerBinary;
use project::project_settings::{BinarySettings, ProjectSettings};
use settings::Settings;
use smol::fs::{self, File};
use std::{any::Any, env::consts, path::PathBuf, sync::Arc};
use util::{
@@ -14,10 +16,51 @@ use util::{
pub struct CLspAdapter;
impl CLspAdapter {
const SERVER_NAME: &'static str = "clangd";
}
#[async_trait(?Send)]
impl super::LspAdapter for CLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("clangd".into())
LanguageServerName(Self::SERVER_NAME.into())
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
cx: &AsyncAppContext,
) -> Option<LanguageServerBinary> {
let configured_binary = cx.update(|cx| {
ProjectSettings::get_global(cx)
.lsp
.get(Self::SERVER_NAME)
.and_then(|s| s.binary.clone())
});
if let Ok(Some(BinarySettings {
path: Some(path),
arguments,
})) = configured_binary
{
Some(LanguageServerBinary {
path: path.into(),
arguments: arguments
.unwrap_or_default()
.iter()
.map(|arg| arg.into())
.collect(),
env: None,
})
} else {
let env = delegate.shell_env().await;
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
Some(LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
})
}
}
async fn fetch_latest_server_version(
@@ -45,20 +88,6 @@ impl super::LspAdapter for CLspAdapter {
Ok(Box::new(version) as Box<_>)
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: &AsyncAppContext,
) -> Option<LanguageServerBinary> {
let env = delegate.shell_env().await;
let path = delegate.which("clangd".as_ref()).await?;
Some(LanguageServerBinary {
path,
arguments: vec![],
env: Some(env),
})
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,

View File

@@ -88,7 +88,7 @@ impl super::LspAdapter for GoLspAdapter {
})
} else {
let env = delegate.shell_env().await;
let path = delegate.which("gopls".as_ref()).await?;
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
Some(LanguageServerBinary {
path,
arguments: server_binary_arguments(),

View File

@@ -20,5 +20,28 @@
(info_string
(language) @text.literal))
; hack to deal with incorrect grammar parsing
(atx_heading
(block_quote_marker) @punctuation.block_quote_marker
)
; hack to deal with incorrect grammar parsing
(paragraph
(block_quote_marker) @punctuation.block_quote_marker
)
; hack to deal with incorrect grammar parsing
(list_item
(block_quote_marker) @punctuation.block_quote_marker
)
(block_quote
(block_quote_marker) @punctuation.block_quote_marker
)
(block_quote
(paragraph) @text.block_quote
)
(link_destination) @link_uri
(link_text) @link_text

View File

@@ -138,8 +138,16 @@ struct AnyResponse<'a> {
struct Response<T> {
jsonrpc: &'static str,
id: RequestId,
result: Option<T>,
error: Option<Error>,
#[serde(flatten)]
value: LspResult<T>,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum LspResult<T> {
#[serde(rename = "result")]
Ok(Option<T>),
Error(Option<Error>),
}
/// Language server protocol RPC notification message.
@@ -867,16 +875,14 @@ impl LanguageServer {
Ok(result) => Response {
jsonrpc: JSON_RPC_VERSION,
id,
result: Some(result),
error: None,
value: LspResult::Ok(Some(result)),
},
Err(error) => Response {
jsonrpc: JSON_RPC_VERSION,
id,
result: None,
error: Some(Error {
value: LspResult::Error(Some(Error {
message: error.to_string(),
}),
})),
},
};
if let Some(response) =
@@ -1503,4 +1509,27 @@ mod tests {
let expected_id = RequestId::Int(2);
assert_eq!(notification.id, Some(expected_id));
}
#[test]
fn test_serialize_has_no_nulls() {
// Ensure we're not setting both result and error variants. (ticket #10595)
let no_tag = Response::<u32> {
jsonrpc: "",
id: RequestId::Int(0),
value: LspResult::Ok(None),
};
assert_eq!(
serde_json::to_string(&no_tag).unwrap(),
"{\"jsonrpc\":\"\",\"id\":0,\"result\":null}"
);
let no_tag = Response::<u32> {
jsonrpc: "",
id: RequestId::Int(0),
value: LspResult::Error(None),
};
assert_eq!(
serde_json::to_string(&no_tag).unwrap(),
"{\"jsonrpc\":\"\",\"id\":0,\"error\":null}"
);
}
}

View File

@@ -71,7 +71,8 @@ pub struct InlineBlameSettings {
/// Whether or not to show git blame data inline in
/// the currently focused line.
///
/// Default: false
/// Default: true
#[serde(default = "true_value")]
pub enabled: bool,
/// Whether to only show the inline blame information
/// after a delay once the cursor stops moving.
@@ -80,6 +81,10 @@ pub struct InlineBlameSettings {
pub delay_ms: Option<u64>,
}
const fn true_value() -> bool {
true
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct BinarySettings {
pub path: Option<String>,

View File

@@ -14,6 +14,7 @@ use serde_json::json;
#[cfg(not(windows))]
use std::os;
use std::task::Poll;
use task::{TaskContext, TaskSource, TaskTemplate, TaskTemplates};
use unindent::Unindent as _;
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
use worktree::WorktreeModelHandle as _;
@@ -125,8 +126,19 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await;
let worktree = project.update(cx, |project, _| project.worktrees().next().unwrap());
let task_context = TaskContext::default();
cx.executor().run_until_parked();
let workree_id = cx.update(|cx| {
project.update(cx, |project, cx| {
project.worktrees().next().unwrap().read(cx).id()
})
});
let global_task_source_kind = TaskSourceKind::Worktree {
id: workree_id,
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
id_base: "local_tasks_for_worktree",
};
cx.update(|cx| {
let tree = worktree.read(cx);
@@ -154,17 +166,29 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
assert_eq!(settings_a.tab_size.get(), 8);
assert_eq!(settings_b.tab_size.get(), 2);
let workree_id = project.update(cx, |project, cx| {
project.worktrees().next().unwrap().read(cx).id()
});
let all_tasks = project
.update(cx, |project, cx| {
project
.task_inventory()
.update(cx, |inventory, cx| inventory.list_tasks(None, None, cx))
project.task_inventory().update(cx, |inventory, cx| {
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
old.extend(new);
old
})
})
.into_iter()
.map(|(source_kind, task)| (source_kind, task.label))
.map(|(source_kind, task)| {
let resolved = task.resolved.unwrap();
(
source_kind,
task.resolved_label,
resolved.args,
resolved.env,
)
})
.collect::<Vec<_>>();
assert_eq!(
all_tasks,
@@ -172,24 +196,141 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
(
TaskSourceKind::Worktree {
id: workree_id,
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
id_base: "local_tasks_for_worktree",
},
"cargo check".to_string()
"cargo check".to_string(),
vec!["check".to_string()],
HashMap::default(),
),
(
global_task_source_kind.clone(),
"cargo check".to_string(),
vec!["check".to_string(), "--all".to_string()],
HashMap::default(),
),
]
);
});
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,
);
old.extend(new);
let (_, resolved_task) = old
.into_iter()
.find(|(source_kind, _)| source_kind == &global_task_source_kind)
.expect("should have one global task");
inventory.task_scheduled(global_task_source_kind.clone(), resolved_task);
})
});
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,
);
let (mut old, new) = inventory.used_and_current_resolved_tasks(
None,
Some(workree_id),
&task_context,
cx,
);
old.extend(new);
old
})
})
.into_iter()
.map(|(source_kind, task)| {
let resolved = task.resolved.unwrap();
(
source_kind,
task.resolved_label,
resolved.args,
resolved.env,
)
})
.collect::<Vec<_>>();
assert_eq!(
all_tasks,
vec![
(
TaskSourceKind::Worktree {
id: workree_id,
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
id_base: "local_tasks_for_worktree",
},
"cargo check".to_string()
"cargo check".to_string(),
vec!["check".to_string()],
HashMap::default(),
),
(
TaskSourceKind::Worktree {
id: workree_id,
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
id_base: "local_tasks_for_worktree",
},
"cargo check".to_string(),
vec![
"check".to_string(),
"--all".to_string(),
"--all-targets".to_string()
],
HashMap::from_iter(Some((
"RUSTFLAGS".to_string(),
"-Zunstable-options".to_string()
))),
),
]
);
});
}
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

@@ -7,7 +7,7 @@ use std::{
sync::Arc,
};
use collections::{HashMap, VecDeque};
use collections::{hash_map, HashMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use itertools::{Either, Itertools};
use language::Language;
@@ -229,7 +229,7 @@ impl Inventory {
},
);
let not_used_score = post_inc(&mut lru_score);
let current_resolved_tasks = self
let currently_resolved_tasks = self
.sources
.iter()
.filter(|source| {
@@ -257,16 +257,55 @@ impl Inventory {
(kind.clone(), task, lru_score)
})
.collect::<Vec<_>>();
let previous_resolved_tasks = task_usage
let previously_spawned_tasks = task_usage
.into_iter()
.map(|(_, (kind, task, lru_score))| (kind.clone(), task.clone(), lru_score));
previous_resolved_tasks
.chain(current_resolved_tasks)
let mut tasks_by_label = HashMap::default();
tasks_by_label = previously_spawned_tasks.into_iter().fold(
tasks_by_label,
|mut tasks_by_label, (source, task, lru_score)| {
match tasks_by_label.entry((source, task.resolved_label.clone())) {
hash_map::Entry::Occupied(mut o) => {
let (_, previous_lru_score) = o.get();
if previous_lru_score >= &lru_score {
o.insert((task, lru_score));
}
}
hash_map::Entry::Vacant(v) => {
v.insert((task, lru_score));
}
}
tasks_by_label
},
);
tasks_by_label = currently_resolved_tasks.into_iter().fold(
tasks_by_label,
|mut tasks_by_label, (source, task, lru_score)| {
match tasks_by_label.entry((source, task.resolved_label.clone())) {
hash_map::Entry::Occupied(mut o) => {
let (previous_task, _) = o.get();
let new_template = task.original_task();
if new_template.ignore_previously_resolved
|| new_template != previous_task.original_task()
{
o.insert((task, lru_score));
}
}
hash_map::Entry::Vacant(v) => {
v.insert((task, lru_score));
}
}
tasks_by_label
},
);
tasks_by_label
.into_iter()
.map(|((kind, _), (task, lru_score))| (kind, task, lru_score))
.sorted_unstable_by(task_lru_comparator)
.unique_by(|(kind, task, _)| (kind.clone(), task.resolved_label.clone()))
.partition_map(|(kind, task, lru_index)| {
if lru_index < not_used_score {
.partition_map(|(kind, task, lru_score)| {
if lru_score < not_used_score {
Either::Left((kind, task))
} else {
Either::Right((kind, task))
@@ -705,7 +744,7 @@ mod tests {
(
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_1.to_path_buf(),
abs_path: path_2.to_path_buf(),
},
common_name.to_string(),
),
@@ -719,7 +758,7 @@ mod tests {
(
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_2.to_path_buf(),
abs_path: path_1.to_path_buf(),
},
common_name.to_string(),
),

View File

@@ -55,6 +55,14 @@ impl Project {
id: spawn_task.id,
full_label: spawn_task.full_label,
label: spawn_task.label,
command_label: spawn_task.args.iter().fold(
spawn_task.command.clone(),
|mut command_label, new_arg| {
command_label.push(' ');
command_label.push_str(new_arg);
command_label
},
),
status: TaskStatus::Running,
completion_rx,
}),

View File

@@ -1642,7 +1642,10 @@ impl ProjectPanel {
.child(if let Some(icon) = &icon {
h_flex().child(Icon::from_path(icon.to_string()).color(filename_text_color))
} else {
h_flex().size(IconSize::default().rems()).invisible()
h_flex()
.size(IconSize::default().rems())
.invisible()
.flex_none()
})
.child(
if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) {
@@ -1835,7 +1838,7 @@ impl Render for DraggedProjectEntryView {
let settings = ProjectPanelSettings::get_global(cx);
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
h_flex()
.font_family(ui_font)
.font(ui_font)
.bg(cx.theme().colors().background)
.w(self.width)
.child(

View File

@@ -3,18 +3,21 @@ use assistant::{AssistantPanel, InlineAssist};
use editor::{Editor, EditorSettings};
use gpui::{
Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
Subscription, View, ViewContext, WeakView,
anchored, deferred, Action, AnchorCorner, ClickEvent, DismissEvent, ElementId, EventEmitter,
InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
};
use search::{buffer_search, BufferSearchBar};
use settings::{Settings, SettingsStore};
use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
use ui::{
prelude::*, ButtonSize, ButtonStyle, ContextMenu, IconButton, IconName, IconSize, Tooltip,
};
use workspace::{
item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
pub struct QuickActionBar {
buffer_search_bar: View<BufferSearchBar>,
toggle_settings_menu: Option<View<ContextMenu>>,
active_item: Option<Box<dyn ItemHandle>>,
_inlay_hints_enabled_subscription: Option<Subscription>,
workspace: WeakView<Workspace>,
@@ -29,6 +32,7 @@ impl QuickActionBar {
) -> Self {
let mut this = Self {
buffer_search_bar,
toggle_settings_menu: None,
active_item: None,
_inlay_hints_enabled_subscription: None,
workspace: workspace.weak_handle(),
@@ -63,6 +67,17 @@ impl QuickActionBar {
ToolbarItemLocation::Hidden
}
}
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
div().absolute().bottom_0().right_0().size_0().child(
deferred(
anchored()
.anchor(AnchorCorner::TopRight)
.child(menu.clone()),
)
.with_priority(1),
)
}
}
impl Render for QuickActionBar {
@@ -70,22 +85,6 @@ impl Render for QuickActionBar {
let Some(editor) = self.active_editor() else {
return div().id("empty quick action bar");
};
let inlay_hints_button = Some(QuickActionBarButton::new(
"toggle inlay hints",
IconName::InlayHint,
editor.read(cx).inlay_hints_enabled(),
Box::new(editor::actions::ToggleInlayHints),
"Toggle Inlay Hints",
{
let editor = editor.clone();
move |_, cx| {
editor.update(cx, |editor, cx| {
editor.toggle_inlay_hints(&editor::actions::ToggleInlayHints, cx);
});
}
},
))
.filter(|_| editor.read(cx).supports_inlay_hints(cx));
let search_button = Some(QuickActionBarButton::new(
"toggle buffer search",
@@ -122,14 +121,87 @@ impl Render for QuickActionBar {
},
);
let editor_settings_dropdown =
IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
.size(ButtonSize::Compact)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggle_settings_menu.is_some())
.on_click({
let editor = editor.clone();
cx.listener(move |quick_action_bar, _, cx| {
let inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
let supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
let git_blame_inline_enabled = editor.read(cx).git_blame_inline_enabled();
let menu = ContextMenu::build(cx, |mut menu, _| {
if supports_inlay_hints {
menu = menu.toggleable_entry(
"Show Inlay Hints",
inlay_hints_enabled,
Some(editor::actions::ToggleInlayHints.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_inlay_hints(
&editor::actions::ToggleInlayHints,
cx,
);
});
}
},
);
}
menu = menu.toggleable_entry(
"Show Git Blame Inline",
git_blame_inline_enabled,
Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
{
let editor = editor.clone();
move |cx| {
editor.update(cx, |editor, cx| {
editor.toggle_git_blame_inline(
&editor::actions::ToggleGitBlameInline,
cx,
)
});
}
},
);
menu
});
cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
quick_action_bar.toggle_settings_menu = None;
})
.detach();
quick_action_bar.toggle_settings_menu = Some(menu);
})
})
.when(self.toggle_settings_menu.is_none(), |this| {
this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
});
h_flex()
.id("quick action bar")
.gap_2()
.children(inlay_hints_button)
.children(search_button)
.when(AssistantSettings::get_global(cx).button, |bar| {
bar.child(assistant_button)
})
.gap_3()
.child(
h_flex()
.gap_1p5()
.children(search_button)
.when(AssistantSettings::get_global(cx).button, |bar| {
bar.child(assistant_button)
}),
)
.child(editor_settings_dropdown)
.when_some(
self.toggle_settings_menu.as_ref(),
|el, toggle_settings_menu| {
el.child(Self::render_menu_overlay(toggle_settings_menu))
},
)
}
}

View File

@@ -43,19 +43,6 @@ pub struct RichText {
Option<Arc<dyn Fn(usize, Range<usize>, &mut WindowContext) -> Option<AnyView>>>,
}
impl Default for RichText {
fn default() -> Self {
Self {
text: SharedString::default(),
highlights: Vec::new(),
link_ranges: Vec::new(),
link_urls: Arc::from([]),
custom_ranges: Vec::new(),
custom_ranges_tooltip_fn: None,
}
}
}
/// Allows one to specify extra links to the rendered markdown, which can be used
/// for e.g. mentions.
#[derive(Debug)]
@@ -65,37 +52,6 @@ pub struct Mention {
}
impl RichText {
pub fn new(
block: String,
mentions: &[Mention],
language_registry: &Arc<LanguageRegistry>,
) -> Self {
let mut text = String::new();
let mut highlights = Vec::new();
let mut link_ranges = Vec::new();
let mut link_urls = Vec::new();
render_markdown_mut(
&block,
mentions,
language_registry,
None,
&mut text,
&mut highlights,
&mut link_ranges,
&mut link_urls,
);
text.truncate(text.trim_end().len());
RichText {
text: SharedString::from(text),
link_urls: link_urls.into(),
link_ranges,
highlights,
custom_ranges: Vec::new(),
custom_ranges_tooltip_fn: None,
}
}
pub fn set_tooltip_builder_for_custom_ranges(
&mut self,
f: impl Fn(usize, Range<usize>, &mut WindowContext) -> Option<AnyView> + 'static,
@@ -391,6 +347,38 @@ pub fn render_markdown_mut(
}
}
pub fn render_rich_text(
block: String,
mentions: &[Mention],
language_registry: &Arc<LanguageRegistry>,
language: Option<&Arc<Language>>,
) -> RichText {
let mut text = String::new();
let mut highlights = Vec::new();
let mut link_ranges = Vec::new();
let mut link_urls = Vec::new();
render_markdown_mut(
&block,
mentions,
language_registry,
language,
&mut text,
&mut highlights,
&mut link_ranges,
&mut link_urls,
);
text.truncate(text.trim_end().len());
RichText {
text: SharedString::from(text),
link_urls: link_urls.into(),
link_ranges,
highlights,
custom_ranges: Vec::new(),
custom_ranges_tooltip_fn: None,
}
}
pub fn render_code(
text: &mut String,
highlights: &mut Vec<(Range<usize>, Highlight)>,

View File

@@ -54,8 +54,6 @@ struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
impl Global for ActiveSettings {}
const SEARCH_CONTEXT: u32 = 2;
pub fn init(cx: &mut AppContext) {
cx.set_global(ActiveSettings::default());
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
@@ -234,7 +232,7 @@ impl ProjectSearch {
excerpts.stream_excerpts_with_context_lines(
buffer,
ranges,
SEARCH_CONTEXT,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
)
})

View File

@@ -9,11 +9,6 @@ license = "GPL-3.0-or-later"
[lib]
path = "src/semantic_index.rs"
[[example]]
name = "index"
path = "examples/index.rs"
crate-type = ["bin"]
[dependencies]
anyhow.workspace = true
client.workspace = true

View File

@@ -1,16 +1,25 @@
use client::Client;
use futures::channel::oneshot;
use gpui::{App, Global};
use gpui::{App, Global, TestAppContext};
use language::language_settings::AllLanguageSettings;
use project::Project;
use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, SemanticIndex};
use settings::SettingsStore;
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use std::{path::Path, sync::Arc};
use util::http::HttpClientWithUrl;
pub fn init_test(cx: &mut TestAppContext) {
_ = cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
language::init(cx);
Project::init_settings(cx);
SettingsStore::update(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |_| {});
});
});
}
fn main() {
env_logger::init();
@@ -49,7 +58,7 @@ fn main() {
);
let semantic_index = SemanticIndex::new(
PathBuf::from("/tmp/semantic-index-db.mdb"),
Path::new("/tmp/semantic-index-db.mdb"),
Arc::new(embedding_provider),
cx,
);

View File

@@ -21,7 +21,7 @@ use std::{
cmp::Ordering,
future::Future,
ops::Range,
path::{Path, PathBuf},
path::Path,
sync::Arc,
time::{Duration, SystemTime},
};
@@ -38,15 +38,15 @@ impl Global for SemanticIndex {}
impl SemanticIndex {
pub fn new(
db_path: PathBuf,
db_path: &Path,
embedding_provider: Arc<dyn EmbeddingProvider>,
cx: &mut AppContext,
) -> Task<Result<Self>> {
let db_path = db_path.to_path_buf();
cx.spawn(|cx| async move {
let db_connection = cx
.background_executor()
.spawn(async move {
std::fs::create_dir_all(&db_path)?;
unsafe {
heed::EnvOpenOptions::new()
.map_size(1024 * 1024 * 1024)
@@ -54,8 +54,7 @@ impl SemanticIndex {
.open(db_path)
}
})
.await
.context("opening database connection")?;
.await?;
Ok(SemanticIndex {
db_connection,
@@ -880,8 +879,11 @@ mod tests {
let mut semantic_index = cx
.update(|cx| {
let semantic_index =
SemanticIndex::new(temp_dir.path().into(), Arc::new(TestEmbeddingProvider), cx);
let semantic_index = SemanticIndex::new(
Path::new(temp_dir.path()),
Arc::new(TestEmbeddingProvider),
cx,
);
semantic_index
})
.await

View File

@@ -2,7 +2,6 @@ mod keymap_file;
mod settings_file;
mod settings_store;
use gpui::AppContext;
use rust_embed::RustEmbed;
use std::{borrow::Cow, str};
use util::asset_str;
@@ -20,14 +19,6 @@ pub use settings_store::{
#[exclude = "*.DS_Store"]
pub struct SettingsAssets;
pub fn init(cx: &mut AppContext) {
let mut settings = SettingsStore::default();
settings
.set_default_settings(&default_settings(), cx)
.unwrap();
cx.set_global(settings);
}
pub fn default_settings() -> Cow<'static, str> {
asset_str::<SettingsAssets>("settings/default.json")
}

View File

@@ -29,14 +29,14 @@ pub enum ComponentStory {
ListHeader,
ListItem,
OverflowScroll,
Picker,
Scroll,
Tab,
TabBar,
Text,
TitleBar,
ToggleButton,
Text,
ViewportUnits,
Picker,
}
impl ComponentStory {

View File

@@ -10,7 +10,7 @@ use gpui::{
div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowOptions,
};
use log::LevelFilter;
use settings::{KeymapFile, Settings};
use settings::{default_settings, KeymapFile, Settings, SettingsStore};
use simplelog::SimpleLogger;
use strum::IntoEnumIterator;
use theme::{ThemeRegistry, ThemeSettings};
@@ -63,7 +63,12 @@ fn main() {
gpui::App::new().with_assets(Assets).run(move |cx| {
load_embedded_fonts(cx).unwrap();
settings::init(cx);
let mut store = SettingsStore::default();
store
.set_default_settings(default_settings().as_ref(), cx)
.unwrap();
cx.set_global(store);
theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
let selector = story_selector;
@@ -115,7 +120,7 @@ impl Render for StoryWrapper {
.flex()
.flex_col()
.size_full()
.font_family("Zed Mono")
.font("Zed Mono")
.child(self.story.clone())
}
}

View File

@@ -598,6 +598,7 @@ pub struct TaskState {
pub id: TaskId,
pub full_label: String,
pub label: String,
pub command_label: String,
pub status: TaskStatus,
pub completion_rx: Receiver<()>,
}
@@ -657,13 +658,7 @@ impl Terminal {
AlacTermEvent::Bell => {
cx.emit(Event::Bell);
}
AlacTermEvent::Exit => match &mut self.task {
Some(task) => {
task.status.register_terminal_exit();
self.completion_tx.try_send(()).ok();
}
None => cx.emit(Event::CloseTerminal),
},
AlacTermEvent::Exit => self.register_task_finished(None, cx),
AlacTermEvent::MouseCursorDirty => {
//NOOP, Handled in render
}
@@ -679,10 +674,7 @@ impl Terminal {
.push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
}
AlacTermEvent::ChildExit(error_code) => {
if let Some(task) = &mut self.task {
task.status.register_task_exit(*error_code);
self.completion_tx.try_send(()).ok();
}
self.register_task_finished(Some(*error_code), cx);
}
}
}
@@ -1425,6 +1417,97 @@ impl Terminal {
}
Task::ready(())
}
fn register_task_finished(
&mut self,
error_code: Option<i32>,
cx: &mut ModelContext<'_, Terminal>,
) {
self.completion_tx.try_send(()).ok();
let task = match &mut self.task {
Some(task) => task,
None => {
if error_code.is_none() {
cx.emit(Event::CloseTerminal);
}
return;
}
};
if task.status != TaskStatus::Running {
return;
}
match error_code {
Some(error_code) => {
task.status.register_task_exit(error_code);
}
None => {
task.status.register_terminal_exit();
}
};
let (task_line, command_line) = task_summary(task, error_code);
// SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once,
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
// when Zed task finishes and no more output is made.
// After the task summary is output once, no more text is appended to the terminal.
unsafe { append_text_to_term(&mut self.term.lock(), &[&task_line, &command_line]) };
}
}
const TASK_DELIMITER: &str = "";
fn task_summary(task: &TaskState, error_code: Option<i32>) -> (String, String) {
let escaped_full_label = task.full_label.replace("\r\n", "\r").replace('\n', "\r");
let task_line = match error_code {
Some(0) => {
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished successfully")
}
Some(error_code) => {
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished with non-zero error code: {error_code}")
}
None => {
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished")
}
};
let escaped_command_label = task.command_label.replace("\r\n", "\r").replace('\n', "\r");
let command_line = format!("{TASK_DELIMITER}Command: '{escaped_command_label}'");
(task_line, command_line)
}
/// Appends a stringified task summary to the terminal, after its output.
///
/// SAFETY: This function should only be called after terminal's PTY is no longer alive.
/// New text being added to the terminal here, uses "less public" APIs,
/// which are not maintaining the entire terminal state intact.
///
///
/// The library
///
/// * does not increment inner grid cursor's _lines_ on `input` calls
/// (but displaying the lines correctly and incrementing cursor's columns)
///
/// * ignores `\n` and \r` character input, requiring the `newline` call instead
///
/// * does not alter grid state after `newline` call
/// so its `bottommost_line` is always the the same additions, and
/// the cursor's `point` is not updated to the new line and column values
///
/// * ??? there could be more consequences, and any further "proper" streaming from the PTY might bug and/or panic.
/// Still, concequent `append_text_to_term` invocations are possible and display the contents correctly.
///
/// Despite the quirks, this is the simplest approach to appending text to the terminal: its alternative, `grid_mut` manipulations,
/// do not properly set the scrolling state and display odd text after appending; also those manipulations are more tedious and error-prone.
/// The function achieves proper display and scrolling capabilities, at a cost of grid state not properly synchronized.
/// This is enough for printing moderately-sized texts like task summaries, but might break or perform poorly for larger texts.
unsafe fn append_text_to_term(term: &mut Term<ZedListener>, text_lines: &[&str]) {
term.newline();
term.grid_mut().cursor.point.column = Column(0);
for line in text_lines {
for c in line.chars() {
term.input(c);
}
term.newline();
term.grid_mut().cursor.point.column = Column(0);
}
}
impl Drop for Terminal {

View File

@@ -36,6 +36,7 @@ pub struct TerminalSettings {
pub alternate_scroll: AlternateScroll,
pub option_as_meta: bool,
pub copy_on_select: bool,
pub button: bool,
pub dock: TerminalDockPosition,
pub default_width: Pixels,
pub default_height: Pixels,
@@ -138,6 +139,10 @@ pub struct TerminalSettingsContent {
///
/// Default: false
pub copy_on_select: Option<bool>,
/// Whether to show the terminal button in the status bar.
///
/// Default: true
pub button: Option<bool>,
pub dock: Option<TerminalDockPosition>,
/// Default width when the terminal is docked to the left or right.
///

View File

@@ -26,7 +26,7 @@ use workspace::{
item::Item,
pane,
ui::IconName,
DraggedTab, NewTerminal, Pane, Workspace,
DraggedTab, NewTerminal, Pane, ToggleZoom, Workspace,
};
use anyhow::Result;
@@ -98,8 +98,13 @@ impl TerminalPanel {
.on_click(cx.listener(|pane, _, cx| {
pane.toggle_zoom(&workspace::ToggleZoom, cx);
}))
// TODO kb
.tooltip(move |cx| {
Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
Tooltip::for_action(
if zoomed { "Zoom Out" } else { "Zoom In" },
&ToggleZoom,
cx,
)
})
})
.into_any_element()
@@ -726,8 +731,10 @@ impl Panel for TerminalPanel {
"TerminalPanel"
}
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
Some(IconName::Terminal)
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
TerminalSettings::get_global(cx)
.button
.then(|| IconName::Terminal)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

View File

@@ -22,7 +22,6 @@ smallvec.workspace = true
story = { workspace = true, optional = true }
strum = { version = "0.25.0", features = ["derive"] }
theme.workspace = true
nanoid = "0.4"
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@@ -40,7 +40,7 @@ impl<V: 'static> TodoList<V> {
All of this is relatively straightforward.
We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to [TODO: someone who actually knows please explain why we use SharedString].
We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to efficiently handle shared string data across multiple components and threads without the performance overhead of copying strings.
When we want to pass an action we pass a `ClickHandler`. Whenever we want to add an action, the struct it belongs to needs to be generic over the view type `V`.

View File

@@ -1,7 +1,6 @@
mod avatar;
mod button;
mod checkbox;
mod collapsible_container;
mod context_menu;
mod disclosure;
mod divider;
@@ -26,7 +25,6 @@ mod stories;
pub use avatar::*;
pub use button::*;
pub use checkbox::*;
pub use collapsible_container::*;
pub use context_menu::*;
pub use disclosure::*;
pub use divider::*;

View File

@@ -1,152 +0,0 @@
use crate::{prelude::*, ButtonLike};
use smallvec::SmallVec;
use gpui::*;
#[derive(Default, Clone, Copy, Debug, PartialEq)]
pub enum ContainerStyle {
#[default]
None,
Card,
}
struct ContainerStyles {
pub background_color: Hsla,
pub border_color: Hsla,
pub text_color: Hsla,
}
#[derive(IntoElement)]
pub struct CollapsibleContainer {
id: ElementId,
base: ButtonLike,
toggle: bool,
/// A slot for content that appears before the label, like an icon or avatar.
start_slot: Option<AnyElement>,
/// A slot for content that appears after the label, usually on the other side of the header.
/// This might be a button, a disclosure arrow, a face pile, etc.
end_slot: Option<AnyElement>,
style: ContainerStyle,
children: SmallVec<[AnyElement; 1]>,
}
impl CollapsibleContainer {
pub fn new(id: impl Into<ElementId>, toggle: bool) -> Self {
Self {
id: id.into(),
base: ButtonLike::new("button_base"),
toggle,
start_slot: None,
end_slot: None,
style: ContainerStyle::Card,
children: SmallVec::new(),
}
}
pub fn start_slot<E: IntoElement>(mut self, start_slot: impl Into<Option<E>>) -> Self {
self.start_slot = start_slot.into().map(IntoElement::into_any_element);
self
}
pub fn end_slot<E: IntoElement>(mut self, end_slot: impl Into<Option<E>>) -> Self {
self.end_slot = end_slot.into().map(IntoElement::into_any_element);
self
}
pub fn child<E: IntoElement>(mut self, child: E) -> Self {
self.children.push(child.into_any_element());
self
}
}
impl Clickable for CollapsibleContainer {
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
self.base = self.base.on_click(handler);
self
}
}
impl RenderOnce for CollapsibleContainer {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let color = cx.theme().colors();
let styles = match self.style {
ContainerStyle::None => ContainerStyles {
background_color: color.ghost_element_background,
border_color: color.border_transparent,
text_color: color.text,
},
ContainerStyle::Card => ContainerStyles {
background_color: color.elevated_surface_background,
border_color: color.border,
text_color: color.text,
},
};
v_flex()
.id(self.id)
.relative()
.rounded_md()
.bg(styles.background_color)
.border()
.border_color(styles.border_color)
.text_color(styles.text_color)
.overflow_hidden()
.child(
h_flex()
.overflow_hidden()
.w_full()
.group("toggleable_container_header")
.border_b()
.border_color(if self.toggle {
styles.border_color
} else {
color.border_transparent
})
.child(
self.base.full_width().style(ButtonStyle::Subtle).child(
div()
.h_7()
.p_1()
.flex()
.flex_1()
.items_center()
.justify_between()
.w_full()
.gap_1()
.cursor_pointer()
.group_hover("toggleable_container_header", |this| {
this.bg(color.element_hover)
})
.child(
h_flex()
.gap_1()
.child(
IconButton::new(
"toggle_icon",
match self.toggle {
true => IconName::ChevronDown,
false => IconName::ChevronRight,
},
)
.icon_color(Color::Muted)
.icon_size(IconSize::XSmall),
)
.child(
div()
.id("label_container")
.flex()
.gap_1()
.items_center()
.children(self.start_slot),
),
)
.child(h_flex().children(self.end_slot)),
),
),
)
.when(self.toggle, |this| {
this.child(h_flex().flex_1().w_full().p_1().children(self.children))
})
}
}

View File

@@ -13,6 +13,7 @@ enum ContextMenuItem {
Separator,
Header(SharedString),
Entry {
toggled: Option<bool>,
label: SharedString,
icon: Option<IconName>,
handler: Rc<dyn Fn(&mut WindowContext)>,
@@ -92,6 +93,24 @@ impl ContextMenu {
handler: impl Fn(&mut WindowContext) + 'static,
) -> Self {
self.items.push(ContextMenuItem::Entry {
toggled: None,
label: label.into(),
handler: Rc::new(handler),
icon: None,
action,
});
self
}
pub fn toggleable_entry(
mut self,
label: impl Into<SharedString>,
toggled: bool,
action: Option<Box<dyn Action>>,
handler: impl Fn(&mut WindowContext) + 'static,
) -> Self {
self.items.push(ContextMenuItem::Entry {
toggled: Some(toggled),
label: label.into(),
handler: Rc::new(handler),
icon: None,
@@ -114,6 +133,7 @@ impl ContextMenu {
pub fn action(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.items.push(ContextMenuItem::Entry {
toggled: None,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
@@ -124,6 +144,7 @@ impl ContextMenu {
pub fn link(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
self.items.push(ContextMenuItem::Entry {
toggled: None,
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
@@ -279,6 +300,7 @@ impl Render for ContextMenu {
.inset(true)
.into_any_element(),
ContextMenuItem::Entry {
toggled,
label,
handler,
icon,
@@ -300,13 +322,14 @@ impl Render for ContextMenu {
ListItem::new(ix)
.inset(true)
.selected(Some(ix) == self.selected_index)
.on_click(move |_, cx| {
handler(cx);
menu.update(cx, |menu, cx| {
menu.clicked = true;
cx.emit(DismissEvent);
.when_some(*toggled, |list_item, toggled| {
list_item.start_slot(if toggled {
v_flex().flex_none().child(
Icon::new(IconName::Check).color(Color::Accent),
)
} else {
v_flex().flex_none().size(IconSize::default().rems())
})
.ok();
})
.child(
h_flex()
@@ -328,6 +351,14 @@ impl Render for ContextMenu {
.map(|binding| div().ml_1().child(binding))
})),
)
.on_click(move |_, cx| {
handler(cx);
menu.update(cx, |menu, cx| {
menu.clicked = true;
cx.emit(DismissEvent);
})
.ok();
})
.into_any_element()
}
ContextMenuItem::CustomEntry {

View File

@@ -105,6 +105,7 @@ pub enum IconName {
Return,
ReplyArrowRight,
Settings,
Sliders,
Screen,
SelectAll,
Server,
@@ -204,6 +205,7 @@ impl IconName {
IconName::Return => "icons/return.svg",
IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
IconName::Settings => "icons/file_icons/settings.svg",
IconName::Sliders => "icons/sliders.svg",
IconName::Screen => "icons/desktop.svg",
IconName::SelectAll => "icons/select_all.svg",
IconName::Server => "icons/server.svg",

View File

@@ -110,7 +110,7 @@ impl RenderOnce for WindowsCaptionButton {
.content_center()
.w(width)
.h_full()
.font_family("Segoe Fluent Icons")
.font("Segoe Fluent Icons")
.text_size(px(10.0))
.hover(|style| style.bg(self.hover_background_color))
.active(|style| {

View File

@@ -95,7 +95,7 @@ pub fn tooltip_container<V>(
div().pl_2().pt_2p5().child(
v_flex()
.elevation_2(cx)
.font_family(ui_font)
.font(ui_font)
.text_ui()
.text_color(cx.theme().colors().text)
.py_1()

View File

@@ -93,7 +93,7 @@ impl RenderOnce for Headline {
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
div()
.font_family(ui_font)
.font(ui_font)
.line_height(self.size.line_height())
.text_size(self.size.size())
.text_color(cx.theme().colors().text)

View File

@@ -239,7 +239,7 @@ pub fn move_to_internal(
};
let mut query = regex::escape(&query);
if whole_word {
query = format!(r"\b{}\b", query);
query = format!(r"\<{}\>", query);
}
Some(search_bar.search(&query, Some(options), cx))
});

View File

@@ -365,7 +365,11 @@ impl Pane {
pane.toggle_zoom(&crate::ToggleZoom, cx);
}))
.tooltip(move |cx| {
Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
Tooltip::for_action(
if zoomed { "Zoom Out" } else { "Zoom In" },
&ToggleZoom,
cx,
)
})
})
.when_some(pane.split_item_menu.as_ref(), |el, split_item_menu| {
@@ -2852,6 +2856,6 @@ impl Render for DraggedTab {
.selected(self.is_active)
.child(label)
.render(cx)
.font_family(ui_font)
.font(ui_font)
}
}

View File

@@ -138,6 +138,7 @@ define_connection! {
// window_height: Option<f32>, // WindowBounds::Fixed RectF height
// display: Option<Uuid>, // Display id
// fullscreen: Option<bool>, // Is the window fullscreen?
// centered_layout: Option<bool>, // Is the Centered Layout mode activated?
// )
//
// pane_groups(
@@ -284,6 +285,11 @@ define_connection! {
sql!(
ALTER TABLE items ADD COLUMN preview INTEGER; //bool
),
// Add centered_layout field to workspace
sql!(
ALTER TABLE workspaces ADD COLUMN centered_layout INTEGER; //bool
),
];
}
@@ -299,12 +305,13 @@ impl WorkspaceDb {
// Note that we re-assign the workspace_id here in case it's empty
// and we've grabbed the most recent workspace
let (workspace_id, workspace_location, bounds, display, fullscreen, docks): (
let (workspace_id, workspace_location, bounds, display, fullscreen, centered_layout, docks): (
WorkspaceId,
WorkspaceLocation,
Option<SerializedWindowsBounds>,
Option<Uuid>,
Option<bool>,
Option<bool>,
DockStructure,
) = self
.select_row_bound(sql! {
@@ -318,6 +325,7 @@ impl WorkspaceDb {
window_height,
display,
fullscreen,
centered_layout,
left_dock_visible,
left_dock_active_panel,
left_dock_zoom,
@@ -344,6 +352,7 @@ impl WorkspaceDb {
.log_err()?,
bounds: bounds.map(|bounds| bounds.0),
fullscreen: fullscreen.unwrap_or(false),
centered_layout: centered_layout.unwrap_or(false),
display,
docks,
})
@@ -678,6 +687,14 @@ impl WorkspaceDb {
WHERE workspace_id = ?1
}
}
query! {
pub(crate) async fn set_centered_layout(workspace_id: WorkspaceId, centered_layout: bool) -> Result<()> {
UPDATE workspaces
SET centered_layout = ?2
WHERE workspace_id = ?1
}
}
}
#[cfg(test)]
@@ -764,6 +781,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
};
let workspace_2 = SerializedWorkspace {
@@ -774,6 +792,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
};
db.save_workspace(workspace_1.clone()).await;
@@ -873,6 +892,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
};
db.save_workspace(workspace.clone()).await;
@@ -902,6 +922,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
};
let mut workspace_2 = SerializedWorkspace {
@@ -912,6 +933,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
};
db.save_workspace(workspace_1.clone()).await;
@@ -949,6 +971,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
};
db.save_workspace(workspace_3.clone()).await;
@@ -983,6 +1006,7 @@ mod tests {
display: Default::default(),
docks: Default::default(),
fullscreen: false,
centered_layout: false,
}
}

View File

@@ -71,6 +71,7 @@ pub(crate) struct SerializedWorkspace {
pub(crate) center_group: SerializedPaneGroup,
pub(crate) bounds: Option<Bounds<DevicePixels>>,
pub(crate) fullscreen: bool,
pub(crate) centered_layout: bool,
pub(crate) display: Option<Uuid>,
pub(crate) docks: DockStructure,
}

View File

@@ -26,7 +26,7 @@ use futures::{
Future, FutureExt, StreamExt,
};
use gpui::{
actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
actions, canvas, impl_actions, point, relative, size, Action, AnyElement, AnyView, AnyWeakView,
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent,
Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke,
LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
@@ -78,9 +78,9 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
use ui::{
div, Context as _, Div, Element, ElementContext, FluentBuilder as _, InteractiveElement as _,
IntoElement, Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext,
VisualContext as _, WindowContext,
div, h_flex, Context as _, Div, Element, ElementContext, FluentBuilder,
InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString,
Styled as _, ViewContext, VisualContext as _, WindowContext,
};
use util::ResultExt;
use uuid::Uuid;
@@ -133,6 +133,7 @@ actions!(
ToggleLeftDock,
ToggleRightDock,
ToggleBottomDock,
ToggleCenteredLayout,
CloseAllDocks,
]
);
@@ -581,6 +582,7 @@ pub struct Workspace {
_schedule_serialize: Option<Task<()>>,
pane_history_timestamp: Arc<AtomicUsize>,
bounds: Bounds<Pixels>,
centered_layout: bool,
bounds_save_task_queued: Option<Task<()>>,
}
@@ -600,6 +602,9 @@ struct FollowerState {
}
impl Workspace {
const DEFAULT_PADDING: f32 = 0.2;
const MAX_PADDING: f32 = 0.4;
pub fn new(
workspace_id: WorkspaceId,
project: Model<Project>,
@@ -867,6 +872,7 @@ impl Workspace {
workspace_actions: Default::default(),
// This data will be incorrect, but it will be overwritten by the time it needs to be used.
bounds: Default::default(),
centered_layout: false,
bounds_save_task_queued: None,
}
}
@@ -956,12 +962,19 @@ impl Workspace {
let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
options.bounds = bounds;
options.fullscreen = fullscreen;
let centered_layout = serialized_workspace
.as_ref()
.map(|w| w.centered_layout)
.unwrap_or(false);
cx.open_window(options, {
let app_state = app_state.clone();
let project_handle = project_handle.clone();
move |cx| {
cx.new_view(|cx| {
Workspace::new(workspace_id, project_handle, app_state, cx)
let mut workspace =
Workspace::new(workspace_id, project_handle, app_state, cx);
workspace.centered_layout = centered_layout;
workspace
})
}
})?
@@ -3541,6 +3554,7 @@ impl Workspace {
display: Default::default(),
docks,
fullscreen: cx.is_fullscreen(),
centered_layout: self.centered_layout,
};
return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace));
}
@@ -3704,6 +3718,7 @@ impl Workspace {
workspace.reopen_closed_item(cx).detach();
}),
)
.on_action(cx.listener(Workspace::toggle_centered_layout))
}
#[cfg(any(test, feature = "test-support"))]
@@ -3772,6 +3787,21 @@ impl Workspace {
self.modal_layer
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
}
pub fn toggle_centered_layout(&mut self, _: &ToggleCenteredLayout, cx: &mut ViewContext<Self>) {
self.centered_layout = !self.centered_layout;
cx.background_executor()
.spawn(DB.set_centered_layout(self.database_id, self.centered_layout))
.detach_and_log_err(cx);
cx.notify();
}
fn adjust_padding(padding: Option<f32>) -> f32 {
padding
.unwrap_or(Self::DEFAULT_PADDING)
.min(Self::MAX_PADDING)
.max(0.0)
}
}
fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
@@ -3916,7 +3946,27 @@ impl Render for Workspace {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let mut context = KeyContext::default();
context.add("Workspace");
let centered_layout = self.centered_layout
&& self.center.panes().len() == 1
&& self.active_item(cx).is_some();
let render_padding = |size| {
(size > 0.0).then(|| {
div()
.h_full()
.w(relative(size))
.bg(cx.theme().colors().editor_background)
.border_color(cx.theme().colors().pane_group_border)
})
};
let paddings = if centered_layout {
let settings = WorkspaceSettings::get_global(cx).centered_layout;
(
render_padding(Self::adjust_padding(settings.left_padding)),
render_padding(Self::adjust_padding(settings.right_padding)),
)
} else {
(None, None)
};
let (ui_font, ui_font_size) = {
let theme_settings = ThemeSettings::get_global(cx);
(
@@ -3935,7 +3985,7 @@ impl Render for Workspace {
.size_full()
.flex()
.flex_col()
.font_family(ui_font)
.font(ui_font)
.gap_0()
.justify_start()
.items_start()
@@ -4009,15 +4059,25 @@ impl Render for Workspace {
.flex_col()
.flex_1()
.overflow_hidden()
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
))
.child(
h_flex()
.flex_1()
.when_some(paddings.0, |this, p| {
this.child(p.border_r_1())
})
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
cx,
))
.when_some(paddings.1, |this, p| {
this.child(p.border_l_1())
}),
)
.children(
self.zoomed_position
.ne(&Some(DockPosition::Bottom))

View File

@@ -7,6 +7,7 @@ use settings::{Settings, SettingsSources};
#[derive(Deserialize)]
pub struct WorkspaceSettings {
pub active_pane_magnification: f32,
pub centered_layout: CenteredLayoutSettings,
pub confirm_quit: bool,
pub show_call_status_icon: bool,
pub autosave: AutosaveSetting,
@@ -31,6 +32,8 @@ pub struct WorkspaceSettingsContent {
///
/// Default: `1.0`
pub active_pane_magnification: Option<f32>,
// Centered layout related settings.
pub centered_layout: Option<CenteredLayoutSettings>,
/// Whether or not to prompt the user to confirm before closing the application.
///
/// Default: false
@@ -75,6 +78,21 @@ pub enum AutosaveSetting {
OnWindowChange,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct CenteredLayoutSettings {
/// The relative width of the left padding of the central pane from the
/// workspace when the centered layout is used.
///
/// Default: 0.2
pub left_padding: Option<f32>,
// The relative width of the right padding of the central pane from the
// workspace when the centered layout is used.
///
/// Default: 0.2
pub right_padding: Option<f32>,
}
impl Settings for WorkspaceSettings {
const KEY: Option<&'static str> = None;

View File

@@ -151,7 +151,7 @@ fn init_headless(dev_server_token: DevServerToken) {
})
}
fn init_ui() {
fn init_ui(args: Args) {
menu::init();
zed_actions::init();
@@ -231,18 +231,27 @@ fn init_ui() {
load_embedded_fonts(cx);
settings::init(cx);
let mut store = SettingsStore::default();
store
.set_default_settings(default_settings().as_ref(), cx)
.unwrap();
cx.set_global(store);
handle_settings_file_changes(user_settings_file_rx, cx);
handle_keymap_file_changes(user_keymap_file_rx, cx);
client::init_settings(cx);
let client = Client::production(cx);
let clock = Arc::new(clock::RealSystemClock);
let http = Arc::new(HttpClientWithUrl::new(
&client::ClientSettings::get_global(cx).server_url,
));
let client = client::Client::new(clock, http.clone(), cx);
let mut languages =
LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
let copilot_language_server_id = languages.next_language_server_id();
languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
let languages = Arc::new(languages);
let node_runtime = RealNodeRuntime::new(client.http_client());
let node_runtime = RealNodeRuntime::new(http.clone());
language::init(cx);
languages::init(languages.clone(), node_runtime.clone(), cx);
@@ -262,7 +271,7 @@ fn init_ui() {
diagnostics::init(cx);
copilot::init(
copilot_language_server_id,
client.http_client(),
http.clone(),
node_runtime.clone(),
cx,
);
@@ -287,7 +296,7 @@ fn init_ui() {
cx.observe_global::<SettingsStore>({
let languages = languages.clone();
let http = client.http_client();
let http = http.clone();
let client = client.clone();
move |cx| {
@@ -335,7 +344,7 @@ fn init_ui() {
AppState::set_global(Arc::downgrade(&app_state), cx);
audio::init(Assets, cx);
auto_update::init(client.http_client(), cx);
auto_update::init(http.clone(), cx);
workspace::init(app_state.clone(), cx);
recent_projects::init(cx);
@@ -368,11 +377,10 @@ fn init_ui() {
initialize_workspace(app_state.clone(), cx);
// todo(linux): unblock this
upload_panics_and_crashes(client.http_client(), cx);
upload_panics_and_crashes(http.clone(), cx);
cx.activate(true);
let args = Args::parse();
let mut triggered_authentication = false;
let urls: Vec<_> = args
.paths_or_urls
@@ -427,7 +435,7 @@ fn main() {
let dev_server_token = DevServerToken(dev_server_token);
init_headless(dev_server_token)
} else {
init_ui()
init_ui(args)
}
}

View File

@@ -3030,7 +3030,11 @@ mod tests {
])
.unwrap();
let themes = ThemeRegistry::default();
settings::init(cx);
let mut settings = SettingsStore::default();
settings
.set_default_settings(&settings::default_settings(), cx)
.unwrap();
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
let mut has_default_theme = false;

View File

@@ -68,7 +68,7 @@ pub fn app_menus() -> Vec<Menu<'static>> {
MenuItem::os_action("Paste", editor::actions::Paste, OsAction::Paste),
MenuItem::separator(),
MenuItem::action("Find", search::buffer_search::Deploy::find()),
MenuItem::action("Find In Project", workspace::NewSearch),
MenuItem::action("Find In Project", workspace::DeploySearch::find()),
MenuItem::separator(),
MenuItem::action(
"Toggle Line Comment",
@@ -179,6 +179,12 @@ pub fn app_menus() -> Vec<Menu<'static>> {
url: "https://twitter.com/zeddotdev".into(),
},
),
MenuItem::action(
"Join the Team",
super::OpenBrowser {
url: "https://zed.dev/jobs".into(),
},
),
],
},
]

View File

@@ -142,6 +142,24 @@ For example, to disable ligatures for a given font you can add the following to
`boolean` values
## Centered Layout
- Description: Configuration for the centered layout mode.
- Setting: `centered_layout`
- Default:
```json
"centered_layout": {
"left_padding": 0.2,
"right_padding": 0.2,
}
```
**Options**
The `left_padding` and `right_padding` options define the relative width of the
left and right padding of the central pane from the workspace when the centered layout mode is activated. Valid values range is from `0` to `0.4`.
## Copilot
- Description: Copilot-specific settings.
@@ -190,6 +208,102 @@ List of `string` values
2. Position the dock to the right of the workspace like a side panel: `right`
3. Position the dock full screen over the entire workspace: `expanded`
## Editor Scrollbar
- Description: Whether or not to show the editor scrollbar and various elements in it.
- Setting: `scrollbar`
- Default:
```json
"scrollbar": {
"show": "auto",
"git_diff": true,
"search_results": true,
"selected_symbol": true,
"diagnostics": true
},
```
### Show Mode
- Description: When to show the editor scrollbar.
- Setting: `show`
- Default: `auto`
**Options**
1. Show the scrollbar if there's important information or follow the system's configured behavior:
```json
"scrollbar": {
"show": "auto"
}
```
2. Match the system's configured behavior:
```json
"scrollbar": {
"show": "system"
}
```
3. Always show the scrollbar:
```json
"scrollbar": {
"show": "always"
}
```
4. Never show the scrollbar:
```json
"scrollbar": {
"show": "never"
}
```
### Git Diff Indicators
- Description: Whether to show git diff indicators in the scrollbar.
- Setting: `git_diff`
- Default: `true`
**Options**
`boolean` values
### Search Results Indicators
- Description: Whether to show buffer search results in the scrollbar.
- Setting: `search_results`
- Default: `true`
**Options**
`boolean` values
### Selected Symbols Indicators
- Description: Whether to show selected symbol occurrences in the scrollbar.
- Setting: `selected_symbol`
- Default: `true`
**Options**
`boolean` values
### Diagnostics
- Description: Whether to show diagnostic indicators in the scrollbar.
- Setting: `diagnostics`
- Default: `true`
**Options**
`boolean` values
## Editor Toolbar
- Description: Whether or not to show various elements in the editor toolbar.
@@ -387,7 +501,7 @@ To override settings for a language, add an entry for that language server's nam
## Auto close
- Description: Whether or not to automatically type closing characters for you.
- Description: Whether to automatically add matching closing characters when typing opening parenthesis, bracket, brace, single or double quote characters.
- Setting: `use_autoclose`
- Default: `true`
@@ -441,9 +555,14 @@ To interpret all `.c` files as C++, and files called `MyLockFile` as TOML:
- Default:
```json
"git": {
"git_gutter": "tracked_files"
},
{
"git": {
"git_gutter": "tracked_files",
"inline_blame": {
"enabled": true
}
}
}
```
### Git Gutter
@@ -458,7 +577,9 @@ To interpret all `.c` files as C++, and files called `MyLockFile` as TOML:
```json
{
"git_gutter": "tracked_files"
"git": {
"git_gutter": "tracked_files"
}
}
```
@@ -466,7 +587,52 @@ To interpret all `.c` files as C++, and files called `MyLockFile` as TOML:
```json
{
"git_gutter": "hide"
"git": {
"git_gutter": "hide"
}
}
```
### Inline Git Blame
- Description: Whether or not to show git blame information inline, on the currently focused line (requires Zed `0.132.0`).
- Setting: `inline_blame`
- Default:
```json
{
"git": {
"inline_blame": {
"enabled": true
}
}
}
```
**Options**
1. Disable inline git blame:
```json
{
"git": {
"inline_blame": {
"enabled": false
}
}
}
```
2. Only show inline git blame after a delay (that starts after cursor stops moving):
```json
{
"git": {
"inline_blame": {
"enabled": false,
"delay_ms": 500
}
}
}
```
@@ -616,12 +782,13 @@ These values take in the same options as the root-level settings with the same n
## Preview tabs
- Description:
(requires Zed `0.132.x`) \
Preview tabs allow you to open files in preview mode, where they close automatically when you switch to another file unless you explicitly pin them. This is useful for quickly viewing files without cluttering your workspace. Preview tabs display their file names in italics. \
There are several ways to convert a preview tab into a regular tab:
- Double-clicking on the file
- Double-clicking on the tab header
- Using the 'project_panel::OpenPermanent' action
- Using the `project_panel::OpenPermanent` action
- Editing the file
- Dragging the file to a different pane
@@ -635,8 +802,6 @@ These values take in the same options as the root-level settings with the same n
}
```
**Options**
### Enable preview from file finder
- Description: Determines whether to open files in preview mode when selected from the file finder.
@@ -812,6 +977,7 @@ These values take in the same options as the root-level settings with the same n
"font_features": null,
"font_size": null,
"option_as_meta": false,
"button": false,
"shell": {},
"toolbar": {
"title": true
@@ -990,6 +1156,16 @@ See Buffer Font Features
At the moment, only the `title` option is available, it controls displaying of the terminal title that can be changed via `PROMPT_COMMAND`. If the title is hidden, the terminal toolbar is not displayed.
### Terminal Button
- Description: Control to show or hide the terminal button in the status bar
- Setting: `button`
- Default: `true`
**Options**
`boolean` values
### Working Directory
- Description: What working directory to use when launching the terminal.
@@ -1180,6 +1356,21 @@ Run the `theme selector: toggle` action in the command palette to see a current
`boolean` values
## Calls
- Description: Customise behaviour when participating in a call
- Setting: `calls`
- Default:
```json
"calls": {
// Join calls with the microphone live by default
"mute_on_join": false,
// Share your project when you are the first to join a channel
"share_on_join": true
},
```
## An example configuration:
```json

View File

@@ -0,0 +1,41 @@
# Zed Releases
Zed currently maintains two public releases for macOS:
- [Stable](https://zed.dev/download). This is the primary version that people download and use.
- [Preview](https://zed.dev/releases/preview), which receives updates a week ahead of stable for early adopters.
Typically we cut a new minor release every Wednesday. The current Preview becomes Stable, and the new Preview contains everything on main up until that point.
If bugs are found and fixed during the week, they may be cherry-picked into the release branches and so new patch versions for preview and stable can become available throughout the week.
## Wednesday release process
You will need write access to the Zed repository to do this:
- Checkout `main` and ensure your working copy is clean.
- Run `./script/bump-zed-minor-versions` and push the tags
and branches as instructed.
- Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes)
- Copy the release notes from the previous Preview release(s) to the current Stable release.
- Write new release notes for Preview. `/script/get-preview-channel-changes` can help with this, but you'll need to edit and format the output to make it good.
- Download the artifacts for each release and test that you can run them locally.
- Publish the releases.
## Patch release process
If your PR fixes a panic or a crash, you should cherry-pick it to the current stable and preview branches. If your PR fixes a regression in recently released code, you should cherry-pick it to the appropriate branch.
You will need write access to the Zed repository to do this:
- Cherry pick them onto the correct branch. You can either do this manually, or leave a comment of the form `/cherry-pick v0.XXX.x` on the PR, and the GitHub bot should do it for you.
- Run `./script/trigger-release {preview|stable}`
- Wait for the builds to appear at https://github.com/zed-industries/zed/releases (typically takes around 30 minutes)
- Add release notes using the `Release notes:` section of each cherry-picked PR.
- Download the artifacts for each release and test that you can run them locally.
- Publish the release.
## Nightly release process
- Merge your changes to main
- Run `./script/trigger-release {nightly}`

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_clojure"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "clojure"
name = "Clojure"
description = "Clojure support."
version = "0.0.1"
version = "0.0.2"
schema_version = 1
authors = ["Paulo Roberto de Oliveira Castro <p.oliveira.castro@gmail.com>"]
repository = "https://github.com/zed-industries/zed"

View File

@@ -11,17 +11,16 @@ impl ClojureExtension {
config: zed::LanguageServerConfig,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("clojure-lsp") {
return Ok(path);
}
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
if let Some(path) = worktree.which("clojure-lsp") {
self.cached_binary_path = Some(path.clone());
return Ok(path);
}
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_csharp"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "csharp"
name = "C#"
description = "C# support."
version = "0.0.1"
version = "0.0.2"
schema_version = 1
authors = ["fminkowski <fminkowski@gmail.com>"]
repository = "https://github.com/zed-industries/zed"

View File

@@ -11,17 +11,16 @@ impl CsharpExtension {
config: zed::LanguageServerConfig,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("OmniSharp") {
return Ok(path);
}
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
if let Some(path) = worktree.which("OmniSharp") {
self.cached_binary_path = Some(path.clone());
return Ok(path);
}
zed::set_language_server_installation_status(
&config.name,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,

View File

@@ -11,5 +11,5 @@ name = "Dart LSP"
language = "Dart"
[grammars.dart]
repository = "https://github.com/agent3bood/tree-sitter-dart"
commit = "48934e3bf757a9b78f17bdfaa3e2b4284656fdc7"
repository = "https://github.com/UserNobody14/tree-sitter-dart"
commit = "6da46473ab8accb13da48113f4634e729a71d335"

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_gleam"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "gleam"
name = "Gleam"
description = "Gleam support."
version = "0.1.0"
version = "0.1.1"
schema_version = 1
authors = ["Marshall Bowers <elliott.codes@gmail.com>"]
repository = "https://github.com/zed-industries/zed"

View File

@@ -13,17 +13,16 @@ impl GleamExtension {
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("gleam") {
return Ok(path);
}
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
if let Some(path) = worktree.which("gleam") {
self.cached_binary_path = Some(path.clone());
return Ok(path);
}
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_lua"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -3,10 +3,7 @@ name = "Lua"
description = "Lua support."
version = "0.0.2"
schema_version = 1
authors = [
"Max Brunsfeld <max@zed.dev>",
"Marshall Bowers <elliott.codes@gmail.com>"
]
authors = ["Max Brunsfeld <max@zed.dev>"]
repository = "https://github.com/zed-industries/zed"
[language_servers.lua-language-server]

View File

@@ -13,17 +13,16 @@ impl LuaExtension {
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("lua-language-server") {
return Ok(path);
}
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
if let Some(path) = worktree.which("lua-language-server") {
self.cached_binary_path = Some(path.clone());
return Ok(path);
}
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
@@ -47,7 +46,7 @@ impl LuaExtension {
},
arch = match arch {
zed::Architecture::Aarch64 => "arm64",
zed::Architecture::X8664 => "x86_64",
zed::Architecture::X8664 => "x64",
zed::Architecture::X86 => return Err("unsupported platform x86".into()),
},
);

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_terraform"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "terraform"
name = "Terraform"
description = "Terraform support."
version = "0.0.1"
version = "0.0.2"
schema_version = 1
authors = ["Caius Durling <dev@caius.name>", "Daniel Banck <dbanck@users.noreply.github.com>"]
repository = "https://github.com/zed-industries/zed"

View File

@@ -12,17 +12,16 @@ impl TerraformExtension {
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = worktree.which("terraform-ls") {
return Ok(path);
}
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
if let Some(path) = worktree.which("terraform-ls") {
self.cached_binary_path = Some(path.clone());
return Ok(path);
}
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,

View File

@@ -1,6 +1,6 @@
[package]
name = "zed_zig"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
publish = false
license = "Apache-2.0"

View File

@@ -1,7 +1,7 @@
id = "zig"
name = "Zig"
description = "Zig support."
version = "0.1.0"
version = "0.1.1"
schema_version = 1
authors = ["Allan Calix <contact@acx.dev>"]
repository = "https://github.com/zed-industries/zed"

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