Compare commits

..

97 Commits

Author SHA1 Message Date
Nate Butler
3772a467e0 Remove logs 2024-10-07 15:14:54 -04:00
Nate Butler
1be27e7270 wip first approach at advanced entries 2024-10-07 15:14:34 -04:00
Marshall Bowers
7d380e9e18 Temporarily prevent deploying collab to production (#18825)
This PR adds a temporary measure to prevent deploying collab to
production, while we investigate some issues stemming from the HTTP
client change.

Release Notes:

- N/A
2024-10-07 14:31:23 -04:00
Piotr Osiewicz
60c12a8d06 ssh: Remove old dev servers code paths (#18823)
Closes #ISSUE

Release Notes:

- N/A
2024-10-07 19:18:44 +02:00
Marshall Bowers
11206a8444 ui: Fix avatar indicators getting cut off (#18821)
This PR fixes an issue introduced in #18810 that was causing the avatar
indicators to get cut off.

Release Notes:

- N/A
2024-10-07 12:53:11 -04:00
Marshall Bowers
c83690ff14 storybook: Wire up HTTP client (#18818)
This PR wires up the HTTP client in the Storybook.

Release Notes:

- N/A
2024-10-07 12:29:10 -04:00
Marshall Bowers
d1a758708d php: Bump to v0.2.1 (#18815)
This PR bumps the PHP extension to v0.2.1.

Changes:

- https://github.com/zed-industries/zed/pull/18368
- https://github.com/zed-industries/zed/pull/18774

Release Notes:

- N/A
2024-10-07 10:23:16 -04:00
Marshall Bowers
7c7151551a proto: Bump to v0.2.0 (#18814)
This PR bumps the Protobuf extension to v0.2.0.

Changes:

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

Release Notes:

- N/A
2024-10-07 10:11:12 -04:00
Bennet Bo Fenner
a3b63448df ssh: Do not cancel connection process if user is typing password (#18812)
Previously, the connection process would be cancelled after 10 seconds,
even if the connection was established successfully but the user was
still typing in a password.
We know recognize when the user is prompted for a password, and cancel
the timeout task.

Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-07 15:53:32 +02:00
Nate Butler
65c9b15796 Remove avatar shape (#18810)
This PR re-removes `AvatarShape` as it is unused. The previous time it
was removed incorrectly, resulting in square avatars!

Release Notes:

- N/A
2024-10-07 09:23:40 -04:00
Bennet Bo Fenner
25a97a6a2b ssh: Detect timeouts when server is unresponsive (#18808)
To detect connection timeouts we ping the remote server every X seconds
and attempt to reconnect if the server failed to respond.
Next up is showing some feedback in the UI to make this visible to the
user, and stop reconnecting after X amount of retries.

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-07 15:08:16 +02:00
Piotr Osiewicz
5aa165c530 ssh: Overhaul remoting UI (#18727)
Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2024-10-07 15:01:50 +02:00
Thorsten Ball
9c5bec5efb formatting: Use project environment to find external formatters (#18611)
Closes #18261

This makes sure that we find external formatters in the project
environment.

TODO:

- [x] Use a different type for the triplet of `(buffer_handle,
buffer_path, buffer_env)`. Something like `FormattableBuffer`.
- [x] Test this!!

Release Notes:

- Fixed external formatters not being found, even when they were
available in the `$PATH` of a project.

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-07 12:24:12 +02:00
Thorsten Ball
c03b8d6c48 ssh remoting: Enable reconnecting after connection losses (#18586)
Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-07 11:40:59 +02:00
Danilo Leal
67fbdbbed6 Put back code that makes the avatar rounded (#18799)
Follow-up to https://github.com/zed-industries/zed/pull/18768

---

Release Notes:

- N/A
2024-10-07 05:42:48 -03:00
Piotr Osiewicz
03c84466c2 chore: Fix some violations of 'needless_pass_by_ref_mut' lint (#18795)
While this lint is allow-by-default, it seems pretty useful to get rid
of mutable borrows when they're not needed.

Closes #ISSUE

Release Notes:

- N/A
2024-10-07 01:29:58 +02:00
Agustin Gomes
59f0f4ac42 Fix script/linux on RHEL/Fedora (#18788)
- Add missing `/etc/os-release` from a grep call
- Remove typo `grep grep` from another.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-10-06 14:47:48 -04:00
Peter Tripp
bd746145b0 ci: Make docs-only PRs only trigger docs-related tests (#18744)
This should speed up any docs-only PRs so that they don't have to run the full 5 minute battery of tests.

Release Notes:

- N/A
2024-10-06 10:28:39 -04:00
Peter Tripp
1b06c70a76 Fix alt-t context (#18783)
- Fix incorrect context introduced in https://github.com/zed-industries/zed/pull/18749/

Release Notes:

- N/A
2024-10-06 10:26:26 -04:00
Peter
06bd2431d2 proto: Add language server support (#18763)
Closes #18762

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-06 10:12:06 -04:00
Roman Zipp
200b2bf70a php: Add syntax highlighting for Intelephense completions (#18774)
Release Notes:

- N/A

This PR introduces syntax highlighting for intelephense autocomple. The
styling was selected to roughly match PHPStorm's default scheme.

Please note that I'm not very familiar with writing Rust, but I'm happy
to adapt to any requested changes!

## Examples

### Object attributes, methods and constants

![Screenshot 2024-10-06 at 13 38
03](https://github.com/user-attachments/assets/a91634ff-0f2e-41f0-b548-ecb09c40947c)
![Screenshot 2024-10-06 at 13 38
11](https://github.com/user-attachments/assets/b6f179f4-898b-4d82-9d36-a3e82328325c)

### Typed enum members

![Screenshot 2024-10-06 at 13 38
53](https://github.com/user-attachments/assets/7133b981-4f68-4210-b233-403cdf3ec9bb)
![Screenshot 2024-10-06 at 13 38
41](https://github.com/user-attachments/assets/2e806f3d-3538-45f2-b075-b8be5902b786)

### Variables

Includes altered highlighting for [reserved variable
names](https://www.php.net/manual/en/reserved.variables.php).

![Screenshot 2024-10-06 at 13 39
30](https://github.com/user-attachments/assets/be426eb8-5879-432d-b302-391c2c68a7cb)

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-06 10:11:21 -04:00
Nate Butler
8376dd2011 ui crate docs & spring cleaning (#18768)
Similar to https://github.com/zed-industries/zed/pull/18690 &
https://github.com/zed-industries/zed/pull/18695, this PR enables
required docs for `ui` and does some cleanup.

Changes:
- Enables the `deny(missing_docs)` crate-wide.
- Adds `allow(missing_docs)` on many modules until folks pick them up to
document them
- Documents some modules (all in `ui/src/styles`)
- Crate root-level organization: Traits move to `traits`, other misc
organization
- Cleaned out a bunch of unused code.

Note: I'd like to remove `utils/format_distance` but the assistant panel
uses it. To move it over to use the `time_format` crate we may need to
update it to use `time` instead of `chrono`. Needs more investigation.

Release Notes:

- N/A
2024-10-05 23:28:34 -04:00
Chris Boette
c9bee9f81f docs: Note the need for Rust when developing extensions (#18753) 2024-10-05 12:26:28 -04:00
Kirill Bulatov
1f31022cbe Compare migrations formatted uniformly (#18760)
Otherwise old migrations may be formatted differently than new
migrations, causing comparison errors.

Follow-up of https://github.com/zed-industries/zed/pull/18676

Release Notes:

- N/A
2024-10-05 12:58:45 +03:00
Peter Tripp
7608000df8 Fix option-t and option-shift-t in terminal (#18749) 2024-10-04 16:56:01 -04:00
Remco Smits
8f27ffda4d gpui: Fix uniform list horizon offset for non-horizontal scrollable lists (#18748)
Closes #18739

/cc @osiewicz 
/cc @maxdeviant 

I'm not sure why the `+ padding.left` was added, but this was the cause
of the issue. I also tested removing the extra left padding but didn't
seem to see a difference inside the project panel. So we can maybe even
remove it?

**Before:**
![Screenshot 2024-10-04 at 21 43
34](https://github.com/user-attachments/assets/b5d67cd9-f92b-4301-880c-d351fe156c98)

**After:**
<img width="294" alt="Screenshot 2024-10-04 at 21 49 05"
src="https://github.com/user-attachments/assets/8cc84170-a86b-46b8-91c9-39def64f0bd0">

Release Notes:

- Fix code action list not horizontal aligned correctly
2024-10-04 23:07:58 +03:00
Marshall Bowers
cee019b1ea editor: Qualify RangeExt::overlaps call to prevent phantom diagnostics (#18743)
This PR qualifies a call to `RangeExt::overlaps` to avoid some confusion
in rust-analyzer not being able to distinguish between
`RangeExt::overlaps` and `AnchorRangeExt::overlaps` and producing
phantom diagnostics.

We may also want to consider renaming the method on `AnchorRangeExt` to
disambiguate them.

Release Notes:

- N/A
2024-10-04 15:06:05 -04:00
Boris Cherny
01ad22683d telemetry: Add language_name and model_provider (#18640)
This PR adds a bit more metadata for assistant logging.

Release Notes:

- Assistant: Added `language_name` and `model_provider` fields to
telemetry events.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Max <max@zed.dev>
2024-10-04 14:37:27 -04:00
Peter Tripp
dfe1e43832 docs: Linux XDG desktop secrets portals 2024-10-04 14:13:07 -04:00
Marshall Bowers
e3a6f89e2d Make report_assistant_event take an AssistantEvent struct (#18741)
This PR makes the `report_assistant_event` method take an
`AssistantEvent` struct instead of all of the struct fields as
individual parameters.

Release Notes:

- N/A
2024-10-04 13:19:18 -04:00
Peter Tripp
07e808d16f Document File Scan Exclusions (#18738)
Release Notes:

- N/A
2024-10-04 12:07:43 -04:00
Muhammad Talal Anwar
2f7430af70 c: Add runnable for main function (#18720)
Release Notes:

- Added Runnable for C main function

This tags can then be used in tasks, for example:

```json
[
  {
    "label": "Run ${ZED_STEM}",
    "command": "gcc",
    "args": [
      "$ZED_FILE",
      "-o",
      "${ZED_DIRNAME}/${ZED_STEM}.out",
      "&&",
      "${ZED_DIRNAME}/${ZED_STEM}.out"
    ],
    "tags": ["c-main"]
  }
]

```
2024-10-04 17:28:12 +02:00
renovate[bot]
d012e35b04 Update Rust crate parking to v2.2.1 (#18664)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [parking](https://redirect.github.com/smol-rs/parking) | dependencies
| patch | `2.2.0` -> `2.2.1` |

---

### Release Notes

<details>
<summary>smol-rs/parking (parking)</summary>

###
[`v2.2.1`](https://redirect.github.com/smol-rs/parking/blob/HEAD/CHANGELOG.md#Version-221)

[Compare
Source](https://redirect.github.com/smol-rs/parking/compare/v2.2.0...v2.2.1)

- Specify the reason for using `parking` in the docs.
([#&#8203;25](https://redirect.github.com/smol-rs/parking/issues/25))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 11:09:20 -04:00
Daste
d695de4504 tab_switcher: Use git-aware colors for file icons (#18733)
Release Notes:

- Fixed tab switcher icons not respecting the `tabs.git_status` setting.

Fixes an issue mentioned in
https://github.com/zed-industries/zed/pull/17115#issuecomment-2378966170
- file icons in the tab switcher weren't colored according to git
status, even if `tabs.git_status` was set to true.

I used a similar approach I saw in other places of the project to get
the project entry and its git status, but maybe we could move the
coloring logic entirely to `tab_icon()`? Wouldn't this break anything?

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-04 10:37:41 -04:00
renovate[bot]
9702310737 Update Rust crate sqlformat to v0.2.6 (#18676)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sqlformat](https://redirect.github.com/shssoichiro/sqlformat-rs) |
dependencies | patch | `0.2.4` -> `0.2.6` |

---

### Release Notes

<details>
<summary>shssoichiro/sqlformat-rs (sqlformat)</summary>

###
[`v0.2.6`](https://redirect.github.com/shssoichiro/sqlformat-rs/blob/HEAD/CHANGELOG.md#Version-026)

[Compare
Source](https://redirect.github.com/shssoichiro/sqlformat-rs/compare/v0.2.5...v0.2.6)

- fix: ON UPDATE with two many blank formatted incorrectly
([#&#8203;46](https://redirect.github.com/shssoichiro/sqlformat-rs/issues/46))
-   fix: `EXCEPT` not handled well
- fix: REFERENCES xyz ON UPDATE .. causes formatter to treat the
remaining as an UPDATE statement
-   fix: Escaped strings formatted incorrectly
-   fix: RETURNING is not placed on a new line
- fix: fix the issue of misaligned comments after formatting
([#&#8203;40](https://redirect.github.com/shssoichiro/sqlformat-rs/issues/40))

###
[`v0.2.5`](https://redirect.github.com/shssoichiro/sqlformat-rs/compare/v0.2.4...v0.2.5)

[Compare
Source](https://redirect.github.com/shssoichiro/sqlformat-rs/compare/v0.2.4...v0.2.5)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-04 10:36:30 -04:00
Piotr Osiewicz
bafd7ed000 gpui: Store measure functions as context of taffy nodes (#18732)
Taffy maintains a mapping of NodeId <-> Context anyways (and does the
lookup), so it's redundant for us to store it separately. Tl;dr: we get
rid of one map and one map lookup per layout request.

Release Notes:

- N/A
2024-10-04 13:58:57 +02:00
Piotr Osiewicz
37ded190cf gpui: Use taffy to retrieve the parent for a given layout node (#18730)
Again. https://github.com/zed-industries/zed/pull/4070

Let's see how it goes this time around. The only thing that might've
been related to that revert on our Slack was about crashing in collab
panel.

Release Notes:

- N/A
2024-10-04 12:37:55 +02:00
Piotr Osiewicz
a99750fd35 chore: Bump taffy to 0.5.2 (#18729)
Release Notes:

- N/A
2024-10-04 12:37:44 +02:00
Ömer Sinan Ağacan
e2647025ac Add vim::MoveTo{Next,Prev} flags for regex and case sensitive search (#18429)
This makes the hard-coded regex and case-sensitive search flags in
`vim::MoveToNext` and `vim::MoveToPrev` commands configurable in key
bindings.

Example:

```json
{
  "context": "VimControl && !menu",
  "bindings": {
    "*": ["vim::MoveToNext", { "regex": false, "caseSensitive": false }],
    "#": ["vim::MoveToPrev", { "regex": false, "caseSensitive": false }]
  }
}
```

Closes #15837.

Release Notes:

- Added `regex` and `caseSensitive` arguments to `vim::MoveToNext` and
`vim ::MoveToPrev` commands, for toggling regex and case sensitive
search.
2024-10-04 09:10:26 +02:00
Marshall Bowers
6635758009 vcs_menu: Streamline branch creation from branch selector (#18712)
This PR streamlines the branch creation from the branch selector when
searching for a branch that does not exist.

The branch selector will show the available branches, as it does today:

<img width="576" alt="Screenshot 2024-10-03 at 4 01 25 PM"
src="https://github.com/user-attachments/assets/e1904f5b-4aad-4f88-901d-ab9422ec18bb">

When entering the name of a branch that does not exist, the picker will
be populated with an entry to create a new branch:

<img width="570" alt="Screenshot 2024-10-03 at 4 01 37 PM"
src="https://github.com/user-attachments/assets/07f8d12c-9422-4fd8-a6dc-ae450e297a13">

Selecting that entry will create the branch and switch to it.

Release Notes:

- Streamlined creating a new branch from the branch selector.
2024-10-03 16:18:28 -04:00
Junkui Zhang
8d6fa9526e windows: Fix sometimes log error messages don't show the crate name (#18706)
On windows, path could be something like `C:\path\to\the\crate`. Hence,
`split('/')` would refuse to work in this case.

### Before

![Screenshot 2024-10-04
023652](https://github.com/user-attachments/assets/9c14fb24-5ee0-4b56-8fbd-313abb28f134)

### After

![Screenshot 2024-10-04
024115](https://github.com/user-attachments/assets/217e175c-b0e1-4589-9c3d-98670882b185)


Release Notes:

- N/A
2024-10-03 13:00:33 -07:00
Marshall Bowers
fd22c9bef9 editor: Use predefined rounding value for color swatches (#18708)
This PR updates the color swatches added in #18665 to use a predefined
`rounding` value instead of a literal value.

The underlying values are the same, but we don't want to diverge from
our design system.

Release Notes:

- N/A
2024-10-03 15:20:41 -04:00
Joseph T. Lyons
43d05a432b Close stale issues out after 7 days (#18707)
Closes #ISSUE

Release Notes:

- N/A
2024-10-03 14:38:49 -04:00
Jordan Pittman
cac98b7bbf Show color swatches for LSP completions (#18665)
Closes #11991

Release Notes:

- Added support for color swatches for language server completions.

<img width="502" alt="Screenshot 2024-10-02 at 19 02 22"
src="https://github.com/user-attachments/assets/57e85492-3760-461a-9b17-a846dc40576b">

<img width="534" alt="Screenshot 2024-10-02 at 19 02 48"
src="https://github.com/user-attachments/assets/713ac41c-16f0-4ad3-9103-d2c9b3fa8b2e">

This implementation is mostly a port of the VSCode version of the
ColorExtractor. It seems reasonable the we should support _at least_
what VSCode does for detecting color swatches from LSP completions.

This implementation could definitely be better perf-wise by writing a
dedicated color parser. I also think it would be neat if, in the future,
Zed handled _more_ color formats — especially wide-gamut colors.

There are a few differences to the regexes in the VSCode implementation
but mainly so simplify the implementation :
- The hex vs rgb/hsl regexes were split into two parts
- The rgb/hsl regexes allow 3 or 4 color components whether hsla/rgba or
not and the parsing implementation accepts/rejects colors as needed

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-03 14:38:17 -04:00
Marshall Bowers
cddd7875a4 Extract Protocol Buffers support into an extension (#18704)
This PR extracts the Protocol Buffers support into an extension.

Release Notes:

- Removed built-in support for Protocol Buffers, in favor of making it
available as an extension. The Protocol Buffers extension will be
suggested for download when you open a `.proto` file.
2024-10-03 13:37:43 -04:00
Nate Butler
8c95b8d89a theme crate spring cleaning (#18695)
This PR does some spring cleaning on the `theme` crate:

- Removed two unused stories and the story dep
- Removed the `one` theme family (from the `theme` crate, not the app),
this is now `zed_default_themes`.
- This will hopefully remove some confusion caused by this theme we
started in rust but didn't end up using
- Removed `theme::prelude` (it just re-exported scale colors, which we
don't use outside `theme`)
- Removed completely unused `zed_pro` themes (we started on these during
the gpui2 port and didn't finish them.)

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-03 13:17:31 -04:00
Marshall Bowers
a9f816d5fb telemetry_events: Update crate-level docs (#18703)
This PR updates the `telemetry_events` crate to use module-level
documentation for its crate-level docs.

Release Notes:

- N/A
2024-10-03 12:38:51 -04:00
renovate[bot]
f7b3680e4d Update Rust crate pretty_assertions to v1.4.1 (#18668)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[pretty_assertions](https://redirect.github.com/rust-pretty-assertions/rust-pretty-assertions)
| workspace.dependencies | patch | `1.4.0` -> `1.4.1` |

---

### Release Notes

<details>
<summary>rust-pretty-assertions/rust-pretty-assertions
(pretty_assertions)</summary>

###
[`v1.4.1`](https://redirect.github.com/rust-pretty-assertions/rust-pretty-assertions/blob/HEAD/CHANGELOG.md#v141)

[Compare
Source](https://redirect.github.com/rust-pretty-assertions/rust-pretty-assertions/compare/v1.4.0...v1.4.1)

#### Fixed

- Show feature-flagged code in documentation. Thanks to
[@&#8203;sandydoo](https://redirect.github.com/sandydoo) for the fix!
([#&#8203;130](https://redirect.github.com/rust-pretty-assertions/rust-pretty-assertions/pull/130))

#### Internal

- Bump `yansi` version to `1.x`. Thanks to
[@&#8203;SergioBenitez](https://redirect.github.com/SergioBenitez) for
the update, and maintaining this library!
([#&#8203;121](https://redirect.github.com/rust-pretty-assertions/rust-pretty-assertions/pull/121))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 11:32:04 -04:00
renovate[bot]
ded3d3fc14 Update Python to v3.12.7 (#18652)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [python](https://redirect.github.com/containerbase/python-prebuild) |
dependencies | patch | `3.12.6` -> `3.12.7` |

---

### Release Notes

<details>
<summary>containerbase/python-prebuild (python)</summary>

###
[`v3.12.7`](https://redirect.github.com/containerbase/python-prebuild/releases/tag/3.12.7)

[Compare
Source](https://redirect.github.com/containerbase/python-prebuild/compare/3.12.6...3.12.7)

##### Bug Fixes

-   **deps:** update dependency python to v3.12.7

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 11:29:29 -04:00
Danilo Leal
ddcd45bb45 docs: Add tweaks to the outline panel page (#18697)
Thought we could be extra clear here with the meaning of "singleton
buffers".

Release Notes:

- N/A
2024-10-03 12:27:42 -03:00
renovate[bot]
29796aa412 Update Rust crate serde_json to v1.0.128 (#18669)
This PR contains the following updates:

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

---

### Release Notes

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

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

[Compare
Source](https://redirect.github.com/serde-rs/json/compare/1.0.127...1.0.128)

- Support serializing maps containing 128-bit integer keys to
serde_json::Value
([#&#8203;1188](https://redirect.github.com/serde-rs/json/issues/1188),
thanks [@&#8203;Mrreadiness](https://redirect.github.com/Mrreadiness))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-03 11:14:22 -04:00
Nate Butler
773ad6bfd1 Document the theme crate (#18690)
This PR enables required documentation for the `theme` crate starts on
documenting it.

The end goal is to have all meaningful documentation in the crate filled
out – However I'm not sure that just adding `#![deny(missing_docs)]` to
the whole crate is the right approach.

I don't know that having 200+ "The color of the _ color" field docs is
useful however–In the short term I've excluded some of the modules that
contain structs with a ton of fields (`colors, `status`, etc.) until we
decide what the right solution here is.

Next steps are to clean up the crate, removing unused modules or those
with low usage in favor of other approaches.

Changes in this PR:
- Enable the `deny(missing_docs)` lint for the `theme` crate 
- Start documenting a subset of the crate.
- Enable `#![allow(missing_docs)]` for some modules.


Release Notes:

- N/A
2024-10-03 10:27:19 -04:00
Danilo Leal
dc85378b96 Clean up style properties on hunk controls (#18639)
This PR removes some duplicate style properties on the hunk controls,
namely padding, border, and background color.

Release Notes:

- N/A
2024-10-03 11:23:56 -03:00
Kirill Bulatov
1e8297a469 Remove a debug dev config line (#18689)
Follow-up of https://github.com/zed-industries/zed/pull/18645

Release Notes:

- N/A
2024-10-03 15:38:42 +03:00
renovate[bot]
9cd42427d8 Update Rust crate thiserror to v1.0.64 (#18677)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [thiserror](https://redirect.github.com/dtolnay/thiserror) |
workspace.dependencies | patch | `1.0.63` -> `1.0.64` |

---

### Release Notes

<details>
<summary>dtolnay/thiserror (thiserror)</summary>

###
[`v1.0.64`](https://redirect.github.com/dtolnay/thiserror/releases/tag/1.0.64)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

- Exclude derived impls from coverage instrumentation
([#&#8203;322](https://redirect.github.com/dtolnay/thiserror/issues/322),
thanks [@&#8203;oxalica](https://redirect.github.com/oxalica))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-02 23:28:00 -04:00
Joseph T. Lyons
df21fe174d Add command palette action name to outline panel docs (#18678)
Release Notes:

- N/A
2024-10-02 22:16:56 -04:00
Joseph T. Lyons
c48d4dbc6b Add basic outline panel docs (#18674)
Bandaid to: https://github.com/zed-industries/zed/issues/18672

Release Notes:

- Added basic outline panel docs
2024-10-02 22:06:07 -04:00
Piotr Osiewicz
19b186671b ssh: Add session state indicator to title bar (#18645)
![image](https://github.com/user-attachments/assets/0ed6f59c-e0e7-49e6-8db7-f09ec5cdf653)
The indicator turns yellow when ssh client is trying to reconnect. Note
that the state tracking is probably not ideal (we'll see how it pans out
once we start dog-fooding), but at the very least "green=good" should be
a decent mental model for now.

Release Notes:

- N/A
2024-10-03 00:35:56 +02:00
renovate[bot]
e2d613a803 Update Rust crate clap to v4.5.19 (#18660)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [clap](https://redirect.github.com/clap-rs/clap) |
workspace.dependencies | patch | `4.5.18` -> `4.5.19` |

---

### Release Notes

<details>
<summary>clap-rs/clap (clap)</summary>

###
[`v4.5.19`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4519---2024-10-01)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.5.18...v4.5.19)

##### Internal

-   Update dependencies

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-02 17:39:32 -04:00
Marshall Bowers
6f4385e737 Sort dependencies in Cargo.toml files (#18657)
This PR sorts the dependencies in various `Cargo.toml` files after
#18414.

Release Notes:

- N/A
2024-10-02 16:26:48 -04:00
Marshall Bowers
9565a90528 collab: Revert changes to Clickhouse event rows (#18654)
This PR reverts the changes to the Clickhouse event rows that were
included in https://github.com/zed-industries/zed/pull/18414.

The changes don't seem to be correct, as they make the row structs
differ from the underlying table schema.

Release Notes:

- N/A
2024-10-02 16:10:25 -04:00
Conrad Irwin
3a5deb5c6f Replace isahc with async ureq (#18414)
REplace isahc with ureq everywhere gpui is used.

This should allow us to make http requests without libssl; and avoid a
long-tail of panics caused by ishac.

Release Notes:

- (potentially breaking change) updated our http client

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-02 12:30:48 -07:00
renovate[bot]
f809787275 Update cloudflare/wrangler-action digest to 168bc28 (#18651)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cloudflare/wrangler-action](https://redirect.github.com/cloudflare/wrangler-action)
| action | digest | `f84a562` -> `168bc28` |

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-02 15:23:22 -04:00
Kirill Bulatov
778dedec6c Prepare to sync other kinds of settings (#18616)
This PR does not change how things work for settings, but lays the
ground work for the future functionality.
After this change, Zed is prepared to sync more than just
`settings.json` files from local worktree and user config.

* ssh tasks

Part of this work is to streamline the task sync mechanism.
Instead of having an extra set of requests to fetch the task contents
from the server (as remote-via-collab does now and does not cover all
sync cases), we want to reuse the existing mechanism for synchronizing
user and local settings.

* editorconfig

Part of the task is to sync .editorconfig file changes to everyone which
involves sending and storing those configs.


Both ssh (and remove-over-collab) .zed/tasks.json and .editorconfig
files behave similar to .zed/settings.json local files: they belong to a
certain path in a certain worktree; may update over time, changing Zed's
functionality; can be merged hierarchically.
Settings sync follows the same "config file changed -> send to watchers
-> parse and merge locally and on watchers" path that's needed for both
new kinds of files, ergo the messaging layer is extended to send more
types of settings for future watch & parse and merge impls to follow.

Release Notes:

- N/A
2024-10-02 22:00:40 +03:00
Marshall Bowers
7c4615519b editor: Ensure proposed changes editor is syntax-highlighted when opened (#18648)
This PR fixes an issue where the proposed changes editor would not have
any syntax highlighting until a modification was made.

When creating the branch buffer we reparse the buffer to rebuild the
syntax map.

Release Notes:

- N/A
2024-10-02 14:23:59 -04:00
Marshall Bowers
0e8276560f language: Update buffer doc comments (#18646)
This PR updates the doc comments in `buffer.rs` to use the standard
style for linking to other items.

Release Notes:

- N/A
2024-10-02 14:10:19 -04:00
Mikayla Maki
209ebb0c65 Revert "Fix blurry cursor on Wayland at a scale other than 100%" (#18642)
Closes #17771

Reverts zed-industries/zed#17496

This PR turns out to need more work than I thought when I merged it. 

Release Notes:

- Linux: Fix a bug where the cursor would be the wrong size on Wayland
2024-10-02 10:44:16 -07:00
Danilo Leal
a5f50e5c1e Tweak warning diagnostic toggle (#18637)
This PR adds color to the warning diagnostic toggle, so that, if it's
turned on, the warning icon is yellow. And, in the opposite case, it's
muted.

| Turned on | Turned off |
|--------|--------|
| <img width="1136" alt="Screenshot 2024-10-02 at 6 08 30 PM"
src="https://github.com/user-attachments/assets/be64738b-4c14-41d4-b1d4-ad788cf9e72b">
| <img width="1136" alt="Screenshot 2024-10-02 at 6 08 36 PM"
src="https://github.com/user-attachments/assets/d144ff50-4bf6-4c23-925a-05bcbbcd8b9d">
|

---

Release Notes:

- N/A
2024-10-02 13:57:20 -03:00
Danilo Leal
5aaaed52fc Adjust spacing and sizing of buffer search bar icon buttons (#18638)
This PR mostly makes all of the search bar icon buttons all squared and
adjusts the spacing between them, as well as the additional input that
appears when you toggle the "Replace all" action.

<img width="900" alt="Screenshot 2024-10-02 at 6 08 30 PM"
src="https://github.com/user-attachments/assets/86d50a3b-94bd-4c6a-822e-5f7f7b2e2707">

---

Release Notes:

- N/A
2024-10-02 13:57:03 -03:00
Junseong Park
845991c0e5 docs: Add missing UI font settings to "Configuring Zed" (#18267)
- Add missing `ui_font` options in `configuring-zed.md`

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-02 12:35:35 -04:00
Marshall Bowers
167af4bc1d Use const over static for string literals (#18635)
I noticed a few places where we were storing `&'static str`s in
`static`s instead of `const`s.

This PR updates them to use `const`.

Release Notes:

- N/A
2024-10-02 12:33:13 -04:00
Victor Roetman
2cd12f84de docs: Add FIPS mode error to Linux troubleshooting (#18407)
- Closes: #18335
Update linux.md with a workaround for the
```
crypto/fips/fips.c:154: OpenSSL internal error: FATAL FIPS SELFTEST FAILURE
```
error when using bundled libssl and libcrypto.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-10-02 12:18:41 -04:00
Joseph T Lyons
028d7a624f v0.157.x dev 2024-10-02 11:03:57 -04:00
Marshall Bowers
cfd61f9337 Clean up formatting in Cargo.toml (#18632)
This PR cleans up some formatting in some `Cargo.toml` files.

Release Notes:

- N/A
2024-10-02 10:38:23 -04:00
Marshall Bowers
21336eb124 docs: Add note about forking the extensions repo to a personal GitHub account (#18631)
This PR adds a note to the docs encouraging folks to fork the
`zed-industries/extensions` repo to a personal GitHub account rather
than a GitHub organization, as this makes life easier for everyone.

Release Notes:

- N/A
2024-10-02 10:10:53 -04:00
Danilo Leal
8a18c94f33 Make slash command descriptions consistent (#18595)
This PR adds a description constant in most of the slash command files
so that both the editor _and_ footer pickers use the same string. In
terms of copywriting, I did some tweaking to reduce the longer ones a
bit. Also standardized them all to use sentence case, as opposed to each
instance using a different convention. The editor picker needs more
work, though, given the arguments and descriptions are being cut at the
moment. This should happen in a follow-up!

<img width="900" alt="Screenshot 2024-10-01 at 7 25 19 PM"
src="https://github.com/user-attachments/assets/e8759eff-0de9-4a4d-a026-366d85507b3c">

---

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-02 10:35:50 -03:00
Roy Williams
82d3fcdf4b Tweak assistant prompt to only fix diagnostic issues when requested to do so (#18596)
Release Notes:

- Assistant: Make the model less likely to incorporate diagnostic
information when not requested to fix any issues.
 
![CleanShot 2024-10-01 at 13 44
08](https://github.com/user-attachments/assets/f0e9a132-6cac-4dc6-889f-467e59ec8bbc)
2024-10-02 09:29:11 -04:00
Piotr Osiewicz
e01bc6765d editor: Fix "Reveal in File Manager" not working with multibuffers (#18626)
Additionally, mark context menu entry as disabled when the action would
fail (untitled buffer, collab sessions).

Supersedes #18584 

Release Notes:

- Fixed "Reveal in Finder/File Manager", "Copy Path", "Copy Relative
Path" and "Copy file location" actions not working with multibuffers.
2024-10-02 13:45:07 +02:00
Patrick
fd94c2b3fd Keep tab position when closing tabs (#18168)
- Closes #18036

Release Notes:

- N/A
2024-10-02 13:44:42 +02:00
loczek
0ee1d7ab26 Add snippet commands (#18453)
Closes #17860
Closes #15403

Release Notes:

- Added `snippets: configure snippets` command to create and modify
snippets
- Added `snippets: open folder` command for opening the
`~/.config/zed/snippets` directory


https://github.com/user-attachments/assets/fd9e664c-44b1-49bf-87a8-42b9e516f12f
2024-10-02 13:27:16 +02:00
Bennet Bo Fenner
b3cdd2ccff ssh remoting: Fix ssh process not being cleaned up when connection is closed (#18623)
We introduced a memory leak in #18572, which meant that `Drop` was never
called on `SshRemoteConnection`, meaning that the ssh process kept
running

Co-Authored-by: Thorsten <thorsten@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-10-02 13:21:19 +02:00
Roman Zipp
e80cbab93f Fix docs format_on_save value is not a boolean (#18619)
Fixed [Configuring
Languages](https://zed.dev/docs/configuring-languages) docs using
boolean value for `format_on_save` option although it accepts string
values of `"on"` or `"off"`

Details:

The documentation on [configuring
languages](https://zed.dev/docs/configuring-languages) states the use of
boolean values for the `format_on_save` option although the
[configuration
reference](https://zed.dev/docs/configuring-zed#format-on-save) only
allows the usage of string values `"on"` or `"off"`. In fact using
boolean values will not work and won't translate to `on` or `off`

Release Notes:

- N/A
2024-10-02 14:03:23 +03:00
Max Brunsfeld
563a1dcbab Fix panic when opening proposed changes editor with reversed ranges (#18599)
Closes https://github.com/zed-industries/zed/issues/18589

Release Notes:

- N/A

Co-authored-by: Antonio <antonio@zed.dev>
2024-10-01 12:58:21 -06:00
Max Brunsfeld
7dcb0de28c Keep all hunks expanded in proposed change editor (#18598)
Also, fix visual bug when pressing escape with a non-empty selection in
a deleted text block.

Release Notes:

- N/A

Co-authored-by: Antonio <antonio@zed.dev>
2024-10-01 12:58:12 -06:00
Junkui Zhang
9b148f3dcc Limit the value can be set for font weight (#18594)
Closes #18531



This PR limits the range of values that can be set for `FontWeight`.
Since any value less than 1.0 or greater than 999.9 causes Zed to crash
on Windows, I’ve restricted `FontWeight` to this range.

I could apply this constraint only on Windows, but considering the
documentation at https://zed.dev/docs/configuring-zed#buffer-font-weight
indicates that `FontWeight` should be between 100 and 900, I thought it
might be a good idea to apply this restriction in the settings.


Release Notes:

- Changed `ui_font_weight` and `buffer_font_weight` settings to require
values to be between `100` and `950` (inclusive).

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-01 13:32:31 -04:00
Max Brunsfeld
d14e36b323 Add an apply button to hunks in proposed changes editor (#18592)
Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
2024-10-01 11:07:52 -06:00
Marshall Bowers
eb962b7bfc editor: Include proposed changes editor in navigation history (#18593)
This PR makes it so the proposed changes editor works with the workspace
navigation history.

This allows for easily navigating back to the proposed changes editor
after opening one of the excerpts into the base buffer.

Release Notes:

- N/A
2024-10-01 13:05:50 -04:00
Marshall Bowers
280b8a89ea editor: Allow opening excerpts from proposed changes editor (#18591)
This PR adds the ability to open excerpts in the base buffer from the
proposed changes editor.

Release Notes:

- N/A
2024-10-01 12:40:18 -04:00
Kirill Bulatov
051627c449 Project panel horizontal scrollbar (#18513)
<img width="389" alt="image"
src="https://github.com/user-attachments/assets/c6718c6e-0fe1-40ed-b3db-7d576c4d98c8">


https://github.com/user-attachments/assets/734f1f52-70d9-4308-b1fc-36c7cfd4dd76

Closes https://github.com/zed-industries/zed/issues/7001
Closes https://github.com/zed-industries/zed/issues/4427
Part of https://github.com/zed-industries/zed/issues/15324
Part of https://github.com/zed-industries/zed/issues/14551

* Adjusts a `UniformList` to have a horizontal sizing behavior: the old
mode forced all items to have the size of the list exactly.
A new mode (with corresponding `ListItems` having `overflow_x` enabled)
lays out the uniform list elements with width of its widest element,
setting the same width to the list itself too.

* Using the new behavior, adds a new scrollbar into the project panel
and enhances its file name editor to scroll it during editing of long
file names

* Also restyles the scrollbar a bit, making it narrower and removing its
background

* Changes the project_panel.scrollbar.show settings to accept `null` and
be `null` by default, to inherit `editor`'s scrollbar settings. All
editor scrollbar settings are supported now.

Release Notes:

- Added a horizontal scrollbar to project panel
([#7001](https://github.com/zed-industries/zed/issues/7001))
([#4427](https://github.com/zed-industries/zed/issues/4427))

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
2024-10-01 18:32:16 +03:00
pantheraleo-7
68d6177d37 docs: Correct typo in configuring-zed.md (#18580)
Release Notes:

- N/A
2024-10-01 18:09:34 +03:00
Peter Tripp
1be24f7739 Rename proto language to Proto (#18559)
All the other languages are capitalized. Proto should be too.
2024-10-01 09:31:03 -04:00
Junkui Zhang
6336248c1a windows: Revert "Fix hide, activate method on Windows to hide/show application" (#18571)
This PR reverts the changes introduced via #18164. As shown in the video
below, once you `hide` the app, there is essentially no way to bring it
back. I must emphasize that the window logic on Windows is entirely
different from macOS. On macOS, when you `hide` an app, its icon always
remains visible in the dock, and you can always bring the hidden app
back by clicking that icon. However, on Windows, there is no such
mechanism—the app is literally hidden.

I think the `hide` feature should be macOS-only.



https://github.com/user-attachments/assets/65c8a007-eedb-4444-9499-787b50f2d1e9



Release Notes:

- N/A
2024-10-01 13:58:40 +03:00
Thorsten Ball
7ce8797d78 ssh remoting: Add infrastructure to handle reconnects (#18572)
This restructures the code in `remote` so that it's easier to replace
the current SSH connection with a new one in case of
disconnects/reconnects.

Right now, it successfully reconnects, BUT we're still missing the big
piece on the server-side: keeping the server process alive and
reconnecting to the same process that keeps the project-state.

Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-01 12:16:44 +02:00
Michael Sloan
527c9097f8 linux: Various X11 scroll improvements (#18484)
Closes  #14089, #14416, #15970, #17230, #18485

Release Notes:

- Fixed some cases where Linux X11 mouse scrolling doesn't work at all
(#14089, ##15970, #17230)
- Fixed handling of switching between Linux X11 devices used for
scrolling (#14416, #18485)

Change details:

Also includes the commit from PR #18317 so I don't have to deal with
merge conflicts.

* Now uses valuator info from slave pointers rather than master. This
hopefully fixes remaining cases where scrolling is fully
broken. https://github.com/zed-industries/zed/issues/14089,
https://github.com/zed-industries/zed/issues/15970,
https://github.com/zed-industries/zed/issues/17230

* Per-device recording of "last scroll position" used to calculate
deltas. This meant that swithing scroll devices would cause a sudden
jump of scroll position, often to the beginning or end of the
file (https://github.com/zed-industries/zed/issues/14416).

* Re-queries device metadata when devices change, so that newly
plugged in devices will work, and re-use of device-ids don't use old
metadata with a new device.

* xinput 2 documentation describes support for multiple master
devices. I believe this implementation will support that, since now it
just uses `DeviceInfo` from slave devices. The concept of master
devices is only used in registering for events.

* Uses popcount+bit masking to resolve axis indexes, instead of
iterating bit indices.

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-10-01 09:14:40 +02:00
Jason Lee
72be8c5d14 gpui: Fix hide, activate method on Windows to hide/show application (#18164)
Release Notes:

- N/A

Continue #18161 to fix `cx.hide`, `cx.activate` method on Windows to
hide/show application.

## After


https://github.com/user-attachments/assets/fe0070f9-7844-4c2a-b859-3e22ee4b8d22

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-09-30 23:20:24 -07:00
Alvaro Parker
8d795ff882 Fix file watching for symlinks (#17609)
Closes #17605

Watches for target paths if file watched is a symlink in Linux. 
This will check if the generated `notify::Event` has any paths matching
the `root_path` and if the file is a symlink it will also check if the
path matches the `target_root_path` (the path that the symlink is
pointing to)

Release Notes:

- Added file watching for symlinks
2024-09-30 23:04:35 -07:00
Jason Lee
39be9e5949 gpui: Fix show: false support on Windows to create an invisible window (#18161)
Release Notes:

- N/A
- 
The `show` of WindowOptions is valid on macOS but not on Windows, this
changes to fix it to support create an invisible window.

```bash
cargo run -p gpui --example window
```

## Before



https://github.com/user-attachments/assets/4157bdaa-39a7-44df-bbdc-30b00e9c61e9


## After



https://github.com/user-attachments/assets/d48fa524-0caa-4f87-932d-01d7a468c488


https://github.com/user-attachments/assets/dd052f15-c8db-4a2a-a6af-a7c0ffecca84
2024-09-30 18:25:02 -07:00
292 changed files with 8819 additions and 5559 deletions

View File

@@ -7,9 +7,13 @@ on:
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
paths-ignore:
- "docs/**"
pull_request:
branches:
- "**"
paths-ignore:
- "docs/**"
concurrency:
# Allow only one workflow per any non-`main` branch.
@@ -65,13 +69,13 @@ jobs:
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> $GITHUB_ENV
fi
- name: Protobuf checks
uses: bufbuild/buf-action@v1
- uses: bufbuild/buf-setup-action@v1
with:
version: v1.29.0
- uses: bufbuild/buf-breaking-action@v1
with:
version: 1.35.0
input: "crates/proto/proto/"
breaking_against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${GITHUB_BASE_REF},subdir=crates/proto/proto/"
lint: false
against: "https://github.com/${GITHUB_REPOSITORY}.git#branch=${BUF_BASE_BRANCH},subdir=crates/proto/proto/"
macos_tests:
timeout-minutes: 60

View File

@@ -14,7 +14,7 @@ jobs:
stale-issue-message: >
Hi there! 👋
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 10 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity; feel free to open a new issue if you're still experiencing this problem!"
@@ -23,7 +23,7 @@ jobs:
# 'community' to 'zed' repository. The migration added activity to all
# issues, preventing 365 days from working until then.
days-before-stale: 180
days-before-close: 10
days-before-close: 7
any-of-issue-labels: "defect,panic / crash"
operations-per-run: 1000
ascending: true

View File

@@ -36,28 +36,28 @@ jobs:
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Deploy Docs
uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3
uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy target/deploy --project-name=docs
- name: Deploy Install
uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3
uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
- name: Deploy Docs Workers
uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3
uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Deploy Install Workers
uses: cloudflare/wrangler-action@f84a562284fc78278ff9052435d9526f9c718361 # v3
uses: cloudflare/wrangler-action@168bc28b7078db16f6f1ecc26477fc2248592143 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

View File

@@ -3,7 +3,8 @@ name: Publish Collab Server Image
on:
push:
tags:
- collab-production
# Pause production deploys while we investigate an issue.
# - collab-production
- collab-staging
env:

View File

@@ -20,11 +20,14 @@ jobs:
with:
version: 9
- run: |
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx prettier . --check || {
echo "To fix, run from the root of the zed repo:"
echo " cd docs && pnpm dlx prettier . --write && cd .."
false
}
working-directory: ./docs
- name: Check spelling
run: script/check-spelling docs/

View File

@@ -36,9 +36,6 @@
"RUST_DEFAULT_PACKAGE_RUN": "zed"
}
}
},
"proto": {
"tab_size": 2
}
},
"file_types": {

706
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,6 @@ members = [
"crates/indexed_docs",
"crates/inline_completion_button",
"crates/install_cli",
"crates/isahc_http_client",
"crates/journal",
"crates/language",
"crates/language_model",
@@ -88,6 +87,7 @@ members = [
"crates/remote",
"crates/remote_server",
"crates/repl",
"crates/reqwest_client",
"crates/rich_text",
"crates/rope",
"crates/rpc",
@@ -99,6 +99,7 @@ members = [
"crates/settings_ui",
"crates/snippet",
"crates/snippet_provider",
"crates/snippets_ui",
"crates/sqlez",
"crates/sqlez_macros",
"crates/story",
@@ -121,6 +122,7 @@ members = [
"crates/ui",
"crates/ui_input",
"crates/ui_macros",
"crates/ureq_client",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -152,6 +154,7 @@ members = [
"extensions/php",
"extensions/perplexity",
"extensions/prisma",
"extensions/proto",
"extensions/purescript",
"extensions/ruff",
"extensions/ruby",
@@ -174,6 +177,7 @@ members = [
default-members = ["crates/zed"]
[workspace.dependencies]
#
# Workspace member crates
#
@@ -219,7 +223,6 @@ go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui" }
gpui_macros = { path = "crates/gpui_macros" }
handlebars = "4.3"
headless = { path = "crates/headless" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
@@ -227,7 +230,6 @@ image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" }
isahc_http_client = { path = "crates/isahc_http_client" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_model = { path = "crates/language_model" }
@@ -264,6 +266,7 @@ release_channel = { path = "crates/release_channel" }
remote = { path = "crates/remote" }
remote_server = { path = "crates/remote_server" }
repl = { path = "crates/repl" }
reqwest_client = { path = "crates/reqwest_client" }
rich_text = { path = "crates/rich_text" }
rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
@@ -275,6 +278,7 @@ settings = { path = "crates/settings" }
settings_ui = { path = "crates/settings_ui" }
snippet = { path = "crates/snippet" }
snippet_provider = { path = "crates/snippet_provider" }
snippets_ui = { path = "crates/snippets_ui" }
sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
@@ -297,6 +301,7 @@ title_bar = { path = "crates/title_bar" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
ureq_client = { path = "crates/ureq_client" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
@@ -316,6 +321,7 @@ any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = "0.9.1"
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "1.6"
@@ -323,7 +329,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
async-recursion = "1.0.0"
async-tar = "0.5.0"
async-trait = "0.1"
async-tungstenite = "0.23"
async-tungstenite = "0.28"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
@@ -354,18 +360,15 @@ futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.19", default-features = false }
globset = "0.4"
handlebars = "4.3"
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
hex = "0.4.3"
hyper = "0.14"
html5ever = "0.27.0"
hyper = "0.14"
ignore = "0.4.22"
image = "0.25.1"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "2"
# We explicitly disable http2 support in isahc.
isahc = { version = "1.7.2", default-features = false, features = [
"text-decoding",
] }
itertools = "0.13.0"
jsonwebtoken = "9.3"
libc = "0.2"
@@ -380,9 +383,9 @@ ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
pathdiff = "0.2"
profiling = "1"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
profiling = "1"
prost = "0.9"
prost-build = "0.9"
prost-types = "0.9"
@@ -390,13 +393,14 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
rand = "0.8.5"
regex = "1.5"
repair_json = "0.1.0"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29" }
rsa = "0.9.6"
runtimelib = { version = "0.15", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustls = "0.20.3"
rustls = "0.21.12"
rustls-native-certs = "0.8.0"
schemars = { version = "0.8", features = ["impl_json_schema"] }
semver = "1.0"
@@ -416,6 +420,7 @@ similar = "1.3"
simplelog = "0.12.2"
smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
sqlformat = "0.2"
strsim = "0.11"
strum = { version = "0.25.0", features = ["derive"] }
subtle = "2.5.0"
@@ -450,15 +455,14 @@ tree-sitter-html = "0.20"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.23"
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
protols-tree-sitter-proto = { git = "https://github.com/zed-industries/tree-sitter-proto", rev = "0848bd30a64be48772e15fbb9d5ba8c0cc5772ad" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.23"
tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unindent = "0.1.7"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unindent = "0.1.7"
unicode-segmentation = "1.10"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>

After

Width:  |  Height:  |  Size: 330 B

View File

@@ -440,7 +440,12 @@
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"cmd-shift-x": "zed::Extensions",
"cmd-shift-x": "zed::Extensions"
}
},
{
"context": "Workspace && !Terminal",
"bindings": {
"alt-t": "task::Rerun",
"alt-shift-t": "task::Spawn"
}

View File

@@ -50,6 +50,9 @@ And here's the section to rewrite based on that prompt again for reference:
{{#if diagnostic_errors}}
{{#each diagnostic_errors}}
Below are the diagnostic errors visible to the user. If the user requests problems to be fixed, use this information, but do not try to fix these errors if the user hasn't asked you to.
<diagnostic_error>
<line_number>{{line_number}}</line_number>
<error_message>{{error_message}}</error_message>

View File

@@ -356,9 +356,19 @@
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take four values:
///
/// Default: always
"show": "always"
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default):
/// "auto"
/// 3. Match the system's configured behavior:
/// "system"
/// 4. Always show the scrollbar:
/// "always"
/// 5. Never show the scrollbar:
/// "never"
"show": null
}
},
"outline_panel": {

View File

@@ -1 +1,2 @@
allow-private-module-inception = true
avoid-breaking-exported-api = false

View File

@@ -77,7 +77,7 @@ use ui::TintColor;
use ui::{
prelude::*,
utils::{format_distance_from_now, DateTimeType},
Avatar, AvatarShape, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
};
use util::{maybe, ResultExt};
@@ -262,9 +262,7 @@ impl PickerDelegate for SavedContextPickerDelegate {
.gap_2()
.children(if let Some(host_user) = host_user {
vec![
Avatar::new(host_user.avatar_uri.clone())
.shape(AvatarShape::Circle)
.into_any_element(),
Avatar::new(host_user.avatar_uri.clone()).into_any_element(),
Label::new(format!("Shared by @{}", host_user.github_login))
.color(Color::Muted)
.size(LabelSize::Small)

View File

@@ -46,7 +46,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::{AssistantKind, AssistantPhase};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use text::BufferSnapshot;
use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -549,7 +549,7 @@ impl Context {
cx: &mut ModelContext<Self>,
) -> Self {
let buffer = cx.new_model(|_cx| {
let mut buffer = Buffer::remote(
let buffer = Buffer::remote(
language::BufferId::new(1).unwrap(),
replica_id,
capability,
@@ -2133,14 +2133,21 @@ impl Context {
});
if let Some(telemetry) = this.telemetry.as_ref() {
telemetry.report_assistant_event(
Some(this.id.0.clone()),
AssistantKind::Panel,
AssistantPhase::Response,
model.telemetry_id(),
let language_name = this
.buffer
.read(cx)
.language()
.map(|language| language.name());
telemetry.report_assistant_event(AssistantEvent {
conversation_id: Some(this.id.0.clone()),
kind: AssistantKind::Panel,
phase: AssistantPhase::Response,
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency,
error_message,
);
language_name,
});
}
if let Ok(stop_reason) = result {

View File

@@ -50,6 +50,7 @@ use std::{
task::{self, Poll},
time::{Duration, Instant},
};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::terminal_panel::TerminalPanel;
use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings;
@@ -209,18 +210,6 @@ impl InlineAssistant {
initial_prompt: Option<String>,
cx: &mut WindowContext,
) {
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
telemetry_events::AssistantPhase::Invoked,
model.telemetry_id(),
None,
None,
);
}
}
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
let mut selections = Vec::<Selection<Point>>::new();
@@ -267,6 +256,21 @@ impl InlineAssistant {
text_anchor: buffer.anchor_after(buffer_range.end),
};
codegen_ranges.push(start..end);
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Invoked,
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name: buffer.language().map(|language| language.name()),
});
}
}
}
let assist_group_id = self.next_assist_group_id.post_inc();
@@ -761,23 +765,34 @@ impl InlineAssistant {
}
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
if undo {
telemetry_events::AssistantPhase::Rejected
} else {
telemetry_events::AssistantPhase::Accepted
},
model.telemetry_id(),
None,
None,
);
}
}
if let Some(assist) = self.assists.get(&assist_id) {
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let language_name = assist.editor.upgrade().and_then(|editor| {
let multibuffer = editor.read(cx).buffer().read(cx);
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.map(|language| language.name())
});
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: if undo {
AssistantPhase::Rejected
} else {
AssistantPhase::Accepted
},
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name,
});
}
}
let assist_group_id = assist.group_id;
if self.assist_groups[&assist_group_id].linked {
for assist_id in self.unlink_assist_group(assist_group_id, cx) {
@@ -2706,6 +2721,7 @@ impl CodegenAlternative {
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
let telemetry_id = model.telemetry_id();
let provider_id = model.provider_id();
let chunks: LocalBoxFuture<Result<BoxStream<Result<String>>>> =
if user_prompt.trim().to_lowercase() == "delete" {
async { Ok(stream::empty().boxed()) }.boxed_local()
@@ -2716,7 +2732,7 @@ impl CodegenAlternative {
.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await });
async move { Ok(chunks.await?.boxed()) }.boxed_local()
};
self.handle_stream(telemetry_id, chunks, cx);
self.handle_stream(telemetry_id, provider_id.to_string(), chunks, cx);
Ok(())
}
@@ -2780,6 +2796,7 @@ impl CodegenAlternative {
pub fn handle_stream(
&mut self,
model_telemetry_id: String,
model_provider_id: String,
stream: impl 'static + Future<Output = Result<BoxStream<'static, Result<String>>>>,
cx: &mut ModelContext<Self>,
) {
@@ -2810,6 +2827,15 @@ impl CodegenAlternative {
}
let telemetry = self.telemetry.clone();
let language_name = {
let multibuffer = self.buffer.read(cx);
let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx);
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.map(|language| language.name())
};
self.diff = Diff::default();
self.status = CodegenStatus::Pending;
let mut edit_start = self.range.start.to_offset(&snapshot);
@@ -2920,14 +2946,16 @@ impl CodegenAlternative {
let error_message =
result.as_ref().err().map(|error| error.to_string());
if let Some(telemetry) = telemetry {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
telemetry_events::AssistantPhase::Response,
model_telemetry_id,
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
);
language_name,
});
}
result?;
@@ -3539,6 +3567,7 @@ mod tests {
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
@@ -3610,6 +3639,7 @@ mod tests {
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
@@ -3684,6 +3714,7 @@ mod tests {
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
@@ -3757,6 +3788,7 @@ mod tests {
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
@@ -3820,6 +3852,7 @@ mod tests {
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,

View File

@@ -910,7 +910,7 @@ impl PromptLibrary {
.features
.clone(),
font_size: HeadlineSize::Large
.size()
.rems()
.into(),
font_weight: settings.ui_font.weight,
line_height: relative(

View File

@@ -31,11 +31,11 @@ impl SlashCommand for AutoCommand {
}
fn description(&self) -> String {
"Automatically infer what context to add, based on your prompt".into()
"Automatically infer what context to add".into()
}
fn menu_text(&self) -> String {
"Automatically Infer Context".into()
self.description()
}
fn label(&self, cx: &AppContext) -> CodeLabel {

View File

@@ -19,11 +19,11 @@ impl SlashCommand for DeltaSlashCommand {
}
fn description(&self) -> String {
"re-insert changed files".into()
"Re-insert changed files".into()
}
fn menu_text(&self) -> String {
"Re-insert Changed Files".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -95,7 +95,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
}
fn menu_text(&self) -> String {
"Insert Diagnostics".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -104,11 +104,11 @@ impl SlashCommand for FetchSlashCommand {
}
fn description(&self) -> String {
"insert URL contents".into()
"Insert fetched URL contents".into()
}
fn menu_text(&self) -> String {
"Insert fetched URL contents".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -110,11 +110,11 @@ impl SlashCommand for FileSlashCommand {
}
fn description(&self) -> String {
"insert file".into()
"Insert file".into()
}
fn menu_text(&self) -> String {
"Insert File".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

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

View File

@@ -47,11 +47,11 @@ impl SlashCommand for ProjectSlashCommand {
}
fn description(&self) -> String {
"Generate semantic searches based on the current context".into()
"Generate a semantic search based on context".into()
}
fn menu_text(&self) -> String {
"Project Context".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -16,11 +16,11 @@ impl SlashCommand for PromptSlashCommand {
}
fn description(&self) -> String {
"insert prompt from library".into()
"Insert prompt from library".into()
}
fn menu_text(&self) -> String {
"Insert Prompt from Library".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -34,11 +34,11 @@ impl SlashCommand for SearchSlashCommand {
}
fn description(&self) -> String {
"semantic search".into()
"Search your project semantically".into()
}
fn menu_text(&self) -> String {
"Semantic Search".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -17,11 +17,11 @@ impl SlashCommand for OutlineSlashCommand {
}
fn description(&self) -> String {
"insert symbols for active tab".into()
"Insert symbols for active tab".into()
}
fn menu_text(&self) -> String {
"Insert Symbols for Active Tab".into()
self.description()
}
fn complete_argument(

View File

@@ -24,11 +24,11 @@ impl SlashCommand for TabSlashCommand {
}
fn description(&self) -> String {
"insert open tabs (active tab by default)".to_owned()
"Insert open tabs (active tab by default)".to_owned()
}
fn menu_text(&self) -> String {
"Insert Open Tabs".to_owned()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -29,11 +29,11 @@ impl SlashCommand for TerminalSlashCommand {
}
fn description(&self) -> String {
"insert terminal output".into()
"Insert terminal output".into()
}
fn menu_text(&self) -> String {
"Insert Terminal Output".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -29,11 +29,11 @@ impl SlashCommand for WorkflowSlashCommand {
}
fn description(&self) -> String {
"insert a prompt that opts into the edit workflow".into()
"Insert prompt to opt into the edit workflow".into()
}
fn menu_text(&self) -> String {
"Insert Workflow Prompt".into()
self.description()
}
fn requires_argument(&self) -> bool {

View File

@@ -184,7 +184,7 @@ impl PickerDelegate for SlashCommandDelegate {
h_flex()
.group(format!("command-entry-label-{ix}"))
.w_full()
.min_w(px(220.))
.min_w(px(250.))
.child(
v_flex()
.child(
@@ -203,7 +203,9 @@ impl PickerDelegate for SlashCommandDelegate {
div()
.font_buffer(cx)
.child(
Label::new(args).size(LabelSize::Small),
Label::new(args)
.size(LabelSize::Small)
.color(Color::Muted),
)
.visible_on_hover(format!(
"command-entry-label-{ix}"

View File

@@ -25,6 +25,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
@@ -1039,6 +1040,7 @@ impl Codegen {
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
self.generation = cx.spawn(|this, mut cx| async move {
let model_telemetry_id = model.telemetry_id();
let model_provider_id = model.provider_id();
let response = model.stream_completion_text(prompt, &cx).await;
let generate = async {
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
@@ -1063,14 +1065,16 @@ impl Codegen {
let error_message = result.as_ref().err().map(|error| error.to_string());
if let Some(telemetry) = telemetry {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
telemetry_events::AssistantPhase::Response,
model_telemetry_id,
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
);
language_name: None,
});
}
result?;

View File

@@ -18,6 +18,7 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup
[dependencies]
anyhow.workspace = true
async-recursion = "0.3"
async-tls = "0.13"
async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
chrono = { workspace = true, features = ["serde"] }
clock.workspace = true
@@ -34,8 +35,6 @@ postage.workspace = true
rand.workspace = true
release_channel.workspace = true
rpc = { workspace = true, features = ["gpui"] }
rustls.workspace = true
rustls-native-certs.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -394,7 +394,7 @@ pub struct PendingEntitySubscription<T: 'static> {
}
impl<T: 'static> PendingEntitySubscription<T> {
pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
pub fn set_model(mut self, model: &Model<T>, cx: &AsyncAppContext) -> Subscription {
self.consumed = true;
let mut handlers = self.client.handler_set.lock();
let id = (TypeId::of::<T>(), self.remote_id);
@@ -1023,7 +1023,7 @@ impl Client {
&self,
http: Arc<HttpClientWithUrl>,
release_channel: Option<ReleaseChannel>,
) -> impl Future<Output = Result<Url>> {
) -> impl Future<Output = Result<url::Url>> {
#[cfg(any(test, feature = "test-support"))]
let url_override = self.rpc_url.read().clone();
@@ -1117,7 +1117,7 @@ impl Client {
// for us from the RPC URL.
//
// Among other things, it will generate and set a `Sec-WebSocket-Key` header for us.
let mut request = rpc_url.into_client_request()?;
let mut request = IntoClientRequest::into_client_request(rpc_url.as_str())?;
// We then modify the request to add our desired headers.
let request_headers = request.headers_mut();
@@ -1137,30 +1137,13 @@ impl Client {
match url_scheme {
Https => {
let client_config = {
let mut root_store = rustls::RootCertStore::empty();
let root_certs = rustls_native_certs::load_native_certs();
for error in root_certs.errors {
log::warn!("error loading native certs: {:?}", error);
}
root_store.add_parsable_certificates(
&root_certs
.certs
.into_iter()
.map(|cert| cert.as_ref().to_owned())
.collect::<Vec<_>>(),
);
rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth()
};
let (stream, _) =
async_tungstenite::async_tls::client_async_tls_with_connector(
request,
stream,
Some(client_config.into()),
Some(async_tls::TlsConnector::from(
http_client::TLS_CONFIG.clone(),
)),
)
.await?;
Ok(Connection::new(
@@ -1752,7 +1735,7 @@ impl CredentialsProvider for KeychainCredentialsProvider {
}
/// prefix for the zed:// url scheme
pub static ZED_URL_SCHEME: &str = "zed";
pub const ZED_URL_SCHEME: &str = "zed";
/// Parses the given link into a Zed link.
///

View File

@@ -16,9 +16,9 @@ use std::io::Write;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, AssistantKind, AssistantPhase, CallEvent, CpuEvent,
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent,
InlineCompletionEvent, MemoryEvent, ReplEvent, SettingEvent,
ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event,
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
SettingEvent,
};
use tempfile::NamedTempFile;
#[cfg(not(debug_assertions))]
@@ -288,7 +288,7 @@ impl Telemetry {
system_id: Option<String>,
installation_id: Option<String>,
session_id: String,
cx: &mut AppContext,
cx: &AppContext,
) {
let mut state = self.state.lock();
state.system_id = system_id.map(|id| id.into());
@@ -391,25 +391,8 @@ impl Telemetry {
self.report_event(event)
}
pub fn report_assistant_event(
self: &Arc<Self>,
conversation_id: Option<String>,
kind: AssistantKind,
phase: AssistantPhase,
model: String,
response_latency: Option<Duration>,
error_message: Option<String>,
) {
let event = Event::Assistant(AssistantEvent {
conversation_id,
kind,
phase,
model: model.to_string(),
response_latency,
error_message,
});
self.report_event(event)
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
self.report_event(Event::Assistant(event));
}
pub fn report_call_event(

View File

@@ -138,7 +138,7 @@ enum UpdateContacts {
}
impl UserStore {
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
pub fn new(client: Arc<Client>, cx: &ModelContext<Self>) -> Self {
let (mut current_user_tx, current_user_rx) = watch::channel();
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
let rpc_subscriptions = vec![
@@ -310,7 +310,7 @@ impl UserStore {
fn update_contacts(
&mut self,
message: UpdateContacts,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<()>> {
match message {
UpdateContacts::Wait(barrier) => {
@@ -525,9 +525,9 @@ impl UserStore {
}
pub fn dismiss_contact_request(
&mut self,
&self,
requester_id: u64,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<()>> {
let client = self.client.upgrade();
cx.spawn(move |_, _| async move {
@@ -573,7 +573,7 @@ impl UserStore {
})
}
pub fn clear_contacts(&mut self) -> impl Future<Output = ()> {
pub fn clear_contacts(&self) -> impl Future<Output = ()> {
let (tx, mut rx) = postage::barrier::channel();
self.update_contacts_tx
.unbounded_send(UpdateContacts::Clear(tx))
@@ -583,7 +583,7 @@ impl UserStore {
}
}
pub fn contact_updates_done(&mut self) -> impl Future<Output = ()> {
pub fn contact_updates_done(&self) -> impl Future<Output = ()> {
let (tx, mut rx) = postage::barrier::channel();
self.update_contacts_tx
.unbounded_send(UpdateContacts::Wait(tx))
@@ -594,9 +594,9 @@ impl UserStore {
}
pub fn get_users(
&mut self,
&self,
user_ids: Vec<u64>,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<Vec<Arc<User>>>> {
let mut user_ids_to_fetch = user_ids.clone();
user_ids_to_fetch.retain(|id| !self.users.contains_key(id));
@@ -629,9 +629,9 @@ impl UserStore {
}
pub fn fuzzy_search_users(
&mut self,
&self,
query: String,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<Vec<Arc<User>>>> {
self.load_users(proto::FuzzySearchUsers { query }, cx)
}
@@ -640,11 +640,7 @@ impl UserStore {
self.users.get(&user_id).cloned()
}
pub fn get_user_optimistic(
&mut self,
user_id: u64,
cx: &mut ModelContext<Self>,
) -> Option<Arc<User>> {
pub fn get_user_optimistic(&self, user_id: u64, cx: &ModelContext<Self>) -> Option<Arc<User>> {
if let Some(user) = self.users.get(&user_id).cloned() {
return Some(user);
}
@@ -653,11 +649,7 @@ impl UserStore {
None
}
pub fn get_user(
&mut self,
user_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<Arc<User>>> {
pub fn get_user(&self, user_id: u64, cx: &ModelContext<Self>) -> Task<Result<Arc<User>>> {
if let Some(user) = self.users.get(&user_id).cloned() {
return Task::ready(Ok(user));
}
@@ -697,7 +689,7 @@ impl UserStore {
.map(|accepted_tos_at| accepted_tos_at.is_some())
}
pub fn accept_terms_of_service(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
pub fn accept_terms_of_service(&self, cx: &ModelContext<Self>) -> Task<Result<()>> {
if self.current_user().is_none() {
return Task::ready(Err(anyhow!("no current user")));
};
@@ -726,9 +718,9 @@ impl UserStore {
}
fn load_users(
&mut self,
&self,
request: impl RequestMessage<Response = UsersResponse>,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Task<Result<Vec<Arc<User>>>> {
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {

View File

@@ -28,8 +28,8 @@ axum = { version = "0.6", features = ["json", "headers", "ws"] }
axum-extra = { version = "0.4", features = ["erased-json"] }
base64.workspace = true
chrono.workspace = true
clock.workspace = true
clickhouse.workspace = true
clock.workspace = true
collections.workspace = true
dashmap.workspace = true
envy = "0.4.2"
@@ -37,19 +37,19 @@ futures.workspace = true
google_ai.workspace = true
hex.workspace = true
http_client.workspace = true
isahc_http_client.workspace = true
jsonwebtoken.workspace = true
live_kit_server.workspace = true
log.workspace = true
nanoid.workspace = true
open_ai.workspace = true
supermaven_api.workspace = true
parking_lot.workspace = true
prometheus = "0.13"
prost.workspace = true
rand.workspace = true
reqwest = { version = "0.11", features = ["json"] }
reqwest_client.workspace = true
rpc.workspace = true
rustc-demangle.workspace = true
scrypt = "0.11"
sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
semantic_version.workspace = true
@@ -61,7 +61,7 @@ sha2.workspace = true
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
strum.workspace = true
subtle.workspace = true
rustc-demangle.workspace = true
supermaven_api.workspace = true
telemetry_events.workspace = true
text.workspace = true
thiserror.workspace = true
@@ -85,6 +85,7 @@ client = { workspace = true, features = ["test-support"] }
collab_ui = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true
dev_server_projects.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
file_finder.workspace = true
@@ -92,6 +93,7 @@ fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] }
git_hosting_providers.workspace = true
gpui = { workspace = true, features = ["test-support"] }
headless.workspace = true
hyper.workspace = true
indoc.workspace = true
language = { workspace = true, features = ["test-support"] }
@@ -108,7 +110,6 @@ recent_projects = { workspace = true }
release_channel.workspace = true
remote = { workspace = true, features = ["test-support"] }
remote_server.workspace = true
dev_server_projects.workspace = true
rpc = { workspace = true, features = ["test-support"] }
sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-sqlite"] }
serde_json.workspace = true
@@ -120,7 +121,6 @@ unindent.workspace = true
util.workspace = true
workspace = { workspace = true, features = ["test-support"] }
worktree = { workspace = true, features = ["test-support"] }
headless.workspace = true
[package.metadata.cargo-machete]
ignored = ["async-stripe"]

View File

@@ -112,6 +112,7 @@ CREATE TABLE "worktree_settings_files" (
"worktree_id" INTEGER NOT NULL,
"path" VARCHAR NOT NULL,
"content" TEXT,
"kind" VARCHAR,
PRIMARY KEY(project_id, worktree_id, path),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
);

View File

@@ -0,0 +1 @@
ALTER TABLE "worktree_settings_files" ADD COLUMN "kind" VARCHAR;

View File

@@ -23,7 +23,7 @@ use telemetry_events::{
};
use uuid::Uuid;
static CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
const CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
pub fn router() -> Router {
Router::new()

View File

@@ -35,6 +35,7 @@ use std::{
};
use time::PrimitiveDateTime;
use tokio::sync::{Mutex, OwnedMutexGuard};
use worktree_settings_file::LocalSettingsKind;
#[cfg(test)]
pub use tests::TestDb;
@@ -766,6 +767,7 @@ pub struct Worktree {
pub struct WorktreeSettingsFile {
pub path: String,
pub content: String,
pub kind: LocalSettingsKind,
}
pub struct NewExtensionVersion {
@@ -783,3 +785,21 @@ pub struct ExtensionVersionConstraints {
pub schema_versions: RangeInclusive<i32>,
pub wasm_api_versions: RangeInclusive<SemanticVersion>,
}
impl LocalSettingsKind {
pub fn from_proto(proto_kind: proto::LocalSettingsKind) -> Self {
match proto_kind {
proto::LocalSettingsKind::Settings => Self::Settings,
proto::LocalSettingsKind::Tasks => Self::Tasks,
proto::LocalSettingsKind::Editorconfig => Self::Editorconfig,
}
}
pub fn to_proto(&self) -> proto::LocalSettingsKind {
match self {
Self::Settings => proto::LocalSettingsKind::Settings,
Self::Tasks => proto::LocalSettingsKind::Tasks,
Self::Editorconfig => proto::LocalSettingsKind::Editorconfig,
}
}
}

View File

@@ -1,3 +1,4 @@
use anyhow::Context as _;
use util::ResultExt;
use super::*;
@@ -527,6 +528,12 @@ impl Database {
connection: ConnectionId,
) -> Result<TransactionGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id);
let kind = match update.kind {
Some(kind) => proto::LocalSettingsKind::from_i32(kind)
.with_context(|| format!("unknown worktree settings kind: {kind}"))?,
None => proto::LocalSettingsKind::Settings,
};
let kind = LocalSettingsKind::from_proto(kind);
self.project_transaction(project_id, |tx| async move {
// Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id)
@@ -543,6 +550,7 @@ impl Database {
worktree_id: ActiveValue::Set(update.worktree_id as i64),
path: ActiveValue::Set(update.path.clone()),
content: ActiveValue::Set(content.clone()),
kind: ActiveValue::Set(kind),
})
.on_conflict(
OnConflict::columns([
@@ -800,6 +808,7 @@ impl Database {
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
kind: db_settings_file.kind,
});
}
}

View File

@@ -735,6 +735,7 @@ impl Database {
worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path,
content: db_settings_file.content,
kind: db_settings_file.kind,
});
}
}

View File

@@ -11,9 +11,25 @@ pub struct Model {
#[sea_orm(primary_key)]
pub path: String,
pub content: String,
pub kind: LocalSettingsKind,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
#[derive(
Copy, Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default, Hash, serde::Serialize,
)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
#[serde(rename_all = "snake_case")]
pub enum LocalSettingsKind {
#[default]
#[sea_orm(string_value = "settings")]
Settings,
#[sea_orm(string_value = "tasks")]
Tasks,
#[sea_orm(string_value = "editorconfig")]
Editorconfig,
}

View File

@@ -22,7 +22,8 @@ use chrono::{DateTime, Duration, Utc};
use collections::HashMap;
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
use futures::{Stream, StreamExt as _};
use isahc_http_client::IsahcHttpClient;
use reqwest_client::ReqwestClient;
use rpc::ListModelsResponse;
use rpc::{
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
@@ -43,7 +44,7 @@ pub struct LlmState {
pub config: Config,
pub executor: Executor,
pub db: Arc<LlmDatabase>,
pub http_client: IsahcHttpClient,
pub http_client: ReqwestClient,
pub clickhouse_client: Option<clickhouse::Client>,
active_user_count_by_model:
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
@@ -69,11 +70,8 @@ impl LlmState {
let db = Arc::new(db);
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
let http_client = IsahcHttpClient::builder()
.default_header("User-Agent", user_agent)
.build()
.map(IsahcHttpClient::from)
.context("failed to construct http client")?;
let http_client =
ReqwestClient::user_agent(&user_agent).context("failed to construct http client")?;
let this = Self {
executor,

View File

@@ -36,8 +36,8 @@ use collections::{HashMap, HashSet};
pub use connection_pool::{ConnectionPool, ZedVersion};
use core::fmt::{self, Debug, Formatter};
use http_client::HttpClient;
use isahc_http_client::IsahcHttpClient;
use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
use reqwest_client::ReqwestClient;
use sha2::Digest;
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
@@ -954,8 +954,8 @@ impl Server {
tracing::info!("connection opened");
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
Ok(http_client) => Arc::new(IsahcHttpClient::from(http_client)),
let http_client = match ReqwestClient::user_agent(&user_agent) {
Ok(http_client) => Arc::new(http_client),
Err(error) => {
tracing::error!(?error, "failed to create HTTP client");
return;
@@ -1739,6 +1739,7 @@ fn notify_rejoined_projects(
worktree_id: worktree.id,
path: settings_file.path,
content: Some(settings_file.content),
kind: Some(settings_file.kind.to_proto().into()),
},
)?;
}
@@ -2220,6 +2221,7 @@ fn join_project_internal(
worktree_id: worktree.id,
path: settings_file.path,
content: Some(settings_file.content),
kind: Some(proto::update_user_settings::Kind::Settings.into()),
},
)?;
}

View File

@@ -33,7 +33,7 @@ use project::{
};
use rand::prelude::*;
use serde_json::json;
use settings::SettingsStore;
use settings::{LocalSettingsKind, SettingsStore};
use std::{
cell::{Cell, RefCell},
env, future, mem,
@@ -3327,8 +3327,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(
Path::new("").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":2}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
]
)
});
@@ -3346,8 +3354,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{}"#.to_string()),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(
Path::new("").into(),
LocalSettingsKind::Settings,
r#"{}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
]
)
});
@@ -3375,8 +3391,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(
Path::new("b").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":4}"#.to_string()
),
]
)
});
@@ -3406,7 +3430,11 @@ async fn test_local_settings(
store
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
&[(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"hard_tabs":true}"#.to_string()
),]
)
});
}

View File

@@ -4,7 +4,7 @@ use fs::{FakeFs, Fs as _};
use gpui::{Context as _, TestAppContext};
use language::language_settings::all_language_settings;
use project::ProjectPath;
use remote::SshSession;
use remote::SshRemoteClient;
use remote_server::HeadlessProject;
use serde_json::json;
use std::{path::Path, sync::Arc};
@@ -24,7 +24,7 @@ async fn test_sharing_an_ssh_remote_project(
.await;
// Set up project on remote FS
let (client_ssh, server_ssh) = SshSession::fake(cx_a, server_cx);
let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx);
let remote_fs = FakeFs::new(server_cx.executor());
remote_fs
.insert_tree(

View File

@@ -25,7 +25,7 @@ use node_runtime::NodeRuntime;
use notifications::NotificationStore;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
use remote::SshSession;
use remote::SshRemoteClient;
use rpc::{
proto::{self, ChannelRole},
RECEIVE_TIMEOUT,
@@ -835,7 +835,7 @@ impl TestClient {
pub async fn build_ssh_project(
&self,
root_path: impl AsRef<Path>,
ssh: Arc<SshSession>,
ssh: Model<SshRemoteClient>,
cx: &mut TestAppContext,
) -> (Model<Project>, WorktreeId) {
let project = cx.update(|cx| {

View File

@@ -188,7 +188,7 @@ macro_rules! define_connection {
};
}
pub fn write_and_log<F>(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send + 'static)
pub fn write_and_log<F>(cx: &AppContext, db_write: impl FnOnce() -> F + Send + 'static)
where
F: Future<Output = anyhow::Result<()>> + Send,
{

View File

@@ -1,7 +1,7 @@
use crate::ProjectDiagnosticsEditor;
use gpui::{EventEmitter, ParentElement, Render, View, ViewContext, WeakView};
use ui::prelude::*;
use ui::{IconButton, IconName, Tooltip};
use ui::{IconButton, IconButtonShape, IconName, Tooltip};
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub struct ToolbarControls {
@@ -33,11 +33,19 @@ impl Render for ToolbarControls {
"Include Warnings"
};
let warning_color = if include_warnings {
Color::Warning
} else {
Color::Muted
};
h_flex()
.gap_1()
.when(has_stale_excerpts, |div| {
div.child(
IconButton::new("update-excerpts", IconName::Update)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.disabled(is_updating)
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
.on_click(cx.listener(|this, _, cx| {
@@ -51,6 +59,8 @@ impl Render for ToolbarControls {
})
.child(
IconButton::new("toggle-warnings", IconName::Warning)
.icon_color(warning_color)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text(tooltip, cx))
.on_click(cx.listener(|this, _, cx| {
if let Some(editor) = this.editor() {

View File

@@ -193,6 +193,7 @@ gpui::actions!(
AcceptPartialInlineCompletion,
AddSelectionAbove,
AddSelectionBelow,
ApplyDiffHunk,
Backspace,
Cancel,
CancelLanguageServerWork,

View File

@@ -9,7 +9,7 @@ use crate::lsp_ext::find_specific_language_server_in_selection;
use crate::{element::register_action, Editor, SwitchSourceHeader};
static CLANGD_SERVER_NAME: &str = "clangd";
const CLANGD_SERVER_NAME: &str = "clangd";
fn is_c_language(language: &Language) -> bool {
return language.name() == "C++".into() || language.name() == "C".into();

View File

@@ -61,7 +61,7 @@ use debounced_delay::DebouncedDelay;
use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder};
pub use editor_settings::{
CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings,
CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar,
};
pub use editor_settings_controls::*;
use element::LineWithInvisibles;
@@ -1228,6 +1228,10 @@ impl CompletionsMenu {
None
};
let color_swatch = completion
.color()
.map(|color| div().size_4().bg(color).rounded_sm());
div().min_w(px(220.)).max_w(px(540.)).child(
ListItem::new(mat.candidate_id)
.inset(true)
@@ -1243,6 +1247,7 @@ impl CompletionsMenu {
task.detach_and_log_err(cx)
}
}))
.start_slot::<Div>(color_swatch)
.child(h_flex().overflow_hidden().child(completion_label))
.end_slot::<Label>(documentation_label),
)
@@ -3059,7 +3064,7 @@ impl Editor {
}
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
if self.clear_clicked_diff_hunks(cx) {
if self.clear_expanded_diff_hunks(cx) {
cx.notify();
return;
}
@@ -6205,6 +6210,20 @@ impl Editor {
}
}
fn apply_selected_diff_hunks(&mut self, _: &ApplyDiffHunk, cx: &mut ViewContext<Self>) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
self.transact(cx, |editor, cx| {
for hunk in hunks {
if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
buffer.update(cx, |buffer, cx| {
buffer.merge_into_base(Some(hunk.buffer_range.to_offset(buffer)), cx);
});
}
}
});
}
pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let project_path = buffer.read(cx).project_path(cx)?;
@@ -10773,7 +10792,7 @@ impl Editor {
.selections
.all::<Point>(cx)
.iter()
.any(|selection| selection.range().overlaps(&intersection_range));
.any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
}
@@ -11243,30 +11262,32 @@ impl Editor {
None
}
fn target_file<'a>(&self, cx: &'a AppContext) -> Option<&'a dyn language::LocalFile> {
self.active_excerpt(cx)?
.1
.read(cx)
.file()
.and_then(|f| f.as_local())
}
pub fn reveal_in_finder(&mut self, _: &RevealInFileManager, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
cx.reveal_path(&file.abs_path(cx));
}
if let Some(target) = self.target_file(cx) {
cx.reveal_path(&target.abs_path(cx));
}
}
pub fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
if let Some(path) = file.abs_path(cx).to_str() {
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
}
if let Some(file) = self.target_file(cx) {
if let Some(path) = file.abs_path(cx).to_str() {
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
}
}
}
pub fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
if let Some(path) = file.path().to_str() {
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
}
if let Some(file) = self.target_file(cx) {
if let Some(path) = file.path().to_str() {
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
}
}
}
@@ -11477,12 +11498,10 @@ impl Editor {
}
pub fn copy_file_location(&mut self, _: &CopyFileLocation, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
if let Some(path) = file.path().to_str() {
let selection = self.selections.newest::<Point>(cx).start.row + 1;
cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
}
if let Some(file) = self.target_file(cx) {
if let Some(path) = file.path().to_str() {
let selection = self.selections.newest::<Point>(cx).start.row + 1;
cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
}
}
}
@@ -12243,12 +12262,9 @@ impl Editor {
let buffer = self.buffer.read(cx);
let mut new_selections_by_buffer = HashMap::default();
for selection in self.selections.all::<usize>(cx) {
for (buffer, mut range, _) in
for (buffer, range, _) in
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
{
if selection.reversed {
mem::swap(&mut range.start, &mut range.end);
}
let mut range = range.to_point(buffer.read(cx));
range.start.column = 0;
range.end.column = buffer.read(cx).line_len(range.end.row);

View File

@@ -436,6 +436,7 @@ impl EditorElement {
register_action(view, cx, Editor::accept_inline_completion);
register_action(view, cx, Editor::revert_file);
register_action(view, cx, Editor::revert_selected_hunks);
register_action(view, cx, Editor::apply_selected_diff_hunks);
register_action(view, cx, Editor::open_active_item_in_terminal)
}

View File

@@ -14,9 +14,9 @@ use ui::{
use util::RangeExt;
use crate::{
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, BlockDisposition,
BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot,
Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyDiffHunk,
BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
};
@@ -32,6 +32,7 @@ pub(super) struct ExpandedHunks {
pub(crate) hunks: Vec<ExpandedHunk>,
diff_base: HashMap<BufferId, DiffBaseBuffer>,
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
expand_all: bool,
}
#[derive(Debug, Clone)]
@@ -72,6 +73,10 @@ impl ExpandedHunks {
}
impl Editor {
pub fn set_expand_all_diff_hunks(&mut self) {
self.expanded_hunks.expand_all = true;
}
pub(super) fn toggle_hovered_hunk(
&mut self,
hovered_hunk: &HoveredHunk,
@@ -133,6 +138,10 @@ impl Editor {
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
cx: &mut ViewContext<Self>,
) {
if self.expanded_hunks.expand_all {
return;
}
let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);
let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
if let Some(task) = previous_toggle_task {
@@ -238,19 +247,14 @@ impl Editor {
cx: &mut ViewContext<'_, Editor>,
) -> Option<()> {
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
let multi_buffer_row_range = hunk
.multi_buffer_range
.start
.to_point(&multi_buffer_snapshot)
..hunk.multi_buffer_range.end.to_point(&multi_buffer_snapshot);
let hunk_start = hunk.multi_buffer_range.start;
let hunk_end = hunk.multi_buffer_range.end;
let hunk_range = hunk.multi_buffer_range.clone();
let hunk_point_range = hunk_range.to_point(&multi_buffer_snapshot);
let buffer = self.buffer().clone();
let snapshot = self.snapshot(cx);
let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {
let hunk = buffer_diff_hunk(&snapshot.buffer_snapshot, multi_buffer_row_range.clone())?;
let mut buffer_ranges = buffer.range_to_buffer_ranges(multi_buffer_row_range, cx);
let hunk = buffer_diff_hunk(&snapshot.buffer_snapshot, hunk_point_range.clone())?;
let mut buffer_ranges = buffer.range_to_buffer_ranges(hunk_point_range, cx);
if buffer_ranges.len() == 1 {
let (buffer, _, _) = buffer_ranges.pop()?;
let diff_base_buffer = diff_base_buffer
@@ -275,7 +279,7 @@ impl Editor {
probe
.hunk_range
.start
.cmp(&hunk_start, &multi_buffer_snapshot)
.cmp(&hunk_range.start, &multi_buffer_snapshot)
}) {
Ok(_already_present) => return None,
Err(ix) => ix,
@@ -295,7 +299,7 @@ impl Editor {
}
DiffHunkStatus::Added => {
self.highlight_rows::<DiffRowHighlight>(
hunk_start..hunk_end,
hunk_range.clone(),
added_hunk_color(cx),
false,
cx,
@@ -304,7 +308,7 @@ impl Editor {
}
DiffHunkStatus::Modified => {
self.highlight_rows::<DiffRowHighlight>(
hunk_start..hunk_end,
hunk_range.clone(),
added_hunk_color(cx),
false,
cx,
@@ -323,7 +327,7 @@ impl Editor {
block_insert_index,
ExpandedHunk {
blocks,
hunk_range: hunk_start..hunk_end,
hunk_range,
status: hunk.status,
folded: false,
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
@@ -333,12 +337,49 @@ impl Editor {
Some(())
}
fn apply_changes_in_range(
&mut self,
range: Range<Anchor>,
cx: &mut ViewContext<'_, Editor>,
) -> Option<()> {
let (buffer, range, _) = self
.buffer
.read(cx)
.range_to_buffer_ranges(range, cx)
.into_iter()
.next()?;
buffer.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(Some(range), cx);
});
None
}
pub(crate) fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
let buffers = self.buffer.read(cx).all_buffers();
for branch_buffer in buffers {
branch_buffer.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(None, cx);
});
}
}
fn hunk_header_block(
&self,
hunk: &HoveredHunk,
cx: &mut ViewContext<'_, Editor>,
) -> BlockProperties<Anchor> {
let is_branch_buffer = self
.buffer
.read(cx)
.point_to_buffer_offset(hunk.multi_buffer_range.start, cx)
.map_or(false, |(buffer, _, _)| {
buffer.read(cx).diff_base_buffer().is_some()
});
let border_color = cx.theme().colors().border_variant;
let bg_color = cx.theme().colors().editor_background;
let gutter_color = match hunk.status {
DiffHunkStatus::Added => cx.theme().status().created,
DiffHunkStatus::Modified => cx.theme().status().modified,
@@ -354,6 +395,7 @@ impl Editor {
render: Box::new({
let editor = cx.view().clone();
let hunk = hunk.clone();
move |cx| {
let hunk_controls_menu_handle =
editor.read(cx).hunk_controls_menu_handle.clone();
@@ -364,7 +406,7 @@ impl Editor {
.w_full()
.border_t_1()
.border_color(border_color)
.bg(cx.theme().colors().editor_background)
.bg(bg_color)
.child(
div()
.id("gutter-strip")
@@ -384,135 +426,70 @@ impl Editor {
)
.child(
h_flex()
.pl_2()
.pr_6()
.px_6()
.size_full()
.justify_between()
.child(
h_flex()
.gap_1()
.child(
IconButton::new("next-hunk", IconName::ArrowDown)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let position = hunk
.multi_buffer_range
.end
.to_point(
&snapshot.buffer_snapshot,
);
if let Some(hunk) = editor
.go_to_hunk_after_position(
&snapshot, position, cx,
)
{
let multi_buffer_start = snapshot
.buffer_snapshot
.anchor_before(Point::new(
hunk.row_range.start.0,
0,
));
let multi_buffer_end = snapshot
.buffer_snapshot
.anchor_after(Point::new(
hunk.row_range.end.0,
0,
));
editor.expand_diff_hunk(
None,
&HoveredHunk {
multi_buffer_range:
multi_buffer_start
..multi_buffer_end,
status: hunk_status(&hunk),
diff_base_byte_range: hunk
.diff_base_byte_range,
},
.when(!is_branch_buffer, |row| {
row.child(
IconButton::new("next-hunk", IconName::ArrowDown)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor.go_to_subsequent_hunk(
hunk.multi_buffer_range.end,
cx,
);
}
});
}
}),
)
.child(
IconButton::new("prev-hunk", IconName::ArrowUp)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPrevHunk,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let position = hunk
.multi_buffer_range
.start
.to_point(
&snapshot.buffer_snapshot,
);
let hunk = editor
.go_to_hunk_before_position(
&snapshot, position, cx,
);
if let Some(hunk) = hunk {
let multi_buffer_start = snapshot
.buffer_snapshot
.anchor_before(Point::new(
hunk.row_range.start.0,
0,
));
let multi_buffer_end = snapshot
.buffer_snapshot
.anchor_after(Point::new(
hunk.row_range.end.0,
0,
));
editor.expand_diff_hunk(
None,
&HoveredHunk {
multi_buffer_range:
multi_buffer_start
..multi_buffer_end,
status: hunk_status(&hunk),
diff_base_byte_range: hunk
.diff_base_byte_range,
},
});
}
}),
)
.child(
IconButton::new("prev-hunk", IconName::ArrowUp)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPrevHunk,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor.go_to_preceding_hunk(
hunk.multi_buffer_range.start,
cx,
);
}
});
}
}),
)
});
}
}),
)
})
.child(
IconButton::new("discard", IconName::Undo)
.shape(IconButtonShape::Square)
@@ -558,46 +535,90 @@ impl Editor {
}
}),
)
.child({
let focus = editor.focus_handle(cx);
PopoverMenu::new("hunk-controls-dropdown")
.trigger(
IconButton::new(
"toggle_editor_selections_icon",
IconName::EllipsisVertical,
)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(
hunk_controls_menu_handle.is_deployed(),
)
.when(
!hunk_controls_menu_handle.is_deployed(),
|this| {
this.tooltip(|cx| {
Tooltip::text("Hunk Controls", cx)
})
},
),
.map(|this| {
if is_branch_buffer {
this.child(
IconButton::new("apply", IconName::Check)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle =
editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Apply Hunk",
&ApplyDiffHunk,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor.apply_changes_in_range(
hunk.multi_buffer_range
.clone(),
cx,
);
});
}
}),
)
.anchor(AnchorCorner::TopRight)
.with_handle(hunk_controls_menu_handle)
.menu(move |cx| {
let focus = focus.clone();
let menu =
ContextMenu::build(cx, move |menu, _| {
menu.context(focus.clone()).action(
"Discard All",
RevertFile.boxed_clone(),
} else {
this.child({
let focus = editor.focus_handle(cx);
PopoverMenu::new("hunk-controls-dropdown")
.trigger(
IconButton::new(
"toggle_editor_selections_icon",
IconName::EllipsisVertical,
)
});
Some(menu)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(
hunk_controls_menu_handle
.is_deployed(),
)
.when(
!hunk_controls_menu_handle
.is_deployed(),
|this| {
this.tooltip(|cx| {
Tooltip::text(
"Hunk Controls",
cx,
)
})
},
),
)
.anchor(AnchorCorner::TopRight)
.with_handle(hunk_controls_menu_handle)
.menu(move |cx| {
let focus = focus.clone();
let menu = ContextMenu::build(
cx,
move |menu, _| {
menu.context(focus.clone())
.action(
"Discard All Hunks",
RevertFile
.boxed_clone(),
)
},
);
Some(menu)
})
})
}
}),
)
.child(
div().child(
.when(!is_branch_buffer, |div| {
div.child(
IconButton::new("collapse", IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
@@ -621,8 +642,8 @@ impl Editor {
});
}
}),
),
),
)
}),
)
.into_any_element()
}
@@ -697,7 +718,10 @@ impl Editor {
}
}
pub(super) fn clear_clicked_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
if self.expanded_hunks.expand_all {
return false;
}
self.expanded_hunks.hunk_update_tasks.clear();
self.clear_row_highlights::<DiffRowHighlight>();
let to_remove = self
@@ -801,33 +825,43 @@ impl Editor {
status,
} => {
let hunk_display_range = display_row_range;
if expanded_hunk_display_range.start
> hunk_display_range.end
{
recalculated_hunks.next();
continue;
} else if expanded_hunk_display_range.end
< hunk_display_range.start
{
break;
} else {
if !expanded_hunk.folded
&& expanded_hunk_display_range == hunk_display_range
&& expanded_hunk.status == hunk_status(buffer_hunk)
&& expanded_hunk.diff_base_byte_range
== buffer_hunk.diff_base_byte_range
{
recalculated_hunks.next();
retain = true;
} else {
if editor.expanded_hunks.expand_all {
hunks_to_reexpand.push(HoveredHunk {
status,
multi_buffer_range,
diff_base_byte_range,
});
}
continue;
}
if expanded_hunk_display_range.end
< hunk_display_range.start
{
break;
}
if !expanded_hunk.folded
&& expanded_hunk_display_range == hunk_display_range
&& expanded_hunk.status == hunk_status(buffer_hunk)
&& expanded_hunk.diff_base_byte_range
== buffer_hunk.diff_base_byte_range
{
recalculated_hunks.next();
retain = true;
} else {
hunks_to_reexpand.push(HoveredHunk {
status,
multi_buffer_range,
diff_base_byte_range,
});
}
break;
}
}
}
@@ -839,6 +873,26 @@ impl Editor {
retain
});
if editor.expanded_hunks.expand_all {
for hunk in recalculated_hunks {
match diff_hunk_to_display(&hunk, &snapshot) {
DisplayDiffHunk::Folded { .. } => {}
DisplayDiffHunk::Unfolded {
diff_base_byte_range,
multi_buffer_range,
status,
..
} => {
hunks_to_reexpand.push(HoveredHunk {
status,
multi_buffer_range,
diff_base_byte_range,
});
}
}
}
}
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
editor.remove_blocks(blocks_to_remove, None, cx);
@@ -876,6 +930,51 @@ impl Editor {
}
})
}
fn go_to_subsequent_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
let position = position.to_point(&snapshot.buffer_snapshot);
if let Some(hunk) = self.go_to_hunk_after_position(&snapshot, position, cx) {
let multi_buffer_start = snapshot
.buffer_snapshot
.anchor_before(Point::new(hunk.row_range.start.0, 0));
let multi_buffer_end = snapshot
.buffer_snapshot
.anchor_after(Point::new(hunk.row_range.end.0, 0));
self.expand_diff_hunk(
None,
&HoveredHunk {
multi_buffer_range: multi_buffer_start..multi_buffer_end,
status: hunk_status(&hunk),
diff_base_byte_range: hunk.diff_base_byte_range,
},
cx,
);
}
}
fn go_to_preceding_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
let position = position.to_point(&snapshot.buffer_snapshot);
let hunk = self.go_to_hunk_before_position(&snapshot, position, cx);
if let Some(hunk) = hunk {
let multi_buffer_start = snapshot
.buffer_snapshot
.anchor_before(Point::new(hunk.row_range.start.0, 0));
let multi_buffer_end = snapshot
.buffer_snapshot
.anchor_after(Point::new(hunk.row_range.end.0, 0));
self.expand_diff_hunk(
None,
&HoveredHunk {
multi_buffer_range: multi_buffer_start..multi_buffer_end,
status: hunk_status(&hunk),
diff_base_byte_range: hunk.diff_base_byte_range,
},
cx,
);
}
}
}
fn to_diff_hunk(
@@ -958,13 +1057,15 @@ fn editor_with_deleted_text(
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.highlight_rows::<DiffRowHighlight>(
enum DeletedBlockRowHighlight {}
editor.highlight_rows::<DeletedBlockRowHighlight>(
Anchor::min()..Anchor::max(),
deleted_color,
false,
cx,
);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor.set_current_line_highlight(Some(CurrentLineHighlight::None)); //
editor
._subscriptions
.extend([cx.on_blur(&editor.focus_handle, |editor, cx| {
@@ -973,37 +1074,41 @@ fn editor_with_deleted_text(
});
})]);
let parent_editor_for_reverts = parent_editor.clone();
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
let diff_base_range = hunk.diff_base_byte_range.clone();
editor
.register_action::<RevertSelectedHunks>(move |_, cx| {
parent_editor_for_reverts
.update(cx, |editor, cx| {
let Some((buffer, original_text)) =
editor.buffer().update(cx, |buffer, cx| {
let (_, buffer, _) = buffer
.excerpt_containing(original_multi_buffer_range.start, cx)?;
let original_text =
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
Some((buffer, Arc::from(original_text.to_string())))
})
else {
return;
};
buffer.update(cx, |buffer, cx| {
buffer.edit(
Some((
original_multi_buffer_range.start.text_anchor
..original_multi_buffer_range.end.text_anchor,
original_text,
)),
None,
cx,
)
});
})
.ok();
.register_action::<RevertSelectedHunks>({
let parent_editor = parent_editor.clone();
move |_, cx| {
parent_editor
.update(cx, |editor, cx| {
let Some((buffer, original_text)) =
editor.buffer().update(cx, |buffer, cx| {
let (_, buffer, _) = buffer.excerpt_containing(
original_multi_buffer_range.start,
cx,
)?;
let original_text =
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
Some((buffer, Arc::from(original_text.to_string())))
})
else {
return;
};
buffer.update(cx, |buffer, cx| {
buffer.edit(
Some((
original_multi_buffer_range.start.text_anchor
..original_multi_buffer_range.end.text_anchor,
original_text,
)),
None,
cx,
)
});
})
.ok();
}
})
.detach();
let hunk = hunk.clone();

View File

@@ -158,6 +158,12 @@ pub fn deploy_context_menu(
}
let focus = cx.focused();
let has_reveal_target = editor.target_file(cx).is_some();
let reveal_in_finder_label = if cfg!(target_os = "macos") {
"Reveal in Finder"
} else {
"Reveal in File Manager"
};
ui::ContextMenu::build(cx, |menu, _cx| {
let builder = menu
.on_blur_subscription(Subscription::new(|| {}))
@@ -180,11 +186,13 @@ pub fn deploy_context_menu(
.action("Copy", Box::new(Copy))
.action("Paste", Box::new(Paste))
.separator()
.when(cfg!(target_os = "macos"), |builder| {
builder.action("Reveal in Finder", Box::new(RevealInFileManager))
})
.when(cfg!(not(target_os = "macos")), |builder| {
builder.action("Reveal in File Manager", Box::new(RevealInFileManager))
.map(|builder| {
if has_reveal_target {
builder.action(reveal_in_finder_label, Box::new(RevealInFileManager))
} else {
builder
.disabled_action(reveal_in_finder_label, Box::new(RevealInFileManager))
}
})
.action("Open in Terminal", Box::new(OpenInTerminal))
.action("Copy Permalink", Box::new(CopyPermalinkToLine));

View File

@@ -11,14 +11,14 @@ use text::ToOffset;
use ui::prelude::*;
use workspace::{
searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation,
ToolbarItemView,
ToolbarItemView, Workspace,
};
pub struct ProposedChangesEditor {
editor: View<Editor>,
_subscriptions: Vec<Subscription>,
_recalculate_diffs_task: Task<Option<()>>,
recalculate_diffs_tx: mpsc::UnboundedSender<Model<Buffer>>,
recalculate_diffs_tx: mpsc::UnboundedSender<RecalculateDiff>,
}
pub struct ProposedChangesBuffer<T> {
@@ -30,6 +30,11 @@ pub struct ProposedChangesEditorToolbar {
current_editor: Option<View<ProposedChangesEditor>>,
}
struct RecalculateDiff {
buffer: Model<Buffer>,
debounce: bool,
}
impl ProposedChangesEditor {
pub fn new<T: ToOffset>(
buffers: Vec<ProposedChangesBuffer<T>>,
@@ -58,21 +63,26 @@ impl ProposedChangesEditor {
let (recalculate_diffs_tx, mut recalculate_diffs_rx) = mpsc::unbounded();
Self {
editor: cx
.new_view(|cx| Editor::for_multibuffer(multibuffer.clone(), project, true, cx)),
editor: cx.new_view(|cx| {
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
editor.set_expand_all_diff_hunks();
editor
}),
recalculate_diffs_tx,
_recalculate_diffs_task: cx.spawn(|_, mut cx| async move {
let mut buffers_to_diff = HashSet::default();
while let Some(buffer) = recalculate_diffs_rx.next().await {
buffers_to_diff.insert(buffer);
while let Some(mut recalculate_diff) = recalculate_diffs_rx.next().await {
buffers_to_diff.insert(recalculate_diff.buffer);
loop {
while recalculate_diff.debounce {
cx.background_executor()
.timer(Duration::from_millis(250))
.await;
let mut had_further_changes = false;
while let Ok(next_buffer) = recalculate_diffs_rx.try_next() {
buffers_to_diff.insert(next_buffer?);
while let Ok(next_recalculate_diff) = recalculate_diffs_rx.try_next() {
let next_recalculate_diff = next_recalculate_diff?;
recalculate_diff.debounce &= next_recalculate_diff.debounce;
buffers_to_diff.insert(next_recalculate_diff.buffer);
had_further_changes = true;
}
if !had_further_changes {
@@ -99,19 +109,24 @@ impl ProposedChangesEditor {
event: &BufferEvent,
_cx: &mut ViewContext<Self>,
) {
if let BufferEvent::Edited = event {
self.recalculate_diffs_tx.unbounded_send(buffer).ok();
}
}
fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
let buffers = self.editor.read(cx).buffer.read(cx).all_buffers();
for branch_buffer in buffers {
if let Some(base_buffer) = branch_buffer.read(cx).diff_base_buffer() {
base_buffer.update(cx, |base_buffer, cx| {
base_buffer.merge(&branch_buffer, None, cx)
});
match event {
BufferEvent::Operation { .. } => {
self.recalculate_diffs_tx
.unbounded_send(RecalculateDiff {
buffer,
debounce: true,
})
.ok();
}
BufferEvent::DiffBaseChanged => {
self.recalculate_diffs_tx
.unbounded_send(RecalculateDiff {
buffer,
debounce: false,
})
.ok();
}
_ => (),
}
}
}
@@ -159,6 +174,31 @@ impl Item for ProposedChangesEditor {
None
}
}
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
self.editor.update(cx, |editor, cx| {
Item::added_to_workspace(editor, workspace, cx)
});
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, Item::deactivated);
}
fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
self.editor
.update(cx, |editor, cx| Item::navigate(editor, data, cx))
}
fn set_nav_history(
&mut self,
nav_history: workspace::ItemNavHistory,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
Item::set_nav_history(editor, nav_history, cx)
});
}
}
impl ProposedChangesEditorToolbar {
@@ -183,7 +223,9 @@ impl Render for ProposedChangesEditorToolbar {
Button::new("apply-changes", "Apply All").on_click(move |_, cx| {
if let Some(editor) = &editor {
editor.update(cx, |editor, cx| {
editor.apply_all_changes(cx);
editor.editor.update(cx, |editor, cx| {
editor.apply_all_changes(cx);
})
});
}
})

View File

@@ -10,7 +10,7 @@ use crate::{
ExpandMacroRecursively,
};
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
const RUST_ANALYZER_NAME: &str = "rust-analyzer";
fn is_rust_language(language: &Language) -> bool {
language.name() == "Rust".into()

View File

@@ -14,8 +14,8 @@ name = "eval"
path = "src/eval.rs"
[dependencies]
clap.workspace = true
anyhow.workspace = true
clap.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
@@ -24,15 +24,15 @@ feature_flags.workspace = true
fs.workspace = true
git.workspace = true
gpui.workspace = true
isahc_http_client.workspace = true
http_client.workspace = true
language.workspace = true
languages.workspace = true
http_client.workspace = true
node_runtime.workspace = true
open_ai.workspace = true
project.workspace = true
settings.workspace = true
semantic_index.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
semantic_index.workspace = true
node_runtime.workspace = true
ureq_client.workspace = true

View File

@@ -32,6 +32,7 @@ use std::{
Arc,
},
};
use ureq_client::UreqClient;
const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net";
const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos";
@@ -100,7 +101,11 @@ fn main() -> Result<()> {
gpui::App::headless().run(move |cx| {
let executor = cx.background_executor().clone();
let client = isahc_http_client::IsahcHttpClient::new(None, None);
let client = Arc::new(UreqClient::new(
None,
"Zed LLM evals".to_string(),
executor.clone(),
));
cx.set_http_client(client.clone());
match cli.command {
Commands::Fetch {} => {

View File

@@ -39,30 +39,31 @@ schemars.workspace = true
semantic_version.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
snippet_provider.workspace = true
task.workspace = true
theme.workspace = true
toml.workspace = true
ui.workspace = true
url.workspace = true
util.workspace = true
wasm-encoder.workspace = true
wasmtime.workspace = true
wasmtime-wasi.workspace = true
wasmparser.workspace = true
wasmtime-wasi.workspace = true
wasmtime.workspace = true
wit-component.workspace = true
workspace.workspace = true
task.workspace = true
serde_json_lenient.workspace = true
[dev-dependencies]
isahc_http_client.workspace = true
ctor.workspace = true
env_logger.workspace = true
parking_lot.workspace = true
fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true
tokio.workspace = true
ureq_client.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -25,7 +25,7 @@ use wit_component::ComponentEncoder;
/// Once Rust 1.78 is released, there will be a `wasm32-wasip2` target available, so we will
/// not need the adapter anymore.
const RUST_TARGET: &str = "wasm32-wasip1";
const WASI_ADAPTER_URL: &str =
pub const WASI_ADAPTER_URL: &str =
"https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm";
/// Compiling Tree-sitter parsers from C to WASM requires Clang 17, and a WASM build of libc

View File

@@ -1,3 +1,4 @@
use crate::extension_builder::WASI_ADAPTER_URL;
use crate::extension_manifest::SchemaVersion;
use crate::extension_settings::ExtensionSettings;
use crate::{
@@ -11,14 +12,14 @@ use collections::BTreeMap;
use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, SemanticVersion, TestAppContext};
use http_client::{FakeHttpClient, Response};
use http_client::{AsyncBody, FakeHttpClient, HttpClient, Response};
use indexed_docs::IndexedDocsRegistry;
use isahc_http_client::IsahcHttpClient;
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
use release_channel::AppVersion;
use reqwest_client::ReqwestClient;
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use snippet_provider::SnippetRegistry;
@@ -28,6 +29,7 @@ use std::{
sync::Arc,
};
use theme::ThemeRegistry;
use ureq_client::UreqClient;
use util::test::temp_tree;
#[cfg(test)]
@@ -576,7 +578,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
std::env::consts::ARCH
)
});
let builder_client = IsahcHttpClient::new(None, Some(user_agent));
let builder_client = Arc::new(UreqClient::new(None, user_agent, cx.executor().clone()));
let extension_store = cx.new_model(|cx| {
ExtensionStore::new(
@@ -769,6 +771,50 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
assert!(fs.metadata(&expected_server_path).await.unwrap().is_none());
}
#[gpui::test]
async fn test_wasi_adapter_download(cx: &mut TestAppContext) {
let client = Arc::new(UreqClient::new(
None,
"zed-test-wasi-adapter-download".to_string(),
cx.executor().clone(),
));
let mut response = client
.get(WASI_ADAPTER_URL, AsyncBody::default(), true)
.await
.unwrap();
let mut content = Vec::new();
let mut body = BufReader::new(response.body_mut());
body.read_to_end(&mut content).await.unwrap();
assert!(wasmparser::Parser::is_core_wasm(&content));
assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
wit_component::ComponentEncoder::default()
.adapter("wasi_snapshot_preview1", &content)
.unwrap();
}
#[tokio::test]
async fn test_wasi_adapter_download_tokio() {
let client = Arc::new(ReqwestClient::new());
let mut response = client
.get(WASI_ADAPTER_URL, AsyncBody::default(), true)
.await
.unwrap();
let mut content = Vec::new();
let mut body = BufReader::new(response.body_mut());
body.read_to_end(&mut content).await.unwrap();
assert!(wasmparser::Parser::is_core_wasm(&content));
assert_eq!(content.len(), 96801); // Determined by downloading this to my computer
wit_component::ComponentEncoder::default()
.adapter("wasi_snapshot_preview1", &content)
.unwrap();
}
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let store = SettingsStore::test(cx);

View File

@@ -18,9 +18,9 @@ clap = { workspace = true, features = ["derive"] }
env_logger.workspace = true
extension = { workspace = true, features = ["no-webrtc"] }
fs.workspace = true
isahc_http_client.workspace = true
language.workspace = true
log.workspace = true
reqwest_client.workspace = true
rpc.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -13,8 +13,8 @@ use extension::{
extension_builder::{CompileExtensionOptions, ExtensionBuilder},
ExtensionManifest,
};
use isahc_http_client::IsahcHttpClient;
use language::LanguageConfig;
use reqwest_client::ReqwestClient;
use theme::ThemeRegistry;
use tree_sitter::{Language, Query, WasmStore};
@@ -66,12 +66,7 @@ async fn main() -> Result<()> {
std::env::consts::OS,
std::env::consts::ARCH
);
let http_client = Arc::new(
IsahcHttpClient::builder()
.default_header("User-Agent", user_agent)
.build()
.map(IsahcHttpClient::from)?,
);
let http_client = Arc::new(ReqwestClient::user_agent(&user_agent)?);
let builder = ExtensionBuilder::new(http_client, scratch_dir);
builder

View File

@@ -54,6 +54,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("ocaml", &["ml", "mli"]),
("php", &["php"]),
("prisma", &["prisma"]),
("proto", &["proto"]),
("purescript", &["purs"]),
("r", &["r", "R"]),
("racket", &["rkt"]),

View File

@@ -587,38 +587,54 @@ impl Fs for RealFs {
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let root_path = path.to_path_buf();
watcher::global(|g| {
let tx = tx.clone();
let pending_paths = pending_paths.clone();
g.add(move |event: &notify::Event| {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let mut paths = event
.paths
.iter()
.filter_map(|path| {
path.starts_with(&root_path).then(|| PathEvent {
path: path.clone(),
kind,
})
})
.collect::<Vec<_>>();
// Check if root path is a symlink
let target_path = self.read_link(&path).await.ok();
if !paths.is_empty() {
paths.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
watcher::global({
let target_path = target_path.clone();
|g| {
let tx = tx.clone();
let pending_paths = pending_paths.clone();
g.add(move |event: &notify::Event| {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let mut paths = event
.paths
.iter()
.filter_map(|path| {
if let Some(target) = target_path.clone() {
if path.starts_with(target) {
return Some(PathEvent {
path: path.clone(),
kind,
});
}
} else if path.starts_with(&root_path) {
return Some(PathEvent {
path: path.clone(),
kind,
});
}
None
})
.collect::<Vec<_>>();
if !paths.is_empty() {
paths.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| {
a.path.cmp(&b.path)
});
}
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| {
a.path.cmp(&b.path)
});
}
})
})
}
})
.log_err();
@@ -626,6 +642,14 @@ impl Fs for RealFs {
watcher.add(path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
// Check if path is a symlink and follow the target parent
if let Some(target) = target_path {
watcher.add(&target).ok();
if let Some(parent) = target.parent() {
watcher.add(parent).log_err();
}
}
// watch the parent dir so we can tell when settings.json is created
if let Some(parent) = path.parent() {
watcher.add(parent).log_err();

View File

@@ -8,7 +8,7 @@ use gpui::AppContext;
pub use crate::providers::*;
/// Initializes the Git hosting providers.
pub fn init(cx: &mut AppContext) {
pub fn init(cx: &AppContext) {
let provider_registry = GitHostingProviderRegistry::global(cx);
// The providers are stored in a `BTreeMap`, so insertion order matters.

View File

@@ -66,7 +66,7 @@ smallvec.workspace = true
smol.workspace = true
strum.workspace = true
sum_tree.workspace = true
taffy = "0.4.3"
taffy = "0.5"
thiserror.workspace = true
util.workspace = true
uuid.workspace = true

View File

@@ -0,0 +1,180 @@
use gpui::*;
use prelude::FluentBuilder as _;
struct SubWindow {
custom_titlebar: bool,
}
fn button(text: &str, on_click: impl Fn(&mut WindowContext) + 'static) -> impl IntoElement {
div()
.id(SharedString::from(text.to_string()))
.flex_none()
.px_2()
.bg(rgb(0xf7f7f7))
.active(|this| this.opacity(0.85))
.border_1()
.border_color(rgb(0xe0e0e0))
.rounded_md()
.cursor_pointer()
.child(text.to_string())
.on_click(move |_, cx| on_click(cx))
}
impl Render for SubWindow {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.bg(rgb(0xffffff))
.size_full()
.gap_2()
.when(self.custom_titlebar, |cx| {
cx.child(
div()
.flex()
.h(px(32.))
.px_4()
.bg(gpui::blue())
.text_color(gpui::white())
.w_full()
.child(
div()
.flex()
.items_center()
.justify_center()
.size_full()
.child("Custom Titlebar"),
),
)
})
.child(
div()
.p_8()
.gap_2()
.child("SubWindow")
.child(button("Close", |cx| {
cx.remove_window();
})),
)
}
}
struct WindowDemo {}
impl Render for WindowDemo {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let window_bounds =
WindowBounds::Windowed(Bounds::centered(None, size(px(300.0), px(300.0)), cx));
div()
.p_4()
.flex()
.flex_wrap()
.bg(rgb(0xffffff))
.size_full()
.justify_center()
.items_center()
.gap_2()
.child(button("Normal", move |cx| {
cx.open_window(
WindowOptions {
window_bounds: Some(window_bounds),
..Default::default()
},
|cx| {
cx.new_view(|_cx| SubWindow {
custom_titlebar: false,
})
},
)
.unwrap();
}))
.child(button("Popup", move |cx| {
cx.open_window(
WindowOptions {
window_bounds: Some(window_bounds),
kind: WindowKind::PopUp,
..Default::default()
},
|cx| {
cx.new_view(|_cx| SubWindow {
custom_titlebar: false,
})
},
)
.unwrap();
}))
.child(button("Custom Titlebar", move |cx| {
cx.open_window(
WindowOptions {
titlebar: None,
window_bounds: Some(window_bounds),
..Default::default()
},
|cx| {
cx.new_view(|_cx| SubWindow {
custom_titlebar: true,
})
},
)
.unwrap();
}))
.child(button("Invisible", move |cx| {
cx.open_window(
WindowOptions {
show: false,
window_bounds: Some(window_bounds),
..Default::default()
},
|cx| {
cx.new_view(|_cx| SubWindow {
custom_titlebar: false,
})
},
)
.unwrap();
}))
.child(button("Unmovable", move |cx| {
cx.open_window(
WindowOptions {
is_movable: false,
titlebar: None,
window_bounds: Some(window_bounds),
..Default::default()
},
|cx| {
cx.new_view(|_cx| SubWindow {
custom_titlebar: false,
})
},
)
.unwrap();
}))
.child(button("Hide Application", |cx| {
cx.hide();
// Restore the application after 3 seconds
cx.spawn(|mut cx| async move {
Timer::after(std::time::Duration::from_secs(3)).await;
cx.update(|cx| {
cx.activate(false);
})
})
.detach();
}))
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(800.0), px(600.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|cx| cx.new_view(|_cx| WindowDemo {}),
)
.unwrap();
});
}

View File

@@ -348,7 +348,7 @@ impl AppContext {
}
/// Gracefully quit the application via the platform's standard routine.
pub fn quit(&mut self) {
pub fn quit(&self) {
self.platform.quit();
}
@@ -1004,11 +1004,7 @@ impl AppContext {
self.globals_by_type.insert(global_type, lease.global);
}
pub(crate) fn new_view_observer(
&mut self,
key: TypeId,
value: NewViewListener,
) -> Subscription {
pub(crate) fn new_view_observer(&self, key: TypeId, value: NewViewListener) -> Subscription {
let (subscription, activate) = self.new_view_observers.insert(key, value);
activate();
subscription
@@ -1016,7 +1012,7 @@ impl AppContext {
/// Arrange for the given function to be invoked whenever a view of the specified type is created.
/// The function will be passed a mutable reference to the view along with an appropriate context.
pub fn observe_new_views<V: 'static>(
&mut self,
&self,
on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
) -> Subscription {
self.new_view_observer(
@@ -1035,7 +1031,7 @@ impl AppContext {
/// Observe the release of a model or view. The callback is invoked after the model or view
/// has no more strong references but before it has been dropped.
pub fn observe_release<E, T>(
&mut self,
&self,
handle: &E,
on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
) -> Subscription
@@ -1062,7 +1058,7 @@ impl AppContext {
mut f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
) -> Subscription {
fn inner(
keystroke_observers: &mut SubscriberSet<(), KeystrokeObserver>,
keystroke_observers: &SubscriberSet<(), KeystrokeObserver>,
handler: KeystrokeObserver,
) -> Subscription {
let (subscription, activate) = keystroke_observers.insert((), handler);
@@ -1140,7 +1136,7 @@ impl AppContext {
/// Register a callback to be invoked when the application is about to quit.
/// It is not possible to cancel the quit event at this point.
pub fn on_app_quit<Fut>(
&mut self,
&self,
mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
) -> Subscription
where
@@ -1186,7 +1182,7 @@ impl AppContext {
}
/// Sets the menu bar for this application. This will replace any existing menu bar.
pub fn set_menus(&mut self, menus: Vec<Menu>) {
pub fn set_menus(&self, menus: Vec<Menu>) {
self.platform.set_menus(menus, &self.keymap.borrow());
}
@@ -1196,7 +1192,7 @@ impl AppContext {
}
/// Sets the right click menu for the app icon in the dock
pub fn set_dock_menu(&mut self, menus: Vec<MenuItem>) {
pub fn set_dock_menu(&self, menus: Vec<MenuItem>) {
self.platform.set_dock_menu(menus, &self.keymap.borrow());
}
@@ -1204,7 +1200,7 @@ impl AppContext {
/// The list is usually shown on the application icon's context menu in the dock,
/// and allows to open the recent files via that context menu.
/// If the path is already in the list, it will be moved to the bottom of the list.
pub fn add_recent_document(&mut self, path: &Path) {
pub fn add_recent_document(&self, path: &Path) {
self.platform.add_recent_document(path);
}

View File

@@ -107,7 +107,7 @@ impl Context for AsyncAppContext {
impl AsyncAppContext {
/// Schedules all windows in the application to be redrawn.
pub fn refresh(&mut self) -> Result<()> {
pub fn refresh(&self) -> Result<()> {
let app = self
.app
.upgrade()
@@ -205,7 +205,7 @@ impl AsyncAppContext {
/// A convenience method for [AppContext::update_global]
/// for updating the global state of the specified type.
pub fn update_global<G: Global, R>(
&mut self,
&self,
update: impl FnOnce(&mut G, &mut AppContext) -> R,
) -> Result<R> {
let app = self

View File

@@ -91,7 +91,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
/// Register a callback to be invoked when GPUI releases this model.
pub fn on_release(
&mut self,
&self,
on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
) -> Subscription
where
@@ -110,7 +110,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
/// Register a callback to be run on the release of another model or view
pub fn observe_release<T2, E>(
&mut self,
&self,
entity: &E,
on_release: impl FnOnce(&mut T, &mut T2, &mut ModelContext<'_, T>) + 'static,
) -> Subscription
@@ -154,7 +154,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
/// Arrange for the given function to be invoked whenever the application is quit.
/// The future returned from this callback will be polled for up to [crate::SHUTDOWN_TIMEOUT] until the app fully quits.
pub fn on_app_quit<Fut>(
&mut self,
&self,
mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + 'static,
) -> Subscription
where

View File

@@ -1418,7 +1418,7 @@ impl Interactivity {
}
fn clamp_scroll_position(
&mut self,
&self,
bounds: Bounds<Pixels>,
style: &Style,
cx: &mut WindowContext,
@@ -1547,7 +1547,7 @@ impl Interactivity {
#[cfg(debug_assertions)]
fn paint_debug_info(
&mut self,
&self,
global_id: Option<&GlobalElementId>,
hitbox: &Hitbox,
style: &Style,
@@ -2057,6 +2057,7 @@ impl Interactivity {
fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) {
if let Some(scroll_offset) = self.scroll_offset.clone() {
let overflow = style.overflow;
let allow_concurrent_scroll = style.allow_concurrent_scroll;
let line_height = cx.line_height();
let hitbox = hitbox.clone();
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
@@ -2065,27 +2066,31 @@ impl Interactivity {
let old_scroll_offset = *scroll_offset;
let delta = event.delta.pixel_delta(line_height);
let mut delta_x = Pixels::ZERO;
if overflow.x == Overflow::Scroll {
let mut delta_x = Pixels::ZERO;
if !delta.x.is_zero() {
delta_x = delta.x;
} else if overflow.y != Overflow::Scroll {
delta_x = delta.y;
}
scroll_offset.x += delta_x;
}
let mut delta_y = Pixels::ZERO;
if overflow.y == Overflow::Scroll {
let mut delta_y = Pixels::ZERO;
if !delta.y.is_zero() {
delta_y = delta.y;
} else if overflow.x != Overflow::Scroll {
delta_y = delta.x;
}
scroll_offset.y += delta_y;
}
if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() {
if delta_x.abs() > delta_y.abs() {
delta_y = Pixels::ZERO;
} else {
delta_x = Pixels::ZERO;
}
}
scroll_offset.y += delta_y;
scroll_offset.x += delta_x;
cx.stop_propagation();
if *scroll_offset != old_scroll_offset {

View File

@@ -89,6 +89,16 @@ pub enum ListSizingBehavior {
Auto,
}
/// The horizontal sizing behavior to apply during layout.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ListHorizontalSizingBehavior {
/// List items' width can never exceed the width of the list.
#[default]
FitList,
/// List items' width may go over the width of the list, if any item is wider.
Unconstrained,
}
struct LayoutItemsResponse {
max_item_width: Pixels,
scroll_top: ListOffset,

View File

@@ -252,7 +252,7 @@ impl TextLayout {
}
fn layout(
&mut self,
&self,
text: SharedString,
runs: Option<Vec<TextRun>>,
cx: &mut WindowContext,
@@ -350,7 +350,7 @@ impl TextLayout {
layout_id
}
fn prepaint(&mut self, bounds: Bounds<Pixels>, text: &str) {
fn prepaint(&self, bounds: Bounds<Pixels>, text: &str) {
let mut element_state = self.lock();
let element_state = element_state
.as_mut()
@@ -359,7 +359,7 @@ impl TextLayout {
element_state.bounds = Some(bounds);
}
fn paint(&mut self, text: &str, cx: &mut WindowContext) {
fn paint(&self, text: &str, cx: &mut WindowContext) {
let element_state = self.lock();
let element_state = element_state
.as_ref()

View File

@@ -5,8 +5,8 @@
//! elements with uniform height.
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId,
GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId,
point, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId,
GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId,
ListSizingBehavior, Pixels, Render, ScrollHandle, Size, StyleRefinement, Styled, View,
ViewContext, WindowContext,
};
@@ -14,6 +14,8 @@ use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
use taffy::style::Overflow;
use super::ListHorizontalSizingBehavior;
/// uniform_list provides lazy rendering for a set of items that are of uniform height.
/// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
/// uniform_list will only render the visible subset of items.
@@ -57,6 +59,7 @@ where
},
scroll_handle: None,
sizing_behavior: ListSizingBehavior::default(),
horizontal_sizing_behavior: ListHorizontalSizingBehavior::default(),
}
}
@@ -69,11 +72,11 @@ pub struct UniformList {
interactivity: Interactivity,
scroll_handle: Option<UniformListScrollHandle>,
sizing_behavior: ListSizingBehavior,
horizontal_sizing_behavior: ListHorizontalSizingBehavior,
}
/// Frame state used by the [UniformList].
pub struct UniformListFrameState {
item_size: Size<Pixels>,
items: SmallVec<[AnyElement; 32]>,
}
@@ -87,7 +90,18 @@ pub struct UniformListScrollHandle(pub Rc<RefCell<UniformListScrollState>>);
pub struct UniformListScrollState {
pub base_handle: ScrollHandle,
pub deferred_scroll_to_item: Option<usize>,
pub last_item_height: Option<Pixels>,
/// Size of the item, captured during last layout.
pub last_item_size: Option<ItemSize>,
}
#[derive(Copy, Clone, Debug, Default)]
/// The size of the item and its contents.
pub struct ItemSize {
/// The size of the item.
pub item: Size<Pixels>,
/// The size of the item's contents, which may be larger than the item itself,
/// if the item was bounded by a parent element.
pub contents: Size<Pixels>,
}
impl UniformListScrollHandle {
@@ -96,12 +110,12 @@ impl UniformListScrollHandle {
Self(Rc::new(RefCell::new(UniformListScrollState {
base_handle: ScrollHandle::new(),
deferred_scroll_to_item: None,
last_item_height: None,
last_item_size: None,
})))
}
/// Scroll the list to the given item index.
pub fn scroll_to_item(&mut self, ix: usize) {
pub fn scroll_to_item(&self, ix: usize) {
self.0.borrow_mut().deferred_scroll_to_item = Some(ix);
}
@@ -170,7 +184,6 @@ impl Element for UniformList {
(
layout_id,
UniformListFrameState {
item_size,
items: SmallVec::new(),
},
)
@@ -193,17 +206,30 @@ impl Element for UniformList {
- point(border.right + padding.right, border.bottom + padding.bottom),
);
let can_scroll_horizontally = matches!(
self.horizontal_sizing_behavior,
ListHorizontalSizingBehavior::Unconstrained
);
let longest_item_size = self.measure_item(None, cx);
let content_width = if can_scroll_horizontally {
padded_bounds.size.width.max(longest_item_size.width)
} else {
padded_bounds.size.width
};
let content_size = Size {
width: padded_bounds.size.width,
height: frame_state.item_size.height * self.item_count + padding.top + padding.bottom,
width: content_width,
height: longest_item_size.height * self.item_count + padding.top + padding.bottom,
};
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
let item_height = longest_item_size.height;
let shared_scroll_to_item = self.scroll_handle.as_mut().and_then(|handle| {
let mut handle = handle.0.borrow_mut();
handle.last_item_height = Some(item_height);
handle.last_item_size = Some(ItemSize {
item: padded_bounds.size,
contents: content_size,
});
handle.deferred_scroll_to_item.take()
});
@@ -228,12 +254,19 @@ impl Element for UniformList {
if self.item_count > 0 {
let content_height =
item_height * self.item_count + padding.top + padding.bottom;
let min_scroll_offset = padded_bounds.size.height - content_height;
let is_scrolled = scroll_offset.y != px(0.);
let is_scrolled_vertically = !scroll_offset.y.is_zero();
let min_vertical_scroll_offset = padded_bounds.size.height - content_height;
if is_scrolled_vertically && scroll_offset.y < min_vertical_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_vertical_scroll_offset;
scroll_offset.y = min_vertical_scroll_offset;
}
if is_scrolled && scroll_offset.y < min_scroll_offset {
shared_scroll_offset.borrow_mut().y = min_scroll_offset;
scroll_offset.y = min_scroll_offset;
let content_width = content_size.width + padding.left + padding.right;
let is_scrolled_horizontally =
can_scroll_horizontally && !scroll_offset.x.is_zero();
if is_scrolled_horizontally && content_width <= padded_bounds.size.width {
shared_scroll_offset.borrow_mut().x = Pixels::ZERO;
scroll_offset.x = Pixels::ZERO;
}
if let Some(ix) = shared_scroll_to_item {
@@ -263,9 +296,21 @@ impl Element for UniformList {
cx.with_content_mask(Some(content_mask), |cx| {
for (mut item, ix) in items.into_iter().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(px(0.), item_height * ix + scroll_offset.y + padding.top);
+ point(
if can_scroll_horizontally {
scroll_offset.x + padding.left
} else {
scroll_offset.x
},
item_height * ix + scroll_offset.y + padding.top,
);
let available_width = if can_scroll_horizontally {
padded_bounds.size.width + scroll_offset.x.abs()
} else {
padded_bounds.size.width
};
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(available_width),
AvailableSpace::Definite(item_height),
);
item.layout_as_root(available_space, cx);
@@ -318,6 +363,25 @@ impl UniformList {
self
}
/// Sets the horizontal sizing behavior, controlling the way list items laid out horizontally.
/// With [`ListHorizontalSizingBehavior::Unconstrained`] behavior, every item and the list itself will
/// have the size of the widest item and lay out pushing the `end_slot` to the right end.
pub fn with_horizontal_sizing_behavior(
mut self,
behavior: ListHorizontalSizingBehavior,
) -> Self {
self.horizontal_sizing_behavior = behavior;
match behavior {
ListHorizontalSizingBehavior::FitList => {
self.interactivity.base_style.overflow.x = None;
}
ListHorizontalSizingBehavior::Unconstrained => {
self.interactivity.base_style.overflow.x = Some(Overflow::Scroll);
}
}
self
}
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
if self.item_count == 0 {
return Size::default();

View File

@@ -706,11 +706,7 @@ pub struct Bounds<T: Clone + Default + Debug> {
impl Bounds<Pixels> {
/// Generate a centered bounds for the given display or primary display if none is provided
pub fn centered(
display_id: Option<DisplayId>,
size: Size<Pixels>,
cx: &mut AppContext,
) -> Self {
pub fn centered(display_id: Option<DisplayId>, size: Size<Pixels>, cx: &AppContext) -> Self {
let display = display_id
.and_then(|id| cx.find_display(id))
.or_else(|| cx.primary_display());
@@ -730,7 +726,7 @@ impl Bounds<Pixels> {
}
/// Generate maximized bounds for the given display or primary display if none is provided
pub fn maximized(display_id: Option<DisplayId>, cx: &mut AppContext) -> Self {
pub fn maximized(display_id: Option<DisplayId>, cx: &AppContext) -> Self {
let display = display_id
.and_then(|id| cx.find_display(id))
.or_else(|| cx.primary_display());

View File

@@ -219,7 +219,7 @@ impl DispatchTree {
self.focusable_node_ids.insert(focus_id, node_id);
}
pub fn parent_view_id(&mut self) -> Option<EntityId> {
pub fn parent_view_id(&self) -> Option<EntityId> {
self.view_stack.last().copied()
}
@@ -484,7 +484,7 @@ impl DispatchTree {
/// Converts the longest prefix of input to a replay event and returns the rest.
fn replay_prefix(
&mut self,
&self,
mut input: SmallVec<[Keystroke; 1]>,
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
) -> (SmallVec<[Keystroke; 1]>, SmallVec<[Replay; 1]>) {

View File

@@ -171,7 +171,7 @@ pub enum OsAction {
Redo,
}
pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &mut AppContext) {
pub(crate) fn init_app_menus(platform: &dyn Platform, cx: &AppContext) {
platform.on_will_open_app_menu(Box::new({
let cx = cx.to_async();
move || {

View File

@@ -45,7 +45,7 @@ use crate::{
use super::x11::X11Client;
pub(crate) const SCROLL_LINES: f64 = 3.0;
pub(crate) const SCROLL_LINES: f32 = 3.0;
// Values match the defaults on GTK.
// Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320

View File

@@ -477,8 +477,7 @@ impl WaylandClient {
.as_ref()
.map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ()));
// FIXME: Determine the scaling factor dynamically by the compositor
let mut cursor = Cursor::new(&conn, &globals, 24, 2);
let mut cursor = Cursor::new(&conn, &globals, 24);
handle
.insert_source(XDPEventSource::new(&common.background_executor), {
@@ -1634,10 +1633,10 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0));
match axis {
wl_pointer::Axis::VerticalScroll => {
scroll_delta.y += discrete as f32 * axis_modifier * SCROLL_LINES as f32;
scroll_delta.y += discrete as f32 * axis_modifier * SCROLL_LINES;
}
wl_pointer::Axis::HorizontalScroll => {
scroll_delta.x += discrete as f32 * axis_modifier * SCROLL_LINES as f32;
scroll_delta.x += discrete as f32 * axis_modifier * SCROLL_LINES;
}
_ => unreachable!(),
}
@@ -1662,10 +1661,10 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
let wheel_percent = value120 as f32 / 120.0;
match axis {
wl_pointer::Axis::VerticalScroll => {
scroll_delta.y += wheel_percent * axis_modifier * SCROLL_LINES as f32;
scroll_delta.y += wheel_percent * axis_modifier * SCROLL_LINES;
}
wl_pointer::Axis::HorizontalScroll => {
scroll_delta.x += wheel_percent * axis_modifier * SCROLL_LINES as f32;
scroll_delta.x += wheel_percent * axis_modifier * SCROLL_LINES;
}
_ => unreachable!(),
}

View File

@@ -11,7 +11,6 @@ pub(crate) struct Cursor {
theme_name: Option<String>,
surface: WlSurface,
size: u32,
scale: u32,
shm: WlShm,
connection: Connection,
}
@@ -24,7 +23,7 @@ impl Drop for Cursor {
}
impl Cursor {
pub fn new(connection: &Connection, globals: &Globals, size: u32, scale: u32) -> Self {
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
Self {
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
theme_name: None,
@@ -32,7 +31,6 @@ impl Cursor {
shm: globals.shm.clone(),
connection: connection.clone(),
size,
scale,
}
}
@@ -40,18 +38,14 @@ impl Cursor {
if let Some(size) = size {
self.size = size;
}
if let Some(theme) = CursorTheme::load_from_name(
&self.connection,
self.shm.clone(),
theme_name,
self.size * self.scale,
)
.log_err()
if let Some(theme) =
CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size)
.log_err()
{
self.theme = Some(theme);
self.theme_name = Some(theme_name.to_string());
} else if let Some(theme) =
CursorTheme::load(&self.connection, self.shm.clone(), self.size * self.scale).log_err()
CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()
{
self.theme = Some(theme);
self.theme_name = None;
@@ -97,22 +91,9 @@ impl Cursor {
let (width, height) = buffer.dimensions();
let (hot_x, hot_y) = buffer.hotspot();
let scaled_width = width / self.scale;
let scaled_height = height / self.scale;
let scaled_hot_x = hot_x / self.scale;
let scaled_hot_y = hot_y / self.scale;
self.surface.set_buffer_scale(self.scale as i32);
wl_pointer.set_cursor(
serial_id,
Some(&self.surface),
scaled_hot_x as i32,
scaled_hot_y as i32,
);
wl_pointer.set_cursor(serial_id, Some(&self.surface), hot_x as i32, hot_y as i32);
self.surface.attach(Some(&buffer), 0, 0);
self.surface
.damage(0, 0, scaled_width as i32, scaled_height as i32);
self.surface.damage(0, 0, width as i32, height as i32);
self.surface.commit();
}
} else {

View File

@@ -1,6 +1,6 @@
use core::str;
use std::cell::RefCell;
use std::collections::HashSet;
use std::collections::{BTreeMap, HashSet};
use std::ops::Deref;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
@@ -42,7 +42,10 @@ use crate::{
WindowParams, X11Window,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
use super::{
button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state,
pressed_button_from_mask, ButtonOrScroll, ScrollDirection,
};
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};
@@ -51,7 +54,15 @@ use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
};
pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
/// Value for DeviceId parameters which selects all devices.
pub(crate) const XINPUT_ALL_DEVICES: xinput::DeviceId = 0;
/// Value for DeviceId parameters which selects all device groups. Events that
/// occur within the group are emitted by the group itself.
///
/// In XInput 2's interface, these are referred to as "master devices", but that
/// terminology is both archaic and unclear.
pub(crate) const XINPUT_ALL_DEVICE_GROUPS: xinput::DeviceId = 1;
pub(crate) struct WindowRef {
window: X11WindowStatePtr,
@@ -117,6 +128,26 @@ pub struct Xdnd {
position: Point<Pixels>,
}
#[derive(Debug)]
struct PointerDeviceState {
horizontal: ScrollAxisState,
vertical: ScrollAxisState,
}
#[derive(Debug, Default)]
struct ScrollAxisState {
/// Valuator number for looking up this axis's scroll value.
valuator_number: Option<u16>,
/// Conversion factor from scroll units to lines.
multiplier: f32,
/// Last scroll value for calculating scroll delta.
///
/// This gets set to `None` whenever it might be invalid - when devices change or when window focus changes.
/// The logic errs on the side of invalidating this, since the consequence is just skipping the delta of one scroll event.
/// The consequence of not invalidating it can be large invalid deltas, which are much more user visible.
scroll_value: Option<f32>,
}
pub struct X11ClientState {
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
@@ -152,9 +183,7 @@ pub struct X11ClientState {
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
pub(crate) scroll_class_data: Vec<xinput::DeviceClassDataScroll>,
pub(crate) scroll_x: Option<f32>,
pub(crate) scroll_y: Option<f32>,
pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>,
pub(crate) common: LinuxCommon,
pub(crate) clipboard: x11_clipboard::Clipboard,
@@ -266,31 +295,21 @@ impl X11Client {
.prefetch_extension_information(xinput::X11_EXTENSION_NAME)
.unwrap();
// Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and
// beyond, support for touch events would need to be added.
let xinput_version = xcb_connection
.xinput_xi_query_version(2, 0)
.xinput_xi_query_version(2, 1)
.unwrap()
.reply()
.unwrap();
// XInput 1.x is not supported.
assert!(
xinput_version.major_version >= 2,
"XInput Extension v2 not supported."
"XInput version >= 2 required."
);
let master_device_query = xcb_connection
.xinput_xi_query_device(XINPUT_MASTER_DEVICE)
.unwrap()
.reply()
.unwrap();
let scroll_class_data = master_device_query
.infos
.iter()
.find(|info| info.type_ == xinput::DeviceType::MASTER_POINTER)
.unwrap()
.classes
.iter()
.filter_map(|class| class.data.as_scroll())
.map(|class| *class)
.collect::<Vec<_>>();
let pointer_device_states =
get_new_pointer_device_states(&xcb_connection, &BTreeMap::new());
let atoms = XcbAtoms::new(&xcb_connection).unwrap().reply().unwrap();
@@ -434,9 +453,7 @@ impl X11Client {
cursor_styles: HashMap::default(),
cursor_cache: HashMap::default(),
scroll_class_data,
scroll_x: None,
scroll_y: None,
pointer_device_states,
clipboard,
clipboard_item: None,
@@ -950,35 +967,56 @@ impl X11Client {
window.handle_ime_commit(text);
state = self.0.borrow_mut();
}
if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
let click_elapsed = state.last_click.elapsed();
match button_or_scroll_from_event_detail(event.detail) {
Some(ButtonOrScroll::Button(button)) => {
let click_elapsed = state.last_click.elapsed();
if click_elapsed < DOUBLE_CLICK_INTERVAL
&& state
.last_mouse_button
.is_some_and(|prev_button| prev_button == button)
&& is_within_click_distance(state.last_location, position)
{
state.current_count += 1;
} else {
state.current_count = 1;
}
if click_elapsed < DOUBLE_CLICK_INTERVAL
&& state
.last_mouse_button
.is_some_and(|prev_button| prev_button == button)
&& is_within_click_distance(state.last_location, position)
{
state.current_count += 1;
} else {
state.current_count = 1;
state.last_click = Instant::now();
state.last_mouse_button = Some(button);
state.last_location = position;
let current_count = state.current_count;
drop(state);
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
button,
position,
modifiers,
click_count: current_count,
first_mouse: false,
}));
}
Some(ButtonOrScroll::Scroll(direction)) => {
drop(state);
// Emulated scroll button presses are sent simultaneously with smooth scrolling XinputMotion events.
// Since handling those events does the scrolling, they are skipped here.
if !event
.flags
.contains(xinput::PointerEventFlags::POINTER_EMULATED)
{
let scroll_delta = match direction {
ScrollDirection::Up => Point::new(0.0, SCROLL_LINES),
ScrollDirection::Down => Point::new(0.0, -SCROLL_LINES),
ScrollDirection::Left => Point::new(SCROLL_LINES, 0.0),
ScrollDirection::Right => Point::new(-SCROLL_LINES, 0.0),
};
window.handle_input(PlatformInput::ScrollWheel(
make_scroll_wheel_event(position, scroll_delta, modifiers),
));
}
}
None => {
log::error!("Unknown x11 button: {}", event.detail);
}
state.last_click = Instant::now();
state.last_mouse_button = Some(button);
state.last_location = position;
let current_count = state.current_count;
drop(state);
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
button,
position,
modifiers,
click_count: current_count,
first_mouse: false,
}));
} else {
log::warn!("Unknown button press: {event:?}");
}
}
Event::XinputButtonRelease(event) => {
@@ -991,15 +1029,19 @@ impl X11Client {
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
);
if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
let click_count = state.current_count;
drop(state);
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
button,
position,
modifiers,
click_count,
}));
match button_or_scroll_from_event_detail(event.detail) {
Some(ButtonOrScroll::Button(button)) => {
let click_count = state.current_count;
drop(state);
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
button,
position,
modifiers,
click_count,
}));
}
Some(ButtonOrScroll::Scroll(_)) => {}
None => {}
}
}
Event::XinputMotion(event) => {
@@ -1014,12 +1056,6 @@ impl X11Client {
state.modifiers = modifiers;
drop(state);
let axisvalues = event
.axisvalues
.iter()
.map(|axisvalue| fp3232_to_f32(*axisvalue))
.collect::<Vec<_>>();
if event.valuator_mask[0] & 3 != 0 {
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
position,
@@ -1028,64 +1064,17 @@ impl X11Client {
}));
}
let mut valuator_idx = 0;
let scroll_class_data = self.0.borrow().scroll_class_data.clone();
for shift in 0..32 {
if (event.valuator_mask[0] >> shift) & 1 == 0 {
continue;
state = self.0.borrow_mut();
if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) {
let scroll_delta = get_scroll_delta_and_update_state(&mut pointer, &event);
drop(state);
if let Some(scroll_delta) = scroll_delta {
window.handle_input(PlatformInput::ScrollWheel(make_scroll_wheel_event(
position,
scroll_delta,
modifiers,
)));
}
for scroll_class in &scroll_class_data {
if scroll_class.scroll_type == xinput::ScrollType::HORIZONTAL
&& scroll_class.number == shift
{
let new_scroll = axisvalues[valuator_idx]
/ fp3232_to_f32(scroll_class.increment)
* SCROLL_LINES as f32;
let old_scroll = self.0.borrow().scroll_x;
self.0.borrow_mut().scroll_x = Some(new_scroll);
if let Some(old_scroll) = old_scroll {
let delta_scroll = old_scroll - new_scroll;
window.handle_input(PlatformInput::ScrollWheel(
crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(delta_scroll, 0.0)),
modifiers,
touch_phase: TouchPhase::default(),
},
));
}
} else if scroll_class.scroll_type == xinput::ScrollType::VERTICAL
&& scroll_class.number == shift
{
// the `increment` is the valuator delta equivalent to one positive unit of scrolling. Here that means SCROLL_LINES lines.
let new_scroll = axisvalues[valuator_idx]
/ fp3232_to_f32(scroll_class.increment)
* SCROLL_LINES as f32;
let old_scroll = self.0.borrow().scroll_y;
self.0.borrow_mut().scroll_y = Some(new_scroll);
if let Some(old_scroll) = old_scroll {
let delta_scroll = old_scroll - new_scroll;
let (x, y) = if !modifiers.shift {
(0.0, delta_scroll)
} else {
(delta_scroll, 0.0)
};
window.handle_input(PlatformInput::ScrollWheel(
crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(x, y)),
modifiers,
touch_phase: TouchPhase::default(),
},
));
}
}
}
valuator_idx += 1;
}
}
Event::XinputEnter(event) if event.mode == xinput::NotifyMode::NORMAL => {
@@ -1095,10 +1084,10 @@ impl X11Client {
state.mouse_focused_window = Some(event.event);
}
Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
self.0.borrow_mut().scroll_y = None;
let mut state = self.0.borrow_mut();
// Set last scroll values to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
reset_all_pointer_device_scroll_positions(&mut state.pointer_device_states);
state.mouse_focused_window = None;
let pressed_button = pressed_button_from_mask(event.buttons[0]);
let position = point(
@@ -1117,6 +1106,26 @@ impl X11Client {
}));
window.set_hovered(false);
}
Event::XinputHierarchy(event) => {
let mut state = self.0.borrow_mut();
// Temporarily use `state.pointer_device_states` to only store pointers that still have valid scroll values.
// Any change to a device invalidates its scroll values.
for info in event.infos {
if is_pointer_device(info.type_) {
state.pointer_device_states.remove(&info.deviceid);
}
}
state.pointer_device_states = get_new_pointer_device_states(
&state.xcb_connection,
&state.pointer_device_states,
);
}
Event::XinputDeviceChanged(event) => {
let mut state = self.0.borrow_mut();
if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) {
reset_pointer_device_scroll_positions(&mut pointer);
}
}
_ => {}
};
@@ -1742,3 +1751,142 @@ fn xdnd_send_status(
.send_event(false, target, EventMask::default(), message)
.unwrap();
}
/// Recomputes `pointer_device_states` by querying all pointer devices.
/// When a device is present in `scroll_values_to_preserve`, its value for `ScrollAxisState.scroll_value` is used.
fn get_new_pointer_device_states(
xcb_connection: &XCBConnection,
scroll_values_to_preserve: &BTreeMap<xinput::DeviceId, PointerDeviceState>,
) -> BTreeMap<xinput::DeviceId, PointerDeviceState> {
let devices_query_result = xcb_connection
.xinput_xi_query_device(XINPUT_ALL_DEVICES)
.unwrap()
.reply()
.unwrap();
let mut pointer_device_states = BTreeMap::new();
pointer_device_states.extend(
devices_query_result
.infos
.iter()
.filter(|info| is_pointer_device(info.type_))
.filter_map(|info| {
let scroll_data = info
.classes
.iter()
.filter_map(|class| class.data.as_scroll())
.map(|class| *class)
.rev()
.collect::<Vec<_>>();
let old_state = scroll_values_to_preserve.get(&info.deviceid);
let old_horizontal = old_state.map(|state| &state.horizontal);
let old_vertical = old_state.map(|state| &state.vertical);
let horizontal = scroll_data
.iter()
.find(|data| data.scroll_type == xinput::ScrollType::HORIZONTAL)
.map(|data| scroll_data_to_axis_state(data, old_horizontal));
let vertical = scroll_data
.iter()
.find(|data| data.scroll_type == xinput::ScrollType::VERTICAL)
.map(|data| scroll_data_to_axis_state(data, old_vertical));
if horizontal.is_none() && vertical.is_none() {
None
} else {
Some((
info.deviceid,
PointerDeviceState {
horizontal: horizontal.unwrap_or_else(Default::default),
vertical: vertical.unwrap_or_else(Default::default),
},
))
}
}),
);
if pointer_device_states.is_empty() {
log::error!("Found no xinput mouse pointers.");
}
return pointer_device_states;
}
/// Returns true if the device is a pointer device. Does not include pointer device groups.
fn is_pointer_device(type_: xinput::DeviceType) -> bool {
type_ == xinput::DeviceType::SLAVE_POINTER
}
fn scroll_data_to_axis_state(
data: &xinput::DeviceClassDataScroll,
old_axis_state_with_valid_scroll_value: Option<&ScrollAxisState>,
) -> ScrollAxisState {
ScrollAxisState {
valuator_number: Some(data.number),
multiplier: SCROLL_LINES / fp3232_to_f32(data.increment),
scroll_value: old_axis_state_with_valid_scroll_value.and_then(|state| state.scroll_value),
}
}
fn reset_all_pointer_device_scroll_positions(
pointer_device_states: &mut BTreeMap<xinput::DeviceId, PointerDeviceState>,
) {
pointer_device_states
.iter_mut()
.for_each(|(_, device_state)| reset_pointer_device_scroll_positions(device_state));
}
fn reset_pointer_device_scroll_positions(pointer: &mut PointerDeviceState) {
pointer.horizontal.scroll_value = None;
pointer.vertical.scroll_value = None;
}
/// Returns the scroll delta for a smooth scrolling motion event, or `None` if no scroll data is present.
fn get_scroll_delta_and_update_state(
pointer: &mut PointerDeviceState,
event: &xinput::MotionEvent,
) -> Option<Point<f32>> {
let delta_x = get_axis_scroll_delta_and_update_state(event, &mut pointer.horizontal);
let delta_y = get_axis_scroll_delta_and_update_state(event, &mut pointer.vertical);
if delta_x.is_some() || delta_y.is_some() {
Some(Point::new(delta_x.unwrap_or(0.0), delta_y.unwrap_or(0.0)))
} else {
None
}
}
fn get_axis_scroll_delta_and_update_state(
event: &xinput::MotionEvent,
axis: &mut ScrollAxisState,
) -> Option<f32> {
let axis_index = get_valuator_axis_index(&event.valuator_mask, axis.valuator_number?)?;
if let Some(axis_value) = event.axisvalues.get(axis_index) {
let new_scroll = fp3232_to_f32(*axis_value);
let delta_scroll = axis
.scroll_value
.map(|old_scroll| (old_scroll - new_scroll) * axis.multiplier);
axis.scroll_value = Some(new_scroll);
delta_scroll
} else {
log::error!("Encountered invalid XInput valuator_mask, scrolling may not work properly.");
None
}
}
fn make_scroll_wheel_event(
position: Point<Pixels>,
scroll_delta: Point<f32>,
modifiers: Modifiers,
) -> crate::ScrollWheelEvent {
// When shift is held down, vertical scrolling turns into horizontal scrolling.
let delta = if modifiers.shift {
Point {
x: scroll_delta.y,
y: 0.0,
}
} else {
scroll_delta
};
crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(delta),
modifiers,
touch_phase: TouchPhase::default(),
}
}

View File

@@ -5,13 +5,29 @@ use x11rb::protocol::{
use crate::{Modifiers, MouseButton, NavigationDirection};
pub(crate) fn button_of_key(detail: xproto::Button) -> Option<MouseButton> {
pub(crate) enum ButtonOrScroll {
Button(MouseButton),
Scroll(ScrollDirection),
}
pub(crate) enum ScrollDirection {
Up,
Down,
Left,
Right,
}
pub(crate) fn button_or_scroll_from_event_detail(detail: u32) -> Option<ButtonOrScroll> {
Some(match detail {
1 => MouseButton::Left,
2 => MouseButton::Middle,
3 => MouseButton::Right,
8 => MouseButton::Navigate(NavigationDirection::Back),
9 => MouseButton::Navigate(NavigationDirection::Forward),
1 => ButtonOrScroll::Button(MouseButton::Left),
2 => ButtonOrScroll::Button(MouseButton::Middle),
3 => ButtonOrScroll::Button(MouseButton::Right),
4 => ButtonOrScroll::Scroll(ScrollDirection::Up),
5 => ButtonOrScroll::Scroll(ScrollDirection::Down),
6 => ButtonOrScroll::Scroll(ScrollDirection::Left),
7 => ButtonOrScroll::Scroll(ScrollDirection::Right),
8 => ButtonOrScroll::Button(MouseButton::Navigate(NavigationDirection::Back)),
9 => ButtonOrScroll::Button(MouseButton::Navigate(NavigationDirection::Forward)),
_ => return None,
})
}
@@ -48,3 +64,91 @@ pub(crate) fn pressed_button_from_mask(button_mask: u32) -> Option<MouseButton>
return None;
})
}
pub(crate) fn get_valuator_axis_index(
valuator_mask: &Vec<u32>,
valuator_number: u16,
) -> Option<usize> {
// XInput valuator masks have a 1 at the bit indexes corresponding to each
// valuator present in this event's axisvalues. Axisvalues is ordered from
// lowest valuator number to highest, so counting bits before the 1 bit for
// this valuator yields the index in axisvalues.
if bit_is_set_in_vec(&valuator_mask, valuator_number) {
Some(popcount_upto_bit_index(&valuator_mask, valuator_number) as usize)
} else {
None
}
}
/// Returns the number of 1 bits in `bit_vec` for all bits where `i < bit_index`.
fn popcount_upto_bit_index(bit_vec: &Vec<u32>, bit_index: u16) -> u32 {
let array_index = bit_index as usize / 32;
let popcount: u32 = bit_vec
.get(array_index)
.map_or(0, |bits| keep_bits_upto(*bits, bit_index % 32).count_ones());
if array_index == 0 {
popcount
} else {
// Valuator numbers over 32 probably never occur for scroll position, but may as well
// support it.
let leading_popcount: u32 = bit_vec
.iter()
.take(array_index)
.map(|bits| bits.count_ones())
.sum();
popcount + leading_popcount
}
}
fn bit_is_set_in_vec(bit_vec: &Vec<u32>, bit_index: u16) -> bool {
let array_index = bit_index as usize / 32;
bit_vec
.get(array_index)
.map_or(false, |bits| bit_is_set(*bits, bit_index % 32))
}
fn bit_is_set(bits: u32, bit_index: u16) -> bool {
bits & (1 << bit_index) != 0
}
/// Sets every bit with `i >= bit_index` to 0.
fn keep_bits_upto(bits: u32, bit_index: u16) -> u32 {
if bit_index == 0 {
0
} else if bit_index >= 32 {
u32::MAX
} else {
bits & ((1 << bit_index) - 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_valuator_axis_index() {
assert!(get_valuator_axis_index(&vec![0b11], 0) == Some(0));
assert!(get_valuator_axis_index(&vec![0b11], 1) == Some(1));
assert!(get_valuator_axis_index(&vec![0b11], 2) == None);
assert!(get_valuator_axis_index(&vec![0b100], 0) == None);
assert!(get_valuator_axis_index(&vec![0b100], 1) == None);
assert!(get_valuator_axis_index(&vec![0b100], 2) == Some(0));
assert!(get_valuator_axis_index(&vec![0b100], 3) == None);
assert!(get_valuator_axis_index(&vec![0b1010, 0], 0) == None);
assert!(get_valuator_axis_index(&vec![0b1010, 0], 1) == Some(0));
assert!(get_valuator_axis_index(&vec![0b1010, 0], 2) == None);
assert!(get_valuator_axis_index(&vec![0b1010, 0], 3) == Some(1));
assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 0) == None);
assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 1) == Some(0));
assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 2) == None);
assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 3) == Some(1));
assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 32) == Some(2));
assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 33) == None);
assert!(get_valuator_axis_index(&vec![0b1010, 0b101], 34) == Some(3));
}
}

View File

@@ -29,7 +29,7 @@ use std::{
sync::Arc,
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
use super::{X11Display, XINPUT_ALL_DEVICES, XINPUT_ALL_DEVICE_GROUPS};
x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie {
XA_ATOM,
@@ -475,7 +475,7 @@ impl X11WindowState {
.xinput_xi_select_events(
x_window,
&[xinput::EventMask {
deviceid: XINPUT_MASTER_DEVICE,
deviceid: XINPUT_ALL_DEVICE_GROUPS,
mask: vec![
xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
@@ -487,6 +487,19 @@ impl X11WindowState {
)
.unwrap();
xcb_connection
.xinput_xi_select_events(
x_window,
&[xinput::EventMask {
deviceid: XINPUT_ALL_DEVICES,
mask: vec![
xinput::XIEventMask::HIERARCHY,
xinput::XIEventMask::DEVICE_CHANGED,
],
}],
)
.unwrap();
xcb_connection.flush().unwrap();
let raw = RawWindow {
@@ -1253,7 +1266,7 @@ impl PlatformWindow for X11Window {
self.0.x_window,
state.atoms._GTK_SHOW_WINDOW_MENU,
[
XINPUT_MASTER_DEVICE as u32,
XINPUT_ALL_DEVICE_GROUPS as u32,
coords.dst_x as u32,
coords.dst_y as u32,
0,

View File

@@ -284,11 +284,11 @@ impl MetalRenderer {
}
}
pub fn update_transparency(&mut self, _transparent: bool) {
pub fn update_transparency(&self, _transparent: bool) {
// todo(mac)?
}
pub fn destroy(&mut self) {
pub fn destroy(&self) {
// nothing to do
}
@@ -486,7 +486,7 @@ impl MetalRenderer {
}
fn rasterize_paths(
&mut self,
&self,
paths: &[Path<ScaledPixels>],
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
@@ -576,7 +576,7 @@ impl MetalRenderer {
}
fn draw_shadows(
&mut self,
&self,
shadows: &[Shadow],
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
@@ -639,7 +639,7 @@ impl MetalRenderer {
}
fn draw_quads(
&mut self,
&self,
quads: &[Quad],
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
@@ -698,7 +698,7 @@ impl MetalRenderer {
}
fn draw_paths(
&mut self,
&self,
paths: &[Path<ScaledPixels>],
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
instance_buffer: &mut InstanceBuffer,
@@ -808,7 +808,7 @@ impl MetalRenderer {
}
fn draw_underlines(
&mut self,
&self,
underlines: &[Underline],
instance_buffer: &mut InstanceBuffer,
instance_offset: &mut usize,
@@ -871,7 +871,7 @@ impl MetalRenderer {
}
fn draw_monochrome_sprites(
&mut self,
&self,
texture_id: AtlasTextureId,
sprites: &[MonochromeSprite],
instance_buffer: &mut InstanceBuffer,
@@ -945,7 +945,7 @@ impl MetalRenderer {
}
fn draw_polychrome_sprites(
&mut self,
&self,
texture_id: AtlasTextureId,
sprites: &[PolychromeSprite],
instance_buffer: &mut InstanceBuffer,

View File

@@ -1432,7 +1432,7 @@ impl UTType {
self.0
}
fn inner_mut(&mut self) -> *mut Object {
fn inner_mut(&self) -> *mut Object {
self.0 as *mut _
}
}

View File

@@ -707,7 +707,7 @@ impl MacWindow {
}
}
if focus {
if focus && show {
native_window.makeKeyAndOrderFront_(nil);
} else if show {
native_window.orderFront_(nil);

View File

@@ -295,13 +295,9 @@ impl Platform for WindowsPlatform {
}
}
// todo(windows)
fn activate(&self, _ignoring_other_apps: bool) {}
// todo(windows)
fn hide(&self) {
unimplemented!()
}
fn hide(&self) {}
// todo(windows)
fn hide_other_apps(&self) {

View File

@@ -287,7 +287,7 @@ impl WindowsWindow {
.map(|title| title.as_ref())
.unwrap_or(""),
);
let (dwexstyle, dwstyle) = if params.kind == WindowKind::PopUp {
let (dwexstyle, mut dwstyle) = if params.kind == WindowKind::PopUp {
(WS_EX_TOOLWINDOW, WINDOW_STYLE(0x0))
} else {
(
@@ -295,6 +295,10 @@ impl WindowsWindow {
WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
)
};
if !params.show {
dwstyle |= WS_MINIMIZE;
}
let hinstance = get_module_handle();
let display = if let Some(display_id) = params.display_id {
// if we obtain a display_id, then this ID must be valid.
@@ -357,7 +361,12 @@ impl WindowsWindow {
drop(lock);
SetWindowPlacement(raw_hwnd, &placement)?;
}
unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok()? };
if params.show {
unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok()? };
} else {
unsafe { ShowWindow(raw_hwnd, SW_HIDE).ok()? };
}
Ok(Self(state_ptr))
}

View File

@@ -156,6 +156,8 @@ pub struct Style {
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
/// Whether both x and y axis should be scrollable at the same time.
pub allow_concurrent_scroll: bool,
// Position properties
/// What should the `position` value of this struct use as a base offset?
@@ -667,6 +669,7 @@ impl Default for Style {
x: Overflow::Visible,
y: Overflow::Visible,
},
allow_concurrent_scroll: false,
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),

View File

@@ -15,36 +15,30 @@ use taffy::{
type NodeMeasureFn =
Box<dyn FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>>;
struct NodeContext {
measure: NodeMeasureFn,
}
pub struct TaffyLayoutEngine {
taffy: TaffyTree<()>,
styles: FxHashMap<LayoutId, Style>,
children_to_parents: FxHashMap<LayoutId, LayoutId>,
taffy: TaffyTree<NodeContext>,
absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
computed_layouts: FxHashSet<LayoutId>,
nodes_to_measure: FxHashMap<LayoutId, NodeMeasureFn>,
}
static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
impl TaffyLayoutEngine {
pub fn new() -> Self {
TaffyLayoutEngine {
taffy: TaffyTree::new(),
styles: FxHashMap::default(),
children_to_parents: FxHashMap::default(),
absolute_layout_bounds: FxHashMap::default(),
computed_layouts: FxHashSet::default(),
nodes_to_measure: FxHashMap::default(),
}
}
pub fn clear(&mut self) {
self.taffy.clear();
self.children_to_parents.clear();
self.absolute_layout_bounds.clear();
self.computed_layouts.clear();
self.nodes_to_measure.clear();
self.styles.clear();
}
pub fn request_layout(
@@ -68,11 +62,8 @@ impl TaffyLayoutEngine {
})
.expect(EXPECT_MESSAGE)
.into();
self.children_to_parents
.extend(children.iter().map(|child_id| (*child_id, parent_id)));
parent_id
};
self.styles.insert(layout_id, style);
layout_id
}
@@ -87,11 +78,14 @@ impl TaffyLayoutEngine {
let layout_id = self
.taffy
.new_leaf_with_context(taffy_style, ())
.new_leaf_with_context(
taffy_style,
NodeContext {
measure: Box::new(measure),
},
)
.expect(EXPECT_MESSAGE)
.into();
self.nodes_to_measure.insert(layout_id, Box::new(measure));
self.styles.insert(layout_id, style);
layout_id
}
@@ -180,8 +174,8 @@ impl TaffyLayoutEngine {
.compute_layout_with_measure(
id.into(),
available_space.into(),
|known_dimensions, available_space, node_id, _context| {
let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else {
|known_dimensions, available_space, _node_id, node_context, _style| {
let Some(node_context) = node_context else {
return taffy::geometry::Size::default();
};
@@ -190,7 +184,7 @@ impl TaffyLayoutEngine {
height: known_dimensions.height.map(Pixels),
};
measure(known_dimensions, available_space.into(), cx).into()
(node_context.measure)(known_dimensions, available_space.into(), cx).into()
},
)
.expect(EXPECT_MESSAGE);
@@ -209,8 +203,8 @@ impl TaffyLayoutEngine {
size: layout.size.into(),
};
if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
let parent_bounds = self.layout_bounds(parent_id);
if let Some(parent_id) = self.taffy.parent(id.0) {
let parent_bounds = self.layout_bounds(parent_id.into());
bounds.origin += parent_bounds.origin;
}
self.absolute_layout_bounds.insert(id, bounds);

View File

@@ -835,10 +835,7 @@ impl Window {
prompt: None,
})
}
fn new_focus_listener(
&mut self,
value: AnyWindowFocusListener,
) -> (Subscription, impl FnOnce()) {
fn new_focus_listener(&self, value: AnyWindowFocusListener) -> (Subscription, impl FnOnce()) {
self.focus_listeners.insert((), value)
}
}
@@ -929,7 +926,7 @@ impl<'a> WindowContext<'a> {
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
/// for elements rendered within this window.
pub fn focus_handle(&mut self) -> FocusHandle {
pub fn focus_handle(&self) -> FocusHandle {
FocusHandle::new(&self.window.focus_handles)
}
@@ -1127,7 +1124,7 @@ impl<'a> WindowContext<'a> {
/// Register a callback to be invoked when the given Model or View is released.
pub fn observe_release<E, T>(
&mut self,
&self,
entity: &E,
mut on_release: impl FnOnce(&mut T, &mut WindowContext) + 'static,
) -> Subscription
@@ -1155,7 +1152,7 @@ impl<'a> WindowContext<'a> {
}
/// Schedule the given closure to be run directly after the current frame is rendered.
pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
pub fn on_next_frame(&self, callback: impl FnOnce(&mut WindowContext) + 'static) {
RefCell::borrow_mut(&self.window.next_frame_callbacks).push(Box::new(callback));
}
@@ -1165,7 +1162,7 @@ impl<'a> WindowContext<'a> {
/// It will cause the window to redraw on the next frame, even if no other changes have occurred.
///
/// If called from within a view, it will notify that view on the next frame. Otherwise, it will refresh the entire window.
pub fn request_animation_frame(&mut self) {
pub fn request_animation_frame(&self) {
let parent_id = self.parent_view_id();
self.on_next_frame(move |cx| {
if let Some(parent_id) = parent_id {
@@ -1179,7 +1176,7 @@ impl<'a> WindowContext<'a> {
/// Spawn the future returned by the given closure on the application thread pool.
/// The closure is provided a handle to the current window and an `AsyncWindowContext` for
/// use within your future.
pub fn spawn<Fut, R>(&mut self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task<R>
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task<R>
where
R: 'static,
Fut: Future<Output = R> + 'static,
@@ -2865,7 +2862,7 @@ impl<'a> WindowContext<'a> {
}
/// Get the last view id for the current element
pub fn parent_view_id(&mut self) -> Option<EntityId> {
pub fn parent_view_id(&self) -> Option<EntityId> {
self.window.next_frame.dispatch_tree.parent_view_id()
}
@@ -3606,7 +3603,7 @@ impl<'a> WindowContext<'a> {
}
/// Updates the IME panel position suggestions for languages like japanese, chinese.
pub fn invalidate_character_coordinates(&mut self) {
pub fn invalidate_character_coordinates(&self) {
self.on_next_frame(|cx| {
if let Some(mut input_handler) = cx.window.platform_window.take_input_handler() {
if let Some(bounds) = input_handler.selected_bounds(cx) {
@@ -3752,7 +3749,7 @@ impl<'a> WindowContext<'a> {
/// Register a callback that can interrupt the closing of the current window based the returned boolean.
/// If the callback returns false, the window won't be closed.
pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
pub fn on_window_should_close(&self, f: impl Fn(&mut WindowContext) -> bool + 'static) {
let mut this = self.to_async();
self.window
.platform_window
@@ -4070,7 +4067,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}
/// Sets a given callback to be run on the next frame.
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
pub fn on_next_frame(&self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + 'static)
where
V: 'static,
{
@@ -4162,7 +4159,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// The callback receives a handle to the view's window. This handle may be
/// invalid, if the window was closed before the view was released.
pub fn on_release(
&mut self,
&self,
on_release: impl FnOnce(&mut V, AnyWindowHandle, &mut AppContext) + 'static,
) -> Subscription {
let window_handle = self.window.handle;
@@ -4179,7 +4176,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Register a callback to be invoked when the given Model or View is released.
pub fn observe_release<V2, E>(
&mut self,
&self,
entity: &E,
mut on_release: impl FnMut(&mut V, &mut V2, &mut ViewContext<'_, V>) + 'static,
) -> Subscription
@@ -4212,7 +4209,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Register a callback to be invoked when the window is resized.
pub fn observe_window_bounds(
&mut self,
&self,
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
@@ -4226,7 +4223,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Register a callback to be invoked when the window is activated or deactivated.
pub fn observe_window_activation(
&mut self,
&self,
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
@@ -4240,7 +4237,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Registers a callback to be invoked when the window appearance changes.
pub fn observe_window_appearance(
&mut self,
&self,
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
@@ -4260,7 +4257,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
mut f: impl FnMut(&mut V, &KeystrokeEvent, &mut ViewContext<V>) + 'static,
) -> Subscription {
fn inner(
keystroke_observers: &mut SubscriberSet<(), KeystrokeObserver>,
keystroke_observers: &SubscriberSet<(), KeystrokeObserver>,
handler: KeystrokeObserver,
) -> Subscription {
let (subscription, activate) = keystroke_observers.insert((), handler);
@@ -4284,7 +4281,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Register a callback to be invoked when the window's pending input changes.
pub fn observe_pending_input(
&mut self,
&self,
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
@@ -4372,7 +4369,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// and this callback lets you chose a default place to restore the users focus.
/// Returns a subscription and persists until the subscription is dropped.
pub fn on_focus_lost(
&mut self,
&self,
mut listener: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
) -> Subscription {
let view = self.view.downgrade();
@@ -4418,10 +4415,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// The given callback is invoked with a [`WeakView<V>`] to avoid leaking the view for a long-running process.
/// It's also given an [`AsyncWindowContext`], which can be used to access the state of the view across await points.
/// The returned future will be polled on the main thread.
pub fn spawn<Fut, R>(
&mut self,
f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut,
) -> Task<R>
pub fn spawn<Fut, R>(&self, f: impl FnOnce(WeakView<V>, AsyncWindowContext) -> Fut) -> Task<R>
where
R: 'static,
Fut: Future<Output = R> + 'static,

View File

@@ -16,11 +16,13 @@ path = "src/http_client.rs"
doctest = true
[dependencies]
http = "0.2"
anyhow.workspace = true
derive_more.workspace = true
futures.workspace = true
http = "1.1"
log.workspace = true
rustls-native-certs.workspace = true
rustls.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true

View File

@@ -11,13 +11,21 @@ use http::request::Builder;
#[cfg(feature = "test-support")]
use std::fmt;
use std::{
sync::{Arc, Mutex},
sync::{Arc, LazyLock, Mutex},
time::Duration,
};
pub use url::Url;
#[derive(Clone)]
pub struct ReadTimeout(pub Duration);
#[derive(Default, Debug, Clone)]
impl Default for ReadTimeout {
fn default() -> Self {
Self(Duration::from_secs(5))
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub enum RedirectPolicy {
#[default]
NoFollow,
@@ -26,6 +34,23 @@ pub enum RedirectPolicy {
}
pub struct FollowRedirects(pub bool);
pub static TLS_CONFIG: LazyLock<Arc<rustls::ClientConfig>> = LazyLock::new(|| {
let mut root_store = rustls::RootCertStore::empty();
let root_certs = rustls_native_certs::load_native_certs();
for error in root_certs.errors {
log::warn!("error loading native certs: {:?}", error);
}
root_store.add_parsable_certificates(&root_certs.certs);
Arc::new(
rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth(),
)
});
pub trait HttpRequestExt {
/// Set a read timeout on the request.
/// For isahc, this is the low_speed_timeout.

View File

@@ -1,105 +0,0 @@
use std::{mem, sync::Arc, time::Duration};
use futures::future::BoxFuture;
use util::maybe;
pub use isahc::config::Configurable;
pub struct IsahcHttpClient(isahc::HttpClient);
pub use http_client::*;
impl IsahcHttpClient {
pub fn new(proxy: Option<Uri>, user_agent: Option<String>) -> Arc<IsahcHttpClient> {
let mut builder = isahc::HttpClient::builder()
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(proxy.clone());
if let Some(agent) = user_agent {
builder = builder.default_header("User-Agent", agent);
}
Arc::new(IsahcHttpClient(builder.build().unwrap()))
}
pub fn builder() -> isahc::HttpClientBuilder {
isahc::HttpClientBuilder::new()
}
}
impl From<isahc::HttpClient> for IsahcHttpClient {
fn from(client: isahc::HttpClient) -> Self {
Self(client)
}
}
impl HttpClient for IsahcHttpClient {
fn proxy(&self) -> Option<&Uri> {
None
}
fn send(
&self,
req: http_client::http::Request<http_client::AsyncBody>,
) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
{
let redirect_policy = req
.extensions()
.get::<http_client::RedirectPolicy>()
.cloned()
.unwrap_or_default();
let read_timeout = req
.extensions()
.get::<http_client::ReadTimeout>()
.map(|t| t.0);
let req = maybe!({
let (mut parts, body) = req.into_parts();
let mut builder = isahc::Request::builder()
.method(parts.method)
.uri(parts.uri)
.version(parts.version);
if let Some(read_timeout) = read_timeout {
builder = builder.low_speed_timeout(100, read_timeout);
}
let headers = builder.headers_mut()?;
mem::swap(headers, &mut parts.headers);
let extensions = builder.extensions_mut()?;
mem::swap(extensions, &mut parts.extensions);
let isahc_body = match body.0 {
http_client::Inner::Empty => isahc::AsyncBody::empty(),
http_client::Inner::AsyncReader(reader) => isahc::AsyncBody::from_reader(reader),
http_client::Inner::SyncReader(reader) => {
isahc::AsyncBody::from_bytes_static(reader.into_inner())
}
};
builder
.redirect_policy(match redirect_policy {
http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow,
http_client::RedirectPolicy::FollowLimit(limit) => {
isahc::config::RedirectPolicy::Limit(limit)
}
http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None,
})
.body(isahc_body)
.ok()
});
let client = self.0.clone();
Box::pin(async move {
match req {
Some(req) => client
.send_async(req)
.await
.map_err(Into::into)
.map(|response| {
let (parts, body) = response.into_parts();
let body = http_client::AsyncBody::from_reader(body);
http_client::Response::from_parts(parts, body)
}),
None => Err(anyhow::anyhow!("Request was malformed")),
}
})
}
}

View File

@@ -62,7 +62,7 @@ pub use text::{
use theme::SyntaxTheme;
#[cfg(any(test, feature = "test-support"))]
use util::RandomCharIter;
use util::RangeExt;
use util::{debug_panic, RangeExt};
#[cfg(any(test, feature = "test-support"))]
pub use {tree_sitter_rust, tree_sitter_typescript};
@@ -73,7 +73,7 @@ pub use lsp::DiagnosticSeverity;
/// a diff against the contents of its file.
pub static BUFFER_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
/// Indicate whether a [Buffer] has permissions to edit.
/// Indicate whether a [`Buffer`] has permissions to edit.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Capability {
/// The buffer is a mutable replica.
@@ -211,7 +211,7 @@ pub struct Diagnostic {
///
/// When a language server produces a diagnostic with
/// one or more associated diagnostics, those diagnostics are all
/// assigned a single group id.
/// assigned a single group ID.
pub group_id: usize,
/// Whether this diagnostic is the primary diagnostic for its group.
///
@@ -588,7 +588,7 @@ impl IndentGuide {
impl Buffer {
/// Create a new buffer with the given base text.
pub fn local<T: Into<String>>(base_text: T, cx: &mut ModelContext<Self>) -> Self {
pub fn local<T: Into<String>>(base_text: T, cx: &ModelContext<Self>) -> Self {
Self::build(
TextBuffer::new(0, cx.entity_id().as_non_zero_u64().into(), base_text.into()),
None,
@@ -601,7 +601,7 @@ impl Buffer {
pub fn local_normalized(
base_text_normalized: Rope,
line_ending: LineEnding,
cx: &mut ModelContext<Self>,
cx: &ModelContext<Self>,
) -> Self {
Self::build(
TextBuffer::new_normalized(
@@ -718,7 +718,7 @@ impl Buffer {
self
}
/// Returns the [Capability] of this buffer.
/// Returns the [`Capability`] of this buffer.
pub fn capability(&self) -> Capability {
self.capability
}
@@ -728,7 +728,7 @@ impl Buffer {
self.capability == Capability::ReadOnly
}
/// Builds a [Buffer] with the given underlying [TextBuffer], diff base, [File] and [Capability].
/// Builds a [`Buffer`] with the given underlying [`TextBuffer`], diff base, [`File`] and [`Capability`].
pub fn build(
buffer: TextBuffer,
diff_base: Option<String>,
@@ -819,44 +819,48 @@ impl Buffer {
branch.set_language_registry(language_registry);
}
// Reparse the branch buffer so that we get syntax highlighting immediately.
branch.reparse(cx);
branch
})
}
/// Applies all of the changes in `branch` buffer that intersect the given `range`
/// to this buffer.
pub fn merge(
&mut self,
branch: &Model<Self>,
range: Option<Range<Anchor>>,
cx: &mut ModelContext<Self>,
) {
let edits = branch.read_with(cx, |branch, _| {
branch
.edits_since_in_range::<usize>(
&self.version,
range.unwrap_or(Anchor::MIN..Anchor::MAX),
)
.map(|edit| {
(
edit.old,
branch.text_for_range(edit.new).collect::<String>(),
)
})
.collect::<Vec<_>>()
});
let operation = self.edit(edits, None, cx);
/// Applies all of the changes in this buffer that intersect the given `range`
/// to its base buffer. This buffer must be a branch buffer to call this method.
pub fn merge_into_base(&mut self, range: Option<Range<usize>>, cx: &mut ModelContext<Self>) {
let Some(base_buffer) = self.diff_base_buffer() else {
debug_panic!("not a branch buffer");
return;
};
// Prevent this operation from being reapplied to the branch.
branch.update(cx, |branch, cx| {
base_buffer.update(cx, |base_buffer, cx| {
let edits = self
.edits_since::<usize>(&base_buffer.version)
.filter_map(|edit| {
if range
.as_ref()
.map_or(true, |range| range.overlaps(&edit.new))
{
Some((edit.old, self.text_for_range(edit.new).collect::<String>()))
} else {
None
}
})
.collect::<Vec<_>>();
let operation = base_buffer.edit(edits, None, cx);
// Prevent this operation from being reapplied to the branch.
if let Some(BufferDiffBase::PastBufferVersion {
operations_to_ignore,
..
}) = &mut branch.diff_base
}) = &mut self.diff_base
{
operations_to_ignore.extend(operation);
}
cx.emit(BufferEvent::Edited)
cx.emit(BufferEvent::DiffBaseChanged);
});
}
@@ -930,7 +934,7 @@ impl Buffer {
/// Assign a language registry to the buffer. This allows the buffer to retrieve
/// other languages if parts of the buffer are written in different languages.
pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
pub fn set_language_registry(&self, language_registry: Arc<LanguageRegistry>) {
self.syntax_map
.lock()
.set_language_registry(language_registry);
@@ -940,7 +944,7 @@ impl Buffer {
self.syntax_map.lock().language_registry()
}
/// Assign the buffer a new [Capability].
/// Assign the buffer a new [`Capability`].
pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext<Self>) {
self.capability = capability;
cx.emit(BufferEvent::CapabilityChanged)
@@ -963,16 +967,13 @@ impl Buffer {
}
/// This method is called to signal that the buffer has been discarded.
pub fn discarded(&mut self, cx: &mut ModelContext<Self>) {
pub fn discarded(&self, cx: &mut ModelContext<Self>) {
cx.emit(BufferEvent::Discarded);
cx.notify();
}
/// Reloads the contents of the buffer from disk.
pub fn reload(
&mut self,
cx: &mut ModelContext<Self>,
) -> oneshot::Receiver<Option<Transaction>> {
pub fn reload(&mut self, cx: &ModelContext<Self>) -> oneshot::Receiver<Option<Transaction>> {
let (tx, rx) = futures::channel::oneshot::channel();
let prev_version = self.text.version();
self.reload_task = Some(cx.spawn(|this, mut cx| async move {
@@ -1031,7 +1032,7 @@ impl Buffer {
cx.notify();
}
/// Updates the [File] backing this buffer. This should be called when
/// Updates the [`File`] backing this buffer. This should be called when
/// the file has changed or has been deleted.
pub fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) {
let mut file_changed = false;
@@ -1070,7 +1071,7 @@ impl Buffer {
}
}
/// Returns the current diff base, see [Buffer::set_diff_base].
/// Returns the current diff base, see [`Buffer::set_diff_base`].
pub fn diff_base(&self) -> Option<&Rope> {
match self.diff_base.as_ref()? {
BufferDiffBase::Git(rope) | BufferDiffBase::PastBufferVersion { rope, .. } => {
@@ -1081,7 +1082,7 @@ impl Buffer {
/// Sets the text that will be used to compute a Git diff
/// against the buffer text.
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &ModelContext<Self>) {
self.diff_base = diff_base.map(|mut raw_diff_base| {
LineEnding::normalize(&mut raw_diff_base);
BufferDiffBase::Git(Rope::from(raw_diff_base))
@@ -1113,7 +1114,7 @@ impl Buffer {
}
/// Recomputes the diff.
pub fn recalculate_diff(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
pub fn recalculate_diff(&self, cx: &ModelContext<Self>) -> Option<Task<()>> {
let diff_base_rope = match self.diff_base.as_ref()? {
BufferDiffBase::Git(rope) => rope.clone(),
BufferDiffBase::PastBufferVersion { buffer, .. } => buffer.read(cx).as_rope().clone(),
@@ -1141,12 +1142,12 @@ impl Buffer {
}))
}
/// Returns the primary [Language] assigned to this [Buffer].
/// Returns the primary [`Language`] assigned to this [`Buffer`].
pub fn language(&self) -> Option<&Arc<Language>> {
self.language.as_ref()
}
/// Returns the [Language] at the given location.
/// Returns the [`Language`] at the given location.
pub fn language_at<D: ToOffset>(&self, position: D) -> Option<Arc<Language>> {
let offset = position.to_offset(self);
self.syntax_map
@@ -2245,12 +2246,7 @@ impl Buffer {
}
}
fn send_operation(
&mut self,
operation: Operation,
is_local: bool,
cx: &mut ModelContext<Self>,
) {
fn send_operation(&self, operation: Operation, is_local: bool, cx: &mut ModelContext<Self>) {
cx.emit(BufferEvent::Operation {
operation,
is_local,
@@ -2729,6 +2725,7 @@ impl BufferSnapshot {
.collect();
(captures, highlight_maps)
}
/// Iterates over chunks of text in the given range of the buffer. Text is chunked
/// in an arbitrary way due to being stored in a [`Rope`](text::Rope). The text is also
/// returned in chunks where each chunk has a single syntax highlighting style and
@@ -2780,12 +2777,12 @@ impl BufferSnapshot {
.last()
}
/// Returns the main [Language]
/// Returns the main [`Language`].
pub fn language(&self) -> Option<&Arc<Language>> {
self.language.as_ref()
}
/// Returns the [Language] at the given location.
/// Returns the [`Language`] at the given location.
pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
self.syntax_layer_at(position)
.map(|info| info.language)
@@ -2805,7 +2802,7 @@ impl BufferSnapshot {
CharClassifier::new(self.language_scope_at(point))
}
/// Returns the [LanguageScope] at the given location.
/// Returns the [`LanguageScope`] at the given location.
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
let offset = position.to_offset(self);
let mut scope = None;
@@ -2960,7 +2957,7 @@ impl BufferSnapshot {
/// Returns the outline for the buffer.
///
/// This method allows passing an optional [SyntaxTheme] to
/// This method allows passing an optional [`SyntaxTheme`] to
/// syntax-highlight the returned symbols.
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
self.outline_items_containing(0..self.len(), true, theme)
@@ -2969,7 +2966,7 @@ impl BufferSnapshot {
/// Returns all the symbols that contain the given position.
///
/// This method allows passing an optional [SyntaxTheme] to
/// This method allows passing an optional [`SyntaxTheme`] to
/// syntax-highlight the returned symbols.
pub fn symbols_containing<T: ToOffset>(
&self,
@@ -3212,7 +3209,7 @@ impl BufferSnapshot {
}
/// For each grammar in the language, runs the provided
/// [tree_sitter::Query] against the given range.
/// [`tree_sitter::Query`] against the given range.
pub fn matches(
&self,
range: Range<usize>,
@@ -3773,7 +3770,7 @@ impl BufferSnapshot {
})
}
/// Whether the buffer contains any git changes.
/// Whether the buffer contains any Git changes.
pub fn has_git_diff(&self) -> bool {
!self.git_diff.is_empty()
}
@@ -3855,7 +3852,7 @@ impl BufferSnapshot {
}
/// Returns all the diagnostic groups associated with the given
/// language server id. If no language server id is provided,
/// language server ID. If no language server ID is provided,
/// all diagnostics groups are returned.
pub fn diagnostic_groups(
&self,
@@ -4238,7 +4235,7 @@ impl Default for Diagnostic {
}
impl IndentSize {
/// Returns an [IndentSize] representing the given spaces.
/// Returns an [`IndentSize`] representing the given spaces.
pub fn spaces(len: u32) -> Self {
Self {
len,
@@ -4246,7 +4243,7 @@ impl IndentSize {
}
}
/// Returns an [IndentSize] representing a tab.
/// Returns an [`IndentSize`] representing a tab.
pub fn tab() -> Self {
Self {
len: 1,
@@ -4254,12 +4251,12 @@ impl IndentSize {
}
}
/// An iterator over the characters represented by this [IndentSize].
/// An iterator over the characters represented by this [`IndentSize`].
pub fn chars(&self) -> impl Iterator<Item = char> {
iter::repeat(self.char()).take(self.len as usize)
}
/// The character representation of this [IndentSize].
/// The character representation of this [`IndentSize`].
pub fn char(&self) -> char {
match self.kind {
IndentKind::Space => ' ',
@@ -4267,7 +4264,7 @@ impl IndentSize {
}
}
/// Consumes the current [IndentSize] and returns a new one that has
/// Consumes the current [`IndentSize`] and returns a new one that has
/// been shrunk or enlarged by the given size along the given direction.
pub fn with_delta(mut self, direction: Ordering, size: IndentSize) -> Self {
match direction {

View File

@@ -2471,8 +2471,8 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
});
// Merging the branch applies all of its changes to the base.
base_buffer.update(cx, |base_buffer, cx| {
base_buffer.merge(&branch_buffer, None, cx);
branch_buffer.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(None, cx);
});
branch_buffer.update(cx, |branch_buffer, cx| {
@@ -2484,6 +2484,18 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
});
}
#[gpui::test]
fn test_merge_into_base(cx: &mut AppContext) {
init_settings(cx, |_| {});
let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
branch.update(cx, |branch, cx| {
branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
branch.merge_into_base(Some(5..8), cx);
});
assert_eq!(base.read(cx).text(), "abcdefgHIjk");
}
fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
buffer
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())

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