Compare commits

..

61 Commits

Author SHA1 Message Date
mgsloan@gmail.com
e2673bb667 WIP jumping into diagnostics 2024-12-19 21:21:17 -07:00
Max Brunsfeld
3632b36fde Move multibuffer tests to their own source file (#22270)
Release Notes:

- N/A
2024-12-20 02:02:32 +00:00
renovate[bot]
97e11fd5d2 Update aws-sdk-rust monorepo (#22215)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aws-config](https://redirect.github.com/smithy-lang/smithy-rs) |
dependencies | patch | `1.5.10` -> `1.5.11` |
| [aws-sdk-kinesis](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.52.0` -> `1.53.0` |
| [aws-sdk-s3](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.65.0` -> `1.66.0` |

---

### 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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:15:25 +00:00
renovate[bot]
150aa03c5f Update Rust crate hyper to v0.14.32 (#22207)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [hyper](https://hyper.rs)
([source](https://redirect.github.com/hyperium/hyper)) |
workspace.dependencies | patch | `0.14.31` -> `0.14.32` |

---

### Release Notes

<details>
<summary>hyperium/hyper (hyper)</summary>

###
[`v0.14.32`](https://redirect.github.com/hyperium/hyper/releases/tag/v0.14.32)

[Compare
Source](https://redirect.github.com/hyperium/hyper/compare/v0.14.31...v0.14.32)

#### Features

- **server:** add `Builder::max_pending_accept_reset_streams(num)`
option
([a24f0c0](a24f0c0af8))

#### Bug Fixes

- **http1:** fix intermittent panic parsing partial headers
([0f274ae](0f274ae653))

#### New Contributors

- [@&#8203;cratelyn](https://redirect.github.com/cratelyn) made their
first contribution in
[https://github.com/hyperium/hyper/pull/3796](https://redirect.github.com/hyperium/hyper/pull/3796)

**Full Changelog**:
https://github.com/hyperium/hyper/compare/v0.14.31...v0.14.32

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:14:22 +00:00
renovate[bot]
ebf6115c3c Update Rust crate semver to v1.0.24 (#22211)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [semver](https://redirect.github.com/dtolnay/semver) |
workspace.dependencies | patch | `1.0.23` -> `1.0.24` |

---

### Release Notes

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

###
[`v1.0.24`](https://redirect.github.com/dtolnay/semver/releases/tag/1.0.24)

[Compare
Source](https://redirect.github.com/dtolnay/semver/compare/1.0.23...1.0.24)

- Optimize Ord impls for semver::Prerelease and semver::BuildMetadata
([#&#8203;328](https://redirect.github.com/dtolnay/semver/issues/328),
thanks [@&#8203;Eh2406](https://redirect.github.com/Eh2406))

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:14:10 +00:00
renovate[bot]
394af7481d Update Rust crate tree-sitter-c to v0.23.4 (#22212)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tree-sitter-c](https://redirect.github.com/tree-sitter/tree-sitter-c)
| workspace.dependencies | patch | `0.23.2` -> `0.23.4` |

---

### Release Notes

<details>
<summary>tree-sitter/tree-sitter-c (tree-sitter-c)</summary>

###
[`v0.23.4`](https://redirect.github.com/tree-sitter/tree-sitter-c/releases/tag/v0.23.4)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-c/compare/v0.23.2...v0.23.4)

**NOTE:** Download `tree-sitter-c.tar.xz` for the *complete* source
code.

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:13:51 +00:00
renovate[bot]
7b0d63fffb Update Rust crate tree-sitter-json to 0.24 (#22226)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[tree-sitter-json](https://redirect.github.com/tree-sitter/tree-sitter-json)
| workspace.dependencies | minor | `0.23` -> `0.24` |

---

### Release Notes

<details>
<summary>tree-sitter/tree-sitter-json (tree-sitter-json)</summary>

###
[`v0.24.8`](https://redirect.github.com/tree-sitter/tree-sitter-json/releases/tag/v0.24.8)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.7...v0.24.8)

**NOTE:** Download `tree-sitter-json.tar.xz` for the *complete* source
code.

###
[`v0.24.7`](https://redirect.github.com/tree-sitter/tree-sitter-json/releases/tag/v0.24.7)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.5...v0.24.7)

**NOTE:** Download `tree-sitter-json.tar.xz` for the *complete* source
code.

###
[`v0.24.5`](https://redirect.github.com/tree-sitter/tree-sitter-json/releases/tag/v0.24.5)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.4...v0.24.5)

**NOTE:** Download `tree-sitter-json.tar.xz` for the *complete* source
code.

###
[`v0.24.4`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.3...v0.24.4)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.3...v0.24.4)

###
[`v0.24.3`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.2...v0.24.3)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.2...v0.24.3)

###
[`v0.24.2`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.1...v0.24.2)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.1...v0.24.2)

###
[`v0.24.1`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.0...v0.24.1)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.0...v0.24.1)

###
[`v0.24.0`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.23.0...v0.24.0)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.23.0...v0.24.0)

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:13:17 +00:00
tims
f64bfe8c1d linux: Fix saving file with root ownership (#22045)
Closes #13585

Currently, saving files with `root` ownership or `root` as the group
throws a `Permission denied (os error 13). Please try again.` error.
This PR fixes the issue on Linux by prompting the user for a password
and saving the file with elevated privileges.

It uses `pkexec` (Polkit), which is by default available on GNOME, KDE,
and most Linux systems. I haven't implemented this for macOS as I don't
have a device to test it on.

This implementation is similar to how Vscode handles it. Except, they
don't show custom message.

**Working**:

When file saving fails due to a `PermissionDenied` error, we create a
temporary file in the same directory as the target file and writes the
data to this temporary file. After, the contents of this file are copied
to the original file using the `tee` command instead of `cp` or `mv`.
This ensures that the ownership and permissions of the original file are
preserved. This command is executed using `pkexec` which will prompt
user for their password.

**Custom Message**:

The message displayed to the user in the prompt is automatically
retrieved from the `org.zed.app.policy` file, which is located at
`/usr/share/polkit-1/actions/`. This file should be installed during the
setup process. While the policy file is optional, omitting it will cause
the user to see the underlying command being executed rather than a
user-friendly message. Currently, VSCode does not display the
user-friendly message.

The policy file must specify a unique binary, ensuring that only that
binary can use the policy file. It cannot be as generic as a
`/bin/bash`, as any software using bash to prompt will end up showing
Zed’s custom message. To address this, we will create a custom bash
script, as simple as the following, placed in `/usr/bin/zed/elevate.sh`.
The script should have root ownership and should not reside in the home
directory, since the policy file cannot resolve `$HOME`.

```sh
#!/bin/bash
eval "$@"
```

*IMPORTANT NOTE*

Since copying the policy file and our script requires sudo privileges,
the installation script will now prompt for the password at very end.
Only on Linux, if `pexec` is installed.

Screenshots:

KDE with policy file:
![Screenshot from 2024-12-15
22-13-06](https://github.com/user-attachments/assets/b8bb7565-85df-4c95-bb10-82e50acf9b56)

Gnome with policy file:
![Screenshot from 2024-12-15
22-21-48](https://github.com/user-attachments/assets/83d15056-a2bd-41d9-a01d-9b8954260381)

Gnome without policy file:

![image](https://github.com/user-attachments/assets/66c39d02-eed4-4f09-886f-621b6d37ff43)

VSCode:

![image](https://github.com/user-attachments/assets/949dc470-c3df-4e2f-8cc6-31babaee1d18)

User declines the permission request:

![image](https://github.com/user-attachments/assets/c5cbf056-f6f9-43a8-8d88-f2b0597e14d6)

Release Notes:

- Fixed file saving with root ownership on Linux.
2024-12-19 22:16:01 +00:00
Michael Sloan
5b86845605 Fix docs for Bounds::from_corner_and_size (#22265)
(left in a todo!, oops)

Release Notes:

- N/A
2024-12-19 21:50:52 +00:00
Marshall Bowers
9782abf3c5 ci: Put docs-only conditionals on each step (#22261)
This PR is a follow up to #22254 with a different approach.

We need to put the conditional on each step in order to skip them, as I
couldn't see any other way of bailing out of the pipeline early based on
a condition.

Release Notes:

- N/A
2024-12-19 19:46:10 +00:00
Cole Miller
6231072d85 Hide chat panel button when not in a call (#22200)
cc @nathansobo 

Release Notes:

- Hide chat panel button by default when not in a call
2024-12-19 19:32:45 +00:00
Cole Miller
2094d50514 Fix permalink-to-line when Git repo root and worktree dir don't coincide (#22003)
Closes #21505. This should work if the git dir is an ancestor of the
worktree dir or vice versa.

Release Notes:

- Fixed GitHub permalink-to-line actions when worktree dir and Git dir
aren't the same
2024-12-19 19:23:50 +00:00
Ringo De Smet
1e2fa3b022 Update macOS system requirements in docs (#22248)
Update macOS system requirements to include Sequoia (15.x), the latest
major version.

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-12-19 17:55:46 +00:00
Piotr Osiewicz
8e81070091 terminal: Clear output after venv is activated (#22256)
The command used to activate the venv can still be accessed/scrolled to
if needed.

Release Notes:

- The Python virtual environment activation command is no longer shown
in the terminal output by default.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-19 17:43:19 +00:00
Thorsten Ball
1071814d41 zeta: Always include current file's outline in telemetry (#22257)
This doesn't include the outline in the prompt yet, but it does send it
up via telemetry so we can use it to see whether it would have improved
generated output.

Release Notes:

- N/A

Co-authored-by: Agus <agus@zed.dev>
2024-12-19 17:17:10 +00:00
Marshall Bowers
3d3d8f20eb ci: Run required status checks for docs-only PRs, but exit early (#22254)
I noticed a problem with the addition of required status checks where
PRs that only touched the docs wouldn't pass the status checks due to
all of the checks being skipped via `paths-ignore`:

<img width="950" alt="Screenshot 2024-12-19 at 11 16 38 AM"
src="https://github.com/user-attachments/assets/a6fa43ee-de63-40a6-a15a-f2f3519e9db8"
/>

This PR aims to address this by making it so required status checks run
for docs-only PRs, but exit early (before doing all of the work).

Release Notes:

- N/A
2024-12-19 17:05:11 +00:00
Aaron Feickert
536a958c58 Fix cursor overlap (#21999)
When the cursor has a block shape and is not on the first line, its name
popup exhibits an overlap:
<img width="171" alt="Screenshot 2024-12-13 at 18 00 54"
src="https://github.com/user-attachments/assets/1dc2ef93-020b-45c4-9fc6-db7d97f65c62"
/>

This occurs because the popup's horizontal alignment is set differently
when the cursor
[is](fff12ec1e5/crates/editor/src/element.rs (L6383))
or
[isn't](fff12ec1e5/crates/editor/src/element.rs (L6385))
on the first line.

This PR makes the horizontal alignment the same in both cases, which
removes the overlap:
<img width="176" alt="Screenshot 2024-12-13 at 17 57 20"
src="https://github.com/user-attachments/assets/a3c10ed5-6a1b-4040-9408-92290e9da30b"
/>

Closes #21887.

Release Notes:

- Fixed an overlap that cuts off user names when a cursor has a block
shape.
2024-12-19 16:24:30 +00:00
Thorsten Ball
96ad022cd7 Fix project environment not working correctly with multiple worktrees (#22246)
Fixes https://github.com/zed-industries/zed/issues/21972

This fixes two bugs:

**Bug 1**: this bug caused us to only ever load a single environment in
a multi-worktree project, thanks to this line:

```rust
if let Some(task) = self.get_environment_task.as_ref()
```

We'd only ever run a single task per project, which is wrong.

What does code does is to cache the tasks per `worktree_id`, which means
we don't even need to cache the environments again, since we can just
cache the `Shared<Task<...>>`.

**Bug 2**: we assumed that every `worktree_abs_path` is a directory,
which lead to `Failed to run direnv` log messages when opening a project
that had a worktree with a single file open (easy to reproduce: open a
normal project, open your settings, close Zed, reopen it — the settings
faile caused environments to not load)

It's fixed by checking whether the `worktree_abs_path` is an absolute
directory. Since this is always running locally, it's fine to use
`smol::fs` here instead of using our `Fs`.

Release Notes:

- Fixed shell environments not being loaded properly to be used by
language servers and terminals in case a project had multiple worktrees.
- Fixed `Failed to run direnv` messages showing up in case Zed restored
a window that contained a worktree with a single file.
https://github.com/zed-industries/zed/issues/21972
2024-12-19 14:50:14 +00:00
Agus Zubiaga
11260e6d37 Match keymap-style action names in command palette (#22149)
For example, `editor::TabPrev` matches "editor: tab prev".

Release Notes:

- Added support for searching command palette using keymap-style action
names.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-19 12:48:54 +00:00
Thorsten Ball
d54662e683 zeta: Do not change user's menu selection if completion is late (#22238)
Release Notes:

- N/A
2024-12-19 09:42:29 +00:00
Thorsten Ball
2a17274ec2 zeta: Always show ghost text, even if showing completion in menu (#22194)
See:
![screenshot-2024-12-18-17 26
22@2x](https://github.com/user-attachments/assets/82bdea76-6c96-4c4d-a08a-1601381a378f)


https://github.com/user-attachments/assets/165d9f0d-8339-4fd9-a796-a67121689af6



Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2024-12-19 09:00:58 +00:00
Michael Sloan
b93cee8d27 Use static LazyLocks for all constant regexes (#22225)
Release Notes:

- N/A
2024-12-19 02:20:35 +00:00
Peter Tripp
837bbc851f astro: Bump to v0.1.2 (#22220)
Includes:
- https://github.com/zed-industries/zed/pull/20206

Release Notes:

- N/A
2024-12-18 23:22:24 +00:00
Peter Tripp
0fe88a88b1 elixir: Bump to v0.1.2 (#22219)
Includes:
- https://github.com/zed-industries/zed/pull/21666
- https://github.com/zed-industries/zed/pull/20206

Release Notes:

- N/A
2024-12-18 23:22:19 +00:00
renovate[bot]
2b4f0deff5 Update Rust crate async-tungstenite to v0.28.2 (#22206)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[async-tungstenite](https://redirect.github.com/sdroege/async-tungstenite)
| workspace.dependencies | patch | `0.28.1` -> `0.28.2` |

---

### Release Notes

<details>
<summary>sdroege/async-tungstenite (async-tungstenite)</summary>

###
[`v0.28.2`](https://redirect.github.com/sdroege/async-tungstenite/blob/HEAD/CHANGELOG.md#0282---2024-12-15)

[Compare
Source](https://redirect.github.com/sdroege/async-tungstenite/compare/0.28.1...0.28.2)

##### Fixed

- Add `alloc` feature to `futures_task` dependency to make sure
`futures-task::ArcWake` is available.

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 23:05:46 +00:00
Marshall Bowers
4f2ab812fb ci: Enable merge_group for workflows (#22214)
Enable the `merge_group` for CI workflows so that they work with the
merge queue.

Release Notes:

- N/A
2024-12-18 22:59:20 +00:00
Michael Sloan
3f40d76be4 Enable search within diagnostics pane (#22213)
Closes #16033
Addresses one part of #4475

Release Notes:

- Added support for find and replace in diagnostics. Also causes
keybindings that use search to now work (such as `*` and `#` vim
bindings).
2024-12-18 22:52:16 +00:00
Michael Sloan
6bb21b1e5e Fix vim repeat (.) and macro playback insertions in diagnostics and assistant (#22210)
Release Notes:

- Fixed vim repeat (`.`) and macro playback of insertions in diagnostics
and assistant.

Co-authored-by: Conrad <conrad@zed.dev>
2024-12-18 15:30:52 -07:00
tims
f7a7866d4a linux: Implement Menus (#21873)
Closes #19837

This PR implements menus for Linux and Windows, inspired by JetBrains
IDEs. Thanks to @notpeter for the inspiration.


https://github.com/user-attachments/assets/7267fcdf-fec5-442e-a53b-281f89471095

I plan to complete this in multiple parts. While this PR delivers a
fully functional menus, there are many UX improvements that can be done.
So, this is part 1 of 3.

**This PR**:
- [x] Clicking the application menu opens the first menu popup. This
also shows other available menus.
- [x] While a menu is open, hovering over other menus opens them without
needing a click.
- [x] Up/down arrow keys works out of the box. Thanks GPUI. 

**Future - Part 2**:
- Add keybinding support to open specific menus using `Option + first
character of menu item`.
- Add support for left/right arrow keys to move between menus.

**Future - Part 3**:
- Implement nested context menus in GPUI for submenus. (I haven't
checked if this already exists).

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-12-18 17:27:25 -05:00
Dzmitry Malyshau
298b9df589 Switch to a single GPU context in Blade (#20853)
Closes #17005

Release Notes:

- Improved GPU context management: share a single context with multiple
surfaces.

### High Level

Blade got a proper support for Surface objects in
https://github.com/kvark/blade/pull/203.
That was mainly motivated by Zed needing to draw multiple windows. With
the Surface API, Zed is now able to have the GPU context tied to the
"Platform" instead of "Window". Practically speaking, this means:
  - architecture more sound
  - faster to open/close windows
  - less surprises, more robust

### Concerns

1. Zed has been using a temporary workaround for the platform bug on
some Intel+Nvidia machines that makes us unable to present -
https://github.com/kvark/blade/pull/144 . This workaround is no longer
available with the new architecture. I'm looking for ideas on how to
approach this better.
- we are now picking up the change in
https://github.com/kvark/blade/pull/210, which allows forcing a specific
Device ID. This should allow Zed users to work around the issue. We
could help them to automate it, too.
2. ~~Metal-rs dependency is switched to
https://github.com/kvark/metal-rs/tree/blade, since upstream isn't
responsive in merging changes that are required for Blade. Hopefully,
temporary.~~
- ~~we can also hack around it by just transmuting the texture
references, since we know those are unchanged in the branch. That would
allow Blade to use it's own version of Metal, temporarily, if switching
metal-rs in the workspace is a concern.~~
- merged my metal-rs changes and updated Zed to use the upstream github
reference

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-12-18 14:47:09 -07:00
Agus Zubiaga
56d20fc0a3 Reuse prompt editor across buffer and terminal assist (#22188)
Builds on https://github.com/zed-industries/zed/pull/22160 and extracts
the rest of `PromptEditor` so it can be shared across terminal and
inline assistants. This will help avoid the UI drifting as we have
already observed.

Note: This is mostly a mechanical refactor. I imagine some things could
be factored in a better way by someone with more context, but I think
this is a good start.

Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2024-12-18 16:12:54 -05:00
Marshall Bowers
fc00eaa161 assistant2: Reduce message editor's maximum height (#22205)
This PR reduces the message editor's maximum height to 10 lines, after
which point it will scroll.

Release Notes:

- N/A
2024-12-18 16:11:31 -05:00
renovate[bot]
7414e91a85 Update actions/upload-artifact digest to 6f51ac0 (#22203)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | digest | `b4b15b8` -> `6f51ac0` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:59:38 +02:00
renovate[bot]
b51a162d22 Update Rust crate tiktoken-rs to 0.6.0 (#21900)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tiktoken-rs](https://redirect.github.com/zurawiki/tiktoken-rs) |
workspace.dependencies | minor | `0.5.9` -> `0.6.0` |

---

### Release Notes

<details>
<summary>zurawiki/tiktoken-rs (tiktoken-rs)</summary>

###
[`v0.6.0`](https://redirect.github.com/zurawiki/tiktoken-rs/releases/tag/v0.6.0)

[Compare
Source](https://redirect.github.com/zurawiki/tiktoken-rs/compare/v0.5.9...v0.6.0)

**Minor version release signifies a breaking change in 0.x**

#### What's Changed

- Add support for chatgpt-4o-latest by
[@&#8203;Congyuwang](https://redirect.github.com/Congyuwang) in
[https://github.com/zurawiki/tiktoken-rs/pull/85](https://redirect.github.com/zurawiki/tiktoken-rs/pull/85)
- Refactor internals to make future updates to tiktoken easier to merge
-   Do not expose tiktoken internal modules and functions
-   Update dependencies

#### New Contributors

- [@&#8203;Congyuwang](https://redirect.github.com/Congyuwang) made
their first contribution in
[https://github.com/zurawiki/tiktoken-rs/pull/85](https://redirect.github.com/zurawiki/tiktoken-rs/pull/85)

**Full Changelog**:
https://github.com/zurawiki/tiktoken-rs/compare/v0.5.9...v0.6.0

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS41OC4xIiwidXBkYXRlZEluVmVyIjoiMzkuNTguMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:56 +02:00
renovate[bot]
78dde63337 Update Rust crate heed to 0.21.0 (#21892)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [heed](https://redirect.github.com/Kerollmops/heed) |
workspace.dependencies | minor | `0.20.1` -> `0.21.0` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzOS41OC4xIiwidXBkYXRlZEluVmVyIjoiMzkuNTguMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:48 +02:00
renovate[bot]
c0b40d0bd0 Update Rust crate cargo_toml to 0.21 (#21880)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [cargo_toml](https://lib.rs/cargo_toml)
([source](https://gitlab.com/lib.rs/cargo_toml)) |
workspace.dependencies | minor | `0.20` -> `0.21` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzOS41OC4xIiwidXBkYXRlZEluVmVyIjoiMzkuNTguMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:30 +02:00
renovate[bot]
0acb743dac Update 2428392/gh-truncate-string-action action to v1.4.1 (#22204)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[2428392/gh-truncate-string-action](https://redirect.github.com/2428392/gh-truncate-string-action)
| action | patch | `v1.4.0` -> `v1.4.1` |

---

### Release Notes

<details>
<summary>2428392/gh-truncate-string-action
(2428392/gh-truncate-string-action)</summary>

###
[`v1.4.1`](https://redirect.github.com/2428392/gh-truncate-string-action/releases/tag/v1.4.1)

[Compare
Source](https://redirect.github.com/2428392/gh-truncate-string-action/compare/v1.4.0...v1.4.1)

#### What's Changed

- update action.yml inputs to include truncationSymbol by
[@&#8203;aballman](https://redirect.github.com/aballman) in
[https://github.com/2428392/gh-truncate-string-action/pull/14](https://redirect.github.com/2428392/gh-truncate-string-action/pull/14)

#### New Contributors

- [@&#8203;aballman](https://redirect.github.com/aballman) made their
first contribution in
[https://github.com/2428392/gh-truncate-string-action/pull/14](https://redirect.github.com/2428392/gh-truncate-string-action/pull/14)

**Full Changelog**:
https://github.com/2428392/gh-truncate-string-action/compare/v1...v1.4.1

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS43Mi41IiwidXBkYXRlZEluVmVyIjoiMzkuNzIuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:02 +02:00
Marshall Bowers
8b2afab0d3 assistant2: Wire up basic @-mention interaction for context (#22197)
This PR adds an initial version of using `@` in the message editor to
add context to the thread.

We don't yet insert any sort of reference to it in the message body
itself.

Release Notes:

- N/A
2024-12-18 13:29:39 -05:00
Peter Tripp
b79117c882 Bump Zed to v0.168.x (#22198) 2024-12-18 12:03:40 -05:00
Conrad Irwin
77abf13f42 Allow telemetry::event! with no properties (#22190)
CC @josephTLyons

Release Notes:

- N/A
2024-12-18 09:41:47 -07:00
Michael Sloan
433cb99170 Remove main push from bump-zed-minor-versions script (#22170)
Motivation for this is to allow the `main` branch to be protected. This will enable use of auto-merge and merge queue.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-18 11:32:58 -05:00
Marshall Bowers
670ade9546 extensions_ui: Don't block scrolling on disabled extension cards (#22189)
This PR fixes an issue where disabled extension cards would block scroll
events when the mouse was over them.

Instead of using `.occlude`—which captures _all_ mouse events—we use
`.block_mouse_down` to just block mouse down events (to prevent clicking
the buttons on the disabled card). We also set the cursor style to the
default, as it was still switching to the pointer when hovered over top
of one of the buttons.

Release Notes:

- Fixed disabled extensions blocking scroll events in the extensions
list.
2024-12-18 11:20:58 -05:00
Conrad Irwin
4a6f071fde vim: Add support for :g/ and :v/ (#22177)
Closes #ISSUE

Still TODO to make this feature good is better command history

Release Notes:

- vim: Add support for `:g/<pattern>/<cmd>` and `:v/<pattern>/<cmd>`
2024-12-18 08:28:42 -07:00
Conrad Irwin
2ecbd97fe8 vim: Support count with [x and ]x (#22176)
Fixes: #21577
Fixes: #17245

Release Notes:

- vim: Add <count> support for [x/]x
2024-12-18 08:28:22 -07:00
Thorsten Ball
a0a095c6a3 zeta: Show deletions when inline completion is shown in menu (#22186)
If an inline completion isn't shown in a menu, we highlight text in the
editor as deleted.

But if it's shown in the menu, we didn't even show deleted text, which
makes it hard to understand what's going on.

This fixes it.
![screenshot-2024-12-18-14 34
55@2x](https://github.com/user-attachments/assets/579639e4-5ed9-4fe6-8e21-65166d192432)


Release Notes:

- N/A
2024-12-18 14:46:51 +01:00
Richard Feldman
4bfc107e3a Fix inconsistencies in "Transform" vs "Generate" tooltips for assistant v2 (#22160)
Also makes the inline assistant and inline terminal assistant share a
bunch more code.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
2024-12-18 07:10:55 -05:00
Thorsten Ball
6192c83f23 Show inline completions in menu only for supported providers (#22181)
This changes #22093 by making the change only have an effect for the
supported provider: Zeta.

Made the change because the UX is still experimental and I don't want to
break existing workflows for Copilot/Supermaven users.

Even Zeta users can opt-out of it by setting
`"show_inline_completions_in_menu": false` in their settings, in case
they want the old show-inline-completion-or-show-lsp-completion behavior
back.

Release Notes:

- N/A
2024-12-18 12:55:09 +01:00
Hai Zhu
e8c9283700 Fix the illegal JSON in the documentation (#22175)
Release Notes:

- N/A
2024-12-18 11:10:13 +02:00
Thorsten Ball
2469122784 Fix panic when calculating inline completion (#22180)
Possible panic here in case we can't find the excerpt in the
multibuffer.

I thought this could happen when the inline completion references an
excerpt that disappeared from the multibuffer, but looking at the code
I'm not sure anymore - we use the same multibuffer snapshot in this
whole function and the `anchor_in_excerpt` method clips the anchors.

Still, let's be safe here.

Release Notes:

- N/A
2024-12-18 09:53:34 +01:00
Conrad Irwin
6898a31f06 Fix panic in linked editing ranges (#21905)
We were committing the cardinal sin of subtracting one from a column
without clipping

Closes #ISSUE

Release Notes:

- Fixed a panic in linked editing ranges
2024-12-17 22:17:08 -07:00
Conrad Irwin
94bfb93d35 More telemetry events (#22171)
- **Convert more events to telemetry::event**
- **And call events**

Closes #ISSUE

Release Notes:

- N/A
2024-12-17 22:16:59 -07:00
Enrique Kessler Martínez
1b83020dc8 Update collab local development instructions (#22018)
When running the collab server locally on a new machine, I found there
were some instructions missing for setting up the Postgres database.
Namely, the user mentioned on the `.env.toml` is not created by default.

Ref: https://github.com/zed-industries/zed/issues/8260

Release Notes:

- N/A
2024-12-17 20:43:48 -07:00
Cole Miller
613deb6421 Remove panics from X11Window::set_title (#22173)
Release Notes:

- N/A
2024-12-17 22:15:35 -05:00
Cole Miller
672fc76832 Fix panic in activate_pane_in_direction (#21948)
Ah, invalid SecondaryMap key, we meet again...

Release Notes:

- Fixed panic when jumping between panes
2024-12-17 22:10:30 -05:00
Michael Sloan
5d7b6141fd Improve context menu aside layout via custom logic (#22154)
* Presence of the aside no longer affects position or size of the
context menu.

* Prefers to fit to the right, then on same side of line, then other
side of line, within the following preference order:
  - Max possible size within text area.
  - Max possible size within window.
- Actual size within window. This is the only case that could cause it
to jump around with less stability.

A further enhancement atop this might be to dynamically resize aside
height to fit.

Release notes are N/A as they are covered by the notes for #22102.

Closes #8523

Release Notes:

* N/A
2024-12-17 17:01:15 -07:00
Marshall Bowers
6aad616165 assistant2: Fix focus issues with opening history (#22169)
This PR fixes some focus issues with opening the thread history, which
required that the Assistant2 panel be focused in order for the history
to open.

Release Notes:

- N/A
2024-12-17 18:07:28 -05:00
Marshall Bowers
918866b7de assistant2: Allow creating a new thread via the command palette (#22168)
This PR allows a new thread to be created via the command palette when
the Assistant2 panel is not focused.

Release Notes:

- N/A
2024-12-17 17:51:47 -05:00
Peter Tripp
5b2653a1d1 Improve GitHub Issue template for Environment (#22147)
Add support for pre-filling feature request template from zed::RequestFeature action.

Co-authored-by: Agus <agus@zed.dev>
2024-12-17 17:28:47 -05:00
Marshall Bowers
ba44db7f49 gpui: Rename GPUSpecs to GpuSpecs (#22166)
This PR renames the `GPUSpecs` type to `GpuSpecs` to match Rust naming
conventions.

Release Notes:

- N/A
2024-12-17 17:22:53 -05:00
Danilo Leal
ce97e4ddc1 assistant2: Add temporary icon for the status bar item (#22156)
This is temporary and should be removed whenever we fully transition to
Assistant 2.

<img width="800" alt="Screenshot 2024-12-17 at 16 50 48"
src="https://github.com/user-attachments/assets/61137993-ec2c-4754-9f9a-7559d1b21d89"
/>

Release Notes:

- N/A
2024-12-17 16:57:47 -05:00
Marshall Bowers
63d8a43f9d assistant2: Fix file context picker (#22163)
This PR fixes an issue with the Assistant2 file context picker where
files weren't being attached properly.

We needed to retrieve the files from the worktree without the worktree
root name in the file path.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-12-17 16:53:56 -05:00
138 changed files with 8611 additions and 17931 deletions

View File

@@ -18,8 +18,11 @@ body:
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
label: Zed Version and System Specs
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
placeholder: |
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `request feature` and this field will be autopopulated -->
validations:
required: true
- type: textarea

View File

@@ -20,8 +20,13 @@ body:
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
label: Zed Version and System Specs
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
placeholder: |
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `file bug report` and this field will be autopopulated -->
<!-- If Zed won't launch, include the equivalent with other relevant details (e.g. video card driver version for display bugs, etc) -->
<!-- Zed Version: 0.xxx.x; Channel: Stable, OS: macOS xx.xx, RAM: XXGB, Architecture: x86_64"
validations:
required: true
- type: textarea

View File

@@ -7,14 +7,10 @@ on:
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
paths-ignore:
- "docs/**"
pull_request:
branches:
- "**"
paths-ignore:
- "docs/**/*"
- ".github/workflows/community_*"
merge_group:
concurrency:
# Allow only one workflow per any non-`main` branch.
@@ -28,6 +24,23 @@ env:
RUSTFLAGS: "-D warnings"
jobs:
check_docs_only:
runs-on: ubuntu-latest
outputs:
docs_only: ${{ steps.check_changes.outputs.docs_only }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for non-docs changes
id: check_changes
run: |
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
echo "docs_only=false" >> $GITHUB_OUTPUT
else
echo "docs_only=true" >> $GITHUB_OUTPUT
fi
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
if: github.repository_owner == 'zed-industries'
@@ -96,6 +109,7 @@ jobs:
runs-on:
- self-hosted
- test
needs: check_docs_only
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -103,29 +117,35 @@ jobs:
clean: false
- name: cargo clippy
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/clippy
- name: Check unused dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: bnjbvr/cargo-machete@main
- name: Check licenses
if: needs.check_docs_only.outputs.docs_only == 'false'
run: |
script/check-licenses
script/generate-licenses /tmp/zed_licenses_output
- name: Check for new vulnerable dependencies
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && needs.check_docs_only.outputs.docs_only == 'false'
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
with:
license-check: false
- name: Run tests
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: ./.github/actions/run_tests
- name: Build collab
if: needs.check_docs_only.outputs.docs_only == 'false'
run: cargo build -p collab
- name: Build other binaries and features
if: needs.check_docs_only.outputs.docs_only == 'false'
run: |
cargo build --workspace --bins --all-features
cargo check -p gpui --features "macos-blade"
@@ -139,6 +159,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
needs: check_docs_only
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -149,21 +170,26 @@ jobs:
clean: false
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Linux dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/linux
- name: cargo clippy
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/clippy
- name: Run tests
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: ./.github/actions/run_tests
- name: Build other binaries and features
if: needs.check_docs_only.outputs.docs_only == 'false'
run: |
cargo build -p zed
cargo check -p workspace
@@ -174,6 +200,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
needs: check_docs_only
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -184,15 +211,18 @@ jobs:
clean: false
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Clang & Mold
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/remote-server && ./script/install-mold 2.34.0
- name: Build Remote Server
if: needs.check_docs_only.outputs.docs_only == 'false'
run: cargo build -p remote_server
# todo(windows): Actually run the tests
@@ -201,6 +231,7 @@ jobs:
name: (Windows) Run Clippy and tests
if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-1
needs: check_docs_only
steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
@@ -211,16 +242,19 @@ jobs:
clean: false
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
- name: cargo clippy
if: needs.check_docs_only.outputs.docs_only == 'false'
# Windows can't run shell scripts, so we need to use `cargo xtask`.
run: cargo xtask clippy
- name: Build Zed
if: needs.check_docs_only.outputs.docs_only == 'false'
run: cargo build
bundle-mac:
@@ -289,14 +323,14 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -345,7 +379,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -391,7 +425,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz

View File

@@ -18,7 +18,7 @@ jobs:
fi
echo "::set-output name=URL::$URL"
- name: Get content
uses: 2428392/gh-truncate-string-action@e6b5885fb83c81ca9a700a91b079baec2133be3e # v1.4.0
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
id: get-content
with:
stringToTruncate: |

View File

@@ -7,6 +7,7 @@ on:
push:
branches:
- main
merge_group:
jobs:
check_formatting:

194
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "activity_indicator"
@@ -934,7 +934,7 @@ dependencies = [
"chrono",
"futures-util",
"http-types",
"hyper 0.14.31",
"hyper 0.14.32",
"hyper-rustls 0.24.2",
"serde",
"serde_json",
@@ -1008,9 +1008,9 @@ dependencies = [
[[package]]
name = "async-tungstenite"
version = "0.28.1"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6cd055774e8b7f2ff9e5f9646efb298f180c3b886cdf20ef27f5a0bfbe2677b"
checksum = "1c348fb0b6d132c596eca3dcd941df48fb597aafcb07a738ec41c004b087dc99"
dependencies = [
"async-std",
"async-tls",
@@ -1166,9 +1166,9 @@ dependencies = [
[[package]]
name = "aws-config"
version = "1.5.10"
version = "1.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924"
checksum = "a5d1c2c88936a73c699225d0bc00684a534166b0cebc2659c3cdf08de8edc64c"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1177,7 +1177,7 @@ dependencies = [
"aws-sdk-sts",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json 0.60.7",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -1208,9 +1208,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
version = "1.4.4"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea"
checksum = "300a12520b4e6d08b73f77680f12c16e8ae43250d55100e0b2be46d78da16a48"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -1234,15 +1234,15 @@ dependencies = [
[[package]]
name = "aws-sdk-kinesis"
version = "1.52.0"
version = "1.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5d4932ecd8754ec808b57c13b5ab4965d2b568ae1c1984d1823a4e2aa3e7bc"
checksum = "cb367ea65d5a59b230d7e670ba59d68d1e51fc53802bf0219effafed21dca23f"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json 0.61.1",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -1256,9 +1256,9 @@ dependencies = [
[[package]]
name = "aws-sdk-s3"
version = "1.65.0"
version = "1.66.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3ba2c5c0f2618937ce3d4a5ad574b86775576fa24006bcb3128c6e2cbf3c34e"
checksum = "154488d16ab0d627d15ab2832b57e68a16684c8c902f14cb8a75ec933fc94852"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1267,7 +1267,7 @@ dependencies = [
"aws-smithy-checksums",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-json 0.61.1",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -1290,15 +1290,15 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
version = "1.50.0"
version = "1.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab"
checksum = "74995133da38f109a0eb8e8c886f9e80c713b6e9f2e6e5a6a1ba4450ce2ffc46"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json 0.61.1",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -1312,15 +1312,15 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
version = "1.51.0"
version = "1.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0"
checksum = "e7062a779685cbf3b2401eb36151e2c6589fd5f3569b8a6bc2d199e5aaa1d059"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json 0.61.1",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
@@ -1334,15 +1334,15 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
version = "1.51.0"
version = "1.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf"
checksum = "299dae7b1dc0ee50434453fa5a229dc4b22bd3ee50409ff16becf1f7346e0193"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json 0.61.1",
"aws-smithy-json",
"aws-smithy-query",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
@@ -1386,9 +1386,9 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c"
checksum = "8aa8ff1492fd9fb99ae28e8467af0dbbb7c31512b16fabf1a0f10d7bb6ef78bb"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -1448,15 +1448,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "aws-smithy-json"
version = "0.60.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-json"
version = "0.61.1"
@@ -1478,9 +1469,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
version = "1.7.4"
version = "1.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45"
checksum = "431a10d0e07e09091284ef04453dae4069283aa108d209974d67e77ae1caa658"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -1493,7 +1484,7 @@ dependencies = [
"http-body 0.4.6",
"http-body 1.0.1",
"httparse",
"hyper 0.14.31",
"hyper 0.14.32",
"hyper-rustls 0.24.2",
"once_cell",
"pin-project-lite",
@@ -1522,9 +1513,9 @@ dependencies = [
[[package]]
name = "aws-smithy-types"
version = "1.2.9"
version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510"
checksum = "8ecbf4d5dfb169812e2b240a4350f15ad3c6b03a54074e5712818801615f2dc5"
dependencies = [
"base64-simd",
"bytes 1.9.0",
@@ -1584,7 +1575,7 @@ dependencies = [
"headers",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.31",
"hyper 0.14.32",
"itoa",
"matchit",
"memchr",
@@ -1750,15 +1741,6 @@ dependencies = [
"bit-vec 0.6.3",
]
[[package]]
name = "bit-set"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f"
dependencies = [
"bit-vec 0.7.0",
]
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -1774,12 +1756,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bit-vec"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bit-vec"
version = "0.8.0"
@@ -1828,7 +1804,7 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.5.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"ash",
"ash-window",
@@ -1858,7 +1834,7 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"proc-macro2",
"quote",
@@ -1868,7 +1844,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
source = "git+https://github.com/kvark/blade?rev=099555282605c7c4cca9e66a8f40148298347f80#099555282605c7c4cca9e66a8f40148298347f80"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -2115,6 +2091,7 @@ dependencies = [
"serde",
"serde_derive",
"settings",
"telemetry",
"util",
]
@@ -2255,9 +2232,9 @@ dependencies = [
[[package]]
name = "cargo_toml"
version = "0.20.5"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0"
checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472"
dependencies = [
"serde",
"toml 0.8.19",
@@ -2522,7 +2499,7 @@ dependencies = [
"anyhow",
"async-native-tls",
"async-recursion 0.3.2",
"async-tungstenite 0.28.1",
"async-tungstenite 0.28.2",
"chrono",
"clock",
"cocoa 0.26.0",
@@ -2655,7 +2632,7 @@ dependencies = [
"assistant_tool",
"async-stripe",
"async-trait",
"async-tungstenite 0.28.1",
"async-tungstenite 0.28.2",
"audio",
"aws-config",
"aws-sdk-kinesis",
@@ -2687,7 +2664,7 @@ dependencies = [
"gpui",
"hex",
"http_client",
"hyper 0.14.31",
"hyper 0.14.32",
"indoc",
"jsonwebtoken",
"language",
@@ -2782,6 +2759,7 @@ dependencies = [
"settings",
"smallvec",
"story",
"telemetry",
"theme",
"time",
"time_format",
@@ -4410,6 +4388,7 @@ dependencies = [
"serde",
"settings",
"smallvec",
"telemetry",
"theme",
"ui",
"util",
@@ -4426,12 +4405,13 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fancy-regex"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7493d4c459da9f84325ad297371a6b2b8a162800873a22e3b6b6512e61d18c05"
checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2"
dependencies = [
"bit-set 0.5.3",
"regex",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
[[package]]
@@ -4803,11 +4783,13 @@ dependencies = [
"rope",
"serde",
"serde_json",
"shlex",
"smol",
"tempfile",
"text",
"time",
"util",
"which 6.0.3",
"windows 0.58.0",
]
@@ -5608,9 +5590,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "heed"
version = "0.20.5"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d4f449bab7320c56003d37732a917e18798e2f1709d80263face2b4f9436ddb"
checksum = "bd54745cfacb7b97dee45e8fdb91814b62bccddb481debb7de0f9ee6b7bf5b43"
dependencies = [
"bitflags 2.6.0",
"byteorder",
@@ -5633,9 +5615,9 @@ checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff"
[[package]]
name = "heed-types"
version = "0.20.1"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d3f528b053a6d700b2734eabcd0fd49cb8230647aa72958467527b0b7917114"
checksum = "13c255bdf46e07fb840d120a36dcc81f385140d7191c76a7391672675c01a55d"
dependencies = [
"bincode",
"byteorder",
@@ -5862,9 +5844,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.31"
version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes 1.9.0",
"futures-channel",
@@ -5877,7 +5859,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.8",
"socket2 0.4.10",
"tokio",
"tower-service",
"tracing",
@@ -5912,7 +5894,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.31",
"hyper 0.14.32",
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
@@ -5945,7 +5927,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.9.0",
"hyper 0.14.31",
"hyper 0.14.32",
"native-tls",
"tokio",
"tokio-native-tls",
@@ -6623,7 +6605,7 @@ checksum = "58d9afa5bc6eeafb78f710a2efc585f69099f8b6a99dc7eb826581e3773a6e31"
dependencies = [
"anyhow",
"async-trait",
"async-tungstenite 0.28.1",
"async-tungstenite 0.28.2",
"futures 0.3.31",
"jupyter-protocol",
"serde",
@@ -7000,7 +6982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@@ -7590,9 +7572,8 @@ dependencies = [
[[package]]
name = "metal"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
version = "0.30.0"
source = "git+https://github.com/gfx-rs/metal-rs?rev=ef768ff9d742ae6a0f4e83ddc8031264e7d460c4#ef768ff9d742ae6a0f4e83ddc8031264e7d460c4"
dependencies = [
"bitflags 2.6.0",
"block",
@@ -7702,13 +7683,11 @@ dependencies = [
"ctor",
"env_logger 0.11.5",
"futures 0.3.31",
"git",
"gpui",
"itertools 0.13.0",
"language",
"log",
"parking_lot",
"project",
"rand 0.8.5",
"serde",
"settings",
@@ -7733,12 +7712,11 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
[[package]]
name = "naga"
version = "22.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad"
version = "23.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=1a643291c2e8854ba7e4f5445a4388202731bfa1#1a643291c2e8854ba7e4f5445a4388202731bfa1"
dependencies = [
"arrayvec",
"bit-set 0.6.0",
"bit-set 0.8.0",
"bitflags 2.6.0",
"cfg_aliases 0.1.1",
"codespan-reporting",
@@ -9607,6 +9585,7 @@ dependencies = [
"tempfile",
"terminal",
"text",
"toml 0.8.19",
"unindent",
"url",
"util",
@@ -10374,7 +10353,7 @@ dependencies = [
"alacritty_terminal",
"anyhow",
"async-dispatcher",
"async-tungstenite 0.28.1",
"async-tungstenite 0.28.2",
"base64 0.22.1",
"client",
"collections",
@@ -10432,7 +10411,7 @@ dependencies = [
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.31",
"hyper 0.14.32",
"hyper-rustls 0.24.2",
"hyper-tls",
"ipnet",
@@ -10677,7 +10656,7 @@ name = "rpc"
version = "0.1.0"
dependencies = [
"anyhow",
"async-tungstenite 0.28.1",
"async-tungstenite 0.28.2",
"base64 0.22.1",
"chrono",
"collections",
@@ -11306,9 +11285,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [
"serde",
]
@@ -12877,7 +12856,6 @@ dependencies = [
name = "theme_selector"
version = "0.1.0"
dependencies = [
"client",
"fs",
"fuzzy",
"gpui",
@@ -12885,6 +12863,7 @@ dependencies = [
"picker",
"serde",
"settings",
"telemetry",
"theme",
"ui",
"util",
@@ -12955,16 +12934,17 @@ dependencies = [
[[package]]
name = "tiktoken-rs"
version = "0.5.9"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c314e7ce51440f9e8f5a497394682a57b7c323d0f4d0a6b1b13c429056e0e234"
checksum = "44075987ee2486402f0808505dd65692163d243a337fc54363d49afac41087f6"
dependencies = [
"anyhow",
"base64 0.21.7",
"bstr",
"fancy-regex 0.12.0",
"fancy-regex 0.13.0",
"lazy_static",
"parking_lot",
"regex",
"rustc-hash 1.1.0",
]
@@ -13114,6 +13094,7 @@ dependencies = [
"settings",
"smallvec",
"story",
"telemetry",
"theme",
"tree-sitter-md",
"ui",
@@ -13487,9 +13468,9 @@ dependencies = [
[[package]]
name = "tree-sitter-c"
version = "0.23.2"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db56fadd8c3c6bc880dffcf1177c9d1c54a71a5207716db8660189082e63b587"
checksum = "afd2b1bf1585dc2ef6d69e87d01db8adb059006649dd5f96f31aa789ee6e9c71"
dependencies = [
"cc",
"tree-sitter-language",
@@ -13604,9 +13585,9 @@ dependencies = [
[[package]]
name = "tree-sitter-json"
version = "0.23.0"
version = "0.24.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86a5d6b3ea17e06e7a34aabeadd68f5866c0d0f9359155d432095f8b751865e4"
checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471"
dependencies = [
"cc",
"tree-sitter-language",
@@ -14247,7 +14228,7 @@ dependencies = [
"futures-util",
"headers",
"http 0.2.12",
"hyper 0.14.31",
"hyper 0.14.32",
"log",
"mime",
"mime_guess",
@@ -14892,6 +14873,7 @@ dependencies = [
"schemars",
"serde",
"settings",
"telemetry",
"ui",
"util",
"vim_mode_setting",
@@ -14997,7 +14979,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -15998,7 +15980,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.167.0"
version = "0.168.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16095,6 +16077,7 @@ dependencies = [
"tab_switcher",
"task",
"tasks_ui",
"telemetry",
"telemetry_events",
"terminal_view",
"theme",
@@ -16131,7 +16114,7 @@ dependencies = [
[[package]]
name = "zed_astro"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"serde",
"zed_extension_api 0.1.0",
@@ -16160,7 +16143,7 @@ dependencies = [
[[package]]
name = "zed_elixir"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -16467,6 +16450,7 @@ dependencies = [
"serde_json",
"settings",
"similar",
"telemetry",
"telemetry_events",
"theme",
"tree-sitter-go",

View File

@@ -355,13 +355,13 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-util = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
cargo_toml = "0.20"
cargo_toml = "0.21"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26"
@@ -385,7 +385,7 @@ 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"] }
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
hyper = "0.14"
@@ -472,7 +472,7 @@ sys-locale = "0.3.1"
sysinfo = "0.31.0"
tempfile = "3.9.0"
thiserror = "1.0.29"
tiktoken-rs = "0.5.9"
tiktoken-rs = "0.6.0"
time = { version = "0.3", features = [
"macros",
"parsing",
@@ -498,7 +498,7 @@ tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex",
tree-sitter-diff = "0.1.0"
tree-sitter-html = "0.20"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
@@ -525,6 +525,12 @@ wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zstd = "0.11"
# Custom metal-rs is only needed for "macos-blade" feature of GPUI
#TODO: switch to crates once these are published:
# - https://github.com/gfx-rs/metal-rs/pull/335
# - https://github.com/gfx-rs/metal-rs/pull/336
# - https://github.com/gfx-rs/metal-rs/pull/337
metal = { git = "https://github.com/gfx-rs/metal-rs", rev = "ef768ff9d742ae6a0f4e83ddc8031264e7d460c4" }
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.27772 1.38585L4.39187 4.07909C4.34653 4.21692 4.26946 4.34219 4.16685 4.44479C4.06425 4.5474 3.93898 4.62447 3.80115 4.66981L1.10791 5.55566L3.80115 6.44151C3.93898 6.48685 4.06425 6.56392 4.16685 6.66653C4.26946 6.76913 4.34653 6.8944 4.39187 7.03223L5.27772 9.72547L6.16357 7.03223C6.20891 6.8944 6.28598 6.76913 6.38859 6.66653C6.49119 6.56392 6.61646 6.48685 6.7543 6.44151L9.44753 5.55566L6.7543 4.66981C6.61646 4.62447 6.49119 4.5474 6.38859 4.44479C6.28598 4.34219 6.20891 4.21692 6.16357 4.07909L5.27772 1.38585Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.35938 12.3555C8.35938 12.0664 8.52734 11.8086 9.00781 11.3594L10.2031 10.2344C10.6094 9.85156 10.7891 9.60156 10.7891 9.34375C10.7891 9.05469 10.5781 8.85938 10.2734 8.85938C10.0391 8.85938 9.87109 8.95312 9.66406 9.21094C9.42578 9.50781 9.25391 9.60938 8.99219 9.60938C8.61719 9.60938 8.35156 9.35938 8.35156 9.01172C8.35156 8.25 9.26953 7.57812 10.3594 7.57812C11.4961 7.57812 12.3438 8.26172 12.3438 9.17969C12.3438 9.75391 12.0391 10.3008 11.418 10.8516L10.4961 11.6719V11.7344H11.8047C12.2578 11.7344 12.5391 11.9766 12.5391 12.3711C12.5391 12.7656 12.2656 13 11.8047 13H9.08203C8.65234 13 8.35938 12.7383 8.35938 12.3555Z" fill="black"/>
<path d="M11.0834 1.38585V3.71918M9.91675 2.55248H12.2501" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -230,8 +230,8 @@
"ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore",
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode",
"[ x": "vim::SelectLargerSyntaxNode",
"] x": "vim::SelectSmallerSyntaxNode",
"] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",

View File

@@ -160,6 +160,9 @@
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
/// Whether to show the inline completions next to the completions provided by a language server.
/// Only has an effect if inline completion provider supports it.
"show_inline_completions_in_menu": true,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -478,8 +481,10 @@
"default_width": 240
},
"chat_panel": {
// Whether to show the chat panel button in the status bar.
"button": true,
// When to show the chat panel button in the status bar.
// Can be 'never', 'always', or 'when_in_call',
// or a boolean (interpreted as 'never'/'always').
"button": "when_in_call",
// Where to the chat panel. Can be 'left' or 'right'.
"dock": "right",
// Default width of the chat panel.

View File

@@ -22,6 +22,7 @@ use paths::contexts_dir;
use project::Project;
use regex::Regex;
use rpc::AnyProtoClient;
use std::sync::LazyLock;
use std::{
cmp::Reverse,
ffi::OsStr,
@@ -753,8 +754,8 @@ impl ContextStore {
continue;
}
let pattern = r" - \d+.zed.json$";
let re = Regex::new(pattern).unwrap();
static ASSISTANT_CONTEXT_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r" - \d+.zed.json$").unwrap());
let metadata = fs.metadata(&path).await?;
if let Some((file_name, metadata)) = path
@@ -763,11 +764,15 @@ impl ContextStore {
.zip(metadata)
{
// This is used to filter out contexts saved by the new assistant.
if !re.is_match(file_name) {
if !ASSISTANT_CONTEXT_REGEX.is_match(file_name) {
continue;
}
if let Some(title) = re.replace(file_name, "").lines().next() {
if let Some(title) = ASSISTANT_CONTEXT_REGEX
.replace(file_name, "")
.lines()
.next()
{
contexts.push(SavedContextMetadata {
title: title.to_string(),
path,

View File

@@ -1,14 +1,17 @@
mod active_thread;
mod assistant_panel;
mod assistant_settings;
mod buffer_codegen;
mod context;
mod context_picker;
mod context_store;
mod context_strip;
mod inline_assistant;
mod inline_prompt_editor;
mod message_editor;
mod prompts;
mod streaming_diff;
mod terminal_codegen;
mod terminal_inline_assistant;
mod thread;
mod thread_history;

View File

@@ -27,9 +27,22 @@ use crate::{NewThread, OpenHistory, ToggleFocus};
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
});
workspace
.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
})
.register_action(|workspace, _: &NewThread, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| panel.new_thread(cx));
workspace.focus_panel::<AssistantPanel>(cx);
}
})
.register_action(|workspace, _: &OpenHistory, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_history(cx));
}
});
},
)
.detach();
@@ -157,6 +170,12 @@ impl AssistantPanel {
self.message_editor.focus_handle(cx).focus(cx);
}
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(cx);
cx.notify();
}
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
let Some(thread) = self
.thread_store
@@ -257,7 +276,7 @@ impl Panel for AssistantPanel {
}
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
Some(IconName::ZedAssistant)
Some(IconName::ZedAssistant2)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -572,9 +591,7 @@ impl Render for AssistantPanel {
this.new_thread(cx);
}))
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
this.active_view = ActiveView::History;
this.history.focus_handle(cx).focus(cx);
cx.notify();
this.open_history(cx);
}))
.child(self.render_toolbar(cx))
.map(|parent| match self.active_view {

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,12 @@ use crate::context_picker::thread_context_picker::ThreadContextPicker;
use crate::context_store::ContextStore;
use crate::thread_store::ThreadStore;
#[derive(Debug, Clone, Copy)]
pub enum ConfirmBehavior {
KeepOpen,
Close,
}
#[derive(Debug, Clone)]
enum ContextPickerMode {
Default,
@@ -41,6 +47,7 @@ impl ContextPicker {
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let mut entries = vec![
@@ -74,6 +81,7 @@ impl ContextPicker {
workspace,
thread_store,
context_store,
confirm_behavior,
entries,
selected_ix: 0,
};
@@ -136,6 +144,7 @@ pub(crate) struct ContextPickerDelegate {
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
entries: Vec<ContextPickerEntry>,
selected_ix: usize,
}
@@ -175,6 +184,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));
@@ -185,6 +195,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));
@@ -195,6 +206,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));
@@ -206,6 +218,7 @@ impl PickerDelegate for ContextPickerDelegate {
thread_store.clone(),
self.context_picker.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));

View File

@@ -11,7 +11,7 @@ use ui::{prelude::*, ListItem};
use util::ResultExt as _;
use workspace::Workspace;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct DirectoryContextPicker {
@@ -23,10 +23,15 @@ impl DirectoryContextPicker {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate =
DirectoryContextPickerDelegate::new(context_picker, workspace, context_store);
let delegate = DirectoryContextPickerDelegate::new(
context_picker,
workspace,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
@@ -49,6 +54,7 @@ pub struct DirectoryContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
}
@@ -58,11 +64,13 @@ impl DirectoryContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
context_picker,
workspace,
context_store,
confirm_behavior,
matches: Vec::new(),
selected_index: 0,
}
@@ -93,8 +101,12 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
Task::ready(())
}
fn confirm(&mut self, _secondary: bool, _cx: &mut ViewContext<Picker<Self>>) {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
// TODO: Implement this once we fix the issues with the file context picker.
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(cx),
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {

View File

@@ -12,7 +12,7 @@ use ui::{prelude::*, ListItem, ViewContext};
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FetchContextPicker {
@@ -24,9 +24,15 @@ impl FetchContextPicker {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_store);
let delegate = FetchContextPickerDelegate::new(
context_picker,
workspace,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
@@ -56,6 +62,7 @@ pub struct FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
url: String,
}
@@ -64,11 +71,13 @@ impl FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
FetchContextPickerDelegate {
context_picker,
workspace,
context_store,
confirm_behavior,
url: String::new(),
}
}
@@ -184,6 +193,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone();
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
let text = Self::build_message(http_client, &url).await?;
@@ -192,7 +202,14 @@ impl PickerDelegate for FetchContextPickerDelegate {
.context_store
.update(cx, |context_store, _cx| {
context_store.insert_context(ContextKind::FetchedUrl, url, text);
})
})?;
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
})??;
anyhow::Ok(())

View File

@@ -1,6 +1,6 @@
use std::fmt::Write as _;
use std::ops::RangeInclusive;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -13,7 +13,7 @@ use util::ResultExt as _;
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FileContextPicker {
@@ -25,9 +25,15 @@ impl FileContextPicker {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_store);
let delegate = FileContextPickerDelegate::new(
context_picker,
workspace,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
@@ -50,6 +56,7 @@ pub struct FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
}
@@ -59,11 +66,13 @@ impl FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
context_picker,
workspace,
context_store,
confirm_behavior,
matches: Vec::new(),
selected_index: 0,
}
@@ -79,44 +88,37 @@ impl FileContextPickerDelegate {
if query.is_empty() {
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let entries = workspace.recent_navigation_history(Some(10), cx);
let entries = entries
let recent_matches = workspace
.recent_navigation_history(Some(10), cx)
.into_iter()
.map(|entries| entries.0)
.chain(project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let id = worktree.id();
worktree
.child_entries(Path::new(""))
.filter(|entry| entry.kind.is_file())
.map(move |entry| project::ProjectPath {
worktree_id: id,
path: entry.path.clone(),
})
}))
.collect::<Vec<_>>();
let path_prefix: Arc<str> = Arc::default();
Task::ready(
entries
.into_iter()
.filter_map(|entry| {
let worktree = project.worktree_for_id(entry.worktree_id, cx)?;
let mut full_path = PathBuf::from(worktree.read(cx).root_name());
full_path.push(&entry.path);
Some(PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: entry.worktree_id.to_usize(),
path: full_path.into(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: false,
})
.filter_map(|(project_path, _)| {
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
Some(PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: project_path.worktree_id.to_usize(),
path: project_path.path,
path_prefix: worktree.read(cx).root_name().into(),
distance_to_relative_ancestor: 0,
is_dir: false,
})
.collect(),
)
});
let file_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.files(true, 0).map(move |entry| PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
path: entry.path.clone(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: false,
})
});
Task::ready(recent_matches.chain(file_matches).collect())
} else {
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
let candidate_sets = worktrees
@@ -201,6 +203,7 @@ impl PickerDelegate for FileContextPickerDelegate {
};
let path = mat.path.clone();
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
let Some(open_buffer_task) = project
.update(&mut cx, |project, cx| {
@@ -214,22 +217,31 @@ impl PickerDelegate for FileContextPickerDelegate {
let buffer = open_buffer_task.await?;
this.update(&mut cx, |this, cx| {
this.delegate.context_store.update(cx, |context_store, cx| {
let mut text = String::new();
text.push_str(&codeblock_fence_for_path(Some(&path), None));
text.push_str(&buffer.read(cx).text());
if !text.ends_with('\n') {
text.push('\n');
}
this.delegate
.context_store
.update(cx, |context_store, cx| {
let mut text = String::new();
text.push_str(&codeblock_fence_for_path(Some(&path), None));
text.push_str(&buffer.read(cx).text());
if !text.ends_with('\n') {
text.push('\n');
}
text.push_str("```\n");
text.push_str("```\n");
context_store.insert_context(
ContextKind::File,
path.to_string_lossy().to_string(),
text,
);
})
context_store.insert_context(
ContextKind::File,
path.to_string_lossy().to_string(),
text,
);
})?;
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
})??;
anyhow::Ok(())
@@ -253,16 +265,30 @@ impl PickerDelegate for FileContextPickerDelegate {
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
let file_name = path_match
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let directory = path_match
.path
.parent()
.map(|directory| format!("{}/", directory.to_string_lossy()));
let (file_name, directory) = if path_match.path.as_ref() == Path::new("") {
(SharedString::from(path_match.path_prefix.clone()), None)
} else {
let file_name = path_match
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string()
.into();
let mut directory = format!("{}/", path_match.path_prefix);
if let Some(parent) = path_match
.path
.parent()
.filter(|parent| parent != &Path::new(""))
{
directory.push_str(&parent.to_string_lossy());
directory.push('/');
}
(file_name, Some(directory))
};
Some(
ListItem::new(ix).inset(true).toggle_state(selected).child(

View File

@@ -6,7 +6,7 @@ use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem};
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store;
use crate::thread::ThreadId;
use crate::thread_store::ThreadStore;
@@ -20,10 +20,15 @@ impl ThreadContextPicker {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate =
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
let delegate = ThreadContextPickerDelegate::new(
thread_store,
context_picker,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
ThreadContextPicker { picker }
@@ -52,6 +57,7 @@ pub struct ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>,
selected_index: usize,
}
@@ -61,11 +67,13 @@ impl ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
ThreadContextPickerDelegate {
thread_store,
context_picker,
context_store,
confirm_behavior,
matches: Vec::new(),
selected_index: 0,
}
@@ -180,6 +188,11 @@ impl PickerDelegate for ThreadContextPickerDelegate {
context_store.insert_context(ContextKind::Thread, entry.summary.clone(), text);
})
.ok();
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(cx),
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {

View File

@@ -4,7 +4,7 @@ use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
use workspace::Workspace;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::thread_store::ThreadStore;
use crate::ui::ContextPill;
@@ -33,6 +33,7 @@ impl ContextStrip {
workspace.clone(),
thread_store.clone(),
context_store.downgrade(),
ConfirmBehavior::KeepOpen,
cx,
)
}),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,24 @@
use std::sync::Arc;
use editor::{Editor, EditorElement, EditorStyle};
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs;
use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakModel, WeakView};
use gpui::{
AppContext, DismissEvent, FocusableView, Model, Subscription, TextStyle, View, WeakModel,
WeakView,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use rope::Point;
use settings::{update_settings_file, Settings};
use theme::ThemeSettings;
use ui::{
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenuHandle,
Tooltip,
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenu,
PopoverMenuHandle, Tooltip,
};
use workspace::Workspace;
use crate::assistant_settings::AssistantSettings;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::thread::{RequestKind, Thread};
@@ -27,9 +31,12 @@ pub struct MessageEditor {
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
inline_context_picker: View<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
}
impl MessageEditor {
@@ -42,14 +49,31 @@ impl MessageEditor {
) -> Self {
let context_store = cx.new_model(|_cx| ContextStore::new());
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
let mut editor = Editor::auto_height(10, cx);
editor.set_placeholder_text("Ask anything, @ to add context", cx);
editor.set_show_indent_guides(false, cx);
editor
});
let inline_context_picker = cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
Some(thread_store.clone()),
context_store.downgrade(),
ConfirmBehavior::Close,
cx,
)
});
let subscriptions = vec![
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(
&inline_context_picker,
Self::handle_inline_context_picker_event,
),
];
Self {
thread,
@@ -66,6 +90,8 @@ impl MessageEditor {
)
}),
context_picker_menu_handle,
inline_context_picker,
inline_context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
@@ -81,6 +107,7 @@ impl MessageEditor {
}),
language_model_selector_menu_handle: PopoverMenuHandle::default(),
use_tools: false,
_subscriptions: subscriptions,
}
}
@@ -143,6 +170,40 @@ impl MessageEditor {
None
}
fn handle_editor_event(
&mut self,
editor: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::SelectionsChanged { .. } => {
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let newest_cursor = editor.selections.newest::<Point>(cx).head();
if newest_cursor.column > 0 {
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
if char_behind_cursor == Some('@') {
self.inline_context_picker_menu_handle.show(cx);
}
}
});
}
_ => {}
}
}
fn handle_inline_context_picker_event(
&mut self,
_inline_context_picker: View<ContextPicker>,
_event: &DismissEvent,
cx: &mut ViewContext<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
}
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.language_model_selector.focus_handle(cx).clone();
@@ -199,6 +260,7 @@ impl Render for MessageEditor {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
let focus_handle = self.editor.focus_handle(cx);
let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background;
v_flex()
@@ -211,7 +273,7 @@ impl Render for MessageEditor {
.p_2()
.bg(bg_color)
.child(self.context_strip.clone())
.child(div().id("thread_editor").overflow_y_scroll().h_12().child({
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
@@ -232,7 +294,18 @@ impl Render for MessageEditor {
..Default::default()
},
)
}))
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |_cx| Some(inline_context_picker.clone()))
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
x: px(0.0),
y: px(-16.0),
})
.with_handle(self.inline_context_picker_menu_handle.clone()),
)
.child(
h_flex()
.justify_between()

View File

@@ -0,0 +1,192 @@
use crate::inline_prompt_editor::CodegenStatus;
use client::telemetry::Telemetry;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{AppContext, EventEmitter, Model, ModelContext, Task};
use language_model::{LanguageModelRegistry, LanguageModelRequest};
use language_models::report_assistant_event;
use std::{sync::Arc, time::Instant};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
pub struct TerminalCodegen {
pub status: CodegenStatus,
pub telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>,
generation: Task<()>,
pub message_id: Option<String>,
transaction: Option<TerminalTransaction>,
}
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
impl TerminalCodegen {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self {
terminal,
telemetry,
status: CodegenStatus::Idle,
generation: Task::ready(()),
message_id: None,
transaction: None,
}
}
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
let model_api_key = model.api_key(cx);
let http_client = cx.http_client();
let telemetry = self.telemetry.clone();
self.status = CodegenStatus::Pending;
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 message_id = response
.as_ref()
.ok()
.and_then(|response| response.message_id.clone());
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
let task = cx.background_executor().spawn({
let message_id = message_id.clone();
let executor = cx.background_executor().clone();
async move {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut chunks = response?.stream;
while let Some(chunk) = chunks.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());
}
let chunk = chunk?;
hunks_tx.send(chunk).await?;
}
anyhow::Ok(())
};
let result = task.await;
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
language_name: None,
},
telemetry,
http_client,
model_api_key,
&executor,
);
result?;
anyhow::Ok(())
}
});
this.update(&mut cx, |this, _| {
this.message_id = message_id;
})?;
while let Some(hunk) = hunks_rx.next().await {
this.update(&mut cx, |this, cx| {
if let Some(transaction) = &mut this.transaction {
transaction.push(hunk, cx);
cx.notify();
}
})?;
}
task.await?;
anyhow::Ok(())
};
let result = generate.await;
this.update(&mut cx, |this, cx| {
if let Err(error) = result {
this.status = CodegenStatus::Error(error);
} else {
this.status = CodegenStatus::Done;
}
cx.emit(CodegenEvent::Finished);
cx.notify();
})
.ok();
});
cx.notify();
}
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished);
cx.notify();
}
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.complete(cx);
}
}
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.undo(cx);
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum CodegenEvent {
Finished,
}
pub const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
terminal: Model<Terminal>,
}
impl TerminalTransaction {
pub fn start(terminal: Model<Terminal>) -> Self {
Self { terminal }
}
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
pub fn undo(&self, cx: &mut AppContext) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
}
pub fn complete(&self, cx: &mut AppContext) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});
}
fn sanitize_input(input: String) -> String {
input.replace(['\r', '\n'], "")
}
}

View File

@@ -1,37 +1,29 @@
use crate::assistant_settings::AssistantSettings;
use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::inline_prompt_editor::{
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
};
use crate::prompts::PromptBuilder;
use crate::terminal_codegen::{CodegenEvent, TerminalCodegen, CLEAR_INPUT};
use crate::thread_store::ThreadStore;
use crate::ToggleContextPicker;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque};
use editor::{
actions::{MoveDown, MoveUp, SelectAll},
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
};
use editor::{actions::SelectAll, MultiBuffer};
use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakModel, WeakView,
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
WeakView,
};
use language::Buffer;
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_models::report_assistant_event;
use settings::{update_settings_file, Settings};
use std::{cmp, sync::Arc, time::Instant};
use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{prelude::*, text_for_action, IconButtonShape, PopoverMenuHandle, Tooltip};
use ui::prelude::*;
use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -47,17 +39,6 @@ pub fn init(
const DEFAULT_CONTEXT_LINES: usize = 50;
const PROMPT_HISTORY_MAX_LEN: usize = 20;
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
struct TerminalInlineAssistId(usize);
impl TerminalInlineAssistId {
fn post_inc(&mut self) -> TerminalInlineAssistId {
let id = *self;
self.0 += 1;
id
}
}
pub struct TerminalInlineAssistant {
next_assist_id: TerminalInlineAssistId,
assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>,
@@ -98,10 +79,10 @@ impl TerminalInlineAssistant {
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
});
let context_store = cx.new_model(|_cx| ContextStore::new());
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_view(|cx| {
PromptEditor::new(
PromptEditor::new_terminal(
assist_id,
self.prompt_history.clone(),
prompt_buffer.clone(),
@@ -150,11 +131,11 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: View<PromptEditor>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
event: &PromptEditorEvent,
cx: &mut WindowContext,
) {
let assist_id = prompt_editor.read(cx).id;
let assist_id = prompt_editor.read(cx).id();
match event {
PromptEditorEvent::StartRequested => {
self.start_assist(assist_id, cx);
@@ -380,8 +361,8 @@ impl TerminalInlineAssistant {
struct TerminalInlineAssist {
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor>>,
codegen: Model<Codegen>,
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
codegen: Model<TerminalCodegen>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
_subscriptions: Vec<Subscription>,
@@ -391,12 +372,12 @@ impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &View<TerminalView>,
prompt_editor: View<PromptEditor>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
cx: &mut WindowContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen.clone();
let codegen = prompt_editor.read(cx).codegen().clone();
Self {
terminal: terminal.downgrade(),
prompt_editor: Some(prompt_editor.clone()),
@@ -447,660 +428,3 @@ impl TerminalInlineAssist {
}
}
}
enum PromptEditorEvent {
StartRequested,
StopRequested,
ConfirmRequested { execute: bool },
CancelRequested,
DismissRequested,
Resized { height_in_lines: u8 },
}
struct PromptEditor {
id: TerminalInlineAssistId,
height_in_lines: u8,
editor: View<Editor>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
pending_prompt: String,
codegen: Model<Codegen>,
_codegen_subscription: Subscription,
editor_subscriptions: Vec<Subscription>,
}
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status;
let mut buttons = Vec::new();
buttons.extend(match status {
CodegenStatus::Idle => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element(),
],
CodegenStatus::Pending => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Interrupt Generation",
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element(),
],
CodegenStatus::Error(_) | CodegenStatus::Done => {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element();
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
vec![
cancel,
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Restart Generation",
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element(),
]
} else {
vec![
cancel,
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element(),
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}))
.into_any_element(),
]
}
}
});
v_flex()
.border_y_1()
.border_color(cx.theme().status().info_border)
.py_2()
.size_full()
.child(
h_flex()
.key_context("PromptEditor")
.bg(cx.theme().colors().editor_background)
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::secondary_confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.child(
h_flex()
.w_12()
.justify_center()
.gap_2()
.child(LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
cx,
)
}),
))
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string());
Some(
div()
.id("error")
.tooltip(move |cx| {
Tooltip::text(error_message.clone(), cx)
})
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
.color(Color::Error),
),
)
} else {
None
},
),
)
.child(div().flex_1().child(self.render_prompt_editor(cx)))
.child(h_flex().gap_1().pr_4().children(buttons)),
)
.child(h_flex().child(self.context_strip.clone()))
}
}
impl FocusableView for PromptEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
}
impl PromptEditor {
const MAX_LINES: u8 = 8;
#[allow(clippy::too_many_arguments)]
fn new(
id: TerminalInlineAssistId,
prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<Codegen>,
fs: Arc<dyn Fs>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut ViewContext<Self>,
) -> Self {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
},
prompt_buffer,
None,
false,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
let mut this = Self {
id,
height_in_lines: 1,
editor: prompt_editor.clone(),
context_strip: cx.new_view(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
cx,
)
}),
context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
cx,
)
}),
edited_since_done: false,
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(),
codegen,
};
this.count_lines(cx);
this.subscribe_to_editor(cx);
this
}
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
self.editor_subscriptions
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
}
fn prompt(&self, cx: &AppContext) -> String {
self.editor.read(cx).text(cx)
}
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
self.editor
.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1),
Self::MAX_LINES as u32,
),
) as u8;
if height_in_lines != self.height_in_lines {
self.height_in_lines = height_in_lines;
cx.emit(PromptEditorEvent::Resized { height_in_lines });
}
}
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
self.count_lines(cx);
}
fn handle_prompt_editor_events(
&mut self,
_: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
let prompt = self.editor.read(cx).text(cx);
if self
.prompt_history_ix
.map_or(true, |ix| self.prompt_history[ix] != prompt)
{
self.prompt_history_ix.take();
self.pending_prompt = prompt;
}
self.edited_since_done = true;
cx.notify();
}
_ => {}
}
}
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
CodegenStatus::Pending => {
self.editor
.update(cx, |editor, _| editor.set_read_only(true));
}
CodegenStatus::Done | CodegenStatus::Error(_) => {
self.edited_since_done = false;
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
}
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
}
CodegenStatus::Pending => {
cx.emit(PromptEditorEvent::StopRequested);
}
}
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
if !self.editor.read(cx).text(cx).trim().is_empty() {
cx.emit(PromptEditorEvent::StartRequested);
}
}
CodegenStatus::Pending => {
cx.emit(PromptEditorEvent::DismissRequested);
}
CodegenStatus::Done => {
if self.edited_since_done {
cx.emit(PromptEditorEvent::StartRequested);
} else {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}
}
CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::StartRequested);
}
}
}
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}
}
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
}
}
}
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
},
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size.into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
EditorElement::new(
&self.editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
}
}
#[derive(Debug)]
pub enum CodegenEvent {
Finished,
}
impl EventEmitter<CodegenEvent> for Codegen {}
const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
terminal: Model<Terminal>,
}
impl TerminalTransaction {
pub fn start(terminal: Model<Terminal>) -> Self {
Self { terminal }
}
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
pub fn undo(&self, cx: &mut AppContext) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
}
pub fn complete(&self, cx: &mut AppContext) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});
}
fn sanitize_input(input: String) -> String {
input.replace(['\r', '\n'], "")
}
}
pub struct Codegen {
status: CodegenStatus,
telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>,
generation: Task<()>,
message_id: Option<String>,
transaction: Option<TerminalTransaction>,
}
impl Codegen {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self {
terminal,
telemetry,
status: CodegenStatus::Idle,
generation: Task::ready(()),
message_id: None,
transaction: None,
}
}
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
let model_api_key = model.api_key(cx);
let http_client = cx.http_client();
let telemetry = self.telemetry.clone();
self.status = CodegenStatus::Pending;
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 message_id = response
.as_ref()
.ok()
.and_then(|response| response.message_id.clone());
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
let task = cx.background_executor().spawn({
let message_id = message_id.clone();
let executor = cx.background_executor().clone();
async move {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut chunks = response?.stream;
while let Some(chunk) = chunks.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());
}
let chunk = chunk?;
hunks_tx.send(chunk).await?;
}
anyhow::Ok(())
};
let result = task.await;
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
language_name: None,
},
telemetry,
http_client,
model_api_key,
&executor,
);
result?;
anyhow::Ok(())
}
});
this.update(&mut cx, |this, _| {
this.message_id = message_id;
})?;
while let Some(hunk) = hunks_rx.next().await {
this.update(&mut cx, |this, cx| {
if let Some(transaction) = &mut this.transaction {
transaction.push(hunk, cx);
cx.notify();
}
})?;
}
task.await?;
anyhow::Ok(())
};
let result = generate.await;
this.update(&mut cx, |this, cx| {
if let Err(error) = result {
this.status = CodegenStatus::Error(error);
} else {
this.status = CodegenStatus::Done;
}
cx.emit(CodegenEvent::Finished);
cx.notify();
})
.ok();
});
cx.notify();
}
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished);
cx.notify();
}
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.complete(cx);
}
}
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.undo(cx);
}
}
}
enum CodegenStatus {
Idle,
Pending,
Done,
Error(anyhow::Error),
}

View File

@@ -40,6 +40,7 @@ schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
settings.workspace = true
telemetry.workspace = true
util.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]

View File

@@ -250,7 +250,9 @@ impl ActiveCall {
cx.spawn(move |this, mut cx| async move {
let result = invite.await;
if result.is_ok() {
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
this.update(&mut cx, |this, cx| {
this.report_call_event("Participant Invited", cx)
})?;
} else {
//TODO: report collaboration error
log::error!("invite failed: {:?}", result);
@@ -318,7 +320,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("accept incoming", cx)
this.report_call_event("Incoming Call Accepted", cx)
})?;
Ok(())
})
@@ -331,7 +333,7 @@ impl ActiveCall {
.borrow_mut()
.take()
.ok_or_else(|| anyhow!("no incoming call"))?;
report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
telemetry::event!("Incoming Call Declined", room_id = call.room_id);
self.client.send(proto::DeclineCall {
room_id: call.room_id,
})?;
@@ -366,7 +368,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("join channel", cx)
this.report_call_event("Channel Joined", cx)
})?;
Ok(room)
})
@@ -374,7 +376,7 @@ impl ActiveCall {
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
self.report_call_event("hang up", cx);
self.report_call_event("Call Ended", cx);
Audio::end_call(cx);
@@ -393,7 +395,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("share project", cx);
self.report_call_event("Project Shared", cx);
room.update(cx, |room, cx| room.share_project(project, cx))
} else {
Task::ready(Err(anyhow!("no active call")))
@@ -406,7 +408,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("unshare project", cx);
self.report_call_event("Project Unshared", cx);
room.update(cx, |room, cx| room.unshare_project(project, cx))
} else {
Err(anyhow!("no active call"))
@@ -486,35 +488,15 @@ impl ActiveCall {
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
if let Some(room) = self.room() {
let room = room.read(cx);
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
telemetry::event!(
operation,
room_id = room.id(),
channel_id = room.channel_id()
)
}
}
}
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<ChannelId>,
client: &Arc<Client>,
) {
let telemetry = client.telemetry();
telemetry.report_call_event(operation, Some(room_id), channel_id)
}
pub fn report_call_event_for_channel(
operation: &'static str,
channel_id: ChannelId,
client: &Arc<Client>,
cx: &AppContext,
) {
let room = ActiveCall::global(cx).read(cx).room();
let telemetry = client.telemetry();
telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
}
#[cfg(test)]
mod test {
use gpui::TestAppContext;

View File

@@ -243,7 +243,9 @@ impl ActiveCall {
cx.spawn(move |this, mut cx| async move {
let result = invite.await;
if result.is_ok() {
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
this.update(&mut cx, |this, cx| {
this.report_call_event("Participant Invited", cx)
})?;
} else {
//TODO: report collaboration error
log::error!("invite failed: {:?}", result);
@@ -311,7 +313,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("accept incoming", cx)
this.report_call_event("Incoming Call Accepted", cx)
})?;
Ok(())
})
@@ -324,7 +326,7 @@ impl ActiveCall {
.borrow_mut()
.take()
.ok_or_else(|| anyhow!("no incoming call"))?;
report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
telemetry::event!("Incoming Call Declined", room_id = call.room_id);
self.client.send(proto::DeclineCall {
room_id: call.room_id,
})?;
@@ -359,7 +361,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("join channel", cx)
this.report_call_event("Channel Joined", cx)
})?;
Ok(room)
})
@@ -367,7 +369,7 @@ impl ActiveCall {
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
self.report_call_event("hang up", cx);
self.report_call_event("Call Ended", cx);
Audio::end_call(cx);
@@ -386,7 +388,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("share project", cx);
self.report_call_event("Project Shared", cx);
room.update(cx, |room, cx| room.share_project(project, cx))
} else {
Task::ready(Err(anyhow!("no active call")))
@@ -399,7 +401,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("unshare project", cx);
self.report_call_event("Project Unshared", cx);
room.update(cx, |room, cx| room.unshare_project(project, cx))
} else {
Err(anyhow!("no active call"))
@@ -479,35 +481,15 @@ impl ActiveCall {
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
if let Some(room) = self.room() {
let room = room.read(cx);
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
telemetry::event!(
operation,
room_id = room.id(),
channel_id = room.channel_id()
);
}
}
}
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<ChannelId>,
client: &Arc<Client>,
) {
let telemetry = client.telemetry();
telemetry.report_call_event(operation, Some(room_id), channel_id)
}
pub fn report_call_event_for_channel(
operation: &'static str,
channel_id: ChannelId,
client: &Arc<Client>,
cx: &AppContext,
) {
let room = ActiveCall::global(cx).read(cx).room();
let telemetry = client.telemetry();
telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
}
#[cfg(test)]
mod test {
use gpui::TestAppContext;

View File

@@ -19,7 +19,7 @@ use std::time::Instant;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use telemetry_events::{
AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
InlineCompletionEvent, InlineCompletionRating, InlineCompletionRatingEvent, SettingEvent,
InlineCompletionEvent,
};
use util::{ResultExt, TryFutureExt};
use worktree::{UpdatedEntriesSet, WorktreeId};
@@ -349,24 +349,6 @@ impl Telemetry {
self.report_event(event)
}
pub fn report_inline_completion_rating_event(
self: &Arc<Self>,
rating: InlineCompletionRating,
input_events: Arc<str>,
input_excerpt: Arc<str>,
output_excerpt: Arc<str>,
feedback: String,
) {
let event = Event::InlineCompletionRating(InlineCompletionRatingEvent {
rating,
input_events,
input_excerpt,
output_excerpt,
feedback,
});
self.report_event(event);
}
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
self.report_event(Event::Assistant(event));
}
@@ -394,15 +376,6 @@ impl Telemetry {
event
}
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
let event = Event::Setting(SettingEvent {
setting: setting.to_string(),
value,
});
self.report_event(event)
}
pub fn log_edit_event(self: &Arc<Self>, environment: &'static str, is_via_ssh: bool) {
let mut state = self.state.lock();
let period_data = state.event_coalescer.log_event(environment);

View File

@@ -16,7 +16,9 @@ use util::TryFutureExt as _;
pub type UserId = u64;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
)]
pub struct ChannelId(pub u64);
impl std::fmt::Display for ChannelId {

View File

@@ -8,7 +8,12 @@ It contains our back-end logic for collaboration, to which we connect from the Z
## Database setup
Before you can run the collab server locally, you'll need to set up a zed Postgres database.
Before you can run the collab server locally, you'll need to set up a zed Postgres database. Follow the steps sequentially:
1. Ensure you have postgres installed. If not, install with `brew install postgresql@15`.
2. Follow the steps on Brew's formula and verify your `$PATH` contains `/opt/homebrew/opt/postgresql@15/bin`.
3. If you hadn't done it before, create the `postgres` user with `createuser -s postgres`.
4. You are now ready to run the `bootstrap` script:
```sh
script/bootstrap

View File

@@ -10,7 +10,7 @@ use editor::{
ToggleCodeActions, Undo,
},
test::editor_test_context::{AssertionContextManager, EditorTestContext},
Editor, RowInfo,
Editor,
};
use fs::Fs;
use futures::StreamExt;
@@ -20,6 +20,7 @@ use language::{
language_settings::{AllLanguageSettings, InlayHintSettings},
FakeLspAdapter,
};
use multi_buffer::MultiBufferRow;
use project::{
project_settings::{InlineBlameSettings, ProjectSettings},
SERVER_PROGRESS_THROTTLE_TIMEOUT,
@@ -2074,15 +2075,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
.blame_for_rows(
&(0..4)
.map(|row| RowInfo {
buffer_row: Some(row),
..Default::default()
})
.collect::<Vec<_>>(),
cx,
)
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
.collect::<Vec<_>>()
});
@@ -2121,15 +2114,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
.blame_for_rows(
&(0..4)
.map(|row| RowInfo {
buffer_row: Some(row),
..Default::default()
})
.collect::<Vec<_>>(),
cx,
)
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
.collect::<Vec<_>>()
});
@@ -2156,15 +2141,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let blame = editor_b.blame().expect("editor_b should have blame now");
let entries = blame.update(cx, |blame, cx| {
blame
.blame_for_rows(
&(0..4)
.map(|row| RowInfo {
buffer_row: Some(row),
..Default::default()
})
.collect::<Vec<_>>(),
cx,
)
.blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx)
.collect::<Vec<_>>()
});

View File

@@ -56,6 +56,7 @@ serde_json.workspace = true
settings.workspace = true
smallvec.workspace = true
story = { workspace = true, optional = true }
telemetry.workspace = true
theme.workspace = true
time.workspace = true
time_format.workspace = true

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use call::report_call_event_for_channel;
use call::ActiveCall;
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore};
use client::{
proto::{self, PeerId},
@@ -66,11 +66,13 @@ impl ChannelView {
cx.spawn(|mut cx| async move {
let channel_view = channel_view.await?;
pane.update(&mut cx, |pane, cx| {
report_call_event_for_channel(
"open channel notes",
telemetry::event!(
"Channel Notes Opened",
channel_id,
&workspace.read(cx).app_state().client,
cx,
room_id = ActiveCall::global(cx)
.read(cx)
.room()
.map(|r| r.read(cx).id())
);
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
})?;

View File

@@ -1,4 +1,4 @@
use crate::{collab_panel, ChatPanelSettings};
use crate::{collab_panel, ChatPanelButton, ChatPanelSettings};
use anyhow::Result;
use call::{room, ActiveCall};
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore};
@@ -1135,7 +1135,14 @@ impl Panel for ChatPanel {
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
match ChatPanelSettings::get_global(cx).button {
ChatPanelButton::Never => None,
ChatPanelButton::Always => Some(ui::IconName::MessageBubbles),
ChatPanelButton::WhenInCall => ActiveCall::global(cx)
.read(cx)
.room()
.map(|_| ui::IconName::MessageBubbles),
}
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {

View File

@@ -14,7 +14,7 @@ use gpui::{
};
use panel_settings::MessageEditorSettings;
pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
ChatPanelButton, ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
};
use release_channel::ReleaseChannel;
use settings::Settings;

View File

@@ -1,6 +1,6 @@
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use workspace::dock::DockPosition;
@@ -11,13 +11,82 @@ pub struct CollaborationPanelSettings {
pub default_width: Pixels,
}
#[derive(Clone, Copy, Default, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChatPanelButton {
Never,
Always,
#[default]
WhenInCall,
}
impl<'de> Deserialize<'de> for ChatPanelButton {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = ChatPanelButton;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
r#"a boolean or one of "never", "always", "when_in_call""#
)
}
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match b {
false => Ok(ChatPanelButton::Never),
true => Ok(ChatPanelButton::Always),
}
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match s {
"never" => Ok(ChatPanelButton::Never),
"always" => Ok(ChatPanelButton::Always),
"when_in_call" => Ok(ChatPanelButton::WhenInCall),
_ => Err(E::unknown_variant(s, &["never", "always", "when_in_call"])),
}
}
}
deserializer.deserialize_any(Visitor)
}
}
#[derive(Deserialize, Debug)]
pub struct ChatPanelSettings {
pub button: bool,
pub button: ChatPanelButton,
pub dock: DockPosition,
pub default_width: Pixels,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct ChatPanelSettingsContent {
/// When to show the panel button in the status bar.
///
/// Default: only when in a call
pub button: Option<ChatPanelButton>,
/// Where to dock the panel.
///
/// Default: right
pub dock: Option<DockPosition>,
/// Default width of the panel in pixels.
///
/// Default: 240
pub default_width: Option<f32>,
}
#[derive(Deserialize, Debug)]
pub struct NotificationPanelSettings {
pub button: bool,
@@ -66,7 +135,7 @@ impl Settings for CollaborationPanelSettings {
impl Settings for ChatPanelSettings {
const KEY: Option<&'static str> = Some("chat_panel");
type FileContent = PanelSettingsContent;
type FileContent = ChatPanelSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,

View File

@@ -36,21 +36,26 @@ pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>,
}
fn trim_consecutive_whitespaces(input: &str) -> String {
/// Removes subsequent whitespace characters and double colons from the query.
///
/// This improves the likelihood of a match by either humanized name or keymap-style name.
fn normalize_query(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut last_char_was_whitespace = false;
let mut last_char = None;
for char in input.trim().chars() {
if char.is_whitespace() {
if !last_char_was_whitespace {
result.push(char);
match (last_char, char) {
(Some(':'), ':') => continue,
(Some(last_char), char) if last_char.is_whitespace() && char.is_whitespace() => {
continue
}
_ => {
last_char = Some(char);
}
last_char_was_whitespace = true;
} else {
result.push(char);
last_char_was_whitespace = false;
}
result.push(char);
}
result
}
@@ -258,7 +263,7 @@ impl PickerDelegate for CommandPaletteDelegate {
let mut commands = self.all_commands.clone();
let hit_counts = cx.global::<HitCounts>().clone();
let executor = cx.background_executor().clone();
let query = trim_consecutive_whitespaces(query.as_str());
let query = normalize_query(query.as_str());
async move {
commands.sort_by_key(|action| {
(
@@ -463,6 +468,25 @@ mod tests {
);
}
#[test]
fn test_normalize_query() {
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
assert_eq!(
normalize_query("editor::GoToDefinition"),
"editor:GoToDefinition"
);
assert_eq!(
normalize_query("editor::::GoToDefinition"),
"editor:GoToDefinition"
);
assert_eq!(
normalize_query("editor: :GoToDefinition"),
"editor: :GoToDefinition"
);
}
#[gpui::test]
async fn test_command_palette(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@@ -533,6 +557,40 @@ mod tests {
assert!(palette.delegate.matches.is_empty())
});
}
#[gpui::test]
async fn test_normalized_matches(cx: &mut TestAppContext) {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_text("abc", cx);
editor
});
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
editor.update(cx, |editor, cx| editor.focus(cx))
});
// Test normalize (trimming whitespace and double colons)
cx.simulate_keystrokes("cmd-shift-p");
let palette = workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<CommandPalette>(cx)
.unwrap()
.read(cx)
.picker
.clone()
});
cx.simulate_input("Editor:: Backspace");
palette.update(cx, |palette, _| {
assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
});
}
#[gpui::test]
async fn test_go_to_line(cx: &mut TestAppContext) {

View File

@@ -59,6 +59,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
"Copilot"
}
fn show_completions_in_menu() -> bool {
false
}
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -326,17 +330,15 @@ mod tests {
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
// We want to show both: the inline completion and the completion menu
assert!(editor.context_menu_visible());
assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
assert!(!editor.context_menu_contains_inline_completion());
assert!(!editor.has_active_inline_completion());
// Since we have both, the copilot suggestion is not shown inline
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
assert_eq!(editor.display_text(cx), "one.\ntwo\nthree\n");
// Confirming a non-copilot completion inserts it and hides the context menu, without showing
// the copilot suggestion afterwards.
editor.context_menu_next(&Default::default(), cx);
editor
.confirm_completion(&Default::default(), cx)
.unwrap()
@@ -384,23 +386,12 @@ mod tests {
// Ensure existing inline completion is interpolated when inserting again.
cx.simulate_keystroke("c");
drop(handle_completion_request(
&mut cx,
indoc! {"
one.c|<>
two
three
"},
vec!["completion_a", "completion_b"],
));
executor.run_until_parked();
cx.update_editor(|editor, cx| {
// Since we have an LSP completion too, the inline completion is
// shown in the menu now
assert!(editor.context_menu_visible());
assert!(editor.context_menu_contains_inline_completion());
assert!(!editor.context_menu_visible());
assert!(!editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
});
@@ -416,16 +407,9 @@ mod tests {
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
assert!(editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert!(editor.context_menu_contains_inline_completion());
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
// Canceling should first hide the menu and make Copilot suggestion visible.
editor.cancel(&Default::default(), cx);
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert!(!editor.context_menu_contains_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
@@ -928,8 +912,8 @@ mod tests {
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
assert!(editor.context_menu_visible());
assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion(),);
assert!(!editor.context_menu_contains_inline_completion());
assert!(!editor.has_active_inline_completion(),);
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
});
}

View File

@@ -12,7 +12,8 @@ use editor::{
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
highlight_diagnostic_message,
scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
Anchor, AnchorRangeExt, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
RangeToAnchorExt, ToOffset,
};
use gpui::{
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
@@ -21,7 +22,8 @@ use gpui::{
WeakView, WindowContext,
};
use language::{
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
Bias, Buffer, BufferId, Diagnostic, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, Point,
Selection, SelectionGoal,
};
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
@@ -38,9 +40,10 @@ use std::{
use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
use ui::{h_flex, prelude::*, Icon, IconName, Label};
use util::ResultExt;
use util::{maybe, RangeExt, ResultExt};
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
searchable::SearchableItemHandle,
ItemNavHistory, ToolbarItemLocation, Workspace,
};
@@ -255,26 +258,69 @@ impl ProjectDiagnosticsEditor {
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
workspace.activate_item(&existing, true, true, cx);
} else {
let workspace_handle = cx.view().downgrade();
let diagnostic_to_select = maybe!({
let active_item = workspace.active_item(cx)?;
// Already on diagnostics, so don't update position / selection within.
if active_item.downcast::<ProjectDiagnosticsEditor>().is_some() {
None
} else {
let active_editor = active_item.act_as::<Editor>(cx)?.read(cx);
let snapshot = active_editor.buffer().read(cx).snapshot(cx);
let newest_selection = active_editor.selections.newest::<usize>(cx).range();
let mut diagnostics =
snapshot.diagnostics_in_range(newest_selection.clone(), false);
let diagnostic_entry: DiagnosticEntry<usize> = dbg!(diagnostics.next())?;
let buffer_id = diagnostic_entry
.range
.to_anchors(&snapshot)
.start
.buffer_id?;
Some((buffer_id, diagnostic_entry.range, newest_selection))
}
});
let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0,
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
let diagnostics =
if let Some(diagnostics) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
// todo! dedupe
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
diagnostics.update(cx, |diagnostics, cx| {
diagnostics.set_selection_that_intersects_diagnostic(
buffer_id,
diagnostic_range,
selection_range,
cx,
)
});
}
workspace.activate_item(&diagnostics, true, true, cx);
} else {
let workspace_handle = cx.view().downgrade();
let include_warnings = match cx.try_global::<IncludeWarnings>() {
Some(include_warnings) => include_warnings.0,
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
};
let diagnostics = cx.new_view(|cx| {
ProjectDiagnosticsEditor::new(
workspace.project().clone(),
include_warnings,
workspace_handle,
cx,
)
});
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
diagnostics.update(cx, |diagnostics, cx| {
diagnostics.set_selection_that_intersects_diagnostic(
buffer_id,
diagnostic_range,
selection_range,
cx,
)
});
}
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
};
let diagnostics = cx.new_view(|cx| {
ProjectDiagnosticsEditor::new(
workspace.project().clone(),
include_warnings,
workspace_handle,
cx,
)
});
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
}
}
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
@@ -631,6 +677,30 @@ impl ProjectDiagnosticsEditor {
cx.notify();
}
fn set_selection_that_intersects_diagnostic(
&self,
buffer_id: BufferId,
diagnostic_range: Range<usize>,
selection_range: Range<usize>,
cx: &mut ViewContext<Self>,
) {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
snapshot.excerpts_for_range(range)
/*
let excerpts = buffer.excerpts_for_buffer_id(buffer_id, cx);
for (excerpt_id, excerpt_range) in excerpts {
let excerpt_range = excerpt_range.context.to_offset(snapshot.excerpt_containing(range));
if excerpt_range.contains_inclusive(&diagnostic_range.to_offset(&snapshot)) {}
}
*/
// editor.change_selections(Some(Autoscroll::center()), cx, |selections| {
// selections.select_ranges(vec![diagnostic_range]);
// })
});
}
#[cfg(test)]
fn check_invariants(&self, cx: &mut ViewContext<Self>) {
let mut excerpts = Vec::new();
@@ -810,6 +880,10 @@ impl Item for ProjectDiagnosticsEditor {
}
}
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft
}

View File

@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::{cell::Cell, cmp::Reverse, ops::Range, rc::Rc};
use std::{cmp::Reverse, ops::Range, rc::Rc};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
@@ -14,8 +14,8 @@ use multi_buffer::{Anchor, ExcerptId};
use ordered_float::OrderedFloat;
use project::{CodeAction, Completion, TaskSourceKind};
use task::ResolvedTask;
use ui::{prelude::*, Color, IntoElement, ListItem, Popover, Styled};
use util::ResultExt as _;
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
use util::ResultExt;
use workspace::Workspace;
use crate::{
@@ -26,6 +26,8 @@ use crate::{
};
use crate::{AcceptInlineCompletion, InlineCompletionMenuHint, InlineCompletionText};
pub const MAX_COMPLETIONS_ASIDE_WIDTH: Pixels = px(500.);
pub enum CodeContextMenu {
Completions(CompletionsMenu),
CodeActions(CodeActionsMenu),
@@ -109,20 +111,33 @@ impl CodeContextMenu {
CodeContextMenu::CodeActions(menu) => menu.origin(cursor_position),
}
}
pub fn render(
&self,
style: &EditorStyle,
max_height_in_lines: u32,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
match self {
CodeContextMenu::Completions(menu) => {
menu.render(style, max_height_in_lines, workspace, cx)
}
CodeContextMenu::Completions(menu) => menu.render(style, max_height_in_lines, cx),
CodeContextMenu::CodeActions(menu) => menu.render(style, max_height_in_lines, cx),
}
}
pub fn render_aside(
&self,
style: &EditorStyle,
max_height: Pixels,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
match self {
CodeContextMenu::Completions(menu) => {
menu.render_aside(style, max_height, workspace, cx)
}
CodeContextMenu::CodeActions(_) => None,
}
}
}
pub enum ContextMenuOrigin {
@@ -142,7 +157,6 @@ pub struct CompletionsMenu {
pub selected_item: usize,
scroll_handle: UniformListScrollHandle,
resolve_completions: bool,
pub aside_was_displayed: Cell<bool>,
show_completion_documentation: bool,
}
@@ -160,7 +174,6 @@ impl CompletionsMenu {
initial_position: Anchor,
buffer: Model<Buffer>,
completions: Box<[Completion]>,
aside_was_displayed: bool,
) -> Self {
let match_candidates = completions
.iter()
@@ -180,7 +193,6 @@ impl CompletionsMenu {
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: true,
aside_was_displayed: Cell::new(aside_was_displayed),
}
}
@@ -236,7 +248,6 @@ impl CompletionsMenu {
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: false,
aside_was_displayed: Cell::new(false),
show_completion_documentation: false,
}
}
@@ -314,7 +325,9 @@ impl CompletionsMenu {
}
}
.into();
self.selected_item = 0;
if self.selected_item != 0 && self.selected_item + 1 < self.entries.len() {
self.selected_item += 1;
}
}
pub fn resolve_selected_completion(
@@ -362,11 +375,8 @@ impl CompletionsMenu {
&self,
style: &EditorStyle,
max_height_in_lines: u32,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
let max_height = max_height_in_lines as f32 * cx.line_height();
let completions = self.completions.borrow_mut();
let show_completion_documentation = self.show_completion_documentation;
let widest_completion_ix = self
@@ -393,71 +403,12 @@ impl CompletionsMenu {
}) => provider_name.len(),
})
.map(|(ix, _)| ix);
drop(completions);
let selected_item = self.selected_item;
let style = style.clone();
let multiline_docs = match &self.entries[selected_item] {
CompletionEntry::Match(mat) if show_completion_documentation => {
match &completions[mat.candidate_id].documentation {
Some(Documentation::MultiLinePlainText(text)) => {
Some(div().child(SharedString::from(text.clone())))
}
Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
Some(div().child(render_parsed_markdown(
"completions_markdown",
parsed,
&style,
workspace,
cx,
)))
}
Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
Some(div().child("No documentation"))
}
_ => None,
}
}
CompletionEntry::InlineCompletionHint(hint) => Some(match &hint.text {
InlineCompletionText::Edit { text, highlights } => div()
.my_1()
.rounded(px(6.))
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.child(
gpui::StyledText::new(text.clone())
.with_highlights(&style.text, highlights.clone()),
),
InlineCompletionText::Move(text) => div().child(text.clone()),
}),
_ => None,
};
let aside_contents = if let Some(multiline_docs) = multiline_docs {
Some(multiline_docs)
} else if show_completion_documentation && self.aside_was_displayed.get() {
Some(div().child("Fetching documentation..."))
} else {
None
};
self.aside_was_displayed.set(aside_contents.is_some());
let aside_contents = aside_contents.map(|div| {
div.id("multiline_docs")
.max_h(max_height)
.flex_1()
.px_1p5()
.py_1()
.max_w(px(640.))
.w(px(450.))
.overflow_y_scroll()
.occlude()
});
drop(completions);
let completions = self.completions.clone();
let matches = self.entries.clone();
let style = style.clone();
let list = uniform_list(
cx.view().clone(),
"completions",
@@ -588,12 +539,71 @@ impl CompletionsMenu {
.with_width_from_item(widest_completion_ix)
.with_sizing_behavior(ListSizingBehavior::Infer);
Popover::new()
.child(list)
.when_some(aside_contents, |popover, aside_contents| {
popover.aside(aside_contents)
})
.into_any_element()
Popover::new().child(list).into_any_element()
}
fn render_aside(
&self,
style: &EditorStyle,
max_height: Pixels,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
if !self.show_completion_documentation {
return None;
}
let multiline_docs = match &self.entries[self.selected_item] {
CompletionEntry::Match(mat) => {
match self.completions.borrow_mut()[mat.candidate_id]
.documentation
.as_ref()?
{
Documentation::MultiLinePlainText(text) => {
div().child(SharedString::from(text.clone()))
}
Documentation::MultiLineMarkdown(parsed) if !parsed.text.is_empty() => div()
.child(render_parsed_markdown(
"completions_markdown",
parsed,
&style,
workspace,
cx,
)),
Documentation::MultiLineMarkdown(_) => return None,
Documentation::SingleLine(_) => return None,
Documentation::Undocumented => return None,
}
}
CompletionEntry::InlineCompletionHint(hint) => match &hint.text {
InlineCompletionText::Edit { text, highlights } => div()
.mx_1()
.rounded(px(6.))
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.child(
gpui::StyledText::new(text.clone())
.with_highlights(&style.text, highlights.clone()),
),
InlineCompletionText::Move(text) => div().child(text.clone()),
},
};
Some(
Popover::new()
.child(
multiline_docs
.id("multiline_docs")
.max_h(max_height)
.px_2()
.min_w(px(260.))
.max_w(MAX_COMPLETIONS_ASIDE_WIDTH)
.overflow_y_scroll()
.occlude(),
)
.into_any_element(),
)
}
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {

View File

@@ -20,7 +20,6 @@
mod block_map;
mod crease_map;
mod custom_highlights;
mod diff_map;
mod fold_map;
mod inlay_map;
pub(crate) mod invisibles;
@@ -31,18 +30,16 @@ use crate::{
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
};
pub use block_map::{
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock,
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
};
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
pub use crease_map::*;
use diff_map::{DiffMap, DiffMapSnapshot, DiffOffset, DiffPoint};
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
use fold_map::{FoldMap, FoldSnapshot};
use gpui::{
AnyElement, AppContext, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels,
UnderlineStyle,
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
};
pub(crate) use inlay_map::Inlay;
use inlay_map::{InlayMap, InlaySnapshot};
@@ -54,10 +51,9 @@ use language::{
};
use lsp::DiagnosticSeverity;
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
ToOffset, ToPoint,
};
use project::buffer_store::BufferChangeSet;
use serde::Deserialize;
use std::{
any::TypeId,
@@ -70,7 +66,7 @@ use std::{
};
use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot};
use text::{BufferId, LineIndent};
use text::LineIndent;
use ui::{px, SharedString, WindowContext};
use unicode_segmentation::UnicodeSegmentation;
use wrap_map::{WrapMap, WrapSnapshot};
@@ -100,8 +96,6 @@ pub struct DisplayMap {
buffer_subscription: BufferSubscription,
/// Decides where the [`Inlay`]s should be displayed.
inlay_map: InlayMap,
/// Decides where diff hunks should be.
diff_map: Model<DiffMap>,
/// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
fold_map: FoldMap,
/// Keeps track of hard tabs in a buffer.
@@ -140,8 +134,7 @@ impl DisplayMap {
let tab_size = Self::tab_size(&buffer, cx);
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let crease_map = CreaseMap::new(&buffer_snapshot);
let (diff_map, snapshot) = DiffMap::new(buffer.clone(), cx);
let (inlay_map, snapshot) = InlayMap::new(snapshot);
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
let (fold_map, snapshot) = FoldMap::new(snapshot);
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
@@ -160,7 +153,6 @@ impl DisplayMap {
buffer_subscription,
fold_map,
inlay_map,
diff_map,
tab_map,
wrap_map,
block_map,
@@ -176,10 +168,7 @@ impl DisplayMap {
pub fn snapshot(&mut self, cx: &mut ModelContext<Self>) -> DisplaySnapshot {
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let (diff_snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), edits, cx)
});
let (inlay_snapshot, edits) = self.inlay_map.sync(diff_snapshot, edits);
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
@@ -228,10 +217,7 @@ impl DisplayMap {
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), edits, cx)
});
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
@@ -304,9 +290,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -336,9 +319,6 @@ impl DisplayMap {
.collect::<Vec<_>>();
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -361,9 +341,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -378,9 +355,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -413,71 +387,6 @@ impl DisplayMap {
self.crease_map.remove(crease_ids, &snapshot)
}
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ModelContext<Self>,
) {
self.diff_map.update(cx, |diff_map, cx| {
diff_map.add_change_set(change_set, cx);
});
}
pub fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
self.diff_map.read(cx).has_multiple_hunks()
}
pub fn has_expanded_diff_hunks_in_ranges(
&mut self,
ranges: &[Range<multi_buffer::Anchor>],
cx: &mut ModelContext<Self>,
) -> bool {
self.diff_map
.read(cx)
.has_expanded_diff_hunks_in_ranges(ranges)
}
pub fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext<Self>) {
self.update_diff_map(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx))
}
pub fn expand_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut ModelContext<Self>) {
self.update_diff_map(cx, |diff_map, cx| diff_map.expand_diff_hunks(ranges, cx))
}
pub fn collapse_diff_hunks(&mut self, ranges: Vec<Range<Anchor>>, cx: &mut ModelContext<Self>) {
self.update_diff_map(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx))
}
pub fn diff_base_for<'a>(
&'a self,
buffer_id: BufferId,
cx: &'a AppContext,
) -> Option<&'a Model<BufferChangeSet>> {
self.diff_map.read(cx).diff_base_for(buffer_id)
}
fn update_diff_map(
&mut self,
cx: &mut ModelContext<Self>,
f: impl FnOnce(&mut DiffMap, &mut ModelContext<DiffMap>),
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
f(diff_map, cx);
diff_map.sync(snapshot, edits, cx)
});
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.write(snapshot, edits);
}
pub fn insert_blocks(
&mut self,
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
@@ -486,9 +395,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -507,9 +413,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -528,9 +431,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -549,9 +449,6 @@ impl DisplayMap {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self
.diff_map
.update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx));
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -627,10 +524,7 @@ impl DisplayMap {
}
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), edits, cx)
});
let (snapshot, edits) = self.inlay_map.sync(snapshot.clone(), edits);
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
@@ -816,35 +710,17 @@ impl DisplaySnapshot {
self.fold_snapshot.fold_count()
}
pub fn diff_hunks<'a>(&'a self) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
self.diff_snapshot()
.diff_hunks_in_range(0..self.buffer_snapshot.len())
}
pub fn diff_hunks_in_range<'a, T: ToOffset>(
&'a self,
range: Range<T>,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
self.diff_snapshot().diff_hunks_in_range(range)
}
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
&'a self,
range: Range<T>,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
self.diff_snapshot().diff_hunks_in_range_rev(range)
}
pub fn has_diff_hunks(&self) -> bool {
self.diff_snapshot().has_diff_hunks()
}
pub fn is_empty(&self) -> bool {
self.buffer_snapshot.len() == 0
}
pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
self.block_snapshot.row_infos(BlockRow(start_row.0))
pub fn buffer_rows(
&self,
start_row: DisplayRow,
) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
self.block_snapshot
.buffer_rows(BlockRow(start_row.0))
.map(|row| row.map(MultiBufferRow))
}
pub fn widest_line_number(&self) -> u32 {
@@ -853,7 +729,7 @@ impl DisplaySnapshot {
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
loop {
let mut inlay_point = self.inlay_snapshot.make_inlay_point(point);
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
fold_point.0.column = 0;
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
@@ -875,7 +751,7 @@ impl DisplaySnapshot {
) -> (MultiBufferPoint, DisplayPoint) {
let original_point = point;
loop {
let mut inlay_point = self.inlay_snapshot.make_inlay_point(point);
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
@@ -919,7 +795,7 @@ impl DisplaySnapshot {
}
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
let inlay_point = self.inlay_snapshot.make_inlay_point(point);
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
@@ -937,15 +813,9 @@ impl DisplaySnapshot {
.to_offset(self.display_point_to_inlay_point(point, bias))
}
pub fn display_point_to_diff_offset(&self, point: DisplayPoint, bias: Bias) -> DiffOffset {
self.diff_snapshot()
.point_to_offset(self.display_point_to_diff_point(point, bias))
}
pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
let multibuffer_offset = anchor.to_offset(&self.buffer_snapshot);
let diff_offset = self.diff_snapshot().to_diff_offset(multibuffer_offset);
self.inlay_snapshot.to_inlay_offset(diff_offset)
self.inlay_snapshot
.to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
}
pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
@@ -961,17 +831,6 @@ impl DisplaySnapshot {
fold_point.to_inlay_point(&self.fold_snapshot)
}
fn diff_point_to_display_point(&self, diff_point: DiffPoint, bias: Bias) -> DisplayPoint {
let inlay_point = self.inlay_snapshot.to_inlay_point(diff_point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
self.fold_point_to_display_point(fold_point)
}
fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint {
self.inlay_snapshot
.to_diff_point(self.display_point_to_inlay_point(point, bias))
}
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
@@ -1209,22 +1068,6 @@ impl DisplaySnapshot {
})
}
pub fn anchor_before(&self, point: DisplayPoint) -> DisplayAnchor {
let diff_point = self.display_point_to_diff_point(point, Bias::Left);
self.diff_snapshot().point_to_anchor(diff_point, Bias::Left)
}
pub fn anchor_after(&self, point: DisplayPoint) -> DisplayAnchor {
let diff_point = self.display_point_to_diff_point(point, Bias::Left);
self.diff_snapshot()
.point_to_anchor(diff_point, Bias::Right)
}
pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DisplayPoint {
let diff_point = self.diff_snapshot().anchor_to_point(anchor);
self.diff_point_to_display_point(diff_point, Bias::Left)
}
pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint {
let mut clipped = self.block_snapshot.clip_point(point.0, bias);
if self.clip_at_line_ends {
@@ -1464,51 +1307,6 @@ impl DisplaySnapshot {
pub fn excerpt_header_height(&self) -> u32 {
self.block_snapshot.excerpt_header_height
}
pub(crate) fn diff_snapshot(&self) -> &DiffMapSnapshot {
&self.inlay_snapshot.diff_map_snapshot
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub struct DisplayAnchor {
pub anchor: multi_buffer::Anchor,
pub diff_base_anchor: Option<text::Anchor>,
}
impl DisplayAnchor {
pub fn min() -> Self {
Self {
anchor: multi_buffer::Anchor::min(),
diff_base_anchor: None,
}
}
pub fn max() -> Self {
Self {
anchor: multi_buffer::Anchor::max(),
diff_base_anchor: None,
}
}
pub fn cmp(&self, other: &DisplayAnchor, map: &DisplaySnapshot) -> std::cmp::Ordering {
map.diff_snapshot().compare_anchors(self, other)
}
}
pub trait DisplayCoordinate {
fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor;
}
impl DisplayCoordinate for DisplayAnchor {
fn to_display_anchor(self, _: &DisplaySnapshot) -> DisplayAnchor {
self
}
}
impl DisplayCoordinate for DisplayPoint {
fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor {
map.anchor_before(self)
}
}
#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]

View File

@@ -2,7 +2,7 @@ use super::{
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
Highlights,
};
use crate::{EditorStyle, GutterDimensions, RowInfo};
use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, AppContext, EntityId, Pixels, WindowContext};
use language::{Chunk, Patch, Point};
@@ -399,9 +399,9 @@ pub struct BlockChunks<'a> {
}
#[derive(Clone)]
pub struct BlockRows<'a> {
pub struct BlockBufferRows<'a> {
transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
input_rows: wrap_map::WrapRows<'a>,
input_buffer_rows: wrap_map::WrapBufferRows<'a>,
output_row: BlockRow,
started: bool,
}
@@ -1360,7 +1360,7 @@ impl BlockSnapshot {
}
}
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&start_row, Bias::Right, &());
let (output_start, input_start) = cursor.start();
@@ -1373,9 +1373,9 @@ impl BlockSnapshot {
0
};
let input_start_row = input_start.0 + overshoot;
BlockRows {
BlockBufferRows {
transforms: cursor,
input_rows: self.wrap_snapshot.row_infos(input_start_row),
input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
output_row: start_row,
started: false,
}
@@ -1766,8 +1766,8 @@ impl<'a> Iterator for BlockChunks<'a> {
}
}
impl<'a> Iterator for BlockRows<'a> {
type Item = RowInfo;
impl<'a> Iterator for BlockBufferRows<'a> {
type Item = Option<u32>;
fn next(&mut self) -> Option<Self::Item> {
if self.started {
@@ -1796,7 +1796,7 @@ impl<'a> Iterator for BlockRows<'a> {
.as_ref()
.map_or(true, |block| block.is_replacement())
{
self.input_rows.seek(self.transforms.start().1 .0);
self.input_buffer_rows.seek(self.transforms.start().1 .0);
}
}
@@ -1804,15 +1804,15 @@ impl<'a> Iterator for BlockRows<'a> {
if let Some(block) = transform.block.as_ref() {
if block.is_replacement() && self.transforms.start().0 == self.output_row {
if matches!(block, Block::FoldedBuffer { .. }) {
Some(RowInfo::default())
Some(None)
} else {
Some(self.input_rows.next().unwrap())
Some(self.input_buffer_rows.next().unwrap())
}
} else {
Some(RowInfo::default())
Some(None)
}
} else {
Some(self.input_rows.next().unwrap())
Some(self.input_buffer_rows.next().unwrap())
}
}
}
@@ -1928,8 +1928,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
mod tests {
use super::*;
use crate::display_map::{
diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap,
wrap_map::WrapMap,
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
};
use gpui::{div, font, px, AppContext, Context as _, Element};
use itertools::Itertools;
@@ -1963,8 +1962,7 @@ mod tests {
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
let (wrap_map, wraps_snapshot) =
@@ -2089,10 +2087,7 @@ mod tests {
);
assert_eq!(
snapshot
.row_infos(BlockRow(0))
.map(|row_info| row_info.buffer_row)
.collect::<Vec<_>>(),
snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
&[
Some(0),
None,
@@ -2113,10 +2108,8 @@ mod tests {
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
@@ -2178,8 +2171,8 @@ mod tests {
.width;
}
let (_, diff_snapshot) = DiffMap::new(multi_buffer.clone(), cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
@@ -2217,8 +2210,7 @@ mod tests {
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (_diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (_inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
let (_wrap_map, wraps_snapshot) =
@@ -2320,8 +2312,7 @@ mod tests {
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wraps_snapshot) = cx.update(|cx| {
@@ -2365,8 +2356,7 @@ mod tests {
let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let tab_size = 1.try_into().unwrap();
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
@@ -2393,14 +2383,10 @@ mod tests {
buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(
buffer_snapshot,
buffer_subscription.consume().into_inner(),
cx,
)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot.clone(), diff_edits);
let (inlay_snapshot, inlay_edits) = inlay_map.sync(
buffer_snapshot.clone(),
buffer_subscription.consume().into_inner(),
);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
@@ -2420,14 +2406,10 @@ mod tests {
);
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(
buffer_snapshot.clone(),
buffer_subscription.consume().into_inner(),
cx,
)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) = inlay_map.sync(
buffer_snapshot.clone(),
buffer_subscription.consume().into_inner(),
);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
@@ -2542,8 +2524,7 @@ mod tests {
let buffer_id_2 = buffer_ids[1];
let buffer_id_3 = buffer_ids[2];
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wrap_snapshot) =
@@ -2556,10 +2537,7 @@ mod tests {
"\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
);
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![
None,
None,
@@ -2635,10 +2613,7 @@ mod tests {
"\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
);
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![
None,
None,
@@ -2713,10 +2688,7 @@ mod tests {
"\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
);
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![
None,
None,
@@ -2781,10 +2753,7 @@ mod tests {
);
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![
None,
None,
@@ -2838,10 +2807,7 @@ mod tests {
"Should have extra newline for 111 buffer, due to a new block added when it was folded"
);
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![
None,
None,
@@ -2895,10 +2861,7 @@ mod tests {
"Should have a single, first buffer left after folding"
);
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![
None,
None,
@@ -2932,8 +2895,7 @@ mod tests {
assert_eq!(buffer_ids.len(), 1);
let buffer_id = buffer_ids[0];
let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wrap_snapshot) =
@@ -2969,10 +2931,7 @@ mod tests {
);
assert_eq!(blocks_snapshot.text(), "\n");
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(0))
.map(|i| i.buffer_row)
.collect::<Vec<_>>(),
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
vec![None, None],
"When fully folded, should be no buffer rows"
);
@@ -3018,8 +2977,7 @@ mod tests {
};
let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (wrap_map, wraps_snapshot) = cx
@@ -3077,10 +3035,8 @@ mod tests {
})
.collect::<Vec<_>>();
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), vec![]);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, tab_size);
@@ -3117,10 +3073,8 @@ mod tests {
.map(|block| block.id)
.collect::<HashSet<_>>();
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), vec![]);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, tab_size);
@@ -3140,11 +3094,8 @@ mod tests {
log::info!("Noop fold/unfold operation on a singleton buffer");
continue;
}
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), vec![], cx)
});
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(diff_snapshot.clone(), diff_edits);
inlay_map.sync(buffer_snapshot.clone(), vec![]);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, tab_size);
@@ -3229,10 +3180,8 @@ mod tests {
}
}
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
@@ -3435,8 +3384,7 @@ mod tests {
);
assert_eq!(
blocks_snapshot
.row_infos(BlockRow(start_row as u32))
.map(|row_info| row_info.buffer_row)
.buffer_rows(BlockRow(start_row as u32))
.collect::<Vec<_>>(),
&expected_buffer_rows[start_row..],
"incorrect buffer_rows starting at row {:?}",

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,3 @@
use crate::RowInfo;
use super::{
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
Highlights,
@@ -141,7 +139,7 @@ impl<'a> FoldMapWriter<'a> {
let mut folds = Vec::new();
let snapshot = self.0.snapshot.inlay_snapshot.clone();
for (range, fold_text) in ranges.into_iter() {
let buffer = snapshot.buffer();
let buffer = &snapshot.buffer;
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
// Ignore any empty ranges.
@@ -163,14 +161,14 @@ impl<'a> FoldMapWriter<'a> {
});
let inlay_range =
snapshot.make_inlay_offset(range.start)..snapshot.make_inlay_offset(range.end);
snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end);
edits.push(InlayEdit {
old: inlay_range.clone(),
new: inlay_range,
});
}
let buffer = snapshot.buffer();
let buffer = &snapshot.buffer;
folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
self.0.snapshot.folds = {
@@ -222,7 +220,7 @@ impl<'a> FoldMapWriter<'a> {
let mut edits = Vec::new();
let mut fold_ixs_to_delete = Vec::new();
let snapshot = self.0.snapshot.inlay_snapshot.clone();
let buffer = snapshot.buffer();
let buffer = &snapshot.buffer;
for range in ranges.into_iter() {
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
let mut folds_cursor =
@@ -232,8 +230,8 @@ impl<'a> FoldMapWriter<'a> {
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
if should_unfold(fold) {
if offset_range.end > offset_range.start {
let inlay_range = snapshot.make_inlay_offset(offset_range.start)
..snapshot.make_inlay_offset(offset_range.end);
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
..snapshot.to_inlay_offset(offset_range.end);
edits.push(InlayEdit {
old: inlay_range.clone(),
new: inlay_range,
@@ -277,7 +275,7 @@ impl FoldMap {
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
let this = Self {
snapshot: FoldSnapshot {
folds: SumTree::new(inlay_snapshot.buffer()),
folds: SumTree::new(&inlay_snapshot.buffer),
transforms: SumTree::from_item(
Transform {
summary: TransformSummary {
@@ -338,7 +336,9 @@ impl FoldMap {
let mut folds = self.snapshot.folds.iter().peekable();
while let Some(fold) = folds.next() {
if let Some(next_fold) = folds.peek() {
let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer());
let comparison = fold
.range
.cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
assert!(comparison.is_le());
}
}
@@ -410,31 +410,31 @@ impl FoldMap {
InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize);
let anchor = inlay_snapshot
.buffer()
.buffer
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
let mut folds_cursor = self
.snapshot
.folds
.cursor::<FoldRange>(&inlay_snapshot.buffer());
.cursor::<FoldRange>(&inlay_snapshot.buffer);
folds_cursor.seek(
&FoldRange(anchor..Anchor::max()),
Bias::Left,
&inlay_snapshot.buffer(),
&inlay_snapshot.buffer,
);
let mut folds = iter::from_fn({
let inlay_snapshot = &inlay_snapshot;
move || {
let item = folds_cursor.item().map(|fold| {
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer());
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer());
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer);
(
fold.clone(),
inlay_snapshot.make_inlay_offset(buffer_start)
..inlay_snapshot.make_inlay_offset(buffer_end),
inlay_snapshot.to_inlay_offset(buffer_start)
..inlay_snapshot.to_inlay_offset(buffer_end),
)
});
folds_cursor.next(&inlay_snapshot.buffer());
folds_cursor.next(&inlay_snapshot.buffer);
item
}
})
@@ -578,10 +578,6 @@ pub struct FoldSnapshot {
}
impl FoldSnapshot {
pub fn buffer(&self) -> &MultiBufferSnapshot {
self.inlay_snapshot.buffer()
}
#[cfg(test)]
pub fn text(&self) -> String {
self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
@@ -591,7 +587,7 @@ impl FoldSnapshot {
#[cfg(test)]
pub fn fold_count(&self) -> usize {
self.folds.items(self.inlay_snapshot.buffer()).len()
self.folds.items(&self.inlay_snapshot.buffer).len()
}
pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
@@ -645,32 +641,6 @@ impl FoldSnapshot {
summary
}
pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint {
self.to_fold_point(self.inlay_snapshot.make_inlay_point(point), bias)
}
pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset {
self.to_fold_offset(self.inlay_snapshot.make_inlay_offset(buffer_offset), bias)
}
pub fn to_fold_offset(&self, inlay_offset: InlayOffset, bias: Bias) -> FoldOffset {
let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(&());
cursor.seek(&inlay_offset, Bias::Right, &());
if cursor.item().map_or(false, |t| t.is_fold()) {
if bias == Bias::Left || inlay_offset == cursor.start().0 {
cursor.start().1
} else {
cursor.end(&()).1
}
} else {
let overshoot = inlay_offset.0 - cursor.start().0 .0;
FoldOffset(cmp::min(
cursor.start().1 .0 + overshoot,
cursor.end(&()).1 .0,
))
}
}
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
cursor.seek(&point, Bias::Right, &());
@@ -703,7 +673,7 @@ impl FoldSnapshot {
(line_end - line_start) as u32
}
pub fn row_infos(&self, start_row: u32) -> FoldRows {
pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
if start_row > self.transforms.summary().output.lines.row {
panic!("invalid display row {}", start_row);
}
@@ -714,11 +684,11 @@ impl FoldSnapshot {
let overshoot = fold_point.0 - cursor.start().0 .0;
let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
FoldRows {
FoldBufferRows {
fold_point,
input_rows,
input_buffer_rows,
cursor,
}
}
@@ -736,12 +706,12 @@ impl FoldSnapshot {
where
T: ToOffset,
{
let buffer = self.inlay_snapshot.buffer();
let buffer = &self.inlay_snapshot.buffer;
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
iter::from_fn(move || {
let item = folds.item();
folds.next(buffer);
folds.next(&self.inlay_snapshot.buffer);
item
})
}
@@ -750,8 +720,8 @@ impl FoldSnapshot {
where
T: ToOffset,
{
let buffer_offset = offset.to_offset(self.inlay_snapshot.buffer());
let inlay_offset = self.inlay_snapshot.make_inlay_offset(buffer_offset);
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
let mut cursor = self.transforms.cursor::<InlayOffset>(&());
cursor.seek(&inlay_offset, Bias::Right, &());
cursor.item().map_or(false, |t| t.placeholder.is_some())
@@ -760,7 +730,7 @@ impl FoldSnapshot {
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
let mut inlay_point = self
.inlay_snapshot
.make_inlay_point(Point::new(buffer_row.0, 0));
.to_inlay_point(Point::new(buffer_row.0, 0));
let mut cursor = self.transforms.cursor::<InlayPoint>(&());
cursor.seek(&inlay_point, Bias::Right, &());
loop {
@@ -900,7 +870,7 @@ fn intersecting_folds<'a>(
range: Range<usize>,
inclusive: bool,
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
let buffer = inlay_snapshot.buffer();
let buffer = &inlay_snapshot.buffer;
let start = buffer.anchor_before(range.start.to_offset(buffer));
let end = buffer.anchor_after(range.end.to_offset(buffer));
let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
@@ -1164,25 +1134,25 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
}
#[derive(Clone)]
pub struct FoldRows<'a> {
pub struct FoldBufferRows<'a> {
cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
input_rows: InlayBufferRows<'a>,
input_buffer_rows: InlayBufferRows<'a>,
fold_point: FoldPoint,
}
impl<'a> FoldRows<'a> {
impl<'a> FoldBufferRows<'a> {
pub(crate) fn seek(&mut self, row: u32) {
let fold_point = FoldPoint::new(row, 0);
self.cursor.seek(&fold_point, Bias::Left, &());
let overshoot = fold_point.0 - self.cursor.start().0 .0;
let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
self.input_rows.seek(inlay_point.row());
self.input_buffer_rows.seek(inlay_point.row());
self.fold_point = fold_point;
}
}
impl<'a> Iterator for FoldRows<'a> {
type Item = RowInfo;
impl<'a> Iterator for FoldBufferRows<'a> {
type Item = Option<u32>;
fn next(&mut self) -> Option<Self::Item> {
let mut traversed_fold = false;
@@ -1196,11 +1166,11 @@ impl<'a> Iterator for FoldRows<'a> {
if self.cursor.item().is_some() {
if traversed_fold {
self.input_rows.seek(self.cursor.start().1 .0.row);
self.input_rows.next();
self.input_buffer_rows.seek(self.cursor.start().1.row());
self.input_buffer_rows.next();
}
*self.fold_point.row_mut() += 1;
self.input_rows.next()
self.input_buffer_rows.next()
} else {
None
}
@@ -1410,10 +1380,7 @@ pub type FoldEdit = Edit<FoldOffset>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{
display_map::{diff_map::DiffMap, inlay_map::InlayMap},
MultiBuffer, ToPoint,
};
use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint};
use collections::HashSet;
use rand::prelude::*;
use settings::SettingsStore;
@@ -1428,8 +1395,8 @@ mod tests {
init_test(cx);
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
@@ -1464,14 +1431,8 @@ mod tests {
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(
buffer_snapshot.clone(),
subscription.consume().into_inner(),
cx,
)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits);
assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee");
assert_eq!(
@@ -1492,11 +1453,8 @@ mod tests {
buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx);
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits);
assert_eq!(snapshot4.text(), "123a⋯c123456eee");
@@ -1516,8 +1474,8 @@ mod tests {
init_test(cx);
let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
{
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
@@ -1563,10 +1521,8 @@ mod tests {
buffer.edit([(0..1, "12345")], None, cx);
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
assert_eq!(snapshot.text(), "12345⋯fghijkl");
}
@@ -1575,8 +1531,8 @@ mod tests {
#[gpui::test]
fn test_overlapping_folds(cx: &mut gpui::AppContext) {
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![
@@ -1594,8 +1550,8 @@ mod tests {
init_test(cx);
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
@@ -1610,10 +1566,8 @@ mod tests {
buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx);
buffer.snapshot(cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
let (snapshot, _) = map.read(inlay_snapshot, inlay_edits);
assert_eq!(snapshot.text(), "aa⋯eeeee");
}
@@ -1622,8 +1576,7 @@ mod tests {
fn test_folds_in_range(cx: &mut gpui::AppContext) {
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
@@ -1665,8 +1618,7 @@ mod tests {
MultiBuffer::build_random(&mut rng, cx)
};
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
@@ -1696,10 +1648,8 @@ mod tests {
}),
};
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
});
let (inlay_snapshot, new_inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, new_inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
log::info!("inlay text {:?}", inlay_snapshot.text());
let inlay_edits = Patch::new(inlay_edits)
@@ -1710,8 +1660,8 @@ mod tests {
let mut expected_text: String = inlay_snapshot.text().to_string();
for fold_range in map.merged_folds().into_iter().rev() {
let fold_inlay_start = inlay_snapshot.make_inlay_offset(fold_range.start);
let fold_inlay_end = inlay_snapshot.make_inlay_offset(fold_range.end);
let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "");
}
@@ -1726,19 +1676,19 @@ mod tests {
let mut expected_buffer_rows = Vec::new();
for fold_range in map.merged_folds() {
let fold_start = inlay_snapshot
.to_point(inlay_snapshot.make_inlay_offset(fold_range.start))
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
.row();
let fold_end = inlay_snapshot
.to_point(inlay_snapshot.make_inlay_offset(fold_range.end))
.to_point(inlay_snapshot.to_inlay_offset(fold_range.end))
.row();
expected_buffer_rows.extend(
inlay_snapshot
.row_infos(prev_row)
.buffer_rows(prev_row)
.take((1 + fold_start - prev_row) as usize),
);
prev_row = 1 + fold_end;
}
expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
assert_eq!(
expected_buffer_rows.len(),
@@ -1827,7 +1777,7 @@ mod tests {
let mut fold_row = 0;
while fold_row < expected_buffer_rows.len() as u32 {
assert_eq!(
snapshot.row_infos(fold_row).collect::<Vec<_>>(),
snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
expected_buffer_rows[(fold_row as usize)..],
"wrong buffer rows starting at fold row {}",
fold_row,
@@ -1929,8 +1879,8 @@ mod tests {
let text = sample_text(6, 6, 'a') + "\n";
let buffer = MultiBuffer::build_simple(&text, cx);
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
@@ -1942,19 +1892,10 @@ mod tests {
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
assert_eq!(
snapshot
.row_infos(0)
.map(|info| info.buffer_row)
.collect::<Vec<_>>(),
snapshot.buffer_rows(0).collect::<Vec<_>>(),
[Some(0), Some(3), Some(5), Some(6)]
);
assert_eq!(
snapshot
.row_infos(3)
.map(|info| info.buffer_row)
.collect::<Vec<_>>(),
[Some(6)]
);
assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
}
fn init_test(cx: &mut gpui::AppContext) {
@@ -1965,7 +1906,7 @@ mod tests {
impl FoldMap {
fn merged_folds(&self) -> Vec<Range<usize>> {
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
let buffer = inlay_snapshot.buffer();
let buffer = &inlay_snapshot.buffer;
let mut folds = self.snapshot.folds.items(buffer);
// Ensure sorting doesn't change how folds get merged and displayed.
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
@@ -2001,7 +1942,7 @@ mod tests {
match rng.gen_range(0..=100) {
0..=39 if !self.snapshot.folds.is_empty() => {
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
let buffer = inlay_snapshot.buffer();
let buffer = &inlay_snapshot.buffer;
let mut to_unfold = Vec::new();
for _ in 0..rng.gen_range(1..=3) {
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
@@ -2017,7 +1958,7 @@ mod tests {
}
_ => {
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
let buffer = inlay_snapshot.buffer();
let buffer = &inlay_snapshot.buffer;
let mut to_fold = Vec::new();
for _ in 0..rng.gen_range(1..=2) {
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);

View File

@@ -1,7 +1,7 @@
use crate::{HighlightStyles, InlayId, RowInfo};
use crate::{HighlightStyles, InlayId};
use collections::BTreeSet;
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{Anchor, MultiBufferSnapshot, ToOffset};
use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset};
use std::{
cmp,
ops::{Add, AddAssign, Range, Sub, SubAssign},
@@ -9,10 +9,7 @@ use std::{
use sum_tree::{Bias, Cursor, SumTree};
use text::{Patch, Rope};
use super::{
diff_map::{DiffEdit, DiffMapChunks, DiffMapRows, DiffMapSnapshot, DiffOffset, DiffPoint},
Highlights,
};
use super::{custom_highlights::CustomHighlightsChunks, Highlights};
/// Decides where the [`Inlay`]s should be displayed.
///
@@ -24,7 +21,7 @@ pub struct InlayMap {
#[derive(Clone)]
pub struct InlaySnapshot {
pub diff_map_snapshot: DiffMapSnapshot,
pub buffer: MultiBufferSnapshot,
transforms: SumTree<Transform>,
pub version: usize,
}
@@ -175,37 +172,37 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffOffset {
fn zero(_cx: &()) -> Self {
DiffOffset(0)
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.input.len;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffPoint {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.input.lines;
*self += &summary.input.len;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
*self += &summary.input.lines;
}
}
#[derive(Clone)]
pub struct InlayBufferRows<'a> {
transforms: Cursor<'a, Transform, (InlayPoint, DiffPoint)>,
diff_rows: DiffMapRows<'a>,
transforms: Cursor<'a, Transform, (InlayPoint, Point)>,
buffer_rows: MultiBufferRows<'a>,
inlay_row: u32,
max_point: DiffPoint,
max_buffer_row: MultiBufferRow,
}
pub struct InlayChunks<'a> {
transforms: Cursor<'a, Transform, (InlayOffset, DiffOffset)>,
diff_map_chunks: DiffMapChunks<'a>,
transforms: Cursor<'a, Transform, (InlayOffset, usize)>,
buffer_chunks: CustomHighlightsChunks<'a>,
buffer_chunk: Option<Chunk<'a>>,
inlay_chunks: Option<text::Chunks<'a>>,
inlay_chunk: Option<&'a str>,
@@ -217,21 +214,21 @@ pub struct InlayChunks<'a> {
}
impl<'a> InlayChunks<'a> {
pub fn offset(&self) -> InlayOffset {
self.output_offset
}
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
self.transforms.seek(&new_range.start, Bias::Right, &());
let diff_range = self.snapshot.to_diff_offset(new_range.start)
..self.snapshot.to_diff_offset(new_range.end);
self.diff_map_chunks.seek(diff_range);
let buffer_range = self.snapshot.to_buffer_offset(new_range.start)
..self.snapshot.to_buffer_offset(new_range.end);
self.buffer_chunks.seek(buffer_range);
self.inlay_chunks = None;
self.buffer_chunk = None;
self.output_offset = new_range.start;
self.max_output_offset = new_range.end;
}
pub fn offset(&self) -> InlayOffset {
self.output_offset
}
}
impl<'a> Iterator for InlayChunks<'a> {
@@ -246,9 +243,9 @@ impl<'a> Iterator for InlayChunks<'a> {
Transform::Isomorphic(_) => {
let chunk = self
.buffer_chunk
.get_or_insert_with(|| self.diff_map_chunks.next().unwrap());
.get_or_insert_with(|| self.buffer_chunks.next().unwrap());
if chunk.text.is_empty() {
*chunk = self.diff_map_chunks.next().unwrap();
*chunk = self.buffer_chunks.next().unwrap();
}
let (prefix, suffix) = chunk.text.split_at(
@@ -347,33 +344,33 @@ impl<'a> InlayBufferRows<'a> {
let inlay_point = InlayPoint::new(row, 0);
self.transforms.seek(&inlay_point, Bias::Left, &());
let mut diff_point = self.transforms.start().1;
let row = if row == 0 {
let mut buffer_point = self.transforms.start().1;
let buffer_row = MultiBufferRow(if row == 0 {
0
} else {
match self.transforms.item() {
Some(Transform::Isomorphic(_)) => {
diff_point.0 += inlay_point.0 - self.transforms.start().0 .0;
diff_point.row()
buffer_point += inlay_point.0 - self.transforms.start().0 .0;
buffer_point.row
}
_ => cmp::min(diff_point.row() + 1, self.max_point.row()),
_ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0),
}
};
});
self.inlay_row = inlay_point.row();
self.diff_rows.seek(row);
self.buffer_rows.seek(buffer_row);
}
}
impl<'a> Iterator for InlayBufferRows<'a> {
type Item = RowInfo;
type Item = Option<u32>;
fn next(&mut self) -> Option<Self::Item> {
let row_info = if self.inlay_row == 0 {
self.diff_rows.next().unwrap()
let buffer_row = if self.inlay_row == 0 {
self.buffer_rows.next().unwrap()
} else {
match self.transforms.item()? {
Transform::Inlay(_) => Default::default(),
Transform::Isomorphic(_) => self.diff_rows.next().unwrap(),
Transform::Inlay(_) => None,
Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
}
};
@@ -381,7 +378,7 @@ impl<'a> Iterator for InlayBufferRows<'a> {
self.transforms
.seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &());
Some(row_info)
Some(buffer_row)
}
}
@@ -396,14 +393,11 @@ impl InlayPoint {
}
impl InlayMap {
pub fn new(diff_map_snapshot: DiffMapSnapshot) -> (Self, InlaySnapshot) {
pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) {
let version = 0;
let snapshot = InlaySnapshot {
transforms: SumTree::from_iter(
Some(Transform::Isomorphic(diff_map_snapshot.text_summary())),
&(),
),
diff_map_snapshot,
buffer: buffer.clone(),
transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
version,
};
@@ -418,121 +412,133 @@ impl InlayMap {
pub fn sync(
&mut self,
diff_map_snapshot: DiffMapSnapshot,
mut diff_edits: Vec<DiffEdit>,
buffer_snapshot: MultiBufferSnapshot,
mut buffer_edits: Vec<text::Edit<usize>>,
) -> (InlaySnapshot, Vec<InlayEdit>) {
let snapshot = &mut self.snapshot;
if diff_edits.is_empty()
&& snapshot.buffer().trailing_excerpt_update_count()
!= diff_map_snapshot.buffer().trailing_excerpt_update_count()
if buffer_edits.is_empty()
&& snapshot.buffer.trailing_excerpt_update_count()
!= buffer_snapshot.trailing_excerpt_update_count()
{
diff_edits.push(Edit {
old: snapshot.diff_map_snapshot.len()..snapshot.diff_map_snapshot.len(),
new: diff_map_snapshot.len()..diff_map_snapshot.len(),
buffer_edits.push(Edit {
old: snapshot.buffer.len()..snapshot.buffer.len(),
new: buffer_snapshot.len()..buffer_snapshot.len(),
});
}
let mut inlay_edits = Patch::default();
let mut new_transforms = SumTree::default();
let mut cursor = snapshot.transforms.cursor::<(DiffOffset, InlayOffset)>(&());
let mut diff_edits_iter = diff_edits.iter().peekable();
while let Some(diff_edit) = diff_edits_iter.next() {
new_transforms.append(cursor.slice(&diff_edit.old.start, Bias::Left, &()), &());
if let Some(Transform::Isomorphic(transform)) = cursor.item() {
if cursor.end(&()).0 == diff_edit.old.start {
push_isomorphic(&mut new_transforms, transform.clone());
if buffer_edits.is_empty() {
if snapshot.buffer.edit_count() != buffer_snapshot.edit_count()
|| snapshot.buffer.non_text_state_update_count()
!= buffer_snapshot.non_text_state_update_count()
|| snapshot.buffer.trailing_excerpt_update_count()
!= buffer_snapshot.trailing_excerpt_update_count()
{
snapshot.version += 1;
}
snapshot.buffer = buffer_snapshot;
(snapshot.clone(), Vec::new())
} else {
let mut inlay_edits = Patch::default();
let mut new_transforms = SumTree::default();
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&());
let mut buffer_edits_iter = buffer_edits.iter().peekable();
while let Some(buffer_edit) = buffer_edits_iter.next() {
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
if let Some(Transform::Isomorphic(transform)) = cursor.item() {
if cursor.end(&()).0 == buffer_edit.old.start {
push_isomorphic(&mut new_transforms, transform.clone());
cursor.next(&());
}
}
// Remove all the inlays and transforms contained by the edit.
let old_start =
cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0);
cursor.seek(&buffer_edit.old.end, Bias::Right, &());
let old_end =
cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0);
// Push the unchanged prefix.
let prefix_start = new_transforms.summary().input.len;
let prefix_end = buffer_edit.new.start;
push_isomorphic(
&mut new_transforms,
buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
);
let new_start = InlayOffset(new_transforms.summary().output.len);
let start_ix = match self.inlays.binary_search_by(|probe| {
probe
.position
.to_offset(&buffer_snapshot)
.cmp(&buffer_edit.new.start)
.then(std::cmp::Ordering::Greater)
}) {
Ok(ix) | Err(ix) => ix,
};
for inlay in &self.inlays[start_ix..] {
let buffer_offset = inlay.position.to_offset(&buffer_snapshot);
if buffer_offset > buffer_edit.new.end {
break;
}
let prefix_start = new_transforms.summary().input.len;
let prefix_end = buffer_offset;
push_isomorphic(
&mut new_transforms,
buffer_snapshot.text_summary_for_range(prefix_start..prefix_end),
);
if inlay.position.is_valid(&buffer_snapshot) {
new_transforms.push(Transform::Inlay(inlay.clone()), &());
}
}
// Apply the rest of the edit.
let transform_start = new_transforms.summary().input.len;
push_isomorphic(
&mut new_transforms,
buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end),
);
let new_end = InlayOffset(new_transforms.summary().output.len);
inlay_edits.push(Edit {
old: old_start..old_end,
new: new_start..new_end,
});
// If the next edit doesn't intersect the current isomorphic transform, then
// we can push its remainder.
if buffer_edits_iter
.peek()
.map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
{
let transform_start = new_transforms.summary().input.len;
let transform_end =
buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end);
push_isomorphic(
&mut new_transforms,
buffer_snapshot.text_summary_for_range(transform_start..transform_end),
);
cursor.next(&());
}
}
// Remove all the inlays and transforms contained by the edit.
let old_start =
cursor.start().1 + InlayOffset((diff_edit.old.start - cursor.start().0).0);
cursor.seek(&diff_edit.old.end, Bias::Right, &());
let old_end = cursor.start().1 + InlayOffset((diff_edit.old.end - cursor.start().0).0);
// Push the unchanged prefix.
let prefix_start = DiffOffset(new_transforms.summary().input.len);
let prefix_end = diff_edit.new.start;
push_isomorphic(
&mut new_transforms,
diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end),
);
let new_start = InlayOffset(new_transforms.summary().output.len);
let edit_buffer_range = diff_map_snapshot.to_multibuffer_offset(diff_edit.new.start)
..diff_map_snapshot.to_multibuffer_offset(diff_edit.new.end);
let start_ix = match self.inlays.binary_search_by(|probe| {
probe
.position
.to_offset(diff_map_snapshot.buffer())
.cmp(&edit_buffer_range.start)
.then(std::cmp::Ordering::Greater)
}) {
Ok(ix) | Err(ix) => ix,
};
for inlay in &self.inlays[start_ix..] {
let buffer_offset = inlay.position.to_offset(&diff_map_snapshot.buffer);
let diff_offset = diff_map_snapshot.to_diff_offset(buffer_offset);
if buffer_offset > edit_buffer_range.end {
break;
}
let prefix_start = DiffOffset(new_transforms.summary().input.len);
let prefix_end = diff_offset;
push_isomorphic(
&mut new_transforms,
diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end),
);
if inlay.position.is_valid(&diff_map_snapshot.buffer) {
new_transforms.push(Transform::Inlay(inlay.clone()), &());
}
new_transforms.append(cursor.suffix(&()), &());
if new_transforms.is_empty() {
new_transforms.push(Transform::Isomorphic(Default::default()), &());
}
// Apply the rest of the edit.
let transform_start = DiffOffset(new_transforms.summary().input.len);
push_isomorphic(
&mut new_transforms,
diff_map_snapshot.text_summary_for_range(transform_start..diff_edit.new.end),
);
let new_end = InlayOffset(new_transforms.summary().output.len);
inlay_edits.push(Edit {
old: old_start..old_end,
new: new_start..new_end,
});
drop(cursor);
snapshot.transforms = new_transforms;
snapshot.version += 1;
snapshot.buffer = buffer_snapshot;
snapshot.check_invariants();
// If the next edit doesn't intersect the current isomorphic transform, then
// we can push its remainder.
if diff_edits_iter
.peek()
.map_or(true, |edit| edit.old.start >= cursor.end(&()).0)
{
let transform_start = DiffOffset(new_transforms.summary().input.len);
let transform_end = diff_edit.new.end + (cursor.end(&()).0 - diff_edit.old.end);
push_isomorphic(
&mut new_transforms,
diff_map_snapshot.text_summary_for_range(transform_start..transform_end),
);
cursor.next(&());
}
(snapshot.clone(), inlay_edits.into_inner())
}
new_transforms.append(cursor.suffix(&()), &());
if new_transforms.is_empty() {
new_transforms.push(Transform::Isomorphic(Default::default()), &());
}
drop(cursor);
snapshot.transforms = new_transforms;
snapshot.version += 1;
snapshot.diff_map_snapshot = diff_map_snapshot;
snapshot.check_invariants();
(snapshot.clone(), inlay_edits.into_inner())
}
pub fn splice(
@@ -546,9 +552,7 @@ impl InlayMap {
self.inlays.retain(|inlay| {
let retain = !to_remove.contains(&inlay.id);
if !retain {
let offset = snapshot
.diff_map_snapshot
.to_diff_offset(inlay.position.to_offset(&snapshot.diff_map_snapshot.buffer));
let offset = inlay.position.to_offset(&snapshot.buffer);
edits.insert(offset);
}
retain
@@ -560,16 +564,11 @@ impl InlayMap {
continue;
}
let offset = snapshot.diff_map_snapshot.to_diff_offset(
inlay_to_insert
.position
.to_offset(&snapshot.diff_map_snapshot.buffer),
);
let offset = inlay_to_insert.position.to_offset(&snapshot.buffer);
match self.inlays.binary_search_by(|probe| {
probe.position.cmp(
&inlay_to_insert.position,
&snapshot.diff_map_snapshot.buffer,
)
probe
.position
.cmp(&inlay_to_insert.position, &snapshot.buffer)
}) {
Ok(ix) | Err(ix) => {
self.inlays.insert(ix, inlay_to_insert);
@@ -586,7 +585,7 @@ impl InlayMap {
new: offset..offset,
})
.collect();
let buffer_snapshot = snapshot.diff_map_snapshot.clone();
let buffer_snapshot = snapshot.buffer.clone();
let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
(snapshot, edits)
}
@@ -609,8 +608,7 @@ impl InlayMap {
let snapshot = &mut self.snapshot;
for i in 0..rng.gen_range(1..=5) {
if self.inlays.is_empty() || rng.gen() {
let position = snapshot.buffer().random_byte_range(0, rng).start;
let anchor = snapshot.buffer().anchor_at(position, Bias::Left);
let position = snapshot.buffer.random_byte_range(0, rng).start;
let bias = if rng.gen() { Bias::Left } else { Bias::Right };
let len = if rng.gen_bool(0.01) {
0
@@ -628,7 +626,7 @@ impl InlayMap {
InlayId::InlineCompletion(post_inc(next_inlay_id))
};
log::info!(
"creating inlay {:?} at buffer offset {:?} with bias {:?} and text {:?}",
"creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}",
inlay_id,
position,
bias,
@@ -637,7 +635,7 @@ impl InlayMap {
to_insert.push(Inlay {
id: inlay_id,
position: anchor,
position: snapshot.buffer.anchor_at(position, bias),
text: text.into(),
});
} else {
@@ -661,16 +659,16 @@ impl InlaySnapshot {
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
let mut cursor = self
.transforms
.cursor::<(InlayOffset, (InlayPoint, DiffOffset))>(&());
.cursor::<(InlayOffset, (InlayPoint, usize))>(&());
cursor.seek(&offset, Bias::Right, &());
let overshoot = offset.0 - cursor.start().0 .0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let buffer_offset_start = cursor.start().1 .1;
let buffer_offset_end = buffer_offset_start + DiffOffset(overshoot);
let buffer_start = self.diff_map_snapshot.offset_to_point(buffer_offset_start);
let buffer_end = self.diff_map_snapshot.offset_to_point(buffer_offset_end);
cursor.start().1 .0 + InlayPoint(buffer_end.0 - buffer_start.0)
let buffer_offset_end = buffer_offset_start + overshoot;
let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start))
}
Some(Transform::Inlay(inlay)) => {
let overshoot = inlay.text.offset_to_point(overshoot);
@@ -691,68 +689,51 @@ impl InlaySnapshot {
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
let mut cursor = self
.transforms
.cursor::<(InlayPoint, (InlayOffset, DiffPoint))>(&());
.cursor::<(InlayPoint, (InlayOffset, Point))>(&());
cursor.seek(&point, Bias::Right, &());
let overshoot = point.0 - cursor.start().0 .0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let buffer_point_start = cursor.start().1 .1;
let buffer_point_end = buffer_point_start + DiffPoint(overshoot);
let buffer_offset_start =
self.diff_map_snapshot.point_to_offset(buffer_point_start);
let buffer_offset_end = self.diff_map_snapshot.point_to_offset(buffer_point_end);
cursor.start().1 .0 + InlayOffset((buffer_offset_end - buffer_offset_start).0)
let buffer_point_end = buffer_point_start + overshoot;
let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start))
}
Some(Transform::Inlay(inlay)) => {
let overshoot = inlay.text.point_to_offset(overshoot);
cursor.start().1 .0 + InlayOffset(overshoot)
InlayOffset(cursor.start().1 .0 .0 + overshoot)
}
None => self.len(),
}
}
pub fn to_diff_point(&self, point: InlayPoint) -> DiffPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&());
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
cursor.seek(&point, Bias::Right, &());
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let overshoot = point.0 - cursor.start().0 .0;
cursor.start().1 + DiffPoint(overshoot)
cursor.start().1 + overshoot
}
Some(Transform::Inlay(_)) => cursor.start().1,
None => self.diff_map_snapshot.max_point(),
None => self.buffer.max_point(),
}
}
pub fn to_diff_offset(&self, offset: InlayOffset) -> DiffOffset {
let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&());
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
cursor.seek(&offset, Bias::Right, &());
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let overshoot = offset - cursor.start().0;
cursor.start().1 + DiffOffset(overshoot.0)
cursor.start().1 + overshoot.0
}
Some(Transform::Inlay(_)) => cursor.start().1,
None => self.diff_map_snapshot.len(),
None => self.buffer.len(),
}
}
pub fn buffer(&self) -> &MultiBufferSnapshot {
&self.diff_map_snapshot.buffer
}
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
self.diff_map_snapshot
.to_multibuffer_point(self.to_diff_point(point))
}
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
self.diff_map_snapshot
.to_multibuffer_offset(self.to_diff_offset(offset))
}
pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset {
let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&());
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
cursor.seek(&offset, Bias::Left, &());
loop {
match cursor.item() {
@@ -767,7 +748,7 @@ impl InlaySnapshot {
}
return cursor.end(&()).1;
} else {
let overshoot = offset.0 - cursor.start().0 .0;
let overshoot = offset - cursor.start().0;
return InlayOffset(cursor.start().1 .0 + overshoot);
}
}
@@ -784,17 +765,8 @@ impl InlaySnapshot {
}
}
}
pub fn make_inlay_point(&self, multibuffer_point: Point) -> InlayPoint {
self.to_inlay_point(self.diff_map_snapshot.to_diff_point(multibuffer_point))
}
pub fn make_inlay_offset(&self, multibuffer_point: usize) -> InlayOffset {
self.to_inlay_offset(self.diff_map_snapshot.to_diff_offset(multibuffer_point))
}
pub fn to_inlay_point(&self, point: DiffPoint) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&());
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
cursor.seek(&point, Bias::Left, &());
loop {
match cursor.item() {
@@ -810,7 +782,7 @@ impl InlaySnapshot {
return cursor.end(&()).1;
} else {
let overshoot = point - cursor.start().0;
return InlayPoint(cursor.start().1 .0 + overshoot.0);
return InlayPoint(cursor.start().1 .0 + overshoot);
}
}
Some(Transform::Inlay(inlay)) => {
@@ -828,7 +800,7 @@ impl InlaySnapshot {
}
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&());
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
cursor.seek(&point, Bias::Left, &());
loop {
match cursor.item() {
@@ -864,11 +836,10 @@ impl InlaySnapshot {
}
} else {
let overshoot = point.0 - cursor.start().0 .0;
let diff_point = cursor.start().1 + DiffPoint(overshoot);
let clipped_diff_point =
self.diff_map_snapshot.clip_point(diff_point, bias);
let clipped_overshoot = clipped_diff_point - cursor.start().1;
let clipped_point = cursor.start().0 + InlayPoint(clipped_overshoot.0);
let buffer_point = cursor.start().1 + overshoot;
let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
let clipped_overshoot = clipped_buffer_point - cursor.start().1;
let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
if clipped_point == point {
return clipped_point;
} else {
@@ -926,19 +897,17 @@ impl InlaySnapshot {
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
let mut summary = TextSummary::default();
let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&());
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
cursor.seek(&range.start, Bias::Right, &());
let overshoot = range.start.0 - cursor.start().0 .0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let buffer_start = cursor.start().1;
let suffix_start = buffer_start + DiffOffset(overshoot);
let suffix_end = buffer_start
+ DiffOffset(cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
summary = self
.diff_map_snapshot
.text_summary_for_range(suffix_start..suffix_end);
let suffix_start = buffer_start + overshoot;
let suffix_end =
buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0);
summary = self.buffer.text_summary_for_range(suffix_start..suffix_end);
cursor.next(&());
}
Some(Transform::Inlay(inlay)) => {
@@ -959,10 +928,10 @@ impl InlaySnapshot {
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let prefix_start = cursor.start().1;
let prefix_end = prefix_start + DiffOffset(overshoot);
let prefix_end = prefix_start + overshoot;
summary += self
.diff_map_snapshot
.text_summary_for_range(prefix_start..prefix_end);
.buffer
.text_summary_for_range::<TextSummary, _>(prefix_start..prefix_end);
}
Some(Transform::Inlay(inlay)) => {
let prefix_end = overshoot;
@@ -975,30 +944,30 @@ impl InlaySnapshot {
summary
}
pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&());
pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
let inlay_point = InlayPoint::new(row, 0);
cursor.seek(&inlay_point, Bias::Left, &());
let max_point = self.diff_map_snapshot.max_point();
let mut diff_point = cursor.start().1;
let diff_row = if row == 0 {
0
let max_buffer_row = self.buffer.max_row();
let mut buffer_point = cursor.start().1;
let buffer_row = if row == 0 {
MultiBufferRow(0)
} else {
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
diff_point += DiffPoint(inlay_point.0 - cursor.start().0 .0);
diff_point.row()
buffer_point += inlay_point.0 - cursor.start().0 .0;
MultiBufferRow(buffer_point.row)
}
_ => cmp::min(diff_point.row() + 1, max_point.row()),
_ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row),
}
};
InlayBufferRows {
transforms: cursor,
inlay_row: inlay_point.row(),
diff_rows: self.diff_map_snapshot.row_infos(diff_row),
max_point,
buffer_rows: self.buffer.buffer_rows(buffer_row),
max_buffer_row,
}
}
@@ -1018,17 +987,20 @@ impl InlaySnapshot {
language_aware: bool,
highlights: Highlights<'a>,
) -> InlayChunks<'a> {
let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&());
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
cursor.seek(&range.start, Bias::Right, &());
let diff_range = self.to_diff_offset(range.start)..self.to_diff_offset(range.end);
let diff_map_chunks =
self.diff_map_snapshot
.chunks(diff_range, language_aware, highlights.text_highlights);
let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end);
let buffer_chunks = CustomHighlightsChunks::new(
buffer_range,
language_aware,
highlights.text_highlights,
&self.buffer,
);
InlayChunks {
transforms: cursor,
diff_map_chunks,
buffer_chunks,
inlay_chunks: None,
inlay_chunk: None,
buffer_chunk: None,
@@ -1050,10 +1022,7 @@ impl InlaySnapshot {
fn check_invariants(&self) {
#[cfg(any(debug_assertions, feature = "test-support"))]
{
assert_eq!(
self.transforms.summary().input,
self.diff_map_snapshot.text_summary()
);
assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
let mut transforms = self.transforms.iter().peekable();
while let Some(transform) = transforms.next() {
let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
@@ -1094,7 +1063,7 @@ fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: TextSummary) {
mod tests {
use super::*;
use crate::{
display_map::{DiffMap, InlayHighlights, TextHighlights},
display_map::{InlayHighlights, TextHighlights},
hover_links::InlayHighlight,
InlayId, MultiBuffer,
};
@@ -1194,8 +1163,7 @@ mod tests {
fn test_basic_inlays(cx: &mut AppContext) {
let buffer = MultiBuffer::build_simple("abcdefghi", cx);
let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
let (diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_map_snapshot.clone());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
assert_eq!(inlay_snapshot.text(), "abcdefghi");
let mut next_inlay_id = 0;
@@ -1209,27 +1177,27 @@ mod tests {
);
assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
assert_eq!(
inlay_snapshot.make_inlay_point(Point::new(0, 0)),
inlay_snapshot.to_inlay_point(Point::new(0, 0)),
InlayPoint::new(0, 0)
);
assert_eq!(
inlay_snapshot.make_inlay_point(Point::new(0, 1)),
inlay_snapshot.to_inlay_point(Point::new(0, 1)),
InlayPoint::new(0, 1)
);
assert_eq!(
inlay_snapshot.make_inlay_point(Point::new(0, 2)),
inlay_snapshot.to_inlay_point(Point::new(0, 2)),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.make_inlay_point(Point::new(0, 3)),
inlay_snapshot.to_inlay_point(Point::new(0, 3)),
InlayPoint::new(0, 3)
);
assert_eq!(
inlay_snapshot.make_inlay_point(Point::new(0, 4)),
inlay_snapshot.to_inlay_point(Point::new(0, 4)),
InlayPoint::new(0, 9)
);
assert_eq!(
inlay_snapshot.make_inlay_point(Point::new(0, 5)),
inlay_snapshot.to_inlay_point(Point::new(0, 5)),
InlayPoint::new(0, 10)
);
assert_eq!(
@@ -1261,26 +1229,18 @@ mod tests {
buffer.update(cx, |buffer, cx| {
buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx)
});
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(
buffer.read(cx).snapshot(cx),
buffer_edits.consume().into_inner(),
cx,
)
});
let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, _) = inlay_map.sync(
buffer.read(cx).snapshot(cx),
buffer_edits.consume().into_inner(),
);
assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi");
// An edit surrounding the inlay should invalidate it.
buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx));
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(
buffer.read(cx).snapshot(cx),
buffer_edits.consume().into_inner(),
cx,
)
});
let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, _) = inlay_map.sync(
buffer.read(cx).snapshot(cx),
buffer_edits.consume().into_inner(),
);
assert_eq!(inlay_snapshot.text(), "abxyDzefghi");
let (inlay_snapshot, _) = inlay_map.splice(
@@ -1302,16 +1262,10 @@ mod tests {
// Edits ending where the inlay starts should not move it if it has a left bias.
buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx));
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(
buffer.read(cx).snapshot(cx),
buffer_edits.consume().into_inner(),
cx,
)
});
let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, _) = inlay_map.sync(
buffer.read(cx).snapshot(cx),
buffer_edits.consume().into_inner(),
);
assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
assert_eq!(
@@ -1496,8 +1450,7 @@ mod tests {
#[gpui::test]
fn test_inlay_buffer_rows(cx: &mut AppContext) {
let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx);
let (_diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_map_snapshot);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi");
let mut next_inlay_id = 0;
@@ -1523,10 +1476,7 @@ mod tests {
);
assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
assert_eq!(
inlay_snapshot
.row_infos(0)
.map(|r| r.buffer_row)
.collect::<Vec<_>>(),
inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
vec![Some(0), None, Some(1), None, None, Some(2)]
);
}
@@ -1551,8 +1501,7 @@ mod tests {
let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
let mut next_inlay_id = 0;
log::info!("buffer text: {:?}", buffer_snapshot.text());
let (diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(diff_map_snapshot.clone());
let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
for _ in 0..operations {
let mut inlay_edits = Patch::default();
@@ -1575,12 +1524,8 @@ mod tests {
}),
};
let (new_diff_snapshot, new_diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer.read(cx).snapshot(cx), buffer_edits, cx)
});
let (new_inlay_snapshot, new_inlay_edits) =
inlay_map.sync(new_diff_snapshot, new_diff_edits);
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
inlay_snapshot = new_inlay_snapshot;
inlay_edits = inlay_edits.compose(new_inlay_edits);
@@ -1602,7 +1547,7 @@ mod tests {
}
assert_eq!(inlay_snapshot.text(), expected_text.to_string());
let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
assert_eq!(
expected_buffer_rows.len() as u32,
expected_text.max_point().row + 1
@@ -1610,7 +1555,7 @@ mod tests {
for row_start in 0..expected_buffer_rows.len() {
assert_eq!(
inlay_snapshot
.row_infos(row_start as u32)
.buffer_rows(row_start as u32)
.collect::<Vec<_>>(),
&expected_buffer_rows[row_start..],
"incorrect buffer rows starting at {}",
@@ -1728,14 +1673,14 @@ mod tests {
assert_eq!(expected_text.len(), inlay_snapshot.len().0);
let mut buffer_point = Point::default();
let mut inlay_point = inlay_snapshot.make_inlay_point(buffer_point);
let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
let mut buffer_chars = buffer_snapshot.chars_at(0);
loop {
// Ensure conversion from buffer coordinates to inlay coordinates
// is consistent.
let buffer_offset = buffer_snapshot.point_to_offset(buffer_point);
assert_eq!(
inlay_snapshot.to_point(inlay_snapshot.make_inlay_offset(buffer_offset)),
inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)),
inlay_point
);
@@ -1762,7 +1707,7 @@ mod tests {
}
// Ensure that moving forward in the buffer always moves the inlay point forward as well.
let new_inlay_point = inlay_snapshot.make_inlay_point(buffer_point);
let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
assert!(new_inlay_point > inlay_point);
inlay_point = new_inlay_point;
} else {
@@ -1822,7 +1767,7 @@ mod tests {
// Ensure the clipped points are at valid buffer locations.
assert_eq!(
inlay_snapshot
.to_inlay_point(inlay_snapshot.to_diff_point(clipped_left_point)),
.to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
clipped_left_point,
"to_buffer_point({:?}) = {:?}",
clipped_left_point,
@@ -1830,7 +1775,7 @@ mod tests {
);
assert_eq!(
inlay_snapshot
.to_inlay_point(inlay_snapshot.to_diff_point(clipped_right_point)),
.to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
clipped_right_point,
"to_buffer_point({:?}) = {:?}",
clipped_right_point,

View File

@@ -164,7 +164,7 @@ pub struct TabSnapshot {
impl TabSnapshot {
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
self.fold_snapshot.inlay_snapshot.buffer()
&self.fold_snapshot.inlay_snapshot.buffer
}
pub fn line_len(&self, row: u32) -> u32 {
@@ -272,8 +272,8 @@ impl TabSnapshot {
}
}
pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> {
self.fold_snapshot.row_infos(row)
pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
self.fold_snapshot.buffer_rows(row)
}
#[cfg(test)]
@@ -317,7 +317,8 @@ impl TabSnapshot {
}
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
let fold_point = self.fold_snapshot.make_fold_point(point, bias);
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
self.to_tab_point(fold_point)
}
@@ -601,7 +602,7 @@ impl<'a> Iterator for TabChunks<'a> {
mod tests {
use super::*;
use crate::{
display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap},
display_map::{fold_map::FoldMap, inlay_map::InlayMap},
MultiBuffer,
};
use rand::{prelude::StdRng, Rng};
@@ -609,8 +610,8 @@ mod tests {
#[gpui::test]
fn test_expand_tabs(cx: &mut gpui::AppContext) {
let buffer = MultiBuffer::build_simple("", cx);
let (_, diff_snapshot) = DiffMap::new(buffer, cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
@@ -626,8 +627,8 @@ mod tests {
let output = "A BC DEF G HI J K L M";
let buffer = MultiBuffer::build_simple(input, cx);
let (_, diff_snapshot) = DiffMap::new(buffer, cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
@@ -673,8 +674,8 @@ mod tests {
let input = "abcdefg⋯hij";
let buffer = MultiBuffer::build_simple(input, cx);
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
@@ -687,8 +688,8 @@ mod tests {
let input = "\t \thello";
let buffer = MultiBuffer::build_simple(input, cx);
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (_, inlay_snapshot) = InlayMap::new(diff_snapshot);
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
@@ -748,11 +749,8 @@ mod tests {
let buffer_snapshot = buffer.read(cx).snapshot(cx);
log::info!("Buffer text: {:?}", buffer_snapshot.text());
let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone());
log::info!("DiffMap text: {:?}", diff_snapshot.text());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone());
fold_map.randomly_mutate(&mut rng);
let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]);

View File

@@ -1,7 +1,5 @@
use crate::RowInfo;
use super::{
fold_map::FoldRows,
fold_map::FoldBufferRows,
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
Highlights,
};
@@ -62,16 +60,16 @@ pub struct WrapChunks<'a> {
}
#[derive(Clone)]
pub struct WrapRows<'a> {
input_buffer_rows: FoldRows<'a>,
input_buffer_row: RowInfo,
pub struct WrapBufferRows<'a> {
input_buffer_rows: FoldBufferRows<'a>,
input_buffer_row: Option<u32>,
output_row: u32,
soft_wrapped: bool,
max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
}
impl<'a> WrapRows<'a> {
impl<'a> WrapBufferRows<'a> {
pub(crate) fn seek(&mut self, start_row: u32) {
self.transforms
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
@@ -719,7 +717,7 @@ impl WrapSnapshot {
self.transforms.summary().output.longest_row
}
pub fn row_infos(&self, start_row: u32) -> WrapRows {
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
let mut input_row = transforms.start().1.row();
@@ -727,9 +725,9 @@ impl WrapSnapshot {
input_row += start_row - transforms.start().0.row();
}
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
let input_buffer_row = input_buffer_rows.next().unwrap();
WrapRows {
WrapBufferRows {
transforms,
input_buffer_row,
input_buffer_rows,
@@ -849,7 +847,7 @@ impl WrapSnapshot {
}
let text = language::Rope::from(self.text().as_str());
let mut input_buffer_rows = self.tab_snapshot.rows(0);
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
let mut expected_buffer_rows = Vec::new();
let mut prev_tab_row = 0;
for display_row in 0..=self.max_point().row() {
@@ -857,7 +855,7 @@ impl WrapSnapshot {
if tab_point.row() == prev_tab_row && display_row != 0 {
expected_buffer_rows.push(None);
} else {
expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
}
prev_tab_row = tab_point.row();
@@ -866,8 +864,7 @@ impl WrapSnapshot {
for start_display_row in 0..expected_buffer_rows.len() {
assert_eq!(
self.row_infos(start_display_row as u32)
.map(|row_info| row_info.buffer_row)
self.buffer_rows(start_display_row as u32)
.collect::<Vec<_>>(),
&expected_buffer_rows[start_display_row..],
"invalid buffer_rows({}..)",
@@ -961,8 +958,8 @@ impl<'a> Iterator for WrapChunks<'a> {
}
}
impl<'a> Iterator for WrapRows<'a> {
type Item = RowInfo;
impl<'a> Iterator for WrapBufferRows<'a> {
type Item = Option<u32>;
fn next(&mut self) -> Option<Self::Item> {
if self.output_row > self.max_output_row {
@@ -982,11 +979,7 @@ impl<'a> Iterator for WrapRows<'a> {
self.soft_wrapped = true;
}
Some(if soft_wrapped {
RowInfo::default()
} else {
buffer_row
})
Some(if soft_wrapped { None } else { buffer_row })
}
}
@@ -1168,7 +1161,7 @@ fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
mod tests {
use super::*;
use crate::{
display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
MultiBuffer,
};
use gpui::{font, px, test::observe};
@@ -1216,11 +1209,9 @@ mod tests {
});
let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
log::info!("Buffer text: {:?}", buffer_snapshot.text());
let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx));
log::info!("DiffMap text: {:?}", diff_snapshot.text());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot);
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
log::info!("FoldMap text: {:?}", fold_snapshot.text());
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
let tabs_snapshot = tab_map.set_max_expansion_column(32);
@@ -1302,10 +1293,8 @@ mod tests {
}
log::info!("Buffer text: {:?}", buffer_snapshot.text());
let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| {
diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx)
});
let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits);
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
log::info!("FoldMap text: {:?}", fold_snapshot.text());

View File

@@ -89,7 +89,7 @@ use gpui::{
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
pub(crate) use hunk_diff::HoveredHunk;
use hunk_diff::{diff_hunk_to_display, DiffMap};
use hunk_diff::{diff_hunk_to_display, DiffMap, DiffMapSnapshot};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion::Direction;
@@ -121,8 +121,8 @@ use lsp::{
use movement::TextLayoutDetails;
pub use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
ToOffset, ToPoint,
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
ToPoint,
};
use multi_buffer::{
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
@@ -725,6 +725,7 @@ pub struct EditorSnapshot {
git_blame_gutter_max_author_length: Option<usize>,
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
diff_map: DiffMapSnapshot,
is_focused: bool,
scroll_anchor: ScrollAnchor,
ongoing_scroll: OngoingScroll,
@@ -1204,12 +1205,7 @@ impl Editor {
let mut code_action_providers = Vec::new();
if let Some(project) = project.clone() {
get_unstaged_changes_for_buffers(
&project,
buffer.read(cx).all_buffers(),
display_map.clone(),
cx,
);
get_unstaged_changes_for_buffers(&project, buffer.read(cx).all_buffers(), cx);
code_action_providers.push(Rc::new(project) as Rc<_>);
}
@@ -1572,6 +1568,7 @@ impl Editor {
scroll_anchor: self.scroll_manager.anchor(),
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
placeholder_text: self.placeholder_text.clone(),
diff_map: self.diff_map.snapshot(),
is_focused: self.focus_handle.is_focused(cx),
current_line_highlight: self
.current_line_highlight
@@ -2476,7 +2473,7 @@ impl Editor {
}
if self.hide_context_menu(cx).is_some() {
if self.has_active_inline_completion() {
if self.show_inline_completions_in_menu(cx) && self.has_active_inline_completion() {
self.update_visible_inline_completion(cx);
}
return true;
@@ -2859,6 +2856,7 @@ impl Editor {
);
}
let had_active_inline_completion = this.has_active_inline_completion();
this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
s.select(new_selections)
});
@@ -2879,7 +2877,9 @@ impl Editor {
this.show_signature_help(&ShowSignatureHelp, cx);
}
this.trigger_completion_on_input(&text, true, cx);
let trigger_in_words =
this.show_inline_completions_in_menu(cx) || !had_active_inline_completion;
this.trigger_completion_on_input(&text, trigger_in_words, cx);
linked_editing_ranges::refresh_linked_ranges(this, cx);
this.refresh_inline_completion(true, false, cx);
});
@@ -3685,10 +3685,6 @@ impl Editor {
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
let aside_was_displayed = match self.context_menu.borrow().deref() {
Some(CodeContextMenu::Completions(menu)) => menu.aside_was_displayed.get(),
_ => false,
};
let trigger_kind = match &options.trigger {
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
CompletionTriggerKind::TRIGGER_CHARACTER
@@ -3723,7 +3719,6 @@ impl Editor {
position,
buffer.clone(),
completions.into(),
aside_was_displayed,
);
menu.filter(query.as_deref(), cx.background_executor().clone())
@@ -3749,9 +3744,12 @@ impl Editor {
let mut menu = menu.unwrap();
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
editor.hide_active_inline_completion(cx);
menu.show_inline_completion_hint(hint);
if editor.show_inline_completions_in_menu(cx) {
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
menu.show_inline_completion_hint(hint);
}
} else {
editor.discard_inline_completion(false, cx);
}
*editor.context_menu.borrow_mut() =
@@ -3760,9 +3758,14 @@ impl Editor {
cx.notify();
} else if editor.completion_tasks.len() <= 1 {
// If there are no more completion tasks and the last menu was
// empty, we should hide it. If it was already hidden, we should
// also show the copilot completion when available.
editor.hide_context_menu(cx);
// empty, we should hide it.
let was_hidden = editor.hide_context_menu(cx).is_none();
// If it was already hidden and we don't show inline
// completions in the menu, we should also show the
// inline-completion when available.
if was_hidden && editor.show_inline_completions_in_menu(cx) {
editor.update_visible_inline_completion(cx);
}
}
})?;
@@ -3816,7 +3819,9 @@ impl Editor {
return Some(Task::ready(Ok(())));
}
CompletionEntry::Match(mat) => {
self.discard_inline_completion(true, cx);
if self.show_inline_completions_in_menu(cx) {
self.discard_inline_completion(true, cx);
}
mat
}
};
@@ -4554,7 +4559,9 @@ impl Editor {
_: &AcceptInlineCompletion,
cx: &mut ViewContext<Self>,
) {
self.hide_context_menu(cx);
if self.show_inline_completions_in_menu(cx) {
self.hide_context_menu(cx);
}
let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
return;
@@ -4703,17 +4710,6 @@ impl Editor {
Some(active_inline_completion.completion)
}
fn hide_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_inline_completion) = self.active_inline_completion.as_ref() {
self.splice_inlays(
active_inline_completion.inlay_ids.clone(),
Default::default(),
cx,
);
self.clear_highlights::<InlineCompletionHighlight>(cx);
}
}
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
let selection = self.selections.newest_anchor();
let cursor = selection.head();
@@ -4721,7 +4717,11 @@ impl Editor {
let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
let excerpt_id = cursor.excerpt_id;
if !offset_selection.is_empty()
let completions_menu_has_precedence = !self.show_inline_completions_in_menu(cx)
&& (self.context_menu.borrow().is_some()
|| (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
if completions_menu_has_precedence
|| !offset_selection.is_empty()
|| self
.active_inline_completion
.as_ref()
@@ -4745,16 +4745,10 @@ impl Editor {
let edits = completion
.edits
.into_iter()
.map(|(range, new_text)| {
(
multibuffer
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap()
..multibuffer
.anchor_in_excerpt(excerpt_id, range.end)
.unwrap(),
new_text,
)
.flat_map(|(range, new_text)| {
let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
Some((start..end, new_text))
})
.collect::<Vec<_>>();
if edits.is_empty() {
@@ -4785,34 +4779,32 @@ impl Editor {
invalidation_row_range = edit_start_row..cursor_row;
completion = InlineCompletion::Move(first_edit_start);
} else {
if !self.has_active_completions_menu() {
if edits
.iter()
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
{
let mut inlays = Vec::new();
for (range, new_text) in &edits {
let inlay = Inlay::inline_completion(
post_inc(&mut self.next_inlay_id),
range.start,
new_text.as_str(),
);
inlay_ids.push(inlay.id);
inlays.push(inlay);
}
self.splice_inlays(vec![], inlays, cx);
} else {
let background_color = cx.theme().status().deleted_background;
self.highlight_text::<InlineCompletionHighlight>(
edits.iter().map(|(range, _)| range.clone()).collect(),
HighlightStyle {
background_color: Some(background_color),
..Default::default()
},
cx,
if edits
.iter()
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
{
let mut inlays = Vec::new();
for (range, new_text) in &edits {
let inlay = Inlay::inline_completion(
post_inc(&mut self.next_inlay_id),
range.start,
new_text.as_str(),
);
inlay_ids.push(inlay.id);
inlays.push(inlay);
}
self.splice_inlays(vec![], inlays, cx);
} else {
let background_color = cx.theme().status().deleted_background;
self.highlight_text::<InlineCompletionHighlight>(
edits.iter().map(|(range, _)| range.clone()).collect(),
HighlightStyle {
background_color: Some(background_color),
..Default::default()
},
cx,
);
}
invalidation_row_range = edit_start_row..edit_end_row;
@@ -4832,7 +4824,7 @@ impl Editor {
invalidation_range,
});
if self.has_active_completions_menu() {
if self.show_inline_completions_in_menu(cx) && self.has_active_completions_menu() {
if let Some(hint) = self.inline_completion_menu_hint(cx) {
match self.context_menu.borrow_mut().as_mut() {
Some(CodeContextMenu::Completions(menu)) => {
@@ -4858,7 +4850,7 @@ impl Editor {
let text = match &self.active_inline_completion.as_ref()?.completion {
InlineCompletion::Edit(edits) => {
inline_completion_edit_text(&editor_snapshot, edits, cx)
inline_completion_edit_text(&editor_snapshot, edits, true, cx)
}
InlineCompletion::Move(target) => {
let target_point =
@@ -4883,6 +4875,13 @@ impl Editor {
Some(self.inline_completion_provider.as_ref()?.provider.clone())
}
fn show_inline_completions_in_menu(&self, cx: &AppContext) -> bool {
EditorSettings::get_global(cx).show_inline_completions_in_menu
&& self
.inline_completion_provider()
.map_or(false, |provider| provider.show_completions_in_menu())
}
fn render_code_actions_indicator(
&self,
_style: &EditorStyle,
@@ -5085,7 +5084,7 @@ impl Editor {
}))
}
#[cfg(any(test, feature = "test-support"))]
#[cfg(feature = "test-support")]
pub fn context_menu_visible(&self) -> bool {
self.context_menu
.borrow()
@@ -5121,12 +5120,27 @@ impl Editor {
) -> Option<AnyElement> {
self.context_menu.borrow().as_ref().and_then(|menu| {
if menu.visible() {
Some(menu.render(
Some(menu.render(style, max_height_in_lines, cx))
} else {
None
}
})
}
fn render_context_menu_aside(
&self,
style: &EditorStyle,
max_height: Pixels,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
self.context_menu.borrow().as_ref().and_then(|menu| {
if menu.visible() {
menu.render_aside(
style,
max_height_in_lines,
max_height,
self.workspace.as_ref().map(|(w, _)| w.clone()),
cx,
))
)
} else {
None
}
@@ -5136,7 +5150,11 @@ impl Editor {
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<CodeContextMenu> {
cx.notify();
self.completion_tasks.clear();
self.context_menu.borrow_mut().take()
let context_menu = self.context_menu.borrow_mut().take();
if context_menu.is_some() && !self.show_inline_completions_in_menu(cx) {
self.update_visible_inline_completion(cx);
}
context_menu
}
fn show_snippet_choices(
@@ -5425,7 +5443,8 @@ impl Editor {
if end_point == start_point {
let offset = text::ToOffset::to_offset(&range.start, &snapshot)
.saturating_sub(1);
start_point = TP::to_point(&offset, &snapshot);
start_point =
snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
};
(start_point..end_point, empty_str.clone())
@@ -5921,9 +5940,10 @@ impl Editor {
pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext<Self>) {
let mut revert_changes = HashMap::default();
let snapshot = self.snapshot(cx);
for hunk in snapshot
.hunks_for_ranges(Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter())
{
for hunk in hunks_for_ranges(
Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter(),
&snapshot,
) {
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
}
if !revert_changes.is_empty() {
@@ -5941,12 +5961,7 @@ impl Editor {
}
pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
let selections = self.selections.all(cx).into_iter().map(|s| s.range());
let mut revert_changes = HashMap::default();
let snapshot = self.snapshot(cx);
for hunk in &snapshot.hunks_for_ranges(selections) {
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
}
let revert_changes = self.gather_revert_changes(&self.selections.all(cx), cx);
if !revert_changes.is_empty() {
self.transact(cx, |editor, cx| {
editor.revert(revert_changes, cx);
@@ -5983,18 +5998,28 @@ impl Editor {
}
}
fn gather_revert_changes(
&mut self,
selections: &[Selection<Point>],
cx: &mut ViewContext<'_, Editor>,
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
let mut revert_changes = HashMap::default();
let snapshot = self.snapshot(cx);
for hunk in hunks_for_selections(&snapshot, selections) {
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
}
revert_changes
}
pub fn prepare_revert_change(
&mut self,
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
hunk: &MultiBufferDiffHunk,
cx: &mut WindowContext,
cx: &AppContext,
) -> Option<()> {
let change_set = self
.display_map
.read(cx)
.diff_base_for(hunk.buffer_id, cx)?;
let buffer = self.buffer.read(cx).buffer(hunk.buffer_id)?;
let buffer = buffer.read(cx);
let change_set = &self.diff_map.diff_bases.get(&hunk.buffer_id)?.change_set;
let original_text = change_set
.read(cx)
.base_text
@@ -9210,8 +9235,9 @@ impl Editor {
snapshot,
position,
ix > 0,
snapshot.diff_hunks_in_range(
snapshot.diff_map.diff_hunks_in_range(
position + Point::new(1, 0)..snapshot.buffer_snapshot.max_point(),
&snapshot.buffer_snapshot,
),
cx,
) {
@@ -9241,7 +9267,9 @@ impl Editor {
snapshot,
position,
ix > 0,
snapshot.diff_hunks_in_range_rev(Point::zero()..position),
snapshot
.diff_map
.diff_hunks_in_range_rev(Point::zero()..position, &snapshot.buffer_snapshot),
cx,
) {
return Some(hunk);
@@ -10371,7 +10399,7 @@ impl Editor {
self.end_transaction_at(Instant::now(), cx)
}
fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
pub fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
self.end_selection(cx);
if let Some(tx_id) = self
.buffer
@@ -10385,7 +10413,7 @@ impl Editor {
}
}
fn end_transaction_at(
pub fn end_transaction_at(
&mut self,
now: Instant,
cx: &mut ViewContext<Self>,
@@ -10924,29 +10952,6 @@ impl Editor {
self.display_map.read(cx).fold_placeholder.clone()
}
pub fn set_expand_all_diff_hunks(&mut self, cx: &mut AppContext) {
self.display_map.update(cx, |display_map, cx| {
display_map.set_all_hunks_expanded(cx);
});
}
pub fn expand_all_diff_hunks(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
self.display_map.update(cx, |display_map, cx| {
display_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
});
}
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
self.display_map.update(cx, |display_map, cx| {
if display_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
display_map.collapse_diff_hunks(ranges, cx)
} else {
display_map.expand_diff_hunks(ranges, cx)
}
})
}
pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext<Self>) {
if hovered != self.gutter_hovered {
self.gutter_hovered = hovered;
@@ -12250,12 +12255,7 @@ impl Editor {
let buffer_id = buffer.read(cx).remote_id();
if !self.diff_map.diff_bases.contains_key(&buffer_id) {
if let Some(project) = &self.project {
get_unstaged_changes_for_buffers(
project,
[buffer.clone()],
self.display_map.clone(),
cx,
);
get_unstaged_changes_for_buffers(project, [buffer.clone()], cx);
}
}
cx.emit(EditorEvent::ExcerptsAdded {
@@ -12959,8 +12959,7 @@ impl Editor {
fn get_unstaged_changes_for_buffers(
project: &Model<Project>,
buffers: impl IntoIterator<Item = Model<Buffer>>,
display_map: Model<DisplayMap>,
cx: &mut AppContext,
cx: &mut ViewContext<Editor>,
) {
let mut tasks = Vec::new();
project.update(cx, |project, cx| {
@@ -12968,17 +12967,16 @@ fn get_unstaged_changes_for_buffers(
tasks.push(project.open_unstaged_changes(buffer.clone(), cx))
}
});
cx.spawn(|mut cx| async move {
cx.spawn(|this, mut cx| async move {
let change_sets = futures::future::join_all(tasks).await;
display_map
.update(&mut cx, |display_map, cx| {
for change_set in change_sets {
if let Some(change_set) = change_set.log_err() {
display_map.add_change_set(change_set, cx);
}
this.update(&mut cx, |this, cx| {
for change_set in change_sets {
if let Some(change_set) = change_set.log_err() {
this.diff_map.add_change_set(change_set, cx);
}
})
.ok();
}
})
.ok();
})
.detach();
}
@@ -13266,6 +13264,56 @@ fn test_wrap_with_prefix() {
);
}
fn hunks_for_selections(
snapshot: &EditorSnapshot,
selections: &[Selection<Point>],
) -> Vec<MultiBufferDiffHunk> {
hunks_for_ranges(
selections.iter().map(|selection| selection.range()),
snapshot,
)
}
pub fn hunks_for_ranges(
ranges: impl Iterator<Item = Range<Point>>,
snapshot: &EditorSnapshot,
) -> Vec<MultiBufferDiffHunk> {
let mut hunks = Vec::new();
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
HashMap::default();
for query_range in ranges {
let query_rows =
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
for hunk in snapshot.diff_map.diff_hunks_in_range(
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
&snapshot.buffer_snapshot,
) {
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
// when the caret is just above or just below the deleted hunk.
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
let related_to_selection = if allow_adjacent {
hunk.row_range.overlaps(&query_rows)
|| hunk.row_range.start == query_rows.end
|| hunk.row_range.end == query_rows.start
} else {
hunk.row_range.overlaps(&query_rows)
};
if related_to_selection {
if !processed_buffer_rows
.entry(hunk.buffer_id)
.or_default()
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
{
continue;
}
hunks.push(hunk);
}
}
}
hunks
}
pub trait CollaborationHub {
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator>;
fn user_participant_indices<'a>(
@@ -13814,45 +13862,6 @@ impl EditorSnapshot {
})
}
pub fn hunks_for_ranges(
&self,
ranges: impl Iterator<Item = Range<Point>>,
) -> Vec<MultiBufferDiffHunk> {
let mut hunks = Vec::new();
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
HashMap::default();
for query_range in ranges {
let query_rows =
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
for hunk in self.diff_snapshot().diff_hunks_in_range(
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
) {
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
// when the caret is just above or just below the deleted hunk.
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
let related_to_selection = if allow_adjacent {
hunk.row_range.overlaps(&query_rows)
|| hunk.row_range.start == query_rows.end
|| hunk.row_range.end == query_rows.start
} else {
hunk.row_range.overlaps(&query_rows)
};
if related_to_selection {
if !processed_buffer_rows
.entry(hunk.buffer_id)
.or_default()
.insert(hunk.buffer_range.start..hunk.buffer_range.end)
{
continue;
}
hunks.push(hunk);
}
}
}
hunks
}
pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
self.display_snapshot.buffer_snapshot.language_at(position)
}
@@ -14607,6 +14616,7 @@ pub fn diagnostic_block_renderer(
fn inline_completion_edit_text(
editor_snapshot: &EditorSnapshot,
edits: &Vec<(Range<Anchor>, String)>,
include_deletions: bool,
cx: &WindowContext,
) -> InlineCompletionText {
let edit_start = edits
@@ -14630,12 +14640,24 @@ fn inline_completion_edit_text(
offset = old_offset_range.end;
let start = text.len();
text.push_str(new_text);
let color = if include_deletions && new_text.is_empty() {
text.extend(
editor_snapshot
.buffer_snapshot
.chunks(old_offset_range.start..offset, false)
.map(|chunk| chunk.text),
);
cx.theme().status().deleted_background
} else {
text.push_str(new_text);
cx.theme().status().created_background
};
let end = text.len();
highlights.push((
start..end,
HighlightStyle {
background_color: Some(cx.theme().status().created_background),
background_color: Some(color),
..Default::default()
},
));

View File

@@ -35,6 +35,7 @@ pub struct EditorSettings {
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
pub jupyter: Jupyter,
pub show_inline_completions_in_menu: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -300,6 +301,12 @@ pub struct EditorSettingsContent {
/// Default: false
pub show_signature_help_after_edits: Option<bool>,
/// Whether to show the inline completions next to the completions provided by a language server.
/// Only has an effect if inline completion provider supports it.
///
/// Default: true
pub show_inline_completions_in_menu: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<JupyterContent>,
}

View File

@@ -3359,7 +3359,8 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
let snapshot = editor.snapshot(cx);
assert_eq!(
snapshot
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
.diff_map
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
.collect::<Vec<_>>(),
Vec::new(),
"Should not have any diffs for files with custom newlines"
@@ -11654,9 +11655,7 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
cx,
)
});
editor.display_map.update(cx, |display_map, cx| {
display_map.add_change_set(change_set, cx)
});
editor.diff_map.add_change_set(change_set, cx)
}
});
cx.executor().run_until_parked();
@@ -12074,34 +12073,12 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
);
cx.update_editor(|editor, cx| {
for _ in 0..2 {
for _ in 0..3 {
editor.go_to_next_hunk(&GoToHunk, cx);
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
}
});
executor.run_until_parked();
cx.assert_state_with_diff(
r#"
- use some::mod;
+ ˇuse some::modified;
fn main() {
- println!("hello");
+ println!("hello there");
+ println!("around the");
println!("world");
}
"#
.unindent(),
);
cx.update_editor(|editor, cx| {
editor.go_to_next_hunk(&GoToHunk, cx);
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
r#"
- use some::mod;
@@ -12187,7 +12164,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
executor.run_until_parked();
cx.update_editor(|editor, cx| {
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
@@ -12232,7 +12209,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
);
cx.update_editor(|editor, cx| {
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
cx.assert_state_with_diff(
@@ -12315,7 +12292,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
executor.run_until_parked();
cx.update_editor(|editor, cx| {
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -12508,9 +12485,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
cx,
)
});
editor.display_map.update(cx, |display_map, cx| {
display_map.add_change_set(change_set, cx)
});
editor.diff_map.add_change_set(change_set, cx)
}
})
.unwrap();
@@ -12620,16 +12595,14 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
let buffer = buffer.read(cx).text_snapshot();
let change_set = cx
.new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
editor.display_map.update(cx, |display_map, cx| {
display_map.add_change_set(change_set, cx)
})
editor.diff_map.add_change_set(change_set, cx)
})
.unwrap();
let mut cx = EditorTestContext::for_editor(editor, cx).await;
cx.run_until_parked();
cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx));
cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
cx.executor().run_until_parked();
cx.assert_state_with_diff(
@@ -12693,7 +12666,7 @@ async fn test_edits_around_expanded_insertion_hunks(
executor.run_until_parked();
cx.update_editor(|editor, cx| {
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -12712,7 +12685,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12735,7 +12708,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12759,7 +12732,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12784,7 +12757,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12810,7 +12783,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12831,7 +12804,7 @@ async fn test_edits_around_expanded_insertion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
}
@@ -12884,7 +12857,7 @@ async fn test_edits_around_expanded_deletion_hunks(
executor.run_until_parked();
cx.update_editor(|editor, cx| {
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -12903,7 +12876,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12926,7 +12899,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12949,7 +12922,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
@@ -12973,7 +12946,7 @@ async fn test_edits_around_expanded_deletion_hunks(
println!("world");
}
"#
"#
.unindent(),
);
}
@@ -13026,7 +12999,7 @@ async fn test_edit_after_expanded_modification_hunk(
cx.set_diff_base(&diff_base);
executor.run_until_parked();
cx.update_editor(|editor, cx| {
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
});
executor.run_until_parked();
@@ -14394,7 +14367,7 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
fn test_inline_completion_text(cx: &mut TestAppContext) {
init_test(cx, |_| {});
// Test case 1: Simple insertion
// Simple insertion
{
let window = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
@@ -14410,7 +14383,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
let edits = vec![(edit_range, " beautiful".to_string())];
let InlineCompletionText::Edit { text, highlights } =
inline_completion_edit_text(&snapshot, &edits, cx)
inline_completion_edit_text(&snapshot, &edits, false, cx)
else {
panic!("Failed to generate inline completion text");
};
@@ -14426,7 +14399,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
.unwrap();
}
// Test case 2: Replacement
// Replacement
{
let window = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("This is a test.", cx);
@@ -14444,7 +14417,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
)];
let InlineCompletionText::Edit { text, highlights } =
inline_completion_edit_text(&snapshot, &edits, cx)
inline_completion_edit_text(&snapshot, &edits, false, cx)
else {
panic!("Failed to generate inline completion text");
};
@@ -14460,7 +14433,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
.unwrap();
}
// Test case 3: Multiple edits
// Multiple edits
{
let window = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
@@ -14485,7 +14458,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
];
let InlineCompletionText::Edit { text, highlights } =
inline_completion_edit_text(&snapshot, &edits, cx)
inline_completion_edit_text(&snapshot, &edits, false, cx)
else {
panic!("Failed to generate inline completion text");
};
@@ -14506,7 +14479,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
.unwrap();
}
// Test case 4: Multiple lines with edits
// Multiple lines with edits
{
let window = cx.add_window(|cx| {
let buffer =
@@ -14537,7 +14510,7 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
];
let InlineCompletionText::Edit { text, highlights } =
inline_completion_edit_text(&snapshot, &edits, cx)
inline_completion_edit_text(&snapshot, &edits, false, cx)
else {
panic!("Failed to generate inline completion text");
};
@@ -14559,6 +14532,75 @@ fn test_inline_completion_text(cx: &mut TestAppContext) {
}
}
#[gpui::test]
fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
init_test(cx, |_| {});
// Deletion
{
let window = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
Editor::new(EditorMode::Full, buffer, None, true, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
window
.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 5))
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 11));
let edits = vec![(edit_range, "".to_string())];
let InlineCompletionText::Edit { text, highlights } =
inline_completion_edit_text(&snapshot, &edits, true, cx)
else {
panic!("Failed to generate inline completion text");
};
assert_eq!(text, "Hello, world!");
assert_eq!(highlights.len(), 1);
assert_eq!(highlights[0].0, 5..11);
assert_eq!(
highlights[0].1.background_color,
Some(cx.theme().status().deleted_background)
);
})
.unwrap();
}
// Insertion
{
let window = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("Hello, world!", cx);
Editor::new(EditorMode::Full, buffer, None, true, cx)
});
let cx = &mut VisualTestContext::from_window(*window, cx);
window
.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let edit_range = snapshot.buffer_snapshot.anchor_after(Point::new(0, 6))
..snapshot.buffer_snapshot.anchor_before(Point::new(0, 6));
let edits = vec![(edit_range, " digital".to_string())];
let InlineCompletionText::Edit { text, highlights } =
inline_completion_edit_text(&snapshot, &edits, true, cx)
else {
panic!("Failed to generate inline completion text");
};
assert_eq!(text, "Hello, digital world!");
assert_eq!(highlights.len(), 1);
assert_eq!(highlights[0].0, 6..14);
assert_eq!(
highlights[0].1.background_color,
Some(cx.theme().status().created_background)
);
})
.unwrap();
}
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point
@@ -14726,7 +14768,8 @@ fn assert_hunk_revert(
let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
let reverted_hunk_statuses = snapshot
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
.diff_map
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
.map(|hunk| hunk_status(&hunk))
.collect::<Vec<_>>();

View File

@@ -1,6 +1,6 @@
use crate::{
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
code_context_menus::CodeActionsMenu,
code_context_menus::{CodeActionsMenu, MAX_COMPLETIONS_ASIDE_WIDTH},
display_map::{
Block, BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint,
},
@@ -21,8 +21,8 @@ use crate::{
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowInfo, RowRangeExt, SelectPhase,
Selection, SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use client::ParticipantIndex;
@@ -381,7 +381,7 @@ impl EditorElement {
register_action(view, cx, Editor::toggle_git_blame);
register_action(view, cx, Editor::toggle_git_blame_inline);
register_action(view, cx, Editor::toggle_hunk_diff);
register_action(view, cx, Editor::expand_all_diff_hunks);
register_action(view, cx, Editor::expand_all_hunk_diffs);
register_action(view, cx, |editor, action, cx| {
if let Some(task) = editor.format(action, cx) {
task.detach_and_log_err(cx);
@@ -1196,7 +1196,7 @@ impl EditorElement {
let editor = self.editor.read(cx);
let is_singleton = editor.is_singleton(cx);
// Git
(is_singleton && scrollbar_settings.git_diff && snapshot.has_diff_hunks())
(is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty())
||
// Buffer Search Results
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
@@ -1461,10 +1461,10 @@ impl EditorElement {
.unwrap_err();
let mut expanded_hunks = expanded_hunks[expanded_hunks_start_ix..].iter().peekable();
let mut display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)> = snapshot
.display_snapshot
.diff_snapshot()
.diff_hunks_in_range(buffer_start..buffer_end)
let mut display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)> = editor
.diff_map
.snapshot
.diff_hunks_in_range(buffer_start..buffer_end, &buffer_snapshot)
.filter_map(|hunk| {
let display_hunk = diff_hunk_to_display(&hunk, snapshot);
@@ -1532,7 +1532,7 @@ impl EditorElement {
fn layout_inline_blame(
&self,
display_row: DisplayRow,
row_info: &RowInfo,
display_snapshot: &DisplaySnapshot,
line_layout: &LineWithInvisibles,
crease_trailer: Option<&CreaseTrailerLayout>,
em_width: Pixels,
@@ -1555,10 +1555,13 @@ impl EditorElement {
.as_ref()
.map(|(w, _)| w.clone());
let display_point = DisplayPoint::new(display_row, 0);
let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
let blame = self.editor.read(cx).blame.clone()?;
let blame_entry = blame
.update(cx, |blame, cx| {
blame.blame_for_rows(&[*row_info], cx).next()
blame.blame_for_rows([Some(buffer_row)], cx).next()
})
.flatten()?;
@@ -1598,7 +1601,7 @@ impl EditorElement {
#[allow(clippy::too_many_arguments)]
fn layout_blame_entries(
&self,
buffer_rows: &[RowInfo],
buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
em_width: Pixels,
scroll_position: gpui::Point<f32>,
line_height: Pixels,
@@ -1935,7 +1938,7 @@ impl EditorElement {
let end = rows.end.max(relative_to);
let buffer_rows = snapshot
.row_infos(start)
.buffer_rows(start)
.take(1 + end.minus(start) as usize)
.collect::<Vec<_>>();
@@ -1943,7 +1946,7 @@ impl EditorElement {
let mut delta = 1;
let mut i = head_idx + 1;
while i < buffer_rows.len() as u32 {
if buffer_rows[i as usize].buffer_row.is_some() {
if buffer_rows[i as usize].is_some() {
if rows.contains(&DisplayRow(i + start.0)) {
relative_rows.insert(DisplayRow(i + start.0), delta);
}
@@ -1953,13 +1956,13 @@ impl EditorElement {
}
delta = 1;
i = head_idx.min(buffer_rows.len() as u32 - 1);
while i > 0 && buffer_rows[i as usize].buffer_row.is_none() {
while i > 0 && buffer_rows[i as usize].is_none() {
i -= 1;
}
while i > 0 {
i -= 1;
if buffer_rows[i as usize].buffer_row.is_some() {
if buffer_rows[i as usize].is_some() {
if rows.contains(&DisplayRow(i + start.0)) {
relative_rows.insert(DisplayRow(i + start.0), delta);
}
@@ -1973,7 +1976,7 @@ impl EditorElement {
fn layout_line_numbers(
&self,
rows: Range<DisplayRow>,
buffer_rows: &[RowInfo],
buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
active_rows: &BTreeMap<DisplayRow, bool>,
newest_selection_head: Option<DisplayPoint>,
snapshot: &EditorSnapshot,
@@ -2015,7 +2018,8 @@ impl EditorElement {
buffer_rows
.into_iter()
.enumerate()
.map(|(ix, row_info)| {
.map(|(ix, multibuffer_row)| {
let multibuffer_row = multibuffer_row?;
let display_row = DisplayRow(rows.start.0 + ix as u32);
let color = if active_rows.contains_key(&display_row) {
cx.theme().colors().editor_active_line_number
@@ -2023,7 +2027,7 @@ impl EditorElement {
cx.theme().colors().editor_line_number
};
line_number.clear();
let default_number = row_info.buffer_row? + 1;
let default_number = multibuffer_row.0 + 1;
let number = relative_rows
.get(&DisplayRow(ix as u32 + rows.start.0))
.unwrap_or(&default_number);
@@ -2048,7 +2052,7 @@ impl EditorElement {
fn layout_crease_toggles(
&self,
rows: Range<DisplayRow>,
row_infos: &[RowInfo],
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
active_rows: &BTreeMap<DisplayRow, bool>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
@@ -2057,21 +2061,22 @@ impl EditorElement {
&& snapshot.mode == EditorMode::Full
&& self.editor.read(cx).is_singleton(cx);
if include_fold_statuses {
row_infos
buffer_rows
.into_iter()
.enumerate()
.map(|(ix, info)| {
let row = info.buffer_row?;
let display_row = DisplayRow(rows.start.0 + ix as u32);
let active = active_rows.contains_key(&display_row);
// todo(max): Retrieve the multibuffer row correctly
snapshot.render_crease_toggle(
MultiBufferRow(row),
active,
self.editor.clone(),
cx,
)
.map(|(ix, row)| {
if let Some(multibuffer_row) = row {
let display_row = DisplayRow(rows.start.0 + ix as u32);
let active = active_rows.contains_key(&display_row);
snapshot.render_crease_toggle(
multibuffer_row,
active,
self.editor.clone(),
cx,
)
} else {
None
}
})
.collect()
} else {
@@ -2081,16 +2086,15 @@ impl EditorElement {
fn layout_crease_trailers(
&self,
buffer_rows: impl IntoIterator<Item = RowInfo>,
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
) -> Vec<Option<AnyElement>> {
buffer_rows
.into_iter()
.map(|row_info| {
// FIXME: These are not really MultiBufferRow?!
if let Some(row) = row_info.buffer_row {
snapshot.render_crease_trailer(MultiBufferRow(row), cx)
.map(|row| {
if let Some(multibuffer_row) = row {
snapshot.render_crease_trailer(multibuffer_row, cx)
} else {
None
}
@@ -2897,38 +2901,44 @@ impl EditorElement {
else {
return;
};
let target_offset = match context_menu_origin {
crate::ContextMenuOrigin::EditorPoint(display_point) => {
let cursor_row_layout =
&line_layouts[display_point.row().minus(start_row) as usize];
gpui::Point {
x: cursor_row_layout.x_for_index(display_point.column() as usize)
- scroll_pixel_position.x,
y: display_point.row().next_row().as_f32() * line_height
- scroll_pixel_position.y,
let target_position = content_origin
+ match context_menu_origin {
crate::ContextMenuOrigin::EditorPoint(display_point) => {
let cursor_row_layout =
&line_layouts[display_point.row().minus(start_row) as usize];
gpui::Point {
x: cmp::max(
px(0.),
cursor_row_layout.x_for_index(display_point.column() as usize)
- scroll_pixel_position.x,
),
y: cmp::max(
px(0.),
display_point.row().next_row().as_f32() * line_height
- scroll_pixel_position.y,
),
}
}
}
crate::ContextMenuOrigin::GutterIndicator(row) => {
// Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
// text field.
gpui::Point {
x: -gutter_overshoot,
y: row.next_row().as_f32() * line_height - scroll_pixel_position.y,
crate::ContextMenuOrigin::GutterIndicator(row) => {
// Context menu was spawned via a click on a gutter. Ensure it's a bit closer to the indicator than just a plain first column of the
// text field.
gpui::Point {
x: -gutter_overshoot,
y: row.next_row().as_f32() * line_height - scroll_pixel_position.y,
}
}
}
};
};
// If the context menu's max height won't fit below, then flip it above the line and display
// it in reverse order. If the available space above is less than below.
let unconstrained_max_height = line_height * 12. + POPOVER_Y_PADDING;
let min_height = line_height * 3. + POPOVER_Y_PADDING;
let target_position = content_origin + target_offset;
let y_overflows_below = target_position.y + unconstrained_max_height > text_hitbox.bottom();
let bottom_y_when_flipped = target_position.y - line_height;
let available_above = bottom_y_when_flipped - text_hitbox.top();
let available_below = text_hitbox.bottom() - target_position.y;
let y_overflows_below = unconstrained_max_height > available_below;
let mut y_is_flipped = y_overflows_below && available_above > available_below;
let mut max_height = cmp::min(
let mut height = cmp::min(
unconstrained_max_height,
if y_is_flipped {
available_above
@@ -2938,33 +2948,33 @@ impl EditorElement {
);
// If less than 3 lines fit within the text bounds, instead fit within the window.
if max_height < 3. * line_height {
if height < min_height {
let available_above = bottom_y_when_flipped;
let available_below = cx.viewport_size().height - target_position.y;
if available_below > 3. * line_height {
y_is_flipped = false;
max_height = min_height;
height = min_height;
} else if available_above > 3. * line_height {
y_is_flipped = true;
max_height = min_height;
height = min_height;
} else if available_above > available_below {
y_is_flipped = true;
max_height = available_above;
height = available_above;
} else {
y_is_flipped = false;
max_height = available_below;
height = available_below;
}
}
let max_height_in_lines = ((max_height - POPOVER_Y_PADDING) / line_height).floor() as u32;
let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
let Some(mut menu) = self.editor.update(cx, |editor, cx| {
let Some(mut menu_element) = self.editor.update(cx, |editor, cx| {
editor.render_context_menu(&self.style, max_height_in_lines, cx)
}) else {
return;
};
let menu_size = menu.layout_as_root(AvailableSpace::min_size(), cx);
let menu_size = menu_element.layout_as_root(AvailableSpace::min_size(), cx);
let menu_position = gpui::Point {
x: if target_position.x + menu_size.width > cx.viewport_size().width {
// Snap the right edge of the list to the right edge of the window if its horizontal bounds
@@ -2979,8 +2989,114 @@ impl EditorElement {
target_position.y
},
};
cx.defer_draw(menu_element, menu_position, 1);
cx.defer_draw(menu, menu_position, 1);
let aside_element = self.editor.update(cx, |editor, cx| {
editor.render_context_menu_aside(&self.style, unconstrained_max_height, cx)
});
if let Some(aside_element) = aside_element {
let menu_bounds = Bounds::new(menu_position, menu_size);
let max_menu_size = size(menu_size.width, unconstrained_max_height);
let max_menu_bounds = if y_is_flipped {
Bounds::new(
point(
menu_position.x,
bottom_y_when_flipped - max_menu_size.height,
),
max_menu_size,
)
} else {
Bounds::new(target_position, max_menu_size)
};
// Pad the target by 4 pixels to create a gap.
let mut extend_amount = Edges::all(px(4.));
// Extend to include the cursored line to avoid overlapping it.
if y_is_flipped {
extend_amount.bottom = line_height;
} else {
extend_amount.top = line_height;
}
self.layout_context_menu_aside(
text_hitbox,
y_is_flipped,
menu_position,
menu_bounds.extend(extend_amount),
max_menu_bounds.extend(extend_amount),
unconstrained_max_height,
aside_element,
cx,
);
}
}
#[allow(clippy::too_many_arguments)]
fn layout_context_menu_aside(
&self,
text_hitbox: &Hitbox,
y_is_flipped: bool,
menu_position: gpui::Point<Pixels>,
target_bounds: Bounds<Pixels>,
max_target_bounds: Bounds<Pixels>,
max_height: Pixels,
aside: AnyElement,
cx: &mut WindowContext,
) {
let mut aside = aside;
let actual_size = aside.layout_as_root(AvailableSpace::min_size(), cx);
// Snap to right side of window if it would overflow.
let aside_x = cmp::min(
menu_position.x,
cx.viewport_size().width - actual_size.width,
);
if aside_x < px(0.) {
// Not enough space, skip drawing.
return;
}
let top_position = point(aside_x, target_bounds.top() - actual_size.height);
let bottom_position = point(aside_x, target_bounds.bottom());
let right_position = point(target_bounds.right(), menu_position.y);
let fit_horizontally_within = |available: Edges<Pixels>, wanted: Size<Pixels>| {
// Prefer to fit to the right, then on the same side of the line as the menu, then on
// the other side of the line.
if wanted.width < available.right {
Some(right_position)
} else if !y_is_flipped && wanted.height < available.bottom {
Some(bottom_position)
} else if !y_is_flipped && wanted.height < available.top {
Some(top_position)
} else if y_is_flipped && wanted.height < available.top {
Some(top_position)
} else if y_is_flipped && wanted.height < available.bottom {
Some(bottom_position)
} else {
None
}
};
// Prefer choosing a direction using max sizes rather than actual size for stability.
let mut available = max_target_bounds.space_within(&text_hitbox.bounds);
let mut wanted = size(MAX_COMPLETIONS_ASIDE_WIDTH, max_height);
let aside_position = fit_horizontally_within(available, wanted)
.or_else(|| {
// Fallback: fit max size in window.
available = max_target_bounds
.space_within(&Bounds::new(Default::default(), cx.viewport_size()));
fit_horizontally_within(available, wanted)
})
.or_else(|| {
// Fallback: fit actual size in window.
wanted = actual_size;
fit_horizontally_within(available, wanted)
});
// Skip drawing if it doesn't fit anywhere.
if let Some(aside_position) = aside_position {
cx.defer_draw(aside, aside_position, 1);
}
}
#[allow(clippy::too_many_arguments)]
@@ -3105,7 +3221,7 @@ impl EditorElement {
}
let crate::InlineCompletionText::Edit { text, highlights } =
crate::inline_completion_edit_text(editor_snapshot, edits, cx)
crate::inline_completion_edit_text(editor_snapshot, edits, false, cx)
else {
return None;
};
@@ -4372,29 +4488,32 @@ impl EditorElement {
let max_point = snapshot.display_snapshot.buffer_snapshot.max_point();
let mut marker_quads = Vec::new();
if scrollbar_settings.git_diff {
let marker_row_ranges = snapshot.diff_hunks().map(|hunk| {
let start_display_row =
MultiBufferPoint::new(hunk.row_range.start.0, 0)
.to_display_point(&snapshot.display_snapshot)
.row();
let mut end_display_row =
MultiBufferPoint::new(hunk.row_range.end.0, 0)
.to_display_point(&snapshot.display_snapshot)
.row();
if end_display_row != start_display_row {
end_display_row.0 -= 1;
}
let color = match hunk_status(&hunk) {
DiffHunkStatus::Added => theme.status().created,
DiffHunkStatus::Modified => theme.status().modified,
DiffHunkStatus::Removed => theme.status().deleted,
};
ColoredRange {
start: start_display_row,
end: end_display_row,
color,
}
});
let marker_row_ranges = snapshot
.diff_map
.diff_hunks(&snapshot.buffer_snapshot)
.map(|hunk| {
let start_display_row =
MultiBufferPoint::new(hunk.row_range.start.0, 0)
.to_display_point(&snapshot.display_snapshot)
.row();
let mut end_display_row =
MultiBufferPoint::new(hunk.row_range.end.0, 0)
.to_display_point(&snapshot.display_snapshot)
.row();
if end_display_row != start_display_row {
end_display_row.0 -= 1;
}
let color = match hunk_status(&hunk) {
DiffHunkStatus::Added => theme.status().created,
DiffHunkStatus::Modified => theme.status().modified,
DiffHunkStatus::Removed => theme.status().deleted,
};
ColoredRange {
start: start_display_row,
end: end_display_row,
color,
}
});
marker_quads.extend(
scrollbar_layout
@@ -5813,15 +5932,12 @@ impl Element for EditorElement {
);
let end_row = DisplayRow(end_row);
let row_infos = snapshot
.row_infos(start_row)
let buffer_rows = snapshot
.buffer_rows(start_row)
.take((start_row..end_row).len())
.collect::<Vec<RowInfo>>();
let is_row_soft_wrapped = |row: usize| {
row_infos
.get(row)
.map_or(true, |info| info.buffer_row.is_none())
};
.collect::<Vec<_>>();
let is_row_soft_wrapped =
|row| buffer_rows.get(row).copied().flatten().is_none();
let start_anchor = if start_row == Default::default() {
Anchor::min()
@@ -5838,21 +5954,9 @@ impl Element for EditorElement {
)
};
let mut highlighted_rows = self
let highlighted_rows = self
.editor
.update(cx, |editor, cx| editor.highlighted_display_rows(cx));
for (ix, row_info) in row_infos.iter().enumerate() {
let color = match row_info.diff_status {
Some(DiffHunkStatus::Added) => style.status.created_background,
Some(DiffHunkStatus::Removed) => style.status.deleted_background,
_ => continue,
};
highlighted_rows
.entry(start_row + DisplayRow(ix as u32))
.or_insert(color);
}
let highlighted_ranges = self.editor.read(cx).background_highlights_in_range(
start_anchor..end_anchor,
&snapshot.display_snapshot,
@@ -5892,7 +5996,7 @@ impl Element for EditorElement {
let line_numbers = self.layout_line_numbers(
start_row..end_row,
&row_infos,
buffer_rows.iter().copied(),
&active_rows,
newest_selection_head,
&snapshot,
@@ -5902,14 +6006,14 @@ impl Element for EditorElement {
let mut crease_toggles = cx.with_element_namespace("crease_toggles", |cx| {
self.layout_crease_toggles(
start_row..end_row,
&row_infos,
buffer_rows.iter().copied(),
&active_rows,
&snapshot,
cx,
)
});
let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| {
self.layout_crease_trailers(row_infos.iter().copied(), &snapshot, cx)
self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx)
});
let display_hunks = self.layout_gutter_git_hunks(
@@ -6050,12 +6154,11 @@ impl Element for EditorElement {
let display_row = newest_selection_head.row();
if (start_row..end_row).contains(&display_row) {
let line_ix = display_row.minus(start_row) as usize;
let row_info = &row_infos[line_ix];
let line_layout = &line_layouts[line_ix];
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
inline_blame = self.layout_inline_blame(
display_row,
row_info,
&snapshot.display_snapshot,
line_layout,
crease_trailer_layout,
em_width,
@@ -6068,7 +6171,7 @@ impl Element for EditorElement {
}
let blamed_display_rows = self.layout_blame_entries(
&row_infos,
buffer_rows.into_iter(),
em_width,
scroll_position,
line_height,
@@ -6157,6 +6260,22 @@ impl Element for EditorElement {
let gutter_settings = EditorSettings::get_global(cx).gutter;
let expanded_add_hunks_by_rows = self.editor.update(cx, |editor, _| {
editor
.diff_map
.hunks(false)
.filter(|hunk| hunk.status == DiffHunkStatus::Added)
.map(|expanded_hunk| {
let start_row = expanded_hunk
.hunk_range
.start
.to_display_point(&snapshot)
.row();
(start_row, expanded_hunk.clone())
})
.collect::<HashMap<_, _>>()
});
let rows_with_hunk_bounds = display_hunks
.iter()
.filter_map(|(hunk, hitbox)| Some((hunk, hitbox.as_ref()?.bounds)))
@@ -6199,32 +6318,38 @@ impl Element for EditorElement {
if show_code_actions {
let newest_selection_point =
newest_selection_head.to_point(&snapshot.display_snapshot);
if !snapshot
.is_line_folded(MultiBufferRow(newest_selection_point.row))
let newest_selection_display_row =
newest_selection_point.to_display_point(&snapshot).row();
if !expanded_add_hunks_by_rows
.contains_key(&newest_selection_display_row)
{
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
MultiBufferRow(newest_selection_point.row),
);
if let Some((buffer, range)) = buffer {
let buffer_id = buffer.remote_id();
let row = range.start.row;
let has_test_indicator = self
.editor
.read(cx)
.tasks
.contains_key(&(buffer_id, row));
if !snapshot
.is_line_folded(MultiBufferRow(newest_selection_point.row))
{
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
MultiBufferRow(newest_selection_point.row),
);
if let Some((buffer, range)) = buffer {
let buffer_id = buffer.remote_id();
let row = range.start.row;
let has_test_indicator = self
.editor
.read(cx)
.tasks
.contains_key(&(buffer_id, row));
if !has_test_indicator {
code_actions_indicator = self
.layout_code_actions_indicator(
line_height,
newest_selection_head,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
&rows_with_hunk_bounds,
cx,
);
if !has_test_indicator {
code_actions_indicator = self
.layout_code_actions_indicator(
line_height,
newest_selection_head,
scroll_pixel_position,
&gutter_dimensions,
&gutter_hitbox,
&rows_with_hunk_bounds,
cx,
);
}
}
}
}
@@ -6898,7 +7023,16 @@ impl CursorLayout {
let name_origin = if cursor_name.is_top_row {
point(bounds.right() - px(1.), bounds.top())
} else {
point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
match self.shape {
CursorShape::Bar => point(
bounds.right() - px(2.),
bounds.top() - text_size / 2. - px(1.),
),
_ => point(
bounds.right() - px(1.),
bounds.top() - text_size / 2. - px(1.),
),
}
};
let mut name_element = div()
.bg(self.color)
@@ -7196,12 +7330,7 @@ mod tests {
.update_window(*window, |_, cx| {
element.layout_line_numbers(
DisplayRow(0)..DisplayRow(6),
&(0..6)
.map(|row| RowInfo {
buffer_row: Some(row),
..Default::default()
})
.collect::<Vec<_>>(),
(0..6).map(MultiBufferRow).map(Some),
&Default::default(),
Some(DisplayPoint::new(DisplayRow(0), 0)),
&snapshot,

View File

@@ -9,13 +9,12 @@ use git::{
use gpui::{Model, ModelContext, Subscription, Task};
use http_client::HttpClient;
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
use multi_buffer::MultiBufferRow;
use project::{Project, ProjectItem};
use smallvec::SmallVec;
use sum_tree::SumTree;
use url::Url;
use crate::RowInfo;
#[derive(Clone, Debug, Default)]
pub struct GitBlameEntry {
pub rows: u32,
@@ -195,15 +194,15 @@ impl GitBlame {
pub fn blame_for_rows<'a>(
&'a mut self,
rows: &'a [RowInfo],
rows: impl 'a + IntoIterator<Item = Option<MultiBufferRow>>,
cx: &mut ModelContext<Self>,
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
self.sync(cx);
let mut cursor = self.entries.cursor::<u32>(&());
rows.into_iter().map(move |info| {
let row = info.buffer_row?;
cursor.seek_forward(&row, Bias::Right, &());
rows.into_iter().map(move |row| {
let row = row?;
cursor.seek_forward(&row.0, Bias::Right, &());
cursor.item()?.blame.clone()
})
}
@@ -564,38 +563,15 @@ mod tests {
use unindent::Unindent as _;
use util::RandomCharIter;
// macro_rules! assert_blame_rows {
// ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
// assert_eq!(
// $blame
// .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
// .collect::<Vec<_>>(),
// $expected
// );
// };
// }
#[track_caller]
fn assert_blame_rows(
blame: &mut GitBlame,
rows: Range<u32>,
expected: Vec<Option<BlameEntry>>,
cx: &mut ModelContext<GitBlame>,
) {
assert_eq!(
blame
.blame_for_rows(
&rows
.map(|row| RowInfo {
buffer_row: Some(row),
..Default::default()
})
.collect::<Vec<_>>(),
cx
)
.collect::<Vec<_>>(),
expected
);
macro_rules! assert_blame_rows {
($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
assert_eq!(
$blame
.blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
.collect::<Vec<_>>(),
$expected
);
};
}
fn init_test(cx: &mut gpui::TestAppContext) {
@@ -658,15 +634,7 @@ mod tests {
blame.update(cx, |blame, cx| {
assert_eq!(
blame
.blame_for_rows(
&(0..1)
.map(|row| RowInfo {
buffer_row: Some(row),
..Default::default()
})
.collect::<Vec<_>>(),
cx
)
.blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx)
.collect::<Vec<_>>(),
vec![None]
);
@@ -730,15 +698,7 @@ mod tests {
// All lines
assert_eq!(
blame
.blame_for_rows(
&(0..8)
.map(|buffer_row| RowInfo {
buffer_row: Some(buffer_row),
..Default::default()
})
.collect::<Vec<_>>(),
cx
)
.blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx)
.collect::<Vec<_>>(),
vec![
Some(blame_entry("1b1b1b", 0..1)),
@@ -754,15 +714,7 @@ mod tests {
// Subset of lines
assert_eq!(
blame
.blame_for_rows(
&(1..4)
.map(|buffer_row| RowInfo {
buffer_row: Some(buffer_row),
..Default::default()
})
.collect::<Vec<_>>(),
cx
)
.blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx)
.collect::<Vec<_>>(),
vec![
Some(blame_entry("0d0d0d", 1..2)),
@@ -773,17 +725,7 @@ mod tests {
// Subset of lines, with some not displayed
assert_eq!(
blame
.blame_for_rows(
&[
RowInfo {
buffer_row: Some(1),
..Default::default()
},
Default::default(),
Default::default(),
],
cx
)
.blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx)
.collect::<Vec<_>>(),
vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
);
@@ -835,16 +777,16 @@ mod tests {
git_blame.update(cx, |blame, cx| {
// Sanity check before edits: make sure that we get the same blame entry for all
// lines.
assert_blame_rows(
assert_blame_rows!(
blame,
0..4,
(0..4),
vec![
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
],
cx,
cx
);
});
@@ -853,11 +795,11 @@ mod tests {
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "X")], None, cx);
});
git_blame.update(cx, |blame, cx| {
assert_blame_rows(
assert_blame_rows!(
blame,
0..2,
(0..2),
vec![None, Some(blame_entry("1b1b1b", 0..4))],
cx,
cx
);
});
// Modify a single line, in the middle of the line
@@ -865,21 +807,21 @@ mod tests {
buffer.edit([(Point::new(1, 2)..Point::new(1, 2), "X")], None, cx);
});
git_blame.update(cx, |blame, cx| {
assert_blame_rows(
assert_blame_rows!(
blame,
1..4,
(1..4),
vec![
None,
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4)),
Some(blame_entry("1b1b1b", 0..4))
],
cx,
cx
);
});
// Before we insert a newline at the end, sanity check:
git_blame.update(cx, |blame, cx| {
assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
assert_blame_rows!(blame, (3..4), vec![Some(blame_entry("1b1b1b", 0..4))], cx);
});
// Insert a newline at the end
buffer.update(cx, |buffer, cx| {
@@ -887,17 +829,17 @@ mod tests {
});
// Only the new line is marked as edited:
git_blame.update(cx, |blame, cx| {
assert_blame_rows(
assert_blame_rows!(
blame,
3..5,
(3..5),
vec![Some(blame_entry("1b1b1b", 0..4)), None],
cx,
cx
);
});
// Before we insert a newline at the start, sanity check:
git_blame.update(cx, |blame, cx| {
assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
assert_blame_rows!(blame, (2..3), vec![Some(blame_entry("1b1b1b", 0..4)),], cx);
});
// Usage example
@@ -907,11 +849,11 @@ mod tests {
});
// Only the new line is marked as edited:
git_blame.update(cx, |blame, cx| {
assert_blame_rows(
assert_blame_rows!(
blame,
2..4,
vec![None, Some(blame_entry("1b1b1b", 0..4))],
cx,
(2..4),
vec![None, Some(blame_entry("1b1b1b", 0..4)),],
cx
);
});
}

View File

@@ -150,7 +150,7 @@ impl ProjectDiffEditor {
let editor = cx.new_view(|cx| {
let mut diff_display_editor =
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx);
diff_display_editor.set_expand_all_diff_hunks(cx);
diff_display_editor.set_expand_all_diff_hunks();
diff_display_editor
});
@@ -311,11 +311,9 @@ impl ProjectDiffEditor {
.update(&mut cx, |project_diff_editor, cx| {
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
project_diff_editor.editor.update(cx, |editor, cx| {
editor.display_map.update(cx, |display_map, cx| {
for change_set in change_sets {
display_map.add_change_set(change_set, cx)
}
});
for change_set in change_sets {
editor.diff_map.add_change_set(change_set, cx)
}
});
})
.ok();
@@ -1195,9 +1193,9 @@ mod tests {
cx,
)
});
file_a_editor.display_map.update(cx, |display_map, cx| {
display_map.add_change_set(change_set.clone(), cx)
});
file_a_editor
.diff_map
.add_change_set(change_set.clone(), cx);
project.update(cx, |project, cx| {
project.buffer_store().update(cx, |buffer_store, cx| {
buffer_store.set_change_set(

View File

@@ -6,10 +6,11 @@ use gpui::{
use language::{Buffer, BufferId, Point};
use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
MultiBufferSnapshot, ToPoint,
MultiBufferSnapshot, ToOffset, ToPoint,
};
use project::buffer_store::BufferChangeSet;
use std::{ops::Range, sync::Arc};
use sum_tree::TreeMap;
use text::OffsetRangeExt;
use ui::{
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
@@ -19,10 +20,10 @@ use util::RangeExt;
use workspace::Item;
use crate::{
editor_settings::CurrentLineHighlight, hunk_status, ApplyAllDiffHunks, ApplyDiffHunk,
BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
DisplaySnapshot, Editor, EditorElement, GoToHunk, GoToPrevHunk, RevertFile,
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks,
ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight,
DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
};
#[derive(Debug, Clone)]
@@ -36,6 +37,7 @@ pub(super) struct HoveredHunk {
pub(super) struct DiffMap {
pub(crate) hunks: Vec<ExpandedHunk>,
pub(crate) diff_bases: HashMap<BufferId, DiffBaseState>,
pub(crate) snapshot: DiffMapSnapshot,
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
expand_all: bool,
}
@@ -49,6 +51,9 @@ pub(super) struct ExpandedHunk {
pub folded: bool,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct DiffMapSnapshot(TreeMap<BufferId, git::diff::BufferDiff>);
pub(crate) struct DiffBaseState {
pub(crate) change_set: Model<BufferChangeSet>,
pub(crate) last_version: Option<usize>,
@@ -69,7 +74,133 @@ pub enum DisplayDiffHunk {
},
}
impl DiffMap {
pub fn snapshot(&self) -> DiffMapSnapshot {
self.snapshot.clone()
}
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ViewContext<Editor>,
) {
let buffer_id = change_set.read(cx).buffer_id;
self.snapshot
.0
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
self.diff_bases.insert(
buffer_id,
DiffBaseState {
last_version: None,
_subscription: cx.observe(&change_set, move |editor, change_set, cx| {
editor
.diff_map
.snapshot
.0
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx);
}),
change_set,
},
);
Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
}
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
self.hunks
.iter()
.filter(move |hunk| include_folded || !hunk.folded)
}
}
impl DiffMapSnapshot {
pub fn is_empty(&self) -> bool {
self.0.values().all(|diff| diff.is_empty())
}
pub fn diff_hunks<'a>(
&'a self,
buffer_snapshot: &'a MultiBufferSnapshot,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot)
}
pub fn diff_hunks_in_range<'a, T: ToOffset>(
&'a self,
range: Range<T>,
buffer_snapshot: &'a MultiBufferSnapshot,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
buffer_snapshot
.excerpts_for_range(range.clone())
.filter_map(move |excerpt| {
let buffer = excerpt.buffer();
let buffer_id = buffer.remote_id();
let diff = self.0.get(&buffer_id)?;
let buffer_range = excerpt.map_range_to_buffer(range.clone());
let buffer_range =
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
Some(
diff.hunks_intersecting_range(buffer_range, excerpt.buffer())
.map(move |hunk| {
let start =
excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0));
let end =
excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0));
MultiBufferDiffHunk {
row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
buffer_id,
buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
}
}),
)
})
.flatten()
}
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
&'a self,
range: Range<T>,
buffer_snapshot: &'a MultiBufferSnapshot,
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
buffer_snapshot
.excerpts_for_range_rev(range.clone())
.filter_map(move |excerpt| {
let buffer = excerpt.buffer();
let buffer_id = buffer.remote_id();
let diff = self.0.get(&buffer_id)?;
let buffer_range = excerpt.map_range_to_buffer(range.clone());
let buffer_range =
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
Some(
diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer())
.map(move |hunk| {
let start_row = excerpt
.map_point_from_buffer(Point::new(hunk.row_range.start, 0))
.row;
let end_row = excerpt
.map_point_from_buffer(Point::new(hunk.row_range.end, 0))
.row;
MultiBufferDiffHunk {
row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row),
buffer_id,
buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
}
}),
)
})
.flatten()
}
}
impl Editor {
pub fn set_expand_all_diff_hunks(&mut self) {
self.diff_map.expand_all = true;
}
pub(super) fn toggle_hovered_hunk(
&mut self,
hovered_hunk: &HoveredHunk,
@@ -82,6 +213,47 @@ impl Editor {
}
}
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
let selections = self.selections.all(cx);
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
}
pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
let snapshot = self.snapshot(cx);
let display_rows_with_expanded_hunks = self
.diff_map
.hunks(false)
.map(|hunk| &hunk.hunk_range)
.map(|anchor_range| {
(
anchor_range
.start
.to_display_point(&snapshot.display_snapshot)
.row(),
anchor_range
.end
.to_display_point(&snapshot.display_snapshot)
.row(),
)
})
.collect::<HashMap<_, _>>();
let hunks = self
.diff_map
.snapshot
.diff_hunks(&snapshot.display_snapshot.buffer_snapshot)
.filter(|hunk| {
let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
.to_display_point(&snapshot.display_snapshot)
..Point::new(hunk.row_range.end.0, 0)
.to_display_point(&snapshot.display_snapshot);
let row_range_end =
display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row());
row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row())
});
self.toggle_hunks_expanded(hunks.collect(), cx);
}
fn toggle_hunks_expanded(
&mut self,
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
@@ -325,8 +497,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
let snapshot = self.snapshot(cx);
let ranges = self.selections.all(cx).into_iter().map(|s| s.range());
let hunks = snapshot.hunks_for_ranges(ranges);
let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
let mut ranges_by_buffer = HashMap::default();
self.transact(cx, |editor, cx| {
for hunk in hunks {
@@ -350,8 +521,10 @@ impl Editor {
}
}
fn has_multiple_hunks(&self, cx: &mut WindowContext) -> bool {
self.display_map.read(cx).has_multiple_hunks(cx)
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot);
hunks.nth(1).is_some()
}
fn hunk_header_block(
@@ -692,15 +865,23 @@ impl Editor {
}
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
self.display_map.update(cx, |diff_map, cx| {
let ranges = vec![Anchor::min()..Anchor::max()];
if diff_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) {
diff_map.collapse_diff_hunks(ranges, cx);
true
} else {
false
}
})
if self.diff_map.expand_all {
return false;
}
self.diff_map.hunk_update_tasks.clear();
self.clear_row_highlights::<DiffRowHighlight>();
let to_remove = self
.diff_map
.hunks
.drain(..)
.flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
.collect::<HashSet<_>>();
if to_remove.is_empty() {
false
} else {
self.remove_blocks(to_remove, None, cx);
true
}
}
pub(super) fn sync_expanded_diff_hunks(
@@ -728,7 +909,8 @@ impl Editor {
.update(&mut cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let mut recalculated_hunks = snapshot
.diff_hunks()
.diff_map
.diff_hunks(&snapshot.buffer_snapshot)
.filter(|hunk| hunk.buffer_id == buffer_id)
.fuse()
.peekable();
@@ -1236,9 +1418,7 @@ mod tests {
cx,
)
});
editor.display_map.update(cx, |display_map, cx| {
display_map.add_change_set(change_set, cx)
});
editor.diff_map.add_change_set(change_set, cx)
}
})
.unwrap();
@@ -1290,7 +1470,8 @@ mod tests {
assert_eq!(
snapshot
.diff_hunks_in_range(Point::zero()..Point::new(12, 0))
.diff_map
.diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot)
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
.collect::<Vec<_>>(),
&expected,
@@ -1298,7 +1479,11 @@ mod tests {
assert_eq!(
snapshot
.diff_hunks_in_range_rev(Point::zero()..Point::new(12, 0))
.diff_map
.diff_hunks_in_range_rev(
Point::zero()..Point::new(12, 0),
&snapshot.buffer_snapshot
)
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
.collect::<Vec<_>>(),
expected

View File

@@ -321,6 +321,10 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
"Fake Completion Provider"
}
fn show_completions_in_menu() -> bool {
false
}
fn is_enabled(
&self,
_buffer: &gpui::Model<language::Buffer>,

View File

@@ -61,7 +61,7 @@ impl ProposedChangesEditor {
let mut this = Self {
editor: cx.new_view(|cx| {
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
editor.set_expand_all_diff_hunks(cx);
editor.set_expand_all_diff_hunks();
editor.set_completion_provider(None);
editor.clear_code_action_providers();
editor.set_semantics_provider(
@@ -223,11 +223,9 @@ impl ProposedChangesEditor {
self.buffer_entries = buffer_entries;
self.editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |selections| selections.refresh());
editor.display_map.update(cx, |display_map, cx| {
for change_set in new_change_sets {
display_map.add_change_set(change_set, cx)
}
})
for change_set in new_change_sets {
editor.diff_map.add_change_set(change_set, cx)
}
});
}

View File

@@ -391,7 +391,7 @@ impl SelectionsCollection {
}
}
pub(crate) fn change_with<R>(
pub fn change_with<R>(
&mut self,
cx: &mut AppContext,
change: impl FnOnce(&mut MutableSelectionsCollection) -> R,
@@ -764,7 +764,7 @@ impl<'a> MutableSelectionsCollection<'a> {
pub fn replace_cursors_with(
&mut self,
mut find_replacement_cursors: impl FnMut(&DisplaySnapshot) -> Vec<DisplayPoint>,
find_replacement_cursors: impl FnOnce(&DisplaySnapshot) -> Vec<DisplayPoint>,
) {
let display_map = self.display_map();
let new_selections = find_replacement_cursors(&display_map)

View File

@@ -1,6 +1,6 @@
use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
RowExt,
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint,
Editor, MultiBuffer, RowExt,
};
use collections::BTreeMap;
use futures::Future;
@@ -11,12 +11,11 @@ use gpui::{
};
use itertools::Itertools;
use language::{Buffer, BufferSnapshot, LanguageRegistry};
use multi_buffer::ExcerptRange;
use multi_buffer::{ExcerptRange, ToPoint};
use parking_lot::RwLock;
use project::{FakeFs, Project};
use std::{
any::TypeId,
cmp,
ops::{Deref, DerefMut, Range},
path::Path,
sync::{
@@ -24,7 +23,6 @@ use std::{
Arc,
},
};
use text::Bias;
use util::{
assert_set_eq,
test::{generate_marked_text, marked_text_ranges},
@@ -334,51 +332,83 @@ impl EditorTestContext {
///
/// Diff hunks are indicated by lines starting with `+` and `-`.
#[track_caller]
pub fn assert_state_with_diff(&mut self, expected_diff_text: String) {
let (snapshot, selections) = self
.editor
.update(&mut self.cx, |editor, cx| editor.selections.all_display(cx));
let diff_snapshot = snapshot.diff_snapshot();
let diff_offsets = selections
.into_iter()
.map(|s| {
let start = snapshot.display_point_to_diff_offset(s.start, Bias::Left).0;
let end = snapshot.display_point_to_diff_offset(s.end, Bias::Left).0;
cmp::min(start, end)..cmp::max(start, end)
pub fn assert_state_with_diff(&mut self, expected_diff: String) {
let has_diff_markers = expected_diff
.lines()
.any(|line| line.starts_with("+") || line.starts_with("-"));
let expected_diff_text = expected_diff
.split('\n')
.map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() {
String::new()
} else if has_diff_markers {
line.to_string()
} else {
format!(" {line}")
}
})
.collect::<Vec<_>>();
.join("\n");
let actual_marked_text = generate_marked_text(&diff_snapshot.text(), &diff_offsets, true);
let actual_selections = self.editor_selections();
let actual_marked_text =
generate_marked_text(&self.buffer_text(), &actual_selections, true);
// Read the actual diff from the editor's row highlights and block
// decorations.
let line_infos = diff_snapshot.row_infos(0).collect::<Vec<_>>();
let has_diff = line_infos.iter().any(|info| info.diff_status.is_some());
let actual_diff = actual_marked_text
.split('\n')
.zip(line_infos)
.map(|(line, info)| {
let mut marker = match info.diff_status {
Some(DiffHunkStatus::Added) => "+ ",
Some(DiffHunkStatus::Removed) => "- ",
Some(DiffHunkStatus::Modified) => unreachable!(),
None => {
if has_diff {
" "
} else {
""
}
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let insertions = editor
.highlighted_rows::<DiffRowHighlight>()
.map(|(range, _)| {
let start = range.start.to_point(&snapshot.buffer_snapshot);
let end = range.end.to_point(&snapshot.buffer_snapshot);
start.row..end.row
})
.collect::<Vec<_>>();
let deletions = editor
.diff_map
.hunks
.iter()
.filter_map(|hunk| {
if hunk.blocks.is_empty() {
return None;
}
};
if line.is_empty() {
marker = marker.trim();
}
format!("{marker}{line}")
})
.collect::<Vec<_>>()
.join("\n");
let row = hunk
.hunk_range
.start
.to_point(&snapshot.buffer_snapshot)
.row;
let (_, buffer, _) = editor
.buffer()
.read(cx)
.excerpt_containing(hunk.hunk_range.start, cx)
.expect("no excerpt for expanded buffer's hunk start");
let buffer_id = buffer.read(cx).remote_id();
let change_set = &editor
.diff_map
.diff_bases
.get(&buffer_id)
.expect("should have a diff base for expanded hunk")
.change_set;
let deleted_text = change_set
.read(cx)
.base_text
.as_ref()
.expect("no base text for expanded hunk")
.read(cx)
.as_rope()
.slice(hunk.diff_base_byte_range.clone())
.to_string();
if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status {
Some((row, deleted_text))
} else {
None
}
})
.collect::<Vec<_>>();
format_diff(actual_marked_text, deletions, insertions)
});
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
}
@@ -473,6 +503,46 @@ impl EditorTestContext {
}
}
fn format_diff(
text: String,
actual_deletions: Vec<(u32, String)>,
actual_insertions: Vec<Range<u32>>,
) -> String {
let mut diff = String::new();
for (row, line) in text.split('\n').enumerate() {
let row = row as u32;
if row > 0 {
diff.push('\n');
}
if let Some(text) = actual_deletions
.iter()
.find_map(|(deletion_row, deleted_text)| {
if *deletion_row == row {
Some(deleted_text)
} else {
None
}
})
{
for line in text.lines() {
diff.push('-');
if !line.is_empty() {
diff.push(' ');
diff.push_str(line);
}
diff.push('\n');
}
}
let marker = if actual_insertions.iter().any(|range| range.contains(&row)) {
"+ "
} else {
" "
};
diff.push_str(format!("{marker}{line}").trim_end());
}
diff
}
impl Deref for EditorTestContext {
type Target = gpui::VisualTestContext;

View File

@@ -30,6 +30,7 @@ semantic_version.workspace = true
serde.workspace = true
settings.workspace = true
smallvec.workspace = true
telemetry.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true

View File

@@ -48,7 +48,8 @@ impl RenderOnce for ExtensionCard {
.absolute()
.top_0()
.left_0()
.occlude()
.block_mouse_down()
.cursor_default()
.size_full()
.items_center()
.justify_center()

View File

@@ -1,6 +1,3 @@
use std::sync::Arc;
use client::telemetry::Telemetry;
use gpui::{AnyElement, Div, StyleRefinement};
use smallvec::SmallVec;
use ui::{prelude::*, ButtonLike};
@@ -8,17 +5,15 @@ use ui::{prelude::*, ButtonLike};
#[derive(IntoElement)]
pub struct FeatureUpsell {
base: Div,
telemetry: Arc<Telemetry>,
text: SharedString,
docs_url: Option<SharedString>,
children: SmallVec<[AnyElement; 2]>,
}
impl FeatureUpsell {
pub fn new(telemetry: Arc<Telemetry>, text: impl Into<SharedString>) -> Self {
pub fn new(text: impl Into<SharedString>) -> Self {
Self {
base: h_flex(),
telemetry,
text: text.into(),
docs_url: None,
children: SmallVec::new(),
@@ -67,12 +62,13 @@ impl RenderOnce for FeatureUpsell {
.child(Icon::new(IconName::ArrowUpRight)),
)
.on_click({
let telemetry = self.telemetry.clone();
let docs_url = docs_url.clone();
move |_event, cx| {
telemetry.report_app_event(format!(
"feature upsell: viewed docs ({docs_url})"
));
telemetry::event!(
"Documentation Viewed",
source = "Feature Upsell",
url = docs_url,
);
cx.open_url(&docs_url)
}
}),

View File

@@ -7,7 +7,6 @@ use std::sync::OnceLock;
use std::time::Duration;
use std::{ops::Range, sync::Arc};
use client::telemetry::Telemetry;
use client::ExtensionMetadata;
use collections::{BTreeMap, BTreeSet};
use editor::{Editor, EditorElement, EditorStyle};
@@ -182,7 +181,6 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
pub struct ExtensionsPage {
workspace: WeakView<Workspace>,
list: UniformListScrollHandle,
telemetry: Arc<Telemetry>,
is_fetching_extensions: bool,
filter: ExtensionFilter,
remote_extension_entries: Vec<ExtensionMetadata>,
@@ -221,7 +219,6 @@ impl ExtensionsPage {
let mut this = Self {
workspace: workspace.weak_handle(),
list: UniformListScrollHandle::new(),
telemetry: workspace.client().telemetry().clone(),
is_fetching_extensions: false,
filter: ExtensionFilter::All,
dev_extension_entries: Vec::new(),
@@ -704,18 +701,15 @@ impl ExtensionsPage {
match status.clone() {
ExtensionStatus::NotInstalled => (
Button::new(SharedString::from(extension.id.clone()), "Install").on_click(
cx.listener({
let extension_id = extension.id.clone();
move |this, _, cx| {
this.telemetry
.report_app_event("extensions: install extension".to_string());
ExtensionStore::global(cx).update(cx, |store, cx| {
store.install_latest_extension(extension_id.clone(), cx)
});
}
}),
),
Button::new(SharedString::from(extension.id.clone()), "Install").on_click({
let extension_id = extension.id.clone();
move |_, cx| {
telemetry::event!("Extension Installed");
ExtensionStore::global(cx).update(cx, |store, cx| {
store.install_latest_extension(extension_id.clone(), cx)
});
}
}),
None,
),
ExtensionStatus::Installing => (
@@ -729,18 +723,15 @@ impl ExtensionsPage {
),
),
ExtensionStatus::Installed(installed_version) => (
Button::new(SharedString::from(extension.id.clone()), "Uninstall").on_click(
cx.listener({
let extension_id = extension.id.clone();
move |this, _, cx| {
this.telemetry
.report_app_event("extensions: uninstall extension".to_string());
ExtensionStore::global(cx).update(cx, |store, cx| {
store.uninstall_extension(extension_id.clone(), cx)
});
}
}),
),
Button::new(SharedString::from(extension.id.clone()), "Uninstall").on_click({
let extension_id = extension.id.clone();
move |_, cx| {
telemetry::event!("Extension Uninstalled", extension_id);
ExtensionStore::global(cx).update(cx, |store, cx| {
store.uninstall_extension(extension_id.clone(), cx)
});
}
}),
if installed_version == extension.manifest.version {
None
} else {
@@ -760,13 +751,11 @@ impl ExtensionsPage {
})
})
.disabled(!is_compatible)
.on_click(cx.listener({
.on_click({
let extension_id = extension.id.clone();
let version = extension.manifest.version.clone();
move |this, _, cx| {
this.telemetry.report_app_event(
"extensions: install extension".to_string(),
);
move |_, cx| {
telemetry::event!("Extension Installed", extension_id, version);
ExtensionStore::global(cx).update(cx, |store, cx| {
store
.upgrade_extension(
@@ -777,7 +766,7 @@ impl ExtensionsPage {
.detach_and_log_err(cx)
});
}
})),
}),
)
},
),
@@ -972,19 +961,16 @@ impl ExtensionsPage {
let upsells_count = self.upsells.len();
v_flex().children(self.upsells.iter().enumerate().map(|(ix, feature)| {
let telemetry = self.telemetry.clone();
let upsell = match feature {
Feature::Git => FeatureUpsell::new(
telemetry,
"Zed comes with basic Git support. More Git features are coming in the future.",
)
.docs_url("https://zed.dev/docs/git"),
Feature::OpenIn => FeatureUpsell::new(
telemetry,
"Zed supports linking to a source line on GitHub and others.",
)
.docs_url("https://zed.dev/docs/git#git-integrations"),
Feature::Vim => FeatureUpsell::new(telemetry, "Vim support is built-in to Zed!")
Feature::Vim => FeatureUpsell::new("Vim support is built-in to Zed!")
.docs_url("https://zed.dev/docs/vim")
.child(CheckboxWithLabel::new(
"enable-vim",
@@ -995,8 +981,7 @@ impl ExtensionsPage {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry
.report_app_event("feature upsell: toggle vim".to_string());
telemetry::event!("Vim Mode Toggled", source = "Feature Upsell");
this.update_settings::<VimModeSetting>(
selection,
cx,
@@ -1004,36 +989,22 @@ impl ExtensionsPage {
);
}),
)),
Feature::LanguageBash => {
FeatureUpsell::new(telemetry, "Shell support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/bash")
}
Feature::LanguageC => {
FeatureUpsell::new(telemetry, "C support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/c")
}
Feature::LanguageCpp => {
FeatureUpsell::new(telemetry, "C++ support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/cpp")
}
Feature::LanguageGo => {
FeatureUpsell::new(telemetry, "Go support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/go")
}
Feature::LanguagePython => {
FeatureUpsell::new(telemetry, "Python support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/python")
}
Feature::LanguageReact => {
FeatureUpsell::new(telemetry, "React support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript")
}
Feature::LanguageRust => {
FeatureUpsell::new(telemetry, "Rust support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/rust")
}
Feature::LanguageBash => FeatureUpsell::new("Shell support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/bash"),
Feature::LanguageC => FeatureUpsell::new("C support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/c"),
Feature::LanguageCpp => FeatureUpsell::new("C++ support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/cpp"),
Feature::LanguageGo => FeatureUpsell::new("Go support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/go"),
Feature::LanguagePython => FeatureUpsell::new("Python support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/python"),
Feature::LanguageReact => FeatureUpsell::new("React support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript"),
Feature::LanguageRust => FeatureUpsell::new("Rust support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/rust"),
Feature::LanguageTypescript => {
FeatureUpsell::new(telemetry, "Typescript support is built-in to Zed!")
FeatureUpsell::new("Typescript support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript")
}
};

View File

@@ -21,13 +21,26 @@ const fn zed_repo_url() -> &'static str {
"https://github.com/zed-industries/zed"
}
const fn request_feature_url() -> &'static str {
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cenhancement&projects=&template=0_feature_request.yml"
fn request_feature_url(specs: &SystemSpecs) -> String {
format!(
concat!(
"https://github.com/zed-industries/zed/issues/new",
"?labels=admin+read%2Ctriage%2Cenhancement",
"&template=0_feature_request.yml",
"&environment={}"
),
urlencoding::encode(&specs.to_string())
)
}
fn file_bug_report_url(specs: &SystemSpecs) -> String {
format!(
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cbug&projects=&template=1_bug_report.yml&environment={}",
concat!(
"https://github.com/zed-industries/zed/issues/new",
"?labels=admin+read%2Ctriage%2Cbug",
"&template=1_bug_report.yml",
"&environment={}"
),
urlencoding::encode(&specs.to_string())
)
}
@@ -57,7 +70,15 @@ pub fn init(cx: &mut AppContext) {
.detach();
})
.register_action(|_, _: &RequestFeature, cx| {
cx.open_url(request_feature_url());
let specs = SystemSpecs::new(cx);
cx.spawn(|_, mut cx| async move {
let specs = specs.await;
cx.update(|cx| {
cx.open_url(&request_feature_url(&specs));
})
.log_err();
})
.detach();
})
.register_action(move |_, _: &FileBugReport, cx| {
let specs = SystemSpecs::new(cx);

View File

@@ -1,4 +1,8 @@
use std::{ops::RangeInclusive, sync::Arc, time::Duration};
use std::{
ops::RangeInclusive,
sync::{Arc, LazyLock},
time::Duration,
};
use anyhow::{anyhow, bail};
use bitflags::bitflags;
@@ -34,7 +38,8 @@ const DEV_MODE: bool = true;
const DEV_MODE: bool = false;
const DATABASE_KEY_NAME: &str = "email_address";
const EMAIL_REGEX: &str = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b";
static EMAIL_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap());
const FEEDBACK_CHAR_LIMIT: RangeInclusive<i32> = 10..=5000;
const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
"Feedback failed to submit, see error log for details.";
@@ -320,7 +325,7 @@ impl FeedbackModal {
let mut invalid_state_flags = InvalidStateFlags::empty();
let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) {
Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address),
Some(email_address) => EMAIL_REGEX.is_match(&email_address),
None => true,
};

View File

@@ -47,9 +47,12 @@ windows.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
ashpd.workspace = true
which.workspace = true
shlex.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
[features]
test-support = ["gpui/test-support", "git/test-support"]

View File

@@ -10,6 +10,8 @@ use git::GitHostingProviderRegistry;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use ashpd::desktop::trash;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use smol::process::Command;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use std::fs::File;
#[cfg(unix)]
use std::os::fd::AsFd;
@@ -514,24 +516,7 @@ impl Fs for RealFs {
async fn atomic_write(&self, path: PathBuf, data: String) -> Result<()> {
smol::unblock(move || {
let mut tmp_file = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
// Use the directory of the destination as temp dir to avoid
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
// See https://github.com/zed-industries/zed/pull/8437 for more details.
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))
} else if cfg!(target_os = "windows") {
// If temp dir is set to a different drive than the destination,
// we receive error:
//
// failed to persist temporary file:
// The system cannot move the file to a different disk drive. (os error 17)
//
// So we use the directory of the destination as a temp dir to avoid it.
// https://github.com/zed-industries/zed/issues/16571
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))
} else {
NamedTempFile::new()
}?;
let mut tmp_file = create_temp_file(&path)?;
tmp_file.write_all(data.as_bytes())?;
tmp_file.persist(path)?;
Ok::<(), anyhow::Error>(())
@@ -546,13 +531,43 @@ impl Fs for RealFs {
if let Some(path) = path.parent() {
self.create_dir(path).await?;
}
let file = smol::fs::File::create(path).await?;
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
for chunk in chunks(text, line_ending) {
writer.write_all(chunk.as_bytes()).await?;
match smol::fs::File::create(path).await {
Ok(file) => {
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
for chunk in chunks(text, line_ending) {
writer.write_all(chunk.as_bytes()).await?;
}
writer.flush().await?;
Ok(())
}
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
let target_path = path.to_path_buf();
let temp_file = smol::unblock(move || create_temp_file(&target_path)).await?;
let temp_path = temp_file.into_temp_path();
let temp_path_for_write = temp_path.to_path_buf();
let async_file = smol::fs::OpenOptions::new()
.write(true)
.open(&temp_path)
.await?;
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, async_file);
for chunk in chunks(text, line_ending) {
writer.write_all(chunk.as_bytes()).await?;
}
writer.flush().await?;
write_to_file_as_root(temp_path_for_write, path.to_path_buf()).await
} else {
// Todo: Implement for Mac and Windows
Err(e.into())
}
}
Err(e) => Err(e.into()),
}
writer.flush().await?;
Ok(())
}
async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
@@ -1999,6 +2014,84 @@ fn chunks(rope: &Rope, line_ending: LineEnding) -> impl Iterator<Item = &str> {
})
}
fn create_temp_file(path: &Path) -> Result<NamedTempFile> {
let temp_file = if cfg!(any(target_os = "linux", target_os = "freebsd")) {
// Use the directory of the destination as temp dir to avoid
// invalid cross-device link error, and XDG_CACHE_DIR for fallback.
// See https://github.com/zed-industries/zed/pull/8437 for more details.
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))?
} else if cfg!(target_os = "windows") {
// If temp dir is set to a different drive than the destination,
// we receive error:
//
// failed to persist temporary file:
// The system cannot move the file to a different disk drive. (os error 17)
//
// So we use the directory of the destination as a temp dir to avoid it.
// https://github.com/zed-industries/zed/issues/16571
NamedTempFile::new_in(path.parent().unwrap_or(paths::temp_dir()))?
} else {
NamedTempFile::new()?
};
Ok(temp_file)
}
#[cfg(target_os = "macos")]
async fn write_to_file_as_root(_temp_file_path: PathBuf, _target_file_path: PathBuf) -> Result<()> {
unimplemented!("write_to_file_as_root is not implemented")
}
#[cfg(target_os = "windows")]
async fn write_to_file_as_root(_temp_file_path: PathBuf, _target_file_path: PathBuf) -> Result<()> {
unimplemented!("write_to_file_as_root is not implemented")
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
async fn write_to_file_as_root(temp_file_path: PathBuf, target_file_path: PathBuf) -> Result<()> {
use shlex::try_quote;
use std::os::unix::fs::PermissionsExt;
use which::which;
let pkexec_path = smol::unblock(|| which("pkexec"))
.await
.map_err(|_| anyhow::anyhow!("pkexec not found in PATH"))?;
let script_file = smol::unblock(move || {
let script_file = tempfile::Builder::new()
.prefix("write-to-file-as-root-")
.tempfile_in(paths::temp_dir())?;
writeln!(
script_file.as_file(),
"#!/usr/bin/env sh\nset -eu\ncat \"{}\" > \"{}\"",
try_quote(&temp_file_path.to_string_lossy())?,
try_quote(&target_file_path.to_string_lossy())?
)?;
let mut perms = script_file.as_file().metadata()?.permissions();
perms.set_mode(0o700); // rwx------
script_file.as_file().set_permissions(perms)?;
Result::<_>::Ok(script_file)
})
.await?;
let script_path = script_file.into_temp_path();
let output = Command::new(&pkexec_path)
.arg("--disable-internal-agent")
.arg(&script_path)
.output()
.await?;
if !output.status.success() {
return Err(anyhow::anyhow!("Failed to write to file as root"));
}
Ok(())
}
pub fn normalize_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {

View File

@@ -1,5 +1,5 @@
use std::str::FromStr;
use std::sync::{Arc, OnceLock};
use std::sync::{Arc, LazyLock};
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
@@ -15,9 +15,9 @@ use git::{
};
fn pull_request_number_regex() -> &'static Regex {
static PULL_REQUEST_NUMBER_REGEX: OnceLock<Regex> = OnceLock::new();
PULL_REQUEST_NUMBER_REGEX.get_or_init(|| Regex::new(r"\(#(\d+)\)$").unwrap())
static PULL_REQUEST_NUMBER_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\(#(\d+)\)$").unwrap());
&PULL_REQUEST_NUMBER_REGEX
}
#[derive(Debug, Deserialize)]

View File

@@ -136,8 +136,8 @@ font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "40391b7"
foreign-types = "0.5"
log.workspace = true
media.workspace = true
metal = "0.29"
objc = "0.2"
metal.workspace = true
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "macos"))'.dependencies]
pathfinder_geometry = "0.5"

View File

@@ -815,14 +815,8 @@ where
Bounds { origin, size }
}
/// Constructs a `Bounds` from a corner point and size.
///
/// # Examples
///
/// ```
/// # use zed::{Bounds, Corner, Point};
/// todo!
/// ```
/// Constructs a `Bounds` from a corner point and size. The specified corner will be placed at
/// the specified origin.
pub fn from_corner_and_size(corner: Corner, origin: Point<T>, size: Size<T>) -> Bounds<T> {
let origin = match corner {
Corner::TopLeft => origin,
@@ -1003,6 +997,18 @@ where
size: self.size.clone() + size(double_amount.clone(), double_amount),
}
}
/// Extends the bounds different amounts in each direction.
pub fn extend(&self, amount: Edges<T>) -> Bounds<T> {
Bounds {
origin: self.origin.clone() - point(amount.left.clone(), amount.top.clone()),
size: self.size.clone()
+ size(
amount.left.clone() + amount.right.clone(),
amount.top.clone() + amount.bottom.clone(),
),
}
}
}
impl<T> Bounds<T>
@@ -1097,6 +1103,21 @@ impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output =
}
}
impl<T> Bounds<T>
where
T: Clone + Debug + Add<T, Output = T> + Sub<T, Output = T> + Default,
{
/// Computes the space available within outer bounds.
pub fn space_within(&self, outer: &Self) -> Edges<T> {
Edges {
top: self.top().clone() - outer.top().clone(),
right: outer.right().clone() - self.right().clone(),
bottom: outer.bottom().clone() - self.bottom().clone(),
left: self.left().clone() - outer.left().clone(),
}
}
}
impl<T, Rhs> Mul<Rhs> for Bounds<T>
where
T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,

View File

@@ -344,15 +344,15 @@ impl<T> Flatten<T> for Result<T> {
}
}
/// Information about the GPU GPUI is running on.
#[derive(Default, Debug)]
/// Information about the GPU GPUI is running on
pub struct GPUSpecs {
/// true if the GPU is really a fake (like llvmpipe) running on the CPU
pub struct GpuSpecs {
/// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU.
pub is_software_emulated: bool,
/// Name of the device as reported by vulkan
/// The name of the device, as reported by Vulkan.
pub device_name: String,
/// Name of the driver as reported by vulkan
/// The name of the driver, as reported by Vulkan.
pub driver_name: String,
/// Further driver info as reported by vulkan
/// Further information about the driver, as reported by Vulkan.
pub driver_info: String,
}

View File

@@ -28,7 +28,7 @@ mod windows;
use crate::{
point, Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GPUSpecs, GlyphId,
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, GpuSpecs,
ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImage,
RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, SvgRenderer,
SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
@@ -432,7 +432,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
WindowControls::default()
}
fn set_client_inset(&self, _inset: Pixels) {}
fn gpu_specs(&self) -> Option<GPUSpecs>;
fn gpu_specs(&self) -> Option<GpuSpecs>;
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>);

View File

@@ -1,5 +1,11 @@
#[cfg(target_os = "macos")]
mod apple_compat;
mod blade_atlas;
mod blade_context;
mod blade_renderer;
#[cfg(target_os = "macos")]
pub(crate) use apple_compat::*;
pub(crate) use blade_atlas::*;
pub(crate) use blade_context::*;
pub(crate) use blade_renderer::*;

View File

@@ -0,0 +1,60 @@
use super::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use blade_graphics as gpu;
use std::{ffi::c_void, ptr::NonNull};
#[derive(Clone)]
pub struct Context {
inner: BladeContext,
}
impl Default for Context {
fn default() -> Self {
Self {
inner: BladeContext::new().unwrap(),
}
}
}
pub type Renderer = BladeRenderer;
pub unsafe fn new_renderer(
context: Context,
_native_window: *mut c_void,
native_view: *mut c_void,
bounds: crate::Size<f32>,
transparent: bool,
) -> Renderer {
use raw_window_handle as rwh;
struct RawWindow {
view: *mut c_void,
}
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let view = NonNull::new(self.view).unwrap();
let handle = rwh::AppKitWindowHandle::new(view);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let handle = rwh::AppKitDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
BladeRenderer::new(
&context.inner,
&RawWindow {
view: native_view as *mut _,
},
BladeSurfaceConfig {
size: gpu::Extent {
width: bounds.width as u32,
height: bounds.height as u32,
depth: 1,
},
transparent,
},
)
.unwrap()
}

View File

@@ -268,7 +268,7 @@ impl BladeAtlasState {
fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
self.flush_initializations(encoder);
let mut transfers = encoder.transfer();
let mut transfers = encoder.transfer("atlas");
for upload in self.uploads.drain(..) {
let texture = &self.storage[upload.id];
transfers.copy_buffer_to_texture(

View File

@@ -0,0 +1,24 @@
use blade_graphics as gpu;
use std::sync::Arc;
#[cfg_attr(target_os = "macos", derive(Clone))]
pub struct BladeContext {
pub(super) gpu: Arc<gpu::Context>,
}
impl BladeContext {
pub fn new() -> anyhow::Result<Self> {
let gpu = Arc::new(
unsafe {
gpu::Context::init(gpu::ContextDesc {
presentation: true,
validation: false,
device_id: 0, //TODO: hook up to user settings
..Default::default()
})
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
Ok(Self { gpu })
}
}

View File

@@ -1,9 +1,9 @@
// Doing `if let` gives you nice scoping with passes/encoders
#![allow(irrefutable_let_patterns)]
use super::{BladeAtlas, PATH_TEXTURE_FORMAT};
use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT};
use crate::{
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GPUSpecs,
AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs,
MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
ScaledPixels, Scene, Shadow, Size, Underline,
};
@@ -11,8 +11,6 @@ use bytemuck::{Pod, Zeroable};
use collections::HashMap;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
#[cfg(target_os = "macos")]
use std::{ffi::c_void, ptr::NonNull};
use blade_graphics as gpu;
use blade_util::{BufferBelt, BufferBeltDescriptor};
@@ -20,66 +18,6 @@ use std::{mem, sync::Arc};
const MAX_FRAME_TIME_MS: u32 = 10000;
#[cfg(target_os = "macos")]
#[derive(Clone, Default)]
pub struct Context {}
#[cfg(target_os = "macos")]
pub type Renderer = BladeRenderer;
#[cfg(target_os = "macos")]
pub unsafe fn new_renderer(
_context: self::Context,
_native_window: *mut c_void,
native_view: *mut c_void,
bounds: crate::Size<f32>,
transparent: bool,
) -> Renderer {
use raw_window_handle as rwh;
struct RawWindow {
view: *mut c_void,
}
impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let view = NonNull::new(self.view).unwrap();
let handle = rwh::AppKitWindowHandle::new(view);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
}
}
impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let handle = rwh::AppKitDisplayHandle::new();
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
}
}
let gpu = Arc::new(
gpu::Context::init_windowed(
&RawWindow {
view: native_view as *mut _,
},
gpu::ContextDesc {
validation: cfg!(debug_assertions),
capture: false,
overlay: false,
},
)
.unwrap(),
);
BladeRenderer::new(
gpu,
BladeSurfaceConfig {
size: gpu::Extent {
width: bounds.width as u32,
height: bounds.height as u32,
depth: 1,
},
transparent,
},
)
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct GlobalParams {
@@ -354,10 +292,14 @@ pub struct BladeSurfaceConfig {
pub transparent: bool,
}
//Note: we could see some of these fields moved into `BladeContext`
// so that they are shared between windows. E.g. `pipelines`.
// But that is complicated by the fact that pipelines depend on
// the format and alpha mode.
pub struct BladeRenderer {
gpu: Arc<gpu::Context>,
surface: gpu::Surface,
surface_config: gpu::SurfaceConfig,
alpha_mode: gpu::AlphaMode,
command_encoder: gpu::CommandEncoder,
last_sync_point: Option<gpu::SyncPoint>,
pipelines: BladePipelines,
@@ -370,7 +312,11 @@ pub struct BladeRenderer {
}
impl BladeRenderer {
pub fn new(gpu: Arc<gpu::Context>, config: BladeSurfaceConfig) -> Self {
pub fn new<I: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle>(
context: &BladeContext,
window: &I,
config: BladeSurfaceConfig,
) -> anyhow::Result<Self> {
let surface_config = gpu::SurfaceConfig {
size: config.size,
usage: gpu::TextureUsage::TARGET,
@@ -379,20 +325,23 @@ impl BladeRenderer {
allow_exclusive_full_screen: false,
transparent: config.transparent,
};
let surface_info = gpu.resize(surface_config);
let surface = context
.gpu
.create_surface_configured(window, surface_config)
.unwrap();
let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc {
name: "main",
buffer_count: 2,
});
let pipelines = BladePipelines::new(&gpu, surface_info);
let pipelines = BladePipelines::new(&context.gpu, surface.info());
let instance_belt = BufferBelt::new(BufferBeltDescriptor {
memory: gpu::Memory::Shared,
min_chunk_size: 0x1000,
alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
});
let atlas = Arc::new(BladeAtlas::new(&gpu));
let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
let atlas = Arc::new(BladeAtlas::new(&context.gpu));
let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc {
name: "atlas",
mag_filter: gpu::FilterMode::Linear,
min_filter: gpu::FilterMode::Linear,
@@ -402,13 +351,13 @@ impl BladeRenderer {
#[cfg(target_os = "macos")]
let core_video_texture_cache = unsafe {
use foreign_types::ForeignType as _;
CVMetalTextureCache::new(gpu.metal_device().as_ptr()).unwrap()
CVMetalTextureCache::new(context.gpu.metal_device().as_ptr()).unwrap()
};
Self {
gpu,
Ok(Self {
gpu: Arc::clone(&context.gpu),
surface,
surface_config,
alpha_mode: surface_info.alpha,
command_encoder,
last_sync_point: None,
pipelines,
@@ -418,7 +367,7 @@ impl BladeRenderer {
atlas_sampler,
#[cfg(target_os = "macos")]
core_video_texture_cache,
}
})
}
fn wait_for_gpu(&mut self) {
@@ -452,7 +401,8 @@ impl BladeRenderer {
if always_resize || gpu_size != self.surface_config.size {
self.wait_for_gpu();
self.surface_config.size = gpu_size;
self.gpu.resize(self.surface_config);
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
}
}
@@ -460,10 +410,10 @@ impl BladeRenderer {
if transparent != self.surface_config.transparent {
self.wait_for_gpu();
self.surface_config.transparent = transparent;
let surface_info = self.gpu.resize(self.surface_config);
self.gpu
.reconfigure_surface(&mut self.surface, self.surface_config);
self.pipelines.destroy(&self.gpu);
self.pipelines = BladePipelines::new(&self.gpu, surface_info);
self.alpha_mode = surface_info.alpha;
self.pipelines = BladePipelines::new(&self.gpu, self.surface.info());
}
}
@@ -477,10 +427,10 @@ impl BladeRenderer {
}
#[cfg_attr(target_os = "macos", allow(dead_code))]
pub fn gpu_specs(&self) -> GPUSpecs {
pub fn gpu_specs(&self) -> GpuSpecs {
let info = self.gpu.device_information();
GPUSpecs {
GpuSpecs {
is_software_emulated: info.is_software_emulated,
device_name: info.device_name.clone(),
driver_name: info.driver_name.clone(),
@@ -490,13 +440,13 @@ impl BladeRenderer {
#[cfg(target_os = "macos")]
pub fn layer(&self) -> metal::MetalLayer {
self.gpu.metal_layer().unwrap()
self.surface.metal_layer()
}
#[cfg(target_os = "macos")]
pub fn layer_ptr(&self) -> *mut metal::CAMetalLayer {
use metal::foreign_types::ForeignType as _;
self.gpu.metal_layer().unwrap().as_ptr()
self.surface.metal_layer().as_ptr()
}
#[profiling::function]
@@ -538,14 +488,17 @@ impl BladeRenderer {
};
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: tex_info.raw_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
});
let mut pass = self.command_encoder.render(
"paths",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: tex_info.raw_view,
init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
},
);
let mut encoder = pass.with(&self.pipelines.path_rasterization);
encoder.bind(
@@ -566,6 +519,7 @@ impl BladeRenderer {
self.instance_belt.destroy(&self.gpu);
self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu);
self.gpu.destroy_surface(&mut self.surface);
}
pub fn draw(&mut self, scene: &Scene) {
@@ -575,7 +529,7 @@ impl BladeRenderer {
let frame = {
profiling::scope!("acquire frame");
self.gpu.acquire_frame()
self.surface.acquire_frame()
};
self.command_encoder.init_texture(frame.texture());
@@ -584,21 +538,24 @@ impl BladeRenderer {
self.surface_config.size.width as f32,
self.surface_config.size.height as f32,
],
premultiplied_alpha: match self.alpha_mode {
premultiplied_alpha: match self.surface.info().alpha {
gpu::AlphaMode::Ignored | gpu::AlphaMode::PostMultiplied => 0,
gpu::AlphaMode::PreMultiplied => 1,
},
pad: 0,
};
if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
}) {
if let mut pass = self.command_encoder.render(
"main",
gpu::RenderTargetSet {
colors: &[gpu::RenderTarget {
view: frame.texture_view(),
init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
finish_op: gpu::FinishOp::Store,
}],
depth_stencil: None,
},
) {
profiling::scope!("render pass");
for batch in scene.batches() {
match batch {

View File

@@ -1,52 +1,45 @@
#![allow(unused)]
use std::any::{type_name, Any};
use std::cell::{self, RefCell};
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
use std::panic::{AssertUnwindSafe, Location};
use std::rc::Weak;
use std::{
env,
panic::AssertUnwindSafe,
path::{Path, PathBuf},
process::Command,
rc::Rc,
sync::Arc,
};
#[cfg(any(feature = "wayland", feature = "x11"))]
use std::{
ffi::OsString,
fs::File,
io::Read as _,
os::fd::{AsFd, AsRawFd, FromRawFd},
time::Duration,
};
use anyhow::{anyhow, Context as _};
use async_task::Runnable;
use calloop::channel::Channel;
use calloop::{EventLoop, LoopHandle, LoopSignal};
use flume::{Receiver, Sender};
use calloop::{channel::Channel, LoopSignal};
use futures::{channel::oneshot, future::FutureExt};
use parking_lot::Mutex;
use util::ResultExt;
use util::ResultExt as _;
#[cfg(any(feature = "wayland", feature = "x11"))]
use xkbcommon::xkb::{self, Keycode, Keysym, State};
use crate::platform::NoopTextSystem;
use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu,
PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem,
PlatformWindow, Point, PromptLevel, Result, ScreenCaptureSource, SemanticVersion, SharedString,
Size, Task, WindowAppearance, WindowOptions, WindowParams,
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
Pixels, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Point, Result,
ScreenCaptureSource, Task, WindowAppearance, WindowParams,
};
#[cfg(any(feature = "wayland", feature = "x11"))]
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
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
#[cfg(any(feature = "wayland", feature = "x11"))]
const FILE_PICKER_PORTAL_MISSING: &str =
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
@@ -54,8 +47,9 @@ pub trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
#[allow(unused)]
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn open_window(
&self,
@@ -98,9 +92,9 @@ pub(crate) struct LinuxCommon {
impl LinuxCommon {
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
#[cfg(any(feature = "wayland", feature = "x11"))]
let text_system = Arc::new(crate::CosmicTextSystem::new());
#[cfg(not(any(feature = "wayland", feature = "x11")))]
let text_system = Arc::new(crate::NoopTextSystem::new());
@@ -218,7 +212,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
}
fn activate(&self, ignoring_other_apps: bool) {
fn activate(&self, _ignoring_other_apps: bool) {
log::info!("activate is not implemented on Linux, ignoring the call")
}
@@ -281,7 +275,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let (done_tx, done_rx) = oneshot::channel();
#[cfg(not(any(feature = "wayland", feature = "x11")))]
done_tx.send(Ok(None));
let _ = (done_tx.send(Ok(None)), options);
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
@@ -306,7 +300,7 @@ impl<P: LinuxClient + 'static> Platform for P {
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
err => err.into(),
};
done_tx.send(Err(result));
let _ = done_tx.send(Err(result));
return;
}
};
@@ -322,7 +316,7 @@ impl<P: LinuxClient + 'static> Platform for P {
Err(ashpd::Error::Response(_)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result);
let _ = done_tx.send(result);
})
.detach();
done_rx
@@ -332,7 +326,7 @@ impl<P: LinuxClient + 'static> Platform for P {
let (done_tx, done_rx) = oneshot::channel();
#[cfg(not(any(feature = "wayland", feature = "x11")))]
done_tx.send(Ok(None));
let _ = (done_tx.send(Ok(None)), directory);
#[cfg(any(feature = "wayland", feature = "x11"))]
self.foreground_executor()
@@ -356,7 +350,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
err => err.into(),
};
done_tx.send(Err(result));
let _ = done_tx.send(Err(result));
return;
}
};
@@ -369,7 +363,7 @@ impl<P: LinuxClient + 'static> Platform for P {
Err(ashpd::Error::Response(_)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result);
let _ = done_tx.send(result);
}
})
.detach();
@@ -426,7 +420,7 @@ impl<P: LinuxClient + 'static> Platform for P {
fn app_path(&self) -> Result<PathBuf> {
// get the path of the executable of the current process
let exe_path = std::env::current_exe()?;
let exe_path = env::current_exe()?;
Ok(exe_path)
}
@@ -440,9 +434,9 @@ impl<P: LinuxClient + 'static> Platform for P {
self.with_common(|common| Some(common.menus.clone()))
}
fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap) {}
fn set_dock_menu(&self, _menu: Vec<MenuItem>, _keymap: &Keymap) {}
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
Err(anyhow::Error::msg(
"Platform<LinuxPlatform>::path_for_auxiliary_executable is not implemented yet",
))
@@ -614,6 +608,7 @@ pub(super) fn reveal_path_internal(
.detach();
}
#[allow(unused)]
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
let diff = a - b;
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
@@ -622,7 +617,7 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
#[cfg(any(feature = "wayland", feature = "x11"))]
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
let mut locales = Vec::default();
if let Some(locale) = std::env::var_os("LC_CTYPE") {
if let Some(locale) = env::var_os("LC_CTYPE") {
locales.push(locale);
}
locales.push(OsString::from("C"));
@@ -650,6 +645,7 @@ pub(super) unsafe fn read_fd(mut fd: filedescriptor::FileDescriptor) -> Result<V
}
impl CursorStyle {
#[allow(unused)]
pub(super) fn to_icon_name(&self) -> String {
// Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME)
// and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
@@ -682,10 +678,12 @@ impl CursorStyle {
}
#[cfg(any(feature = "wayland", feature = "x11"))]
impl Keystroke {
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
let mut modifiers = modifiers;
impl crate::Keystroke {
pub(super) fn from_xkb(
state: &State,
mut modifiers: crate::Modifiers,
keycode: Keycode,
) -> Self {
let key_utf32 = state.key_get_utf32(keycode);
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
@@ -759,7 +757,7 @@ impl Keystroke {
let key_char =
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
Keystroke {
Self {
modifiers,
key,
key_char,
@@ -776,7 +774,6 @@ impl Keystroke {
Keysym::dead_acute => Some("´".to_owned()),
Keysym::dead_circumflex => Some("^".to_owned()),
Keysym::dead_tilde => Some("~".to_owned()),
Keysym::dead_perispomeni => Some("͂".to_owned()),
Keysym::dead_macron => Some("¯".to_owned()),
Keysym::dead_breve => Some("˘".to_owned()),
Keysym::dead_abovedot => Some("˙".to_owned()),
@@ -794,9 +791,7 @@ impl Keystroke {
Keysym::dead_horn => Some("̛".to_owned()),
Keysym::dead_stroke => Some("̶̶".to_owned()),
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
Keysym::dead_psili => Some("᾿".to_owned()),
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
Keysym::dead_dasia => Some("".to_owned()),
Keysym::dead_doublegrave => Some("̏".to_owned()),
Keysym::dead_belowring => Some("˳".to_owned()),
Keysym::dead_belowmacron => Some("̱".to_owned()),
@@ -830,7 +825,7 @@ impl Keystroke {
}
#[cfg(any(feature = "wayland", feature = "x11"))]
impl Modifiers {
impl crate::Modifiers {
pub(super) fn from_xkb(keymap_state: &State) -> Self {
let shift = keymap_state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
let alt = keymap_state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
@@ -838,7 +833,7 @@ impl Modifiers {
keymap_state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
let platform =
keymap_state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
Modifiers {
Self {
shift,
alt,
control,

View File

@@ -1,12 +1,16 @@
use std::cell::{RefCell, RefMut};
use std::hash::Hash;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
use std::{
cell::{RefCell, RefMut},
hash::Hash,
os::fd::{AsRawFd, BorrowedFd},
path::PathBuf,
rc::{Rc, Weak},
time::{Duration, Instant},
};
use calloop::timer::{TimeoutAction, Timer};
use calloop::{EventLoop, LoopHandle};
use calloop::{
timer::{TimeoutAction, Timer},
EventLoop, LoopHandle,
};
use calloop_wayland_source::WaylandSource;
use collections::HashMap;
use filedescriptor::Pipe;
@@ -64,30 +68,28 @@ use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::wayland::clipboard::{
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
};
use crate::platform::linux::wayland::cursor::Cursor;
use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE},
cursor::Cursor,
serial::{SerialKind, SerialTracker},
window::WaylandWindow,
},
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
LinuxClient,
};
use crate::platform::PlatformWindow;
use crate::platform::{blade::BladeContext, PlatformWindow};
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
DOUBLE_CLICK_INTERVAL, SCROLL_LINES,
point, px, size, AnyWindowHandle, Bounds, CursorStyle, DevicePixels, DisplayId, FileDropEvent,
ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels,
ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, DOUBLE_CLICK_INTERVAL,
SCROLL_LINES,
};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta,
ScrollWheelEvent, TouchPhase,
};
use crate::{LinuxCommon, WindowParams};
/// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8;
@@ -186,6 +188,7 @@ pub struct Output {
pub(crate) struct WaylandClientState {
serial_tracker: SerialTracker,
globals: Globals,
gpu_context: BladeContext,
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
wl_pointer: Option<wl_pointer::WlPointer>,
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
@@ -459,6 +462,8 @@ impl WaylandClient {
})
.unwrap();
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let seat = seat.unwrap();
let globals = Globals::new(
globals,
@@ -512,6 +517,7 @@ impl WaylandClient {
let mut state = Rc::new(RefCell::new(WaylandClientState {
serial_tracker: SerialTracker::new(),
globals,
gpu_context,
wl_seat: seat,
wl_pointer: None,
wl_keyboard: None,
@@ -627,6 +633,7 @@ impl LinuxClient for WaylandClient {
let (window, surface_id) = WaylandWindow::new(
handle,
state.globals.clone(),
&state.gpu_context,
WaylandClientStatePtr(Rc::downgrade(&self.0)),
params,
state.common.appearance,

View File

@@ -1,8 +1,10 @@
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::c_void;
use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::Arc;
use std::{
cell::{Ref, RefCell, RefMut},
ffi::c_void,
ptr::NonNull,
rc::Rc,
sync::Arc,
};
use blade_graphics as gpu;
use collections::HashMap;
@@ -19,13 +21,14 @@ use wayland_protocols::xdg::shell::client::xdg_surface;
use wayland_protocols::xdg::shell::client::xdg_toplevel::{self};
use wayland_protocols_plasma::blur::client::org_kde_kwin_blur;
use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
use crate::platform::linux::wayland::display::WaylandDisplay;
use crate::platform::linux::wayland::serial::SerialKind;
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::platform::{
blade::{BladeContext, BladeRenderer, BladeSurfaceConfig},
linux::wayland::{display::WaylandDisplay, serial::SerialKind},
PlatformAtlas, PlatformInputHandler, PlatformWindow,
};
use crate::scene::Scene;
use crate::{
px, size, AnyWindowHandle, Bounds, Decorations, GPUSpecs, Globals, Modifiers, Output, Pixels,
px, size, AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
PlatformDisplay, PlatformInput, Point, PromptLevel, RequestFrameOptions, ResizeEdge,
ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowParams,
@@ -123,37 +126,28 @@ impl WaylandWindowState {
viewport: Option<wp_viewport::WpViewport>,
client: WaylandClientStatePtr,
globals: Globals,
gpu_context: &BladeContext,
options: WindowParams,
) -> anyhow::Result<Self> {
let raw = RawWindow {
window: surface.id().as_ptr().cast::<c_void>(),
display: surface
.backend()
.upgrade()
.unwrap()
.display_ptr()
.cast::<c_void>(),
};
let gpu = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
overlay: false,
},
)
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
size: gpu::Extent {
width: options.bounds.size.width.0 as u32,
height: options.bounds.size.height.0 as u32,
depth: 1,
},
transparent: true,
let renderer = {
let raw_window = RawWindow {
window: surface.id().as_ptr().cast::<c_void>(),
display: surface
.backend()
.upgrade()
.unwrap()
.display_ptr()
.cast::<c_void>(),
};
let config = BladeSurfaceConfig {
size: gpu::Extent {
width: options.bounds.size.width.0 as u32,
height: options.bounds.size.height.0 as u32,
depth: 1,
},
transparent: true,
};
BladeRenderer::new(gpu_context, &raw_window, config)?
};
Ok(Self {
@@ -168,7 +162,7 @@ impl WaylandWindowState {
globals,
outputs: HashMap::default(),
display: None,
renderer: BladeRenderer::new(gpu, config),
renderer,
bounds: options.bounds,
scale: 1.0,
input_handler: None,
@@ -266,6 +260,7 @@ impl WaylandWindow {
pub fn new(
handle: AnyWindowHandle,
globals: Globals,
gpu_context: &BladeContext,
client: WaylandClientStatePtr,
params: WindowParams,
appearance: WindowAppearance,
@@ -308,6 +303,7 @@ impl WaylandWindow {
viewport,
client,
globals,
gpu_context,
params,
)?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())),
@@ -1019,7 +1015,7 @@ impl PlatformWindow for WaylandWindow {
state.client.update_ime_position(bounds);
}
fn gpu_specs(&self) -> Option<GPUSpecs> {
fn gpu_specs(&self) -> Option<GpuSpecs> {
self.borrow().renderer.gpu_specs().into()
}
}

View File

@@ -1,13 +1,17 @@
use core::str;
use std::cell::RefCell;
use std::collections::{BTreeMap, HashSet};
use std::ops::Deref;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::time::{Duration, Instant};
use std::{
cell::RefCell,
collections::{BTreeMap, HashSet},
ops::Deref,
path::PathBuf,
rc::{Rc, Weak},
time::{Duration, Instant},
};
use calloop::generic::{FdWrapper, Generic};
use calloop::{EventLoop, LoopHandle, RegistrationToken};
use calloop::{
generic::{FdWrapper, Generic},
EventLoop, LoopHandle, RegistrationToken,
};
use anyhow::Context as _;
use collections::HashMap;
@@ -15,44 +19,49 @@ use http_client::Url;
use smallvec::SmallVec;
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::cursor;
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xinput::ConnectionExt;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::{
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _,
EventMask, KeyPressEvent,
use x11rb::{
connection::{Connection, RequestConnection},
cursor,
errors::ConnectionError,
protocol::randr::ConnectionExt as _,
protocol::xinput::ConnectionExt,
protocol::xkb::ConnectionExt as _,
protocol::xproto::{
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent,
ConnectionExt as _, EventMask, KeyPressEvent,
},
protocol::{randr, render, xinput, xkb, xproto, Event},
resource_manager::Database,
wrapper::ConnectionExt as _,
xcb_ffi::XCBConnection,
};
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
use x11rb::resource_manager::Database;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::xcb_ffi::XCBConnection;
use xim::{x11rb::X11rbClient, Client};
use xim::{AttributeName, InputStyle};
use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle};
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask};
use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
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};
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
use crate::platform::{
blade::BladeContext,
linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal,
platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
reveal_path_internal,
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
LinuxClient,
},
LinuxCommon, PlatformWindow,
};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
Platform, PlatformDisplay, PlatformInput, Point, RequestFrameOptions, ScaledPixels,
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
/// Value for DeviceId parameters which selects all devices.
@@ -158,6 +167,8 @@ pub struct X11ClientState {
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
gpu_context: BladeContext,
pub(crate) scale_factor: f32,
xkb_context: xkbc::Context,
@@ -360,6 +371,8 @@ impl X11Client {
let compose_state = get_xkb_compose_state(&xkb_context);
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let scale_factor = resource_database
.get_value("Xft.dpi", "Xft.dpi")
.ok()
@@ -428,6 +441,7 @@ impl X11Client {
last_mouse_button: None,
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
gpu_context,
scale_factor,
xkb_context,
@@ -1299,6 +1313,7 @@ impl LinuxClient for X11Client {
handle,
X11ClientStatePtr(Rc::downgrade(&self.0)),
state.common.foreground_executor.clone(),
&state.gpu_context,
params,
&state.xcb_connection,
state.client_side_decorations_supported,

View File

@@ -1,8 +1,8 @@
use anyhow::{anyhow, Context};
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig},
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs,
Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformWindow, Point, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
@@ -247,7 +247,6 @@ pub struct X11WindowState {
x_root_window: xproto::Window,
pub(crate) counter_id: sync::Counter,
pub(crate) last_sync_counter: Option<sync::Int64>,
_raw: RawWindow,
bounds: Bounds<Pixels>,
scale_factor: f32,
renderer: BladeRenderer,
@@ -358,6 +357,7 @@ impl X11WindowState {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
gpu_context: &BladeContext,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -555,50 +555,39 @@ impl X11WindowState {
xcb.flush().with_context(|| "X11 Flush failed.")?;
let raw = RawWindow {
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(xcb)
as *mut _,
screen_id: x_screen_index,
window_id: x_window,
visual_id: visual.id,
let renderer = {
let raw_window = RawWindow {
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
xcb,
) as *mut _,
screen_id: x_screen_index,
window_id: x_window,
visual_id: visual.id,
};
let config = BladeSurfaceConfig {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb, x_window)?,
// We set it to transparent by default, even if we have client-side
// decorations, since those seem to work on X11 even without `true` here.
// If the window appearance changes, then the renderer will get updated
// too
transparent: false,
};
BladeRenderer::new(gpu_context, &raw_window, config)?
};
let gpu = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
overlay: false,
},
)
}
.map_err(|e| anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb, x_window)?,
// We set it to transparent by default, even if we have client-side
// decorations, since those seem to work on X11 even without `true` here.
// If the window appearance changes, then the renderer will get updated
// too
transparent: false,
};
check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
Ok(Self {
client,
executor,
display,
_raw: raw,
x_root_window: visual_set.root,
bounds: bounds.to_pixels(scale_factor),
scale_factor,
renderer: BladeRenderer::new(gpu, config),
renderer,
atoms: *atoms,
input_handler: None,
active: false,
@@ -716,6 +705,7 @@ impl X11Window {
handle: AnyWindowHandle,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
gpu_context: &BladeContext,
params: WindowParams,
xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool,
@@ -730,6 +720,7 @@ impl X11Window {
handle,
client,
executor,
gpu_context,
params,
xcb,
client_side_decorations_supported,
@@ -1215,7 +1206,7 @@ impl PlatformWindow for X11Window {
title.as_bytes(),
),
)
.unwrap();
.log_err();
check_reply(
|| "X11 ChangeProperty8 on _NET_WM_NAME failed.",
@@ -1227,8 +1218,8 @@ impl PlatformWindow for X11Window {
title.as_bytes(),
),
)
.unwrap();
self.flush().unwrap();
.log_err();
self.flush().log_err();
}
fn set_app_id(&mut self, app_id: &str) {
@@ -1536,7 +1527,7 @@ impl PlatformWindow for X11Window {
client.update_ime_position(bounds);
}
fn gpu_specs(&self) -> Option<GPUSpecs> {
fn gpu_specs(&self) -> Option<GpuSpecs> {
self.0.state.borrow().renderer.gpu_specs().into()
}
}

View File

@@ -1102,7 +1102,7 @@ impl PlatformWindow for MacWindow {
self.0.lock().renderer.sprite_atlas().clone()
}
fn gpu_specs(&self) -> Option<crate::GPUSpecs> {
fn gpu_specs(&self) -> Option<crate::GpuSpecs> {
None
}

View File

@@ -1,5 +1,5 @@
use crate::{
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GPUSpecs,
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowParams,
@@ -276,7 +276,7 @@ impl PlatformWindow for TestWindow {
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {}
fn gpu_specs(&self) -> Option<GPUSpecs> {
fn gpu_specs(&self) -> Option<GpuSpecs> {
None
}
}

View File

@@ -28,11 +28,12 @@ use windows::{
UI::ViewManagement::UISettings,
};
use crate::*;
use crate::{platform::blade::BladeContext, *};
pub(crate) struct WindowsPlatform {
state: RefCell<WindowsPlatformState>,
raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
gpu_context: BladeContext,
// The below members will never change throughout the entire lifecycle of the app.
icon: HICON,
main_receiver: flume::Receiver<Runnable>,
@@ -94,12 +95,14 @@ impl WindowsPlatform {
let icon = load_icon().unwrap_or_default();
let state = RefCell::new(WindowsPlatformState::new());
let raw_window_handles = RwLock::new(SmallVec::new());
let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
let validation_number = rand::random::<usize>();
Self {
state,
raw_window_handles,
gpu_context,
icon,
main_receiver,
dispatch_event,
@@ -344,7 +347,12 @@ impl Platform for WindowsPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> {
let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
let window = WindowsWindow::new(
handle,
options,
self.generate_creation_info(),
&self.gpu_context,
)?;
let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle);

View File

@@ -27,7 +27,7 @@ use windows::{
},
};
use crate::platform::blade::BladeRenderer;
use crate::platform::blade::{BladeContext, BladeRenderer};
use crate::*;
pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
@@ -78,6 +78,7 @@ impl WindowsWindowState {
cs: &CREATESTRUCTW,
current_cursor: HCURSOR,
display: WindowsDisplay,
gpu_context: &BladeContext,
) -> Result<Self> {
let scale_factor = {
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
@@ -94,7 +95,7 @@ impl WindowsWindowState {
};
let border_offset = WindowBorderOffset::default();
let is_minimized = None;
let renderer = windows_renderer::windows_renderer(hwnd, transparent)?;
let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
let callbacks = Callbacks::default();
let input_handler = None;
let system_key_handled = false;
@@ -227,6 +228,7 @@ impl WindowsWindowStatePtr {
cs,
context.current_cursor,
context.display,
context.gpu_context,
)?);
Ok(Rc::new_cyclic(|this| Self {
@@ -340,7 +342,7 @@ pub(crate) struct Callbacks {
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
}
struct WindowCreateContext {
struct WindowCreateContext<'a> {
inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
handle: AnyWindowHandle,
hide_title_bar: bool,
@@ -352,6 +354,7 @@ struct WindowCreateContext {
windows_version: WindowsVersion,
validation_number: usize,
main_receiver: flume::Receiver<Runnable>,
gpu_context: &'a BladeContext,
}
impl WindowsWindow {
@@ -359,6 +362,7 @@ impl WindowsWindow {
handle: AnyWindowHandle,
params: WindowParams,
creation_info: WindowCreationInfo,
gpu_context: &BladeContext,
) -> Result<Self> {
let WindowCreationInfo {
icon,
@@ -410,6 +414,7 @@ impl WindowsWindow {
windows_version,
validation_number,
main_receiver,
gpu_context,
};
let lpparam = Some(&context as *const _ as *const _);
let creation_result = unsafe {
@@ -770,7 +775,7 @@ impl PlatformWindow for WindowsWindow {
self.0.hwnd
}
fn gpu_specs(&self) -> Option<GPUSpecs> {
fn gpu_specs(&self) -> Option<GpuSpecs> {
Some(self.0.state.borrow().renderer.gpu_specs())
}
@@ -1236,38 +1241,24 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
}
mod windows_renderer {
use std::{num::NonZeroIsize, sync::Arc};
use blade_graphics as gpu;
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use raw_window_handle as rwh;
use std::num::NonZeroIsize;
use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
use crate::{
get_window_long,
platform::blade::{BladeRenderer, BladeSurfaceConfig},
};
use crate::get_window_long;
pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> anyhow::Result<BladeRenderer> {
pub(super) fn init(
context: &BladeContext,
hwnd: HWND,
transparent: bool,
) -> anyhow::Result<BladeRenderer> {
let raw = RawWindow { hwnd };
let gpu: Arc<gpu::Context> = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
overlay: false,
},
)
}
.map_err(|e| anyhow::anyhow!("{:?}", e))?,
);
let config = BladeSurfaceConfig {
size: gpu::Extent::default(),
size: Default::default(),
transparent,
};
Ok(BladeRenderer::new(gpu, config))
BladeRenderer::new(context, &raw, config)
}
struct RawWindow {

View File

@@ -3,7 +3,7 @@ use crate::{
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Background, Bounds,
BoxShadow, Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, Flatten, FontId, GPUSpecs, Global, GlobalElementId, GlyphId, Hsla, InputHandler,
FileDropEvent, Flatten, FontId, Global, GlobalElementId, GlyphId, GpuSpecs, Hsla, InputHandler,
IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke, KeystrokeEvent,
KeystrokeObserver, LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers,
ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent,
@@ -3808,7 +3808,7 @@ impl<'a> WindowContext<'a> {
/// Read information about the GPU backing this window.
/// Currently returns None on Mac and Windows.
pub fn gpu_specs(&self) -> Option<GPUSpecs> {
pub fn gpu_specs(&self) -> Option<GpuSpecs> {
self.window.platform_window.gpu_specs()
}
}

View File

@@ -1,7 +1,6 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use std::sync::OnceLock;
use std::{cell::RefCell, sync::LazyLock};
use anyhow::Result;
use markup5ever_rcdom::{Handle, NodeData};
@@ -10,13 +9,14 @@ use regex::Regex;
use crate::html_element::HtmlElement;
fn empty_line_regex() -> &'static Regex {
static REGEX: OnceLock<Regex> = OnceLock::new();
REGEX.get_or_init(|| Regex::new(r"^\s*$").unwrap())
static REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^\s*$").expect("Failed to create empty_line_regex"));
&REGEX
}
fn more_than_three_newlines_regex() -> &'static Regex {
static REGEX: OnceLock<Regex> = OnceLock::new();
REGEX.get_or_init(|| Regex::new(r"\n{3,}").unwrap())
static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
&REGEX
}
pub enum StartTagOutcome {

View File

@@ -20,6 +20,7 @@ pub struct InlineCompletion {
pub trait InlineCompletionProvider: 'static + Sized {
fn name() -> &'static str;
fn display_name() -> &'static str;
fn show_completions_in_menu() -> bool;
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -59,6 +60,7 @@ pub trait InlineCompletionProviderHandle {
cursor_position: language::Anchor,
cx: &AppContext,
) -> bool;
fn show_completions_in_menu(&self) -> bool;
fn refresh(
&self,
buffer: Model<Buffer>,
@@ -95,6 +97,10 @@ where
T::display_name()
}
fn show_completions_in_menu(&self) -> bool {
T::show_completions_in_menu()
}
fn is_enabled(
&self,
buffer: &Model<Buffer>,

View File

@@ -19,7 +19,7 @@ anyhow.workspace = true
core-foundation.workspace = true
ctor.workspace = true
foreign-types = "0.5"
metal = "0.29"
metal.workspace = true
objc = "0.2"
[build-dependencies]

View File

@@ -27,13 +27,11 @@ collections.workspace = true
ctor.workspace = true
env_logger.workspace = true
futures.workspace = true
git.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
parking_lot.workspace = true
project.workspace = true
rand.workspace = true
settings.workspace = true
serde.workspace = true

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,317 +0,0 @@
use std::{
fmt::{Debug, Display},
iter::zip,
marker::PhantomData,
ops::{Add, AddAssign, Sub, SubAssign},
};
use text::{Point, PointUtf16};
#[repr(transparent)]
pub struct TypedOffset<T> {
pub value: usize,
_marker: PhantomData<T>,
}
#[repr(transparent)]
pub struct TypedPoint<T> {
pub value: Point,
_marker: PhantomData<T>,
}
#[repr(transparent)]
pub struct TypedPointUtf16<T> {
pub value: PointUtf16,
_marker: PhantomData<T>,
}
#[repr(transparent)]
pub struct TypedRow<T> {
pub value: u32,
_marker: PhantomData<T>,
}
impl<T> TypedOffset<T> {
pub fn new(offset: usize) -> Self {
Self {
value: offset,
_marker: PhantomData,
}
}
pub fn saturating_sub(self, n: TypedOffset<T>) -> Self {
Self {
value: self.value.saturating_sub(n.value),
_marker: PhantomData,
}
}
pub fn zero() -> Self {
Self::new(0)
}
pub fn is_zero(&self) -> bool {
self.value == 0
}
}
impl<T> TypedPoint<T> {
pub fn new(row: u32, column: u32) -> Self {
Self {
value: Point::new(row, column),
_marker: PhantomData,
}
}
pub fn wrap(point: Point) -> Self {
Self {
value: point,
_marker: PhantomData,
}
}
pub fn row(&self) -> u32 {
self.value.row
}
pub fn column(&self) -> u32 {
self.value.column
}
pub fn zero() -> Self {
Self::wrap(Point::zero())
}
pub fn is_zero(&self) -> bool {
self.value.is_zero()
}
}
impl<T> TypedPointUtf16<T> {
pub fn new(row: u32, column: u32) -> Self {
TypedPointUtf16 {
value: PointUtf16::new(row, column),
_marker: PhantomData,
}
}
pub fn wrap(point: PointUtf16) -> Self {
Self {
value: point,
_marker: PhantomData,
}
}
}
impl<T> TypedRow<T> {
pub fn new(row: u32) -> Self {
Self {
value: row,
_marker: PhantomData,
}
}
}
impl<T> Copy for TypedOffset<T> {}
impl<T> Copy for TypedPoint<T> {}
impl<T> Copy for TypedPointUtf16<T> {}
impl<T> Copy for TypedRow<T> {}
impl<T> Clone for TypedOffset<T> {
fn clone(&self) -> Self {
Self {
value: self.value,
_marker: PhantomData,
}
}
}
impl<T> Clone for TypedPoint<T> {
fn clone(&self) -> Self {
Self {
value: self.value,
_marker: PhantomData,
}
}
}
impl<T> Clone for TypedPointUtf16<T> {
fn clone(&self) -> Self {
Self {
value: self.value,
_marker: PhantomData,
}
}
}
impl<T> Clone for TypedRow<T> {
fn clone(&self) -> Self {
Self {
value: self.value,
_marker: PhantomData,
}
}
}
impl<T> Default for TypedOffset<T> {
fn default() -> Self {
Self::new(0)
}
}
impl<T> Default for TypedPoint<T> {
fn default() -> Self {
Self::wrap(Point::default())
}
}
impl<T> Default for TypedPointUtf16<T> {
fn default() -> Self {
Self::wrap(PointUtf16::default())
}
}
impl<T> Default for TypedRow<T> {
fn default() -> Self {
Self::new(0)
}
}
impl<T> PartialOrd for TypedOffset<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.value.cmp(&other.value))
}
}
impl<T> PartialOrd for TypedPoint<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.value.cmp(&other.value))
}
}
impl<T> PartialOrd for TypedPointUtf16<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.value.cmp(&other.value))
}
}
impl<T> PartialOrd for TypedRow<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.value.cmp(&other.value))
}
}
impl<T> Ord for TypedOffset<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<T> Ord for TypedPoint<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<T> Ord for TypedPointUtf16<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<T> Ord for TypedRow<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl<T> PartialEq for TypedOffset<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> PartialEq for TypedPoint<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> PartialEq for TypedPointUtf16<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> PartialEq for TypedRow<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T> Eq for TypedOffset<T> {}
impl<T> Eq for TypedPoint<T> {}
impl<T> Eq for TypedPointUtf16<T> {}
impl<T> Eq for TypedRow<T> {}
impl<T> Debug for TypedOffset<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}Offset({})", type_name::<T>(), self.value)
}
}
impl<T> Debug for TypedPoint<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}Point({}, {})",
type_name::<T>(),
self.value.row,
self.value.column
)
}
}
impl<T> Debug for TypedRow<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}Row({})", type_name::<T>(), self.value)
}
}
impl<T> Display for TypedOffset<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.value, f)
}
}
impl<T> Display for TypedRow<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.value, f)
}
}
fn type_name<T>() -> &'static str {
std::any::type_name::<T>().split("::").last().unwrap()
}
impl<T> Add<TypedOffset<T>> for TypedOffset<T> {
type Output = Self;
fn add(self, other: Self) -> Self {
TypedOffset::new(self.value + other.value)
}
}
impl<T> Add<TypedPoint<T>> for TypedPoint<T> {
type Output = Self;
fn add(self, other: Self) -> Self {
TypedPoint::wrap(self.value + other.value)
}
}
impl<T> Sub<TypedOffset<T>> for TypedOffset<T> {
type Output = Self;
fn sub(self, other: Self) -> Self {
TypedOffset::new(self.value - other.value)
}
}
impl<T> Sub<TypedPoint<T>> for TypedPoint<T> {
type Output = Self;
fn sub(self, other: Self) -> Self {
TypedPoint::wrap(self.value - other.value)
}
}
impl<T> AddAssign<TypedOffset<T>> for TypedOffset<T> {
fn add_assign(&mut self, other: Self) {
self.value += other.value;
}
}
impl<T> AddAssign<TypedPoint<T>> for TypedPoint<T> {
fn add_assign(&mut self, other: Self) {
self.value += other.value;
}
}
impl<T> SubAssign<Self> for TypedOffset<T> {
fn sub_assign(&mut self, other: Self) {
self.value -= other.value;
}
}
impl<T> SubAssign<Self> for TypedRow<T> {
fn sub_assign(&mut self, other: Self) {
self.value -= other.value;
}
}

View File

@@ -68,6 +68,7 @@ snippet.workspace = true
snippet_provider.workspace = true
terminal.workspace = true
text.workspace = true
toml.workspace = true
util.workspace = true
url.workspace = true
which.workspace = true

View File

@@ -5,12 +5,12 @@ use crate::{
ProjectItem as _, ProjectPath,
};
use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegistry};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, bail, Context as _, Result};
use client::Client;
use collections::{hash_map, HashMap, HashSet};
use fs::Fs;
use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt};
use git::{blame::Blame, diff::BufferDiff};
use git::{blame::Blame, diff::BufferDiff, repository::RepoPath};
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Subscription,
Task, WeakModel,
@@ -24,8 +24,16 @@ use language::{
Buffer, BufferEvent, Capability, DiskState, File as _, Language, Operation,
};
use rpc::{proto, AnyProtoClient, ErrorExt as _, TypedEnvelope};
use serde::Deserialize;
use smol::channel::Receiver;
use std::{io, ops::Range, path::Path, str::FromStr as _, sync::Arc, time::Instant};
use std::{
io,
ops::Range,
path::{Path, PathBuf},
str::FromStr as _,
sync::Arc,
time::Instant,
};
use text::{BufferId, LineEnding, Rope};
use util::{debug_panic, maybe, ResultExt as _, TryFutureExt};
use worktree::{File, PathChange, ProjectEntryId, UpdatedGitRepositoriesSet, Worktree, WorktreeId};
@@ -1213,11 +1221,36 @@ impl BufferStore {
match file.worktree.read(cx) {
Worktree::Local(worktree) => {
let Some(repo) = worktree.local_git_repo(file.path()) else {
return Task::ready(Err(anyhow!("no repository for buffer found")));
let worktree_path = worktree.abs_path().clone();
let Some((repo_entry, repo)) =
worktree.repository_for_path(file.path()).and_then(|entry| {
let repo = worktree.get_local_repo(&entry)?.repo().clone();
Some((entry, repo))
})
else {
// If we're not in a Git repo, check whether this is a Rust source
// file in the Cargo registry (presumably opened with go-to-definition
// from a normal Rust file). If so, we can put together a permalink
// using crate metadata.
if !buffer
.language()
.is_some_and(|lang| lang.name() == "Rust".into())
{
return Task::ready(Err(anyhow!("no permalink available")));
}
let file_path = worktree_path.join(file.path());
return cx.spawn(|cx| async move {
let provider_registry =
cx.update(GitHostingProviderRegistry::default_global)?;
get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
.map_err(|_| anyhow!("no permalink available"))
});
};
let path = file.path().clone();
let path = match repo_entry.relativize(worktree, file.path()) {
Ok(RepoPath(path)) => path,
Err(e) => return Task::ready(Err(e)),
};
cx.spawn(|cx| async move {
const REMOTE_NAME: &str = "origin";
@@ -1238,7 +1271,7 @@ impl BufferStore {
let path = path
.to_str()
.context("failed to convert buffer path to string")?;
.ok_or_else(|| anyhow!("failed to convert path to string"))?;
Ok(provider.build_permalink(
remote,
@@ -2432,3 +2465,52 @@ fn deserialize_blame_buffer_response(
remote_url: response.remote_url,
})
}
fn get_permalink_in_rust_registry_src(
provider_registry: Arc<GitHostingProviderRegistry>,
path: PathBuf,
selection: Range<u32>,
) -> Result<url::Url> {
#[derive(Deserialize)]
struct CargoVcsGit {
sha1: String,
}
#[derive(Deserialize)]
struct CargoVcsInfo {
git: CargoVcsGit,
path_in_vcs: String,
}
#[derive(Deserialize)]
struct CargoPackage {
repository: String,
}
#[derive(Deserialize)]
struct CargoToml {
package: CargoPackage,
}
let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
Some((dir, json))
}) else {
bail!("No .cargo_vcs_info.json found in parent directories")
};
let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
.ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
let permalink = provider.build_permalink(
remote,
BuildPermalinkParams {
sha: &cargo_vcs_info.git.sha1,
path: &path.to_string_lossy(),
selection: Some(selection),
},
);
Ok(permalink)
}

View File

@@ -14,8 +14,7 @@ use crate::{
pub struct ProjectEnvironment {
cli_environment: Option<HashMap<String, String>>,
get_environment_task: Option<Shared<Task<Option<HashMap<String, String>>>>>,
cached_shell_environments: HashMap<WorktreeId, HashMap<String, String>>,
environments: HashMap<WorktreeId, Shared<Task<Option<HashMap<String, String>>>>>,
environment_error_messages: HashMap<WorktreeId, EnvironmentErrorMessage>,
}
@@ -35,27 +34,15 @@ impl ProjectEnvironment {
Self {
cli_environment,
get_environment_task: None,
cached_shell_environments: Default::default(),
environments: Default::default(),
environment_error_messages: Default::default(),
}
})
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn set_cached(
&mut self,
shell_environments: &[(WorktreeId, HashMap<String, String>)],
) {
self.cached_shell_environments = shell_environments
.iter()
.cloned()
.collect::<HashMap<_, _>>();
}
pub(crate) fn remove_worktree_environment(&mut self, worktree_id: WorktreeId) {
self.cached_shell_environments.remove(&worktree_id);
self.environment_error_messages.remove(&worktree_id);
self.environments.remove(&worktree_id);
}
/// Returns the inherited CLI environment, if this project was opened from the Zed CLI.
@@ -91,96 +78,83 @@ impl ProjectEnvironment {
worktree_abs_path: Option<Arc<Path>>,
cx: &ModelContext<Self>,
) -> Shared<Task<Option<HashMap<String, String>>>> {
if let Some(task) = self.get_environment_task.as_ref() {
if cfg!(any(test, feature = "test-support")) {
return Task::ready(Some(HashMap::default())).shared();
}
if let Some(cli_environment) = self.get_cli_environment() {
return cx
.spawn(|_, _| async move {
let path = cli_environment
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables from CLI. PATH={:?}",
path
);
Some(cli_environment)
})
.shared();
}
let Some((worktree_id, worktree_abs_path)) = worktree_id.zip(worktree_abs_path) else {
return Task::ready(None).shared();
};
if let Some(task) = self.environments.get(&worktree_id) {
task.clone()
} else {
let task = self
.build_environment_task(worktree_id, worktree_abs_path, cx)
.get_worktree_env(worktree_id, worktree_abs_path, cx)
.shared();
self.get_environment_task = Some(task.clone());
self.environments.insert(worktree_id, task.clone());
task
}
}
fn build_environment_task(
&mut self,
worktree_id: Option<WorktreeId>,
worktree_abs_path: Option<Arc<Path>>,
cx: &ModelContext<Self>,
) -> Task<Option<HashMap<String, String>>> {
let worktree = worktree_id.zip(worktree_abs_path);
let cli_environment = self.get_cli_environment();
if let Some(environment) = cli_environment {
cx.spawn(|_, _| async move {
let path = environment
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables from CLI. PATH={:?}",
path
);
Some(environment)
})
} else if let Some((worktree_id, worktree_abs_path)) = worktree {
self.get_worktree_env(worktree_id, worktree_abs_path, cx)
} else {
Task::ready(None)
}
}
fn get_worktree_env(
&mut self,
worktree_id: WorktreeId,
worktree_abs_path: Arc<Path>,
cx: &ModelContext<Self>,
) -> Task<Option<HashMap<String, String>>> {
let cached_env = self.cached_shell_environments.get(&worktree_id).cloned();
if let Some(env) = cached_env {
Task::ready(Some(env))
} else {
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
let load_direnv = ProjectSettings::get_global(cx).load_direnv.clone();
cx.spawn(|this, mut cx| async move {
let (mut shell_env, error_message) = cx
.background_executor()
.spawn({
let cwd = worktree_abs_path.clone();
async move { load_shell_environment(&cwd, &load_direnv).await }
})
.await;
cx.spawn(|this, mut cx| async move {
let (mut shell_env, error_message) = cx
.background_executor()
.spawn({
let worktree_abs_path = worktree_abs_path.clone();
async move {
load_worktree_shell_environment(&worktree_abs_path, &load_direnv).await
}
})
.await;
if let Some(shell_env) = shell_env.as_mut() {
let path = shell_env
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables shell launched in {:?}. PATH={:?}",
worktree_abs_path,
path
);
this.update(&mut cx, |this, _| {
this.cached_shell_environments
.insert(worktree_id, shell_env.clone());
})
.log_err();
if let Some(shell_env) = shell_env.as_mut() {
let path = shell_env
.get("PATH")
.map(|path| path.as_str())
.unwrap_or_default();
log::info!(
"using project environment variables shell launched in {:?}. PATH={:?}",
worktree_abs_path,
path
);
set_origin_marker(shell_env, EnvironmentOrigin::WorktreeShell);
}
set_origin_marker(shell_env, EnvironmentOrigin::WorktreeShell);
}
if let Some(error) = error_message {
this.update(&mut cx, |this, _| {
this.environment_error_messages.insert(worktree_id, error);
})
.log_err();
}
if let Some(error) = error_message {
this.update(&mut cx, |this, _| {
this.environment_error_messages.insert(worktree_id, error);
})
.log_err();
}
shell_env
})
}
shell_env
})
}
}
@@ -213,6 +187,42 @@ impl EnvironmentErrorMessage {
}
}
async fn load_worktree_shell_environment(
worktree_abs_path: &Path,
load_direnv: &DirenvSettings,
) -> (
Option<HashMap<String, String>>,
Option<EnvironmentErrorMessage>,
) {
match smol::fs::metadata(worktree_abs_path).await {
Ok(meta) => {
let dir = if meta.is_dir() {
worktree_abs_path
} else if let Some(parent) = worktree_abs_path.parent() {
parent
} else {
return (
None,
Some(EnvironmentErrorMessage(format!(
"Failed to load shell environment in {}: not a directory",
worktree_abs_path.display()
))),
);
};
load_shell_environment(&dir, load_direnv).await
}
Err(err) => (
None,
Some(EnvironmentErrorMessage(format!(
"Failed to load shell environment in {}: {}",
worktree_abs_path.display(),
err
))),
),
}
}
#[cfg(any(test, feature = "test-support"))]
async fn load_shell_environment(
_dir: &Path,

View File

@@ -1207,13 +1207,6 @@ impl Project {
.await
.unwrap();
project.update(cx, |project, cx| {
let tree_id = tree.read(cx).id();
project.environment.update(cx, |environment, _| {
environment.set_cached(&[(tree_id, HashMap::default())])
});
});
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
.await;
}

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