Compare commits

..

48 Commits

Author SHA1 Message Date
Peter Tripp
0ea60f64f9 ci: Less braces; more multiline in ci.yml workflow 2024-09-27 23:47:15 -04:00
Max Brunsfeld
0daa070448 More git hunk highlighting fixes (#18459)
Follow-up to https://github.com/zed-industries/zed/pull/18454

Release Notes:

- N/A
2024-09-27 13:48:37 -07:00
Max Brunsfeld
689da9d0b1 Move git hunk controls to the left side (#18460)
![Screenshot 2024-09-27 at 1 05
14 PM](https://github.com/user-attachments/assets/260a7d05-daa8-4a22-92bc-3b956035227f)

Release Notes:

- N/A
2024-09-27 13:13:55 -07:00
Danilo Leal
1c5be9de4e Capitalize tooltip labels on buffer search (#18458)
For consistency, as this seems to be the pattern we're using overall for
labels and buttons.

---

Release Notes:

- N/A
2024-09-27 17:02:32 -03:00
Kirill Bulatov
d5f67406b0 Install cargo-edito without extra features (#18457)
https://github.com/killercup/cargo-edit/pull/907 removed the feature
from the crate

Release Notes:

- N/A
2024-09-27 22:42:04 +03:00
Max Brunsfeld
c3075dfe9a Fix bugs in diff hunk highlighting (#18454)
Fixes https://github.com/zed-industries/zed/issues/18405

In https://github.com/zed-industries/zed/pull/18313, we introduced a
problem where git addition highlights might spuriously return when
undoing certain changes. It turned out, there were already some cases
where git hunk highlighting was incorrect when editing at the boundaries
of expanded diff hunks.

In this PR, I've introduced a test helper method for more rigorously
(and readably) testing the editor's git state. You can assert about the
entire state of an editor's diff decorations using a formatted diff:

```rust
    cx.assert_diff_hunks(
        r#"
        - use some::mod1;
          use some::mod2;
          const A: u32 = 42;
        - const B: u32 = 42;
          const C: u32 = 42;
          fn main() {
        -     println!("hello");
        +     //println!("hello");
              println!("world");
        +     //
        +     //
          }
          fn another() {
              println!("another");
        +     println!("another");
          }
        - fn another2() {
              println!("another2");
          }
        "#
        .unindent(),
    );
```

This will assert about the editor's actual row highlights, not just the
editor's internal hunk-tracking state.

I rewrote all of our editor diff tests to use these more high-level
assertions, and it caught the new bug, as well as some pre-existing bugs
in the highlighting of added content.

The problem was how we *remove* highlighted rows. Previously, it relied
on supplying exactly the same range as one that we had previously
highlighted. I've added a `remove_highlighted_rows(ranges)` APIs which
is much simpler - it clears out any row ranges that intersect the given
ranges (which is all that we need for the Git diff use case).

Release Notes:

- N/A
2024-09-27 11:14:28 -07:00
Richard Feldman
caaa9a00a9 Remove Qwen2 model (#18444)
Removed deprecated Qwen2 7B Instruct model from zed.dev provider (staff
only).

Release Notes:

- N/A
2024-09-27 13:30:25 -04:00
Conrad Irwin
ffd1083cc1 vim: Command selection fixes (#18424)
Release Notes:

- vim: Fixed cursor position after `:{range}yank`.
- vim: Added `:fo[ld]`, `:foldo[pen]` and `:foldc[lose]`
2024-09-27 10:06:19 -06:00
Joseph T. Lyons
6d4ecac610 Add a get-release-notes-since script (#18445)
Release Notes:

- N/A
2024-09-27 10:59:19 -04:00
Ömer Sinan Ağacan
dc5ffe6994 Fix GoToDefinition changing the viewport unnecessarily (#18441)
Closes #10738.

Release Notes:

- Fixed `GoToDefinition` changing the viewport (scrolling up/down) even when the definition is already within the viewport. ([#10738](https://github.com/zed-industries/zed/issues/10738))
2024-09-27 17:55:03 +03:00
Peter Tripp
03c7f08581 docs: Ollama api_url improvements (#18440) 2024-09-27 10:29:49 -04:00
Bennet Bo Fenner
73ff8c0f1f Fix missing tooltips for selected buttons (#18435)
Reverts #13857. Hiding tooltips for selected buttons prevents tooltips
like "Close x dock" from showing up, see #14938 for an example.
The intention of the original PR was to hide the "Show application menu"
tooltip, while the context menu is open.
In order to fix this without breaking other UI elements, we track the
state of the context menu using `PopoverMenuHandle` now, which allows us
to prevent the tooltip from showing up while the context menu is open.

Closes #14938

Release Notes:

- Fixed an issue where some tooltips would not show up
2024-09-27 14:16:14 +02:00
Bennet Bo Fenner
1c5d9c221a Add missing shortcuts in tooltips (#18282)
Fixes some missing shortcuts from Tooltips like the project search,
buffer search, quick action bar, ....


https://github.com/user-attachments/assets/d3a0160a-8d6e-4ddc-bf82-1fabeca42d59

This should hopefully help new users learn and discover some nice
keyboard shortcuts

Release Notes:

- Display keyboard shortcuts inside tooltips in the project search,
buffer search etc.
2024-09-27 11:06:48 +02:00
Bennet Bo Fenner
a1d2e1106e assistant: Fix copy/cut not working when selection is empty (#18403)
Release Notes:

- Fixed copy/cut/paste not working in the assistant panel when selection
was empty
2024-09-27 10:51:49 +02:00
Kirill Bulatov
568a21a700 Fix the numeration in line wrap docs (#18428)
Follow-up of https://github.com/zed-industries/zed/pull/18412

Release Notes:

- N/A

Co-authored-by: Thorsten Ball <thorsten@zed.dev>
2024-09-27 10:48:34 +03:00
Thorsten Ball
5199135b54 ssh remoting: Show error if opening connection timed out (#18401)
This shows an error if opening a connection to a remote host didn't work
in the timeout of 10s (maybe we'll need to make that configurable in the
future? for now it seems fine.)

![screenshot-2024-09-26-18 01
07@2x](https://github.com/user-attachments/assets/cbfa0e9f-9c29-4b6c-bade-07fdd7393c9d)


Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
2024-09-27 09:31:45 +02:00
CharlesChen0823
8559731e0d project: Fix worktree store event missing in remote projects (#18376)
Release Notes:

- N/A
2024-09-27 08:55:35 +02:00
Conrad Irwin
02d0561586 Fix read timeout for ollama (#18417)
Supercedes: #18310

Release Notes:

- Fixed `low_speed_timeout_in_seconds` for Ollama
2024-09-27 00:36:17 -06:00
Conrad Irwin
1be3c44550 vim: Support za (#18421)
Closes #6822
Updates #5142 

Release Notes:

- Added new fold actions to toggle folds (`cmd-k cmd-l`), fold every
fold (`cmd-k cmd-0`) unfold every fold (`cmd-k cmd-j`) to fold
recursively (`cmd-k cmd-[`) and unfold recursively (`cmd-k cmd-]`).
- vim: Added `za` to toggle fold under cursor.
- vim: Added `zO`/`zC`/`zA` to open, close and toggle folds recursively
(and fixed `zc` to not recurse into selections).
- vim: Added `zR`/`zM` to open/close all folds in the buffer.
2024-09-26 23:52:07 -06:00
Conrad Irwin
32605e9ea4 Fix register selection in visual mode (#18418)
Related to #12895

Release Notes:

- vim: Fix register selection in visual yank
2024-09-26 20:27:49 -06:00
Kirill Bulatov
c83d007138 Remove non-wrapping settings from the language configuration (#18412)
Closes https://github.com/zed-industries/zed/issues/17736

Those are limited with 1024 symbols before wrapping still, and were
introduced for git diff deleted hunks display.
Instead of confusing people with actually wrapping, restores behavior
that was before https://github.com/zed-industries/zed/pull/11080

Release Notes:

- Removed confusing soft wrap option behavior
([#17736]https://github.com/zed-industries/zed/issues/17736)
2024-09-26 23:43:58 +03:00
Peter Tripp
48c6eb9ac7 Add script to generate license dependencies as csv (#18411)
Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
2024-09-26 16:21:20 -04:00
Conrad Irwin
e28496d4e2 Stop leaking isahc assumption (#18408)
Users of our http_client crate knew they were interacting with isahc as
they set its extensions on the request. This change adds our own
equivalents for their APIs in preparation for changing the default http
client.

Release Notes:

- N/A
2024-09-26 14:01:05 -06:00
Max Brunsfeld
c1a039a5d7 Remove old project search code path, bump min-supported zed version for collaboration (#18404)
Release Notes:

- N/A
2024-09-26 12:10:39 -07:00
Mikayla Maki
71da81c743 SSH Remoting: Fix bugs in worktree syncing (#18406)
Release Notes:

- N/A

---------

Co-authored-by: conrad <conrad@zed.dev>
2024-09-26 12:03:57 -07:00
thataboy
11058765be Add ability to separately set background color for highlighted brackets (#17566)
Closes https://github.com/zed-industries/zed/issues/16380

Currently brackets are highlighted with
`editor.document_highlight.read_background`. This commit adds a separate
`editor.document_highlight.bracket_background` theme setting so bracket
highlights can be made more prominent without doing the same to other
highlights, making the display too busy.

(My own theme)


https://github.com/user-attachments/assets/29a8c05e-2f1a-4c16-9be8-a4b4cb143548

I set defaults for light and dark theme that I hope are sensible and not
too obnoxious, but noticeable so people can change it if they don't like
it.

Release Notes:

- Added `editor.document_highlight.bracket_background` field to the
theme to set background color of highlighted brackets.
- This will fall back to `editor.document_highlight.read_background`, if
not set.

<img width="355" alt="Screenshot 2024-09-08 at 8 46 57 AM"
src="https://github.com/user-attachments/assets/3270bb4d-19f5-4b34-8003-982377b2ceb6">
<img width="444" alt="Screenshot 2024-09-08 at 9 03 27 AM"
src="https://github.com/user-attachments/assets/3b12d84d-913c-4bde-9132-9b10f4a8d49b">

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-26 12:48:23 -04:00
renovate[bot]
c7a79cfc02 Update Rust crate libc to v0.2.159 (#18370)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v0.2.159`](https://redirect.github.com/rust-lang/libc/releases/tag/0.2.159)

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

##### Added

- Android: add more `AT_*` constants in
[#&#8203;3779](https://redirect.github.com/rust-lang/libc/pull/3779)
- Apple: add missing `NOTE_*` constants in
[#&#8203;3883](https://redirect.github.com/rust-lang/libc/pull/3883)
- Hermit: add missing error numbers in
[#&#8203;3858](https://redirect.github.com/rust-lang/libc/pull/3858)
- Hurd: add `__timeval` for 64-bit support in
[#&#8203;3786](https://redirect.github.com/rust-lang/libc/pull/3786)
- Linux: add `epoll_pwait2` in
[#&#8203;3868](https://redirect.github.com/rust-lang/libc/pull/3868)
- Linux: add `mq_notify` in
[#&#8203;3849](https://redirect.github.com/rust-lang/libc/pull/3849)
- Linux: add missing `NFT_CT_*` constants in
[#&#8203;3844](https://redirect.github.com/rust-lang/libc/pull/3844)
- Linux: add the `fchmodat2` syscall in
[#&#8203;3588](https://redirect.github.com/rust-lang/libc/pull/3588)
- Linux: add the `mseal` syscall in
[#&#8203;3798](https://redirect.github.com/rust-lang/libc/pull/3798)
- OpenBSD: add `sendmmsg` and `recvmmsg` in
[#&#8203;3831](https://redirect.github.com/rust-lang/libc/pull/3831)
- Unix: add `IN6ADDR_ANY_INIT` and `IN6ADDR_LOOPBACK_INIT` in
[#&#8203;3693](https://redirect.github.com/rust-lang/libc/pull/3693)
- VxWorks: add `S_ISVTX` in
[#&#8203;3768](https://redirect.github.com/rust-lang/libc/pull/3768)
- VxWorks: add `vxCpuLib` and `taskLib` functions
[#&#8203;3861](https://redirect.github.com/rust-lang/libc/pull/3861)
- WASIp2: add definitions for `std::net` support in
[#&#8203;3892](https://redirect.github.com/rust-lang/libc/pull/3892)

##### Fixed

- Correctly handle version checks when `clippy-driver` is used
[#&#8203;3893](https://redirect.github.com/rust-lang/libc/pull/3893)

##### Changed

- EspIdf: change signal constants to c_int in
[#&#8203;3895](https://redirect.github.com/rust-lang/libc/pull/3895)
- HorizonOS: update network definitions in
[#&#8203;3863](https://redirect.github.com/rust-lang/libc/pull/3863)
- Linux: combine `ioctl` APIs in
[#&#8203;3722](https://redirect.github.com/rust-lang/libc/pull/3722)
- WASI: enable CI testing in
[#&#8203;3869](https://redirect.github.com/rust-lang/libc/pull/3869)
- WASIp2: enable CI testing in
[#&#8203;3870](https://redirect.github.com/rust-lang/libc/pull/3870)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 12:16:49 -04:00
renovate[bot]
84a6ded657 Update Rust crate clap to v4.5.18 (#18369)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v4.5.18`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4518---2024-09-20)

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

##### Features

- *(builder)* Expose `Arg::get_display_order` and
`Command::get_display_order`

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 11:52:12 -04:00
renovate[bot]
e5bbd378a6 Update Rust crate cargo_toml to v0.20.5 (#18365)
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 | patch | `0.20.4` -> `0.20.5` |

---

### Release Notes

<details>
<summary>lib.rs/cargo_toml (cargo_toml)</summary>

###
[`v0.20.5`](https://gitlab.com/lib.rs/cargo_toml/compare/v0.20.4...v0.20.5)

[Compare
Source](https://gitlab.com/lib.rs/cargo_toml/compare/v0.20.4...v0.20.5)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 11:44:38 -04:00
renovate[bot]
82eb753b31 Update actions/setup-node digest to 0a44ba7 (#18357)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-node](https://redirect.github.com/actions/setup-node) |
action | digest | `1e60f62` -> `0a44ba7` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 11:24:01 -04:00
renovate[bot]
de1889d6a8 Update Rust crate async-trait to v0.1.83 (#18364)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [async-trait](https://redirect.github.com/dtolnay/async-trait) |
workspace.dependencies | patch | `0.1.82` -> `0.1.83` |

---

### Release Notes

<details>
<summary>dtolnay/async-trait (async-trait)</summary>

###
[`v0.1.83`](https://redirect.github.com/dtolnay/async-trait/releases/tag/0.1.83)

[Compare
Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.82...0.1.83)

- Prevent needless_arbitrary_self_type lint being produced in generated
code
([#&#8203;278](https://redirect.github.com/dtolnay/async-trait/issues/278))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-26 08:49:50 -06:00
Piotr Osiewicz
f143396825 ssh: Do not require user to be signed in to use ssh remoting (#18396)
Fixes #18392

Closes #18392

Release Notes:

- N/A
2024-09-26 16:24:11 +02:00
Thorsten Ball
7eea1a6f51 git blame gutter: Use smallest possible space (#18145)
Before:
![screenshot-2024-09-26-15 00
20@2x](https://github.com/user-attachments/assets/f6706325-5bef-404e-a0b4-63a5121969fa)

After:

![screenshot-2024-09-26-15 02
24@2x](https://github.com/user-attachments/assets/739d0831-0b4a-457f-917e-10f3a662e74d)


Release Notes:

- Improved the git blame gutter to take up only the space required to
display the longest git author name in the current file.

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-09-26 15:47:14 +02:00
Galen Elias
1a4f9b2891 Fix minimum gutter line number spacing (#18021)
I was inspecting how Zed did the layout in the editor, specifically for
the gutter, and noticed that `em_width * X` is being used as the 'width
of X consecutive characters'. Howevever, that math didn't work for me,
because em_width doesn't account for the space between characters, so
you can't just multiply it by a character count.

One place this is actually noticeable is in the logic for
`min_width_for_number_on_gutter`, where we try to reserve 4 characters
of line number space. However, once you actually hit 4 characters, the
actual width is bigger, causing things to resize. This seems clearly
counter to the intent of the code.

It seems the more correct logic is to use `em_advance` which accounts
for the space between the characters. I am leaving the rest of the uses
of `em_width` for generic padding. It is also possible that
`column_pixels()` would be the more correct fix here, but it wasn't
straightforward to use that due to it residing EditorElement source
file.

On my MacBook this increases the width of the gutter by 6 pixels when
there are <999 lines in the file, otherwise it's identical.

It might be worth doing some more general audit of some of the other
uses of em_width as a concept. (e.g. `git_blame_entries_width`)


https://github.com/user-attachments/assets/f2a28cd5-9bb6-4109-bf41-1838e56a75f9

Release Notes:

- Fix a slight gutter flicker when going over 999 lines
2024-09-26 16:30:06 +03:00
Taras Martyniuk
1deed247eb terraform: Bump to v0.1.1 (#18382)
This PR bumps the Terraform extension to v0.1.1

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

Release Notes:

- N/A
2024-09-26 08:36:58 -04:00
Piotr Osiewicz
db92a31067 lsp: Do not notify all language servers on file save (#17756)
This is not an ideal solution to
https://github.com/fasterthanlime/zed-diags-readme, but current status
quo is not great either; we were just going through all of the language
servers and notifying them, whereas we should ideally do it based on a
glob.
/cc @fasterthanlime

Release Notes:

- N/A
2024-09-26 13:18:50 +02:00
Bennet Bo Fenner
31902a1b73 Remove leftover println statements (#18389)
Remove some leftover println statements from #17644

Release Notes:

- N/A
2024-09-26 12:52:56 +02:00
Thorsten Ball
3f415f3587 Fix use_on_type_format setting being unused per language (#18387)
Before this change, `use_on_type_format` would only have an effect when
defined on a global level in our settings.

But our default.json settings would also document that it's used in
language settings, i.e.:

```json
{
  "languages": {
    "C": {
      "use_on_type_format": false
    },
    "C++": {
      "use_on_type_format": false
    }
  }
}
```

But this did **not** work.

With the change, it now works globally and per-language.

Release Notes:

- Fixed `use_on_type_format` setting not working when defined inside
`"languages"` in the settings. This change will now change the default
behavior for C, C++, and Markdown, by turning language server's
`OnTypeFormatting` completions off by default.

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-26 12:27:08 +02:00
Thorsten Ball
140d70289e Avoid panic by only restoring workspace if UI has launched (#18386)
This should fix the `unregistered setting type
workspace::workspace_settings::WorkspaceSettings` panic that came from
inside `restorable_workspace_locations`.

We tracked it down to a possible scenario (we can't recreate it though)
in which `app.on_reopen` is called before the app has finished
launching.

In any case, this check makes sense, because we only want to restore a
workspace in case the whole app has launched with a UI.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-26 12:26:58 +02:00
Hyunmin Woo (Hanul)
b9b689d322 Fix Typo in rust language guide (#18383)
Release Notes:

- N/A
2024-09-26 12:24:29 +02:00
Thorsten Ball
2d2e20f9d4 editor: Fix cursor shape not restoring when setting removed (#18379)
Closes #18119

Release Notes:

- Fixed the cursor shape in the editor not changing back to default when
`{"cursor_shape": "..."}` setting is removed. (Does not apply to Vim
mode.)
2024-09-26 11:07:07 +02:00
Conrad Irwin
b701eab44f Avoid unwrap in file finder (#18374)
Release Notes:

- Fixed a (rare) panic in file finder

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-09-26 10:31:17 +03:00
Max Brunsfeld
6167688a63 Proposed changes editor features (#18373)
This PR adds some more functionality to the Proposed Changes Editor
view, which we'll be using in
https://github.com/zed-industries/zed/pull/18240 for allowing the
assistant to propose changes to a set of buffers.

* Add an `Apply All` button, and fully implement applying of changes to
the base buffer
* Make the proposed changes editor searchable
* Fix a bug in branch buffers' diff state management

Release Notes:

- N/A
2024-09-25 16:33:00 -07:00
Mikayla Maki
3161aedcb0 Fix broken collaboration UI from #18308 (#18372)
Fixes a bug introduced by #18308, that caused the call controls to
render incorrectly.

Release Notes:

- N/A
2024-09-25 16:03:08 -07:00
Conrad Irwin
64532e94e4 Move adapters to remote (#18359)
Release Notes:

- ssh remoting: run LSP Adapters on host

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-09-25 15:29:04 -07:00
Peter Tripp
40408e731e Fix sending alt-enter in terminal (#18363) 2024-09-25 18:01:33 -04:00
Peter Tripp
7398f795e3 Ollama llama3.2 default context size (#18366)
Release Notes:

- Ollama: Added llama3.2 support
2024-09-25 18:01:12 -04:00
Piotr Osiewicz
4b4565fb7a assistant: Enable assistant panel/inline assists in ssh remote projects (#18367)
Release Notes:

- ssh remoting: Enable assistant panel and inline assists (running on
client)
2024-09-25 22:55:36 +02:00
139 changed files with 2774 additions and 3640 deletions

View File

@@ -10,7 +10,7 @@ runs:
cargo install cargo-nextest
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
node-version: "18"

View File

@@ -41,7 +41,7 @@ jobs:
exit 1
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit --features vendored-openssl
which cargo-set-version > /dev/null || cargo install cargo-edit
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag v${output}${tag_suffix}

View File

@@ -159,7 +159,9 @@ jobs:
runs-on:
- self-hosted
- bundle
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
if: |
startsWith(github.ref, 'refs/tags/v') ||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [macos_tests]
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
@@ -172,7 +174,7 @@ jobs:
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
node-version: "18"
@@ -190,7 +192,7 @@ jobs:
run: script/clear-target-dir-if-larger-than 100
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -eu
@@ -226,34 +228,42 @@ jobs:
run: script/bundle-mac
- name: Rename single-architecture binaries
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
if: |
github.ref == 'refs/heads/main' ||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
run: |
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (universal) to workflow run if main branch or specific label
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
if: |
github.ref == 'refs/heads/main' }} ||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
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@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
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
path: target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
name: Upload app bundle to release
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
if: env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable'
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
@@ -272,7 +282,9 @@ jobs:
name: Create a Linux bundle
runs-on:
- buildjet-16vcpu-ubuntu-2204
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
if: |
startsWith(github.ref, 'refs/tags/v') ||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
@@ -287,7 +299,7 @@ jobs:
run: ./script/linux
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -eu
@@ -318,7 +330,9 @@ jobs:
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
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
path: target/release/zed-*.tar.gz
@@ -340,7 +354,9 @@ jobs:
name: Create arm64 Linux bundle
runs-on:
- buildjet-16vcpu-ubuntu-2204-arm
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
if: |
startsWith(github.ref, 'refs/tags/v') ||
contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
@@ -355,7 +371,7 @@ jobs:
run: ./script/linux
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -eu
@@ -386,14 +402,16 @@ jobs:
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
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
path: target/release/zed-*.tar.gz
- name: Upload app bundle to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
if: env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable'
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

View File

@@ -21,7 +21,7 @@ jobs:
version: 9
- name: Setup Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
node-version: "20"
cache: "pnpm"

View File

@@ -22,7 +22,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
node-version: "18"

View File

@@ -70,7 +70,7 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
with:
node-version: "18"

2
.gitignore vendored
View File

@@ -10,7 +10,7 @@
/crates/collab/seed.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/dev.zed.Zed*.json
/assets/*licenses.md
/assets/*licenses.*
**/venv
.build
*.wasm

288
Cargo.lock generated
View File

@@ -245,7 +245,6 @@ dependencies = [
"chrono",
"futures 0.3.30",
"http_client",
"isahc",
"schemars",
"serde",
"serde_json",
@@ -847,8 +846,8 @@ dependencies = [
"chrono",
"futures-util",
"http-types",
"hyper 0.14.30",
"hyper-rustls 0.24.2",
"hyper",
"hyper-rustls",
"serde",
"serde_json",
"serde_path_to_error",
@@ -895,9 +894,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.82"
version = "0.1.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
@@ -1085,33 +1084,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "aws-lc-rs"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070"
dependencies = [
"aws-lc-sys",
"mirai-annotations",
"paste",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3ddc4a5b231dd6958b140ff3151b6412b3f4321fab354f399eec8f14b06df62"
dependencies = [
"bindgen 0.69.4",
"cc",
"cmake",
"dunce",
"fs_extra",
"libc",
"paste",
]
[[package]]
name = "aws-runtime"
version = "1.4.2"
@@ -1364,13 +1336,13 @@ dependencies = [
"aws-smithy-types",
"bytes 1.7.1",
"fastrand 2.1.1",
"h2 0.3.26",
"h2",
"http 0.2.12",
"http-body 0.4.6",
"http-body 1.0.1",
"httparse",
"hyper 0.14.30",
"hyper-rustls 0.24.2",
"hyper",
"hyper-rustls",
"once_cell",
"pin-project-lite",
"pin-utils",
@@ -1460,7 +1432,7 @@ dependencies = [
"headers",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"hyper",
"itoa",
"matchit",
"memchr",
@@ -1609,15 +1581,12 @@ dependencies = [
"itertools 0.12.1",
"lazy_static",
"lazycell",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 2.0.76",
"which 4.4.2",
]
[[package]]
@@ -2117,9 +2086,9 @@ dependencies = [
[[package]]
name = "cargo_toml"
version = "0.20.4"
version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965"
checksum = "88da5a13c620b4ca0078845707ea9c3faf11edbc3ffd8497d11d686211cd1ac0"
dependencies = [
"serde",
"toml 0.8.19",
@@ -2313,9 +2282,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.17"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
dependencies = [
"clap_builder",
"clap_derive",
@@ -2323,9 +2292,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.17"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
dependencies = [
"anstream",
"anstyle",
@@ -2345,9 +2314,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.13"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2394,7 +2363,7 @@ dependencies = [
"clickhouse-derive",
"clickhouse-rs-cityhash-sys",
"futures 0.3.30",
"hyper 0.14.30",
"hyper",
"hyper-tls",
"lz4",
"sealed",
@@ -2482,15 +2451,6 @@ dependencies = [
"smallvec",
]
[[package]]
name = "cmake"
version = "0.1.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
dependencies = [
"cc",
]
[[package]]
name = "cobs"
version = "0.2.3"
@@ -2607,7 +2567,7 @@ dependencies = [
"headless",
"hex",
"http_client",
"hyper 1.4.1",
"hyper",
"indoc",
"isahc_http_client",
"jsonwebtoken",
@@ -2889,7 +2849,6 @@ dependencies = [
"gpui",
"http_client",
"indoc",
"isahc",
"language",
"lsp",
"menu",
@@ -3708,12 +3667,6 @@ dependencies = [
"phf",
]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "dwrote"
version = "0.11.1"
@@ -3776,6 +3729,7 @@ dependencies = [
"multi_buffer",
"ordered-float 2.10.1",
"parking_lot",
"pretty_assertions",
"project",
"rand 0.8.5",
"release_channel",
@@ -4173,7 +4127,6 @@ dependencies = [
"gpui",
"http_client",
"indexed_docs",
"isahc",
"isahc_http_client",
"language",
"log",
@@ -4334,7 +4287,6 @@ dependencies = [
"gpui",
"http_client",
"human_bytes",
"isahc",
"language",
"log",
"menu",
@@ -4641,12 +4593,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fsevent"
version = "0.1.0"
@@ -5067,7 +5013,6 @@ dependencies = [
"anyhow",
"futures 0.3.30",
"http_client",
"isahc",
"schemars",
"serde",
"serde_json",
@@ -5236,25 +5181,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "h2"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
dependencies = [
"atomic-waker",
"bytes 1.7.1",
"fnv",
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap 2.4.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "2.4.1"
@@ -5635,7 +5561,7 @@ dependencies = [
"anyhow",
"derive_more",
"futures 0.3.30",
"http 1.1.0",
"http 0.2.12",
"log",
"serde",
"serde_json",
@@ -5677,7 +5603,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.26",
"h2",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
@@ -5691,26 +5617,6 @@ dependencies = [
"want",
]
[[package]]
name = "hyper"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
dependencies = [
"bytes 1.7.1",
"futures-channel",
"futures-util",
"h2 0.4.6",
"http 1.1.0",
"http-body 1.0.1",
"httparse",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
@@ -5719,31 +5625,12 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.30",
"hyper",
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
"tokio",
"tokio-rustls 0.24.1",
]
[[package]]
name = "hyper-rustls"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.4.1",
"hyper-util",
"log",
"rustls 0.23.13",
"rustls-native-certs 0.8.0",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
"tower-service",
"tokio-rustls",
]
[[package]]
@@ -5753,51 +5640,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.7.1",
"hyper 0.14.30",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "hyper-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
dependencies = [
"bytes 1.7.1",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"hyper 1.4.1",
"pin-project-lite",
"socket2 0.5.7",
"tokio",
"tower-service",
"tracing",
]
[[package]]
name = "hyper_client"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_more",
"futures 0.3.30",
"gpui",
"http_client",
"hyper 1.4.1",
"hyper-rustls 0.27.3",
"hyper-util",
"log",
"serde",
"serde_json",
"smol",
"ureq",
"url",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
@@ -6436,7 +6284,6 @@ dependencies = [
"http_client",
"image",
"inline_completion_button",
"isahc",
"language",
"log",
"menu",
@@ -6582,9 +6429,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.158"
version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libdbus-sys"
@@ -7193,12 +7040,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "mirai-annotations"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
[[package]]
name = "multi_buffer"
version = "0.1.0"
@@ -7745,7 +7586,6 @@ dependencies = [
"anyhow",
"futures 0.3.30",
"http_client",
"isahc",
"schemars",
"serde",
"serde_json",
@@ -9276,6 +9116,7 @@ dependencies = [
"gpui",
"http_client",
"language",
"languages",
"log",
"lsp",
"node_runtime",
@@ -9355,10 +9196,10 @@ dependencies = [
"encoding_rs",
"futures-core",
"futures-util",
"h2 0.3.26",
"h2",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
@@ -9755,26 +9596,10 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring 0.17.8",
"rustls-webpki 0.101.7",
"rustls-webpki",
"sct",
]
[[package]]
name = "rustls"
version = "0.23.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring 0.17.8",
"rustls-pki-types",
"rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
@@ -9835,18 +9660,6 @@ dependencies = [
"untrusted 0.9.0",
]
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"aws-lc-rs",
"ring 0.17.8",
"rustls-pki-types",
"untrusted 0.9.0",
]
[[package]]
name = "rustversion"
version = "1.0.17"
@@ -11995,17 +11808,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.13",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-socks"
version = "0.5.2"
@@ -12725,22 +12527,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a"
dependencies = [
"base64 0.22.1",
"flate2",
"log",
"once_cell",
"rustls 0.23.13",
"rustls-pki-types",
"url",
"webpki-roots 0.26.6",
]
[[package]]
name = "url"
version = "2.5.2"
@@ -13044,7 +12830,7 @@ dependencies = [
"futures-util",
"headers",
"http 0.2.12",
"hyper 0.14.30",
"hyper",
"log",
"mime",
"mime_guess",
@@ -13614,15 +13400,6 @@ version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "webpki-roots"
version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.8"
@@ -14652,7 +14429,6 @@ dependencies = [
"image_viewer",
"inline_completion_button",
"install_cli",
"isahc",
"isahc_http_client",
"journal",
"language",
@@ -14904,7 +14680,7 @@ dependencies = [
[[package]]
name = "zed_terraform"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"zed_extension_api 0.1.0",
]

View File

@@ -129,7 +129,6 @@ members = [
"crates/worktree",
"crates/zed",
"crates/zed_actions",
"crates/hyper_client",
#
# Extensions
@@ -357,9 +356,7 @@ git2 = { version = "0.19", default-features = false }
globset = "0.4"
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
hex = "0.4.3"
hyper = "1.4.1"
hyper-util = "0.1.9"
hyper-rustls = "0.27.3"
hyper = "0.14"
html5ever = "0.27.0"
ignore = "0.4.22"
image = "0.25.1"

View File

@@ -196,7 +196,7 @@
}
},
{
"context": "BufferSearchBar && in_replace",
"context": "BufferSearchBar && in_replace > Editor",
"bindings": {
"enter": "search::ReplaceNext",
"ctrl-enter": "search::ReplaceAll"
@@ -310,6 +310,11 @@
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
"ctrl-shift-[": "editor::Fold",
"ctrl-shift-]": "editor::UnfoldLines",
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"alt-ctrl-r": "editor::RevealInFileManager",

View File

@@ -232,7 +232,7 @@
}
},
{
"context": "BufferSearchBar && in_replace",
"context": "BufferSearchBar && in_replace > Editor",
"bindings": {
"enter": "search::ReplaceNext",
"cmd-enter": "search::ReplaceAll"
@@ -347,6 +347,11 @@
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"cmd-k cmd-l": "editor::ToggleFold",
"cmd-k cmd-[": "editor::FoldRecursive",
"cmd-k cmd-]": "editor::UnfoldRecursive",
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"cmd-.": "editor::ToggleCodeActions",
"alt-cmd-r": "editor::RevealInFileManager",

View File

@@ -132,9 +132,15 @@
"z z": "editor::ScrollCursorCenter",
"z .": ["workspace::SendKeystrokes", "z z ^"],
"z b": "editor::ScrollCursorBottom",
"z a": "editor::ToggleFold",
"z A": "editor::ToggleFoldRecursive",
"z c": "editor::Fold",
"z C": "editor::FoldRecursive",
"z o": "editor::UnfoldLines",
"z O": "editor::UnfoldRecursive",
"z f": "editor::FoldSelectedRanges",
"z M": "editor::FoldAll",
"z R": "editor::UnfoldAll",
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
// Count support

View File

@@ -535,17 +535,16 @@
// How to soft-wrap long lines of text.
// Possible values:
//
// 1. Do not soft wrap.
// 1. Prefer a single line generally, unless an overly long line is encountered.
// "soft_wrap": "none",
// 2. Prefer a single line generally, unless an overly long line is encountered.
// "soft_wrap": "prefer_line",
// 3. Soft wrap lines that overflow the editor.
// "soft_wrap": "prefer_line", // (deprecated, same as "none")
// 2. Soft wrap lines that overflow the editor.
// "soft_wrap": "editor_width",
// 4. Soft wrap lines at the preferred line length.
// 3. Soft wrap lines at the preferred line length.
// "soft_wrap": "preferred_line_length",
// 5. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
// 4. Soft wrap lines at the preferred line length or the editor width (whichever is smaller).
// "soft_wrap": "bounded",
"soft_wrap": "prefer_line",
"soft_wrap": "none",
// The column at which to soft-wrap lines, for buffers where soft-wrap
// is enabled.
"preferred_line_length": 80,

View File

@@ -20,7 +20,6 @@ anyhow.workspace = true
chrono.workspace = true
futures.workspace = true
http_client.workspace = true
isahc.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true

View File

@@ -6,8 +6,8 @@ use std::{pin::Pin, str::FromStr};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use isahc::http::{HeaderMap, HeaderValue};
use http_client::http::{HeaderMap, HeaderValue};
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use serde::{Deserialize, Serialize};
use strum::{EnumIter, EnumString};
use thiserror::Error;
@@ -288,7 +288,7 @@ pub async fn stream_completion_with_rate_limit_info(
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let serialized_request =
serde_json::to_string(&request).context("failed to serialize request")?;

View File

@@ -72,6 +72,7 @@ use std::{
time::Duration,
};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use text::SelectionGoal;
use ui::TintColor;
use ui::{
prelude::*,
@@ -960,7 +961,8 @@ impl AssistantPanel {
}
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
if self.project.read(cx).is_via_collab() {
let project = self.project.read(cx);
if project.is_via_collab() && project.dev_server_project_id().is_none() {
let task = self
.context_store
.update(cx, |store, cx| store.create_remote_context(cx));
@@ -3437,7 +3439,7 @@ impl ContextEditor {
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
if self.editor.read(cx).selections.count() == 1 {
let (copied_text, metadata) = self.get_clipboard_contents(cx);
let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
copied_text,
metadata,
@@ -3451,11 +3453,9 @@ impl ContextEditor {
fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
if self.editor.read(cx).selections.count() == 1 {
let (copied_text, metadata) = self.get_clipboard_contents(cx);
let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
self.editor.update(cx, |editor, cx| {
let selections = editor.selections.all::<Point>(cx);
editor.transact(cx, |this, cx| {
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(selections);
@@ -3475,52 +3475,71 @@ impl ContextEditor {
cx.propagate();
}
fn get_clipboard_contents(&mut self, cx: &mut ViewContext<Self>) -> (String, CopyMetadata) {
let creases = self.editor.update(cx, |editor, cx| {
let selection = editor.selections.newest::<Point>(cx);
let selection_start = editor.selections.newest::<usize>(cx).start;
fn get_clipboard_contents(
&mut self,
cx: &mut ViewContext<Self>,
) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
let mut selection = editor.selections.newest::<Point>(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
editor.display_map.update(cx, |display_map, cx| {
display_map
.snapshot(cx)
.crease_snapshot
.creases_in_range(
MultiBufferRow(selection.start.row)..MultiBufferRow(selection.end.row + 1),
&snapshot,
)
.filter_map(|crease| {
if let Some(metadata) = &crease.metadata {
let start = crease
.range
.start
.to_offset(&snapshot)
.saturating_sub(selection_start);
let end = crease
.range
.end
.to_offset(&snapshot)
.saturating_sub(selection_start);
let range_relative_to_selection = start..end;
let is_entire_line = selection.is_empty() || editor.selections.line_mode;
if is_entire_line {
selection.start = Point::new(selection.start.row, 0);
selection.end =
cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
selection.goal = SelectionGoal::None;
}
if range_relative_to_selection.is_empty() {
None
let selection_start = snapshot.point_to_offset(selection.start);
(
snapshot.clone(),
selection.clone(),
editor.display_map.update(cx, |display_map, cx| {
display_map
.snapshot(cx)
.crease_snapshot
.creases_in_range(
MultiBufferRow(selection.start.row)
..MultiBufferRow(selection.end.row + 1),
&snapshot,
)
.filter_map(|crease| {
if let Some(metadata) = &crease.metadata {
let start = crease
.range
.start
.to_offset(&snapshot)
.saturating_sub(selection_start);
let end = crease
.range
.end
.to_offset(&snapshot)
.saturating_sub(selection_start);
let range_relative_to_selection = start..end;
if range_relative_to_selection.is_empty() {
None
} else {
Some(SelectedCreaseMetadata {
range_relative_to_selection,
crease: metadata.clone(),
})
}
} else {
Some(SelectedCreaseMetadata {
range_relative_to_selection,
crease: metadata.clone(),
})
None
}
} else {
None
}
})
.collect::<Vec<_>>()
})
})
.collect::<Vec<_>>()
}),
)
});
let selection = selection.map(|point| snapshot.point_to_offset(point));
let context = self.context.read(cx);
let selection = self.editor.read(cx).selections.newest::<usize>(cx);
let mut text = String::new();
for message in context.messages(cx) {
if message.offset_range.start >= selection.range().end {
@@ -3539,7 +3558,7 @@ impl ContextEditor {
}
}
(text, CopyMetadata { creases })
(text, CopyMetadata { creases }, vec![selection])
}
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {

View File

@@ -1142,7 +1142,7 @@ impl InlineAssistant {
for row_range in inserted_row_ranges {
editor.highlight_rows::<InlineAssist>(
row_range,
Some(cx.theme().status().info_background),
cx.theme().status().info_background,
false,
cx,
);
@@ -1208,8 +1208,8 @@ impl InlineAssistant {
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.highlight_rows::<DeletedLines>(
Anchor::min()..=Anchor::max(),
Some(cx.theme().status().deleted_background),
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,
false,
cx,
);
@@ -2557,7 +2557,7 @@ enum CodegenStatus {
#[derive(Default)]
struct Diff {
deleted_row_ranges: Vec<(Anchor, RangeInclusive<u32>)>,
inserted_row_ranges: Vec<RangeInclusive<Anchor>>,
inserted_row_ranges: Vec<Range<Anchor>>,
}
impl Diff {
@@ -3103,7 +3103,7 @@ impl CodegenAlternative {
new_end_row,
new_snapshot.line_len(MultiBufferRow(new_end_row)),
));
self.diff.inserted_row_ranges.push(start..=end);
self.diff.inserted_row_ranges.push(start..end);
new_row += lines;
}
}
@@ -3181,7 +3181,7 @@ impl CodegenAlternative {
new_end_row,
new_snapshot.line_len(MultiBufferRow(new_end_row)),
));
inserted_row_ranges.push(start..=end);
inserted_row_ranges.push(start..end);
new_row += line_count;
}
}

View File

@@ -1,7 +1,7 @@
use editor::Editor;
use gpui::{
Element, EventEmitter, IntoElement, ParentElement, Render, StyledText, Subscription,
ViewContext,
Element, EventEmitter, FocusableView, IntoElement, ParentElement, Render, StyledText,
Subscription, ViewContext,
};
use itertools::Itertools;
use std::cmp;
@@ -90,17 +90,30 @@ impl Render for Breadcrumbs {
ButtonLike::new("toggle outline view")
.child(breadcrumbs_stack)
.style(ButtonStyle::Transparent)
.on_click(move |_, cx| {
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
.on_click({
let editor = editor.clone();
move |_, cx| {
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
}
}
})
.tooltip(|cx| {
Tooltip::for_action(
"Show symbol outline",
&editor::actions::ToggleOutline,
cx,
)
.tooltip(move |cx| {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show symbol outline",
&editor::actions::ToggleOutline,
&focus_handle,
cx,
)
} else {
Tooltip::for_action(
"Show symbol outline",
&editor::actions::ToggleOutline,
cx,
)
}
}),
),
None => element

View File

@@ -149,18 +149,6 @@ spec:
secretKeyRef:
name: google-ai
key: api_key
- name: RUNPOD_API_KEY
valueFrom:
secretKeyRef:
name: runpod
key: api_key
optional: true
- name: RUNPOD_API_SUMMARY_URL
valueFrom:
secretKeyRef:
name: runpod
key: summary
optional: true
- name: BLOB_STORE_ACCESS_KEY
valueFrom:
secretKeyRef:

View File

@@ -32,6 +32,7 @@ macro_rules! id_type {
#[allow(unused)]
#[allow(missing_docs)]
pub fn from_proto(value: u64) -> Self {
debug_assert!(value != 0);
Self(value as i32)
}

View File

@@ -285,7 +285,7 @@ impl Database {
)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
.ok_or_else(|| anyhow!("no such project: {project_id}"))?;
// Update metadata.
worktree::Entity::update(worktree::ActiveModel {

View File

@@ -170,8 +170,6 @@ pub struct Config {
pub anthropic_api_key: Option<Arc<str>>,
pub anthropic_staff_api_key: Option<Arc<str>>,
pub llm_closed_beta_model_name: Option<Arc<str>>,
pub runpod_api_key: Option<Arc<str>>,
pub runpod_api_summary_url: Option<Arc<str>>,
pub zed_client_checksum_seed: Option<String>,
pub slack_panics_webhook: Option<String>,
pub auto_join_channel_id: Option<ChannelId>,
@@ -235,8 +233,6 @@ impl Config {
stripe_api_key: None,
stripe_price_id: None,
supermaven_admin_api_key: None,
runpod_api_key: None,
runpod_api_summary_url: None,
user_backfiller_github_access_token: None,
}
}

View File

@@ -400,42 +400,6 @@ async fn perform_completion(
})
.boxed()
}
LanguageModelProvider::Zed => {
let api_key = state
.config
.runpod_api_key
.as_ref()
.context("no Qwen2-7B API key configured on the server")?;
let api_url = state
.config
.runpod_api_summary_url
.as_ref()
.context("no Qwen2-7B URL configured on the server")?;
let chunks = open_ai::stream_completion(
&state.http_client,
api_url,
api_key,
serde_json::from_str(params.provider_request.get())?,
None,
)
.await?;
chunks
.map(|event| {
event.map(|chunk| {
let input_tokens =
chunk.usage.as_ref().map_or(0, |u| u.prompt_tokens) as usize;
let output_tokens =
chunk.usage.as_ref().map_or(0, |u| u.completion_tokens) as usize;
(
serde_json::to_vec(&chunk).unwrap(),
input_tokens,
output_tokens,
)
})
})
.boxed()
}
};
Ok(Response::new(Body::wrap_stream(TokenCountingStream {

View File

@@ -77,7 +77,6 @@ fn authorize_access_for_country(
LanguageModelProvider::Anthropic => anthropic::is_supported_country(country_code),
LanguageModelProvider::OpenAi => open_ai::is_supported_country(country_code),
LanguageModelProvider::Google => google_ai::is_supported_country(country_code),
LanguageModelProvider::Zed => true,
};
if !is_country_supported_by_provider {
Err(Error::http(
@@ -213,7 +212,6 @@ mod tests {
(LanguageModelProvider::Anthropic, "T1"), // Tor
(LanguageModelProvider::OpenAi, "T1"), // Tor
(LanguageModelProvider::Google, "T1"), // Tor
(LanguageModelProvider::Zed, "T1"), // Tor
];
for (provider, country_code) in cases {

View File

@@ -40,15 +40,6 @@ pub async fn seed_database(_config: &Config, db: &mut LlmDatabase, _force: bool)
price_per_million_input_tokens: 25, // $0.25/MTok
price_per_million_output_tokens: 125, // $1.25/MTok
},
ModelParams {
provider: LanguageModelProvider::Zed,
name: "Qwen/Qwen2-7B-Instruct".into(),
max_requests_per_minute: 5,
max_tokens_per_minute: 25_000, // These are arbitrary limits we've set to cap costs; we control this number
max_tokens_per_day: 300_000,
price_per_million_input_tokens: 25,
price_per_million_output_tokens: 125,
},
])
.await
}

View File

@@ -26,7 +26,6 @@ async fn test_initialize_providers(db: &mut LlmDatabase) {
LanguageModelProvider::Anthropic,
LanguageModelProvider::Google,
LanguageModelProvider::OpenAi,
LanguageModelProvider::Zed
]
)
}

View File

@@ -474,9 +474,6 @@ impl Server {
.add_request_handler(user_handler(
forward_read_only_project_request::<proto::GetReferences>,
))
.add_request_handler(user_handler(
forward_read_only_project_request::<proto::SearchProject>,
))
.add_request_handler(user_handler(forward_find_search_candidates_request))
.add_request_handler(user_handler(
forward_read_only_project_request::<proto::GetDocumentHighlights>,
@@ -2298,7 +2295,7 @@ async fn list_remote_directory(
let dev_server_connection_id = session
.connection_pool()
.await
.dev_server_connection_id_supporting(dev_server_id, ZedVersion::with_list_directory())?;
.online_dev_server_connection_id(dev_server_id)?;
session
.db()
@@ -2337,10 +2334,7 @@ async fn update_dev_server_project(
let dev_server_connection_id = session
.connection_pool()
.await
.dev_server_connection_id_supporting(
dev_server_project.dev_server_id,
ZedVersion::with_list_directory(),
)?;
.online_dev_server_connection_id(dev_server_project.dev_server_id)?;
session.peer.send(
dev_server_connection_id,
@@ -2950,40 +2944,6 @@ async fn forward_find_search_candidates_request(
.await
.host_for_read_only_project_request(project_id, session.connection_id, session.user_id())
.await?;
let host_version = session
.connection_pool()
.await
.connection(host_connection_id)
.map(|c| c.zed_version);
if host_version.is_some_and(|host_version| host_version < ZedVersion::with_search_candidates())
{
let query = request.query.ok_or_else(|| anyhow!("missing query"))?;
let search = proto::SearchProject {
project_id: project_id.to_proto(),
query: query.query,
regex: query.regex,
whole_word: query.whole_word,
case_sensitive: query.case_sensitive,
files_to_include: query.files_to_include,
files_to_exclude: query.files_to_exclude,
include_ignored: query.include_ignored,
};
let payload = session
.peer
.forward_request(session.connection_id, host_connection_id, search)
.await?;
return response.send(proto::FindSearchCandidatesResponse {
buffer_ids: payload
.locations
.into_iter()
.map(|loc| loc.buffer_id)
.collect(),
});
}
let payload = session
.peer
.forward_request(session.connection_id, host_connection_id, request)

View File

@@ -32,15 +32,7 @@ impl fmt::Display for ZedVersion {
impl ZedVersion {
pub fn can_collaborate(&self) -> bool {
self.0 >= SemanticVersion::new(0, 134, 0)
}
pub fn with_list_directory() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 145, 0))
}
pub fn with_search_candidates() -> ZedVersion {
ZedVersion(SemanticVersion::new(0, 151, 0))
self.0 >= SemanticVersion::new(0, 151, 0)
}
}
@@ -169,6 +161,16 @@ impl ConnectionPool {
self.connected_dev_servers.get(&dev_server_id).copied()
}
pub fn online_dev_server_connection_id(
&self,
dev_server_id: DevServerId,
) -> Result<ConnectionId> {
match self.connected_dev_servers.get(&dev_server_id) {
Some(cid) => Ok(*cid),
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
}
}
pub fn dev_server_connection_id_supporting(
&self,
dev_server_id: DevServerId,

View File

@@ -246,7 +246,7 @@ async fn test_channel_notes_participant_indices(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
// Clients A and B open the same file.

View File

@@ -76,7 +76,7 @@ async fn test_host_disconnect(
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
cx_a.background_executor.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
@@ -192,7 +192,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client A
let buffer_a = project_a
@@ -308,7 +308,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a file in an editor as the guest.
let buffer_b = project_b
@@ -565,7 +565,7 @@ async fn test_collaborating_with_code_actions(
.unwrap();
// Join the project as client B.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
@@ -780,7 +780,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
@@ -1030,7 +1030,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
.await
.unwrap();
executor.run_until_parked();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
project_b.read_with(cx_b, |project, cx| {
let status = project.language_server_statuses(cx).next().unwrap().1;
@@ -1126,9 +1126,7 @@ async fn test_share_project(
.await
.unwrap();
let client_b_peer_id = client_b.peer_id().unwrap();
let project_b = client_b
.build_dev_server_project(initial_project.id, cx_b)
.await;
let project_b = client_b.join_remote_project(initial_project.id, cx_b).await;
let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
@@ -1230,9 +1228,7 @@ async fn test_share_project(
.update(cx_c, |call, cx| call.accept_incoming(cx))
.await
.unwrap();
let _project_c = client_c
.build_dev_server_project(initial_project.id, cx_c)
.await;
let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
// Client B closes the editor, and client A sees client B's selections removed.
cx_b.update(move |_| drop(editor_b));
@@ -1291,7 +1287,7 @@ async fn test_on_input_format_from_host_to_guest(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a file in an editor as the host.
let buffer_a = project_a
@@ -1411,7 +1407,7 @@ async fn test_on_input_format_from_guest_to_host(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a file in an editor as the guest.
let buffer_b = project_b
@@ -1574,7 +1570,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap();
// Client B joins the project
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
@@ -1836,7 +1832,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
@@ -2050,7 +2046,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.unwrap();
// Join the project as client B.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {

View File

@@ -74,7 +74,7 @@ async fn test_basic_following(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
@@ -162,7 +162,7 @@ async fn test_basic_following(
executor.run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global);
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_c = client_c.join_remote_project(project_id, cx_c).await;
let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c);
active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
@@ -175,7 +175,7 @@ async fn test_basic_following(
cx_d.executor().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_dev_server_project(project_id, cx_d).await;
let project_d = client_d.join_remote_project(project_id, cx_d).await;
let (workspace_d, cx_d) = client_d.build_workspace(&project_d, cx_d);
active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
@@ -569,7 +569,7 @@ async fn test_following_tab_order(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
@@ -686,7 +686,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
.unwrap();
// Client B joins the project.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
@@ -1199,7 +1199,7 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await
@@ -1335,7 +1335,7 @@ async fn test_peers_simultaneously_following_each_other(
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
executor.run_until_parked();
@@ -1685,7 +1685,7 @@ async fn test_following_into_excluded_file(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
active_call_b
.update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
.await

View File

@@ -1372,7 +1372,7 @@ async fn test_unshare_project(
.unwrap();
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
executor.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
@@ -1392,7 +1392,7 @@ async fn test_unshare_project(
assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected()));
// Client C opens the project.
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_c = client_c.join_remote_project(project_id, cx_c).await;
// When client A unshares the project, client C's project becomes read-only.
project_a
@@ -1409,7 +1409,7 @@ async fn test_unshare_project(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_c2 = client_c.build_dev_server_project(project_id, cx_c).await;
let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
@@ -1514,9 +1514,9 @@ async fn test_project_reconnect(
.await
.unwrap();
let project_b1 = client_b.build_dev_server_project(project1_id, cx_b).await;
let project_b2 = client_b.build_dev_server_project(project2_id, cx_b).await;
let project_b3 = client_b.build_dev_server_project(project3_id, cx_b).await;
let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
executor.run_until_parked();
let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
@@ -2310,8 +2310,8 @@ async fn test_propagate_saves_and_fs_changes(
.unwrap();
// Join that worktree as clients B and C.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let project_c = client_c.join_remote_project(project_id, cx_c).await;
let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
@@ -2535,7 +2535,7 @@ async fn test_git_diff_base_change(
.await
.unwrap();
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await;
let project_remote = client_b.join_remote_project(project_id, cx_b).await;
let diff_base = "
one
@@ -2791,7 +2791,7 @@ async fn test_git_branch_name(
.await
.unwrap();
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await;
let project_remote = client_b.join_remote_project(project_id, cx_b).await;
client_a
.fs()
.set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
@@ -2836,7 +2836,7 @@ async fn test_git_branch_name(
assert_branch(Some("branch-2"), project, cx)
});
let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked();
project_remote_c.read_with(cx_c, |project, cx| {
@@ -2891,7 +2891,7 @@ async fn test_git_status_sync(
.await
.unwrap();
let project_remote = client_b.build_dev_server_project(project_id, cx_b).await;
let project_remote = client_b.join_remote_project(project_id, cx_b).await;
// Wait for it to catch up to the new status
executor.run_until_parked();
@@ -2967,7 +2967,7 @@ async fn test_git_status_sync(
});
// And synchronization while joining
let project_remote_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked();
project_remote_c.read_with(cx_c, |project, cx| {
@@ -3015,7 +3015,7 @@ async fn test_fs_operations(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
@@ -3316,7 +3316,7 @@ async fn test_local_settings(
executor.run_until_parked();
// As client B, join that project and observe the local settings.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
executor.run_until_parked();
@@ -3439,7 +3439,7 @@ async fn test_buffer_conflict_after_save(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client B
let buffer_b = project_b
@@ -3503,7 +3503,7 @@ async fn test_buffer_reloading(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client B
let buffer_b = project_b
@@ -3557,7 +3557,7 @@ async fn test_editing_while_guest_opens_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open a buffer as client A
let buffer_a = project_a
@@ -3605,7 +3605,7 @@ async fn test_leaving_worktree_while_opening_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// See that a guest has joined as client A.
executor.run_until_parked();
@@ -3652,7 +3652,7 @@ async fn test_canceling_buffer_opening(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
@@ -3709,8 +3709,8 @@ async fn test_leaving_project(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b1 = client_b.build_dev_server_project(project_id, cx_b).await;
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
let project_c = client_c.join_remote_project(project_id, cx_c).await;
// Client A sees that a guest has joined.
executor.run_until_parked();
@@ -3751,7 +3751,7 @@ async fn test_leaving_project(
});
// Client B re-joins the project and can open buffers as before.
let project_b2 = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
executor.run_until_parked();
project_a.read_with(cx_a, |project, _| {
@@ -3927,7 +3927,7 @@ async fn test_collaborating_with_diagnostics(
);
// Join the worktree as client B.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Wait for server to see the diagnostics update.
executor.run_until_parked();
@@ -3952,7 +3952,7 @@ async fn test_collaborating_with_diagnostics(
});
// Join project as client C and observe the diagnostics.
let project_c = client_c.build_dev_server_project(project_id, cx_c).await;
let project_c = client_c.join_remote_project(project_id, cx_c).await;
executor.run_until_parked();
let project_c_diagnostic_summaries =
Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
@@ -4160,7 +4160,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
.unwrap();
// Join the project as client B and open all three files.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx))
}))
@@ -4266,7 +4266,7 @@ async fn test_reloading_buffer_manually(
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
@@ -4364,7 +4364,7 @@ async fn test_formatting_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
@@ -4486,7 +4486,7 @@ async fn test_prettier_formatting_buffer(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
@@ -4599,7 +4599,7 @@ async fn test_definition(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B.
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
@@ -4744,7 +4744,7 @@ async fn test_references(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B.
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx));
@@ -4901,7 +4901,7 @@ async fn test_project_search(
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Perform a search as the guest.
let mut results = HashMap::default();
@@ -4991,7 +4991,7 @@ async fn test_document_highlights(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file on client B.
let open_b = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
@@ -5109,7 +5109,7 @@ async fn test_lsp_hover(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Open the file as the guest
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx));
@@ -5286,7 +5286,7 @@ async fn test_project_symbols(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Cause the language server to start.
let open_buffer_task =
@@ -5381,7 +5381,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let open_buffer_task = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
let buffer_b1 = cx_b.executor().spawn(open_buffer_task).await.unwrap();
@@ -6470,7 +6470,7 @@ async fn test_context_collaboration_with_reconnect(
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
// Client A sees that a guest has joined.
executor.run_until_parked();

View File

@@ -9,7 +9,7 @@ use remote_server::HeadlessProject;
use serde_json::json;
use std::{path::Path, sync::Arc};
#[gpui::test]
#[gpui::test(iterations = 10)]
async fn test_sharing_an_ssh_remote_project(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
@@ -54,9 +54,8 @@ async fn test_sharing_an_ssh_remote_project(
let (project_a, worktree_id) = client_a
.build_ssh_project("/code/project1", client_ssh, cx_a)
.await;
executor.run_until_parked();
// User A shares the remote project.
// While the SSH worktree is being scanned, user A shares the remote project.
let active_call_a = cx_a.read(ActiveCall::global);
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
@@ -64,12 +63,30 @@ async fn test_sharing_an_ssh_remote_project(
.unwrap();
// User B joins the project.
let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let worktree_b = project_b
.update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx))
.unwrap();
let worktree_a = project_a
.update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx))
.unwrap();
executor.run_until_parked();
worktree_a.update(cx_a, |worktree, _cx| {
assert_eq!(
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
vec![
Path::new(".zed"),
Path::new(".zed/settings.json"),
Path::new("README.md"),
Path::new("src"),
Path::new("src/lib.rs"),
]
);
});
worktree_b.update(cx_b, |worktree, _cx| {
assert_eq!(
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),

View File

@@ -679,8 +679,6 @@ impl TestServer {
stripe_api_key: None,
stripe_price_id: None,
supermaven_admin_api_key: None,
runpod_api_key: None,
runpod_api_summary_url: None,
user_backfiller_github_access_token: None,
},
})
@@ -921,7 +919,7 @@ impl TestClient {
})
}
pub async fn build_dev_server_project(
pub async fn join_remote_project(
&self,
host_project_id: u64,
guest_cx: &mut TestAppContext,

View File

@@ -37,7 +37,6 @@ fs.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
isahc.workspace = true
language.workspace = true
lsp.workspace = true
menu.workspace = true

View File

@@ -7,8 +7,7 @@ use chrono::DateTime;
use fs::Fs;
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, Global};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use isahc::config::Configurable;
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use paths::home_dir;
use serde::{Deserialize, Serialize};
use settings::watch_config_file;
@@ -275,7 +274,7 @@ async fn request_api_token(
.header("Accept", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let request = request_builder.body(AsyncBody::empty())?;
@@ -332,7 +331,7 @@ async fn stream_completion(
.header("Copilot-Integration-Id", "vscode-chat");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
let mut response = client.send(request).await?;

View File

@@ -24,7 +24,8 @@ test-support = [
"workspace/test-support",
"tree-sitter-rust",
"tree-sitter-typescript",
"tree-sitter-html"
"tree-sitter-html",
"unindent",
]
[dependencies]
@@ -54,6 +55,7 @@ markdown.workspace = true
multi_buffer.workspace = true
ordered-float.workspace = true
parking_lot.workspace = true
pretty_assertions.workspace = true
project.workspace = true
rand.workspace = true
rpc.workspace = true
@@ -74,6 +76,7 @@ theme.workspace = true
tree-sitter-html = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
unindent = { workspace = true, optional = true }
ui.workspace = true
url.workspace = true
util.workspace = true

View File

@@ -230,7 +230,11 @@ gpui::actions!(
ExpandMacroRecursively,
FindAllReferences,
Fold,
FoldAll,
FoldRecursive,
FoldSelectedRanges,
ToggleFold,
ToggleFoldRecursive,
Format,
GoToDeclaration,
GoToDeclarationSplit,
@@ -340,7 +344,9 @@ gpui::actions!(
Transpose,
Undo,
UndoSelection,
UnfoldAll,
UnfoldLines,
UnfoldRecursive,
UniqueLinesCaseInsensitive,
UniqueLinesCaseSensitive,
]

View File

@@ -98,7 +98,9 @@ use language::{
};
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
use proposed_changes_editor::{ProposedChangesBuffer, ProposedChangesEditor};
pub use proposed_changes_editor::{
ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar,
};
use similar::{ChangeTag, TextDiff};
use task::{ResolvedTask, TaskTemplate, TaskVariables};
@@ -374,12 +376,20 @@ pub enum EditorMode {
Full,
}
#[derive(Clone, Debug)]
#[derive(Copy, Clone, Debug)]
pub enum SoftWrap {
/// Prefer not to wrap at all.
///
/// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
/// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
GitDiff,
/// Prefer a single line generally, unless an overly long line is encountered.
None,
PreferLine,
/// Soft wrap lines that exceed the editor width.
EditorWidth,
/// Soft wrap lines at the preferred line length.
Column(u32),
/// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
Bounded(u32),
}
@@ -661,7 +671,7 @@ pub struct EditorSnapshot {
show_git_diff_gutter: Option<bool>,
show_code_actions: Option<bool>,
show_runnables: Option<bool>,
render_git_blame_gutter: bool,
git_blame_gutter_max_author_length: Option<usize>,
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
is_focused: bool,
@@ -671,7 +681,7 @@ pub struct EditorSnapshot {
gutter_hovered: bool,
}
const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
#[derive(Default, Debug, Clone, Copy)]
pub struct GutterDimensions {
@@ -811,8 +821,8 @@ impl SelectionHistory {
struct RowHighlight {
index: usize,
range: RangeInclusive<Anchor>,
color: Option<Hsla>,
range: Range<Anchor>,
color: Hsla,
should_autoscroll: bool,
}
@@ -1835,7 +1845,7 @@ impl Editor {
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
.then(|| language_settings::SoftWrap::PreferLine);
.then(|| language_settings::SoftWrap::None);
let mut project_subscriptions = Vec::new();
if mode == EditorMode::Full {
@@ -2209,6 +2219,19 @@ impl Editor {
}
pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
let git_blame_gutter_max_author_length = self
.render_git_blame_gutter(cx)
.then(|| {
if let Some(blame) = self.blame.as_ref() {
let max_author_length =
blame.update(cx, |blame, cx| blame.max_author_length(cx));
Some(max_author_length)
} else {
None
}
})
.flatten();
EditorSnapshot {
mode: self.mode,
show_gutter: self.show_gutter,
@@ -2216,7 +2239,7 @@ impl Editor {
show_git_diff_gutter: self.show_git_diff_gutter,
show_code_actions: self.show_code_actions,
show_runnables: self.show_runnables,
render_git_blame_gutter: self.render_git_blame_gutter(cx),
git_blame_gutter_max_author_length,
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
scroll_anchor: self.scroll_manager.anchor(),
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
@@ -3440,7 +3463,7 @@ impl Editor {
s.select(new_selections)
});
if !bracket_inserted && EditorSettings::get_global(cx).use_on_type_format {
if !bracket_inserted {
if let Some(on_type_format_task) =
this.trigger_on_type_formatting(text.to_string(), cx)
{
@@ -4189,6 +4212,15 @@ impl Editor {
.read(cx)
.text_anchor_for_position(position, cx)?;
let settings = language_settings::language_settings(
buffer.read(cx).language_at(buffer_position).as_ref(),
buffer.read(cx).file(),
cx,
);
if !settings.use_on_type_format {
return None;
}
// OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
// hence we do LSP request & edit on host side only — add formats to host's history.
let push_to_lsp_host_history = true;
@@ -9665,7 +9697,7 @@ impl Editor {
if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
let buffer = target.buffer.read(cx);
let range = check_multiline_range(buffer, range);
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
});
} else {
@@ -10519,17 +10551,79 @@ impl Editor {
}
}
pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
pub fn toggle_fold(&mut self, _: &actions::ToggleFold, cx: &mut ViewContext<Self>) {
let selection = self.selections.newest::<Point>(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let range = if selection.is_empty() {
let point = selection.head().to_display_point(&display_map);
let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
.to_point(&display_map);
start..end
} else {
selection.range()
};
if display_map.folds_in_range(range).next().is_some() {
self.unfold_lines(&Default::default(), cx)
} else {
self.fold(&Default::default(), cx)
}
}
pub fn toggle_fold_recursive(
&mut self,
_: &actions::ToggleFoldRecursive,
cx: &mut ViewContext<Self>,
) {
let selection = self.selections.newest::<Point>(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let range = if selection.is_empty() {
let point = selection.head().to_display_point(&display_map);
let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
.to_point(&display_map);
start..end
} else {
selection.range()
};
if display_map.folds_in_range(range).next().is_some() {
self.unfold_recursive(&Default::default(), cx)
} else {
self.fold_recursive(&Default::default(), cx)
}
}
pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all_adjusted(cx);
for selection in selections {
let range = selection.range().sorted();
let buffer_start_row = range.start.row;
for row in (0..=range.end.row).rev() {
if range.start.row != range.end.row {
let mut found = false;
let mut row = range.start.row;
while row <= range.end.row {
if let Some((foldable_range, fold_text)) =
{ display_map.foldable_range(MultiBufferRow(row)) }
{
found = true;
row = foldable_range.end.row + 1;
fold_ranges.push((foldable_range, fold_text));
} else {
row += 1
}
}
if found {
continue;
}
}
for row in (0..=range.start.row).rev() {
if let Some((foldable_range, fold_text)) =
display_map.foldable_range(MultiBufferRow(row))
{
@@ -10546,6 +10640,61 @@ impl Editor {
self.fold_ranges(fold_ranges, true, cx);
}
pub fn fold_all(&mut self, _: &actions::FoldAll, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
for row in 0..display_map.max_buffer_row().0 {
if let Some((foldable_range, fold_text)) =
display_map.foldable_range(MultiBufferRow(row))
{
fold_ranges.push((foldable_range, fold_text));
}
}
self.fold_ranges(fold_ranges, true, cx);
}
pub fn fold_recursive(&mut self, _: &actions::FoldRecursive, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all_adjusted(cx);
for selection in selections {
let range = selection.range().sorted();
let buffer_start_row = range.start.row;
if range.start.row != range.end.row {
let mut found = false;
for row in range.start.row..=range.end.row {
if let Some((foldable_range, fold_text)) =
{ display_map.foldable_range(MultiBufferRow(row)) }
{
found = true;
fold_ranges.push((foldable_range, fold_text));
}
}
if found {
continue;
}
}
for row in (0..=range.start.row).rev() {
if let Some((foldable_range, fold_text)) =
display_map.foldable_range(MultiBufferRow(row))
{
if foldable_range.end.row >= buffer_start_row {
fold_ranges.push((foldable_range, fold_text));
} else {
break;
}
}
}
}
self.fold_ranges(fold_ranges, true, cx);
}
pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext<Self>) {
let buffer_row = fold_at.buffer_row;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -10580,6 +10729,24 @@ impl Editor {
self.unfold_ranges(ranges, true, true, cx);
}
pub fn unfold_recursive(&mut self, _: &UnfoldRecursive, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
let ranges = selections
.iter()
.map(|s| {
let mut range = s.display_range(&display_map).sorted();
*range.start.column_mut() = 0;
*range.end.column_mut() = display_map.line_len(range.end.row());
let start = range.start.to_point(&display_map);
let end = range.end.to_point(&display_map);
start..end
})
.collect::<Vec<_>>();
self.unfold_ranges(ranges, true, true, cx);
}
pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -10598,6 +10765,16 @@ impl Editor {
self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
}
pub fn unfold_all(&mut self, _: &actions::UnfoldAll, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.unfold_ranges(
[Point::zero()..display_map.max_point().to_point(&display_map)],
true,
true,
cx,
);
}
pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {
let selections = self.selections.all::<Point>(cx);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -10874,8 +11051,9 @@ impl Editor {
let settings = self.buffer.read(cx).settings_at(0, cx);
let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
match mode {
language_settings::SoftWrap::None => SoftWrap::None,
language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine,
language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
SoftWrap::None
}
language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
language_settings::SoftWrap::PreferredLineLength => {
SoftWrap::Column(settings.preferred_line_length)
@@ -10923,9 +11101,10 @@ impl Editor {
self.soft_wrap_mode_override.take();
} else {
let soft_wrap = match self.soft_wrap_mode(cx) {
SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth,
SoftWrap::GitDiff => return,
SoftWrap::None => language_settings::SoftWrap::EditorWidth,
SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
language_settings::SoftWrap::PreferLine
language_settings::SoftWrap::None
}
};
self.soft_wrap_mode_override = Some(soft_wrap);
@@ -11321,56 +11500,130 @@ impl Editor {
}
}
/// Adds or removes (on `None` color) a highlight for the rows corresponding to the anchor range given.
/// On matching anchor range, replaces the old highlight; does not clear the other existing highlights.
/// If multiple anchor ranges will produce highlights for the same row, the last range added will be used.
/// Adds a row highlight for the given range. If a row has multiple highlights, the
/// last highlight added will be used.
///
/// If the range ends at the beginning of a line, then that line will not be highlighted.
pub fn highlight_rows<T: 'static>(
&mut self,
rows: RangeInclusive<Anchor>,
color: Option<Hsla>,
range: Range<Anchor>,
color: Hsla,
should_autoscroll: bool,
cx: &mut ViewContext<Self>,
) {
let snapshot = self.buffer().read(cx).snapshot(cx);
let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
let existing_highlight_index = row_highlights.binary_search_by(|highlight| {
highlight
.range
.start()
.cmp(rows.start(), &snapshot)
.then(highlight.range.end().cmp(rows.end(), &snapshot))
let ix = row_highlights.binary_search_by(|highlight| {
Ordering::Equal
.then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
.then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
});
match (color, existing_highlight_index) {
(Some(_), Ok(ix)) | (_, Err(ix)) => row_highlights.insert(
ix,
RowHighlight {
index: post_inc(&mut self.highlight_order),
range: rows,
should_autoscroll,
color,
},
),
(None, Ok(i)) => {
row_highlights.remove(i);
if let Err(mut ix) = ix {
let index = post_inc(&mut self.highlight_order);
// If this range intersects with the preceding highlight, then merge it with
// the preceding highlight. Otherwise insert a new highlight.
let mut merged = false;
if ix > 0 {
let prev_highlight = &mut row_highlights[ix - 1];
if prev_highlight
.range
.end
.cmp(&range.start, &snapshot)
.is_ge()
{
ix -= 1;
if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
prev_highlight.range.end = range.end;
}
merged = true;
prev_highlight.index = index;
prev_highlight.color = color;
prev_highlight.should_autoscroll = should_autoscroll;
}
}
if !merged {
row_highlights.insert(
ix,
RowHighlight {
range: range.clone(),
index,
color,
should_autoscroll,
},
);
}
// If any of the following highlights intersect with this one, merge them.
while let Some(next_highlight) = row_highlights.get(ix + 1) {
let highlight = &row_highlights[ix];
if next_highlight
.range
.start
.cmp(&highlight.range.end, &snapshot)
.is_le()
{
if next_highlight
.range
.end
.cmp(&highlight.range.end, &snapshot)
.is_gt()
{
row_highlights[ix].range.end = next_highlight.range.end;
}
row_highlights.remove(ix + 1);
} else {
break;
}
}
}
}
/// Remove any highlighted row ranges of the given type that intersect the
/// given ranges.
pub fn remove_highlighted_rows<T: 'static>(
&mut self,
ranges_to_remove: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>,
) {
let snapshot = self.buffer().read(cx).snapshot(cx);
let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
let mut ranges_to_remove = ranges_to_remove.iter().peekable();
row_highlights.retain(|highlight| {
while let Some(range_to_remove) = ranges_to_remove.peek() {
match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
Ordering::Less | Ordering::Equal => {
ranges_to_remove.next();
}
Ordering::Greater => {
match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
Ordering::Less | Ordering::Equal => {
return false;
}
Ordering::Greater => break,
}
}
}
}
true
})
}
/// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
pub fn clear_row_highlights<T: 'static>(&mut self) {
self.highlighted_rows.remove(&TypeId::of::<T>());
}
/// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
pub fn highlighted_rows<T: 'static>(
&self,
) -> Option<impl Iterator<Item = (&RangeInclusive<Anchor>, Option<&Hsla>)>> {
Some(
self.highlighted_rows
.get(&TypeId::of::<T>())?
.iter()
.map(|highlight| (&highlight.range, highlight.color.as_ref())),
)
pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
self.highlighted_rows
.get(&TypeId::of::<T>())
.map_or(&[] as &[_], |vec| vec.as_slice())
.iter()
.map(|highlight| (highlight.range.clone(), highlight.color))
}
/// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
@@ -11388,17 +11641,22 @@ impl Editor {
.fold(
BTreeMap::<DisplayRow, Hsla>::new(),
|mut unique_rows, highlight| {
let start_row = highlight.range.start().to_display_point(&snapshot).row();
let end_row = highlight.range.end().to_display_point(&snapshot).row();
for row in start_row.0..=end_row.0 {
let start = highlight.range.start.to_display_point(&snapshot);
let end = highlight.range.end.to_display_point(&snapshot);
let start_row = start.row().0;
let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
&& end.column() == 0
{
end.row().0.saturating_sub(1)
} else {
end.row().0
};
for row in start_row..=end_row {
let used_index =
used_highlight_orders.entry(row).or_insert(highlight.index);
if highlight.index >= *used_index {
*used_index = highlight.index;
match highlight.color {
Some(hsla) => unique_rows.insert(DisplayRow(row), hsla),
None => unique_rows.remove(&DisplayRow(row)),
};
unique_rows.insert(DisplayRow(row), highlight.color);
}
}
unique_rows
@@ -11414,10 +11672,11 @@ impl Editor {
.values()
.flat_map(|highlighted_rows| highlighted_rows.iter())
.filter_map(|highlight| {
if highlight.color.is_none() || !highlight.should_autoscroll {
return None;
if highlight.should_autoscroll {
Some(highlight.range.start.to_display_point(snapshot).row())
} else {
None
}
Some(highlight.range.start().to_display_point(snapshot).row())
})
.min()
}
@@ -11923,12 +12182,19 @@ impl Editor {
)),
cx,
);
let editor_settings = EditorSettings::get_global(cx);
if let Some(cursor_shape) = editor_settings.cursor_shape {
self.cursor_shape = cursor_shape;
let old_cursor_shape = self.cursor_shape;
{
let editor_settings = EditorSettings::get_global(cx);
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
}
if old_cursor_shape != self.cursor_shape {
cx.emit(EditorEvent::CursorShapeChanged);
}
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
let project_settings = ProjectSettings::get_global(cx);
self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
@@ -12938,6 +13204,7 @@ impl EditorSnapshot {
font_id: FontId,
font_size: Pixels,
em_width: Pixels,
em_advance: Pixels,
max_line_number_width: Pixels,
cx: &AppContext,
) -> GutterDimensions {
@@ -12958,7 +13225,7 @@ impl EditorSnapshot {
.unwrap_or(gutter_settings.line_numbers);
let line_gutter_width = if show_line_numbers {
// Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
let min_width_for_number_on_gutter = em_width * 4.0;
let min_width_for_number_on_gutter = em_advance * 4.0;
max_line_number_width.max(min_width_for_number_on_gutter)
} else {
0.0.into()
@@ -12970,9 +13237,19 @@ impl EditorSnapshot {
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
let git_blame_entries_width = self
.render_git_blame_gutter
.then_some(em_width * GIT_BLAME_GUTTER_WIDTH_CHARS);
let git_blame_entries_width =
self.git_blame_gutter_max_author_length
.map(|max_author_length| {
// Length of the author name, but also space for the commit hash,
// the spacing and the timestamp.
let max_char_count = max_author_length
.min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
+ 7 // length of commit sha
+ 14 // length of max relative timestamp ("60 minutes ago")
+ 4; // gaps and margins
em_advance * max_char_count
});
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
left_padding += if show_code_actions || show_runnables {
@@ -13125,6 +13402,7 @@ pub enum EditorEvent {
TransactionBegun {
transaction_id: clock::Lamport,
},
CursorShapeChanged,
}
impl EventEmitter<EditorEvent> for Editor {}

View File

@@ -13,7 +13,6 @@ pub struct EditorSettings {
pub show_completions_on_input: bool,
pub show_completion_documentation: bool,
pub completion_documentation_secondary_query_debounce: u64,
pub use_on_type_format: bool,
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
pub gutter: Gutter,
@@ -209,11 +208,6 @@ pub struct EditorSettingsContent {
///
/// Default: 300 ms
pub completion_documentation_secondary_query_debounce: Option<u64>,
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
///
/// Default: true
pub use_on_type_format: Option<bool>,
/// Toolbar related settings
pub toolbar: Option<ToolbarContent>,
/// Scrollbar related settings

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ use crate::{
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
@@ -335,8 +335,14 @@ impl EditorElement {
register_action(view, cx, Editor::open_url);
register_action(view, cx, Editor::open_file);
register_action(view, cx, Editor::fold);
register_action(view, cx, Editor::fold_all);
register_action(view, cx, Editor::fold_at);
register_action(view, cx, Editor::fold_recursive);
register_action(view, cx, Editor::toggle_fold);
register_action(view, cx, Editor::toggle_fold_recursive);
register_action(view, cx, Editor::unfold_lines);
register_action(view, cx, Editor::unfold_recursive);
register_action(view, cx, Editor::unfold_all);
register_action(view, cx, Editor::unfold_at);
register_action(view, cx, Editor::fold_selected_ranges);
register_action(view, cx, Editor::show_completions);
@@ -1445,7 +1451,7 @@ impl EditorElement {
AvailableSpace::MaxContent
};
let scroll_top = scroll_position.y * line_height;
let start_x = em_width * 1;
let start_x = em_width;
let mut last_used_color: Option<(PlayerColor, Oid)> = None;
@@ -4228,7 +4234,7 @@ fn render_blame_entry(
let short_commit_id = blame_entry.sha.display_short();
let author_name = blame_entry.author.as_deref().unwrap_or("<no name>");
let name = util::truncate_and_trailoff(author_name, 20);
let name = util::truncate_and_trailoff(author_name, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED);
let details = blame.read(cx).details_for_entry(&blame_entry);
@@ -4240,22 +4246,21 @@ fn render_blame_entry(
h_flex()
.w_full()
.justify_between()
.font_family(style.text.font().family)
.line_height(style.text.line_height)
.id(("blame", ix))
.children([
div()
.text_color(sha_color.cursor)
.child(short_commit_id)
.mr_2(),
div()
.w_full()
.h_flex()
.justify_between()
.text_color(cx.theme().status().hint)
.child(name)
.child(relative_timestamp),
])
.text_color(cx.theme().status().hint)
.pr_2()
.gap_2()
.child(
h_flex()
.items_center()
.gap_2()
.child(div().text_color(sha_color.cursor).child(short_commit_id))
.child(name),
)
.child(relative_timestamp)
.on_mouse_down(MouseButton::Right, {
let blame_entry = blame_entry.clone();
let details = details.clone();
@@ -4970,6 +4975,7 @@ impl Element for EditorElement {
font_id,
font_size,
em_width,
em_advance,
self.max_line_number_width(&snapshot, cx),
cx,
);
@@ -4994,10 +5000,8 @@ impl Element for EditorElement {
snapshot
} else {
let wrap_width = match editor.soft_wrap_mode(cx) {
SoftWrap::None => None,
SoftWrap::PreferLine => {
Some((MAX_LINE_LEN / 2) as f32 * em_advance)
}
SoftWrap::GitDiff => None,
SoftWrap::None => Some((MAX_LINE_LEN / 2) as f32 * em_advance),
SoftWrap::EditorWidth => Some(editor_width),
SoftWrap::Column(column) => Some(column as f32 * em_advance),
SoftWrap::Bounded(column) => {
@@ -6283,10 +6287,21 @@ fn compute_auto_height_layout(
.unwrap()
.size
.width;
let em_advance = cx
.text_system()
.advance(font_id, font_size, 'm')
.unwrap()
.width;
let mut snapshot = editor.snapshot(cx);
let gutter_dimensions =
snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
let gutter_dimensions = snapshot.gutter_dimensions(
font_id,
font_size,
em_width,
em_advance,
max_line_number_width,
cx,
);
editor.gutter_dimensions = gutter_dimensions;
let text_width = width - gutter_dimensions.width;

View File

@@ -207,6 +207,27 @@ impl GitBlame {
})
}
pub fn max_author_length(&mut self, cx: &mut ModelContext<Self>) -> usize {
self.sync(cx);
let mut max_author_length = 0;
for entry in self.entries.iter() {
let author_len = entry
.blame
.as_ref()
.and_then(|entry| entry.author.as_ref())
.map(|author| author.len());
if let Some(author_len) = author_len {
if author_len > max_author_length {
max_author_length = author_len;
}
}
}
max_author_length
}
pub fn blur(&mut self, _: &mut ModelContext<Self>) {
self.focused = false;
}

View File

@@ -32,7 +32,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
opening_range.to_anchors(&snapshot.buffer_snapshot),
closing_range.to_anchors(&snapshot.buffer_snapshot),
],
|theme| theme.editor_document_highlight_read_background,
|theme| theme.editor_document_highlight_bracket_background,
cx,
)
}

View File

@@ -6,10 +6,7 @@ use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
MultiBufferSnapshot, ToPoint,
};
use std::{
ops::{Range, RangeInclusive},
sync::Arc,
};
use std::{ops::Range, sync::Arc};
use ui::{
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,
@@ -19,8 +16,8 @@ use util::RangeExt;
use crate::{
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, BlockDisposition,
BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow, DisplaySnapshot,
Editor, EditorElement, EditorSnapshot, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk,
RangeToAnchorExt, RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
};
#[derive(Debug, Clone)]
@@ -219,14 +216,7 @@ impl Editor {
});
}
for removed_rows in highlights_to_remove {
editor.highlight_rows::<DiffRowHighlight>(
to_inclusive_row_range(removed_rows, &snapshot),
None,
false,
cx,
);
}
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
editor.remove_blocks(blocks_to_remove, None, cx);
for hunk in hunks_to_expand {
editor.expand_diff_hunk(None, &hunk, cx);
@@ -305,8 +295,8 @@ impl Editor {
}
DiffHunkStatus::Added => {
self.highlight_rows::<DiffRowHighlight>(
to_inclusive_row_range(hunk_start..hunk_end, &snapshot),
Some(added_hunk_color(cx)),
hunk_start..hunk_end,
added_hunk_color(cx),
false,
cx,
);
@@ -314,8 +304,8 @@ impl Editor {
}
DiffHunkStatus::Modified => {
self.highlight_rows::<DiffRowHighlight>(
to_inclusive_row_range(hunk_start..hunk_end, &snapshot),
Some(added_hunk_color(cx)),
hunk_start..hunk_end,
added_hunk_color(cx),
false,
cx,
);
@@ -520,49 +510,7 @@ impl Editor {
});
}
}),
),
)
.child(
h_flex()
.gap_2()
.pr_6()
.child({
let focus = editor.focus_handle(cx);
PopoverMenu::new("hunk-controls-dropdown")
.trigger(
IconButton::new(
"toggle_editor_selections_icon",
IconName::EllipsisVertical,
)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(
hunk_controls_menu_handle.is_deployed(),
)
.when(
!hunk_controls_menu_handle.is_deployed(),
|this| {
this.tooltip(|cx| {
Tooltip::text("Hunk Controls", cx)
})
},
),
)
.anchor(AnchorCorner::TopRight)
.with_handle(hunk_controls_menu_handle)
.menu(move |cx| {
let focus = focus.clone();
let menu =
ContextMenu::build(cx, move |menu, _| {
menu.context(focus.clone()).action(
"Discard All",
RevertFile.boxed_clone(),
)
});
Some(menu)
})
})
)
.child(
IconButton::new("discard", IconName::RotateCcw)
.shape(IconButtonShape::Square)
@@ -608,31 +556,70 @@ impl Editor {
}
}),
)
.child(
IconButton::new("collapse", IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Collapse Hunk",
&ToggleHunkDiff,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
.child({
let focus = editor.focus_handle(cx);
PopoverMenu::new("hunk-controls-dropdown")
.trigger(
IconButton::new(
"toggle_editor_selections_icon",
IconName::EllipsisVertical,
)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(
hunk_controls_menu_handle.is_deployed(),
)
.when(
!hunk_controls_menu_handle.is_deployed(),
|this| {
this.tooltip(|cx| {
Tooltip::text("Hunk Controls", cx)
})
},
),
)
.anchor(AnchorCorner::TopRight)
.with_handle(hunk_controls_menu_handle)
.menu(move |cx| {
let focus = focus.clone();
let menu =
ContextMenu::build(cx, move |menu, _| {
menu.context(focus.clone()).action(
"Discard All",
RevertFile.boxed_clone(),
)
});
}
}),
),
Some(menu)
})
}),
)
.child(
h_flex().gap_2().pr_6().child(
IconButton::new("collapse", IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |cx| {
Tooltip::for_action_in(
"Collapse Hunk",
&ToggleHunkDiff,
&focus_handle,
cx,
)
}
})
.on_click({
let editor = editor.clone();
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor.toggle_hovered_hunk(&hunk, cx);
});
}
}),
),
),
)
.into_any_element()
@@ -850,14 +837,7 @@ impl Editor {
retain
});
for removed_rows in highlights_to_remove {
editor.highlight_rows::<DiffRowHighlight>(
to_inclusive_row_range(removed_rows, &snapshot),
None,
false,
cx,
);
}
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
editor.remove_blocks(blocks_to_remove, None, cx);
if let Some(diff_base_buffer) = &diff_base_buffer {
@@ -977,8 +957,8 @@ fn editor_with_deleted_text(
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor.highlight_rows::<DiffRowHighlight>(
Anchor::min()..=Anchor::max(),
Some(deleted_color),
Anchor::min()..Anchor::max(),
deleted_color,
false,
cx,
);
@@ -1056,21 +1036,6 @@ fn buffer_diff_hunk(
None
}
fn to_inclusive_row_range(
row_range: Range<Anchor>,
snapshot: &EditorSnapshot,
) -> RangeInclusive<Anchor> {
let mut display_row_range =
row_range.start.to_display_point(snapshot)..row_range.end.to_display_point(snapshot);
if display_row_range.end.row() > display_row_range.start.row() {
*display_row_range.end.row_mut() -= 1;
}
let point_range = display_row_range.start.to_point(&snapshot.display_snapshot)
..display_row_range.end.to_point(&snapshot.display_snapshot);
let new_range = point_range.to_anchors(&snapshot.buffer_snapshot);
new_range.start..=new_range.end
}
impl DisplayDiffHunk {
pub fn start_display_row(&self) -> DisplayRow {
match self {

View File

@@ -6,10 +6,13 @@ use language::{Buffer, BufferEvent, Capability};
use multi_buffer::{ExcerptRange, MultiBuffer};
use project::Project;
use smol::stream::StreamExt;
use std::{ops::Range, time::Duration};
use std::{any::TypeId, ops::Range, time::Duration};
use text::ToOffset;
use ui::prelude::*;
use workspace::Item;
use workspace::{
searchable::SearchableItemHandle, Item, ItemHandle as _, ToolbarItemEvent, ToolbarItemLocation,
ToolbarItemView,
};
pub struct ProposedChangesEditor {
editor: View<Editor>,
@@ -23,6 +26,10 @@ pub struct ProposedChangesBuffer<T> {
pub ranges: Vec<Range<T>>,
}
pub struct ProposedChangesEditorToolbar {
current_editor: Option<View<ProposedChangesEditor>>,
}
impl ProposedChangesEditor {
pub fn new<T: ToOffset>(
buffers: Vec<ProposedChangesBuffer<T>>,
@@ -96,6 +103,17 @@ impl ProposedChangesEditor {
self.recalculate_diffs_tx.unbounded_send(buffer).ok();
}
}
fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
let buffers = self.editor.read(cx).buffer.read(cx).all_buffers();
for branch_buffer in buffers {
if let Some(base_buffer) = branch_buffer.read(cx).diff_base_buffer() {
base_buffer.update(cx, |base_buffer, cx| {
base_buffer.merge(&branch_buffer, None, cx)
});
}
}
}
}
impl Render for ProposedChangesEditor {
@@ -122,4 +140,66 @@ impl Item for ProposedChangesEditor {
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
Some("Proposed changes".into())
}
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a View<Self>,
_: &'a AppContext,
) -> Option<gpui::AnyView> {
if type_id == TypeId::of::<Self>() {
Some(self_handle.to_any())
} else if type_id == TypeId::of::<Editor>() {
Some(self.editor.to_any())
} else {
None
}
}
}
impl ProposedChangesEditorToolbar {
pub fn new() -> Self {
Self {
current_editor: None,
}
}
fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
if self.current_editor.is_some() {
ToolbarItemLocation::PrimaryRight
} else {
ToolbarItemLocation::Hidden
}
}
}
impl Render for ProposedChangesEditorToolbar {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let editor = self.current_editor.clone();
Button::new("apply-changes", "Apply All").on_click(move |_, cx| {
if let Some(editor) = &editor {
editor.update(cx, |editor, cx| {
editor.apply_all_changes(cx);
});
}
})
}
}
impl EventEmitter<ToolbarItemEvent> for ProposedChangesEditorToolbar {}
impl ToolbarItemView for ProposedChangesEditorToolbar {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn workspace::ItemHandle>,
_cx: &mut ViewContext<Self>,
) -> workspace::ToolbarItemLocation {
self.current_editor =
active_pane_item.and_then(|item| item.downcast::<ProposedChangesEditor>());
self.get_toolbar_item_location()
}
}

View File

@@ -88,116 +88,3 @@ pub(crate) fn build_editor_with_project(
) -> Editor {
Editor::new(EditorMode::Full, buffer, Some(project), true, cx)
}
#[cfg(any(test, feature = "test-support"))]
pub fn editor_hunks(
editor: &Editor,
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<'_, Editor>,
) -> Vec<(
String,
git::diff::DiffHunkStatus,
std::ops::Range<crate::DisplayRow>,
)> {
use multi_buffer::MultiBufferRow;
use text::Point;
use crate::hunk_status;
snapshot
.buffer_snapshot
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
.map(|hunk| {
let display_range = Point::new(hunk.row_range.start.0, 0)
.to_display_point(snapshot)
.row()
..Point::new(hunk.row_range.end.0, 0)
.to_display_point(snapshot)
.row();
let (_, buffer, _) = editor
.buffer()
.read(cx)
.excerpt_containing(Point::new(hunk.row_range.start.0, 0), cx)
.expect("no excerpt for expanded buffer's hunk start");
let diff_base = buffer
.read(cx)
.diff_base()
.expect("should have a diff base for expanded hunk")
.slice(hunk.diff_base_byte_range.clone())
.to_string();
(diff_base, hunk_status(&hunk), display_range)
})
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub fn expanded_hunks(
editor: &Editor,
snapshot: &DisplaySnapshot,
cx: &mut ViewContext<'_, Editor>,
) -> Vec<(
String,
git::diff::DiffHunkStatus,
std::ops::Range<crate::DisplayRow>,
)> {
editor
.expanded_hunks
.hunks(false)
.map(|expanded_hunk| {
let hunk_display_range = expanded_hunk
.hunk_range
.start
.to_display_point(snapshot)
.row()
..expanded_hunk
.hunk_range
.end
.to_display_point(snapshot)
.row();
let (_, buffer, _) = editor
.buffer()
.read(cx)
.excerpt_containing(expanded_hunk.hunk_range.start, cx)
.expect("no excerpt for expanded buffer's hunk start");
let diff_base = buffer
.read(cx)
.diff_base()
.expect("should have a diff base for expanded hunk")
.slice(expanded_hunk.diff_base_byte_range.clone())
.to_string();
(diff_base, expanded_hunk.status, hunk_display_range)
})
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub fn expanded_hunks_background_highlights(
editor: &mut Editor,
cx: &mut gpui::WindowContext,
) -> Vec<std::ops::RangeInclusive<crate::DisplayRow>> {
use crate::DisplayRow;
let mut highlights = Vec::new();
let mut range_start = 0;
let mut previous_highlighted_row = None;
for (highlighted_row, _) in editor.highlighted_display_rows(cx) {
match previous_highlighted_row {
Some(previous_row) => {
if previous_row + 1 != highlighted_row.0 {
highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row));
range_start = highlighted_row.0;
}
}
None => {
range_start = highlighted_row.0;
}
}
previous_highlighted_row = Some(highlighted_row.0);
}
if let Some(previous_row) = previous_highlighted_row {
highlights.push(DisplayRow(range_start)..=DisplayRow(previous_row));
}
highlights
}

View File

@@ -1,17 +1,17 @@
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;
use git::diff::DiffHunkStatus;
use gpui::{
AnyWindowHandle, AppContext, Keystroke, ModelContext, Pixels, Point, View, ViewContext,
VisualTestContext,
VisualTestContext, WindowHandle,
};
use indoc::indoc;
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::{
@@ -71,6 +71,16 @@ impl EditorTestContext {
}
}
pub async fn for_editor(editor: WindowHandle<Editor>, cx: &mut gpui::TestAppContext) -> Self {
let editor_view = editor.root_view(cx).unwrap();
Self {
cx: VisualTestContext::from_window(*editor.deref(), cx),
window: editor.into(),
editor: editor_view,
assertion_cx: AssertionContextManager::new(),
}
}
pub fn new_multibuffer<const COUNT: usize>(
cx: &mut gpui::TestAppContext,
excerpts: [&str; COUNT],
@@ -297,19 +307,85 @@ impl EditorTestContext {
state_context
}
#[track_caller]
pub fn assert_diff_hunks(&mut self, expected_diff: String) {
// Normalize the expected diff. If it has no diff markers, then insert blank markers
// before each line. Strip any whitespace-only lines.
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}")
}
})
.join("\n");
// Read the actual diff from the editor's row highlights and block
// decorations.
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let text = editor.text(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
.expanded_hunks
.hunks
.iter()
.filter_map(|hunk| {
if hunk.blocks.is_empty() {
return None;
}
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 deleted_text = buffer
.read(cx)
.diff_base()
.expect("should have a diff base for expanded hunk")
.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(text, deletions, insertions)
});
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
}
/// Make an assertion about the editor's text and the ranges and directions
/// of its selections using a string containing embedded range markers.
///
/// See the `util::test::marked_text_ranges` function for more information.
#[track_caller]
pub fn assert_editor_state(&mut self, marked_text: &str) {
let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
let buffer_text = self.buffer_text();
if buffer_text != unmarked_text {
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}\nRaw unmarked text\n{unmarked_text}");
}
let (expected_text, expected_selections) = marked_text_ranges(marked_text, true);
pretty_assertions::assert_eq!(self.buffer_text(), expected_text, "unexpected buffer text");
self.assert_selections(expected_selections, marked_text.to_string())
}
@@ -382,25 +458,56 @@ impl EditorTestContext {
let actual_marked_text =
generate_marked_text(&self.buffer_text(), &actual_selections, true);
if expected_selections != actual_selections {
panic!(
indoc! {"
{}Editor has unexpected selections.
Expected selections:
{}
Actual selections:
{}
"},
self.assertion_context(),
expected_marked_text,
pretty_assertions::assert_eq!(
actual_marked_text,
expected_marked_text,
"{}Editor has unexpected selections",
self.assertion_context(),
);
}
}
}
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

@@ -28,7 +28,6 @@ futures.workspace = true
gpui.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
isahc.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true

View File

@@ -664,7 +664,7 @@ impl ExtensionStore {
let content_length = response
.headers()
.get(isahc::http::header::CONTENT_LENGTH)
.get(http_client::http::header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok()?.parse::<usize>().ok());
let mut body = BufReader::new(response.body_mut());

View File

@@ -1,5 +1,5 @@
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::http_client::AsyncBody;
use ::http_client::{AsyncBody, HttpRequestExt};
use ::settings::{Settings, WorktreeId};
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
@@ -8,7 +8,6 @@ use async_trait::async_trait;
use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase;
use isahc::config::{Configurable, RedirectPolicy};
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
@@ -297,10 +296,12 @@ fn convert_request(
let mut request = ::http_client::Request::builder()
.method(::http_client::Method::from(extension_request.method))
.uri(&extension_request.url)
.redirect_policy(match extension_request.redirect_policy {
http_client::RedirectPolicy::NoFollow => RedirectPolicy::None,
http_client::RedirectPolicy::FollowLimit(limit) => RedirectPolicy::Limit(limit),
http_client::RedirectPolicy::FollowAll => RedirectPolicy::Follow,
.follow_redirects(match extension_request.redirect_policy {
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
http_client::RedirectPolicy::FollowLimit(limit) => {
::http_client::RedirectPolicy::FollowLimit(limit)
}
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
});
for (key, value) in &extension_request.headers {
request = request.header(key, value);

View File

@@ -1,5 +1,5 @@
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::http_client::AsyncBody;
use ::http_client::{AsyncBody, HttpRequestExt};
use ::settings::{Settings, WorktreeId};
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
@@ -8,7 +8,6 @@ use async_trait::async_trait;
use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase;
use isahc::config::{Configurable, RedirectPolicy};
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
@@ -213,10 +212,12 @@ fn convert_request(
let mut request = ::http_client::Request::builder()
.method(::http_client::Method::from(extension_request.method))
.uri(&extension_request.url)
.redirect_policy(match extension_request.redirect_policy {
http_client::RedirectPolicy::NoFollow => RedirectPolicy::None,
http_client::RedirectPolicy::FollowLimit(limit) => RedirectPolicy::Limit(limit),
http_client::RedirectPolicy::FollowAll => RedirectPolicy::Follow,
.follow_redirects(match extension_request.redirect_policy {
http_client::RedirectPolicy::NoFollow => ::http_client::RedirectPolicy::NoFollow,
http_client::RedirectPolicy::FollowLimit(limit) => {
::http_client::RedirectPolicy::FollowLimit(limit)
}
http_client::RedirectPolicy::FollowAll => ::http_client::RedirectPolicy::FollowAll,
});
for (key, value) in &extension_request.headers {
request = request.header(key, value);

View File

@@ -23,7 +23,6 @@ editor.workspace = true
futures.workspace = true
gpui.workspace = true
human_bytes = "0.4.1"
isahc.workspace = true
http_client.workspace = true
language.workspace = true
log.workspace = true

View File

@@ -11,7 +11,6 @@ use gpui::{
PromptLevel, Render, Task, View, ViewContext,
};
use http_client::HttpClient;
use isahc::Request;
use language::Buffer;
use project::Project;
use regex::Regex;
@@ -299,7 +298,7 @@ impl FeedbackModal {
is_staff: is_staff.unwrap_or(false),
};
let json_bytes = serde_json::to_vec(&request)?;
let request = Request::post(feedback_endpoint)
let request = http_client::http::Request::post(feedback_endpoint)
.header("content-type", "application/json")
.body(json_bytes.into())?;
let mut response = http_client.send(request).await?;

View File

@@ -394,7 +394,7 @@ fn matching_history_items<'a>(
.chars(),
),
};
candidates_paths.insert(Arc::clone(&found_path.project.path), found_path);
candidates_paths.insert(&found_path.project, found_path);
Some((found_path.project.worktree_id, candidate))
})
.fold(
@@ -419,17 +419,21 @@ fn matching_history_items<'a>(
max_results,
)
.into_iter()
.map(|path_match| {
let (_, found_path) = candidates_paths
.remove_entry(&path_match.path)
.expect("candidate info not found");
(
Arc::clone(&path_match.path),
Match::History {
path: found_path.clone(),
panel_match: Some(ProjectPanelOrdMatch(path_match)),
},
)
.filter_map(|path_match| {
candidates_paths
.remove_entry(&ProjectPath {
worktree_id: WorktreeId::from_usize(path_match.worktree_id),
path: Arc::clone(&path_match.path),
})
.map(|(_, found_path)| {
(
Arc::clone(&path_match.path),
Match::History {
path: found_path.clone(),
panel_match: Some(ProjectPanelOrdMatch(path_match)),
},
)
})
}),
);
}

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
use futures::AsyncReadExt;
use http_client::{AsyncBody, HttpClient, Request};
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
use serde::Deserialize;
use url::Url;
@@ -49,14 +49,16 @@ impl Codeberg {
let url =
format!("https://codeberg.org/api/v1/repos/{repo_owner}/{repo}/git/commits/{commit}");
let mut request = Request::get(&url).header("Content-Type", "application/json");
let mut request = Request::get(&url)
.header("Content-Type", "application/json")
.follow_redirects(http_client::RedirectPolicy::FollowAll);
if let Ok(codeberg_token) = std::env::var("CODEBERG_TOKEN") {
request = request.header("Authorization", format!("Bearer {}", codeberg_token));
}
let mut response = client
.send_with_redirect_policy(request.body(AsyncBody::default())?, true)
.send(request.body(AsyncBody::default())?)
.await
.with_context(|| format!("error fetching Codeberg commit details at {:?}", url))?;

View File

@@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock};
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
use futures::AsyncReadExt;
use http_client::{AsyncBody, HttpClient, Request};
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
use regex::Regex;
use serde::Deserialize;
use url::Url;
@@ -53,14 +53,16 @@ impl Github {
) -> Result<Option<User>> {
let url = format!("https://api.github.com/repos/{repo_owner}/{repo}/commits/{commit}");
let mut request = Request::get(&url).header("Content-Type", "application/json");
let mut request = Request::get(&url)
.header("Content-Type", "application/json")
.follow_redirects(http_client::RedirectPolicy::FollowAll);
if let Ok(github_token) = std::env::var("GITHUB_TOKEN") {
request = request.header("Authorization", format!("Bearer {}", github_token));
}
let mut response = client
.send_with_redirect_policy(request.body(AsyncBody::default())?, true)
.send(request.body(AsyncBody::default())?)
.await
.with_context(|| format!("error fetching GitHub commit details at {:?}", url))?;

View File

@@ -116,12 +116,14 @@ impl GoToLine {
if let Some(point) = self.point_from_query(cx) {
self.active_editor.update(cx, |active_editor, cx| {
let snapshot = active_editor.snapshot(cx).display_snapshot;
let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
let anchor = snapshot.buffer_snapshot.anchor_before(point);
let start = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
let end = start + Point::new(1, 0);
let start = snapshot.buffer_snapshot.anchor_before(start);
let end = snapshot.buffer_snapshot.anchor_after(end);
active_editor.clear_row_highlights::<GoToLineRowHighlights>();
active_editor.highlight_rows::<GoToLineRowHighlights>(
anchor..=anchor,
Some(cx.theme().colors().editor_highlighted_line_background),
start..end,
cx.theme().colors().editor_highlighted_line_background,
true,
cx,
);
@@ -244,13 +246,13 @@ mod tests {
field_1: i32, // display line 3
field_2: i32, // display line 4
} // display line 5
// display line 7
struct Another { // display line 8
field_1: i32, // display line 9
field_2: i32, // display line 10
field_3: i32, // display line 11
field_4: i32, // display line 12
} // display line 13
// display line 6
struct Another { // display line 7
field_1: i32, // display line 8
field_2: i32, // display line 9
field_3: i32, // display line 10
field_4: i32, // display line 11
} // display line 12
"}
}),
)

View File

@@ -18,7 +18,6 @@ schemars = ["dep:schemars"]
anyhow.workspace = true
futures.workspace = true
http_client.workspace = true
isahc.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true

View File

@@ -2,8 +2,7 @@ mod supported_countries;
use anyhow::{anyhow, Result};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use isahc::config::Configurable;
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use serde::{Deserialize, Serialize};
use std::time::Duration;
@@ -30,7 +29,7 @@ pub async fn stream_generate_content(
.header("Content-Type", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
};
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
@@ -85,7 +84,7 @@ pub async fn count_tokens(
.header("Content-Type", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let http_request = request_builder.body(AsyncBody::from(request))?;

View File

@@ -1524,10 +1524,9 @@ pub struct KeystrokeEvent {
struct NullHttpClient;
impl HttpClient for NullHttpClient {
fn send_with_redirect_policy(
fn send(
&self,
_req: http_client::Request<http_client::AsyncBody>,
_follow_redirects: bool,
) -> futures::future::BoxFuture<
'static,
Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>,

View File

@@ -16,7 +16,7 @@ path = "src/http_client.rs"
doctest = true
[dependencies]
http = "1.1.0"
http = "0.2"
anyhow.workspace = true
derive_more.workspace = true
futures.workspace = true

View File

@@ -10,22 +10,46 @@ use futures::future::BoxFuture;
use http::request::Builder;
#[cfg(feature = "test-support")]
use std::fmt;
use std::sync::{Arc, Mutex};
use std::{
sync::{Arc, Mutex},
time::Duration,
};
pub use url::Url;
pub struct ReadTimeout(pub Duration);
#[derive(Default, Debug, Clone)]
pub enum RedirectPolicy {
#[default]
NoFollow,
FollowLimit(u32),
FollowAll,
}
pub struct FollowRedirects(pub bool);
pub trait HttpRequestExt {
/// Set a read timeout on the request.
/// For isahc, this is the low_speed_timeout.
/// For other clients, this is the timeout used for read calls when reading the response.
/// In all cases this prevents servers stalling completely, but allows them to send data slowly.
fn read_timeout(self, timeout: Duration) -> Self;
/// Whether or not to follow redirects
fn follow_redirects(self, follow: RedirectPolicy) -> Self;
}
impl HttpRequestExt for http::request::Builder {
fn read_timeout(self, timeout: Duration) -> Self {
self.extension(ReadTimeout(timeout))
}
fn follow_redirects(self, follow: RedirectPolicy) -> Self {
self.extension(follow)
}
}
pub trait HttpClient: 'static + Send + Sync {
fn send(
&self,
req: http::Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.send_with_redirect_policy(req, false)
}
// TODO: Make a better API for this
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>>;
fn get<'a>(
@@ -34,14 +58,17 @@ pub trait HttpClient: 'static + Send + Sync {
body: AsyncBody,
follow_redirects: bool,
) -> BoxFuture<'a, Result<Response<AsyncBody>, anyhow::Error>> {
let request = Builder::new().uri(uri).body(body);
let request = Builder::new()
.uri(uri)
.follow_redirects(if follow_redirects {
RedirectPolicy::FollowAll
} else {
RedirectPolicy::NoFollow
})
.body(body);
match request {
Ok(request) => Box::pin(async move {
self.send_with_redirect_policy(request, follow_redirects)
.await
.map_err(Into::into)
}),
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }),
Err(e) => Box::pin(async move { Err(e.into()) }),
}
}
@@ -92,12 +119,11 @@ impl HttpClientWithProxy {
}
impl HttpClient for HttpClientWithProxy {
fn send_with_redirect_policy(
fn send(
&self,
req: Request<AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
@@ -106,12 +132,11 @@ impl HttpClient for HttpClientWithProxy {
}
impl HttpClient for Arc<HttpClientWithProxy> {
fn send_with_redirect_policy(
fn send(
&self,
req: Request<AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
@@ -218,12 +243,11 @@ impl HttpClientWithUrl {
}
impl HttpClient for Arc<HttpClientWithUrl> {
fn send_with_redirect_policy(
fn send(
&self,
req: Request<AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
@@ -232,12 +256,11 @@ impl HttpClient for Arc<HttpClientWithUrl> {
}
impl HttpClient for HttpClientWithUrl {
fn send_with_redirect_policy(
fn send(
&self,
req: Request<AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
self.client.send(req)
}
fn proxy(&self) -> Option<&Uri> {
@@ -283,14 +306,6 @@ impl HttpClient for BlockedHttpClient {
fn proxy(&self) -> Option<&Uri> {
None
}
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
_: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.send(req)
}
}
#[cfg(feature = "test-support")]
@@ -352,10 +367,9 @@ impl fmt::Debug for FakeHttpClient {
#[cfg(feature = "test-support")]
impl HttpClient for FakeHttpClient {
fn send_with_redirect_policy(
fn send(
&self,
req: Request<AsyncBody>,
_follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
let future = (self.handler)(req);
future

View File

@@ -1,45 +0,0 @@
use anyhow::Error;
use futures::future::BoxFuture;
use http::{Response, Uri};
use hyper_util::client::legacy::{connect::HttpConnector, Client};
struct HyperBasedHttpClient {
client: Client<HttpConnector, Vec<u8>>,
}
struct Executor {
executor: gpui::BackgroundExecutor,
}
impl impl HyperBasedHttpClient {
pub fn new() -> Self {
Self {
client: Client::builder().build(HttpConnector::new()),
}
}
}
impl HttpClient for HyperBasedHttpClient {
fn proxy(&self) -> Option<&Uri> {
None
}
fn send(
&self,
request: HttpRequest,
method: &str,
) -> BoxFuture<'static, Result<Response, Error>> {
let request = request.into_hyper_request(method);
Box::pin(async move {
let response = self.client.request(request).await?;
Ok(response.into())
})
}
fn send_response(&self, response: HttpResponse) -> BoxFuture<'static, Result<(), Error>> {
let response = response.into_hyper_response();
Box::pin(async move {
let _ = self.client.request(response).await?;
Ok(())
})
}
}

View File

@@ -1,38 +0,0 @@
[package]
name = "hyper_client"
version = "0.1.0"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[features]
test-support = []
[lib]
path = "src/hyper_client.rs"
doctest = true
[[example]]
name = "client"
path = "examples/client.rs"
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
futures.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
url.workspace = true
gpui.workspace = true
http_client.workspace = true
ureq = "2.10.1"
hyper.workspace = true
hyper-util = {workspace = true, features = ["client", "http1", "http2", "client-legacy"]}
hyper-rustls.workspace = true

View File

@@ -1,20 +0,0 @@
use futures::AsyncReadExt;
use http_client::{AsyncBody, HttpClient};
use hyper_client::UreqHttpClient;
fn main() {
gpui::App::headless().run(|cx| {
dbg!(std::thread::current().id());
cx.spawn(|cx| async move {
let resp = UreqHttpClient::new(cx.background_executor().clone())
.get("http://zed.dev", AsyncBody::empty(), false)
.await
.unwrap();
let mut body = String::new();
resp.into_body().read_to_string(&mut body).await.unwrap();
dbg!(&body.len());
})
.detach();
})
}

View File

@@ -1,144 +0,0 @@
use std::io::Read;
use std::{pin::Pin, task::Poll};
use anyhow::Error;
use futures::channel::mpsc;
use futures::future::BoxFuture;
use futures::{AsyncRead, StreamExt};
use gpui::AppContext;
use http_client::{http, AsyncBody, HttpClient, Inner};
use hyper::body::{Body, Bytes, Frame, Incoming, SizeHint};
use hyper::http::{Response, Uri};
use hyper_util::client::legacy::{connect::HttpConnector, Client};
use smol::future::FutureExt;
use std::future::Future;
pub struct UreqHttpClient {
client: ureq::Agent,
background_executor: gpui::BackgroundExecutor,
}
impl UreqHttpClient {
pub fn new(background_executor: gpui::BackgroundExecutor) -> Self {
Self {
client: ureq::agent(),
background_executor,
}
}
}
struct UreqResponseReader {
task: gpui::Task<()>,
receiver: mpsc::Receiver<std::io::Result<Vec<u8>>>,
buffer: Vec<u8>,
}
impl UreqResponseReader {
fn new(background_executor: gpui::BackgroundExecutor, response: ureq::Response) -> Self {
let (mut sender, receiver) = mpsc::channel(1);
let mut reader = response.into_reader();
let task = background_executor.spawn(async move {
let mut buffer = vec![0; 8192];
loop {
let n = match reader.read(&mut buffer) {
Ok(0) => break,
Ok(n) => n,
Err(e) => {
let _ = sender.try_send(Err(e));
break;
}
};
let _ = sender.try_send(Ok(buffer[..n].to_vec()));
}
});
UreqResponseReader {
task,
receiver,
buffer: Vec::new(),
}
}
}
impl AsyncRead for UreqResponseReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
let now = std::time::Instant::now();
if self.buffer.is_empty() {
match self.receiver.poll_next_unpin(cx) {
Poll::Ready(Some(Ok(data))) => self.buffer.extend(data),
Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
Poll::Ready(None) => return Poll::Ready(Ok(0)), // EOF
Poll::Pending => {
dbg!(now.elapsed());
return Poll::Pending;
}
}
}
let n = std::cmp::min(buf.len(), self.buffer.len());
dbg!(buf.len(), self.buffer.len(), now.elapsed());
dbg!(std::thread::current().id());
buf[..n].copy_from_slice(&self.buffer[..n]);
self.buffer.drain(..n);
Poll::Ready(Ok(n))
}
}
impl HttpClient for UreqHttpClient {
fn proxy(&self) -> Option<&Uri> {
None
}
fn send_with_redirect_policy(
&self,
request: http::Request<AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<http::Response<AsyncBody>, Error>> {
let method = request.method().clone();
let url = request.uri().to_string();
let headers = request.headers().clone();
let mut req = self.client.request(method.as_str(), &url);
for (name, value) in headers.iter() {
req = req.set(name.as_str(), value.to_str().unwrap());
}
let executor = self.background_executor.clone();
let req = executor.spawn(async move {
let resp = req.send(request.into_body());
dbg!(std::thread::current().id());
resp
});
// Set follow_redirects policy
// req = req.redirects(if follow_redirects { 10 } else { 0 });
async move {
// Set headers
// Send the request
let response = req.await?;
dbg!(std::thread::current().id());
// Convert ureq response to http::Response
let mut builder = http::Response::builder()
.status(response.status())
.version(http::Version::HTTP_11);
// Set response headers
for name in response.headers_names() {
if let Some(value) = response.header(&name) {
builder = builder.header(name, value);
}
}
let body = AsyncBody::from_reader(UreqResponseReader::new(executor, response));
let http_response = builder.body(body)?;
Ok(http_response)
}
.boxed()
}
}

View File

@@ -1,7 +1,6 @@
use std::{mem, sync::Arc, time::Duration};
use futures::future::BoxFuture;
use isahc::config::RedirectPolicy;
use util::maybe;
pub use isahc::config::Configurable;
@@ -36,18 +35,29 @@ impl HttpClient for IsahcHttpClient {
None
}
fn send_with_redirect_policy(
fn send(
&self,
req: http_client::http::Request<http_client::AsyncBody>,
follow_redirects: bool,
) -> BoxFuture<'static, Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>>
{
let redirect_policy = req
.extensions()
.get::<http_client::RedirectPolicy>()
.cloned()
.unwrap_or_default();
let read_timeout = req
.extensions()
.get::<http_client::ReadTimeout>()
.map(|t| t.0);
let req = maybe!({
let (mut parts, body) = req.into_parts();
let mut builder = isahc::Request::builder()
.method(parts.method)
.uri(parts.uri)
.version(parts.version);
if let Some(read_timeout) = read_timeout {
builder = builder.low_speed_timeout(100, read_timeout);
}
let headers = builder.headers_mut()?;
mem::swap(headers, &mut parts.headers);
@@ -64,10 +74,12 @@ impl HttpClient for IsahcHttpClient {
};
builder
.redirect_policy(if follow_redirects {
RedirectPolicy::Follow
} else {
RedirectPolicy::None
.redirect_policy(match redirect_policy {
http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow,
http_client::RedirectPolicy::FollowLimit(limit) => {
isahc::config::RedirectPolicy::Limit(limit)
}
http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None,
})
.body(isahc_body)
.ok()

View File

@@ -87,7 +87,11 @@ pub type BufferRow = u32;
#[derive(Clone)]
enum BufferDiffBase {
Git(Rope),
PastBufferVersion(Model<Buffer>, BufferSnapshot),
PastBufferVersion {
buffer: Model<Buffer>,
rope: Rope,
operations_to_ignore: Vec<clock::Lamport>,
},
}
/// An in-memory representation of a source code file, including its text,
@@ -795,19 +799,15 @@ impl Buffer {
let this = cx.handle();
cx.new_model(|cx| {
let mut branch = Self {
diff_base: Some(BufferDiffBase::PastBufferVersion(
this.clone(),
self.snapshot(),
)),
diff_base: Some(BufferDiffBase::PastBufferVersion {
buffer: this.clone(),
rope: self.as_rope().clone(),
operations_to_ignore: Vec::new(),
}),
language: self.language.clone(),
has_conflict: self.has_conflict,
has_unsaved_edits: Cell::new(self.has_unsaved_edits.get_mut().clone()),
_subscriptions: vec![cx.subscribe(&this, |branch: &mut Self, _, event, cx| {
if let BufferEvent::Operation { operation, .. } = event {
branch.apply_ops([operation.clone()], cx);
branch.diff_base_version += 1;
}
})],
_subscriptions: vec![cx.subscribe(&this, Self::on_base_buffer_event)],
..Self::build(
self.text.branch(),
None,
@@ -823,18 +823,74 @@ impl Buffer {
})
}
pub fn merge(&mut self, branch: &Model<Self>, cx: &mut ModelContext<Self>) {
let branch = branch.read(cx);
let edits = branch
.edits_since::<usize>(&self.version)
.map(|edit| {
(
edit.old,
branch.text_for_range(edit.new).collect::<String>(),
/// Applies all of the changes in `branch` buffer that intersect the given `range`
/// to this buffer.
pub fn merge(
&mut self,
branch: &Model<Self>,
range: Option<Range<Anchor>>,
cx: &mut ModelContext<Self>,
) {
let edits = branch.read_with(cx, |branch, _| {
branch
.edits_since_in_range::<usize>(
&self.version,
range.unwrap_or(Anchor::MIN..Anchor::MAX),
)
})
.collect::<Vec<_>>();
self.edit(edits, None, cx);
.map(|edit| {
(
edit.old,
branch.text_for_range(edit.new).collect::<String>(),
)
})
.collect::<Vec<_>>()
});
let operation = self.edit(edits, None, cx);
// Prevent this operation from being reapplied to the branch.
branch.update(cx, |branch, cx| {
if let Some(BufferDiffBase::PastBufferVersion {
operations_to_ignore,
..
}) = &mut branch.diff_base
{
operations_to_ignore.extend(operation);
}
cx.emit(BufferEvent::Edited)
});
}
fn on_base_buffer_event(
&mut self,
_: Model<Buffer>,
event: &BufferEvent,
cx: &mut ModelContext<Self>,
) {
if let BufferEvent::Operation { operation, .. } = event {
if let Some(BufferDiffBase::PastBufferVersion {
operations_to_ignore,
..
}) = &mut self.diff_base
{
let mut is_ignored = false;
if let Operation::Buffer(text::Operation::Edit(buffer_operation)) = &operation {
operations_to_ignore.retain(|operation_to_ignore| {
match buffer_operation.timestamp.cmp(&operation_to_ignore) {
Ordering::Less => true,
Ordering::Equal => {
is_ignored = true;
false
}
Ordering::Greater => false,
}
});
}
if !is_ignored {
self.apply_ops([operation.clone()], cx);
self.diff_base_version += 1;
}
}
}
}
#[cfg(test)]
@@ -1017,9 +1073,8 @@ impl Buffer {
/// Returns the current diff base, see [Buffer::set_diff_base].
pub fn diff_base(&self) -> Option<&Rope> {
match self.diff_base.as_ref()? {
BufferDiffBase::Git(rope) => Some(rope),
BufferDiffBase::PastBufferVersion(_, buffer_snapshot) => {
Some(buffer_snapshot.as_rope())
BufferDiffBase::Git(rope) | BufferDiffBase::PastBufferVersion { rope, .. } => {
Some(rope)
}
}
}
@@ -1050,29 +1105,36 @@ impl Buffer {
self.diff_base_version
}
pub fn diff_base_buffer(&self) -> Option<Model<Self>> {
match self.diff_base.as_ref()? {
BufferDiffBase::Git(_) => None,
BufferDiffBase::PastBufferVersion { buffer, .. } => Some(buffer.clone()),
}
}
/// Recomputes the diff.
pub fn recalculate_diff(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
let diff_base_rope = match self.diff_base.as_mut()? {
let diff_base_rope = match self.diff_base.as_ref()? {
BufferDiffBase::Git(rope) => rope.clone(),
BufferDiffBase::PastBufferVersion(base_buffer, base_buffer_snapshot) => {
let new_base_snapshot = base_buffer.read(cx).snapshot();
*base_buffer_snapshot = new_base_snapshot;
base_buffer_snapshot.as_rope().clone()
}
BufferDiffBase::PastBufferVersion { buffer, .. } => buffer.read(cx).as_rope().clone(),
};
let snapshot = self.snapshot();
let snapshot = self.snapshot();
let mut diff = self.git_diff.clone();
let diff = cx.background_executor().spawn(async move {
diff.update(&diff_base_rope, &snapshot).await;
diff
(diff, diff_base_rope)
});
Some(cx.spawn(|this, mut cx| async move {
let buffer_diff = diff.await;
let (buffer_diff, diff_base_rope) = diff.await;
this.update(&mut cx, |this, cx| {
this.git_diff = buffer_diff;
this.non_text_state_update_count += 1;
if let Some(BufferDiffBase::PastBufferVersion { rope, .. }) = &mut this.diff_base {
*rope = diff_base_rope;
cx.emit(BufferEvent::DiffBaseChanged);
}
cx.emit(BufferEvent::DiffUpdated);
})
.ok();

View File

@@ -2413,80 +2413,98 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
});
// Edits to the branch are not applied to the base.
branch_buffer.update(cx, |buffer, cx| {
buffer.edit(
[(Point::new(1, 0)..Point::new(1, 0), "ONE_POINT_FIVE\n")],
branch_buffer.update(cx, |branch_buffer, cx| {
branch_buffer.edit(
[
(Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
(Point::new(2, 0)..Point::new(2, 5), "THREE"),
],
None,
cx,
)
});
branch_buffer.read_with(cx, |branch_buffer, cx| {
assert_eq!(base_buffer.read(cx).text(), "one\ntwo\nthree\n");
assert_eq!(branch_buffer.text(), "one\nONE_POINT_FIVE\ntwo\nthree\n");
assert_eq!(branch_buffer.text(), "one\n1.5\ntwo\nTHREE\n");
});
// The branch buffer maintains a diff with respect to its base buffer.
start_recalculating_diff(&branch_buffer, cx);
cx.run_until_parked();
assert_diff_hunks(
&branch_buffer,
cx,
&[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
);
// Edits to the base are applied to the branch.
base_buffer.update(cx, |buffer, cx| {
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
});
branch_buffer.read_with(cx, |branch_buffer, cx| {
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\nthree\n");
assert_eq!(
branch_buffer.text(),
"ZERO\none\nONE_POINT_FIVE\ntwo\nthree\n"
);
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
});
assert_diff_hunks(&branch_buffer, cx, &[(2..3, "", "ONE_POINT_FIVE\n")]);
// Until the git diff recalculation is complete, the git diff references
// the previous content of the base buffer, so that it stays in sync.
start_recalculating_diff(&branch_buffer, cx);
assert_diff_hunks(
&branch_buffer,
cx,
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
);
cx.run_until_parked();
assert_diff_hunks(
&branch_buffer,
cx,
&[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
);
// Edits to any replica of the base are applied to the branch.
base_buffer_replica.update(cx, |buffer, cx| {
buffer.edit(
[(Point::new(2, 0)..Point::new(2, 0), "TWO_POINT_FIVE\n")],
None,
cx,
)
buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
});
branch_buffer.read_with(cx, |branch_buffer, cx| {
assert_eq!(
base_buffer.read(cx).text(),
"ZERO\none\ntwo\nTWO_POINT_FIVE\nthree\n"
);
assert_eq!(
branch_buffer.text(),
"ZERO\none\nONE_POINT_FIVE\ntwo\nTWO_POINT_FIVE\nthree\n"
);
assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
});
// Merging the branch applies all of its changes to the base.
base_buffer.update(cx, |base_buffer, cx| {
base_buffer.merge(&branch_buffer, cx);
base_buffer.merge(&branch_buffer, None, cx);
});
branch_buffer.update(cx, |branch_buffer, cx| {
assert_eq!(
base_buffer.text(),
"ZERO\none\nONE_POINT_FIVE\ntwo\nTWO_POINT_FIVE\nthree\n"
base_buffer.read(cx).text(),
"ZERO\none\n1.5\ntwo\n2.5\nTHREE\n"
);
assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
});
}
fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
buffer
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
.detach();
}
#[track_caller]
fn assert_diff_hunks(
buffer: &Model<Buffer>,
cx: &mut TestAppContext,
expected_hunks: &[(Range<u32>, &str, &str)],
) {
buffer
.update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
.detach();
cx.executor().run_until_parked();
buffer.read_with(cx, |buffer, _| {
let snapshot = buffer.snapshot();
assert_hunks(
snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
&snapshot,
&buffer.diff_base().unwrap().to_string(),
expected_hunks,
);
let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
(buffer.snapshot(), buffer.diff_base().unwrap().to_string())
});
assert_hunks(
snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
&snapshot,
&diff_base,
expected_hunks,
);
}
#[gpui::test(iterations = 100)]

View File

@@ -113,6 +113,9 @@ pub struct LanguageSettings {
pub use_autoclose: bool,
/// Whether to automatically surround text with brackets.
pub use_auto_surround: bool,
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
pub use_on_type_format: bool,
// Controls how the editor handles the autoclosed characters.
pub always_treat_brackets_as_autoclosed: bool,
/// Which code actions to run on save
@@ -333,6 +336,11 @@ pub struct LanguageSettingsContent {
///
/// Default: false
pub always_treat_brackets_as_autoclosed: Option<bool>,
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
///
/// Default: true
pub use_on_type_format: Option<bool>,
/// Which code actions to run on save after the formatter.
/// These are not run if formatting is off.
///
@@ -371,15 +379,16 @@ pub struct FeaturesContent {
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SoftWrap {
/// Do not soft wrap.
/// Prefer a single line generally, unless an overly long line is encountered.
None,
/// Deprecated: use None instead. Left to avoid breakin existing users' configs.
/// Prefer a single line generally, unless an overly long line is encountered.
PreferLine,
/// Soft wrap lines that exceed the editor width
/// Soft wrap lines that exceed the editor width.
EditorWidth,
/// Soft wrap lines at the preferred line length
/// Soft wrap lines at the preferred line length.
PreferredLineLength,
/// Soft wrap line at the preferred line length or the editor width (whichever is smaller)
/// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
Bounded,
}
@@ -1045,6 +1054,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
merge(&mut settings.soft_wrap, src.soft_wrap);
merge(&mut settings.use_autoclose, src.use_autoclose);
merge(&mut settings.use_auto_surround, src.use_auto_surround);
merge(&mut settings.use_on_type_format, src.use_on_type_format);
merge(
&mut settings.always_treat_brackets_as_autoclosed,
src.always_treat_brackets_as_autoclosed,

View File

@@ -32,7 +32,6 @@ futures.workspace = true
google_ai = { workspace = true, features = ["schemars"] }
gpui.workspace = true
http_client.workspace = true
isahc.workspace = true
inline_completion_button.workspace = true
log.workspace = true
menu.workspace = true

View File

@@ -12,7 +12,6 @@ pub enum CloudModel {
Anthropic(anthropic::Model),
OpenAi(open_ai::Model),
Google(google_ai::Model),
Zed(ZedModel),
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, EnumIter)]
@@ -21,26 +20,6 @@ pub enum ZedModel {
Qwen2_7bInstruct,
}
impl ZedModel {
pub fn id(&self) -> &str {
match self {
ZedModel::Qwen2_7bInstruct => "Qwen/Qwen2-7B-Instruct",
}
}
pub fn display_name(&self) -> &str {
match self {
ZedModel::Qwen2_7bInstruct => "Qwen2 7B Instruct",
}
}
pub fn max_token_count(&self) -> usize {
match self {
ZedModel::Qwen2_7bInstruct => 28000,
}
}
}
impl Default for CloudModel {
fn default() -> Self {
Self::Anthropic(anthropic::Model::default())
@@ -53,7 +32,6 @@ impl CloudModel {
Self::Anthropic(model) => model.id(),
Self::OpenAi(model) => model.id(),
Self::Google(model) => model.id(),
Self::Zed(model) => model.id(),
}
}
@@ -62,7 +40,6 @@ impl CloudModel {
Self::Anthropic(model) => model.display_name(),
Self::OpenAi(model) => model.display_name(),
Self::Google(model) => model.display_name(),
Self::Zed(model) => model.display_name(),
}
}
@@ -78,7 +55,6 @@ impl CloudModel {
Self::Anthropic(model) => model.max_token_count(),
Self::OpenAi(model) => model.max_token_count(),
Self::Google(model) => model.max_token_count(),
Self::Zed(model) => model.max_token_count(),
}
}
@@ -115,9 +91,6 @@ impl CloudModel {
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
}
},
Self::Zed(model) => match model {
ZedModel::Qwen2_7bInstruct => LanguageModelAvailability::RequiresPlan(Plan::ZedPro),
},
}
}
}

View File

@@ -3,7 +3,7 @@ use crate::provider::anthropic::map_to_language_model_completion_events;
use crate::{
settings::AllLanguageModelSettings, CloudModel, LanguageModel, LanguageModelCacheConfiguration,
LanguageModelId, LanguageModelName, LanguageModelProviderId, LanguageModelProviderName,
LanguageModelProviderState, LanguageModelRequest, RateLimiter, ZedModel,
LanguageModelProviderState, LanguageModelRequest, RateLimiter,
};
use anthropic::AnthropicError;
use anyhow::{anyhow, Result};
@@ -18,8 +18,7 @@ use gpui::{
AnyElement, AnyView, AppContext, AsyncAppContext, FontWeight, Model, ModelContext,
Subscription, Task,
};
use http_client::{AsyncBody, HttpClient, Method, Response};
use isahc::config::Configurable;
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Response};
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::value::RawValue;
@@ -220,9 +219,6 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
models.insert(model.id().to_string(), CloudModel::Google(model));
}
}
for model in ZedModel::iter() {
models.insert(model.id().to_string(), CloudModel::Zed(model));
}
} else {
models.insert(
anthropic::Model::Claude3_5Sonnet.id().to_string(),
@@ -396,7 +392,7 @@ impl CloudLanguageModel {
let response = loop {
let mut request_builder = http_client::Request::builder();
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
};
let request = request_builder
.method(Method::POST)
@@ -473,7 +469,7 @@ impl LanguageModel for CloudLanguageModel {
min_total_token: cache.min_total_token,
})
}
CloudModel::OpenAi(_) | CloudModel::Google(_) | CloudModel::Zed(_) => None,
CloudModel::OpenAi(_) | CloudModel::Google(_) => None,
}
}
@@ -503,9 +499,6 @@ impl LanguageModel for CloudLanguageModel {
}
.boxed()
}
CloudModel::Zed(_) => {
count_open_ai_tokens(request, open_ai::Model::ThreePointFiveTurbo, cx)
}
}
}
@@ -604,35 +597,6 @@ impl LanguageModel for CloudLanguageModel {
}
.boxed()
}
CloudModel::Zed(model) => {
let client = self.client.clone();
let mut request = request.into_open_ai(model.id().into(), None);
request.max_tokens = Some(4000);
let llm_api_token = self.llm_api_token.clone();
let future = self.request_limiter.stream(async move {
let response = Self::perform_llm_completion(
client.clone(),
llm_api_token,
PerformCompletionParams {
provider: client::LanguageModelProvider::Zed,
model: request.model.clone(),
provider_request: RawValue::from_string(serde_json::to_string(
&request,
)?)?,
},
None,
)
.await?;
Ok(open_ai::extract_text_from_events(response_lines(response)))
});
async move {
Ok(future
.await?
.map(|result| result.map(LanguageModelCompletionEvent::Text))
.boxed())
}
.boxed()
}
}
}
@@ -736,51 +700,6 @@ impl LanguageModel for CloudLanguageModel {
CloudModel::Google(_) => {
future::ready(Err(anyhow!("tool use not implemented for Google AI"))).boxed()
}
CloudModel::Zed(model) => {
// All Zed models are OpenAI-based at the time of writing.
let mut request = request.into_open_ai(model.id().into(), None);
request.tool_choice = Some(open_ai::ToolChoice::Other(
open_ai::ToolDefinition::Function {
function: open_ai::FunctionDefinition {
name: tool_name.clone(),
description: None,
parameters: None,
},
},
));
request.tools = vec![open_ai::ToolDefinition::Function {
function: open_ai::FunctionDefinition {
name: tool_name.clone(),
description: Some(tool_description),
parameters: Some(input_schema),
},
}];
self.request_limiter
.run(async move {
let response = Self::perform_llm_completion(
client.clone(),
llm_api_token,
PerformCompletionParams {
provider: client::LanguageModelProvider::Zed,
model: request.model.clone(),
provider_request: RawValue::from_string(serde_json::to_string(
&request,
)?)?,
},
None,
)
.await?;
Ok(open_ai::extract_tool_args_from_events(
tool_name,
Box::pin(response_lines(response)),
)
.await?
.boxed())
})
.boxed()
}
}
}
}

View File

@@ -10,6 +10,25 @@ workspace = true
[features]
test-support = []
load-grammars = [
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-go",
"tree-sitter-go-mod",
"tree-sitter-gowork",
"tree-sitter-jsdoc",
"tree-sitter-json",
"tree-sitter-md",
"protols-tree-sitter-proto",
"tree-sitter-python",
"tree-sitter-regex",
"tree-sitter-rust",
"tree-sitter-typescript",
"tree-sitter-yaml",
"tree-sitter"
]
[dependencies]
anyhow.workspace = true
@@ -36,25 +55,26 @@ settings.workspace = true
smol.workspace = true
task.workspace = true
toml.workspace = true
tree-sitter-bash.workspace = true
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-css.workspace = true
tree-sitter-go.workspace = true
tree-sitter-go-mod.workspace = true
tree-sitter-gowork.workspace = true
tree-sitter-jsdoc.workspace = true
tree-sitter-json.workspace = true
tree-sitter-md.workspace = true
protols-tree-sitter-proto.workspace = true
tree-sitter-python.workspace = true
tree-sitter-regex.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-yaml.workspace = true
tree-sitter.workspace = true
util.workspace = true
tree-sitter-bash = {workspace = true, optional = true}
tree-sitter-c = {workspace = true, optional = true}
tree-sitter-cpp = {workspace = true, optional = true}
tree-sitter-css = {workspace = true, optional = true}
tree-sitter-go = {workspace = true, optional = true}
tree-sitter-go-mod = {workspace = true, optional = true}
tree-sitter-gowork = {workspace = true, optional = true}
tree-sitter-jsdoc = {workspace = true, optional = true}
tree-sitter-json = {workspace = true, optional = true}
tree-sitter-md = {workspace = true, optional = true}
protols-tree-sitter-proto = {workspace = true, optional = true}
tree-sitter-python = {workspace = true, optional = true}
tree-sitter-regex = {workspace = true, optional = true}
tree-sitter-rust = {workspace = true, optional = true}
tree-sitter-typescript = {workspace = true, optional = true}
tree-sitter-yaml = {workspace = true, optional = true}
tree-sitter = {workspace = true, optional = true}
[dev-dependencies]
text.workspace = true
theme = { workspace = true, features = ["test-support"] }

View File

@@ -31,6 +31,7 @@ mod yaml;
struct LanguageDir;
pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mut AppContext) {
#[cfg(feature = "load-grammars")]
languages.register_native_grammars([
("bash", tree_sitter_bash::LANGUAGE),
("c", tree_sitter_c::LANGUAGE),
@@ -282,9 +283,21 @@ fn load_config(name: &str) -> LanguageConfig {
)
.unwrap();
::toml::from_str(&config_toml)
#[allow(unused_mut)]
let mut config: LanguageConfig = ::toml::from_str(&config_toml)
.with_context(|| format!("failed to load config.toml for language {name:?}"))
.unwrap()
.unwrap();
#[cfg(not(feature = "load-grammars"))]
{
config = LanguageConfig {
name: config.name,
matcher: config.matcher,
..Default::default()
}
}
config
}
fn load_queries(name: &str) -> LanguageQueries {

View File

@@ -1,6 +1,6 @@
use anyhow::{anyhow, Context, Result};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
use http_client::{http, AsyncBody, HttpClient, Method, Request as HttpRequest};
use http_client::{http, AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{value::RawValue, Value};
@@ -83,7 +83,7 @@ fn get_max_tokens(name: &str) -> usize {
"codellama" | "starcoder2" => 16384,
"mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "dolphin-mixtral" => 32768,
"llama3.1" | "phi3" | "phi3.5" | "command-r" | "deepseek-coder-v2" | "yi-coder"
| "qwen2.5-coder" => 128000,
| "llama3.2" | "qwen2.5-coder" => 128000,
_ => DEFAULT_TOKENS,
}
.clamp(1, MAXIMUM_TOKENS)
@@ -262,14 +262,18 @@ pub async fn stream_chat_completion(
client: &dyn HttpClient,
api_url: &str,
request: ChatRequest,
_: Option<Duration>,
low_speed_timeout: Option<Duration>,
) -> Result<BoxStream<'static, Result<ChatResponseDelta>>> {
let uri = format!("{api_url}/api/chat");
let request_builder = http::Request::builder()
let mut request_builder = http::Request::builder()
.method(Method::POST)
.uri(uri)
.header("Content-Type", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
let mut response = client.send(request).await?;
if response.status().is_success() {

View File

@@ -19,7 +19,6 @@ schemars = ["dep:schemars"]
anyhow.workspace = true
futures.workspace = true
http_client.workspace = true
isahc.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true

View File

@@ -6,8 +6,7 @@ use futures::{
stream::{self, BoxStream},
AsyncBufReadExt, AsyncReadExt, Stream, StreamExt,
};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use isahc::config::Configurable;
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{
@@ -318,7 +317,7 @@ pub async fn complete(
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key));
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
};
let mut request_body = request;
@@ -413,7 +412,7 @@ pub async fn stream_completion(
.header("Authorization", format!("Bearer {}", api_key));
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.low_speed_timeout(100, low_speed_timeout);
request_builder = request_builder.read_timeout(low_speed_timeout);
};
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;

View File

@@ -143,8 +143,8 @@ impl OutlineViewDelegate {
self.active_editor.update(cx, |active_editor, cx| {
active_editor.clear_row_highlights::<OutlineRowHighlights>();
active_editor.highlight_rows::<OutlineRowHighlights>(
outline_item.range.start..=outline_item.range.end,
Some(cx.theme().colors().editor_highlighted_line_background),
outline_item.range.start..outline_item.range.end,
cx.theme().colors().editor_highlighted_line_background,
true,
cx,
);
@@ -240,12 +240,12 @@ impl PickerDelegate for OutlineViewDelegate {
self.prev_scroll_position.take();
self.active_editor.update(cx, |active_editor, cx| {
if let Some(rows) = active_editor
let highlight = active_editor
.highlighted_rows::<OutlineRowHighlights>()
.and_then(|highlights| highlights.into_iter().next().map(|(rows, _)| rows.clone()))
{
.next();
if let Some((rows, _)) = highlight {
active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
s.select_ranges([*rows.start()..*rows.start()])
s.select_ranges([rows.start..rows.start])
});
active_editor.clear_row_highlights::<OutlineRowHighlights>();
active_editor.focus(cx);

View File

@@ -36,10 +36,10 @@ use language::{
markdown, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig,
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus,
LanguageServerName, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageName,
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName, LocalFile, LspAdapter,
LspAdapterDelegate, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
};
use lsp::{
CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
@@ -53,7 +53,7 @@ use parking_lot::{Mutex, RwLock};
use postage::watch;
use rand::prelude::*;
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient};
use rpc::AnyProtoClient;
use serde::Serialize;
use settings::{Settings, SettingsLocation, SettingsStore};
use sha2::{Digest, Sha256};
@@ -644,16 +644,15 @@ pub struct RemoteLspStore {
impl RemoteLspStore {}
pub struct SshLspStore {
upstream_client: AnyProtoClient,
current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
}
// pub struct SshLspStore {
// upstream_client: AnyProtoClient,
// current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
// }
#[allow(clippy::large_enum_variant)]
pub enum LspStoreMode {
Local(LocalLspStore), // ssh host and collab host
Remote(RemoteLspStore), // collab guest
Ssh(SshLspStore), // ssh client
}
impl LspStoreMode {
@@ -661,10 +660,6 @@ impl LspStoreMode {
matches!(self, LspStoreMode::Local(_))
}
fn is_ssh(&self) -> bool {
matches!(self, LspStoreMode::Ssh(_))
}
fn is_remote(&self) -> bool {
matches!(self, LspStoreMode::Remote(_))
}
@@ -787,13 +782,6 @@ impl LspStore {
}
}
pub fn as_ssh(&self) -> Option<&SshLspStore> {
match &self.mode {
LspStoreMode::Ssh(ssh_lsp_store) => Some(ssh_lsp_store),
_ => None,
}
}
pub fn as_local(&self) -> Option<&LocalLspStore> {
match &self.mode {
LspStoreMode::Local(local_lsp_store) => Some(local_lsp_store),
@@ -810,9 +798,6 @@ impl LspStore {
pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
match &self.mode {
LspStoreMode::Ssh(SshLspStore {
upstream_client, ..
}) => Some((upstream_client.clone(), SSH_PROJECT_ID)),
LspStoreMode::Remote(RemoteLspStore {
upstream_client,
upstream_project_id,
@@ -827,11 +812,7 @@ impl LspStore {
new_settings: HashMap<LanguageServerName, LspSettings>,
) -> Option<HashMap<LanguageServerName, LspSettings>> {
match &mut self.mode {
LspStoreMode::Ssh(SshLspStore {
current_lsp_settings,
..
})
| LspStoreMode::Local(LocalLspStore {
LspStoreMode::Local(LocalLspStore {
current_lsp_settings,
..
}) => {
@@ -919,43 +900,6 @@ impl LspStore {
})
}
pub fn new_ssh(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
languages: Arc<LanguageRegistry>,
upstream_client: AnyProtoClient,
cx: &mut ModelContext<Self>,
) -> Self {
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
.detach();
Self {
mode: LspStoreMode::Ssh(SshLspStore {
upstream_client,
current_lsp_settings: Default::default(),
}),
downstream_client: None,
buffer_store,
worktree_store,
languages: languages.clone(),
language_server_ids: Default::default(),
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().gen(),
buffer_snapshots: Default::default(),
next_diagnostic_group_id: Default::default(),
diagnostic_summaries: Default::default(),
diagnostics: Default::default(),
active_entry: None,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
}
}
pub fn new_remote(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
@@ -2948,11 +2892,27 @@ impl LspStore {
let file = File::from_dyn(buffer.read(cx).file())?;
let worktree_id = file.worktree_id(cx);
let abs_path = file.as_local()?.abs_path(cx);
let worktree_path = file.as_local()?.path();
let text_document = lsp::TextDocumentIdentifier {
uri: lsp::Url::from_file_path(abs_path).log_err()?,
};
let watched_paths_for_server = &self.as_local()?.language_server_watched_paths;
for server in self.language_servers_for_worktree(worktree_id) {
let should_notify = maybe!({
Some(
watched_paths_for_server
.get(&server.server_id())?
.read(cx)
.worktree_paths
.get(&worktree_id)?
.is_match(worktree_path),
)
})
.unwrap_or_default();
if !should_notify {
continue;
}
if let Some(include_text) = include_text(server.as_ref()) {
let text = if include_text {
Some(buffer.read(cx).text())
@@ -3697,11 +3657,11 @@ impl LspStore {
mut cx: AsyncAppContext,
) -> Result<proto::MultiLspQueryResponse> {
let response_from_ssh = this.update(&mut cx, |this, _| {
let ssh = this.as_ssh()?;
let (upstream_client, project_id) = this.upstream_client()?;
let mut payload = envelope.payload.clone();
payload.project_id = SSH_PROJECT_ID;
payload.project_id = project_id;
Some(ssh.upstream_client.request(payload))
Some(upstream_client.request(payload))
})?;
if let Some(response_from_ssh) = response_from_ssh {
return response_from_ssh.await;
@@ -5009,165 +4969,6 @@ impl LspStore {
Ok(proto::Ack {})
}
pub async fn handle_create_language_server(
this: Model<Self>,
envelope: TypedEnvelope<proto::CreateLanguageServer>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let server_name = LanguageServerName::from_proto(envelope.payload.name);
let binary = envelope
.payload
.binary
.ok_or_else(|| anyhow!("missing binary"))?;
let binary = LanguageServerBinary {
path: PathBuf::from(binary.path),
env: None,
arguments: binary.arguments.into_iter().map(Into::into).collect(),
};
let language = envelope
.payload
.language
.ok_or_else(|| anyhow!("missing language"))?;
let language_name = LanguageName::from_proto(language.name);
let matcher: LanguageMatcher = serde_json::from_str(&language.matcher)?;
this.update(&mut cx, |this, cx| {
let Some(worktree) = this
.worktree_store
.read(cx)
.worktree_for_id(worktree_id, cx)
else {
return Err(anyhow!("worktree not found"));
};
this.languages
.register_language(language_name.clone(), None, matcher.clone(), {
let language_name = language_name.clone();
move || {
Ok((
LanguageConfig {
name: language_name.clone(),
matcher: matcher.clone(),
..Default::default()
},
Default::default(),
Default::default(),
))
}
});
cx.background_executor()
.spawn(this.languages.language_for_name(language_name.0.as_ref()))
.detach();
// host
let adapter = this.languages.get_or_register_lsp_adapter(
language_name.clone(),
server_name.clone(),
|| {
Arc::new(SshLspAdapter::new(
server_name,
binary,
envelope.payload.initialization_options,
envelope.payload.code_action_kinds,
))
},
);
this.start_language_server(&worktree, adapter, language_name, cx);
Ok(())
})??;
Ok(proto::Ack {})
}
pub async fn handle_which_command(
this: Model<Self>,
envelope: TypedEnvelope<proto::WhichCommand>,
mut cx: AsyncAppContext,
) -> Result<proto::WhichCommandResponse> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let command = PathBuf::from(envelope.payload.command);
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(
cx.spawn(|_, _| async move { delegate.which(command.as_os_str()).await }),
)
})??
.await;
Ok(proto::WhichCommandResponse {
path: response.map(|path| path.to_string_lossy().to_string()),
})
}
pub async fn handle_shell_env(
this: Model<Self>,
envelope: TypedEnvelope<proto::ShellEnv>,
mut cx: AsyncAppContext,
) -> Result<proto::ShellEnvResponse> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.shell_env().await }))
})??
.await;
Ok(proto::ShellEnvResponse {
env: response.into_iter().collect(),
})
}
pub async fn handle_try_exec(
this: Model<Self>,
envelope: TypedEnvelope<proto::TryExec>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let binary = envelope
.payload
.binary
.ok_or_else(|| anyhow!("missing binary"))?;
let binary = LanguageServerBinary {
path: PathBuf::from(binary.path),
env: None,
arguments: binary.arguments.into_iter().map(Into::into).collect(),
};
this.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.try_exec(binary).await }))
})??
.await?;
Ok(proto::Ack {})
}
pub async fn handle_read_text_file(
this: Model<Self>,
envelope: TypedEnvelope<proto::ReadTextFile>,
mut cx: AsyncAppContext,
) -> Result<proto::ReadTextFileResponse> {
let path = envelope
.payload
.path
.ok_or_else(|| anyhow!("missing path"))?;
let worktree_id = WorktreeId::from_proto(path.worktree_id);
let path = PathBuf::from(path.path);
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.read_text_file(path).await }))
})??
.await?;
Ok(proto::ReadTextFileResponse { text: response })
}
async fn handle_apply_additional_edits_for_completion(
this: Model<Self>,
envelope: TypedEnvelope<proto::ApplyCompletionAdditionalEdits>,
@@ -5388,89 +5189,6 @@ impl LspStore {
.reorder_language_servers(&language, enabled_lsp_adapters);
}
fn start_language_server_on_ssh_host(
&mut self,
worktree: &Model<Worktree>,
adapter: Arc<CachedLspAdapter>,
language: LanguageName,
cx: &mut ModelContext<Self>,
) {
let ssh = self.as_ssh().unwrap();
let delegate = Arc::new(SshLspAdapterDelegate {
lsp_store: cx.handle().downgrade(),
worktree: worktree.read(cx).snapshot(),
upstream_client: ssh.upstream_client.clone(),
language_registry: self.languages.clone(),
}) as Arc<dyn LspAdapterDelegate>;
let Some((upstream_client, project_id)) = self.upstream_client() else {
return;
};
let worktree_id = worktree.read(cx).id().to_proto();
let name = adapter.name().to_string();
let Some(available_language) = self.languages.available_language_for_name(&language) else {
log::error!("failed to find available language {language}");
return;
};
let user_binary_task =
self.get_language_server_binary(adapter.clone(), delegate.clone(), false, cx);
let task = cx.spawn(|_, _| async move {
let binary = user_binary_task.await?;
let name = adapter.name();
let code_action_kinds = adapter
.adapter
.code_action_kinds()
.map(|kinds| serde_json::to_string(&kinds))
.transpose()?;
let get_options = adapter.adapter.clone().initialization_options(&delegate);
let initialization_options = get_options
.await?
.map(|options| serde_json::to_string(&options))
.transpose()?;
let language_server_command = proto::LanguageServerCommand {
path: binary.path.to_string_lossy().to_string(),
arguments: binary
.arguments
.iter()
.map(|args| args.to_string_lossy().to_string())
.collect(),
env: binary.env.unwrap_or_default().into_iter().collect(),
};
upstream_client
.request(proto::CreateLanguageServer {
project_id,
worktree_id,
name: name.0.to_string(),
binary: Some(language_server_command),
initialization_options,
code_action_kinds,
language: Some(proto::AvailableLanguage {
name: language.to_proto(),
matcher: serde_json::to_string(&available_language.matcher())?,
}),
})
.await
});
cx.spawn(|this, mut cx| async move {
if let Err(e) = task.await {
this.update(&mut cx, |_this, cx| {
cx.emit(LspStoreEvent::Notification(format!(
"failed to start {}: {}",
name, e
)))
})
.ok();
}
})
.detach();
}
fn get_language_server_binary(
&self,
adapter: Arc<CachedLspAdapter>,
@@ -5558,11 +5276,6 @@ impl LspStore {
return;
}
if self.mode.is_ssh() {
self.start_language_server_on_ssh_host(worktree_handle, adapter, language, cx);
return;
}
let project_settings = ProjectSettings::get(
Some(SettingsLocation {
worktree_id,
@@ -5852,9 +5565,6 @@ impl LspStore {
} else {
Task::ready(Vec::new())
}
} else if self.mode.is_ssh() {
// TODO ssh
Task::ready(Vec::new())
} else {
Task::ready(Vec::new())
}
@@ -7905,116 +7615,6 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
}
}
struct SshLspAdapterDelegate {
lsp_store: WeakModel<LspStore>,
worktree: worktree::Snapshot,
upstream_client: AnyProtoClient,
language_registry: Arc<LanguageRegistry>,
}
#[async_trait]
impl LspAdapterDelegate for SshLspAdapterDelegate {
fn show_notification(&self, message: &str, cx: &mut AppContext) {
self.lsp_store
.update(cx, |_, cx| {
cx.emit(LspStoreEvent::Notification(message.to_owned()))
})
.ok();
}
async fn npm_package_installed_version(
&self,
_package_name: &str,
) -> Result<Option<(PathBuf, String)>> {
Ok(None)
}
fn http_client(&self) -> Arc<dyn HttpClient> {
Arc::new(BlockedHttpClient)
}
fn worktree_id(&self) -> WorktreeId {
self.worktree.id()
}
fn worktree_root_path(&self) -> &Path {
self.worktree.abs_path().as_ref()
}
async fn shell_env(&self) -> HashMap<String, String> {
use rpc::proto::SSH_PROJECT_ID;
self.upstream_client
.request(proto::ShellEnv {
project_id: SSH_PROJECT_ID,
worktree_id: self.worktree_id().to_proto(),
})
.await
.map(|response| response.env.into_iter().collect())
.unwrap_or_default()
}
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
use rpc::proto::SSH_PROJECT_ID;
self.upstream_client
.request(proto::WhichCommand {
project_id: SSH_PROJECT_ID,
worktree_id: self.worktree_id().to_proto(),
command: command.to_string_lossy().to_string(),
})
.await
.log_err()
.and_then(|response| response.path)
.map(PathBuf::from)
}
async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
self.upstream_client
.request(proto::TryExec {
project_id: rpc::proto::SSH_PROJECT_ID,
worktree_id: self.worktree.id().to_proto(),
binary: Some(proto::LanguageServerCommand {
path: command.path.to_string_lossy().to_string(),
arguments: command
.arguments
.into_iter()
.map(|s| s.to_string_lossy().to_string())
.collect(),
env: command.env.unwrap_or_default().into_iter().collect(),
}),
})
.await?;
Ok(())
}
async fn language_server_download_dir(&self, _: &LanguageServerName) -> Option<Arc<Path>> {
None
}
fn update_status(
&self,
server_name: LanguageServerName,
status: language::LanguageServerBinaryStatus,
) {
self.language_registry
.update_lsp_status(server_name, status);
}
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
self.upstream_client
.request(proto::ReadTextFile {
project_id: rpc::proto::SSH_PROJECT_ID,
path: Some(proto::ProjectPath {
worktree_id: self.worktree.id().to_proto(),
path: path.to_string_lossy().to_string(),
}),
})
.await
.map(|r| r.text)
}
}
async fn populate_labels_for_symbols(
symbols: Vec<CoreSymbol>,
language_registry: &Arc<LanguageRegistry>,

View File

@@ -558,7 +558,6 @@ impl Project {
client.add_model_message_handler(Self::handle_update_worktree);
client.add_model_request_handler(Self::handle_synchronize_buffers);
client.add_model_request_handler(Self::handle_search_project);
client.add_model_request_handler(Self::handle_search_candidate_buffers);
client.add_model_request_handler(Self::handle_open_buffer_by_id);
client.add_model_request_handler(Self::handle_open_buffer_by_path);
@@ -706,11 +705,12 @@ impl Project {
let environment = ProjectEnvironment::new(&worktree_store, None, cx);
let lsp_store = cx.new_model(|cx| {
LspStore::new_ssh(
LspStore::new_remote(
buffer_store.clone(),
worktree_store.clone(),
languages.clone(),
ssh.clone().into(),
SSH_PROJECT_ID,
cx,
)
});
@@ -886,6 +886,9 @@ impl Project {
cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
.detach();
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
@@ -2691,9 +2694,9 @@ impl Project {
let (result_tx, result_rx) = smol::channel::unbounded();
let matching_buffers_rx = if query.is_opened_only() {
self.sort_candidate_buffers(&query, cx)
self.sort_search_candidates(&query, cx)
} else {
self.search_for_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
self.find_search_candidate_buffers(&query, MAX_SEARCH_RESULT_FILES + 1, cx)
};
cx.spawn(|_, cx| async move {
@@ -2756,7 +2759,7 @@ impl Project {
result_rx
}
fn search_for_candidate_buffers(
fn find_search_candidate_buffers(
&mut self,
query: &SearchQuery,
limit: usize,
@@ -2768,11 +2771,11 @@ impl Project {
buffer_store.find_search_candidates(query, limit, fs, cx)
})
} else {
self.search_for_candidate_buffers_remote(query, limit, cx)
self.find_search_candidates_remote(query, limit, cx)
}
}
fn sort_candidate_buffers(
fn sort_search_candidates(
&mut self,
search_query: &SearchQuery,
cx: &mut ModelContext<Project>,
@@ -2814,7 +2817,7 @@ impl Project {
rx
}
fn search_for_candidate_buffers_remote(
fn find_search_candidates_remote(
&mut self,
query: &SearchQuery,
limit: usize,
@@ -3655,46 +3658,6 @@ impl Project {
Ok(proto::TaskTemplatesResponse { templates })
}
async fn handle_search_project(
this: Model<Self>,
envelope: TypedEnvelope<proto::SearchProject>,
mut cx: AsyncAppContext,
) -> Result<proto::SearchProjectResponse> {
let peer_id = envelope.original_sender_id()?;
let query = SearchQuery::from_proto_v1(envelope.payload)?;
let mut result = this.update(&mut cx, |this, cx| this.search(query, cx))?;
cx.spawn(move |mut cx| async move {
let mut locations = Vec::new();
let mut limit_reached = false;
while let Some(result) = result.next().await {
match result {
SearchResult::Buffer { buffer, ranges } => {
for range in ranges {
let start = serialize_anchor(&range.start);
let end = serialize_anchor(&range.end);
let buffer_id = this.update(&mut cx, |this, cx| {
this.create_buffer_for_peer(&buffer, peer_id, cx).into()
})?;
locations.push(proto::Location {
buffer_id,
start: Some(start),
end: Some(end),
});
}
}
SearchResult::LimitReached => limit_reached = true,
}
}
Ok(proto::SearchProjectResponse {
locations,
limit_reached,
// will restart
})
})
.await
}
async fn handle_search_candidate_buffers(
this: Model<Self>,
envelope: TypedEnvelope<proto::FindSearchCandidates>,
@@ -3708,7 +3671,7 @@ impl Project {
.ok_or_else(|| anyhow!("missing query field"))?,
)?;
let mut results = this.update(&mut cx, |this, cx| {
this.search_for_candidate_buffers(&query, message.limit as _, cx)
this.find_search_candidate_buffers(&query, message.limit as _, cx)
})?;
let mut response = proto::FindSearchCandidatesResponse {

View File

@@ -386,6 +386,34 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
// A server is started up, and it is notified about Rust files.
let mut fake_rust_server = fake_rust_servers.next().await.unwrap();
fake_rust_server
.request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
registrations: vec![lsp::Registration {
id: Default::default(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: serde_json::to_value(
lsp::DidChangeWatchedFilesRegistrationOptions {
watchers: vec![
lsp::FileSystemWatcher {
glob_pattern: lsp::GlobPattern::String(
"/the-root/Cargo.toml".to_string(),
),
kind: None,
},
lsp::FileSystemWatcher {
glob_pattern: lsp::GlobPattern::String(
"/the-root/*.rs".to_string(),
),
kind: None,
},
],
},
)
.ok(),
}],
})
.await
.unwrap();
assert_eq!(
fake_rust_server
.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -433,6 +461,24 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
// A json language server is started up and is only notified about the json buffer.
let mut fake_json_server = fake_json_servers.next().await.unwrap();
fake_json_server
.request::<lsp::request::RegisterCapability>(lsp::RegistrationParams {
registrations: vec![lsp::Registration {
id: Default::default(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: serde_json::to_value(
lsp::DidChangeWatchedFilesRegistrationOptions {
watchers: vec![lsp::FileSystemWatcher {
glob_pattern: lsp::GlobPattern::String("/the-root/*.json".to_string()),
kind: None,
}],
},
)
.ok(),
}],
})
.await
.unwrap();
assert_eq!(
fake_json_server
.receive_notification::<lsp::notification::DidOpenTextDocument>()
@@ -483,7 +529,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
)
);
// Save notifications are reported to all servers.
// Save notifications are reported only to servers that signed up for a given extension.
project
.update(cx, |project, cx| project.save_buffer(toml_buffer, cx))
.await
@@ -495,13 +541,6 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
.text_document,
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
);
assert_eq!(
fake_json_server
.receive_notification::<lsp::notification::DidSaveTextDocument>()
.await
.text_document,
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path("/the-root/Cargo.toml").unwrap())
);
// Renames are reported only to servers matching the buffer's language.
fs.rename(

View File

@@ -147,30 +147,6 @@ impl SearchQuery {
})
}
pub fn from_proto_v1(message: proto::SearchProject) -> Result<Self> {
if message.regex {
Self::regex(
message.query,
message.whole_word,
message.case_sensitive,
message.include_ignored,
deserialize_path_matches(&message.files_to_include)?,
deserialize_path_matches(&message.files_to_exclude)?,
None,
)
} else {
Self::text(
message.query,
message.whole_word,
message.case_sensitive,
message.include_ignored,
deserialize_path_matches(&message.files_to_include)?,
deserialize_path_matches(&message.files_to_exclude)?,
None,
)
}
}
pub fn from_proto(message: proto::SearchQuery) -> Result<Self> {
if message.regex {
Self::regex(
@@ -194,6 +170,7 @@ impl SearchQuery {
)
}
}
pub fn with_replacement(mut self, new_replacement: String) -> Self {
match self {
Self::Text {
@@ -209,18 +186,6 @@ impl SearchQuery {
}
}
}
pub fn to_protov1(&self, project_id: u64) -> proto::SearchProject {
proto::SearchProject {
project_id,
query: self.as_str().to_string(),
regex: self.is_regex(),
whole_word: self.whole_word(),
case_sensitive: self.case_sensitive(),
include_ignored: self.include_ignored(),
files_to_include: self.files_to_include().sources().join(","),
files_to_exclude: self.files_to_exclude().sources().join(","),
}
}
pub fn to_proto(&self) -> proto::SearchQuery {
proto::SearchQuery {

View File

@@ -204,8 +204,11 @@ impl WorktreeStore {
self.loading_worktrees.insert(path.clone(), task.shared());
}
let task = self.loading_worktrees.get(&path).unwrap().clone();
cx.background_executor().spawn(async move {
match task.await {
cx.spawn(|this, mut cx| async move {
let result = task.await;
this.update(&mut cx, |this, _| this.loading_worktrees.remove(&path))
.ok();
match result {
Ok(worktree) => Ok(worktree),
Err(err) => Err((*err).cloned()),
}
@@ -219,7 +222,8 @@ impl WorktreeStore {
visible: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
let mut abs_path = abs_path.as_ref().to_string_lossy().to_string();
let path_key: Arc<Path> = abs_path.as_ref().into();
let mut abs_path = path_key.clone().to_string_lossy().to_string();
// If we start with `/~` that means the ssh path was something like `ssh://user@host/~/home-dir-folder/`
// in which case want to strip the leading the `/`.
// On the host-side, the `~` will get expanded.
@@ -261,8 +265,9 @@ impl WorktreeStore {
)
})?;
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?;
this.update(&mut cx, |this, cx| {
this.add(&worktree, cx);
})?;
Ok(worktree)
})
}
@@ -280,10 +285,6 @@ impl WorktreeStore {
cx.spawn(move |this, mut cx| async move {
let worktree = Worktree::local(path.clone(), visible, fs, next_entry_id, &mut cx).await;
this.update(&mut cx, |project, _| {
project.loading_worktrees.remove(&path);
})?;
let worktree = worktree?;
this.update(&mut cx, |this, cx| this.add(&worktree, cx))?;
@@ -317,7 +318,7 @@ impl WorktreeStore {
});
let abs_path = abs_path.as_ref().to_path_buf();
cx.spawn(move |project, mut cx| async move {
cx.spawn(move |project, cx| async move {
let (tx, rx) = futures::channel::oneshot::channel();
let tx = RefCell::new(Some(tx));
let Some(project) = project.upgrade() else {
@@ -339,14 +340,10 @@ impl WorktreeStore {
request.await?;
let worktree = rx.await.map_err(|e| anyhow!(e))?;
drop(observer);
project.update(&mut cx, |project, _| {
project.loading_worktrees.remove(&path);
})?;
Ok(worktree)
})
}
#[track_caller]
pub fn add(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
let worktree_id = worktree.read(cx).id();
debug_assert!(self.worktrees().all(|w| w.read(cx).id() != worktree_id));
@@ -553,9 +550,12 @@ impl WorktreeStore {
let client = client.clone();
async move {
if client.is_via_collab() {
client.request(update).map(|result| result.is_ok()).await
client
.request(update)
.map(|result| result.log_err().is_some())
.await
} else {
client.send(update).is_ok()
client.send(update).log_err().is_some()
}
}
}

View File

@@ -108,8 +108,6 @@ message Envelope {
PrepareRenameResponse prepare_rename_response = 84;
PerformRename perform_rename = 85;
PerformRenameResponse perform_rename_response = 86;
SearchProject search_project = 87;
SearchProjectResponse search_project_response = 88;
UpdateContacts update_contacts = 89;
UpdateInviteInfo update_invite_info = 90;
@@ -283,25 +281,15 @@ message Envelope {
CloseBuffer close_buffer = 245;
UpdateUserSettings update_user_settings = 246;
CreateLanguageServer create_language_server = 247;
WhichCommand which_command = 248;
WhichCommandResponse which_command_response = 249;
ShellEnv shell_env = 250;
ShellEnvResponse shell_env_response = 251;
TryExec try_exec = 252;
ReadTextFile read_text_file = 253;
ReadTextFileResponse read_text_file_response = 254;
CheckFileExists check_file_exists = 255;
CheckFileExistsResponse check_file_exists_response = 256; // current max
}
reserved 87 to 88;
reserved 158 to 161;
reserved 166 to 169;
reserved 224 to 229;
reserved 247 to 254;
}
// Messages
@@ -1249,22 +1237,6 @@ message PerformRenameResponse {
ProjectTransaction transaction = 2;
}
message SearchProject {
uint64 project_id = 1;
string query = 2;
bool regex = 3;
bool whole_word = 4;
bool case_sensitive = 5;
string files_to_include = 6;
string files_to_exclude = 7;
bool include_ignored = 8;
}
message SearchProjectResponse {
repeated Location locations = 1;
bool limit_reached = 2;
}
message SearchQuery {
string query = 2;
bool regex = 3;
@@ -2517,67 +2489,6 @@ message UpdateUserSettings {
string content = 2;
}
message LanguageServerCommand {
string path = 1;
repeated string arguments = 2;
map<string, string> env = 3;
}
message AvailableLanguage {
string name = 7;
string matcher = 8;
}
message CreateLanguageServer {
uint64 project_id = 1;
uint64 worktree_id = 2;
string name = 3;
LanguageServerCommand binary = 4;
optional string initialization_options = 5;
optional string code_action_kinds = 6;
AvailableLanguage language = 7;
}
message WhichCommand {
uint64 project_id = 1;
uint64 worktree_id = 2;
string command = 3;
}
message WhichCommandResponse {
optional string path = 1;
}
message ShellEnv {
uint64 project_id = 1;
uint64 worktree_id = 2;
}
message ShellEnvResponse {
map<string, string> env = 1;
}
message ReadTextFile {
uint64 project_id = 1;
ProjectPath path = 2;
}
message ReadTextFileResponse {
string text = 1;
}
message TryExec {
uint64 project_id = 1;
uint64 worktree_id = 2;
LanguageServerCommand binary = 3;
}
message TryExecResponse {
string text = 1;
}
message CheckFileExists {
uint64 project_id = 1;
string path = 2;

View File

@@ -279,8 +279,6 @@ messages!(
(SaveBuffer, Foreground),
(SetChannelMemberRole, Foreground),
(SetChannelVisibility, Foreground),
(SearchProject, Background),
(SearchProjectResponse, Background),
(SendChannelMessage, Background),
(SendChannelMessageResponse, Background),
(ShareProject, Foreground),
@@ -365,14 +363,6 @@ messages!(
(FindSearchCandidatesResponse, Background),
(CloseBuffer, Foreground),
(UpdateUserSettings, Foreground),
(CreateLanguageServer, Foreground),
(WhichCommand, Foreground),
(WhichCommandResponse, Foreground),
(ShellEnv, Foreground),
(ShellEnvResponse, Foreground),
(TryExec, Foreground),
(ReadTextFile, Foreground),
(ReadTextFileResponse, Foreground),
(CheckFileExists, Background),
(CheckFileExistsResponse, Background)
);
@@ -462,7 +452,6 @@ request_messages!(
(RespondToChannelInvite, Ack),
(RespondToContactRequest, Ack),
(SaveBuffer, BufferSaved),
(SearchProject, SearchProjectResponse),
(FindSearchCandidates, FindSearchCandidatesResponse),
(SendChannelMessage, SendChannelMessageResponse),
(SetChannelMemberRole, Ack),
@@ -498,11 +487,6 @@ request_messages!(
(SynchronizeContexts, SynchronizeContextsResponse),
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse),
(CreateLanguageServer, Ack),
(WhichCommand, WhichCommandResponse),
(ShellEnv, ShellEnvResponse),
(ReadTextFile, ReadTextFileResponse),
(TryExec, Ack),
(CheckFileExists, CheckFileExistsResponse)
);
@@ -554,7 +538,6 @@ entity_messages!(
ResolveCompletionDocumentation,
ResolveInlayHint,
SaveBuffer,
SearchProject,
StartLanguageServer,
SynchronizeBuffers,
TaskContextForLocation,
@@ -577,11 +560,6 @@ entity_messages!(
SynchronizeContexts,
LspExtSwitchSourceHeader,
UpdateUserSettings,
CreateLanguageServer,
WhichCommand,
ShellEnv,
TryExec,
ReadTextFile,
CheckFileExists,
);

View File

@@ -8,8 +8,8 @@ use editor::actions::{
use editor::{Editor, EditorSettings};
use gpui::{
Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement,
Render, Styled, Subscription, View, ViewContext, WeakView,
Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
};
use search::{buffer_search, BufferSearchBar};
use settings::{Settings, SettingsStore};
@@ -110,12 +110,15 @@ impl Render for QuickActionBar {
)
};
let focus_handle = editor.read(cx).focus_handle(cx);
let search_button = editor.is_singleton(cx).then(|| {
QuickActionBarButton::new(
"toggle buffer search",
IconName::MagnifyingGlass,
!self.buffer_search_bar.read(cx).is_dismissed(),
Box::new(buffer_search::Deploy::find()),
focus_handle.clone(),
"Buffer Search",
{
let buffer_search_bar = self.buffer_search_bar.clone();
@@ -133,6 +136,7 @@ impl Render for QuickActionBar {
IconName::ZedAssistant,
false,
Box::new(InlineAssist::default()),
focus_handle.clone(),
"Inline Assist",
{
let workspace = self.workspace.clone();
@@ -321,6 +325,7 @@ struct QuickActionBarButton {
icon: IconName,
toggled: bool,
action: Box<dyn Action>,
focus_handle: FocusHandle,
tooltip: SharedString,
on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
}
@@ -331,6 +336,7 @@ impl QuickActionBarButton {
icon: IconName,
toggled: bool,
action: Box<dyn Action>,
focus_handle: FocusHandle,
tooltip: impl Into<SharedString>,
on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
) -> Self {
@@ -339,6 +345,7 @@ impl QuickActionBarButton {
icon,
toggled,
action,
focus_handle,
tooltip: tooltip.into(),
on_click: Box::new(on_click),
}
@@ -355,7 +362,9 @@ impl RenderOnce for QuickActionBarButton {
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.selected(self.toggled)
.tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
.tooltip(move |cx| {
Tooltip::for_action_in(tooltip.clone(), &*action, &self.focus_handle, cx)
})
.on_click(move |event, cx| (self.on_click)(event, cx))
}
}

View File

@@ -40,7 +40,6 @@ use ui::{
};
use ui_input::{FieldLabelLayout, TextField};
use util::ResultExt;
use workspace::notifications::NotifyResultExt;
use workspace::OpenOptions;
use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB};
@@ -1133,7 +1132,8 @@ impl DevServerProjects {
let dev_server_id = state.dev_server_id;
let access_token = state.access_token.clone();
let ssh_prompt = state.ssh_prompt.clone();
let use_direct_ssh = SshSettings::get_global(cx).use_direct_ssh();
let use_direct_ssh = SshSettings::get_global(cx).use_direct_ssh()
|| Client::global(cx).status().borrow().is_signed_out();
let mut kind = state.kind;
if use_direct_ssh && kind == NewServerKind::LegacySSH {
@@ -1407,7 +1407,6 @@ impl DevServerProjects {
is_creating = Some(*creating);
creating_dev_server = Some(*dev_server_id);
};
let is_signed_out = Client::global(cx).status().borrow().is_signed_out();
Modal::new("remote-projects", Some(self.scroll_handle.clone()))
.header(
@@ -1415,82 +1414,58 @@ impl DevServerProjects {
.show_dismiss_button(true)
.child(Headline::new("Remote Projects (alpha)").size(HeadlineSize::Small)),
)
.when(is_signed_out, |modal| {
modal
.section(Section::new().child(div().child(Label::new(
"To continue with the remote development features, you need to sign in to Zed.",
))))
.footer(
ModalFooter::new().end_slot(
Button::new("sign_in", "Sign in with GitHub")
.icon(IconName::Github)
.icon_position(IconPosition::Start)
.full_width()
.on_click(cx.listener(|_, _, cx| {
let client = Client::global(cx).clone();
cx.spawn(|_, mut cx| async move {
client
.authenticate_and_connect(true, &cx)
.await
.notify_async_err(&mut cx);
})
.detach();
cx.emit(gpui::DismissEvent);
})),
),
)
})
.when(!is_signed_out, |modal| {
modal.section(
Section::new().child(
div().child(
List::new()
.empty_message("No dev servers registered yet.")
.header(Some(
ListHeader::new("Connections").end_slot(
Button::new("register-dev-server-button", "Connect New Server")
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_color(Color::Muted)
.on_click(cx.listener(|this, _, cx| {
this.mode = Mode::CreateDevServer(
CreateDevServer {
kind: if SshSettings::get_global(cx).use_direct_ssh() { NewServerKind::DirectSSH } else { NewServerKind::LegacySSH },
..Default::default()
}
);
this.dev_server_name_input.update(
cx,
|text_field, cx| {
text_field.editor().update(
cx,
|editor, cx| {
editor.set_text("", cx);
},
);
},
);
cx.notify();
})),
),
))
.children(ssh_connections.iter().cloned().enumerate().map(|(ix, connection)| {
.section(
Section::new().child(
div().child(
List::new()
.empty_message("No dev servers registered yet.")
.header(Some(
ListHeader::new("Connections").end_slot(
Button::new("register-dev-server-button", "Connect New Server")
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_color(Color::Muted)
.on_click(cx.listener(|this, _, cx| {
this.mode = Mode::CreateDevServer(CreateDevServer {
kind: if SshSettings::get_global(cx)
.use_direct_ssh()
{
NewServerKind::DirectSSH
} else {
NewServerKind::LegacySSH
},
..Default::default()
});
this.dev_server_name_input.update(
cx,
|text_field, cx| {
text_field.editor().update(cx, |editor, cx| {
editor.set_text("", cx);
});
},
);
cx.notify();
})),
),
))
.children(ssh_connections.iter().cloned().enumerate().map(
|(ix, connection)| {
self.render_ssh_connection(ix, connection, cx)
.into_any_element()
}))
.children(dev_servers.iter().map(|dev_server| {
let creating = if creating_dev_server == Some(dev_server.id) {
is_creating
} else {
None
};
self.render_dev_server(dev_server, creating, cx)
.into_any_element()
})),
),
},
))
.children(dev_servers.iter().map(|dev_server| {
let creating = if creating_dev_server == Some(dev_server.id) {
is_creating
} else {
None
};
self.render_dev_server(dev_server, creating, cx)
.into_any_element()
})),
),
)
})
),
)
}
}

View File

@@ -16,8 +16,9 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::{
h_flex, v_flex, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement, IntoElement,
Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext, WindowContext,
h_flex, v_flex, Color, FluentBuilder as _, Icon, IconName, IconSize, InteractiveElement,
IntoElement, Label, LabelCommon, Styled, StyledExt as _, ViewContext, VisualContext,
WindowContext,
};
use workspace::{AppState, ModalView, Workspace};
@@ -79,6 +80,7 @@ impl Settings for SshSettings {
pub struct SshPrompt {
connection_string: SharedString,
status_message: Option<SharedString>,
error_message: Option<SharedString>,
prompt: Option<(SharedString, oneshot::Sender<Result<String>>)>,
editor: View<Editor>,
}
@@ -92,6 +94,7 @@ impl SshPrompt {
Self {
connection_string,
status_message: None,
error_message: None,
prompt: None,
editor: cx.new_view(Editor::single_line),
}
@@ -121,6 +124,11 @@ impl SshPrompt {
cx.notify();
}
pub fn set_error(&mut self, error_message: String, cx: &mut ViewContext<Self>) {
self.error_message = Some(error_message.into());
cx.notify();
}
pub fn confirm(&mut self, cx: &mut ViewContext<Self>) {
if let Some((_, tx)) = self.prompt.take() {
self.editor.update(cx, |editor, cx| {
@@ -140,7 +148,12 @@ impl Render for SshPrompt {
.child(
h_flex()
.gap_2()
.child(
.child(if self.error_message.is_some() {
Icon::new(IconName::XCircle)
.size(IconSize::Medium)
.color(Color::Error)
.into_any_element()
} else {
Icon::new(IconName::ArrowCircle)
.size(IconSize::Medium)
.with_animation(
@@ -149,16 +162,21 @@ impl Render for SshPrompt {
|icon, delta| {
icon.transform(Transformation::rotate(percentage(delta)))
},
),
)
)
.into_any_element()
})
.child(
Label::new(format!("ssh {}", self.connection_string))
.size(ui::LabelSize::Large),
),
)
.when_some(self.status_message.as_ref(), |el, status| {
el.child(Label::new(status.clone()))
.when_some(self.error_message.as_ref(), |el, error| {
el.child(Label::new(error.clone()))
})
.when(
self.error_message.is_none() && self.status_message.is_some(),
|el| el.child(Label::new(self.status_message.clone().unwrap())),
)
.when_some(self.prompt.as_ref(), |el, prompt| {
el.child(Label::new(prompt.0.clone()))
.child(self.editor.clone())
@@ -238,6 +256,10 @@ impl remote::SshClientDelegate for SshClientDelegate {
self.update_status(status, cx)
}
fn set_error(&self, error: String, cx: &mut AsyncAppContext) {
self.update_error(error, cx)
}
fn get_server_binary(
&self,
platform: SshPlatform,
@@ -270,6 +292,16 @@ impl SshClientDelegate {
.ok();
}
fn update_error(&self, error: String, cx: &mut AsyncAppContext) {
self.window
.update(cx, |_, cx| {
self.ui.update(cx, |modal, cx| {
modal.set_error(error, cx);
})
})
.ok();
}
async fn get_server_binary_impl(
&self,
platform: SshPlatform,
@@ -388,7 +420,7 @@ pub async fn open_ssh_project(
})?
};
let result = window
let session = window
.update(cx, |workspace, cx| {
cx.activate_window();
workspace.toggle_modal(cx, |cx| SshConnectionModal::new(&connection_options, cx));
@@ -400,12 +432,7 @@ pub async fn open_ssh_project(
.clone();
connect_over_ssh(connection_options.clone(), ui, cx)
})?
.await;
if result.is_err() {
window.update(cx, |_, cx| cx.remove_window()).ok();
}
let session = result?;
.await?;
cx.update(|cx| {
workspace::open_ssh_project(window, connection_options, session, app_state, paths, cx)

View File

@@ -129,6 +129,7 @@ pub trait SshClientDelegate {
cx: &mut AsyncAppContext,
) -> oneshot::Receiver<Result<(PathBuf, SemanticVersion)>>;
fn set_status(&self, status: Option<&str>, cx: &mut AsyncAppContext);
fn set_error(&self, error_message: String, cx: &mut AsyncAppContext);
}
type ResponseChannels = Mutex<HashMap<MessageId, oneshot::Sender<(Envelope, oneshot::Sender<()>)>>>;
@@ -208,16 +209,16 @@ impl SshSession {
result = child_stdout.read(&mut stdout_buffer).fuse() => {
match result {
Ok(len) => {
if len == 0 {
child_stdin.close().await?;
let status = remote_server_child.status().await?;
if !status.success() {
log::info!("channel exited with status: {status:?}");
}
return Ok(());
Ok(0) => {
child_stdin.close().await?;
outgoing_rx.close();
let status = remote_server_child.status().await?;
if !status.success() {
log::error!("channel exited with status: {status:?}");
}
return Ok(());
}
Ok(len) => {
if len < stdout_buffer.len() {
child_stdout.read_exact(&mut stdout_buffer[len..]).await?;
}
@@ -419,8 +420,13 @@ impl SshSession {
let mut response_channels_lock = self.response_channels.lock();
response_channels_lock.insert(MessageId(envelope.id), tx);
drop(response_channels_lock);
self.outgoing_tx.unbounded_send(envelope).ok();
let result = self.outgoing_tx.unbounded_send(envelope);
async move {
if let Err(error) = &result {
log::error!("failed to send message: {}", error);
return Err(anyhow!("failed to send message: {}", error));
}
let response = rx.await.context("connection lost")?.0;
if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
return Err(RpcError::from_proto(error, type_name));
@@ -525,22 +531,25 @@ impl SshClientState {
let listener =
UnixListener::bind(&askpass_socket).context("failed to create askpass socket")?;
let askpass_task = cx.spawn(|mut cx| async move {
while let Ok((mut stream, _)) = listener.accept().await {
let mut buffer = Vec::new();
let mut reader = BufReader::new(&mut stream);
if reader.read_until(b'\0', &mut buffer).await.is_err() {
buffer.clear();
}
let password_prompt = String::from_utf8_lossy(&buffer);
if let Some(password) = delegate
.ask_password(password_prompt.to_string(), &mut cx)
.await
.context("failed to get ssh password")
.and_then(|p| p)
.log_err()
{
stream.write_all(password.as_bytes()).await.log_err();
let askpass_task = cx.spawn({
let delegate = delegate.clone();
|mut cx| async move {
while let Ok((mut stream, _)) = listener.accept().await {
let mut buffer = Vec::new();
let mut reader = BufReader::new(&mut stream);
if reader.read_until(b'\0', &mut buffer).await.is_err() {
buffer.clear();
}
let password_prompt = String::from_utf8_lossy(&buffer);
if let Some(password) = delegate
.ask_password(password_prompt.to_string(), &mut cx)
.await
.context("failed to get ssh password")
.and_then(|p| p)
.log_err()
{
stream.write_all(password.as_bytes()).await.log_err();
}
}
}
});
@@ -575,7 +584,22 @@ impl SshClientState {
// has completed.
let stdout = master_process.stdout.as_mut().unwrap();
let mut output = Vec::new();
stdout.read_to_end(&mut output).await?;
let connection_timeout = std::time::Duration::from_secs(10);
let result = read_with_timeout(stdout, connection_timeout, &mut output).await;
if let Err(e) = result {
let error_message = if e.kind() == std::io::ErrorKind::TimedOut {
format!(
"Failed to connect to host. Timed out after {:?}.",
connection_timeout
)
} else {
format!("Failed to connect to host: {}.", e)
};
delegate.set_error(error_message, cx);
return Err(e.into());
}
drop(askpass_task);
if master_process.try_status()?.is_some() {
@@ -716,6 +740,29 @@ impl SshClientState {
}
}
#[cfg(unix)]
async fn read_with_timeout(
stdout: &mut process::ChildStdout,
timeout: std::time::Duration,
output: &mut Vec<u8>,
) -> Result<(), std::io::Error> {
smol::future::or(
async {
stdout.read_to_end(output).await?;
Ok::<_, std::io::Error>(())
},
async {
smol::Timer::after(timeout).await;
Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"Read operation timed out",
))
},
)
.await
}
impl Drop for SshClientState {
fn drop(&mut self) {
if let Err(error) = self.master_process.kill() {

View File

@@ -39,6 +39,7 @@ shellexpand.workspace = true
smol.workspace = true
worktree.workspace = true
language.workspace = true
languages.workspace = true
util.workspace = true
[dev-dependencies]

View File

@@ -44,6 +44,10 @@ impl HeadlessProject {
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
let node_runtime = NodeRuntime::unavailable();
languages::init(languages.clone(), node_runtime.clone(), cx);
let worktree_store = cx.new_model(|cx| {
let mut store = WorktreeStore::local(true, fs.clone());
store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
@@ -56,7 +60,7 @@ impl HeadlessProject {
});
let prettier_store = cx.new_model(|cx| {
PrettierStore::new(
NodeRuntime::unavailable(),
node_runtime,
fs.clone(),
languages.clone(),
worktree_store.clone(),
@@ -116,12 +120,6 @@ impl HeadlessProject {
client.add_model_request_handler(BufferStore::handle_update_buffer);
client.add_model_message_handler(BufferStore::handle_close_buffer);
client.add_model_request_handler(LspStore::handle_create_language_server);
client.add_model_request_handler(LspStore::handle_which_command);
client.add_model_request_handler(LspStore::handle_shell_env);
client.add_model_request_handler(LspStore::handle_try_exec);
client.add_model_request_handler(LspStore::handle_read_text_file);
BufferStore::init(&client);
WorktreeStore::init(&client);
SettingsObserver::init(&client);

View File

@@ -564,6 +564,48 @@ async fn test_canceling_buffer_opening(cx: &mut TestAppContext, server_cx: &mut
});
}
#[gpui::test]
async fn test_adding_then_removing_then_adding_worktrees(
cx: &mut TestAppContext,
server_cx: &mut TestAppContext,
) {
let (project, _headless, _fs) = init_test(cx, server_cx).await;
let (_worktree, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project1", true, cx)
})
.await
.unwrap();
let (worktree_2, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project2", true, cx)
})
.await
.unwrap();
let worktree_id_2 = worktree_2.read_with(cx, |tree, _| tree.id());
project.update(cx, |project, cx| project.remove_worktree(worktree_id_2, cx));
let (worktree_2, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree("/code/project2", true, cx)
})
.await
.unwrap();
cx.run_until_parked();
worktree_2.update(cx, |worktree, _cx| {
assert!(worktree.is_visible());
let entries = worktree.entries(true, 0).collect::<Vec<_>>();
assert_eq!(entries.len(), 2);
assert_eq!(
entries[1].path.to_string_lossy().to_string(),
"README.md".to_string()
)
})
}
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::try_init().ok();

View File

@@ -12,7 +12,6 @@ pub enum LanguageModelProvider {
Anthropic,
OpenAi,
Google,
Zed,
}
#[derive(Debug, Serialize, Deserialize)]

View File

@@ -13,9 +13,10 @@ use editor::{
};
use futures::channel::oneshot;
use gpui::{
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView, Hsla,
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,
Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _, WindowContext,
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusHandle,
FocusableView, Hsla, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _,
Render, ScrollHandle, Styled, Subscription, Task, TextStyle, View, ViewContext,
VisualContext as _, WindowContext,
};
use project::{
search::SearchQuery,
@@ -142,6 +143,8 @@ impl Render for BufferSearchBar {
return div().id("search_bar");
}
let focus_handle = self.focus_handle(cx);
let narrow_mode =
self.scroll_handle.bounds().size.width / cx.rem_size() < 340. / BASE_REM_SIZE_IN_PX;
let hide_inline_icons = self.editor_needed_width
@@ -217,6 +220,7 @@ impl Render for BufferSearchBar {
div.children(supported_options.case.then(|| {
self.render_search_option_button(
SearchOptions::CASE_SENSITIVE,
focus_handle.clone(),
cx.listener(|this, _, cx| {
this.toggle_case_sensitive(&ToggleCaseSensitive, cx)
}),
@@ -225,6 +229,7 @@ impl Render for BufferSearchBar {
.children(supported_options.word.then(|| {
self.render_search_option_button(
SearchOptions::WHOLE_WORD,
focus_handle.clone(),
cx.listener(|this, _, cx| {
this.toggle_whole_word(&ToggleWholeWord, cx)
}),
@@ -233,6 +238,7 @@ impl Render for BufferSearchBar {
.children(supported_options.regex.then(|| {
self.render_search_option_button(
SearchOptions::REGEX,
focus_handle.clone(),
cx.listener(|this, _, cx| this.toggle_regex(&ToggleRegex, cx)),
)
}))
@@ -250,7 +256,17 @@ impl Render for BufferSearchBar {
}))
.selected(self.replace_enabled)
.size(ButtonSize::Compact)
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Toggle replace",
&ToggleReplace,
&focus_handle,
cx,
)
}
}),
)
})
.when(supported_options.selection, |this| {
@@ -268,8 +284,16 @@ impl Render for BufferSearchBar {
}))
.selected(self.selection_search_enabled)
.size(ButtonSize::Compact)
.tooltip(|cx| {
Tooltip::for_action("Toggle search selection", &ToggleSelection, cx)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Toggle Search Selection",
&ToggleSelection,
&focus_handle,
cx,
)
}
}),
)
})
@@ -280,21 +304,31 @@ impl Render for BufferSearchBar {
IconButton::new("select-all", ui::IconName::SelectAll)
.on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
.size(ButtonSize::Compact)
.tooltip(|cx| {
Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Select All Matches",
&SelectAllMatches,
&focus_handle,
cx,
)
}
}),
)
.child(render_nav_button(
ui::IconName::ChevronLeft,
self.active_match_index.is_some(),
"Select previous match",
"Select Previous Match",
&SelectPrevMatch,
focus_handle.clone(),
))
.child(render_nav_button(
ui::IconName::ChevronRight,
self.active_match_index.is_some(),
"Select next match",
"Select Next Match",
&SelectNextMatch,
focus_handle.clone(),
))
.when(!narrow_mode, |this| {
this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child(
@@ -335,8 +369,16 @@ impl Render for BufferSearchBar {
.flex_none()
.child(
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
.tooltip(move |cx| {
Tooltip::for_action("Replace next", &ReplaceNext, cx)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Replace Next Match",
&ReplaceNext,
&focus_handle,
cx,
)
}
})
.on_click(
cx.listener(|this, _, cx| this.replace_next(&ReplaceNext, cx)),
@@ -344,8 +386,16 @@ impl Render for BufferSearchBar {
)
.child(
IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
.tooltip(move |cx| {
Tooltip::for_action("Replace all", &ReplaceAll, cx)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Replace All Matches",
&ReplaceAll,
&focus_handle,
cx,
)
}
})
.on_click(
cx.listener(|this, _, cx| this.replace_all(&ReplaceAll, cx)),
@@ -392,7 +442,7 @@ impl Render for BufferSearchBar {
div.child(
IconButton::new(SharedString::from("Close"), IconName::Close)
.tooltip(move |cx| {
Tooltip::for_action("Close search bar", &Dismiss, cx)
Tooltip::for_action("Close Search Bar", &Dismiss, cx)
})
.on_click(cx.listener(|this, _: &ClickEvent, cx| {
this.dismiss(&Dismiss, cx)
@@ -719,10 +769,11 @@ impl BufferSearchBar {
fn render_search_option_button(
&self,
option: SearchOptions,
focus_handle: FocusHandle,
action: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
) -> impl IntoElement {
let is_active = self.search_options.contains(option);
option.as_button(is_active, action)
option.as_button(is_active, focus_handle, action)
}
pub fn focus_editor(&mut self, _: &FocusEditor, cx: &mut ViewContext<Self>) {
@@ -1122,6 +1173,7 @@ impl BufferSearchBar {
});
cx.focus(handle);
}
fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
if self.active_searchable_item.is_some() {
self.replace_enabled = !self.replace_enabled;
@@ -1134,6 +1186,7 @@ impl BufferSearchBar {
cx.notify();
}
}
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
let mut should_propagate = true;
if !self.dismissed && self.active_search.is_some() {
@@ -1161,6 +1214,7 @@ impl BufferSearchBar {
cx.stop_propagation();
}
}
pub fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
if !self.dismissed && self.active_search.is_some() {
if let Some(searchable_item) = self.active_searchable_item.as_ref() {

View File

@@ -1551,6 +1551,7 @@ impl Render for ProjectSearchBar {
return div();
};
let search = search.read(cx);
let focus_handle = search.focus_handle(cx);
let query_column = h_flex()
.flex_1()
@@ -1571,18 +1572,21 @@ impl Render for ProjectSearchBar {
h_flex()
.child(SearchOptions::CASE_SENSITIVE.as_button(
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
focus_handle.clone(),
cx.listener(|this, _, cx| {
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
}),
))
.child(SearchOptions::WHOLE_WORD.as_button(
self.is_option_enabled(SearchOptions::WHOLE_WORD, cx),
focus_handle.clone(),
cx.listener(|this, _, cx| {
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
}),
))
.child(SearchOptions::REGEX.as_button(
self.is_option_enabled(SearchOptions::REGEX, cx),
focus_handle.clone(),
cx.listener(|this, _, cx| {
this.toggle_search_option(SearchOptions::REGEX, cx);
}),
@@ -1603,7 +1607,17 @@ impl Render for ProjectSearchBar {
.map(|search| search.read(cx).filters_enabled)
.unwrap_or_default(),
)
.tooltip(|cx| Tooltip::for_action("Toggle filters", &ToggleFilters, cx)),
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Toggle filters",
&ToggleFilters,
&focus_handle,
cx,
)
}
}),
)
.child(
IconButton::new("project-search-toggle-replace", IconName::Replace)
@@ -1616,7 +1630,17 @@ impl Render for ProjectSearchBar {
.map(|search| search.read(cx).replace_enabled)
.unwrap_or_default(),
)
.tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx)),
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Toggle replace",
&ToggleReplace,
&focus_handle,
cx,
)
}
}),
),
);
@@ -1650,8 +1674,16 @@ impl Render for ProjectSearchBar {
})
}
}))
.tooltip(|cx| {
Tooltip::for_action("Go to previous match", &SelectPrevMatch, cx)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Go to previous match",
&SelectPrevMatch,
&focus_handle,
cx,
)
}
}),
)
.child(
@@ -1664,7 +1696,17 @@ impl Render for ProjectSearchBar {
})
}
}))
.tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)),
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Go to next match",
&SelectNextMatch,
&focus_handle,
cx,
)
}
}),
)
.child(
h_flex()
@@ -1702,6 +1744,7 @@ impl Render for ProjectSearchBar {
.border_color(cx.theme().colors().border)
.rounded_lg()
.child(self.render_text_input(&search.replacement_editor, cx));
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
let replace_actions = h_flex().when(search.replace_enabled, |this| {
this.child(
IconButton::new("project-search-replace-next", IconName::ReplaceNext)
@@ -1712,7 +1755,17 @@ impl Render for ProjectSearchBar {
})
}
}))
.tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)),
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Replace next match",
&ReplaceNext,
&focus_handle,
cx,
)
}
}),
)
.child(
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
@@ -1723,7 +1776,17 @@ impl Render for ProjectSearchBar {
})
}
}))
.tooltip(|cx| Tooltip::for_action("Replace all matches", &ReplaceAll, cx)),
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Replace all matches",
&ReplaceAll,
&focus_handle,
cx,
)
}
}),
)
});
h_flex()
@@ -1790,6 +1853,7 @@ impl Render for ProjectSearchBar {
search
.search_options
.contains(SearchOptions::INCLUDE_IGNORED),
focus_handle.clone(),
cx.listener(|this, _, cx| {
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
}),

View File

@@ -1,7 +1,7 @@
use bitflags::bitflags;
pub use buffer_search::BufferSearchBar;
use editor::SearchSettings;
use gpui::{actions, Action, AppContext, IntoElement};
use gpui::{actions, Action, AppContext, FocusHandle, IntoElement};
use project::search::SearchQuery;
pub use project_search::ProjectSearchView;
use ui::{prelude::*, Tooltip};
@@ -53,10 +53,10 @@ bitflags! {
impl SearchOptions {
pub fn label(&self) -> &'static str {
match *self {
SearchOptions::WHOLE_WORD => "Match whole words",
SearchOptions::CASE_SENSITIVE => "Match case sensitively",
SearchOptions::WHOLE_WORD => "Match Whole Words",
SearchOptions::CASE_SENSITIVE => "Match Case Sensitively",
SearchOptions::INCLUDE_IGNORED => "Also search files ignored by configuration",
SearchOptions::REGEX => "Use regular expressions",
SearchOptions::REGEX => "Use Regular Expressions",
_ => panic!("{:?} is not a named SearchOption", self),
}
}
@@ -106,6 +106,7 @@ impl SearchOptions {
pub fn as_button(
&self,
active: bool,
focus_handle: FocusHandle,
action: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static,
) -> impl IntoElement {
IconButton::new(self.label(), self.icon())
@@ -115,7 +116,7 @@ impl SearchOptions {
.tooltip({
let action = self.to_toggle_action();
let label = self.label();
move |cx| Tooltip::for_action(label, &*action, cx)
move |cx| Tooltip::for_action_in(label, &*action, &focus_handle, cx)
})
}
}

View File

@@ -1,4 +1,4 @@
use gpui::{Action, IntoElement};
use gpui::{Action, FocusHandle, IntoElement};
use ui::IconButton;
use ui::{prelude::*, Tooltip};
@@ -7,12 +7,13 @@ pub(super) fn render_nav_button(
active: bool,
tooltip: &'static str,
action: &'static dyn Action,
focus_handle: FocusHandle,
) -> impl IntoElement {
IconButton::new(
SharedString::from(format!("search-nav-button-{}", action.name())),
icon,
)
.on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
.tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
.tooltip(move |cx| Tooltip::for_action_in(tooltip, action, &focus_handle, cx))
.disabled(!active)
}

View File

@@ -46,7 +46,9 @@ pub enum ComponentStory {
impl ComponentStory {
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
match self {
Self::ApplicationMenu => cx.new_view(|_| title_bar::ApplicationMenuStory).into(),
Self::ApplicationMenu => cx
.new_view(|cx| title_bar::ApplicationMenuStory::new(cx))
.into(),
Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
Self::Button => cx.new_view(|_| ui::ButtonStory).into(),

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