Compare commits

..

57 Commits

Author SHA1 Message Date
Bennet Bo Fenner
23ff3fbeac assistant: Show copy code button when cursor is inside a code block
Co-Authored-by: Thorsten <thorsten@zed.dev>
2024-09-19 14:42:09 +02:00
thataboy
1723713dc2 Add ability to copy assistant code block to clipboard or insert into editor, without manual selection (#17853)
Some notes:

- You can put the cursor on the start or end line with triple backticks,
it doesn't actually have to be inside the block.
- Placing the cursor outside of a code block does nothing.
- Code blocks are determined by counting triple backticks pairs from
either start or end of buffer, and nothing else.
- If you manually select something, the selection takes precedence over
any code blocks.

Release Notes:

- Added the ability to copy surrounding code blocks in the assistant
panel into the clipboard, or inserting them directly into the editor,
without manually selecting. Place cursor anywhere in a code block
(marked by triple backticks) and use the `assistant::CopyCode` action
(`cmd-k c` / `ctrl-k c`) to copy to the clipboard, or the
`assistant::InsertIntoEditor` action (`cmd-<` / `ctrl-<`) to insert into
editor.

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Bennet <bennet@zed.dev>
2024-09-19 13:43:49 +02:00
Joseph T. Lyons
ca4980df02 Add system_id (#18040)
This PR adds `system_id` to telemetry, which is contained within a new
`global` database (accessible by any release channel of Zed on a single
system). This will help us get a more accurate understanding of user
count, instead of relying on `installationd_id`, which is different per
release channel. This doesn't solve the problem of a user with multiple
machines, but it gets us closer.

Release Notes:

- N/A
2024-09-19 07:20:27 -04:00
Danilo Leal
5e6d1814e5 Add stray UI tweaks on the task picker (#18059)
This PR adds tiny UI tweaks to the task picker. Just making sure it is
consistent with other pickers throughout Zed.

| Before | After |
|--------|--------|
| <img width="577" alt="Screenshot 2024-09-19 at 12 07 44 PM"
src="https://github.com/user-attachments/assets/c0f010a3-6e08-47ee-9997-9df2b203977c">
| <img width="577" alt="Screenshot 2024-09-19 at 12 07 09 PM"
src="https://github.com/user-attachments/assets/74baf191-4dd9-4765-b2fd-2390d4cb31c6">
|

Release Notes:

- N/A
2024-09-19 07:22:10 -03:00
Piotr Osiewicz
1b612108ba linux: Fix invalid check for denylisted dependencies (#18050)
Closes #ISSUE

Release Notes:

- N/A
2024-09-19 11:40:01 +02:00
hekmyr
c3f47b8040 vim: Fix increment/decrement command (#17644)
Improving vim increment and decrement command.

Closes: #16672

## Release Notes:

- vim: Improved edge-case handling for ctrl-a/ctrl-x

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-09-18 18:28:31 -06:00
Piotr Osiewicz
43e005e936 chore: Remove commented out code following 15446 (#18047)
Closes #ISSUE

Release Notes:

- N/A
2024-09-19 02:19:58 +02:00
Conrad Irwin
b43b800a54 More assistant events (#18032)
Release Notes:

- N/A
2024-09-18 18:07:39 -06:00
Marshall Bowers
eef44aff7f extension: Re-enable test_extension_store_with_test_extension test (#18046)
The `test_extension_store_with_test_extension` test was disabled in
#15446, which got merged before re-enabling the test.

This PR re-enables that test.

Release Notes:

- N/A
2024-09-18 19:48:34 -04:00
Max Brunsfeld
106ca5076f Fix leak of LMDB connection in semantic index (#17992)
Apparently, to close LMDB's file descriptors when using the `heed`
library, you need to explicitly call `prepare_for_closing`.

Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Co-authored-by: Jason <jason@zed.dev>
2024-09-18 16:43:59 -07:00
Marshall Bowers
2cd9a88f53 Clean up after isahc_http_client introduction (#18045)
This PR does some clean up after #15446.

Release Notes:

- N/A
2024-09-18 19:39:15 -04:00
Peter Tripp
a62e8f6396 ci: Explicitly set cache-provider for swatinem/rust-cache (#18034)
- Switches the Cache Dependencies step (`swatinem/rust-cache`) of Linux
tests to use buildjet as `cache-provider`. Explicitly add 'github' (the
default cache provider) to other uses of `swatinem/rust-cache` for
consistency.

Release Notes:

- N/A
2024-09-18 18:05:30 -04:00
Piotr Osiewicz
2c8a6ee7cc remote_server: Remove dependency on libssl and libcrypto (#15446)
Fixes: #15599
Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
2024-09-18 23:29:34 +02:00
renovate[bot]
9016de5d63 Update Rust crate anyhow to v1.0.89 (#18031)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [anyhow](https://redirect.github.com/dtolnay/anyhow) |
workspace.dependencies | patch | `1.0.86` -> `1.0.89` |

---

### Release Notes

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

###
[`v1.0.89`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.89)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.88...1.0.89)

- Make anyhow::Error's `UnwindSafe` and `RefUnwindSafe` impl
consistently available between versions of Rust newer and older than
1.72
([#&#8203;386](https://redirect.github.com/dtolnay/anyhow/issues/386))

###
[`v1.0.88`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.88)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.87...1.0.88)

-   Documentation improvements

###
[`v1.0.87`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.87)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.86...1.0.87)

- Support more APIs, including `Error::new` and `Error::chain`, in
no-std mode on Rust 1.81+
([#&#8203;383](https://redirect.github.com/dtolnay/anyhow/issues/383))

</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-18 15:56:40 -04:00
Peter Tripp
97f5fcf8e6 Fix nightly linux x86 build (#18029)
Makes our nightly script for Linux x86 (broken) match the steps for Linux ARM (working).
2024-09-18 15:18:29 -04:00
renovate[bot]
71b6f739cd Pin actions/checkout action to 692973e (#18030)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) |
action | pinDigest | -> `692973e` |

---

### 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-18 15:08:19 -04:00
Peter Tripp
30ef7e62bf Fix arm buildjet (#18023)
Run `apt-get update` before `apt-get install` on Linux. Hopefully will fix building on Linux Arm.
2024-09-18 14:28:00 -04:00
Marshall Bowers
97dc1d193f Use @tag.doctype for HTML doctype highlights (#18024)
This PR updates the following extensions to use the `@tag.doctype`
selector for highlighting HTML doctypes:

- Astro
- Elixir (HEEx)
- HTML

Additionally, it also changes the base selector for HTML tags from
`@keyword` to `@tag`.

| Before | After |
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
| <img width="308" alt="Screenshot 2024-09-18 at 2 04 41 PM"
src="https://github.com/user-attachments/assets/818d98ba-fce7-4683-b67f-61c86543831c">
| <img width="358" alt="Screenshot 2024-09-18 at 2 05 00 PM"
src="https://github.com/user-attachments/assets/5071db7c-e0bf-44df-8959-38275833833b">
|

Extracted this from https://github.com/zed-industries/zed/pull/16723.

Release Notes:

- N/A

---------

Co-authored-by: 狐狸 <134658521+Huliiiiii@users.noreply.github.com>
2024-09-18 14:24:09 -04:00
Peter Tripp
772bda54a2 Move remaining self-hosted jobs to BuildJet (#18018) 2024-09-18 13:35:55 -04:00
Conrad Irwin
fb7a7a564a ssh remoting: open settings locally (#18020)
Release Notes:

- ssh remoting: Open settings files in a non-remote window.
2024-09-18 11:15:54 -06:00
Conrad Irwin
826777a257 Tidy up LSP (#17973)
Release Notes:

- N/A
2024-09-18 11:15:46 -06:00
Marek Fajkus
eda7e88fd4 nix: Fix (potential) glibc errors in dev shell (#17974)
Previously the rustc and cargo did were not declared dependencies
supplied to devshell. This means that shell relied some impure cargo and
rustc version found in the system. This lead to issues with GLIBC
version on systems which have different GLIBC version globally.

This package exposes nixpkgs rustc and cargo version into the shell
preventing issues with incompatibility.

Release Notes:

- N/A
2024-09-18 12:51:11 -04:00
Danilo Leal
a7977aa64d Tweak multibuffer header padding (#18011) 2024-09-18 17:18:56 +02:00
Marshall Bowers
373a17acf4 Add ability to display backgrounds for inlay hints (#18010)
This PR adds the ability to display backgrounds for inlay hints within
the editor.

This is controlled by the new `inlay_hints.show_background` setting.
This setting defaults to `false`.

To enable the setting, add the following to your `settings.json`:

```json
{
  "inlay_hints": {
    "enabled": true,
    "show_background": true
  }
}
```

When enabled, the inlay hint backgrounds will use the `hint.background`
color from the theme.

| Disabled | Enabled |
|
--------------------------------------------------------------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------------------------------------------------------------
|
| <img width="1624" alt="Screenshot 2024-09-17 at 4 21 53 PM"
src="https://github.com/user-attachments/assets/5534d09b-1e22-4c6f-9d82-314796ed7d22">
| <img width="1624" alt="Screenshot 2024-09-17 at 4 21 43 PM"
src="https://github.com/user-attachments/assets/6ec58cde-6115-4db4-be95-97c5f2f54b2d">
|

Related issues:

- #12485
- #17392

Release Notes:

- Added an `inlay_hints.show_background` setting to allow displaying
backgrounds for inlay hints in the editor.
  - This setting defaults to `false`.
- If enabled, the inlay hint backgrounds will use the `hint.background`
color from the theme.
2024-09-18 11:11:38 -04:00
Joseph T. Lyons
425c8f8c3e Alphabetize actions (#18007)
Drive-by maintenance PR while working on another PR.

Release Notes:

- N/A
2024-09-18 10:42:17 -04:00
Danilo Leal
84f2e0ee37 Use buffer font in the terminal inline assistant (#18009)
This PR is a follow up to
https://github.com/zed-industries/zed/pull/17875.

Release Notes:

- N/A
2024-09-18 11:36:32 -03:00
Conrad Irwin
1a62396b1e vim: Fix gv after indent/toggle comments (#17986)
Release Notes:

- vim: Fixed `gv` after > and < in visual mode
2024-09-18 08:19:06 -06:00
ensi
3ac201e448 gpui: Improve underline appearance (#17586) 2024-09-18 09:32:37 -04:00
Danilo Leal
3b153a54c2 docs: Improve dark mode syntax highlighting (#18002)
This PR introduces [GitHub
Light](https://github.com/highlightjs/highlight.js/blob/main/src/styles/github.css)
and [GitHub
Dark](https://github.com/highlightjs/highlight.js/blob/main/src/styles/github-dark.css)
as the syntax highlighting themes for the corresponding modes.

Release Notes:

- N/A
2024-09-18 09:14:03 -03:00
Danilo Leal
430ce073d2 docs: Improve warning callout docs (#17997)
This PR is a quick follow-up to
https://github.com/zed-industries/zed/pull/1795. 😊

Release Notes:

- N/A
2024-09-18 07:36:02 -03:00
Danilo Leal
a149a50946 docs: Fix links on the Telemetry page (#17995)
This PR tweaks some broken links in the Telemetry page as well as
capitalizing instances of "Zed".

Release Notes:

- N/A
2024-09-18 07:34:51 -03:00
Danilo Leal
f68f4ab982 docs: Add tweaks to the REPL page (#18000)
Just capitalizing some things, making sure URLs are clickable links, and
using the note blockquote callout when appropriate.

Release Notes:

- N/A
2024-09-18 07:34:39 -03:00
Thorsten Ball
aae26ee33d go: Fix tasks when running tests/benchs in packages (#17998)
Turns out that #17645 reintroduced another regression and didn't catch
all the regressions in #17108.

Releases Notes:

- Fixed Go tasks not working properly when running tests or benchmarks
in subfolders/packages.

Co-authored-by: Piotr <piotr@zed.dev>
2024-09-18 12:34:10 +02:00
Thorsten Ball
550ceec549 docs: Update Ruby docs to provide more complete examples (#17987)
Closes #17917

Release Notes:

- N/A
2024-09-18 10:04:13 +02:00
Thorsten Ball
d4e10dfba3 docs: Update rust-analyzer docs (#17988)
Release Notes:

- N/A
2024-09-18 10:04:02 +02:00
Junkui Zhang
2699fa8d4a windows: Fix tailwind-language-server (#17778)
Closes #17741

I'm not sure why, but ever since `tailwind` was upgraded to `0.24`,
there have been occasional errors indicating that the `.ps1` file could
not be found. After reviewing the `.ps1` script, it appears that it
simply starts the server using `node`. This PR directly using the method
from the script to start the server with `node`.


Co-authored-by: Anay <me@anayparaswani.dev>


Release Notes:

- N/A

---------

Co-authored-by: Anay <me@anayparaswani.dev>
2024-09-17 21:59:19 -06:00
jvmncs
8e30229ec9 Fix nix shell (#17982)
Recently `cmake` was added as a build-time dependency to the wasm
runtime. This adds that dependency to our nix shell env.

Release Notes:

- N/A
2024-09-17 22:09:59 -04:00
Antonio Scandurra
2e72fd210a Replace Default trait bound with a zero function on Summary/Dimension (#17975)
This lets us provide a context when constructing the zero value. We need
it so we can require anchors to be associated with a buffer id, which
we're doing as part of simplifying the multibuffer API.

Release Notes:

- N/A

Co-authored-by: Nathan <nathan@zed.dev>
2024-09-17 19:43:59 -06:00
Marshall Bowers
4d074fc737 editor: Fix rewrap with a non-empty selection (#17980)
This PR fixes an issue where rewrapping would not occur with a non-empty
selection.

It is only the expansion to neighboring lines that needs to be gated by
an empty selection.

Release Notes:

- N/A
2024-09-17 19:20:45 -04:00
Junkui Zhang
fbb402ef12 windows: Remove the use of DispatcherQueue and fix FileSaveDialog unresponsive issue (#17946)
Closes #17069, closes #12410


With the help of @kennykerr (Creator of C++/WinRT and the crate
`windows-rs`, Engineer on the Windows team at Microsoft) and @riverar
(Windows Development expert), we discovered that this bug only occurs
when an IME with a candidate window, such as Microsoft Pinyin IME, is
active. In this case, the `FileSaveDialog` becomes unresponsive—while
the dialog itself appears to be functioning, it doesn't accept any mouse
or keyboard input.

After a period of debugging and testing, I found that this issue only
arises when using `DispatcherQueue` to dispatch runnables on the UI
thread. After @kennykerr’s further investigation, Kenny identified that
this is a bug with `DispatcherQueue`, and he recommended to avoid using
`DispatcherQueue`. Given the uncertainty about whether Microsoft will
address this bug in the foreseeable future, I have removed the use of
`DispatcherQueue`.

Co-authored-by: Kenny <kenny@kennykerr.ca>

Release Notes:

- N/A

---------

Co-authored-by: Kenny <kenny@kennykerr.ca>
2024-09-17 15:45:08 -07:00
Mikayla Maki
56f9e4c7b3 Remove visible 'TBD' from docs (#17979)
Release Notes:

- N/A
2024-09-17 15:39:44 -07:00
Conrad Irwin
8e45bf71ca Refactor prettier (#17977)
In preparation for making formatting work on ssh remotes

Release Notes:

- N/A

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-09-17 15:37:56 -07:00
Marshall Bowers
db18f7a2b0 rust: Fix doc comment highlighting (#17976)
This PR fixes an issue where `/` and `!` in Rust doc comments were being
incorrectly highlighted as operators after #17734.

We solve this by removing them from the operators list and using more
scoped queries to highlight them.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-09-17 18:32:22 -04:00
Graham Taylor
e7912370e6 perplexity: Remove duplicate step and fix numbering in README (#17978)
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-17 18:31:06 -04:00
Marek Fajkus
51faf4a1cd Add missing cmake dependency to Nix build (#17968)
cmake is required during build of dependecies and thus needs to be
supplied in nativeBuildInputs (dependecies required for build not during
runtime).

This fixes (sandboxed) nix builds of the project.

Release Notes:

- N/A
2024-09-17 16:28:52 -04:00
Peter Tripp
bdca342cdc Fix "view release notes" on dev/nightly builds (#17967) 2024-09-17 16:28:09 -04:00
Conrad Irwin
8cc6df573c SshLspAdapterDelegate (#17965)
Release Notes:

- N/A
2024-09-17 14:13:37 -06:00
renovate[bot]
7814dd0301 Update Rust crate sysinfo to 0.31.0 (#17733)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sysinfo](https://redirect.github.com/GuillaumeGomez/sysinfo) |
workspace.dependencies | minor | `0.30.7` -> `0.31.0` |

---

### Release Notes

<details>
<summary>GuillaumeGomez/sysinfo (sysinfo)</summary>

###
[`v0.31.4`](https://redirect.github.com/GuillaumeGomez/sysinfo/blob/HEAD/CHANGELOG.md#0314)

[Compare
Source](https://redirect.github.com/GuillaumeGomez/sysinfo/compare/v0.31.3...v0.31.4)

-   macOS: Force memory cleanup in disk list retrieval.

###
[`v0.31.3`](https://redirect.github.com/GuillaumeGomez/sysinfo/blob/HEAD/CHANGELOG.md#0313)

[Compare
Source](https://redirect.github.com/GuillaumeGomez/sysinfo/compare/v0.31.2...v0.31.3)

-   Raspberry Pi: Fix temperature retrieval.

###
[`v0.31.2`](https://redirect.github.com/GuillaumeGomez/sysinfo/blob/HEAD/CHANGELOG.md#0312)

[Compare
Source](https://redirect.github.com/GuillaumeGomez/sysinfo/compare/v0.31.1...v0.31.2)

-   Remove `bstr` dependency (needed for rustc development).

###
[`v0.31.1`](https://redirect.github.com/GuillaumeGomez/sysinfo/blob/HEAD/CHANGELOG.md#0311)

[Compare
Source](https://redirect.github.com/GuillaumeGomez/sysinfo/compare/v0.31.0...v0.31.1)

-   Downgrade version of `memchr` (needed for rustc development).

###
[`v0.31.0`](https://redirect.github.com/GuillaumeGomez/sysinfo/blob/HEAD/CHANGELOG.md#0310)

[Compare
Source](https://redirect.github.com/GuillaumeGomez/sysinfo/compare/v0.30.13...v0.31.0)

-   Split crate in features to only enable what you need.
- Remove `System::refresh_process`, `System::refresh_process_specifics`
and `System::refresh_pids`
    methods.
- Add new argument of type `ProcessesToUpdate` to
`System::refresh_processes` and `System::refresh_processes_specifics`
methods.
-   Add new `NetworkData::ip_networks` method.
-   Add new `System::refresh_cpu_list` method.
-   Global CPU now only contains CPU usage.
-   Rename `TermalSensorType` to `ThermalSensorType`.
-   Process names is now an `OsString`.
-   Remove `System::global_cpu_info`.
-   Add `System::global_cpu_usage`.
- macOS: Fix invalid CPU computation when single processes are refreshed
one after the other.
-   Windows: Fix virtual memory computation.
-   Windows: Fix WoW64 parent process refresh.
-   Linux: Retrieve RSS (Resident Set Size) memory for cgroups.

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

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-09-17 21:50:37 +02:00
Stanislav Alekseev
8a6c65c63b Allow task context providers to access project env (#17964)
Closes #13106

Release Notes:

- Task context providers now have access to the local shell environment,
allowing local rust tool installations to work

Before:
<img width="1136" alt="Screenshot 2024-09-17 at 22 09 38"
src="https://github.com/user-attachments/assets/7d6c5606-4820-4f6f-92d1-c3d314b9ab42">

After:
<img width="1136" alt="Screenshot 2024-09-17 at 22 09 58"
src="https://github.com/user-attachments/assets/a962e607-15f5-44ce-b53e-a0dbe135f2d8">
2024-09-17 21:49:12 +02:00
Max Brunsfeld
d3d3a093b4 Add an eval binary that evaluates our semantic index against CodeSearchNet (#17375)
This PR is the beginning of an evaluation framework for our AI features.
Right now, we're evaluating our semantic search feature against the
[CodeSearchNet](https://github.com/github/CodeSearchNet) code search
dataset. This dataset is very limited (for the most part, only 1 known
good search result per repo) but it has surfaced some problems with our
search already.

Release Notes:

- N/A

---------

Co-authored-by: Jason <jason@zed.dev>
Co-authored-by: Jason Mancuso <7891333+jvmncs@users.noreply.github.com>
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
2024-09-17 12:44:33 -07:00
Marshall Bowers
06a13c2983 svelte: Bump to v0.2.0 (#17962)
This PR bumps the Svelte extension to v0.2.0.

Changes:

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

Release Notes:

- N/A
2024-09-17 15:33:28 -04:00
Peter Tripp
c28b22d1cf Update typos-cli to v1.24.6. Add scripts/check-spelling. Fix typos (#17961) 2024-09-17 15:08:14 -04:00
Mikayla Maki
447a5d6e6e Fix the rendering of warning text in our docs (#17958)
cc: @danilo-leal 

Before:

<img width="753" alt="Screenshot 2024-09-17 at 10 53 13 AM"
src="https://github.com/user-attachments/assets/43a2ef89-4a90-46d4-9e90-350fdd1b46bb">

After:

<img width="759" alt="Screenshot 2024-09-17 at 10 53 35 AM"
src="https://github.com/user-attachments/assets/49f2a250-d339-4f61-afda-3ed87181b018">

Light mode:

<img width="757" alt="Screenshot 2024-09-17 at 10 54 17 AM"
src="https://github.com/user-attachments/assets/4d425e9b-3f97-44c4-ba86-d84dc7349060">

Release Notes:

- N/A
2024-09-17 11:35:45 -07:00
Marshall Bowers
869a72bb3f ruff: Bump to v0.1.0 (#17960)
This PR bumps the Ruff extension to v0.1.0.

Changes:

- https://github.com/zed-industries/zed/pull/15852
- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/17883

Release Notes:

- N/A
2024-09-17 14:21:06 -04:00
Peter Tripp
ab7a7d3480 docs: Mention how to open the Prompt Library (#17957) 2024-09-17 14:12:11 -04:00
ClanEver
fc43b21e78 ruff: Fix wrong Ruff path on Windows (#17883)
Log:

2024-09-16T22:32:04.7715712+08:00 [ERROR] failed to start language
server "ruff": failed to spawn command. path:
"...\\AppData\\Local\\Zed\\extensions\\work\\ruff\\ruff-0.6.5\\ruff-x86_64-pc-windows-msvc\\ruff"

The right path:
`...\\AppData\\Local\\Zed\\extensions\\work\\ruff\\ruff-0.6.5\\ruff.exe`

Release Notes:

- N/A
2024-09-17 14:10:37 -04:00
Richard Feldman
e6c4076ef0 Add cmake to dev build instructions (#17943)
Release Notes:

- N/A
2024-09-17 14:07:50 -04:00
186 changed files with 5171 additions and 9218 deletions

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
fetch-depth: 0

View File

@@ -15,8 +15,7 @@ concurrency:
jobs:
bump_patch_version:
runs-on:
- self-hosted
- test
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4

View File

@@ -39,16 +39,7 @@ jobs:
run: git clean -df
- name: Check spelling
run: |
if ! cargo install --list | grep "typos-cli v$TYPOS_CLI_VERSION" > /dev/null; then
echo "Installing typos-cli@$TYPOS_CLI_VERSION..."
cargo install "typos-cli@$TYPOS_CLI_VERSION"
else
echo "typos-cli@$TYPOS_CLI_VERSION is already installed."
fi
typos
env:
TYPOS_CLI_VERSION: "1.23.3"
run: script/check-spelling
- name: Run style checks
uses: ./.github/actions/check_style
@@ -110,7 +101,7 @@ jobs:
timeout-minutes: 60
name: (Linux) Run Clippy and tests
runs-on:
- hosted-linux-x86-1
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -120,6 +111,12 @@ jobs:
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
@@ -147,6 +144,7 @@ jobs:
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
- name: cargo clippy
# Windows can't run shell scripts, so we need to use `cargo xtask`.
@@ -273,7 +271,7 @@ jobs:
timeout-minutes: 60
name: Create a Linux bundle
runs-on:
- hosted-linux-x86-1
- buildjet-16vcpu-ubuntu-2204
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
needs: [linux_tests]
env:
@@ -288,9 +286,6 @@ jobs:
- name: Install Linux dependencies
run: ./script/linux
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
@@ -344,7 +339,7 @@ jobs:
timeout-minutes: 60
name: Create arm64 Linux bundle
runs-on:
- hosted-linux-arm-1
- buildjet-16vcpu-ubuntu-2204-arm
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
needs: [linux_tests]
env:
@@ -359,9 +354,6 @@ jobs:
- name: Install Linux dependencies
run: ./script/linux
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |

View File

@@ -61,8 +61,7 @@ jobs:
- style
- tests
runs-on:
- self-hosted
- deploy
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -89,8 +88,7 @@ jobs:
needs:
- publish
runs-on:
- self-hosted
- deploy
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Sign into Kubernetes

View File

@@ -24,6 +24,7 @@ jobs:
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
- name: Configure linux
shell: bash -euxo pipefail {0}

View File

@@ -19,8 +19,7 @@ jobs:
tests:
name: Run randomized tests
runs-on:
- self-hosted
- randomized-tests
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Install Node
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4

View File

@@ -97,8 +97,7 @@ jobs:
name: Create a Linux *.tar.gz bundle for x86
if: github.repository_owner == 'zed-industries'
runs-on:
- self-hosted
- deploy
- buildjet-16vcpu-ubuntu-2204
needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
@@ -114,6 +113,12 @@ jobs:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Linux dependencies
run: ./script/linux
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Set release channel to nightly
run: |
set -euo pipefail

258
Cargo.lock generated
View File

@@ -263,9 +263,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
[[package]]
name = "anyhow"
version = "1.0.86"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "approx"
@@ -402,6 +402,7 @@ dependencies = [
"indoc",
"language",
"language_model",
"languages",
"log",
"markdown",
"menu",
@@ -436,6 +437,7 @@ dependencies = [
"text",
"theme",
"toml 0.8.19",
"tree-sitter-md",
"ui",
"unindent",
"util",
@@ -876,6 +878,20 @@ version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-tls"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795"
dependencies = [
"futures-core",
"futures-io",
"rustls 0.20.9",
"rustls-pemfile 1.0.4",
"webpki",
"webpki-roots 0.22.6",
]
[[package]]
name = "async-trait"
version = "0.1.81"
@@ -893,8 +909,8 @@ version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc"
dependencies = [
"async-native-tls",
"async-std",
"async-tls",
"futures-io",
"futures-util",
"log",
@@ -981,7 +997,6 @@ dependencies = [
"editor",
"gpui",
"http_client",
"isahc",
"log",
"markdown_preview",
"menu",
@@ -1049,7 +1064,7 @@ dependencies = [
"fastrand 2.1.1",
"hex",
"http 0.2.12",
"ring",
"ring 0.17.8",
"time",
"tokio",
"tracing",
@@ -1218,7 +1233,7 @@ dependencies = [
"once_cell",
"p256",
"percent-encoding",
"ring",
"ring 0.17.8",
"sha2",
"subtle",
"time",
@@ -1331,7 +1346,7 @@ dependencies = [
"once_cell",
"pin-project-lite",
"pin-utils",
"rustls",
"rustls 0.21.12",
"tokio",
"tracing",
]
@@ -2405,6 +2420,8 @@ dependencies = [
"rand 0.8.5",
"release_channel",
"rpc",
"rustls 0.20.9",
"rustls-native-certs 0.8.0",
"schemars",
"serde",
"serde_json",
@@ -2553,6 +2570,7 @@ dependencies = [
"http_client",
"hyper",
"indoc",
"isahc_http_client",
"jsonwebtoken",
"language",
"language_model",
@@ -4000,6 +4018,34 @@ dependencies = [
"num-traits",
]
[[package]]
name = "evals"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"client",
"clock",
"collections",
"env_logger",
"feature_flags",
"fs",
"git",
"gpui",
"http_client",
"isahc_http_client",
"language",
"languages",
"node_runtime",
"open_ai",
"project",
"semantic_index",
"serde",
"serde_json",
"settings",
"smol",
]
[[package]]
name = "event-listener"
version = "2.5.3"
@@ -4083,6 +4129,7 @@ dependencies = [
"http_client",
"indexed_docs",
"isahc",
"isahc_http_client",
"language",
"log",
"lsp",
@@ -4121,7 +4168,7 @@ dependencies = [
"env_logger",
"extension",
"fs",
"http_client",
"isahc_http_client",
"language",
"log",
"rpc",
@@ -4368,7 +4415,7 @@ dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"spin",
"spin 0.9.8",
]
[[package]]
@@ -4877,7 +4924,6 @@ dependencies = [
"git",
"gpui",
"http_client",
"isahc",
"pretty_assertions",
"regex",
"serde",
@@ -5510,12 +5556,11 @@ dependencies = [
"anyhow",
"derive_more",
"futures 0.3.30",
"futures-lite 1.13.0",
"http 1.1.0",
"isahc",
"http 0.2.12",
"log",
"serde",
"serde_json",
"smol",
"url",
]
@@ -5577,8 +5622,8 @@ dependencies = [
"http 0.2.12",
"hyper",
"log",
"rustls",
"rustls-native-certs",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
"tokio",
"tokio-rustls",
]
@@ -5990,6 +6035,17 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "isahc_http_client"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.30",
"http_client",
"isahc",
"util",
]
[[package]]
name = "itertools"
version = "0.10.5"
@@ -6094,7 +6150,7 @@ dependencies = [
"base64 0.21.7",
"js-sys",
"pem",
"ring",
"ring 0.17.8",
"serde",
"serde_json",
"simple_asn1",
@@ -6345,7 +6401,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin",
"spin 0.9.8",
]
[[package]]
@@ -7005,32 +7061,6 @@ dependencies = [
"util",
]
[[package]]
name = "multi_buffer2"
version = "0.1.0"
dependencies = [
"anyhow",
"clock",
"collections",
"ctor",
"env_logger",
"futures 0.3.30",
"git",
"gpui",
"itertools 0.13.0",
"language",
"log",
"parking_lot",
"rand 0.8.5",
"serde",
"settings",
"smallvec",
"sum_tree",
"text",
"theme",
"util",
]
[[package]]
name = "multimap"
version = "0.8.3"
@@ -7482,7 +7512,6 @@ dependencies = [
"anyhow",
"futures 0.3.30",
"http_client",
"isahc",
"schemars",
"serde",
"serde_json",
@@ -9174,7 +9203,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"serde_urlencoded",
@@ -9238,6 +9267,21 @@ dependencies = [
"util",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin 0.5.2",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]]
name = "ring"
version = "0.17.8"
@@ -9248,8 +9292,8 @@ dependencies = [
"cfg-if",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
"spin 0.9.8",
"untrusted 0.9.0",
"windows-sys 0.52.0",
]
@@ -9405,7 +9449,7 @@ dependencies = [
"futures 0.3.30",
"glob",
"rand 0.8.5",
"ring",
"ring 0.17.8",
"serde",
"serde_json",
"shellexpand 3.1.0",
@@ -9526,6 +9570,18 @@ dependencies = [
"rustix 0.38.35",
]
[[package]]
name = "rustls"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
dependencies = [
"log",
"ring 0.16.20",
"sct",
"webpki",
]
[[package]]
name = "rustls"
version = "0.21.12"
@@ -9533,7 +9589,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"ring 0.17.8",
"rustls-webpki",
"sct",
]
@@ -9545,7 +9601,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pemfile 1.0.4",
"schannel",
"security-framework",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
dependencies = [
"openssl-probe",
"rustls-pemfile 2.1.3",
"rustls-pki-types",
"schannel",
"security-framework",
]
@@ -9559,14 +9628,30 @@ dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pemfile"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
dependencies = [
"base64 0.22.1",
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
"ring 0.17.8",
"untrusted 0.9.0",
]
[[package]]
@@ -9680,8 +9765,8 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
"ring 0.17.8",
"untrusted 0.9.0",
]
[[package]]
@@ -9877,6 +9962,7 @@ dependencies = [
"gpui",
"heed",
"http_client",
"isahc_http_client",
"language",
"language_model",
"languages",
@@ -10436,6 +10522,12 @@ dependencies = [
"smallvec",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
@@ -10558,8 +10650,8 @@ dependencies = [
"paste",
"percent-encoding",
"rust_decimal",
"rustls",
"rustls-pemfile",
"rustls 0.21.12",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"sha2",
@@ -10572,7 +10664,7 @@ dependencies = [
"tracing",
"url",
"uuid",
"webpki-roots",
"webpki-roots 0.25.4",
]
[[package]]
@@ -11084,17 +11176,16 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.30.13"
version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"memchr",
"ntapi",
"once_cell",
"rayon",
"windows 0.52.0",
"windows 0.54.0",
]
[[package]]
@@ -11705,7 +11796,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls",
"rustls 0.21.12",
"tokio",
]
@@ -12232,7 +12323,6 @@ dependencies = [
"http 0.2.12",
"httparse",
"log",
"native-tls",
"rand 0.8.5",
"sha1",
"thiserror",
@@ -12417,6 +12507,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -13271,6 +13367,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring 0.17.8",
"untrusted 0.9.0",
]
[[package]]
name = "webpki-roots"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki",
]
[[package]]
name = "webpki-roots"
version = "0.25.4"
@@ -13442,16 +13557,6 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core 0.52.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.54.0"
@@ -14315,6 +14420,7 @@ dependencies = [
"inline_completion_button",
"install_cli",
"isahc",
"isahc_http_client",
"journal",
"language",
"language_model",
@@ -14542,7 +14648,7 @@ dependencies = [
[[package]]
name = "zed_ruff"
version = "0.0.2"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -14557,7 +14663,7 @@ dependencies = [
[[package]]
name = "zed_svelte"
version = "0.1.1"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0",
]

View File

@@ -27,6 +27,7 @@ members = [
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
"crates/evals",
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
@@ -51,6 +52,7 @@ members = [
"crates/indexed_docs",
"crates/inline_completion_button",
"crates/install_cli",
"crates/isahc_http_client",
"crates/journal",
"crates/language",
"crates/language_model",
@@ -65,7 +67,6 @@ members = [
"crates/media",
"crates/menu",
"crates/multi_buffer",
"crates/multi_buffer2",
"crates/node_runtime",
"crates/notifications",
"crates/ollama",
@@ -226,6 +227,7 @@ image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" }
isahc_http_client = { path = "crates/isahc_http_client" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_model = { path = "crates/language_model" }
@@ -394,6 +396,8 @@ runtimelib = { version = "0.15", default-features = false, features = [
] }
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustls = "0.20.3"
rustls-native-certs = "0.8.0"
schemars = { version = "0.8", features = ["impl_json_schema"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
@@ -416,7 +420,7 @@ strsim = "0.11"
strum = { version = "0.25.0", features = ["derive"] }
subtle = "2.5.0"
sys-locale = "0.3.1"
sysinfo = "0.30.7"
sysinfo = "0.31.0"
tempfile = "3.9.0"
thiserror = "1.0.29"
tiktoken-rs = "0.5.9"
@@ -490,7 +494,6 @@ features = [
"implement",
"Foundation_Numerics",
"Storage",
"System",
"System_Threading",
"UI_ViewManagement",
"Wdk_System_SystemServices",
@@ -521,6 +524,7 @@ features = [
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging",
]

View File

@@ -166,6 +166,7 @@
{
"context": "AssistantPanel",
"bindings": {
"ctrl-k c": "assistant::CopyCode",
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPrevMatch",
"alt-m": "assistant::ToggleModelSelector",

View File

@@ -188,6 +188,7 @@
{
"context": "AssistantPanel",
"bindings": {
"cmd-k c": "assistant::CopyCode",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch",
"alt-m": "assistant::ToggleModelSelector",

View File

@@ -318,6 +318,10 @@
"show_parameter_hints": true,
// Corresponds to null/None LSP hint type value.
"show_other_hints": true,
// Whether to show a background for inlay hints.
//
// If set to `true`, the background will use the `hint.background` color from the current theme.
"show_background": false,
// Time to wait after editing the buffer, before requesting the hints,
// set to 0 to disable debouncing.
"edit_debounce_ms": 700,

View File

@@ -94,9 +94,11 @@ editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
language = { workspace = true, features = ["test-support"] }
language_model = { workspace = true, features = ["test-support"] }
languages = { workspace = true, features = ["test-support"] }
log.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
serde_json_lenient.workspace = true
text = { workspace = true, features = ["test-support"] }
tree-sitter-md.workspace = true
unindent.workspace = true

View File

@@ -58,6 +58,7 @@ actions!(
[
Assist,
Split,
CopyCode,
CycleMessageRole,
QuoteSelection,
InsertIntoEditor,

View File

@@ -12,11 +12,11 @@ use crate::{
slash_command_picker,
terminal_inline_assistant::TerminalInlineAssistant,
Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
ContextStoreEvent, CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId,
InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId, MessageMetadata,
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand,
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split,
ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary,
InlineAssistId, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId,
MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext,
PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, WorkflowStepResolution,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
@@ -45,7 +45,8 @@ use gpui::{
};
use indexed_docs::IndexedDocsStore;
use language::{
language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
language_settings::SoftWrap, BufferSnapshot, Capability, LanguageRegistry, LspAdapterDelegate,
ToOffset,
};
use language_model::{
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
@@ -54,8 +55,9 @@ use language_model::{
use language_model::{LanguageModelImage, LanguageModelToolUse};
use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate};
use project::lsp_store::ProjectLspAdapterDelegate;
use project::lsp_store::LocalLspAdapterDelegate;
use project::{Project, Worktree};
use rope::Point;
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings};
@@ -81,9 +83,10 @@ use util::{maybe, ResultExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
item::{self, FollowableItem, Item, ItemHandle},
notifications::NotificationId,
pane::{self, SaveIntent},
searchable::{SearchEvent, SearchableItem},
DraggedSelection, Pane, Save, ShowConfiguration, ToggleZoom, ToolbarItemEvent,
DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent,
ToolbarItemLocation, ToolbarItemView, Workspace,
};
use workspace::{searchable::SearchableItemHandle, DraggedTab};
@@ -105,6 +108,7 @@ pub fn init(cx: &mut AppContext) {
.register_action(AssistantPanel::inline_assist)
.register_action(ContextEditor::quote_selection)
.register_action(ContextEditor::insert_selection)
.register_action(ContextEditor::copy_code)
.register_action(ContextEditor::insert_dragged_files)
.register_action(AssistantPanel::show_configuration)
.register_action(AssistantPanel::create_new_context);
@@ -1072,6 +1076,13 @@ impl AssistantPanel {
self.show_updated_summary(&context_editor, cx);
cx.notify()
}
EditorEvent::SelectionsChanged { local } => {
if *local {
context_editor.update(cx, |this, cx| {
this.update_code_fence_blocks(cx);
})
}
}
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
_ => {}
}
@@ -1501,6 +1512,7 @@ pub struct ContextEditor {
editor: View<Editor>,
blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
image_blocks: HashSet<CustomBlockId>,
code_fence_blocks: HashSet<CustomBlockId>,
scroll_position: Option<ScrollPosition>,
remote_id: Option<workspace::ViewId>,
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
@@ -1569,6 +1581,7 @@ impl ContextEditor {
lsp_adapter_delegate,
blocks: Default::default(),
image_blocks: Default::default(),
code_fence_blocks: Default::default(),
scroll_position: None,
remote_id: None,
fs,
@@ -3100,6 +3113,40 @@ impl ContextEditor {
});
}
/// Returns either the selected text, or the content of the Markdown code
/// block surrounding the cursor.
fn get_selection_or_code_block(
context_editor_view: &View<ContextEditor>,
cx: &mut ViewContext<Workspace>,
) -> Option<(String, bool)> {
let context_editor = context_editor_view.read(cx).editor.read(cx);
if context_editor.selections.newest::<Point>(cx).is_empty() {
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
let (_, _, snapshot) = snapshot.as_singleton()?;
let head = context_editor.selections.newest::<Point>(cx).head();
let offset = snapshot.point_to_offset(head);
let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
let text = snapshot
.text_for_range(surrounding_code_block_range)
.collect::<String>();
(!text.is_empty()).then_some((text, true))
} else {
let anchor = context_editor.selections.newest_anchor();
let text = context_editor
.buffer()
.read(cx)
.read(cx)
.text_for_range(anchor.range())
.collect::<String>();
(!text.is_empty()).then_some((text, false))
}
}
fn insert_selection(
workspace: &mut Workspace,
_: &InsertIntoEditor,
@@ -3118,17 +3165,7 @@ impl ContextEditor {
return;
};
let context_editor = context_editor_view.read(cx).editor.read(cx);
let anchor = context_editor.selections.newest_anchor();
let text = context_editor
.buffer()
.read(cx)
.read(cx)
.text_for_range(anchor.range())
.collect::<String>();
// If nothing is selected, don't delete the current selection; instead, be a no-op.
if !text.is_empty() {
if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
active_editor_view.update(cx, |editor, cx| {
editor.insert(&text, cx);
editor.focus(cx);
@@ -3136,6 +3173,36 @@ impl ContextEditor {
}
}
fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
let result = maybe!({
let panel = workspace.panel::<AssistantPanel>(cx)?;
let context_editor_view = panel.read(cx).active_context_editor(cx)?;
Self::get_selection_or_code_block(&context_editor_view, cx)
});
let Some((text, is_code_block)) = result else {
return;
};
cx.write_to_clipboard(ClipboardItem::new_string(text));
struct CopyToClipboardToast;
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopyToClipboardToast>(),
format!(
"{} copied to clipboard.",
if is_code_block {
"Code block"
} else {
"Selection"
}
),
)
.autohide(),
cx,
);
}
fn insert_dragged_files(
workspace: &mut Workspace,
action: &InsertDraggedFiles,
@@ -3282,7 +3349,7 @@ impl ContextEditor {
let fence = codeblock_fence_for_path(
filename.as_deref(),
Some(selection.start.row..selection.end.row),
Some(selection.start.row..=selection.end.row),
);
if let Some((line_comment_prefix, outline_text)) =
@@ -3649,6 +3716,54 @@ impl ContextEditor {
});
}
fn update_code_fence_blocks(&mut self, cx: &mut ViewContext<Self>) {
let workspace = self.workspace.clone();
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let Some((_, _, snapshot)) = buffer.as_singleton() else {
return;
};
let old_blocks = std::mem::take(&mut self.code_fence_blocks);
editor.remove_blocks(old_blocks, None, cx);
let selection_head = editor.selections.newest::<usize>(cx).head();
if let Some(range) = find_surrounding_code_block(snapshot, selection_head) {
let start_row = snapshot.offset_to_point(range.start).row.saturating_sub(1);
let position = buffer.anchor_after(Point::new(start_row, 0));
let block = BlockProperties {
position,
height: 0,
style: BlockStyle::Sticky,
render: Box::new(move |_| {
h_flex()
.justify_end()
.child(
IconButton::new("copy-code", IconName::Copy)
.shape(ui::IconButtonShape::Square)
.style(ButtonStyle::Filled)
.on_click({
let workspace = workspace.clone();
move |_, cx| {
workspace
.update(cx, |workspace, cx| {
Self::copy_code(workspace, &CopyCode, cx);
})
.log_err();
}
}),
)
.into_any_element()
}),
disposition: BlockDisposition::Above,
priority: 0,
};
let ids = editor.insert_blocks(vec![block], None, cx);
self.code_fence_blocks = HashSet::from_iter(ids);
}
});
}
fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
self.context.update(cx, |context, cx| {
let selections = self.editor.read(cx).selections.disjoint_anchors();
@@ -4215,6 +4330,48 @@ impl ContextEditor {
}
}
/// Returns the contents of the *outermost* fenced code block that contains the given offset.
fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
const CODE_BLOCK_NODE: &'static str = "fenced_code_block";
const CODE_BLOCK_CONTENT: &'static str = "code_fence_content";
let layer = snapshot.syntax_layers().next()?;
let root_node = layer.node();
let mut cursor = root_node.walk();
// Go to the first child for the given offset
while cursor.goto_first_child_for_byte(offset).is_some() {
// If we're at the end of the node, go to the next one.
// Example: if you have a fenced-code-block, and you're on the start of the line
// right after the closing ```, you want to skip the fenced-code-block and
// go to the next sibling.
if cursor.node().end_byte() == offset {
cursor.goto_next_sibling();
}
if cursor.node().start_byte() > offset {
break;
}
// We found the fenced code block.
if cursor.node().kind() == CODE_BLOCK_NODE {
// Now we need to find the child node that contains the code.
cursor.goto_first_child();
loop {
if cursor.node().kind() == CODE_BLOCK_CONTENT {
return Some(cursor.node().byte_range());
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
None
}
fn render_fold_icon_button(
editor: WeakView<Editor>,
icon: IconName,
@@ -5384,18 +5541,16 @@ fn make_lsp_adapter_delegate(
let worktree = project
.worktrees(cx)
.next()
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
let fs = if project.is_local() {
Some(project.fs().clone())
} else {
None
};
.ok_or_else(|| anyhow!("no worktrees when constructing LocalLspAdapterDelegate"))?;
let http_client = project.client().http_client().clone();
project.lsp_store().update(cx, |lsp_store, cx| {
Ok(
ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, None, cx)
as Arc<dyn LspAdapterDelegate>,
)
Ok(LocalLspAdapterDelegate::new(
lsp_store,
&worktree,
http_client,
project.fs().clone(),
cx,
) as Arc<dyn LspAdapterDelegate>)
})
})
}
@@ -5499,3 +5654,85 @@ fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
None
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{AppContext, Context};
use language::Buffer;
use unindent::Unindent;
#[gpui::test]
fn test_find_code_blocks(cx: &mut AppContext) {
let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
let buffer = cx.new_model(|cx| {
let text = r#"
line 0
line 1
```rust
fn main() {}
```
line 5
line 6
line 7
```go
func main() {}
```
line 11
```
this is plain text code block
```
```go
func another() {}
```
line 19
"#
.unindent();
let mut buffer = Buffer::local(text, cx);
buffer.set_language(Some(markdown.clone()), cx);
buffer
});
let snapshot = buffer.read(cx).snapshot();
let code_blocks = vec![
Point::new(3, 0)..Point::new(4, 0),
Point::new(9, 0)..Point::new(10, 0),
Point::new(13, 0)..Point::new(14, 0),
Point::new(17, 0)..Point::new(18, 0),
]
.into_iter()
.map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
.collect::<Vec<_>>();
let expected_results = vec![
(0, None),
(1, None),
(2, Some(code_blocks[0].clone())),
(3, Some(code_blocks[0].clone())),
(4, Some(code_blocks[0].clone())),
(5, None),
(6, None),
(7, None),
(8, Some(code_blocks[1].clone())),
(9, Some(code_blocks[1].clone())),
(10, Some(code_blocks[1].clone())),
(11, None),
(12, Some(code_blocks[2].clone())),
(13, Some(code_blocks[2].clone())),
(14, Some(code_blocks[2].clone())),
(15, None),
(16, Some(code_blocks[3].clone())),
(17, Some(code_blocks[3].clone())),
(18, Some(code_blocks[3].clone())),
(19, None),
];
for (row, expected) in expected_results {
let offset = snapshot.point_to_offset(Point::new(row, 0));
let range = find_surrounding_code_block(&snapshot, offset);
assert_eq!(range, expected, "unexpected result on row {:?}", row);
}
}
}

View File

@@ -46,7 +46,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::AssistantKind;
use telemetry_events::{AssistantKind, AssistantPhase};
use text::BufferSnapshot;
use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
@@ -2134,6 +2134,7 @@ impl Context {
telemetry.report_assistant_event(
Some(this.id.0.clone()),
AssistantKind::Panel,
AssistantPhase::Response,
model.telemetry_id(),
response_latency,
error_message,

View File

@@ -174,6 +174,18 @@ impl InlineAssistant {
initial_prompt: Option<String>,
cx: &mut WindowContext,
) {
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
telemetry_events::AssistantPhase::Invoked,
model.telemetry_id(),
None,
None,
);
}
}
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
let mut selections = Vec::<Selection<Point>>::new();
@@ -708,6 +720,22 @@ impl InlineAssistant {
}
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
if undo {
telemetry_events::AssistantPhase::Rejected
} else {
telemetry_events::AssistantPhase::Accepted
},
model.telemetry_id(),
None,
None,
);
}
}
if let Some(assist) = self.assists.get(&assist_id) {
let assist_group_id = assist.group_id;
if self.assist_groups[&assist_group_id].linked {
@@ -2558,6 +2586,7 @@ impl Codegen {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
telemetry_events::AssistantPhase::Response,
model_telemetry_id,
response_latency,
error_message,

View File

@@ -921,10 +921,8 @@ impl PromptLibrary {
scrollbar_width: Pixels::ZERO,
syntax: cx.theme().syntax().clone(),
status: cx.theme().status().clone(),
inlay_hints_style: HighlightStyle {
color: Some(cx.theme().status().hint),
..HighlightStyle::default()
},
inlay_hints_style:
editor::make_inlay_hints_style(cx),
suggestions_style: HighlightStyle {
color: Some(cx.theme().status().predictive),
..HighlightStyle::default()

View File

@@ -8,7 +8,7 @@ use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
use std::{
fmt::Write,
ops::Range,
ops::{Range, RangeInclusive},
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
@@ -342,7 +342,10 @@ fn collect_files(
})
}
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
pub fn codeblock_fence_for_path(
path: Option<&Path>,
row_range: Option<RangeInclusive<u32>>,
) -> String {
let mut text = String::new();
write!(text, "```").unwrap();
@@ -357,7 +360,7 @@ pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32
}
if let Some(row_range) = row_range {
write!(text, ":{}-{}", row_range.start + 1, row_range.end + 1).unwrap();
write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
}
text.push('\n');

View File

@@ -8,14 +8,12 @@ use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
use semantic_index::SemanticDb;
use semantic_index::{LoadedSearchResult, SemanticDb};
use std::{
fmt::Write,
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{prelude::*, IconName};
use util::ResultExt;
use workspace::Workspace;
pub(crate) struct SearchSlashCommandFeatureFlag;
@@ -107,52 +105,28 @@ impl SlashCommand for SearchSlashCommand {
})?
.await?;
let mut loaded_results = Vec::new();
for result in results {
let (full_path, file_content) =
result.worktree.read_with(&cx, |worktree, _cx| {
let entry_abs_path = worktree.abs_path().join(&result.path);
let mut entry_full_path = PathBuf::from(worktree.root_name());
entry_full_path.push(&result.path);
let file_content = async {
let entry_abs_path = entry_abs_path;
fs.load(&entry_abs_path).await
};
(entry_full_path, file_content)
})?;
if let Some(file_content) = file_content.await.log_err() {
loaded_results.push((result, full_path, file_content));
}
}
let loaded_results = SemanticDb::load_results(results, &fs, &cx).await?;
let output = cx
.background_executor()
.spawn(async move {
let mut text = format!("Search results for {query}:\n");
let mut sections = Vec::new();
for (result, full_path, file_content) in loaded_results {
let range_start = result.range.start.min(file_content.len());
let range_end = result.range.end.min(file_content.len());
let start_row = file_content[0..range_start].matches('\n').count() as u32;
let end_row = file_content[0..range_end].matches('\n').count() as u32;
let start_line_byte_offset = file_content[0..range_start]
.rfind('\n')
.map(|pos| pos + 1)
.unwrap_or_default();
let end_line_byte_offset = file_content[range_end..]
.find('\n')
.map(|pos| range_end + pos)
.unwrap_or_else(|| file_content.len());
for LoadedSearchResult {
path,
range,
full_path,
file_content,
row_range,
} in loaded_results
{
let section_start_ix = text.len();
text.push_str(&codeblock_fence_for_path(
Some(&result.path),
Some(start_row..end_row),
Some(&path),
Some(row_range.clone()),
));
let mut excerpt =
file_content[start_line_byte_offset..end_line_byte_offset].to_string();
let mut excerpt = file_content[range].to_string();
LineEnding::normalize(&mut excerpt);
text.push_str(&excerpt);
writeln!(text, "\n```\n").unwrap();
@@ -161,7 +135,7 @@ impl SlashCommand for SearchSlashCommand {
section_start_ix..section_end_ix,
Some(&full_path),
false,
Some(start_row + 1..end_row + 1),
Some(row_range.start() + 1..row_range.end() + 1),
));
}

View File

@@ -570,7 +570,7 @@ impl Render for PromptEditor {
.bg(cx.theme().colors().editor_background)
.border_y_1()
.border_color(cx.theme().status().info_border)
.py_1p5()
.py_2()
.h_full()
.w_full()
.on_action(cx.listener(Self::confirm))
@@ -949,12 +949,11 @@ impl PromptEditor {
} else {
cx.theme().colors().text
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size.into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
EditorElement::new(
@@ -1067,6 +1066,7 @@ impl Codegen {
telemetry.report_assistant_event(
None,
telemetry_events::AssistantKind::Inline,
telemetry_events::AssistantPhase::Response,
model_telemetry_id,
response_latency,
error_message,

View File

@@ -19,7 +19,6 @@ db.workspace = true
editor.workspace = true
gpui.workspace = true
http_client.workspace = true
isahc.workspace = true
log.workspace = true
markdown_preview.workspace = true
menu.workspace = true

View File

@@ -9,7 +9,6 @@ use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
};
use isahc::AsyncBody;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use schemars::JsonSchema;
@@ -20,7 +19,7 @@ use smol::{fs, io::AsyncReadExt};
use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs::File, process::Command};
use http_client::{HttpClient, HttpClientWithUrl};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use std::{
env::{
@@ -244,19 +243,22 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
let auto_updater = AutoUpdater::get(cx)?;
let release_channel = ReleaseChannel::try_global(cx)?;
if matches!(
release_channel,
ReleaseChannel::Stable | ReleaseChannel::Preview
) {
let auto_updater = auto_updater.read(cx);
let release_channel = release_channel.dev_name();
let current_version = auto_updater.current_version;
let url = &auto_updater
.http_client
.build_url(&format!("/releases/{release_channel}/{current_version}"));
cx.open_url(url);
match release_channel {
ReleaseChannel::Stable | ReleaseChannel::Preview => {
let auto_updater = auto_updater.read(cx);
let current_version = auto_updater.current_version;
let release_channel = release_channel.dev_name();
let path = format!("/releases/{release_channel}/{current_version}");
let url = &auto_updater.http_client.build_url(&path);
cx.open_url(url);
}
ReleaseChannel::Nightly => {
cx.open_url("https://github.com/zed-industries/zed/commits/nightly/");
}
ReleaseChannel::Dev => {
cx.open_url("https://github.com/zed-industries/zed/commits/main/");
}
}
None
}

View File

@@ -332,7 +332,7 @@ impl ChannelChat {
.update(&mut cx, |chat, cx| {
if let Some(first_id) = chat.first_loaded_message_id() {
if first_id <= message_id {
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>(&());
let message_id = ChannelMessageId::Saved(message_id);
cursor.seek(&message_id, Bias::Left, &());
return ControlFlow::Break(
@@ -498,7 +498,7 @@ impl ChannelChat {
}
pub fn message(&self, ix: usize) -> &ChannelMessage {
let mut cursor = self.messages.cursor::<Count>();
let mut cursor = self.messages.cursor::<Count>(&());
cursor.seek(&Count(ix), Bias::Right, &());
cursor.item().unwrap()
}
@@ -515,13 +515,13 @@ impl ChannelChat {
}
pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
let mut cursor = self.messages.cursor::<Count>();
let mut cursor = self.messages.cursor::<Count>(&());
cursor.seek(&Count(range.start), Bias::Right, &());
cursor.take(range.len())
}
pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
let mut cursor = self.messages.cursor::<ChannelMessageId>();
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
cursor
}
@@ -589,11 +589,11 @@ impl ChannelChat {
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
let nonces = messages
.cursor::<()>()
.cursor::<()>(&())
.map(|m| m.nonce)
.collect::<HashSet<_>>();
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>(&());
let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
let start_ix = old_cursor.start().1 .0;
let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
@@ -646,7 +646,7 @@ impl ChannelChat {
}
fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
let mut cursor = self.messages.cursor::<ChannelMessageId>();
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
if let Some(item) = cursor.item() {
if item.id == ChannelMessageId::Saved(id) {
@@ -685,7 +685,7 @@ impl ChannelChat {
edited_at: Option<OffsetDateTime>,
cx: &mut ModelContext<Self>,
) {
let mut cursor = self.messages.cursor::<ChannelMessageId>();
let mut cursor = self.messages.cursor::<ChannelMessageId>(&());
let mut messages = cursor.slice(&id, Bias::Left, &());
let ix = messages.summary().count;
@@ -716,7 +716,7 @@ async fn messages_from_proto(
cx: &mut AsyncAppContext,
) -> Result<SumTree<ChannelMessage>> {
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
let mut result = SumTree::new();
let mut result = SumTree::default();
result.extend(messages, &());
Ok(result)
}
@@ -825,6 +825,10 @@ impl Default for ChannelMessageId {
impl sum_tree::Summary for ChannelMessageSummary {
type Context = ();
fn zero(_cx: &Self::Context) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &Self, _: &()) {
self.max_id = summary.max_id;
self.count += summary.count;
@@ -832,6 +836,10 @@ impl sum_tree::Summary for ChannelMessageSummary {
}
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
debug_assert!(summary.max_id > *self);
*self = summary.max_id;
@@ -839,6 +847,10 @@ impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
}
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
self.0 += summary.count;
}

View File

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

View File

@@ -240,8 +240,6 @@ pub enum EstablishConnectionError {
#[error("{0}")]
Other(#[from] anyhow::Error),
#[error("{0}")]
Http(#[from] http_client::Error),
#[error("{0}")]
InvalidHeaderValue(#[from] async_tungstenite::tungstenite::http::header::InvalidHeaderValue),
#[error("{0}")]
Io(#[from] std::io::Error),
@@ -529,19 +527,13 @@ impl Client {
}
pub fn production(cx: &mut AppContext) -> Arc<Self> {
let user_agent = format!(
"Zed/{} ({}; {})",
AppVersion::global(cx),
std::env::consts::OS,
std::env::consts::ARCH
);
let clock = Arc::new(clock::RealSystemClock);
let http = Arc::new(HttpClientWithUrl::new(
let http = Arc::new(HttpClientWithUrl::new_uri(
cx.http_client(),
&ClientSettings::get_global(cx).server_url,
Some(user_agent),
ProxySettings::get_global(cx).proxy.clone(),
cx.http_client().proxy().cloned(),
));
Self::new(clock, http.clone(), cx)
Self::new(clock, http, cx)
}
pub fn id(&self) -> u64 {
@@ -1145,8 +1137,32 @@ impl Client {
match url_scheme {
Https => {
let client_config = {
let mut root_store = rustls::RootCertStore::empty();
let root_certs = rustls_native_certs::load_native_certs();
for error in root_certs.errors {
log::warn!("error loading native certs: {:?}", error);
}
root_store.add_parsable_certificates(
&root_certs
.certs
.into_iter()
.map(|cert| cert.as_ref().to_owned())
.collect::<Vec<_>>(),
);
rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store)
.with_no_client_auth()
};
let (stream, _) =
async_tungstenite::async_std::client_async_tls(request, stream).await?;
async_tungstenite::async_tls::client_async_tls_with_connector(
request,
stream,
Some(client_config.into()),
)
.await?;
Ok(Connection::new(
stream
.map_err(|error| anyhow!(error))

View File

@@ -16,9 +16,9 @@ use std::io::Write;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CpuEvent, EditEvent,
EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent,
MemoryEvent, ReplEvent, SettingEvent,
ActionEvent, AppEvent, AssistantEvent, AssistantKind, AssistantPhase, CallEvent, CpuEvent,
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, ExtensionEvent,
InlineCompletionEvent, MemoryEvent, ReplEvent, SettingEvent,
};
use tempfile::NamedTempFile;
#[cfg(not(debug_assertions))]
@@ -37,9 +37,10 @@ pub struct Telemetry {
struct TelemetryState {
settings: TelemetrySettings,
metrics_id: Option<Arc<str>>, // Per logged-in user
system_id: Option<Arc<str>>, // Per system
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<String>, // Per app launch
metrics_id: Option<Arc<str>>, // Per logged-in user
release_channel: Option<&'static str>,
architecture: &'static str,
events_queue: Vec<EventWrapper>,
@@ -191,9 +192,10 @@ impl Telemetry {
settings: *TelemetrySettings::get_global(cx),
architecture: env::consts::ARCH,
release_channel,
system_id: None,
installation_id: None,
metrics_id: None,
session_id: None,
metrics_id: None,
events_queue: Vec::new(),
flush_events_task: None,
log_file: None,
@@ -283,11 +285,13 @@ impl Telemetry {
pub fn start(
self: &Arc<Self>,
system_id: Option<String>,
installation_id: Option<String>,
session_id: String,
cx: &mut AppContext,
) {
let mut state = self.state.lock();
state.system_id = system_id.map(|id| id.into());
state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id);
state.app_version = release_channel::AppVersion::global(cx).to_string();
@@ -304,7 +308,10 @@ impl Telemetry {
let refresh_kind = ProcessRefreshKind::new().with_cpu().with_memory();
let current_process = Pid::from_u32(std::process::id());
system.refresh_process_specifics(current_process, refresh_kind);
system.refresh_processes_specifics(
sysinfo::ProcessesToUpdate::Some(&[current_process]),
refresh_kind,
);
// Waiting some amount of time before the first query is important to get a reasonable value
// https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
@@ -314,7 +321,10 @@ impl Telemetry {
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
let current_process = Pid::from_u32(std::process::id());
system.refresh_process_specifics(current_process, refresh_kind);
system.refresh_processes_specifics(
sysinfo::ProcessesToUpdate::Some(&[current_process]),
refresh_kind,
);
let Some(process) = system.process(current_process) else {
log::error!(
"Failed to find own process {current_process:?} in system process table"
@@ -385,6 +395,7 @@ impl Telemetry {
self: &Arc<Self>,
conversation_id: Option<String>,
kind: AssistantKind,
phase: AssistantPhase,
model: String,
response_latency: Option<Duration>,
error_message: Option<String>,
@@ -392,6 +403,7 @@ impl Telemetry {
let event = Event::Assistant(AssistantEvent {
conversation_id,
kind,
phase,
model: model.to_string(),
response_latency,
error_message,
@@ -629,9 +641,10 @@ impl Telemetry {
let state = this.state.lock();
let request_body = EventRequestBody {
system_id: state.system_id.as_deref().map(Into::into),
installation_id: state.installation_id.as_deref().map(Into::into),
metrics_id: state.metrics_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
metrics_id: state.metrics_id.as_deref().map(Into::into),
is_staff: state.is_staff,
app_version: state.app_version.clone(),
os_name: state.os_name.clone(),
@@ -703,6 +716,7 @@ mod tests {
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response();
let system_id = Some("system_id".to_string());
let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string();
@@ -710,7 +724,7 @@ mod tests {
let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4;
telemetry.start(installation_id, session_id, cx);
telemetry.start(system_id, installation_id, session_id, cx);
assert!(is_empty_state(&telemetry));
@@ -788,13 +802,14 @@ mod tests {
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response();
let system_id = Some("system_id".to_string());
let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string();
cx.update(|cx| {
let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4;
telemetry.start(installation_id, session_id, cx);
telemetry.start(system_id, installation_id, session_id, cx);
assert!(is_empty_state(&telemetry));

View File

@@ -37,6 +37,7 @@ futures.workspace = true
google_ai.workspace = true
hex.workspace = true
http_client.workspace = true
isahc_http_client.workspace = true
jsonwebtoken.workspace = true
live_kit_server.workspace = true
log.workspace = true

View File

@@ -149,7 +149,8 @@ pub async fn post_crash(
installation_id = %installation_id,
description = %description,
backtrace = %summary,
"crash report");
"crash report"
);
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
let payload = slack::WebhookBody::new(|w| {
@@ -627,7 +628,9 @@ where
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct EditorEventRow {
system_id: String,
installation_id: String,
session_id: Option<String>,
metrics_id: String,
operation: String,
app_version: String,
@@ -647,7 +650,6 @@ pub struct EditorEventRow {
historical_event: bool,
architecture: String,
is_staff: Option<bool>,
session_id: Option<String>,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
@@ -677,9 +679,10 @@ impl EditorEventRow {
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
architecture: body.architecture.clone(),
system_id: body.system_id.clone().unwrap_or_default(),
installation_id: body.installation_id.clone().unwrap_or_default(),
metrics_id: body.metrics_id.clone().unwrap_or_default(),
session_id: body.session_id.clone(),
metrics_id: body.metrics_id.clone().unwrap_or_default(),
is_staff: body.is_staff,
time: time.timestamp_millis(),
operation: event.operation,
@@ -699,6 +702,7 @@ impl EditorEventRow {
#[derive(Serialize, Debug, clickhouse::Row)]
pub struct InlineCompletionEventRow {
installation_id: String,
session_id: Option<String>,
provider: String,
suggestion_accepted: bool,
app_version: String,
@@ -713,7 +717,6 @@ pub struct InlineCompletionEventRow {
city: String,
time: i64,
is_staff: Option<bool>,
session_id: Option<String>,
major: Option<i32>,
minor: Option<i32>,
patch: Option<i32>,
@@ -834,6 +837,7 @@ pub struct AssistantEventRow {
// AssistantEventRow
conversation_id: String,
kind: String,
phase: String,
model: String,
response_latency_in_ms: Option<i64>,
error_message: Option<String>,
@@ -866,6 +870,7 @@ impl AssistantEventRow {
time: time.timestamp_millis(),
conversation_id: event.conversation_id.unwrap_or_default(),
kind: event.kind.to_string(),
phase: event.phase.to_string(),
model: event.model,
response_latency_in_ms: event
.response_latency
@@ -877,7 +882,9 @@ impl AssistantEventRow {
#[derive(Debug, clickhouse::Row, Serialize)]
pub struct CpuEventRow {
system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
usage_as_percentage: f32,
core_count: u32,
@@ -886,7 +893,6 @@ pub struct CpuEventRow {
os_name: String,
os_version: String,
time: i64,
session_id: Option<String>,
// pub normalized_cpu_usage: f64, MATERIALIZED
major: Option<i32>,
minor: Option<i32>,
@@ -915,6 +921,7 @@ impl CpuEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -938,6 +945,7 @@ pub struct MemoryEventRow {
os_version: String,
// ClientEventBase
system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -969,6 +977,7 @@ impl MemoryEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -992,6 +1001,7 @@ pub struct AppEventRow {
os_version: String,
// ClientEventBase
system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -1022,6 +1032,7 @@ impl AppEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -1044,6 +1055,7 @@ pub struct SettingEventRow {
os_version: String,
// ClientEventBase
system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -1074,6 +1086,7 @@ impl SettingEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -1097,6 +1110,7 @@ pub struct ExtensionEventRow {
os_version: String,
// ClientEventBase
system_id: Option<String>,
installation_id: Option<String>,
session_id: Option<String>,
is_staff: Option<bool>,
@@ -1132,6 +1146,7 @@ impl ExtensionEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,
@@ -1222,6 +1237,7 @@ pub struct EditEventRow {
os_version: String,
// ClientEventBase
system_id: Option<String>,
installation_id: Option<String>,
// Note: This column name has a typo in the ClickHouse table.
#[serde(rename = "sesssion_id")]
@@ -1259,6 +1275,7 @@ impl EditEventRow {
release_channel: body.release_channel.clone().unwrap_or_default(),
os_name: body.os_name.clone(),
os_version: body.os_version.clone().unwrap_or_default(),
system_id: body.system_id.clone(),
installation_id: body.installation_id.clone(),
session_id: body.session_id.clone(),
is_staff: body.is_staff,

View File

@@ -104,7 +104,7 @@ pub enum ChannelRole {
/// Admin can read/write and change permissions.
#[sea_orm(string_value = "admin")]
Admin,
/// Member can read/write, but not change pemissions.
/// Member can read/write, but not change permissions.
#[sea_orm(string_value = "member")]
#[default]
Member,

View File

@@ -22,7 +22,7 @@ use chrono::{DateTime, Duration, Utc};
use collections::HashMap;
use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase};
use futures::{Stream, StreamExt as _};
use http_client::IsahcHttpClient;
use isahc_http_client::IsahcHttpClient;
use rpc::ListModelsResponse;
use rpc::{
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
@@ -72,6 +72,7 @@ impl LlmState {
let http_client = IsahcHttpClient::builder()
.default_header("User-Agent", user_agent)
.build()
.map(IsahcHttpClient::from)
.context("failed to construct http client")?;
let this = Self {

View File

@@ -35,6 +35,8 @@ use chrono::Utc;
use collections::{HashMap, HashSet};
pub use connection_pool::{ConnectionPool, ZedVersion};
use core::fmt::{self, Debug, Formatter};
use http_client::HttpClient;
use isahc_http_client::IsahcHttpClient;
use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL};
use sha2::Digest;
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
@@ -45,7 +47,6 @@ use futures::{
stream::FuturesUnordered,
FutureExt, SinkExt, StreamExt, TryStreamExt,
};
use http_client::IsahcHttpClient;
use prometheus::{register_int_gauge, IntGauge};
use rpc::{
proto::{
@@ -139,7 +140,7 @@ struct Session {
connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
app_state: Arc<AppState>,
supermaven_client: Option<Arc<SupermavenAdminApi>>,
http_client: Arc<IsahcHttpClient>,
http_client: Arc<dyn HttpClient>,
/// The GeoIP country code for the user.
#[allow(unused)]
geoip_country_code: Option<String>,
@@ -957,7 +958,7 @@ impl Server {
let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() {
Ok(http_client) => Arc::new(http_client),
Ok(http_client) => Arc::new(IsahcHttpClient::from(http_client)),
Err(error) => {
tracing::error!(?error, "failed to create HTTP client");
return;

View File

@@ -1524,6 +1524,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
show_type_hints: true,
show_parameter_hints: false,
show_other_hints: true,
show_background: false,
})
});
});
@@ -1538,6 +1539,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
show_type_hints: true,
show_parameter_hints: false,
show_other_hints: true,
show_background: false,
})
});
});
@@ -1786,6 +1788,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
show_type_hints: false,
show_parameter_hints: false,
show_other_hints: false,
show_background: false,
})
});
});
@@ -1800,6 +1803,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
});

View File

@@ -11,16 +11,14 @@ pub use smol;
pub use sqlez;
pub use sqlez_macros;
use release_channel::ReleaseChannel;
pub use release_channel::RELEASE_CHANNEL;
use sqlez::domain::Migrator;
use sqlez::thread_safe_connection::ThreadSafeConnection;
use sqlez_macros::sql;
use std::env;
use std::future::Future;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::LazyLock;
use std::sync::{atomic::Ordering, LazyLock};
use std::{env, sync::atomic::AtomicBool};
use util::{maybe, ResultExt};
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
@@ -47,16 +45,12 @@ pub static ALL_FILE_DB_FAILED: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBoo
/// This will retry a couple times if there are failures. If opening fails once, the db directory
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
/// In either case, static variables are set so that the user can be notified.
pub async fn open_db<M: Migrator + 'static>(
db_dir: &Path,
release_channel: &ReleaseChannel,
) -> ThreadSafeConnection<M> {
pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, scope: &str) -> ThreadSafeConnection<M> {
if *ZED_STATELESS {
return open_fallback_db().await;
}
let release_channel_name = release_channel.dev_name();
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
let main_db_dir = db_dir.join(format!("0-{}", scope));
let connection = maybe!(async {
smol::fs::create_dir_all(&main_db_dir)
@@ -118,7 +112,7 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M>
/// Implements a basic DB wrapper for a given domain
#[macro_export]
macro_rules! define_connection {
(pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => {
(pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => {
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
impl ::std::ops::Deref for $t {
@@ -139,18 +133,23 @@ macro_rules! define_connection {
}
}
use std::sync::LazyLock;
#[cfg(any(test, feature = "test-support"))]
pub static $id: LazyLock<$t> = LazyLock::new(|| {
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
});
#[cfg(not(any(test, feature = "test-support")))]
pub static $id: LazyLock<$t> = LazyLock::new(|| {
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
let db_dir = $crate::database_dir();
let scope = if false $(|| stringify!($global) == "global")? {
"global"
} else {
$crate::RELEASE_CHANNEL.dev_name()
};
$t($crate::smol::block_on($crate::open_db(db_dir, scope)))
});
};
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => {
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
impl ::std::ops::Deref for $t {
@@ -178,7 +177,13 @@ macro_rules! define_connection {
#[cfg(not(any(test, feature = "test-support")))]
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
let db_dir = $crate::database_dir();
let scope = if false $(|| stringify!($global) == "global")? {
"global"
} else {
$crate::RELEASE_CHANNEL.dev_name()
};
$t($crate::smol::block_on($crate::open_db(db_dir, scope)))
});
};
}
@@ -225,7 +230,11 @@ mod tests {
.prefix("DbTests")
.tempdir()
.unwrap();
let _bad_db = open_db::<BadDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
let _bad_db = open_db::<BadDB>(
tempdir.path(),
&release_channel::ReleaseChannel::Dev.dev_name(),
)
.await;
}
/// Test that DB exists but corrupted (causing recreate)
@@ -262,13 +271,19 @@ mod tests {
.tempdir()
.unwrap();
{
let corrupt_db =
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
let corrupt_db = open_db::<CorruptedDB>(
tempdir.path(),
&release_channel::ReleaseChannel::Dev.dev_name(),
)
.await;
assert!(corrupt_db.persistent());
}
let good_db =
open_db::<GoodDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
let good_db = open_db::<GoodDB>(
tempdir.path(),
&release_channel::ReleaseChannel::Dev.dev_name(),
)
.await;
assert!(
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
.unwrap()
@@ -311,8 +326,11 @@ mod tests {
.unwrap();
{
// Setup the bad database
let corrupt_db =
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
let corrupt_db = open_db::<CorruptedDB>(
tempdir.path(),
&release_channel::ReleaseChannel::Dev.dev_name(),
)
.await;
assert!(corrupt_db.persistent());
}
@@ -323,7 +341,7 @@ mod tests {
let guard = thread::spawn(move || {
let good_db = smol::block_on(open_db::<GoodDB>(
tmp_path.as_path(),
&release_channel::ReleaseChannel::Dev,
&release_channel::ReleaseChannel::Dev.dev_name(),
));
assert!(
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()

View File

@@ -60,3 +60,33 @@ mod tests {
assert_eq!(db.read_kvp("key-1").unwrap(), None);
}
}
define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
&[sql!(
CREATE TABLE IF NOT EXISTS kv_store(
key TEXT PRIMARY KEY,
value TEXT NOT NULL
) STRICT;
)];
global
);
impl GlobalKeyValueStore {
query! {
pub fn read_kvp(key: &str) -> Result<Option<String>> {
SELECT value FROM kv_store WHERE key = (?)
}
}
query! {
pub async fn write_kvp(key: String, value: String) -> Result<()> {
INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
}
}
query! {
pub async fn delete_kvp(key: String) -> Result<()> {
DELETE FROM kv_store WHERE key = (?)
}
}
}

View File

@@ -156,14 +156,14 @@ pub struct DeleteToPreviousWordStart {
impl_actions!(
editor,
[
ComposeCompletion,
ConfirmCodeAction,
ConfirmCompletion,
ComposeCompletion,
DeleteToNextWordEnd,
DeleteToPreviousWordStart,
ExpandExcerpts,
ExpandExcerptsUp,
ExpandExcerptsDown,
ExpandExcerptsUp,
FoldAt,
HandleInput,
MoveDownByLines,
@@ -188,8 +188,8 @@ impl_actions!(
gpui::actions!(
editor,
[
AcceptPartialCopilotSuggestion,
AcceptInlineCompletion,
AcceptPartialCopilotSuggestion,
AcceptPartialInlineCompletion,
AddSelectionAbove,
AddSelectionBelow,
@@ -210,10 +210,10 @@ gpui::actions!(
ConvertToUpperCamelCase,
ConvertToUpperCase,
Copy,
CopyFileLocation,
CopyHighlightJson,
CopyPath,
CopyPermalinkToLine,
CopyFileLocation,
CopyRelativePath,
Cut,
CutToEndOfLine,
@@ -232,10 +232,10 @@ gpui::actions!(
Fold,
FoldSelectedRanges,
Format,
GoToDefinition,
GoToDefinitionSplit,
GoToDeclaration,
GoToDeclarationSplit,
GoToDefinition,
GoToDefinitionSplit,
GoToDiagnostic,
GoToHunk,
GoToImplementation,
@@ -273,9 +273,9 @@ gpui::actions!(
NextScreen,
OpenExcerpts,
OpenExcerptsSplit,
OpenFile,
OpenPermalinkToLine,
OpenUrl,
OpenFile,
Outdent,
PageDown,
PageUp,
@@ -284,23 +284,25 @@ gpui::actions!(
Redo,
RedoSelection,
Rename,
Rewrap,
RestartLanguageServer,
RevealInFileManager,
ReverseLines,
RevertFile,
RevertSelectedHunks,
Rewrap,
ScrollCursorBottom,
ScrollCursorCenter,
ScrollCursorTop,
ScrollCursorCenterTopBottom,
ScrollCursorTop,
SelectAll,
SelectAllMatches,
SelectDown,
SelectLargerSyntaxNode,
SelectEnclosingSymbol,
SelectLargerSyntaxNode,
SelectLeft,
SelectLine,
SelectPageDown,
SelectPageUp,
SelectRight,
SelectSmallerSyntaxNode,
SelectToBeginning,
@@ -312,8 +314,6 @@ gpui::actions!(
SelectToPreviousWordStart,
SelectToStartOfParagraph,
SelectUp,
SelectPageDown,
SelectPageUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShowSignatureHelp,
@@ -327,13 +327,13 @@ gpui::actions!(
ToggleAutoSignatureHelp,
ToggleGitBlame,
ToggleGitBlameInline,
ToggleSelectionMenu,
ToggleHunkDiff,
ToggleIndentGuides,
ToggleInlayHints,
ToggleInlineCompletions,
ToggleLineNumbers,
ToggleRelativeLineNumbers,
ToggleIndentGuides,
ToggleSelectionMenu,
ToggleSoftWrap,
ToggleTabBar,
Transpose,

View File

@@ -127,7 +127,9 @@ impl DisplayMap {
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let tab_size = Self::tab_size(&buffer, cx);
let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx));
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let crease_map = CreaseMap::new(&buffer_snapshot);
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
let (fold_map, snapshot) = FoldMap::new(snapshot);
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
@@ -138,7 +140,6 @@ impl DisplayMap {
excerpt_header_height,
excerpt_footer_height,
);
let crease_map = CreaseMap::default();
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();

View File

@@ -389,10 +389,10 @@ impl BlockMap {
}
let mut transforms = self.transforms.borrow_mut();
let mut new_transforms = SumTree::new();
let mut new_transforms = SumTree::default();
let old_row_count = transforms.summary().input_rows;
let new_row_count = wrap_snapshot.max_point().row() + 1;
let mut cursor = transforms.cursor::<WrapRow>();
let mut cursor = transforms.cursor::<WrapRow>(&());
let mut last_block_ix = 0;
let mut blocks_in_edit = Vec::new();
let mut edits = edits.into_iter().peekable();
@@ -757,7 +757,7 @@ impl<'a> BlockMapReader<'a> {
.unwrap_or(self.wrap_snapshot.max_point().row() + 1),
);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&start_wrap_row, Bias::Left, &());
while let Some(transform) = cursor.item() {
if cursor.start().0 > end_wrap_row {
@@ -950,7 +950,7 @@ impl BlockSnapshot {
highlights: Highlights<'a>,
) -> BlockChunks<'a> {
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
let input_end = {
cursor.seek(&BlockRow(rows.end), Bias::Right, &());
let overshoot = if cursor
@@ -990,7 +990,7 @@ impl BlockSnapshot {
}
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&start_row, Bias::Right, &());
let (output_start, input_start) = cursor.start();
let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
@@ -1008,7 +1008,7 @@ impl BlockSnapshot {
}
pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
let mut cursor = self.transforms.cursor::<BlockRow>();
let mut cursor = self.transforms.cursor::<BlockRow>(&());
cursor.seek(&BlockRow(rows.start), Bias::Left, &());
while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
cursor.next(&());
@@ -1050,7 +1050,7 @@ impl BlockSnapshot {
let wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.start, Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() {
if let Some(block) = transform.block.as_ref() {
@@ -1072,7 +1072,7 @@ impl BlockSnapshot {
.wrap_snapshot
.make_wrap_point(excerpt_range.end, Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() {
if let Some(block) = transform.block.as_ref() {
@@ -1102,7 +1102,7 @@ impl BlockSnapshot {
}
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(row.0), Bias::Right, &());
if let Some(transform) = cursor.item() {
let (output_start, input_start) = cursor.start();
@@ -1118,13 +1118,13 @@ impl BlockSnapshot {
}
pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&row, Bias::Right, &());
cursor.item().map_or(false, |t| t.block.is_some())
}
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(point.row), Bias::Right, &());
let max_input_row = WrapRow(self.transforms.summary().input_rows);
@@ -1172,7 +1172,7 @@ impl BlockSnapshot {
}
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
if let Some(transform) = cursor.item() {
debug_assert!(transform.is_isomorphic());
@@ -1188,7 +1188,7 @@ impl BlockSnapshot {
}
pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
if let Some(transform) = cursor.item() {
match transform.block.as_ref().map(|b| b.disposition()) {
@@ -1368,6 +1368,10 @@ impl sum_tree::Item for Transform {
impl sum_tree::Summary for TransformSummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &Self, _: &()) {
self.input_rows += summary.input_rows;
self.output_rows += summary.output_rows;
@@ -1375,12 +1379,20 @@ impl sum_tree::Summary for TransformSummary {
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += summary.input_rows;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += summary.output_rows;
}

View File

@@ -12,19 +12,34 @@ use crate::FoldPlaceholder;
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct CreaseId(usize);
#[derive(Default)]
pub struct CreaseMap {
snapshot: CreaseSnapshot,
next_id: CreaseId,
id_to_range: HashMap<CreaseId, Range<Anchor>>,
}
#[derive(Clone, Default)]
impl CreaseMap {
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
CreaseMap {
snapshot: CreaseSnapshot::new(snapshot),
next_id: CreaseId::default(),
id_to_range: HashMap::default(),
}
}
}
#[derive(Clone)]
pub struct CreaseSnapshot {
creases: SumTree<CreaseItem>,
}
impl CreaseSnapshot {
pub fn new(snapshot: &MultiBufferSnapshot) -> Self {
CreaseSnapshot {
creases: SumTree::new(snapshot),
}
}
/// Returns the first Crease starting on the specified buffer row.
pub fn query_row<'a>(
&'a self,
@@ -32,7 +47,7 @@ impl CreaseSnapshot {
snapshot: &'a MultiBufferSnapshot,
) -> Option<&'a Crease> {
let start = snapshot.anchor_before(Point::new(row.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>();
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
cursor.seek(&start, Bias::Left, snapshot);
while let Some(item) = cursor.item() {
match Ord::cmp(&item.crease.range.start.to_point(snapshot).row, &row.0) {
@@ -56,7 +71,7 @@ impl CreaseSnapshot {
snapshot: &'a MultiBufferSnapshot,
) -> impl '_ + Iterator<Item = &'a Crease> {
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>();
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
cursor.seek(&start, Bias::Left, snapshot);
std::iter::from_fn(move || {
@@ -79,7 +94,7 @@ impl CreaseSnapshot {
&self,
snapshot: &MultiBufferSnapshot,
) -> Vec<(CreaseId, Range<Point>)> {
let mut cursor = self.creases.cursor::<ItemSummary>();
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
let mut results = Vec::new();
cursor.next(snapshot);
@@ -194,8 +209,8 @@ impl CreaseMap {
) -> Vec<CreaseId> {
let mut new_ids = Vec::new();
self.snapshot.creases = {
let mut new_creases = SumTree::new();
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
let mut new_creases = SumTree::new(snapshot);
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
for crease in creases {
new_creases.append(cursor.slice(&crease.range, Bias::Left, snapshot), snapshot);
@@ -227,8 +242,8 @@ impl CreaseMap {
});
self.snapshot.creases = {
let mut new_creases = SumTree::new();
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>();
let mut new_creases = SumTree::new(snapshot);
let mut cursor = self.snapshot.creases.cursor::<ItemSummary>(snapshot);
for (id, range) in removals {
new_creases.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
@@ -264,6 +279,10 @@ impl Default for ItemSummary {
impl sum_tree::Summary for ItemSummary {
type Context = MultiBufferSnapshot;
fn zero(_cx: &Self::Context) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
self.range = other.range.clone();
}
@@ -303,7 +322,7 @@ mod test {
let text = "line1\nline2\nline3\nline4\nline5";
let buffer = MultiBuffer::build_simple(text, cx);
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let mut crease_map = CreaseMap::default();
let mut crease_map = CreaseMap::new(&buffer.read(cx).read(cx));
// Insert creases
let creases = [
@@ -350,7 +369,7 @@ mod test {
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
let buffer = MultiBuffer::build_simple(text, cx);
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let mut crease_map = CreaseMap::default();
let mut crease_map = CreaseMap::new(&snapshot);
let creases = [
Crease::new(

View File

@@ -79,7 +79,7 @@ impl FoldPoint {
}
pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>();
let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
cursor.seek(&self, Bias::Right, &());
let overshoot = self.0 - cursor.start().0 .0;
InlayPoint(cursor.start().1 .0 + overshoot)
@@ -88,7 +88,7 @@ impl FoldPoint {
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
let mut cursor = snapshot
.transforms
.cursor::<(FoldPoint, TransformSummary)>();
.cursor::<(FoldPoint, TransformSummary)>(&());
cursor.seek(&self, Bias::Right, &());
let overshoot = self.0 - cursor.start().1.output.lines;
let mut offset = cursor.start().1.output.len;
@@ -105,6 +105,10 @@ impl FoldPoint {
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.output.lines;
}
@@ -154,8 +158,8 @@ impl<'a> FoldMapWriter<'a> {
folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer));
self.0.snapshot.folds = {
let mut new_tree = SumTree::new();
let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>();
let mut new_tree = SumTree::new(buffer);
let mut cursor = self.0.snapshot.folds.cursor::<FoldRange>(buffer);
for fold in folds {
new_tree.append(cursor.slice(&fold.range, Bias::Right, buffer), buffer);
new_tree.push(fold, buffer);
@@ -202,8 +206,8 @@ impl<'a> FoldMapWriter<'a> {
fold_ixs_to_delete.dedup();
self.0.snapshot.folds = {
let mut cursor = self.0.snapshot.folds.cursor::<usize>();
let mut folds = SumTree::new();
let mut cursor = self.0.snapshot.folds.cursor::<usize>(buffer);
let mut folds = SumTree::new(buffer);
for fold_ix in fold_ixs_to_delete {
folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer);
cursor.next(buffer);
@@ -230,7 +234,7 @@ impl FoldMap {
pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
let this = Self {
snapshot: FoldSnapshot {
folds: Default::default(),
folds: SumTree::new(&inlay_snapshot.buffer),
transforms: SumTree::from_item(
Transform {
summary: TransformSummary {
@@ -314,8 +318,8 @@ impl FoldMap {
} else {
let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable();
let mut new_transforms = SumTree::<Transform>::new();
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>();
let mut new_transforms = SumTree::<Transform>::default();
let mut cursor = self.snapshot.transforms.cursor::<InlayOffset>(&());
cursor.seek(&InlayOffset(0), Bias::Right, &());
while let Some(mut edit) = inlay_edits_iter.next() {
@@ -367,7 +371,10 @@ impl FoldMap {
let anchor = inlay_snapshot
.buffer
.anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start));
let mut folds_cursor = self.snapshot.folds.cursor::<FoldRange>();
let mut folds_cursor = self
.snapshot
.folds
.cursor::<FoldRange>(&inlay_snapshot.buffer);
folds_cursor.seek(
&FoldRange(anchor..Anchor::max()),
Bias::Left,
@@ -470,8 +477,8 @@ impl FoldMap {
let mut old_transforms = self
.snapshot
.transforms
.cursor::<(InlayOffset, FoldOffset)>();
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>();
.cursor::<(InlayOffset, FoldOffset)>(&());
let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&());
for mut edit in inlay_edits {
old_transforms.seek(&edit.old.start, Bias::Left, &());
@@ -545,7 +552,7 @@ impl FoldSnapshot {
pub fn text_summary_for_range(&self, range: Range<FoldPoint>) -> TextSummary {
let mut summary = TextSummary::default();
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
cursor.seek(&range.start, Bias::Right, &());
if let Some(transform) = cursor.item() {
let start_in_transform = range.start.0 - cursor.start().0 .0;
@@ -594,7 +601,7 @@ impl FoldSnapshot {
}
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>();
let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&());
cursor.seek(&point, Bias::Right, &());
if cursor.item().map_or(false, |t| t.is_fold()) {
if bias == Bias::Left || point == cursor.start().0 {
@@ -631,7 +638,7 @@ impl FoldSnapshot {
}
let fold_point = FoldPoint::new(start_row, 0);
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
cursor.seek(&fold_point, Bias::Left, &());
let overshoot = fold_point.0 - cursor.start().0 .0;
@@ -672,7 +679,7 @@ impl FoldSnapshot {
{
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
let mut cursor = self.transforms.cursor::<InlayOffset>();
let mut cursor = self.transforms.cursor::<InlayOffset>(&());
cursor.seek(&inlay_offset, Bias::Right, &());
cursor.item().map_or(false, |t| t.placeholder.is_some())
}
@@ -681,7 +688,7 @@ impl FoldSnapshot {
let mut inlay_point = self
.inlay_snapshot
.to_inlay_point(Point::new(buffer_row.0, 0));
let mut cursor = self.transforms.cursor::<InlayPoint>();
let mut cursor = self.transforms.cursor::<InlayPoint>(&());
cursor.seek(&inlay_point, Bias::Right, &());
loop {
match cursor.item() {
@@ -711,7 +718,7 @@ impl FoldSnapshot {
language_aware: bool,
highlights: Highlights<'a>,
) -> FoldChunks<'a> {
let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>();
let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
transform_cursor.seek(&range.start, Bias::Right, &());
let inlay_start = {
@@ -766,7 +773,7 @@ impl FoldSnapshot {
}
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>();
let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&());
cursor.seek(&point, Bias::Right, &());
if let Some(transform) = cursor.item() {
let transform_start = cursor.start().0 .0;
@@ -826,7 +833,7 @@ where
let buffer = &inlay_snapshot.buffer;
let start = buffer.anchor_before(range.start.to_offset(buffer));
let end = buffer.anchor_after(range.end.to_offset(buffer));
let mut cursor = folds.filter::<_, usize>(move |summary| {
let mut cursor = folds.filter::<_, usize>(buffer, move |summary| {
let start_cmp = start.cmp(&summary.max_end, buffer);
let end_cmp = end.cmp(&summary.min_start, buffer);
@@ -945,6 +952,10 @@ impl sum_tree::Item for Transform {
impl sum_tree::Summary for TransformSummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, _: &()) {
self.input += &other.input;
self.output += &other.output;
@@ -1028,6 +1039,10 @@ impl Default for FoldSummary {
impl sum_tree::Summary for FoldSummary {
type Context = MultiBufferSnapshot;
fn zero(_cx: &MultiBufferSnapshot) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less {
self.min_start = other.min_start;
@@ -1052,6 +1067,10 @@ impl sum_tree::Summary for FoldSummary {
}
impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
fn zero(_cx: &MultiBufferSnapshot) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
self.0.start = summary.start;
self.0.end = summary.end;
@@ -1065,6 +1084,10 @@ impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
}
impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
fn zero(_cx: &MultiBufferSnapshot) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) {
*self += summary.count;
}
@@ -1196,7 +1219,7 @@ impl FoldOffset {
pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
let mut cursor = snapshot
.transforms
.cursor::<(FoldOffset, TransformSummary)>();
.cursor::<(FoldOffset, TransformSummary)>(&());
cursor.seek(&self, Bias::Right, &());
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
Point::new(0, (self.0 - cursor.start().0 .0) as u32)
@@ -1210,7 +1233,7 @@ impl FoldOffset {
#[cfg(test)]
pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>();
let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&());
cursor.seek(&self, Bias::Right, &());
let overshoot = self.0 - cursor.start().0 .0;
InlayOffset(cursor.start().1 .0 + overshoot)
@@ -1240,18 +1263,30 @@ impl Sub for FoldOffset {
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.output.len;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.input.lines;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.input.len;
}

View File

@@ -97,6 +97,10 @@ struct TransformSummary {
impl sum_tree::Summary for TransformSummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, _: &()) {
self.input += &other.input;
self.output += &other.output;
@@ -137,6 +141,10 @@ impl SubAssign for InlayOffset {
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.output.len;
}
@@ -162,18 +170,30 @@ impl Sub for InlayPoint {
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += &summary.output.lines;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
*self += &summary.input.len;
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
*self += &summary.input.lines;
}
@@ -475,8 +495,8 @@ impl InlayMap {
(snapshot.clone(), Vec::new())
} else {
let mut inlay_edits = Patch::default();
let mut new_transforms = SumTree::new();
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>();
let mut new_transforms = SumTree::default();
let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&());
let mut buffer_edits_iter = buffer_edits.iter().peekable();
while let Some(buffer_edit) = buffer_edits_iter.next() {
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
@@ -693,7 +713,7 @@ impl InlaySnapshot {
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
let mut cursor = self
.transforms
.cursor::<(InlayOffset, (InlayPoint, usize))>();
.cursor::<(InlayOffset, (InlayPoint, usize))>(&());
cursor.seek(&offset, Bias::Right, &());
let overshoot = offset.0 - cursor.start().0 .0;
match cursor.item() {
@@ -723,7 +743,7 @@ impl InlaySnapshot {
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
let mut cursor = self
.transforms
.cursor::<(InlayPoint, (InlayOffset, Point))>();
.cursor::<(InlayPoint, (InlayOffset, Point))>(&());
cursor.seek(&point, Bias::Right, &());
let overshoot = point.0 - cursor.start().0 .0;
match cursor.item() {
@@ -741,9 +761,8 @@ impl InlaySnapshot {
None => self.len(),
}
}
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
cursor.seek(&point, Bias::Right, &());
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
@@ -754,9 +773,8 @@ impl InlaySnapshot {
None => self.buffer.max_point(),
}
}
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
cursor.seek(&offset, Bias::Right, &());
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
@@ -769,7 +787,7 @@ impl InlaySnapshot {
}
pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset {
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>();
let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&());
cursor.seek(&offset, Bias::Left, &());
loop {
match cursor.item() {
@@ -801,9 +819,8 @@ impl InlaySnapshot {
}
}
}
pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&());
cursor.seek(&point, Bias::Left, &());
loop {
match cursor.item() {
@@ -837,7 +854,7 @@ impl InlaySnapshot {
}
pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
cursor.seek(&point, Bias::Left, &());
loop {
match cursor.item() {
@@ -934,7 +951,7 @@ impl InlaySnapshot {
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
let mut summary = TextSummary::default();
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
cursor.seek(&range.start, Bias::Right, &());
let overshoot = range.start.0 - cursor.start().0 .0;
@@ -982,7 +999,7 @@ impl InlaySnapshot {
}
pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
let inlay_point = InlayPoint::new(row, 0);
cursor.seek(&inlay_point, Bias::Left, &());
@@ -1024,7 +1041,7 @@ impl InlaySnapshot {
language_aware: bool,
highlights: Highlights<'a>,
) -> InlayChunks<'a> {
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>();
let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&());
cursor.seek(&range.start, Bias::Right, &());
let mut highlight_endpoints = Vec::new();

View File

@@ -204,7 +204,7 @@ impl WrapMap {
}
} else {
let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
self.snapshot.transforms = SumTree::new();
self.snapshot.transforms = SumTree::default();
let summary = self.snapshot.tab_snapshot.text_summary();
if !summary.lines.is_zero() {
self.snapshot
@@ -303,7 +303,7 @@ impl WrapMap {
impl WrapSnapshot {
fn new(tab_snapshot: TabSnapshot) -> Self {
let mut transforms = SumTree::new();
let mut transforms = SumTree::default();
let extent = tab_snapshot.text_summary();
if !extent.lines.is_zero() {
transforms.push(Transform::isomorphic(extent), &());
@@ -324,7 +324,7 @@ impl WrapSnapshot {
if tab_edits.is_empty() {
new_transforms = self.transforms.clone();
} else {
let mut old_cursor = self.transforms.cursor::<TabPoint>();
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
let mut tab_edits_iter = tab_edits.iter().peekable();
new_transforms =
@@ -424,7 +424,7 @@ impl WrapSnapshot {
new_transforms = self.transforms.clone();
} else {
let mut row_edits = row_edits.into_iter().peekable();
let mut old_cursor = self.transforms.cursor::<TabPoint>();
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
new_transforms = old_cursor.slice(
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
@@ -537,8 +537,8 @@ impl WrapSnapshot {
fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
let mut wrap_edits = Vec::new();
let mut old_cursor = self.transforms.cursor::<TransformSummary>();
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>();
let mut old_cursor = self.transforms.cursor::<TransformSummary>(&());
let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(&());
for mut tab_edit in tab_edits.iter().cloned() {
tab_edit.old.start.0.column = 0;
tab_edit.old.end.0 += Point::new(1, 0);
@@ -579,7 +579,7 @@ impl WrapSnapshot {
) -> WrapChunks<'a> {
let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0);
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
transforms.seek(&output_start, Bias::Right, &());
let mut input_start = TabPoint(transforms.start().1 .0);
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
@@ -606,7 +606,7 @@ impl WrapSnapshot {
}
pub fn line_len(&self, row: u32) -> u32 {
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
if cursor
.item()
@@ -626,7 +626,7 @@ impl WrapSnapshot {
}
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
let mut cursor = self.transforms.cursor::<WrapPoint>();
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
cursor.item().and_then(|transform| {
if transform.is_isomorphic() {
@@ -642,7 +642,7 @@ impl WrapSnapshot {
}
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>();
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
let mut input_row = transforms.start().1.row();
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
@@ -662,7 +662,7 @@ impl WrapSnapshot {
}
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&point, Bias::Right, &());
let mut tab_point = cursor.start().1 .0;
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
@@ -680,14 +680,14 @@ impl WrapSnapshot {
}
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>();
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(&());
cursor.seek(&point, Bias::Right, &());
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
}
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
if bias == Bias::Left {
let mut cursor = self.transforms.cursor::<WrapPoint>();
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
cursor.seek(&point, Bias::Right, &());
if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
point = *cursor.start();
@@ -705,7 +705,7 @@ impl WrapSnapshot {
*point.column_mut() = 0;
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&point, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -725,7 +725,7 @@ impl WrapSnapshot {
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
point.0 += Point::new(1, 0);
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&point, Bias::Right, &());
while let Some(transform) = cursor.item() {
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
@@ -747,7 +747,7 @@ impl WrapSnapshot {
);
{
let mut transforms = self.transforms.cursor::<()>().peekable();
let mut transforms = self.transforms.cursor::<()>(&()).peekable();
while let Some(transform) = transforms.next() {
if let Some(next_transform) = transforms.peek() {
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
@@ -982,6 +982,10 @@ impl WrapPoint {
impl sum_tree::Summary for TransformSummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, _: &()) {
self.input += &other.input;
self.output += &other.output;
@@ -989,6 +993,10 @@ impl sum_tree::Summary for TransformSummary {
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += summary.input.lines;
}
@@ -1001,6 +1009,10 @@ impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoi
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
self.0 += summary.output.lines;
}

View File

@@ -412,6 +412,19 @@ impl Default for EditorStyle {
}
}
pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
let show_background = all_language_settings(None, cx)
.language(None)
.inlay_hints
.show_background;
HighlightStyle {
color: Some(cx.theme().status().hint),
background_color: show_background.then(|| cx.theme().status().hint_background),
..HighlightStyle::default()
}
}
type CompletionId = usize;
#[derive(Clone, Debug)]
@@ -6736,22 +6749,22 @@ impl Editor {
let mut line_prefix = indent_size.chars().collect::<String>();
if selection.is_empty() {
if let Some(comment_prefix) =
buffer
.language_scope_at(selection.head())
.and_then(|language| {
language
.line_comment_prefixes()
.iter()
.find(|prefix| buffer.contains_str_at(indent_end, prefix))
.cloned()
})
{
line_prefix.push_str(&comment_prefix);
should_rewrap = true;
}
if let Some(comment_prefix) =
buffer
.language_scope_at(selection.head())
.and_then(|language| {
language
.line_comment_prefixes()
.iter()
.find(|prefix| buffer.contains_str_at(indent_end, prefix))
.cloned()
})
{
line_prefix.push_str(&comment_prefix);
should_rewrap = true;
}
if selection.is_empty() {
'expand_upwards: while start_row > 0 {
let prev_row = start_row - 1;
if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
@@ -10034,9 +10047,8 @@ impl Editor {
syntax: cx.editor_style.syntax.clone(),
status: cx.editor_style.status.clone(),
inlay_hints_style: HighlightStyle {
color: Some(cx.theme().status().hint),
font_weight: Some(FontWeight::BOLD),
..HighlightStyle::default()
..make_inlay_hints_style(cx)
},
suggestions_style: HighlightStyle {
color: Some(cx.theme().status().predictive),
@@ -12992,10 +13004,7 @@ impl Render for Editor {
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
syntax: cx.theme().syntax().clone(),
status: cx.theme().status().clone(),
inlay_hints_style: HighlightStyle {
color: Some(cx.theme().status().hint),
..HighlightStyle::default()
},
inlay_hints_style: make_inlay_hints_style(cx),
suggestions_style: HighlightStyle {
color: Some(cx.theme().status().predictive),
..HighlightStyle::default()

View File

@@ -4017,6 +4017,39 @@ async fn test_rewrap(cx: &mut TestAppContext) {
cx.assert_editor_state(wrapped_text);
}
// Test that rewrapping works inside of a selection
{
let language = Arc::new(Language::new(
LanguageConfig {
line_comments: vec!["// ".into()],
..LanguageConfig::default()
},
None,
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
let unwrapped_text = indoc! {"
«// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
"};
let wrapped_text = indoc! {"
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
// auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
// tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
// Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
// vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
// et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
// porttitor id. Aliquam id accumsan eros.ˇ
"};
cx.set_state(unwrapped_text);
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
}
// Test that cursors that expand to the same region are collapsed.
{
let language = Arc::new(Language::new(

View File

@@ -2079,13 +2079,13 @@ impl EditorElement {
.id(("path excerpt header", EntityId::from(block_id)))
.w_full()
.px(header_padding)
.pt(header_padding)
.child(
h_flex()
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.id("path header block")
.h(2. * cx.line_height())
.pl(gpui::px(12.))
.pr(gpui::px(8.))
.px(gpui::px(12.))
.rounded_md()
.shadow_md()
.border_1()

View File

@@ -37,12 +37,20 @@ impl sum_tree::Item for GitBlameEntry {
impl sum_tree::Summary for GitBlameEntrySummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &Self, _cx: &()) {
self.rows += summary.rows;
}
}
impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a GitBlameEntrySummary, _cx: &()) {
*self += summary.rows;
}
@@ -191,7 +199,7 @@ impl GitBlame {
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
self.sync(cx);
let mut cursor = self.entries.cursor::<u32>();
let mut cursor = self.entries.cursor::<u32>(&());
rows.into_iter().map(move |row| {
let row = row?;
cursor.seek_forward(&row.0, Bias::Right, &());
@@ -249,8 +257,8 @@ impl GitBlame {
})
.peekable();
let mut new_entries = SumTree::new();
let mut cursor = self.entries.cursor::<u32>();
let mut new_entries = SumTree::default();
let mut cursor = self.entries.cursor::<u32>(&());
while let Some(mut edit) = row_edits.next() {
while let Some(next_edit) = row_edits.peek() {

View File

@@ -1205,6 +1205,7 @@ mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});

View File

@@ -1337,6 +1337,7 @@ mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});

View File

@@ -337,7 +337,7 @@ impl InlayHintCache {
/// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
/// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
/// followed by the delayed queries of the same range above and below the visible one.
/// This way, concequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
/// This way, subsequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
pub(super) fn spawn_hint_refresh(
&mut self,
reason_description: &'static str,
@@ -1296,6 +1296,7 @@ pub mod tests {
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: allowed_hint_kinds.contains(&None),
show_background: false,
})
});
@@ -1428,6 +1429,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -1547,6 +1549,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -1777,6 +1780,7 @@ pub mod tests {
show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)),
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: allowed_hint_kinds.contains(&None),
show_background: false,
})
});
@@ -1941,6 +1945,7 @@ pub mod tests {
show_parameter_hints: new_allowed_hint_kinds
.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: new_allowed_hint_kinds.contains(&None),
show_background: false,
})
});
cx.executor().run_until_parked();
@@ -1987,6 +1992,7 @@ pub mod tests {
show_parameter_hints: another_allowed_hint_kinds
.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: another_allowed_hint_kinds.contains(&None),
show_background: false,
})
});
cx.executor().run_until_parked();
@@ -2047,6 +2053,7 @@ pub mod tests {
show_parameter_hints: final_allowed_hint_kinds
.contains(&Some(InlayHintKind::Parameter)),
show_other_hints: final_allowed_hint_kinds.contains(&None),
show_background: false,
})
});
cx.executor().run_until_parked();
@@ -2122,6 +2129,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -2256,6 +2264,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -2551,6 +2560,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -2902,6 +2912,7 @@ pub mod tests {
show_type_hints: false,
show_parameter_hints: false,
show_other_hints: false,
show_background: false,
})
});
@@ -3096,6 +3107,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
cx.executor().run_until_parked();
@@ -3131,6 +3143,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -3225,6 +3238,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -3305,6 +3319,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
cx.executor().run_until_parked();

38
crates/evals/Cargo.toml Normal file
View File

@@ -0,0 +1,38 @@
[package]
name = "evals"
description = "Evaluations for Zed's AI features"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[[bin]]
name = "eval"
path = "src/eval.rs"
[dependencies]
clap.workspace = true
anyhow.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
env_logger.workspace = true
feature_flags.workspace = true
fs.workspace = true
git.workspace = true
gpui.workspace = true
isahc_http_client.workspace = true
language.workspace = true
languages.workspace = true
http_client.workspace = true
open_ai.workspace = true
project.workspace = true
settings.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
semantic_index.workspace = true
node_runtime.workspace = true

14
crates/evals/build.rs Normal file
View File

@@ -0,0 +1,14 @@
fn main() {
if cfg!(target_os = "macos") {
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
println!("cargo:rerun-if-env-changed=ZED_BUNDLE");
if std::env::var("ZED_BUNDLE").ok().as_deref() == Some("true") {
// Find WebRTC.framework in the Frameworks folder when running as part of an application bundle.
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
} else {
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
}
}
}

644
crates/evals/src/eval.rs Normal file
View File

@@ -0,0 +1,644 @@
use ::fs::{Fs, RealFs};
use anyhow::Result;
use clap::Parser;
use client::{Client, UserStore};
use clock::RealSystemClock;
use collections::BTreeMap;
use feature_flags::FeatureFlagAppExt as _;
use git::GitHostingProviderRegistry;
use gpui::{AsyncAppContext, BackgroundExecutor, Context, Model};
use http_client::{HttpClient, Method};
use language::LanguageRegistry;
use node_runtime::FakeNodeRuntime;
use open_ai::OpenAiEmbeddingModel;
use project::Project;
use semantic_index::{OpenAiEmbeddingProvider, ProjectIndex, SemanticDb, Status};
use serde::{Deserialize, Serialize};
use settings::SettingsStore;
use smol::channel::bounded;
use smol::io::AsyncReadExt;
use smol::Timer;
use std::ops::RangeInclusive;
use std::time::Duration;
use std::{
fs,
path::Path,
process::{exit, Command, Stdio},
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
};
const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net";
const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos";
const EVAL_DB_PATH: &'static str = "target/eval_db";
const SEARCH_RESULT_LIMIT: usize = 8;
const SKIP_EVAL_PATH: &'static str = ".skip_eval";
#[derive(clap::Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Subcommand)]
enum Commands {
Fetch {},
Run {
#[arg(long)]
repo: Option<String>,
},
}
#[derive(Clone, Deserialize, Serialize)]
struct EvaluationProject {
repo: String,
sha: String,
queries: Vec<EvaluationQuery>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct EvaluationQuery {
query: String,
expected_results: Vec<EvaluationSearchResult>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
struct EvaluationSearchResult {
file: String,
lines: RangeInclusive<u32>,
}
#[derive(Clone, Deserialize, Serialize)]
struct EvaluationProjectOutcome {
repo: String,
sha: String,
queries: Vec<EvaluationQueryOutcome>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct EvaluationQueryOutcome {
repo: String,
query: String,
expected_results: Vec<EvaluationSearchResult>,
actual_results: Vec<EvaluationSearchResult>,
covered_file_count: usize,
overlapped_result_count: usize,
covered_result_count: usize,
total_result_count: usize,
covered_result_indices: Vec<usize>,
}
fn main() -> Result<()> {
let cli = Cli::parse();
env_logger::init();
gpui::App::headless().run(move |cx| {
let executor = cx.background_executor().clone();
let client = isahc_http_client::IsahcHttpClient::new(None, None);
cx.set_http_client(client.clone());
match cli.command {
Commands::Fetch {} => {
executor
.clone()
.spawn(async move {
if let Err(err) = fetch_evaluation_resources(client, &executor).await {
eprintln!("Error: {}", err);
exit(1);
}
exit(0);
})
.detach();
}
Commands::Run { repo } => {
cx.spawn(|mut cx| async move {
if let Err(err) = run_evaluation(repo, &executor, &mut cx).await {
eprintln!("Error: {}", err);
exit(1);
}
exit(0);
})
.detach();
}
}
});
Ok(())
}
async fn fetch_evaluation_resources(
http_client: Arc<dyn HttpClient>,
executor: &BackgroundExecutor,
) -> Result<()> {
fetch_code_search_net_resources(&*http_client).await?;
fetch_eval_repos(executor, &*http_client).await?;
Ok(())
}
async fn fetch_code_search_net_resources(http_client: &dyn HttpClient) -> Result<()> {
eprintln!("Fetching CodeSearchNet evaluations...");
let annotations_url = "https://raw.githubusercontent.com/github/CodeSearchNet/master/resources/annotationStore.csv";
let dataset_dir = Path::new(CODESEARCH_NET_DIR);
fs::create_dir_all(&dataset_dir).expect("failed to create CodeSearchNet directory");
// Fetch the annotations CSV, which contains the human-annotated search relevances
let annotations_path = dataset_dir.join("annotations.csv");
let annotations_csv_content = if annotations_path.exists() {
fs::read_to_string(&annotations_path).expect("failed to read annotations")
} else {
let response = http_client
.get(annotations_url, Default::default(), true)
.await
.expect("failed to fetch annotations csv");
let mut body = String::new();
response
.into_body()
.read_to_string(&mut body)
.await
.expect("failed to read annotations.csv response");
fs::write(annotations_path, &body).expect("failed to write annotations.csv");
body
};
// Parse the annotations CSV. Skip over queries with zero relevance.
let rows = annotations_csv_content.lines().filter_map(|line| {
let mut values = line.split(',');
let _language = values.next()?;
let query = values.next()?;
let github_url = values.next()?;
let score = values.next()?;
if score == "0" {
return None;
}
let url_path = github_url.strip_prefix("https://github.com/")?;
let (url_path, hash) = url_path.split_once('#')?;
let (repo_name, url_path) = url_path.split_once("/blob/")?;
let (sha, file_path) = url_path.split_once('/')?;
let line_range = if let Some((start, end)) = hash.split_once('-') {
start.strip_prefix("L")?.parse::<u32>().ok()?..=end.strip_prefix("L")?.parse().ok()?
} else {
let row = hash.strip_prefix("L")?.parse().ok()?;
row..=row
};
Some((repo_name, sha, query, file_path, line_range))
});
// Group the annotations by repo and sha.
let mut evaluations_by_repo = BTreeMap::new();
for (repo_name, sha, query, file_path, lines) in rows {
let evaluation_project = evaluations_by_repo
.entry((repo_name, sha))
.or_insert_with(|| EvaluationProject {
repo: repo_name.to_string(),
sha: sha.to_string(),
queries: Vec::new(),
});
let ix = evaluation_project
.queries
.iter()
.position(|entry| entry.query == query)
.unwrap_or_else(|| {
evaluation_project.queries.push(EvaluationQuery {
query: query.to_string(),
expected_results: Vec::new(),
});
evaluation_project.queries.len() - 1
});
let results = &mut evaluation_project.queries[ix].expected_results;
let result = EvaluationSearchResult {
file: file_path.to_string(),
lines,
};
if !results.contains(&result) {
results.push(result);
}
}
let evaluations = evaluations_by_repo.into_values().collect::<Vec<_>>();
let evaluations_path = dataset_dir.join("evaluations.json");
fs::write(
&evaluations_path,
serde_json::to_vec_pretty(&evaluations).unwrap(),
)
.unwrap();
eprintln!(
"Fetched CodeSearchNet evaluations into {}",
evaluations_path.display()
);
Ok(())
}
async fn run_evaluation(
only_repo: Option<String>,
executor: &BackgroundExecutor,
cx: &mut AsyncAppContext,
) -> Result<()> {
let mut http_client = None;
cx.update(|cx| {
let mut store = SettingsStore::new(cx);
store
.set_default_settings(settings::default_settings().as_ref(), cx)
.unwrap();
cx.set_global(store);
client::init_settings(cx);
language::init(cx);
Project::init_settings(cx);
http_client = Some(cx.http_client());
cx.update_flags(false, vec![]);
})
.unwrap();
let http_client = http_client.unwrap();
let dataset_dir = Path::new(CODESEARCH_NET_DIR);
let evaluations_path = dataset_dir.join("evaluations.json");
let repos_dir = Path::new(EVAL_REPOS_DIR);
let db_path = Path::new(EVAL_DB_PATH);
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>;
let clock = Arc::new(RealSystemClock);
let client = cx
.update(|cx| {
Client::new(
clock,
Arc::new(http_client::HttpClientWithUrl::new(
http_client.clone(),
"https://zed.dev",
None,
)),
cx,
)
})
.unwrap();
let user_store = cx
.new_model(|cx| UserStore::new(client.clone(), cx))
.unwrap();
let node_runtime = Arc::new(FakeNodeRuntime {});
let evaluations = fs::read(&evaluations_path).expect("failed to read evaluations.json");
let evaluations: Vec<EvaluationProject> = serde_json::from_slice(&evaluations).unwrap();
let embedding_provider = Arc::new(OpenAiEmbeddingProvider::new(
http_client.clone(),
OpenAiEmbeddingModel::TextEmbedding3Small,
open_ai::OPEN_AI_API_URL.to_string(),
api_key,
));
let language_registry = Arc::new(LanguageRegistry::new(executor.clone()));
cx.update(|cx| languages::init(language_registry.clone(), node_runtime.clone(), cx))
.unwrap();
let mut covered_result_count = 0;
let mut overlapped_result_count = 0;
let mut covered_file_count = 0;
let mut total_result_count = 0;
eprint!("Running evals.");
for evaluation_project in evaluations {
if only_repo
.as_ref()
.map_or(false, |only_repo| only_repo != &evaluation_project.repo)
{
continue;
}
eprint!("\r\x1B[2K");
eprint!(
"Running evals. {}/{} covered. {}/{} overlapped. {}/{} files captured. Project: {}...",
covered_result_count,
total_result_count,
overlapped_result_count,
total_result_count,
covered_file_count,
total_result_count,
evaluation_project.repo
);
let repo_db_path =
db_path.join(format!("{}.db", evaluation_project.repo.replace('/', "_")));
let mut semantic_index = SemanticDb::new(repo_db_path, embedding_provider.clone(), cx)
.await
.unwrap();
let repo_dir = repos_dir.join(&evaluation_project.repo);
if !repo_dir.exists() || repo_dir.join(SKIP_EVAL_PATH).exists() {
eprintln!("Skipping {}: directory not found", evaluation_project.repo);
continue;
}
let project = cx
.update(|cx| {
Project::local(
client.clone(),
node_runtime.clone(),
user_store.clone(),
language_registry.clone(),
fs.clone(),
None,
cx,
)
})
.unwrap();
let (worktree, _) = project
.update(cx, |project, cx| {
project.find_or_create_worktree(repo_dir, true, cx)
})?
.await?;
worktree
.update(cx, |worktree, _| {
worktree.as_local().unwrap().scan_complete()
})
.unwrap()
.await;
let project_index = cx
.update(|cx| semantic_index.create_project_index(project.clone(), cx))
.unwrap();
wait_for_indexing_complete(&project_index, cx, Some(Duration::from_secs(120))).await;
for query in evaluation_project.queries {
let results = cx
.update(|cx| {
let project_index = project_index.read(cx);
project_index.search(query.query.clone(), SEARCH_RESULT_LIMIT, cx)
})
.unwrap()
.await
.unwrap();
let results = SemanticDb::load_results(results, &fs.clone(), &cx)
.await
.unwrap();
let mut project_covered_result_count = 0;
let mut project_overlapped_result_count = 0;
let mut project_covered_file_count = 0;
let mut covered_result_indices = Vec::new();
for expected_result in &query.expected_results {
let mut file_matched = false;
let mut range_overlapped = false;
let mut range_covered = false;
for (ix, result) in results.iter().enumerate() {
if result.path.as_ref() == Path::new(&expected_result.file) {
file_matched = true;
let start_matched =
result.row_range.contains(&expected_result.lines.start());
let end_matched = result.row_range.contains(&expected_result.lines.end());
if start_matched || end_matched {
range_overlapped = true;
}
if start_matched && end_matched {
range_covered = true;
covered_result_indices.push(ix);
break;
}
}
}
if range_covered {
project_covered_result_count += 1
};
if range_overlapped {
project_overlapped_result_count += 1
};
if file_matched {
project_covered_file_count += 1
};
}
let outcome_repo = evaluation_project.repo.clone();
let query_results = EvaluationQueryOutcome {
repo: outcome_repo,
query: query.query,
total_result_count: query.expected_results.len(),
covered_result_count: project_covered_result_count,
overlapped_result_count: project_overlapped_result_count,
covered_file_count: project_covered_file_count,
expected_results: query.expected_results,
actual_results: results
.iter()
.map(|result| EvaluationSearchResult {
file: result.path.to_string_lossy().to_string(),
lines: result.row_range.clone(),
})
.collect(),
covered_result_indices,
};
overlapped_result_count += query_results.overlapped_result_count;
covered_result_count += query_results.covered_result_count;
covered_file_count += query_results.covered_file_count;
total_result_count += query_results.total_result_count;
println!("{}", serde_json::to_string(&query_results).unwrap());
}
user_store
.update(cx, |_, _| {
drop(semantic_index);
drop(project);
drop(worktree);
drop(project_index);
})
.unwrap();
}
eprint!(
"Running evals. {}/{} covered. {}/{} overlapped. {}/{} files captured.",
covered_result_count,
total_result_count,
overlapped_result_count,
total_result_count,
covered_file_count,
total_result_count,
);
Ok(())
}
async fn wait_for_indexing_complete(
project_index: &Model<ProjectIndex>,
cx: &mut AsyncAppContext,
timeout: Option<Duration>,
) {
let (tx, rx) = bounded(1);
let subscription = cx.update(|cx| {
cx.subscribe(project_index, move |_, event, _| {
if let Status::Idle = event {
let _ = tx.try_send(*event);
}
})
});
let result = match timeout {
Some(timeout_duration) => {
smol::future::or(
async {
rx.recv().await.map_err(|_| ())?;
Ok(())
},
async {
Timer::after(timeout_duration).await;
Err(())
},
)
.await
}
None => rx.recv().await.map(|_| ()).map_err(|_| ()),
};
match result {
Ok(_) => (),
Err(_) => {
if let Some(timeout) = timeout {
eprintln!("Timeout: Indexing did not complete within {:?}", timeout);
}
}
}
drop(subscription);
}
async fn fetch_eval_repos(
executor: &BackgroundExecutor,
http_client: &dyn HttpClient,
) -> Result<()> {
let dataset_dir = Path::new(CODESEARCH_NET_DIR);
let evaluations_path = dataset_dir.join("evaluations.json");
let repos_dir = Path::new(EVAL_REPOS_DIR);
let evaluations = fs::read(&evaluations_path).expect("failed to read evaluations.json");
let evaluations: Vec<EvaluationProject> = serde_json::from_slice(&evaluations).unwrap();
eprint!("Fetching evaluation repositories...");
executor
.scoped(move |scope| {
let done_count = Arc::new(AtomicUsize::new(0));
let len = evaluations.len();
for chunk in evaluations.chunks(evaluations.len() / 8) {
let chunk = chunk.to_vec();
let done_count = done_count.clone();
scope.spawn(async move {
for EvaluationProject { repo, sha, .. } in chunk {
eprint!(
"\rFetching evaluation repositories ({}/{})...",
done_count.load(SeqCst),
len,
);
fetch_eval_repo(repo, sha, repos_dir, http_client).await;
done_count.fetch_add(1, SeqCst);
}
});
}
})
.await;
Ok(())
}
async fn fetch_eval_repo(
repo: String,
sha: String,
repos_dir: &Path,
http_client: &dyn HttpClient,
) {
let Some((owner, repo_name)) = repo.split_once('/') else {
return;
};
let repo_dir = repos_dir.join(owner).join(repo_name);
fs::create_dir_all(&repo_dir).unwrap();
let skip_eval_path = repo_dir.join(SKIP_EVAL_PATH);
if skip_eval_path.exists() {
return;
}
if let Ok(head_content) = fs::read_to_string(&repo_dir.join(".git").join("HEAD")) {
if head_content.trim() == sha {
return;
}
}
let repo_response = http_client
.send(
http_client::Request::builder()
.method(Method::HEAD)
.uri(format!("https://github.com/{}", repo))
.body(Default::default())
.expect(""),
)
.await
.expect("failed to check github repo");
if !repo_response.status().is_success() && !repo_response.status().is_redirection() {
fs::write(&skip_eval_path, "").unwrap();
eprintln!(
"Repo {repo} is no longer public ({:?}). Skipping",
repo_response.status()
);
return;
}
if !repo_dir.join(".git").exists() {
let init_output = Command::new("git")
.current_dir(&repo_dir)
.args(&["init"])
.output()
.unwrap();
if !init_output.status.success() {
eprintln!(
"Failed to initialize git repository for {}: {}",
repo,
String::from_utf8_lossy(&init_output.stderr)
);
return;
}
}
let url = format!("https://github.com/{}.git", repo);
Command::new("git")
.current_dir(&repo_dir)
.args(&["remote", "add", "-f", "origin", &url])
.stdin(Stdio::null())
.output()
.unwrap();
let fetch_output = Command::new("git")
.current_dir(&repo_dir)
.args(&["fetch", "--depth", "1", "origin", &sha])
.stdin(Stdio::null())
.output()
.unwrap();
if !fetch_output.status.success() {
eprintln!(
"Failed to fetch {} for {}: {}",
sha,
repo,
String::from_utf8_lossy(&fetch_output.stderr)
);
return;
}
let checkout_output = Command::new("git")
.current_dir(&repo_dir)
.args(&["checkout", &sha])
.output()
.unwrap();
if !checkout_output.status.success() {
eprintln!(
"Failed to checkout {} for {}: {}",
sha,
repo,
String::from_utf8_lossy(&checkout_output.stderr)
);
}
}

View File

@@ -57,6 +57,7 @@ task.workspace = true
serde_json_lenient.workspace = true
[dev-dependencies]
isahc_http_client.workspace = true
ctor.workspace = true
env_logger.workspace = true
parking_lot.workspace = true

View File

@@ -246,6 +246,7 @@ impl ExtensionBuilder {
.args(scanner_path.exists().then_some(scanner_path))
.output()
.context("failed to run clang")?;
if !clang_output.status.success() {
bail!(
"failed to compile {} parser with clang: {}",
@@ -431,6 +432,7 @@ impl ExtensionBuilder {
let body = BufReader::new(response.body_mut());
let body = GzipDecoder::new(body);
let tar = Archive::new(body);
tar.unpack(&tar_out_dir)
.await
.context("failed to unpack wasi-sdk archive")?;

View File

@@ -38,7 +38,7 @@ impl LspAdapter for ExtensionLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
_: Arc<Path>,
_: Option<Arc<Path>>,
delegate: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncAppContext,

View File

@@ -190,6 +190,7 @@ pub fn init(
None,
fs,
client.http_client().clone(),
client.http_client().clone(),
Some(client.telemetry().clone()),
node_runtime,
language_registry,
@@ -225,6 +226,7 @@ impl ExtensionStore {
build_dir: Option<PathBuf>,
fs: Arc<dyn Fs>,
http_client: Arc<HttpClientWithUrl>,
builder_client: Arc<dyn HttpClient>,
telemetry: Option<Arc<Telemetry>>,
node_runtime: Arc<dyn NodeRuntime>,
language_registry: Arc<LanguageRegistry>,
@@ -244,12 +246,7 @@ impl ExtensionStore {
extension_index: Default::default(),
installed_dir,
index_path,
builder: Arc::new(ExtensionBuilder::new(
// Construct a real HTTP client for the extension builder, as we
// don't want to use a fake one in the tests.
::http_client::client(None, http_client.proxy().cloned()),
build_dir,
)),
builder: Arc::new(ExtensionBuilder::new(builder_client, build_dir)),
outstanding_operations: Default::default(),
modified_extensions: Default::default(),
reload_complete_senders: Vec::new(),

View File

@@ -13,10 +13,12 @@ use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, SemanticVersion, TestAppContext};
use http_client::{FakeHttpClient, Response};
use indexed_docs::IndexedDocsRegistry;
use isahc_http_client::IsahcHttpClient;
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use node_runtime::FakeNodeRuntime;
use parking_lot::Mutex;
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
use release_channel::AppVersion;
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use snippet_provider::SnippetRegistry;
@@ -270,6 +272,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
None,
fs.clone(),
http_client.clone(),
http_client.clone(),
None,
node_runtime.clone(),
language_registry.clone(),
@@ -397,6 +400,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
None,
fs.clone(),
http_client.clone(),
http_client.clone(),
None,
node_runtime.clone(),
language_registry.clone(),
@@ -502,7 +506,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
http_request_count: 0,
}));
let http_client = FakeHttpClient::create({
let extension_client = FakeHttpClient::create({
let language_server_version = language_server_version.clone();
move |request| {
let language_server_version = language_server_version.clone();
@@ -564,13 +568,23 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
}
}
});
let user_agent = cx.update(|cx| {
format!(
"Zed/{} ({}; {})",
AppVersion::global(cx),
std::env::consts::OS,
std::env::consts::ARCH
)
});
let builder_client = IsahcHttpClient::new(None, Some(user_agent));
let extension_store = cx.new_model(|cx| {
ExtensionStore::new(
extensions_dir.clone(),
Some(cache_dir),
fs.clone(),
http_client.clone(),
extension_client.clone(),
builder_client,
None,
node_runtime,
language_registry.clone(),

View File

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

View File

@@ -7,13 +7,13 @@ use std::{
};
use ::fs::{copy_recursive, CopyOptions, Fs, RealFs};
use ::http_client::HttpClientWithProxy;
use anyhow::{anyhow, bail, Context, Result};
use clap::Parser;
use extension::{
extension_builder::{CompileExtensionOptions, ExtensionBuilder},
ExtensionManifest,
};
use isahc_http_client::IsahcHttpClient;
use language::LanguageConfig;
use theme::ThemeRegistry;
use tree_sitter::{Language, Query, WasmStore};
@@ -66,7 +66,13 @@ async fn main() -> Result<()> {
std::env::consts::OS,
std::env::consts::ARCH
);
let http_client = Arc::new(HttpClientWithProxy::new(Some(user_agent), None));
let http_client = Arc::new(
IsahcHttpClient::builder()
.default_header("User-Agent", user_agent)
.build()
.map(IsahcHttpClient::from)?,
);
let builder = ExtensionBuilder::new(http_client, scratch_dir);
builder
.compile_extension(

View File

@@ -44,8 +44,8 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
struct FeedbackRequestBody<'a> {
feedback_text: &'a str,
email: Option<String>,
metrics_id: Option<Arc<str>>,
installation_id: Option<Arc<str>>,
metrics_id: Option<Arc<str>>,
system_specs: SystemSpecs,
is_staff: bool,
}
@@ -296,16 +296,16 @@ impl FeedbackModal {
}
let telemetry = zed_client.telemetry();
let metrics_id = telemetry.metrics_id();
let installation_id = telemetry.installation_id();
let metrics_id = telemetry.metrics_id();
let is_staff = telemetry.is_staff();
let http_client = zed_client.http_client();
let feedback_endpoint = http_client.build_url("/api/feedback");
let request = FeedbackRequestBody {
feedback_text,
email,
metrics_id,
installation_id,
metrics_id,
system_specs,
is_staff: is_staff.unwrap_or(false),
};

View File

@@ -48,6 +48,10 @@ pub struct DiffHunkSummary {
impl sum_tree::Summary for DiffHunkSummary {
type Context = text::BufferSnapshot;
fn zero(_cx: &Self::Context) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
self.buffer_range.start = self
.buffer_range
@@ -63,17 +67,11 @@ pub struct BufferDiff {
tree: SumTree<DiffHunk<Anchor>>,
}
impl Default for BufferDiff {
fn default() -> Self {
Self::new()
}
}
impl BufferDiff {
pub fn new() -> BufferDiff {
pub fn new(buffer: &BufferSnapshot) -> BufferDiff {
BufferDiff {
last_buffer_version: None,
tree: SumTree::new(),
tree: SumTree::new(buffer),
}
}
@@ -97,11 +95,13 @@ impl BufferDiff {
range: Range<Anchor>,
buffer: &'a BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
let mut cursor = self
.tree
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
let anchor_iter = std::iter::from_fn(move || {
cursor.next(buffer);
@@ -142,11 +142,13 @@ impl BufferDiff {
range: Range<Anchor>,
buffer: &'a BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
let mut cursor = self
.tree
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
std::iter::from_fn(move || {
cursor.prev(buffer);
@@ -171,11 +173,11 @@ impl BufferDiff {
#[cfg(test)]
fn clear(&mut self, buffer: &text::BufferSnapshot) {
self.last_buffer_version = Some(buffer.version().clone());
self.tree = SumTree::new();
self.tree = SumTree::new(buffer);
}
pub async fn update(&mut self, diff_base: &Rope, buffer: &text::BufferSnapshot) {
let mut tree = SumTree::new();
let mut tree = SumTree::new(buffer);
let diff_base_text = diff_base.to_string();
let buffer_text = buffer.as_rope().to_string();
@@ -351,7 +353,7 @@ mod tests {
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiff::new();
let mut diff = BufferDiff::new(&buffer);
smol::block_on(diff.update(&diff_base_rope, &buffer));
assert_hunks(
diff.hunks(&buffer),
@@ -412,7 +414,7 @@ mod tests {
.unindent();
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiff::new();
let mut diff = BufferDiff::new(&buffer);
smol::block_on(diff.update(&diff_base_rope, &buffer));
assert_eq!(diff.hunks(&buffer).count(), 8);

View File

@@ -18,7 +18,6 @@ futures.workspace = true
git.workspace = true
gpui.workspace = true
http_client.workspace = true
isahc.workspace = true
regex.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

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

View File

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

View File

@@ -11,13 +11,13 @@ license = "Apache-2.0"
workspace = true
[features]
default = []
default = ["http_client"]
test-support = [
"backtrace",
"collections/test-support",
"rand",
"util/test-support",
"http_client/test-support",
"http_client?/test-support",
]
runtime_shaders = []
macos-blade = ["blade-graphics", "blade-macros", "blade-util", "bytemuck"]
@@ -40,7 +40,7 @@ derive_more.workspace = true
etagere = "0.2"
futures.workspace = true
gpui_macros.workspace = true
http_client.workspace = true
http_client = { optional = true, workspace = true }
image = "0.25.1"
itertools.workspace = true
linkme = "0.3"
@@ -50,7 +50,7 @@ parking = "2.0.0"
parking_lot.workspace = true
postage.workspace = true
profiling.workspace = true
rand = { optional = true, workspace = true}
rand = { optional = true, workspace = true }
raw-window-handle = "0.6"
refineable.workspace = true
resvg = { version = "0.41.0", default-features = false }
@@ -110,6 +110,7 @@ blade-graphics.workspace = true
blade-macros.workspace = true
blade-util.workspace = true
bytemuck = "1"
flume = "0.11"
[target.'cfg(target_os = "linux")'.dependencies]
as-raw-xcb-connection = "1"
@@ -117,7 +118,6 @@ ashpd.workspace = true
calloop = "0.13.0"
calloop-wayland-source = "0.3.0"
cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "542b20c" }
flume = "0.11"
wayland-backend = { version = "0.3.3", features = ["client_system", "dlopen"] }
wayland-client = { version = "0.31.2" }
wayland-cursor = "0.31.1"

View File

@@ -131,6 +131,7 @@ fn main() {
PathBuf::from_str("crates/gpui/examples/image/app-icon.png").unwrap(),
),
remote_resource: "https://picsum.photos/512/512".into(),
asset_resource: "image/color.svg".into(),
})
})

View File

@@ -117,7 +117,7 @@ impl App {
Self(AppContext::new(
current_platform(false),
Arc::new(()),
http_client::client(None, None),
Arc::new(NullHttpClient),
))
}
@@ -128,7 +128,7 @@ impl App {
Self(AppContext::new(
current_platform(true),
Arc::new(()),
http_client::client(None, None),
Arc::new(NullHttpClient),
))
}
@@ -142,6 +142,14 @@ impl App {
self
}
/// Set the http client for the application
pub fn with_http_client(self, http_client: Arc<dyn HttpClient>) -> Self {
let mut context_lock = self.0.borrow_mut();
context_lock.http_client = http_client;
drop(context_lock);
self
}
/// Start the application. The provided callback will be called once the
/// app is fully launched.
pub fn run<F>(self, on_finish_launching: F)
@@ -1512,3 +1520,22 @@ pub struct KeystrokeEvent {
/// The action that was resolved for the keystroke, if any
pub action: Option<Box<dyn Action>>,
}
struct NullHttpClient;
impl HttpClient for NullHttpClient {
fn send_with_redirect_policy(
&self,
_req: http_client::Request<http_client::AsyncBody>,
_follow_redirects: bool,
) -> futures::future::BoxFuture<
'static,
Result<http_client::Response<http_client::AsyncBody>, anyhow::Error>,
> {
async move { Err(anyhow!("No HttpClient available")) }.boxed()
}
fn proxy(&self) -> Option<&http_client::Uri> {
None
}
}

View File

@@ -345,7 +345,10 @@ impl Asset for ImageAsset {
let bytes = match source.clone() {
UriOrPath::Path(uri) => fs::read(uri.as_ref())?,
UriOrPath::Uri(uri) => {
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
let mut response = client
.get(uri.as_ref(), ().into(), true)
.await
.map_err(|e| ImageCacheError::Client(Arc::new(e)))?;
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
if !response.status().is_success() {
@@ -429,7 +432,7 @@ impl Asset for ImageAsset {
pub enum ImageCacheError {
/// An error that occurred while fetching an image from a remote source.
#[error("http error: {0}")]
Client(#[from] http_client::Error),
Client(#[from] Arc<anyhow::Error>),
/// An error that occurred while reading the image from disk.
#[error("IO error: {0}")]
Io(Arc<std::io::Error>),

View File

@@ -181,7 +181,7 @@ impl ListState {
last_layout_bounds: None,
last_padding: None,
render_item: Box::new(render_item),
items: SumTree::new(),
items: SumTree::default(),
logical_scroll_top: None,
alignment,
overdraw,
@@ -228,7 +228,7 @@ impl ListState {
) {
let state = &mut *self.0.borrow_mut();
let mut old_items = state.items.cursor::<Count>();
let mut old_items = state.items.cursor::<Count>(&());
let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
@@ -297,7 +297,7 @@ impl ListState {
scroll_top.item_ix = ix;
scroll_top.offset_in_item = px(0.);
} else {
let mut cursor = state.items.cursor::<ListItemSummary>();
let mut cursor = state.items.cursor::<ListItemSummary>(&());
cursor.seek(&Count(ix + 1), Bias::Right, &());
let bottom = cursor.start().height + padding.top;
let goal_top = px(0.).max(bottom - height + padding.bottom);
@@ -326,7 +326,7 @@ impl ListState {
return None;
}
let mut cursor = state.items.cursor::<(Count, Height)>();
let mut cursor = state.items.cursor::<(Count, Height)>(&());
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
@@ -348,7 +348,7 @@ impl ListState {
impl StateInner {
fn visible_range(&self, height: Pixels, scroll_top: &ListOffset) -> Range<usize> {
let mut cursor = self.items.cursor::<ListItemSummary>();
let mut cursor = self.items.cursor::<ListItemSummary>(&());
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
let start_y = cursor.start().height + scroll_top.offset_in_item;
cursor.seek_forward(&Height(start_y + height), Bias::Left, &());
@@ -378,7 +378,7 @@ impl StateInner {
if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
self.logical_scroll_top = None;
} else {
let mut cursor = self.items.cursor::<ListItemSummary>();
let mut cursor = self.items.cursor::<ListItemSummary>(&());
cursor.seek(&Height(new_scroll_top), Bias::Right, &());
let item_ix = cursor.start().count;
let offset_in_item = new_scroll_top - cursor.start().height;
@@ -418,7 +418,7 @@ impl StateInner {
}
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
let mut cursor = self.items.cursor::<ListItemSummary>();
let mut cursor = self.items.cursor::<ListItemSummary>(&());
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right, &());
cursor.start().height + logical_scroll_top.offset_in_item
}
@@ -445,7 +445,7 @@ impl StateInner {
AvailableSpace::MinContent,
);
let mut cursor = old_items.cursor::<Count>();
let mut cursor = old_items.cursor::<Count>(&());
// Render items after the scroll top, including those in the trailing overdraw
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
@@ -560,7 +560,7 @@ impl StateInner {
}
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
let mut cursor = old_items.cursor::<Count>();
let mut cursor = old_items.cursor::<Count>(&());
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
new_items.extend(measured_items, &());
cursor.seek(&Count(measured_range.end), Bias::Right, &());
@@ -573,7 +573,7 @@ impl StateInner {
if !rendered_focused_item {
let mut cursor = self
.items
.filter::<_, Count>(|summary| summary.has_focus_handles);
.filter::<_, Count>(&(), |summary| summary.has_focus_handles);
cursor.next(&());
while let Some(item) = cursor.item() {
if item.contains_focused(cx) {
@@ -629,7 +629,7 @@ impl StateInner {
offset_in_item: autoscroll_bounds.top() - item_origin.y,
});
} else if autoscroll_bounds.bottom() > bounds.bottom() {
let mut cursor = self.items.cursor::<Count>();
let mut cursor = self.items.cursor::<Count>(&());
cursor.seek(&Count(item.index), Bias::Right, &());
let mut height = bounds.size.height - padding.top - padding.bottom;
@@ -883,6 +883,10 @@ impl sum_tree::Item for ListItem {
impl sum_tree::Summary for ListItemSummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &Self, _: &()) {
self.count += summary.count;
self.rendered_count += summary.rendered_count;
@@ -893,12 +897,20 @@ impl sum_tree::Summary for ListItemSummary {
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
self.0 += summary.count;
}
}
impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) {
self.0 += summary.height;
}

View File

@@ -128,6 +128,7 @@ pub use executor::*;
pub use geometry::*;
pub use global::*;
pub use gpui_macros::{register_action, test, IntoElement, Render};
pub use http_client;
pub use input::*;
pub use interactive::*;
use key_dispatch::*;

View File

@@ -488,8 +488,8 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
let half_thickness = underline.thickness * 0.5;
let st = (input.position.xy - underline.bounds.origin) / underline.bounds.size.y - vec2<f32>(0.0, 0.5);
let frequency = M_PI_F * 3.0 * underline.thickness / 8.0;
let amplitude = 1.0 / (2.0 * underline.thickness);
let frequency = M_PI_F * 3.0 * underline.thickness / 3.0;
let amplitude = 1.0 / (4.0 * underline.thickness);
let sine = sin(st.x * frequency) * amplitude;
let dSine = cos(st.x * frequency) * amplitude * frequency;
let distance = (st.y - sine) / sqrt(1.0 + dSine * dSine);

View File

@@ -3,51 +3,39 @@ use std::{
time::Duration,
};
use anyhow::Context;
use async_task::Runnable;
use flume::Sender;
use parking::Parker;
use parking_lot::Mutex;
use util::ResultExt;
use windows::{
Foundation::TimeSpan,
System::{
DispatcherQueue, DispatcherQueueController, DispatcherQueueHandler,
Threading::{
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemOptions,
WorkItemPriority,
},
},
Win32::System::WinRT::{
CreateDispatcherQueueController, DispatcherQueueOptions, DQTAT_COM_NONE,
DQTYPE_THREAD_CURRENT,
System::Threading::{
ThreadPool, ThreadPoolTimer, TimerElapsedHandler, WorkItemHandler, WorkItemOptions,
WorkItemPriority,
},
Win32::{Foundation::HANDLE, System::Threading::SetEvent},
};
use crate::{PlatformDispatcher, TaskLabel};
use crate::{PlatformDispatcher, SafeHandle, TaskLabel};
pub(crate) struct WindowsDispatcher {
controller: DispatcherQueueController,
main_queue: DispatcherQueue,
main_sender: Sender<Runnable>,
dispatch_event: SafeHandle,
parker: Mutex<Parker>,
main_thread_id: ThreadId,
}
impl WindowsDispatcher {
pub(crate) fn new() -> Self {
let controller = unsafe {
let options = DispatcherQueueOptions {
dwSize: std::mem::size_of::<DispatcherQueueOptions>() as u32,
threadType: DQTYPE_THREAD_CURRENT,
apartmentType: DQTAT_COM_NONE,
};
CreateDispatcherQueueController(options).unwrap()
};
let main_queue = controller.DispatcherQueue().unwrap();
pub(crate) fn new(main_sender: Sender<Runnable>, dispatch_event: HANDLE) -> Self {
let dispatch_event = dispatch_event.into();
let parker = Mutex::new(Parker::new());
let main_thread_id = current().id();
WindowsDispatcher {
controller,
main_queue,
main_sender,
dispatch_event,
parker,
main_thread_id,
}
@@ -86,12 +74,6 @@ impl WindowsDispatcher {
}
}
impl Drop for WindowsDispatcher {
fn drop(&mut self) {
self.controller.ShutdownQueueAsync().log_err();
}
}
impl PlatformDispatcher for WindowsDispatcher {
fn is_main_thread(&self) -> bool {
current().id() == self.main_thread_id
@@ -105,14 +87,11 @@ impl PlatformDispatcher for WindowsDispatcher {
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
let handler = {
let mut task_wrapper = Some(runnable);
DispatcherQueueHandler::new(move || {
task_wrapper.take().unwrap().run();
Ok(())
})
};
self.main_queue.TryEnqueue(&handler).log_err();
self.main_sender
.send(runnable)
.context("Dispatch on main thread failed")
.log_err();
unsafe { SetEvent(*self.dispatch_event).log_err() };
}
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {

View File

@@ -177,6 +177,9 @@ fn handle_timer_msg(
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
for runnable in state_ptr.main_receiver.drain() {
runnable.run();
}
handle_paint_msg(handle, state_ptr)
} else {
None

View File

@@ -8,6 +8,7 @@ use std::{
use ::util::ResultExt;
use anyhow::{anyhow, Context, Result};
use async_task::Runnable;
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use parking_lot::RwLock;
@@ -46,6 +47,8 @@ pub(crate) struct WindowsPlatform {
raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
// The below members will never change throughout the entire lifecycle of the app.
icon: HICON,
main_receiver: flume::Receiver<Runnable>,
dispatch_event: HANDLE,
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
text_system: Arc<DirectWriteTextSystem>,
@@ -89,7 +92,9 @@ impl WindowsPlatform {
unsafe {
OleInitialize(None).expect("unable to initialize Windows OLE");
}
let dispatcher = Arc::new(WindowsDispatcher::new());
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let dispatch_event = unsafe { CreateEventW(None, false, false, None) }.unwrap();
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event));
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
let bitmap_factory = ManuallyDrop::new(unsafe {
@@ -113,6 +118,8 @@ impl WindowsPlatform {
state,
raw_window_handles,
icon,
main_receiver,
dispatch_event,
background_executor,
foreground_executor,
text_system,
@@ -176,6 +183,24 @@ impl WindowsPlatform {
lock.is_empty()
}
#[inline]
fn run_foreground_tasks(&self) {
for runnable in self.main_receiver.drain() {
runnable.run();
}
}
fn generate_creation_info(&self) -> WindowCreationInfo {
WindowCreationInfo {
icon: self.icon,
executor: self.foreground_executor.clone(),
current_cursor: self.state.borrow().current_cursor,
windows_version: self.windows_version,
validation_number: self.validation_number,
main_receiver: self.main_receiver.clone(),
}
}
}
impl Platform for WindowsPlatform {
@@ -197,16 +222,21 @@ impl Platform for WindowsPlatform {
begin_vsync(*vsync_event);
'a: loop {
let wait_result = unsafe {
MsgWaitForMultipleObjects(Some(&[*vsync_event]), false, INFINITE, QS_ALLINPUT)
MsgWaitForMultipleObjects(
Some(&[*vsync_event, self.dispatch_event]),
false,
INFINITE,
QS_ALLINPUT,
)
};
match wait_result {
// compositor clock ticked so we should draw a frame
WAIT_EVENT(0) => {
self.redraw_all();
}
WAIT_EVENT(0) => self.redraw_all(),
// foreground tasks are dispatched
WAIT_EVENT(1) => self.run_foreground_tasks(),
// Windows thread messages are posted
WAIT_EVENT(1) => {
WAIT_EVENT(2) => {
let mut msg = MSG::default();
unsafe {
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
@@ -230,6 +260,8 @@ impl Platform for WindowsPlatform {
}
}
}
// foreground tasks may have been queued in the message handlers
self.run_foreground_tasks();
}
_ => {
log::error!("Something went wrong while waiting {:?}", wait_result);
@@ -319,17 +351,7 @@ impl Platform for WindowsPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Result<Box<dyn PlatformWindow>> {
let lock = self.state.borrow();
let window = WindowsWindow::new(
handle,
options,
self.icon,
self.foreground_executor.clone(),
lock.current_cursor,
self.windows_version,
self.validation_number,
)?;
drop(lock);
let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle);
@@ -558,6 +580,15 @@ impl Drop for WindowsPlatform {
}
}
pub(crate) struct WindowCreationInfo {
pub(crate) icon: HICON,
pub(crate) executor: ForegroundExecutor,
pub(crate) current_cursor: HCURSOR,
pub(crate) windows_version: WindowsVersion,
pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<Runnable>,
}
fn open_target(target: &str) {
unsafe {
let ret = ShellExecuteW(
@@ -631,22 +662,33 @@ fn file_open_dialog(options: PathPromptOptions) -> Result<Option<Vec<PathBuf>>>
fn file_save_dialog(directory: PathBuf) -> Result<Option<PathBuf>> {
let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
if let Some(full_path) = directory.canonicalize().log_err() {
let full_path = full_path.to_string_lossy().to_string();
if !full_path.is_empty() {
let path_item: IShellItem =
unsafe { SHCreateItemFromParsingName(&HSTRING::from(&full_path), None)? };
unsafe { dialog.SetFolder(&path_item).log_err() };
if !directory.to_string_lossy().is_empty() {
if let Some(full_path) = directory.canonicalize().log_err() {
let full_path = full_path.to_string_lossy().to_string();
if !full_path.is_empty() {
let path_item: IShellItem =
unsafe { SHCreateItemFromParsingName(&HSTRING::from(&full_path), None)? };
unsafe { dialog.SetFolder(&path_item).log_err() };
}
}
}
unsafe {
dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC {
pszName: windows::core::w!("All files"),
pszSpec: windows::core::w!("*.*"),
}])?;
if dialog.Show(None).is_err() {
// User cancelled
return Ok(None);
}
}
let shell_item = unsafe { dialog.GetResult()? };
let file_path_string = unsafe { shell_item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
let file_path_string = unsafe {
let pwstr = shell_item.GetDisplayName(SIGDN_FILESYSPATH)?;
let string = pwstr.to_string()?;
CoTaskMemFree(Some(pwstr.0 as _));
string
};
Ok(Some(PathBuf::from(file_path_string)))
}

View File

@@ -12,6 +12,7 @@ use std::{
use ::util::ResultExt;
use anyhow::{Context, Result};
use async_task::Runnable;
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use raw_window_handle as rwh;
@@ -63,6 +64,7 @@ pub(crate) struct WindowsWindowStatePtr {
pub(crate) executor: ForegroundExecutor,
pub(crate) windows_version: WindowsVersion,
pub(crate) validation_number: usize,
pub(crate) main_receiver: flume::Receiver<Runnable>,
}
impl WindowsWindowState {
@@ -226,6 +228,7 @@ impl WindowsWindowStatePtr {
executor: context.executor.clone(),
windows_version: context.windows_version,
validation_number: context.validation_number,
main_receiver: context.main_receiver.clone(),
}))
}
}
@@ -253,18 +256,23 @@ struct WindowCreateContext {
current_cursor: HCURSOR,
windows_version: WindowsVersion,
validation_number: usize,
main_receiver: flume::Receiver<Runnable>,
}
impl WindowsWindow {
pub(crate) fn new(
handle: AnyWindowHandle,
params: WindowParams,
icon: HICON,
executor: ForegroundExecutor,
current_cursor: HCURSOR,
windows_version: WindowsVersion,
validation_number: usize,
creation_info: WindowCreationInfo,
) -> Result<Self> {
let WindowCreationInfo {
icon,
executor,
current_cursor,
windows_version,
validation_number,
main_receiver,
} = creation_info;
let classname = register_wnd_class(icon);
let hide_title_bar = params
.titlebar
@@ -305,6 +313,7 @@ impl WindowsWindow {
current_cursor,
windows_version,
validation_number,
main_receiver,
};
let lpparam = Some(&context as *const _ as *const _);
let creation_result = unsafe {

View File

@@ -16,13 +16,12 @@ path = "src/http_client.rs"
doctest = true
[dependencies]
http = "1.0.0"
http = "0.2"
anyhow.workspace = true
derive_more.workspace = true
futures.workspace = true
isahc.workspace = true
log.workspace = true
serde.workspace = true
serde_json.workspace = true
futures-lite.workspace = true
smol.workspace = true
url.workspace = true

View File

@@ -0,0 +1,109 @@
use std::{borrow::Cow, io::Read, pin::Pin, task::Poll};
use futures::{AsyncRead, AsyncReadExt};
/// Based on the implementation of AsyncBody in
/// https://github.com/sagebind/isahc/blob/5c533f1ef4d6bdf1fd291b5103c22110f41d0bf0/src/body/mod.rs
pub struct AsyncBody(pub Inner);
pub enum Inner {
/// An empty body.
Empty,
/// A body stored in memory.
SyncReader(std::io::Cursor<Cow<'static, [u8]>>),
/// An asynchronous reader.
AsyncReader(Pin<Box<dyn futures::AsyncRead + Send + Sync>>),
}
impl AsyncBody {
/// Create a new empty body.
///
/// An empty body represents the *absence* of a body, which is semantically
/// different than the presence of a body of zero length.
pub fn empty() -> Self {
Self(Inner::Empty)
}
/// Create a streaming body that reads from the given reader.
pub fn from_reader<R>(read: R) -> Self
where
R: AsyncRead + Send + Sync + 'static,
{
Self(Inner::AsyncReader(Box::pin(read)))
}
}
impl Default for AsyncBody {
fn default() -> Self {
Self(Inner::Empty)
}
}
impl From<()> for AsyncBody {
fn from(_: ()) -> Self {
Self(Inner::Empty)
}
}
impl From<Vec<u8>> for AsyncBody {
fn from(body: Vec<u8>) -> Self {
Self(Inner::SyncReader(std::io::Cursor::new(Cow::Owned(body))))
}
}
impl From<&'_ [u8]> for AsyncBody {
fn from(body: &[u8]) -> Self {
body.to_vec().into()
}
}
impl From<String> for AsyncBody {
fn from(body: String) -> Self {
body.into_bytes().into()
}
}
impl From<&'_ str> for AsyncBody {
fn from(body: &str) -> Self {
body.as_bytes().into()
}
}
impl<T: Into<Self>> From<Option<T>> for AsyncBody {
fn from(body: Option<T>) -> Self {
match body {
Some(body) => body.into(),
None => Self(Inner::Empty),
}
}
}
impl std::io::Read for AsyncBody {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.0 {
Inner::Empty => Ok(0),
Inner::SyncReader(cursor) => cursor.read(buf),
Inner::AsyncReader(async_reader) => smol::block_on(async_reader.read(buf)),
}
}
}
impl futures::AsyncRead for AsyncBody {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
// SAFETY: Standard Enum pin projection
let inner = unsafe { &mut self.get_unchecked_mut().0 };
match inner {
Inner::Empty => Poll::Ready(Ok(0)),
// Blocking call is over an in-memory buffer
Inner::SyncReader(cursor) => Poll::Ready(cursor.read(buf)),
Inner::AsyncReader(async_reader) => {
AsyncRead::poll_read(async_reader.as_mut(), cx, buf)
}
}
}
}

View File

@@ -34,7 +34,7 @@ pub async fn latest_github_release(
) -> Result<GithubRelease, anyhow::Error> {
let mut response = http
.get(
&format!("https://api.github.com/repos/{repo_name_with_owner}/releases"),
format!("https://api.github.com/repos/{repo_name_with_owner}/releases").as_str(),
Default::default(),
true,
)
@@ -91,13 +91,14 @@ pub async fn get_release_by_tag_name(
.context("error fetching latest release")?;
let mut body = Vec::new();
let status = response.status();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading latest release")?;
if response.status().is_client_error() {
if status.is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!(
"status error {}, response: {text:?}",

View File

@@ -1,46 +1,48 @@
mod async_body;
pub mod github;
pub use anyhow::{anyhow, Result};
pub use async_body::{AsyncBody, Inner};
use derive_more::Deref;
pub use http::{self, Method, Request, Response, StatusCode, Uri};
use futures::future::BoxFuture;
use futures_lite::FutureExt;
use isahc::config::{Configurable, RedirectPolicy};
pub use isahc::{
http::{Method, StatusCode, Uri},
AsyncBody, Error, HttpClient as IsahcHttpClient, Request, Response,
};
use http::request::Builder;
#[cfg(feature = "test-support")]
use std::fmt;
use std::{
sync::{Arc, Mutex},
time::Duration,
};
use std::sync::{Arc, Mutex};
pub use url::Url;
pub trait HttpClient: Send + Sync {
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>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>;
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>>;
fn get<'a>(
&'a self,
uri: &str,
body: AsyncBody,
follow_redirects: bool,
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
let request = isahc::Request::builder()
.redirect_policy(if follow_redirects {
RedirectPolicy::Follow
} else {
RedirectPolicy::None
})
.method(Method::GET)
.uri(uri)
.body(body);
) -> BoxFuture<'a, Result<Response<AsyncBody>, anyhow::Error>> {
let request = Builder::new().uri(uri).body(body);
match request {
Ok(request) => self.send(request),
Err(error) => async move { Err(error.into()) }.boxed(),
Ok(request) => Box::pin(async move {
self.send_with_redirect_policy(request, follow_redirects)
.await
.map_err(Into::into)
}),
Err(e) => Box::pin(async move { Err(e.into()) }),
}
}
@@ -48,15 +50,16 @@ pub trait HttpClient: Send + Sync {
&'a self,
uri: &str,
body: AsyncBody,
) -> BoxFuture<'a, Result<Response<AsyncBody>, Error>> {
let request = isahc::Request::builder()
.method(Method::POST)
) -> BoxFuture<'a, Result<Response<AsyncBody>, anyhow::Error>> {
let request = Builder::new()
.uri(uri)
.method(Method::POST)
.header("Content-Type", "application/json")
.body(body);
match request {
Ok(request) => self.send(request),
Err(error) => async move { Err(error.into()) }.boxed(),
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }),
Err(e) => Box::pin(async move { Err(e.into()) }),
}
}
@@ -73,29 +76,28 @@ pub struct HttpClientWithProxy {
impl HttpClientWithProxy {
/// Returns a new [`HttpClientWithProxy`] with the given proxy URL.
pub fn new(user_agent: Option<String>, proxy_url: Option<String>) -> Self {
let proxy_url = proxy_url
.and_then(|input| {
input
.parse::<Uri>()
.inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
.ok()
})
pub fn new(client: Arc<dyn HttpClient>, proxy_url: Option<String>) -> Self {
let proxy_uri = proxy_url
.and_then(|proxy| proxy.parse().ok())
.or_else(read_proxy_from_env);
Self::new_uri(client, proxy_uri)
}
pub fn new_uri(client: Arc<dyn HttpClient>, proxy_uri: Option<Uri>) -> Self {
Self {
client: client(user_agent, proxy_url.clone()),
proxy: proxy_url,
client,
proxy: proxy_uri,
}
}
}
impl HttpClient for HttpClientWithProxy {
fn send(
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req)
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
}
fn proxy(&self) -> Option<&Uri> {
@@ -104,11 +106,12 @@ impl HttpClient for HttpClientWithProxy {
}
impl HttpClient for Arc<HttpClientWithProxy> {
fn send(
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req)
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
}
fn proxy(&self) -> Option<&Uri> {
@@ -122,14 +125,35 @@ pub struct HttpClientWithUrl {
client: HttpClientWithProxy,
}
impl std::ops::Deref for HttpClientWithUrl {
type Target = HttpClientWithProxy;
fn deref(&self) -> &Self::Target {
&self.client
}
}
impl HttpClientWithUrl {
/// Returns a new [`HttpClientWithUrl`] with the given base URL.
pub fn new(
client: Arc<dyn HttpClient>,
base_url: impl Into<String>,
user_agent: Option<String>,
proxy_url: Option<String>,
) -> Self {
let client = HttpClientWithProxy::new(user_agent, proxy_url);
let client = HttpClientWithProxy::new(client, proxy_url);
Self {
base_url: Mutex::new(base_url.into()),
client,
}
}
pub fn new_uri(
client: Arc<dyn HttpClient>,
base_url: impl Into<String>,
proxy_uri: Option<Uri>,
) -> Self {
let client = HttpClientWithProxy::new_uri(client, proxy_uri);
Self {
base_url: Mutex::new(base_url.into()),
@@ -194,11 +218,12 @@ impl HttpClientWithUrl {
}
impl HttpClient for Arc<HttpClientWithUrl> {
fn send(
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req)
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
}
fn proxy(&self) -> Option<&Uri> {
@@ -207,11 +232,12 @@ impl HttpClient for Arc<HttpClientWithUrl> {
}
impl HttpClient for HttpClientWithUrl {
fn send(
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
self.client.send(req)
follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
self.client.send_with_redirect_policy(req, follow_redirects)
}
fn proxy(&self) -> Option<&Uri> {
@@ -219,26 +245,7 @@ impl HttpClient for HttpClientWithUrl {
}
}
pub fn client(user_agent: Option<String>, proxy: Option<Uri>) -> Arc<dyn HttpClient> {
let mut builder = isahc::HttpClient::builder()
// Some requests to Qwen2 models on Runpod can take 32+ seconds,
// especially if there's a cold boot involved. We may need to have
// those requests use a different http client, because global timeouts
// of 50 and 60 seconds, respectively, would be very high!
.connect_timeout(Duration::from_secs(5))
.low_speed_timeout(100, Duration::from_secs(5))
.proxy(proxy.clone());
if let Some(user_agent) = user_agent {
builder = builder.default_header("User-Agent", user_agent);
}
Arc::new(HttpClientWithProxy {
client: Arc::new(builder.build().unwrap()),
proxy,
})
}
fn read_proxy_from_env() -> Option<Uri> {
pub fn read_proxy_from_env() -> Option<Uri> {
const ENV_VARS: &[&str] = &[
"ALL_PROXY",
"all_proxy",
@@ -257,23 +264,9 @@ fn read_proxy_from_env() -> Option<Uri> {
None
}
impl HttpClient for isahc::HttpClient {
fn send(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
let client = self.clone();
Box::pin(async move { client.send_async(req).await })
}
fn proxy(&self) -> Option<&Uri> {
None
}
}
#[cfg(feature = "test-support")]
type FakeHttpHandler = Box<
dyn Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>>
dyn Fn(Request<AsyncBody>) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>>
+ Send
+ Sync
+ 'static,
@@ -288,7 +281,7 @@ pub struct FakeHttpClient {
impl FakeHttpClient {
pub fn create<Fut, F>(handler: F) -> Arc<HttpClientWithUrl>
where
Fut: futures::Future<Output = Result<Response<AsyncBody>, Error>> + Send + 'static,
Fut: futures::Future<Output = Result<Response<AsyncBody>, anyhow::Error>> + Send + 'static,
F: Fn(Request<AsyncBody>) -> Fut + Send + Sync + 'static,
{
Arc::new(HttpClientWithUrl {
@@ -330,12 +323,13 @@ impl fmt::Debug for FakeHttpClient {
#[cfg(feature = "test-support")]
impl HttpClient for FakeHttpClient {
fn send(
fn send_with_redirect_policy(
&self,
req: Request<AsyncBody>,
) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
_follow_redirects: bool,
) -> BoxFuture<'static, Result<Response<AsyncBody>, anyhow::Error>> {
let future = (self.handler)(req);
Box::pin(async move { future.await.map(Into::into) })
future
}
fn proxy(&self) -> Option<&Uri> {

View File

@@ -0,0 +1,22 @@
[package]
name = "isahc_http_client"
version = "0.1.0"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[features]
test-support = []
[lib]
path = "src/isahc_http_client.rs"
[dependencies]
anyhow.workspace = true
futures.workspace = true
http_client.workspace = true
isahc.workspace = true
util.workspace = true

View File

@@ -0,0 +1 @@
../../LICENSE-APACHE

View File

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

View File

@@ -722,7 +722,9 @@ impl Buffer {
capability: Capability,
) -> Self {
let saved_mtime = file.as_ref().and_then(|file| file.mtime());
let snapshot = buffer.snapshot();
let git_diff = git::diff::BufferDiff::new(&snapshot);
let syntax_map = Mutex::new(SyntaxMap::new(&snapshot));
Self {
saved_mtime,
saved_version: buffer.version(),
@@ -739,10 +741,10 @@ impl Buffer {
})
.map(Rope::from),
diff_base_version: 0,
git_diff: git::diff::BufferDiff::new(),
git_diff,
file,
capability,
syntax_map: Mutex::new(SyntaxMap::new()),
syntax_map,
parsing_in_background: false,
non_text_state_update_count: 0,
sync_parse_timeout: Duration::from_millis(1),
@@ -809,7 +811,7 @@ impl Buffer {
/// Assign a language to the buffer.
pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
self.non_text_state_update_count += 1;
self.syntax_map.lock().clear();
self.syntax_map.lock().clear(&self.text);
self.language = language;
self.reparse(cx);
cx.emit(BufferEvent::LanguageChanged);
@@ -3025,7 +3027,7 @@ impl BufferSnapshot {
let mut start = text.len();
let end = start + buffer_range.len();
// When multiple names are captured, then the matcheable text
// When multiple names are captured, then the matchable text
// includes the whitespace in between the names.
if !name_ranges.is_empty() {
start -= 1;

View File

@@ -382,7 +382,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
let buffer = cx.new_model(|cx| Buffer::local(text, cx));
// Spawn a task to format the buffer's whitespace.
// Pause so that the foratting task starts running.
// Pause so that the formatting task starts running.
let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
smol::future::yield_now().await;

View File

@@ -15,7 +15,7 @@ use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
/// The diagnostics are stored in a [`SumTree`], which allows this struct
/// to be cheaply copied, and allows for efficient retrieval of the
/// diagnostics that intersect a given range of the buffer.
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug)]
pub struct DiagnosticSet {
diagnostics: SumTree<DiagnosticEntry<Anchor>>,
}
@@ -135,7 +135,7 @@ impl DiagnosticSet {
{
let end_bias = if inclusive { Bias::Right } else { Bias::Left };
let range = buffer.anchor_before(range.start)..buffer.anchor_at(range.end, end_bias);
let mut cursor = self.diagnostics.filter::<_, ()>({
let mut cursor = self.diagnostics.filter::<_, ()>(buffer, {
move |summary: &Summary| {
let start_cmp = range.start.cmp(&summary.max_end, buffer);
let end_cmp = range.end.cmp(&summary.min_start, buffer);
@@ -261,6 +261,10 @@ impl Default for Summary {
impl sum_tree::Summary for Summary {
type Context = text::BufferSnapshot;
fn zero(_cx: &Self::Context) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
if other.min_start.cmp(&self.min_start, buffer).is_lt() {
self.min_start = other.min_start;

View File

@@ -208,7 +208,7 @@ impl CachedLspAdapter {
pub async fn get_language_server_command(
self: Arc<Self>,
container_dir: Arc<Path>,
container_dir: Option<Arc<Path>>,
delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Result<LanguageServerBinary> {
@@ -294,7 +294,7 @@ pub trait LspAdapter: 'static + Send + Sync {
fn get_language_server_command<'a>(
self: Arc<Self>,
container_dir: Arc<Path>,
container_dir: Option<Arc<Path>>,
delegate: Arc<dyn LspAdapterDelegate>,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
cx: &'a mut AsyncAppContext,
@@ -325,6 +325,10 @@ pub trait LspAdapter: 'static + Send + Sync {
return Ok(cached_binary.clone());
}
let Some(container_dir) = container_dir else {
anyhow::bail!("cannot download language servers for remotes (yet)")
};
if !container_dir.exists() {
smol::fs::create_dir_all(&container_dir)
.await
@@ -1664,7 +1668,7 @@ impl LspAdapter for FakeLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
_: Arc<Path>,
_: Option<Arc<Path>>,
_: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
_: &'a mut AsyncAppContext,

View File

@@ -869,12 +869,10 @@ impl LanguageRegistry {
adapter.name.0
);
let download_dir = &self
.language_server_download_dir
.clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
.log_err()?;
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
let container_dir: Option<Arc<Path>> = self
.language_server_download_dir
.as_ref()
.map(|dir| Arc::from(dir.join(adapter.name.0.as_ref())));
let root_path = root_path.clone();
let this = Arc::downgrade(self);
@@ -969,7 +967,7 @@ impl LanguageRegistry {
Some(PendingLanguageServer {
server_id,
task,
container_dir: Some(container_dir),
container_dir,
})
}

View File

@@ -741,6 +741,14 @@ pub struct InlayHintSettings {
/// Default: true
#[serde(default = "default_true")]
pub show_other_hints: bool,
/// Whether to show a background for inlay hints.
///
/// If set to `true`, the background will use the `hint.background` color
/// from the current theme.
///
/// Default: false
#[serde(default)]
pub show_background: bool,
/// Whether or not to debounce inlay hints updates after buffer edits.
///
/// Set to 0 to disable debouncing.

View File

@@ -18,13 +18,12 @@ use sum_tree::{Bias, SeekTarget, SumTree};
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
#[derive(Default)]
pub struct SyntaxMap {
snapshot: SyntaxSnapshot,
language_registry: Option<Arc<LanguageRegistry>>,
}
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct SyntaxSnapshot {
layers: SumTree<SyntaxLayerEntry>,
parsed_version: clock::Global,
@@ -212,8 +211,11 @@ struct ByteChunks<'a>(text::Chunks<'a>);
pub(crate) struct QueryCursorHandle(Option<QueryCursor>);
impl SyntaxMap {
pub fn new() -> Self {
Self::default()
pub fn new(text: &BufferSnapshot) -> Self {
Self {
snapshot: SyntaxSnapshot::new(text),
language_registry: None,
}
}
pub fn set_language_registry(&mut self, registry: Arc<LanguageRegistry>) {
@@ -242,12 +244,21 @@ impl SyntaxMap {
self.snapshot = snapshot;
}
pub fn clear(&mut self) {
self.snapshot = SyntaxSnapshot::default();
pub fn clear(&mut self, text: &BufferSnapshot) {
self.snapshot = SyntaxSnapshot::new(text);
}
}
impl SyntaxSnapshot {
fn new(text: &BufferSnapshot) -> Self {
Self {
layers: SumTree::new(text),
parsed_version: clock::Global::default(),
interpolated_version: clock::Global::default(),
language_registry_version: 0,
}
}
pub fn is_empty(&self) -> bool {
self.layers.is_empty()
}
@@ -262,10 +273,10 @@ impl SyntaxSnapshot {
return;
}
let mut layers = SumTree::new();
let mut layers = SumTree::new(text);
let mut first_edit_ix_for_depth = 0;
let mut prev_depth = 0;
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>(text);
cursor.next(text);
'outer: loop {
@@ -388,7 +399,7 @@ impl SyntaxSnapshot {
let mut resolved_injection_ranges = Vec::new();
let mut cursor = self
.layers
.filter::<_, ()>(|summary| summary.contains_unknown_injections);
.filter::<_, ()>(text, |summary| summary.contains_unknown_injections);
cursor.next(text);
while let Some(layer) = cursor.item() {
let SyntaxLayerContent::Pending { language_name } = &layer.content else {
@@ -430,9 +441,9 @@ impl SyntaxSnapshot {
log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
let max_depth = self.layers.summary().max_depth;
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>(text);
cursor.next(text);
let mut layers = SumTree::new();
let mut layers = SumTree::new(text);
let mut changed_regions = ChangeRegionSet::default();
let mut queue = BinaryHeap::new();
@@ -823,7 +834,7 @@ impl SyntaxSnapshot {
let start = buffer.anchor_before(start_offset);
let end = buffer.anchor_after(end_offset);
let mut cursor = self.layers.filter::<_, ()>(move |summary| {
let mut cursor = self.layers.filter::<_, ()>(buffer, move |summary| {
if summary.max_depth > summary.min_depth {
true
} else {
@@ -1666,6 +1677,10 @@ impl Default for SyntaxLayerSummary {
impl sum_tree::Summary for SyntaxLayerSummary {
type Context = BufferSnapshot;
fn zero(_cx: &BufferSnapshot) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
if other.max_depth > self.max_depth {
self.max_depth = other.max_depth;

View File

@@ -103,7 +103,7 @@ fn test_syntax_map_layers_for_range(cx: &mut AppContext) {
.unindent(),
);
let mut syntax_map = SyntaxMap::new();
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
syntax_map.reparse(language.clone(), &buffer);
@@ -202,7 +202,7 @@ fn test_dynamic_language_injection(cx: &mut AppContext) {
.unindent(),
);
let mut syntax_map = SyntaxMap::new();
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
syntax_map.reparse(markdown.clone(), &buffer);
syntax_map.reparse(markdown_inline.clone(), &buffer);
@@ -897,11 +897,11 @@ fn test_random_edits(
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), text);
let mut syntax_map = SyntaxMap::new();
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
syntax_map.reparse(language.clone(), &buffer);
let mut reference_syntax_map = SyntaxMap::new();
let mut reference_syntax_map = SyntaxMap::new(&buffer);
reference_syntax_map.set_language_registry(registry.clone());
log::info!("initial text:\n{}", buffer.text());
@@ -918,7 +918,7 @@ fn test_random_edits(
syntax_map.reparse(language.clone(), &buffer);
reference_syntax_map.clear();
reference_syntax_map.clear(&buffer);
reference_syntax_map.reparse(language.clone(), &buffer);
}
@@ -931,7 +931,7 @@ fn test_random_edits(
syntax_map.interpolate(&buffer);
syntax_map.reparse(language.clone(), &buffer);
reference_syntax_map.clear();
reference_syntax_map.clear(&buffer);
reference_syntax_map.reparse(language.clone(), &buffer);
assert_eq!(
syntax_map.layers(&buffer).len(),
@@ -1082,7 +1082,7 @@ fn test_edit_sequence(
.unwrap();
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), Default::default());
let mut mutated_syntax_map = SyntaxMap::new();
let mut mutated_syntax_map = SyntaxMap::new(&buffer);
mutated_syntax_map.set_language_registry(registry.clone());
mutated_syntax_map.reparse(language.clone(), &buffer);
@@ -1097,7 +1097,7 @@ fn test_edit_sequence(
// Create a second syntax map from scratch
log::info!("fresh parse {i}: {marked_string:?}");
let mut reference_syntax_map = SyntaxMap::new();
let mut reference_syntax_map = SyntaxMap::new(&buffer);
reference_syntax_map.set_language_registry(registry.clone());
reference_syntax_map.reparse(language.clone(), &buffer);

View File

@@ -25,6 +25,7 @@ pub trait ContextProvider: Send + Sync {
&self,
_variables: &TaskVariables,
_location: &Location,
_project_env: Option<&HashMap<String, String>>,
_cx: &mut AppContext,
) -> Result<TaskVariables> {
Ok(TaskVariables::default())

View File

@@ -1,5 +1,6 @@
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use collections::HashMap;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task};
use http_client::github::latest_github_release;
@@ -454,6 +455,7 @@ impl ContextProvider for GoContextProvider {
&self,
variables: &TaskVariables,
location: &Location,
_: Option<&HashMap<String, String>>,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
let local_abs_path = location
@@ -517,7 +519,6 @@ impl ContextProvider for GoContextProvider {
command: "go".into(),
args: vec![
"test".into(),
GO_PACKAGE_TASK_VARIABLE.template_value(),
"-run".into(),
format!("^{}\\$", VariableName::Symbol.template_value(),),
],
@@ -528,7 +529,7 @@ impl ContextProvider for GoContextProvider {
TaskTemplate {
label: format!("go test {}", GO_PACKAGE_TASK_VARIABLE.template_value()),
command: "go".into(),
args: vec!["test".into(), GO_PACKAGE_TASK_VARIABLE.template_value()],
args: vec!["test".into()],
cwd: package_cwd.clone(),
..TaskTemplate::default()
},
@@ -570,7 +571,6 @@ impl ContextProvider for GoContextProvider {
command: "go".into(),
args: vec![
"test".into(),
GO_PACKAGE_TASK_VARIABLE.template_value(),
"-benchmem".into(),
"-run=^$".into(),
"-bench".into(),

View File

@@ -1,5 +1,6 @@
use anyhow::Result;
use async_trait::async_trait;
use collections::HashMap;
use gpui::AppContext;
use gpui::AsyncAppContext;
use language::{ContextProvider, LanguageServerName, LspAdapter, LspAdapterDelegate};
@@ -215,6 +216,7 @@ impl ContextProvider for PythonContextProvider {
&self,
variables: &task::TaskVariables,
_location: &project::Location,
_: Option<&HashMap<String, String>>,
_cx: &mut gpui::AppContext,
) -> Result<task::TaskVariables> {
let python_module_name = python_module_name_from_relative_path(

View File

@@ -1,6 +1,7 @@
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_trait::async_trait;
use collections::HashMap;
use futures::{io::BufReader, StreamExt};
use gpui::{AppContext, AsyncAppContext};
use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
@@ -76,7 +77,7 @@ impl LspAdapter for RustLspAdapter {
{
Ok(()) => (Some(path), Some(env), None),
Err(err) => {
log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {:?}", path, err);
log::error!("failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}", path, err);
(None, None, None)
}
}
@@ -434,6 +435,7 @@ impl ContextProvider for RustContextProvider {
&self,
task_variables: &TaskVariables,
location: &Location,
project_env: Option<&HashMap<String, String>>,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
let local_abs_path = location
@@ -449,8 +451,8 @@ impl ContextProvider for RustContextProvider {
.is_some();
if is_main_function {
if let Some((package_name, bin_name)) =
local_abs_path.and_then(package_name_and_bin_name_from_abs_path)
if let Some((package_name, bin_name)) = local_abs_path
.and_then(|path| package_name_and_bin_name_from_abs_path(path, project_env))
{
return Ok(TaskVariables::from_iter([
(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name),
@@ -461,7 +463,7 @@ impl ContextProvider for RustContextProvider {
if let Some(package_name) = local_abs_path
.and_then(|local_abs_path| local_abs_path.parent())
.and_then(human_readable_package_name)
.and_then(|path| human_readable_package_name(path, project_env))
{
return Ok(TaskVariables::from_iter([(
RUST_PACKAGE_TASK_VARIABLE.clone(),
@@ -615,8 +617,15 @@ struct CargoTarget {
src_path: String,
}
fn package_name_and_bin_name_from_abs_path(abs_path: &Path) -> Option<(String, String)> {
let output = std::process::Command::new("cargo")
fn package_name_and_bin_name_from_abs_path(
abs_path: &Path,
project_env: Option<&HashMap<String, String>>,
) -> Option<(String, String)> {
let mut command = std::process::Command::new("cargo");
if let Some(envs) = project_env {
command.envs(envs);
}
let output = command
.current_dir(abs_path.parent()?)
.arg("metadata")
.arg("--no-deps")
@@ -654,9 +663,17 @@ fn retrieve_package_id_and_bin_name_from_metadata(
None
}
fn human_readable_package_name(package_directory: &Path) -> Option<String> {
fn human_readable_package_name(
package_directory: &Path,
project_env: Option<&HashMap<String, String>>,
) -> Option<String> {
let mut command = std::process::Command::new("cargo");
if let Some(envs) = project_env {
command.envs(envs);
}
let pkgid = String::from_utf8(
std::process::Command::new("cargo")
command
.current_dir(package_directory)
.arg("pkgid")
.output()

View File

@@ -139,7 +139,6 @@
] @comment.doc
[
"!"
"!="
"%"
"%="
@@ -159,7 +158,6 @@
".."
"..="
"..."
"/"
"/="
":"
";"
@@ -183,6 +181,10 @@
"?"
] @operator
; Avoid highlighting these as operators when used in doc comments.
(unary_expression "!" @operator)
operator: "/" @operator
(lifetime) @lifetime
(parameter (identifier) @variable.parameter)

View File

@@ -18,20 +18,15 @@ use std::{
use util::{maybe, ResultExt};
#[cfg(target_os = "windows")]
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server.ps1";
const SERVER_PATH: &str =
"node_modules/@tailwindcss/language-server/bin/tailwindcss-language-server";
#[cfg(not(target_os = "windows"))]
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server";
#[cfg(not(target_os = "windows"))]
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
#[cfg(target_os = "windows")]
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec!["-File".into(), server_path.into(), "--stdio".into()]
}
pub struct TailwindLspAdapter {
node: Arc<dyn NodeRuntime>,
}
@@ -114,26 +109,11 @@ impl LspAdapter for TailwindLspAdapter {
.await?;
}
#[cfg(target_os = "windows")]
{
let env_path = self.node.node_environment_path().await?;
let mut env = HashMap::default();
env.insert("PATH".to_string(), env_path.to_string_lossy().to_string());
Ok(LanguageServerBinary {
path: "powershell.exe".into(),
env: Some(env),
arguments: server_binary_arguments(&server_path),
})
}
#[cfg(not(target_os = "windows"))]
{
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(

View File

@@ -561,7 +561,7 @@ impl MultiBuffer {
}
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
let mut edited_excerpt_ids = Vec::new();
let mut cursor = snapshot.excerpts.cursor::<usize>();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
for (ix, (range, new_text)) in edits.enumerate() {
let new_text: Arc<str> = new_text.into();
let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
@@ -841,7 +841,7 @@ impl MultiBuffer {
let mut ranges = Vec::new();
let snapshot = self.read(cx);
let buffers = self.buffers.borrow();
let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(&());
for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
let Some(buffer_state) = buffers.get(buffer_id) else {
@@ -957,7 +957,7 @@ impl MultiBuffer {
let mut selections_by_buffer: HashMap<BufferId, Vec<Selection<text::Anchor>>> =
Default::default();
let snapshot = self.read(cx);
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
for selection in selections {
let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
@@ -1281,7 +1281,7 @@ impl MultiBuffer {
let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &());
prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
@@ -1388,7 +1388,7 @@ impl MultiBuffer {
let mut excerpts = Vec::new();
let snapshot = self.read(cx);
let buffers = self.buffers.borrow();
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
for locator in buffers
.get(&buffer.read(cx).remote_id())
.map(|state| &state.excerpts)
@@ -1432,7 +1432,7 @@ impl MultiBuffer {
let snapshot = self.read(cx);
let position = position.to_offset(&snapshot);
let mut cursor = snapshot.excerpts.cursor::<usize>();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
cursor.seek(&position, Bias::Right, &());
cursor
.item()
@@ -1459,7 +1459,7 @@ impl MultiBuffer {
) -> Option<(Model<Buffer>, usize, ExcerptId)> {
let snapshot = self.read(cx);
let offset = point.to_offset(&snapshot);
let mut cursor = snapshot.excerpts.cursor::<usize>();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
cursor.seek(&offset, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -1482,7 +1482,7 @@ impl MultiBuffer {
) -> Option<(Model<Buffer>, Point, ExcerptId)> {
let snapshot = self.read(cx);
let point = point.to_point(&snapshot);
let mut cursor = snapshot.excerpts.cursor::<Point>();
let mut cursor = snapshot.excerpts.cursor::<Point>(&());
cursor.seek(&point, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -1507,7 +1507,7 @@ impl MultiBuffer {
let end = range.end.to_offset(&snapshot);
let mut result = Vec::new();
let mut cursor = snapshot.excerpts.cursor::<usize>();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
cursor.seek(&start, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -1546,8 +1546,8 @@ impl MultiBuffer {
let mut buffers = self.buffers.borrow_mut();
let mut snapshot = self.snapshot.borrow_mut();
let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
let mut new_excerpts = SumTree::default();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
let mut edits = Vec::new();
let mut excerpt_ids = ids.iter().copied().peekable();
@@ -1801,8 +1801,8 @@ impl MultiBuffer {
let ids = ids.into_iter().collect::<Vec<_>>();
let snapshot = self.snapshot(cx);
let locators = snapshot.excerpt_locators_for_ids(ids.iter().copied());
let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
let mut new_excerpts = SumTree::default();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
let mut edits = Vec::<Edit<usize>>::new();
for locator in &locators {
@@ -1927,8 +1927,8 @@ impl MultiBuffer {
excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
let mut edits = Vec::new();
let mut new_excerpts = SumTree::new();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>();
let mut new_excerpts = SumTree::default();
let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(&());
for (locator, buffer, buffer_edited) in excerpts_to_edit {
new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &());
@@ -2230,7 +2230,7 @@ impl MultiBufferSnapshot {
pub fn reversed_chars_at<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
let mut offset = position.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.seek(&offset, Bias::Left, &());
let mut excerpt_chunks = cursor.item().map(|excerpt| {
let end_before_footer = cursor.start() + excerpt.text_summary.len;
@@ -2357,7 +2357,7 @@ impl MultiBufferSnapshot {
return buffer.clip_offset(offset, bias);
}
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.seek(&offset, Bias::Right, &());
let overshoot = if let Some(excerpt) = cursor.item() {
let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
@@ -2376,7 +2376,7 @@ impl MultiBufferSnapshot {
return buffer.clip_point(point, bias);
}
let mut cursor = self.excerpts.cursor::<Point>();
let mut cursor = self.excerpts.cursor::<Point>(&());
cursor.seek(&point, Bias::Right, &());
let overshoot = if let Some(excerpt) = cursor.item() {
let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer);
@@ -2395,7 +2395,7 @@ impl MultiBufferSnapshot {
return buffer.clip_offset_utf16(offset, bias);
}
let mut cursor = self.excerpts.cursor::<OffsetUtf16>();
let mut cursor = self.excerpts.cursor::<OffsetUtf16>(&());
cursor.seek(&offset, Bias::Right, &());
let overshoot = if let Some(excerpt) = cursor.item() {
let excerpt_start = excerpt.range.context.start.to_offset_utf16(&excerpt.buffer);
@@ -2414,7 +2414,7 @@ impl MultiBufferSnapshot {
return buffer.clip_point_utf16(point, bias);
}
let mut cursor = self.excerpts.cursor::<PointUtf16>();
let mut cursor = self.excerpts.cursor::<PointUtf16>(&());
cursor.seek(&point.0, Bias::Right, &());
let overshoot = if let Some(excerpt) = cursor.item() {
let excerpt_start = excerpt
@@ -2432,7 +2432,7 @@ impl MultiBufferSnapshot {
pub fn bytes_in_range<T: ToOffset>(&self, range: Range<T>) -> MultiBufferBytes {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut excerpts = self.excerpts.cursor::<usize>();
let mut excerpts = self.excerpts.cursor::<usize>(&());
excerpts.seek(&range.start, Bias::Right, &());
let mut chunk = &[][..];
@@ -2457,7 +2457,7 @@ impl MultiBufferSnapshot {
range: Range<T>,
) -> ReversedMultiBufferBytes {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut excerpts = self.excerpts.cursor::<usize>();
let mut excerpts = self.excerpts.cursor::<usize>(&());
excerpts.seek(&range.end, Bias::Left, &());
let mut chunk = &[][..];
@@ -2482,7 +2482,7 @@ impl MultiBufferSnapshot {
pub fn buffer_rows(&self, start_row: MultiBufferRow) -> MultiBufferRows {
let mut result = MultiBufferRows {
buffer_row_range: 0..0,
excerpts: self.excerpts.cursor(),
excerpts: self.excerpts.cursor(&()),
};
result.seek(start_row);
result
@@ -2492,7 +2492,7 @@ impl MultiBufferSnapshot {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut chunks = MultiBufferChunks {
range: range.clone(),
excerpts: self.excerpts.cursor(),
excerpts: self.excerpts.cursor(&()),
excerpt_chunks: None,
language_aware,
};
@@ -2505,7 +2505,7 @@ impl MultiBufferSnapshot {
return buffer.offset_to_point(offset);
}
let mut cursor = self.excerpts.cursor::<(usize, Point)>();
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.seek(&offset, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_offset, start_point) = cursor.start();
@@ -2526,7 +2526,7 @@ impl MultiBufferSnapshot {
return buffer.offset_to_point_utf16(offset);
}
let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>();
let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>(&());
cursor.seek(&offset, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_offset, start_point) = cursor.start();
@@ -2547,7 +2547,7 @@ impl MultiBufferSnapshot {
return buffer.point_to_point_utf16(point);
}
let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>();
let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>(&());
cursor.seek(&point, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_offset, start_point) = cursor.start();
@@ -2569,7 +2569,7 @@ impl MultiBufferSnapshot {
return buffer.point_to_offset(point);
}
let mut cursor = self.excerpts.cursor::<(Point, usize)>();
let mut cursor = self.excerpts.cursor::<(Point, usize)>(&());
cursor.seek(&point, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_point, start_offset) = cursor.start();
@@ -2590,7 +2590,7 @@ impl MultiBufferSnapshot {
return buffer.offset_utf16_to_offset(offset_utf16);
}
let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>();
let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>(&());
cursor.seek(&offset_utf16, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_offset_utf16, start_offset) = cursor.start();
@@ -2612,7 +2612,7 @@ impl MultiBufferSnapshot {
return buffer.offset_to_offset_utf16(offset);
}
let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>();
let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>(&());
cursor.seek(&offset, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_offset, start_offset_utf16) = cursor.start();
@@ -2636,7 +2636,7 @@ impl MultiBufferSnapshot {
return buffer.point_utf16_to_offset(point);
}
let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(&());
cursor.seek(&point, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let (start_point, start_offset) = cursor.start();
@@ -2659,7 +2659,7 @@ impl MultiBufferSnapshot {
point: T,
) -> Option<(&BufferSnapshot, usize)> {
let offset = point.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.seek(&offset, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -2680,7 +2680,7 @@ impl MultiBufferSnapshot {
let mut result = BTreeMap::new();
let mut rows_for_excerpt = Vec::new();
let mut cursor = self.excerpts.cursor::<Point>();
let mut cursor = self.excerpts.cursor::<Point>(&());
let mut rows = rows.into_iter().peekable();
let mut prev_row = u32::MAX;
let mut prev_language_indent_size = IndentSize::default();
@@ -2769,7 +2769,7 @@ impl MultiBufferSnapshot {
&self,
row: MultiBufferRow,
) -> Option<(&BufferSnapshot, Range<Point>)> {
let mut cursor = self.excerpts.cursor::<Point>();
let mut cursor = self.excerpts.cursor::<Point>(&());
let point = Point::new(row.0, 0);
cursor.seek(&point, Bias::Right, &());
if cursor.item().is_none() && *cursor.start() == point {
@@ -2803,9 +2803,9 @@ impl MultiBufferSnapshot {
D: TextDimension,
O: ToOffset,
{
let mut summary = D::default();
let mut summary = D::zero(&());
let mut range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.seek(&range.start, Bias::Right, &());
if let Some(excerpt) = cursor.item() {
let mut end_before_newline = cursor.end(&());
@@ -2856,7 +2856,7 @@ impl MultiBufferSnapshot {
where
D: TextDimension + Ord + Sub<D, Output = D>,
{
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
let locator = self.excerpt_locator_for_id(anchor.excerpt_id);
cursor.seek(locator, Bias::Left, &());
@@ -2894,7 +2894,7 @@ impl MultiBufferSnapshot {
}
let mut anchors = anchors.into_iter().peekable();
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
let mut summaries = Vec::new();
while let Some(anchor) = anchors.peek() {
let excerpt_id = anchor.excerpt_id;
@@ -2949,7 +2949,7 @@ impl MultiBufferSnapshot {
I: 'a + IntoIterator<Item = &'a Anchor>,
{
let mut anchors = anchors.into_iter().enumerate().peekable();
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.next(&());
let mut result = Vec::new();
@@ -3064,7 +3064,7 @@ impl MultiBufferSnapshot {
};
}
let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>();
let mut cursor = self.excerpts.cursor::<(usize, Option<ExcerptId>)>(&());
cursor.seek(&offset, Bias::Right, &());
if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left {
cursor.prev(&());
@@ -3099,7 +3099,7 @@ impl MultiBufferSnapshot {
text_anchor: text::Anchor,
) -> Option<Anchor> {
let locator = self.excerpt_locator_for_id(excerpt_id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
cursor.seek(locator, Bias::Left, &());
if let Some(excerpt) = cursor.item() {
if excerpt.id == excerpt_id {
@@ -3139,7 +3139,7 @@ impl MultiBufferSnapshot {
) -> impl Iterator<Item = (&Excerpt, usize)> + '_ {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.seek(&range.start, Bias::Right, &());
cursor.prev(&());
@@ -3183,7 +3183,7 @@ impl MultiBufferSnapshot {
};
let bounds = (start, end);
let mut cursor = self.excerpts.cursor::<(usize, Point)>();
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.seek(&start_offset, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -3550,7 +3550,7 @@ impl MultiBufferSnapshot {
&self,
row_range: Range<MultiBufferRow>,
) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
let mut cursor = self.excerpts.cursor::<Point>();
let mut cursor = self.excerpts.cursor::<Point>(&());
cursor.seek(&Point::new(row_range.end.0, 0), Bias::Left, &());
if cursor.item().is_none() {
@@ -3617,7 +3617,7 @@ impl MultiBufferSnapshot {
&self,
row_range: Range<MultiBufferRow>,
) -> impl Iterator<Item = DiffHunk<MultiBufferRow>> + '_ {
let mut cursor = self.excerpts.cursor::<Point>();
let mut cursor = self.excerpts.cursor::<Point>(&());
cursor.seek(&Point::new(row_range.start.0, 0), Bias::Left, &());
@@ -3779,7 +3779,7 @@ impl MultiBufferSnapshot {
} else if id == ExcerptId::max() {
Locator::max_ref()
} else {
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>(&());
cursor.seek(&id, Bias::Left, &());
if let Some(entry) = cursor.item() {
if entry.id == id {
@@ -3814,7 +3814,7 @@ impl MultiBufferSnapshot {
}
}
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>();
let mut cursor = self.excerpt_ids.cursor::<ExcerptId>(&());
for id in sorted_ids {
if cursor.seek_forward(&id, Bias::Left, &()) {
locators.push(cursor.item().unwrap().locator.clone());
@@ -3839,7 +3839,7 @@ impl MultiBufferSnapshot {
&'a self,
excerpt_id: ExcerptId,
) -> Option<Range<T>> {
let mut cursor = self.excerpts.cursor::<(Option<&Locator>, T)>();
let mut cursor = self.excerpts.cursor::<(Option<&Locator>, T)>(&());
let locator = self.excerpt_locator_for_id(excerpt_id);
if cursor.seek(&Some(locator), Bias::Left, &()) {
let start = cursor.start().1.clone();
@@ -3851,7 +3851,7 @@ impl MultiBufferSnapshot {
}
fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> {
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(&());
let locator = self.excerpt_locator_for_id(excerpt_id);
cursor.seek(&Some(locator), Bias::Left, &());
if let Some(excerpt) = cursor.item() {
@@ -3866,7 +3866,7 @@ impl MultiBufferSnapshot {
pub fn excerpt_containing<T: ToOffset>(&self, range: Range<T>) -> Option<MultiBufferExcerpt> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.seek(&range.start, Bias::Right, &());
let start_excerpt = cursor.item()?;
@@ -3891,7 +3891,7 @@ impl MultiBufferSnapshot {
I: IntoIterator<Item = Range<Anchor>> + 'a,
{
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.next(&());
let mut current_range = ranges.next();
iter::from_fn(move || {
@@ -3943,7 +3943,7 @@ impl MultiBufferSnapshot {
ranges: impl IntoIterator<Item = Range<Anchor>>,
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
let mut cursor = self.excerpts.cursor::<usize>();
let mut cursor = self.excerpts.cursor::<usize>(&());
cursor.next(&());
let mut current_range = ranges.next();
iter::from_fn(move || {
@@ -3980,7 +3980,7 @@ impl MultiBufferSnapshot {
range: &'a Range<Anchor>,
include_local: bool,
) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
let mut cursor = self.excerpts.cursor::<ExcerptSummary>(&());
let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id);
let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id);
cursor.seek(start_locator, Bias::Left, &());
@@ -4519,6 +4519,10 @@ impl sum_tree::KeyedItem for ExcerptIdMapping {
impl sum_tree::Summary for ExcerptId {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, _: &()) {
*self = *other;
}
@@ -4527,6 +4531,10 @@ impl sum_tree::Summary for ExcerptId {
impl sum_tree::Summary for ExcerptSummary {
type Context = ();
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &Self, _: &()) {
debug_assert!(summary.excerpt_locator > self.excerpt_locator);
self.excerpt_locator = summary.excerpt_locator.clone();
@@ -4536,12 +4544,20 @@ impl sum_tree::Summary for ExcerptSummary {
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self += &summary.text;
}
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self += summary.text.len;
}
@@ -4566,30 +4582,50 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator {
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for OffsetUtf16 {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self += summary.text.len_utf16;
}
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self += summary.text.lines;
}
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self += summary.text.lines_utf16()
}
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self = Some(&summary.excerpt_locator);
}
}
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<ExcerptId> {
fn zero(_cx: &()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self = Some(summary.excerpt_id);
}

View File

@@ -1,51 +0,0 @@
[package]
name = "multi_buffer2"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/multi_buffer2.rs"
doctest = false
[features]
test-support = [
"text/test-support",
"language/test-support",
"gpui/test-support",
"util/test-support",
]
[dependencies]
anyhow.workspace = true
clock.workspace = true
collections.workspace = true
ctor.workspace = true
env_logger.workspace = true
futures.workspace = true
git.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
parking_lot.workspace = true
rand.workspace = true
settings.workspace = true
serde.workspace = true
smallvec.workspace = true
sum_tree.workspace = true
text.workspace = true
theme.workspace = true
util.workspace = true
[dev-dependencies]
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
rand.workspace = true
settings = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] }

View File

@@ -1,138 +0,0 @@
use super::{MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
use language::{OffsetUtf16, Point, TextDimension};
use std::{
cmp::Ordering,
ops::{Range, Sub},
};
use sum_tree::Bias;
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub struct Anchor(pub text::Anchor);
impl Anchor {
pub fn min() -> Self {
Self(text::Anchor::MIN)
}
pub fn max() -> Self {
Self(text::Anchor::MAX)
}
pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
todo!()
// let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot);
// if excerpt_id_cmp.is_eq() {
// if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() {
// Ordering::Equal
// } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
// self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer)
// } else {
// Ordering::Equal
// }
// } else {
// excerpt_id_cmp
// }
}
pub fn bias(&self) -> Bias {
self.0.bias
}
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
if self.0.bias != Bias::Left {
todo!()
// if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
// return Self {
// buffer_id: self.buffer_id,
// excerpt_id: self.excerpt_id,
// text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
// };
// }
}
*self
}
pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
if self.0.bias != Bias::Right {
todo!()
// if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
// return Self {
// buffer_id: self.buffer_id,
// excerpt_id: self.excerpt_id,
// text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
// };
// }
}
*self
}
pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
where
D: TextDimension + Ord + Sub<D, Output = D>,
{
snapshot.summary_for_anchor(self)
}
pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
todo!()
// if *self == Anchor::min() || *self == Anchor::max() {
// true
// } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
// excerpt.contains(self)
// && (self.text_anchor == excerpt.range.context.start
// || self.text_anchor == excerpt.range.context.end
// || self.text_anchor.is_valid(&excerpt.buffer))
// } else {
// false
// }
}
}
impl ToOffset for Anchor {
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize {
self.summary(snapshot)
}
}
impl ToOffsetUtf16 for Anchor {
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
self.summary(snapshot)
}
}
impl ToPoint for Anchor {
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
self.summary(snapshot)
}
}
pub trait AnchorRangeExt {
fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
fn overlaps(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
}
impl AnchorRangeExt for Range<Anchor> {
fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering {
match self.start.cmp(&other.start, buffer) {
Ordering::Equal => other.end.cmp(&self.end, buffer),
ord => ord,
}
}
fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
}
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize> {
self.start.to_offset(content)..self.end.to_offset(content)
}
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
self.start.to_point(content)..self.end.to_point(content)
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Ord, PartialOrd)]
pub struct Offset(pub usize);

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