Compare commits

...

129 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
Peter Tripp
7246a0f39c macos: Use ~/Library/Caches/Zed instead of ~/.cache/zed (#17949) 2024-09-17 13:51:11 -04:00
Jason Lee
345efa4e36 gpui: Fix img element to render correct SVG color (#15488)
Release Notes:

- N/A


It should convert RGBA to BGRA.

> I added an example color svg, that was I make based on [Lucide grip
icon](https://lucide.dev/icons/grip).

## Before

<img width="692" alt="image"
src="https://github.com/user-attachments/assets/5eb03606-76ce-4049-b3ad-8d1084a4fa55">


## After

<img width="695" alt="image"
src="https://github.com/user-attachments/assets/650dd411-2095-4e92-b3fd-8e91c6954aa3">

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-17 13:50:36 -04:00
Marshall Bowers
5cdca6d8dd multi_buffer: Fix a panic when expanding an excerpt with the cursor at the end (#17955)
This PR fixes a panic when expanding an excerpt within a multibuffer
that could occur when the cursor was at the end of the buffer.

You can reproduce this by opening a multibuffer, putting your cursor at
the very end of that buffer, and then expanding the excerpt (Shift +
Enter).

Release Notes:

- Fixed a panic that could occur when expanding an excerpt within a
multibuffer when the cursor was at the end of the excerpt.

Co-authored-by: Antonio <antonio@zed.dev>
2024-09-17 13:49:52 -04:00
Thorsten Ball
ccfd4b1887 rust: Test rust-analyzer binary after finding in PATH (#17951)
Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-09-17 11:45:29 -06:00
Marshall Bowers
ee8668ef45 Bind editor::Rewrap to alt-q (#17953)
This PR adds a keybinding for the `editor: rewrap` command.

It is bound to `alt-q`, by default. In Vim mode, it is bound to `g q`.

Release Notes:

- N/A
2024-09-17 13:33:09 -04:00
Marshall Bowers
ac5c35b3df theme: Fix casing of "ANSI" in doc comments (#17952)
This PR fixes the casing of "ANSI" in some doc comments after #17611.

Release Notes:

- N/A
2024-09-17 13:26:34 -04:00
Albert Marashi
0070635b4d Styling option for separating terminal view background from background color (#17611)
Closes #17313

Release Notes:

- Added theme styling option to separate terminal background view from
terminal background color, for transparent terminal backgrounds
2024-09-17 12:51:31 -04:00
Kirill Bulatov
3d69942f71 Use dev icons for dev bundles (#17944)
Follow-up of https://github.com/zed-industries/zed/pull/17486/ actually
using the dev icons for dev bundles

Release Notes:

- N/A
2024-09-17 12:34:36 -04:00
Marshall Bowers
76603a5fc6 ocaml: Bump to v0.1.0 (#17945)
This PR bumps the OCaml extension to v0.1.0.

Changes:

- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/17886

Release Notes:

- N/A
2024-09-17 12:13:06 -04:00
Albert Marashi
accff826ca svelte: Migrate to tree-sitter-grammars/tree-sitter-svelte (#17529)
> [!NOTE]
> The https://github.com/tree-sitter-grammars/tree-sitter-svelte
repository seems to be more well maintained, with higher quality code,
and as per https://github.com/zed-extensions/svelte/issues/1 it was
suggested that we swap to this repository for Svelte grammars

- Closes https://github.com/zed-industries/zed/issues/17310
- Closes https://github.com/zed-industries/zed/issues/10893
- Closes https://github.com/zed-industries/zed/issues/12833
- Closes https://github.com/zed-extensions/svelte/issues/1
- Closes https://github.com/zed-industries/zed/issues/14943
- Closes https://github.com/zed-extensions/svelte/issues/2

- Added: buffer/file symbol outlines for `.svelte` (`outlines.scm`)
- Improved: Attribute directives & modifiers in `.svelte` files can be
styled independently.
- Fixed: issue where svelte expression inside quotes failed parsing
- Improved: Svelte components in Markup are styled differently from
tags.
- Added: Support for Svelte 5 syntax (`{#snippet children()}`, `{@render
foo()`)
- Change: Svelte now using
[tree-sitter-grammars/tree-sitter-svelte](https://github.com/tree-sitter-grammars/tree-sitter-svelte)
for language highlighting
- Added: Support for typescript syntax in svelte expressions


![image](https://github.com/user-attachments/assets/49d199ee-7550-49a7-912d-070cf691b029)

![image](https://github.com/user-attachments/assets/848ac5b6-62da-4c42-8e24-b7023504f8af)

Release Notes:

- N/A

---

**tree-sitter-grammar things to improve**
- [ ] snippet functions aren't being treated as JS code
- [ ] we should be able to detect @component comments and treat them as
markdown
- [x] `foo:bar` style/class/prop directives
- [x] `--foo="..."` var fields
- [ ] snippet/if blocks's children may need to be indented a little
further

Will implement some of the rest of these in a separate PR

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-17 12:02:25 -04:00
Peter Tripp
27f09957c2 Improve CSS highlighting for property_name (#17324) 2024-09-17 11:53:52 -04:00
Marshall Bowers
e88b48a9c7 ocaml: Fix indentation in files (#17942)
This PR fixes the indentation in the Dune-related files after
https://github.com/zed-industries/zed/pull/17886.

Release Notes:

- N/A
2024-09-17 11:42:20 -04:00
VacheDesNeiges
d5003e1121 Update C++ Tree-sitter queries (#17471)
Closes #16443 

Release Notes:

- Fixed C++ functions being wrongly tagged as variables when called
after two or more scope resolution operators.
- Added a "namespace" tag for highlighting purposes

Before : 

![image](https://github.com/user-attachments/assets/743b8407-4e62-4549-9c6a-ed6608ea7e43)
After : 

![image](https://github.com/user-attachments/assets/de563621-e722-463c-97a1-a99b925f126e)
2024-09-17 11:40:43 -04:00
Danilo Leal
7c54965b11 docs: Add dark mode (#17940)
Closes https://github.com/zed-industries/zed/issues/17911

This PR enables dark mode on the documentation. This is done without any
special plugins, just pure JavaScript and CSS variables. I may open
fast-follow PRs to fine-tune design and code details that haven't been
super polished yet. For example, when switching to dark mode, the
`class` attribute on the `html` tag would change immediately, whereas
other attributes such as `data-theme` and `data-color-scheme` would
require a full refresh. This seems to be resolved, but not 100%
confident yet.

---

Release Notes:

- Enabled dark mode on the documentation
2024-09-17 12:39:06 -03:00
Stanislav Alekseev
10cfaecffa ocaml: Add Dune language support (#17886)
This uses my fork of the dune tree-sitter grammar to include the
generated files

Release Notes:

- N/A
2024-09-17 11:28:03 -04:00
Thorsten Ball
469dfe759c ssh: Handle ~ in ssh filenames (#17939)
This allows users to open `ssh://user@host/~/my-home-dir-folder`.

Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2024-09-17 17:21:20 +02:00
Erick Guan
ecd1830793 Fix opening file with colon (#17281)
Closes #14100

Release Notes:

- Fixed unable to open file with a colon from Zed CLI

-----

I didn't make change to tests for the first two commits. I changed them
to easily find offending test cases. Behavior changes are in last commit
message.

In the last commit, I changed how `PathWithPosition` should intreprete
file paths. If my assumptions are off, please advise so that I can make
another approach.

I also believe further constraints would be better for
`PathWithPosition`'s intention. But people can make future improvements
to `PathWithPosition`.
2024-09-17 11:19:07 -04:00
Thorsten Ball
ddaee2e8dd ssh: Handle BufferSaved ssh message (#17936)
Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2024-09-17 17:03:10 +02:00
Antonio Scandurra
54b8232be2 Introduce a new /delta command (#17903)
Release Notes:

- Added a new `/delta` command to re-insert changed files that were
previously included in a context.

---------

Co-authored-by: Roy <roy@anthropic.com>
2024-09-17 08:47:08 -06:00
Peter Tripp
a20c0eb626 Improve error message when docs need fixing with Prettier (#17907) 2024-09-17 10:06:33 -04:00
Kevin Wang
c48584fb79 supermaven: Fix incorrect offset calculation (#17925)
Fixes a bug in https://github.com/zed-industries/zed/pull/17578 when
computing the offset. Specifically, `offset.add_assign()` should be
incremented on every loop match instead of only when the completion text
is found.

Before:


![image](https://github.com/user-attachments/assets/cc09dbf9-03e8-4453-a1c7-11f838c1d959)

After:


![image](https://github.com/user-attachments/assets/f3513769-d9e1-451f-97dc-b9ad3a57ce3a)

Release Notes:

- Fixed a wrong offset calculation in the Supermaven inline completion
provider.
2024-09-17 16:04:33 +02:00
Thorsten Ball
f1d21362fa editor: Fix cursor_shape regression by not setting it to "bar" (#17934)
This fixes the regression introduced here:
https://github.com/zed-industries/zed/pull/17572#issuecomment-2355632615

Essentially: instead of always setting the value when saving settings,
we don't set it by default, but fall back to the default value if it's
not set.

That fixes Vim mode's cursor being overwritten when settings change.

Release Notes:

- N/A
2024-09-17 15:37:43 +02:00
iugo
4139a9a758 docs: Document usage of deno fmt (#17918)
Clarify in the settings description that the default formatter leverages `deno fmt`. This makes it clearer for users what to expect and how formatting is handled out of the box.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-09-17 09:23:48 -04:00
Daste
103f757c11 tab_switcher: Add file and project search icons (#17115)
I found tab switcher file icons to be missing. They were mentioned in
the [initial tab switcher
issue](https://github.com/zed-industries/zed/issues/7653), but left to
be added later (mentioned in
https://github.com/zed-industries/zed/pull/7987).

I also noticed that the project search icon went missing, but I'm not
sure if that's intentional. These changes re-introduce it, as it's
provided by the generic `tab_icon()` function.

There's a small difference between the terminal item and everything
else, because terminal's `tab_content` returns a slightly different
layout, which adds a little more space between the icon and text. I'll
look into resolving this withouth changing too much stuff around in the
terminal crate. If you have any ideas on how to do this well, please
comment.

The new `tab_switcher` config section only has a single boolean option -
`show_icons`. It toggles between icons and not icons, but doesn't
disable the terminal icon. Implementing this would probably also require
some refactoring in terminal's `tab_content` function.

Release Notes:

- Added file icons to the tab switcher

Screenshot:

![image](https://github.com/user-attachments/assets/17f3f4a3-1f95-4830-aef1-cda280726385)
2024-09-17 14:48:05 +02:00
Thorsten Ball
2165d52d3e project: Update variable and change comment (#17933)
Previous this *was* the `cli_environment`, but now it's the project
environment.

Release Notes:

- N/A
2024-09-17 14:33:53 +02:00
Thorsten Ball
c34fc5c6e5 lsp store: Refactor to use shared method to find buffer snapshot (#17929)
Came across this code while investigating something else and I think we
should use the same method. As far as I know, it does the same thing,
except that `buffer_snapshot_for_lsp_version` also cleans up the stored
snapshots.

Release Notes:

- N/A
2024-09-17 11:47:17 +02:00
ClanEver
5f0925fb5d Add Python venv activation support for Windows and PowerShell (#17839)
Release Notes:

- Add Python venv activation support for Windows and PowerShell

Additional:

I discovered a related bug on my Windows system. When first opening the
project, it fails to detect the virtual environment folder `.venv`.
After expanding the .venv folder in the Project Panel, it then becomes
able to detect the virtual environment folder. However, I don't know how
to fix it.
2024-09-17 11:17:29 +02:00
Thorsten Ball
d56e3d99b4 rust: Fix looking up rust-analyzer in $PATH by default (#17926)
This is a follow-up to https://github.com/zed-industries/zed/pull/17885,
which is reverted and fixed in this PR.

This PR actually enables the behavior by default.

Release Notes:

- Changed `rust-analyzer` support to lookup `rust-analyzer` binaries by
default in `$PATH`. That changes the default value to something users
requested.
2024-09-17 10:17:03 +02:00
Galen Elias
7d97855ed7 Use AppContext for UI font adjustments (#17858)
Appologies if this PR is off base, I'm still not super familiar with the
Zed codebase.

I was trying to integrate with
https://github.com/zed-industries/zed/pull/12940 
and found it awkward to hook up global bindings to adjust the UI font
size due to the fact it takes a WindowContext. Looking at the API, it
seemed odd that it took a WindowContext, yet the editor font methods
take an AppContext.

I couldn't find a compelling reason for this to be tied to a
WindowContext, so I personally think it makes sense to switch it.

This does have a behavior change, which hopefully is actually desirable:

Currently, if you have two open and visible Zed windows, and trigger a
UI font adjustment in one, the non-active windows won't update. However,
once you switch focus to the second one it will snap to the new UI font
size. This is inconsistent with adjusting the editor font size, which
applies to all open windows immediately.

Release Notes:

- N/A
2024-09-16 22:23:03 -06:00
renovate[bot]
4160824b10 Update Rust crate rodio to 0.19.0 (#17389)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rodio](https://redirect.github.com/RustAudio/rodio) | dependencies |
minor | `0.17.1` -> `0.19.0` |

---

### Release Notes

<details>
<summary>RustAudio/rodio (rodio)</summary>

###
[`v0.19.0`](https://redirect.github.com/RustAudio/rodio/blob/HEAD/CHANGELOG.md#Version-0190-2024-06-29)

[Compare
Source](https://redirect.github.com/RustAudio/rodio/compare/v0.18.1...v0.19.0)

##### Added

- Adds a new source `track_position`. It keeps track of duration since
the
    beginning of the underlying source.

##### Fixed

- Mp4a with decodable tracks after undecodable tracks now play. This
matches
    VLC's behaviour.

###
[`v0.18.1`](https://redirect.github.com/RustAudio/rodio/blob/HEAD/CHANGELOG.md#Version-0181-2024-05-23)

[Compare
Source](https://redirect.github.com/RustAudio/rodio/compare/v0.18.0...v0.18.1)

##### Fixed

-   Seek no longer hangs if the sink is empty.

###
[`v0.18.0`](https://redirect.github.com/RustAudio/rodio/blob/HEAD/CHANGELOG.md#Version-0180-2024-05-05)

[Compare
Source](https://redirect.github.com/RustAudio/rodio/compare/v0.17.3...v0.18.0)

##### Changed

- `Source` trait is now also implemented for `Box<dyn Source>` and `&mut
Source`
- `fn new_vorbis` is now also available when the `symphonia-vorbis`
feature is enabled

##### Added

- Adds a new method `try_seek` to all sources. It returns either an
error or
seeks to the given position. A few sources are "unsupported" they return
the
    error `Unsupported`.
-   Adds `SpatialSink::clear()` bringing it in line with `Sink`

##### Fixed

- channel upscaling now follows the 'WAVEFORMATEXTENSIBLE' format and no
longer
    repeats the last source channel on all extra output channels.
Stereo content playing on a 5.1 speaker set will now only use the front
left
and front right speaker instead of repeating the right sample on all
speakers
    except the front left one.
- `mp3::is_mp3()` no longer changes the position in the stream when the
stream
    is mp3

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-16 21:55:32 -06:00
Conrad Irwin
1285504b3e Fix panic in wasm extensions (#17922)
Release Notes:

- N/A

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-09-16 21:35:28 -06:00
Peter Tripp
83192c29e8 Try and fix tests (#17920)
Tests on main started breaking following
bc5ed1334f
from:
- https://github.com/zed-industries/zed/pull/17734

First breakage:
https://github.com/zed-industries/zed/actions/runs/10894059586/job/30230118999
2024-09-16 23:16:08 -04:00
thataboy
a141415bd3 Add cursor_shape setting for the default editor (#17572)
Closes https://github.com/zed-industries/zed/issues/16451,
https://github.com/zed-industries/zed/issues/14447,
https://github.com/zed-industries/zed/issues/7203

Addresses but does not closes
https://github.com/zed-industries/zed/issues/5179

Expose cursor shape selection to users. Possibly controversial, since
block cursor seems preserved for vim and terminal. But the heart wants
what it wants?

Release Notes:

- Added a setting for `cursor_shape`. Can be `bar`, `block`,
`underline`, or `hollow`. Default is `bar`.
2024-09-16 21:01:43 -06:00
Kevin Wang
d315405be1 Return completion proposals from inline completion providers (#17578)
Updates the inline completion provider to return a completion proposal
which is then converted to a completion state. This completion proposal
includes more detailed information about which inlays specifically
should be rendered.

Release Notes:

- Added support for fill-in-the-middle style inline completions


![image](https://github.com/user-attachments/assets/1830700f-5a76-4d1f-ac6d-246cc69b64c5)
2024-09-16 20:57:58 -06:00
bestgopher
37b2f4b9d3 Wrap terminal commands in single quotation marks instead of backticks (#17637)
before:

![image](https://github.com/user-attachments/assets/ffe8b036-297a-414e-92af-28a0230d3d25)
after:

![image](https://github.com/user-attachments/assets/0cf22775-69ae-4320-b9bd-6b78fe01571f)

Since I often copy the output commands to run in the command line, using
backticks can cause errors because, in shell, backticks mean passing the
execution result of the command inside them to the -c option. Therefore,
I replace backticks with single quotes here.

![image](https://github.com/user-attachments/assets/f1f809fe-c10a-423a-87a2-58148962d8b0)

Release Notes:

- Fix display of task commands to not use backticks

Signed-off-by: bestgopher <84328409@qq.com>
2024-09-16 20:48:13 -06:00
Bai
4441150809 Add missing Void Linux dependencies (#17827)
Release Notes:

- N/A
2024-09-16 18:46:49 -06:00
Fernando Tagawa
d7c45ccf2f x11: Fix preedit for CJK and partially fix unresponsive keyboard with xim (#17373)
Closes #15833
Related to [#12495
comment](https://github.com/zed-industries/zed/pull/12495#issuecomment-2328356125)

Destroying and recreating the Input context was the only way to reset
the IME but it's making the keyboard unresponsive sometimes due to a XIM
error.

The keyboard will still be unresponsive if you close your IME while
using zed, but I don't know how to fix this.

* Fixed preedit drawing for CJK
* Fixed unresponsive keyboard by properly implementing reset_ic in
`xim-rs`

Release Notes:

- N/A
2024-09-16 18:46:03 -06:00
Max Brunsfeld
bc5ed1334f Upgrade tree sitter and all grammars (#17734)
Fixes https://github.com/zed-industries/zed/issues/5291

Release Notes:

- Fixed a bug where the 'toggle comments' command didn't use the right
comment syntax in JSX and TSX elements.

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-09-16 17:10:57 -07:00
Marshall Bowers
b54b3d6246 editor: Add rewrap command (#17909)
This PR adds a rewrap command to the editor.

Executing this command will rewrap the text in the selection to the
maximum line width:


https://github.com/user-attachments/assets/69aa9428-17fd-4315-89f1-f354d0f5f459

If there isn't an active selection, the selection will be expanded to
contiguous text from the cursor's location.

Rewrapping only works in Markdown, Plain Text, and within comments for
any other language.

Release Notes:

- Added an `editor: rewrap` command for rewrapping text to the maximum
line width.

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Bennet <bennet@zed.dev>
2024-09-16 19:58:22 -04:00
Conrad Irwin
e7d18ef359 See language server status on remote (#17912)
Release Notes:

- ssh-remoting: Show LSP status in status bar

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-09-16 17:05:26 -06:00
Max Brunsfeld
243629cce8 Fix keystroke observer leak in vim crate (#17913)
Release Notes:

- Fixed a performance problem that happened when using vim mode after
opening and closing many editors

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Nathan <nathan@zed.dev>
2024-09-16 15:50:12 -07:00
Peter Tripp
67f149a4bc Ollama: Specify keep_alive via settings (#17906) 2024-09-16 18:47:25 -04:00
Conrad Irwin
e66ea9e5d4 Fix renames over language server for SSH remoting (#17897)
Release Notes:

- ssh remoting: Fix rename over language server

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Max <max@zed.dev>
2024-09-16 16:20:17 -06:00
Max Brunsfeld
01bb10f518 Move ProtoClient to RPC crate, behind feature flag disabled in collab (#17908)
This fixes a bug where we accidentally added a `gpui` transitive
dependency in `collab`.

Release Notes:

- N/A
2024-09-16 14:50:30 -07:00
Conrad Irwin
ca2cce79ed ssh lsp completions (#17665)
Release Notes:

* ssh-remoting: Fixed shell environment loading for remote shells.
2024-09-16 12:22:39 -06:00
Marshall Bowers
dea85099a2 collab: Override Cargo configuration in the Dockerfile (#17901)
This PR moves the override for the Cargo configuration for collab into
the `Dockerfile` rather than having it be something some in the external
environment.

This makes it possible to build the Docker image locally without having
to replace `.cargo/config.toml` with the contents of
`.cargo/collab-config.toml`.

Release Notes:

- N/A
2024-09-16 14:17:15 -04:00
Marshall Bowers
b48c2c5846 Update comment to point to culpable commit (#17896)
This PR updates the comment added in #17893 to reference the commit that
was identified by `git bisect`.

Release Notes:

- N/A
2024-09-16 13:41:31 -04:00
Marshall Bowers
f3769322ad collab: Mark RunPod environment variables as optional in Kubernetes template (#17895)
This PR marks the RunPod environment variables as optional in the
Kubernetes template so that we can deploy without them being present.

Collab is already accounting for their absence.

Release Notes:

- N/A
2024-09-16 13:41:22 -04:00
Thorsten Ball
2c9d07663a rust: Lookup rust-analyzer on PATH by default (#17885)
This is a highly and frequently requested change. Users are confused why
rust-analyzer isn't used if it's on their `$PATH`.

Previously I didn't enable this by default, because rust-analyzer would
complain about an "Unknown binary", like this

Unknown binary 'rust-analyzer' in official toolchain
'1.81-aarch64-apple-darwin'.\n

But turns out that only happens when you have installed rust-analyzer
via the rustup toolchain, it's in your `$PATH`, and the
`rust-toolchain.toml` of the repository doesn't mention it.

The fix is to delete `~/.cargo/bin/rust-analyzer` and, if preferred, use
`rust-analyzer` by installing the binary manually.

Release Notes:

- Changed rust-analyzer support to lookup `rust-analyzer` binaries by
default in `$PATH`. That changes the default value to something users
requested.
2024-09-16 18:45:11 +02:00
Peter Tripp
784c3093ae Remove incorrect documentation for language-specific theme overrides (#17894)
Zed does not support this, the documentation was added in error. And if it were supported, the key would be `experimental.theme_overrides` not `theme_overrides`.
2024-09-16 12:41:33 -04:00
Marshall Bowers
ba5c1322ce collab: Add missing dependencies for building Docker image (#17893)
This PR adds some missing dependencies to the Docker image that are now
needed in order to build collab.

When trying to build the docker image at
761129e373 it fails with the following
error:

```
985.3   = note: /usr/bin/ld: cannot find -lxkbcommon: No such file or directory
985.3           /usr/bin/ld: cannot find -lxkbcommon-x11: No such file or directory
985.3           collect2: error: ld returned 1 exit status
985.3           
985.3 
985.4 error: could not compile `collab` (bin "collab") due to 1 previous error
```

The last time we built the Docker image for collab was:

- Staging: 4f408ec65a
- Production: fc4c533d0a

Release Notes:

- N/A
2024-09-16 12:40:56 -04:00
Peter Tripp
28fb1fd19b Regenerate terms.rtf fixing Privacy Policy link (#17877) 2024-09-16 12:17:41 -04:00
Zhang
90b77e125a Don't show extra row in toolbar if it is empty (#17888)
Closes #17851

Release Notes:

- Removed an extra row in the toolbar if it was empty.
2024-09-16 12:00:49 -04:00
Marshall Bowers
fb79346e6f dart: Bump to v0.1.0 (#17887)
This PR bumps the Dart extension to v0.1.0.

Changes:

- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/17494

Release Notes:

- N/A
2024-09-16 11:57:34 -04:00
jvmncs
761129e373 Update nightly tag every night (#17879)
Previous `release_nightly` workflow would trigger every night or on push
to the `nightly` tag, which means `nightly` tag wasn't always in sync
with the nightly we bundle. This change syncs the tag up with the
bundled releases.

Release Notes:

- N/A
2024-09-16 11:09:31 -04:00
Finn Evers
22db569adf docs: Use json to fix syntax highlighting (#17884)
This follows up the [issue with mdbook notpeter
mentioned](https://github.com/zed-industries/zed/pull/17864#issuecomment-2353089065)
by replacing `jsonc` where used in the docs with `json`.

Additionally, one missing `json` - highlight was added for the
search-section.

Release Notes:

- N/A
2024-09-16 11:04:56 -04:00
Yohanes Bandung Bondowoso
2cae6f3e08 dart: Respect LSP binary settings (#17494)
Enable configuring Dart's LSP from other means of installation types.

Some users don't install the `dart` binary, but uses version manager.

In the example, I uses [FVM](https://fvm.app/) (short for "Flutter
Version Manager").

I have tested this with "Install Dev Extensions".

Release Notes:

- N/A

cc other maintainer: @agent3bood @flo80

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-16 11:03:51 -04:00
ihavecoke
2baa704af7 Clamp tab_size setting between 1 and 16 (#17882)
Release Notes:

- Changed `tab_size` setting to require values be between 1 and 16
(inclusive).


### jetbrains settings 

#### Max value

![image](https://github.com/user-attachments/assets/54b772fd-e670-4d77-b3e9-757b08659f55)

When the maximum value is exceeded, the configuration box turns red to
remind the user


![image](https://github.com/user-attachments/assets/fcdb6313-be97-4528-b654-5900bcaeafec)

If the user still saves, jetbrains does not process it and resets it to
the system default value of 4

<img width="700" alt="image"
src="https://github.com/user-attachments/assets/a76b1cba-d23f-4a32-95ee-f05d208ca186">

Without restrictions, I feel not good. Here is a random setting of a
relatively large value



https://github.com/user-attachments/assets/c3bdf262-ba08-4bc2-996a-5ad2a37c567f

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-16 10:29:42 -04:00
Thorsten Ball
e3d54b2211 vim: Fix ctrl-b not moving the cursor (#17808)
Closes #17687

Release Notes:

- Fixed `ctrl-b` not moving the cursor.

---------

Co-authored-by: Abdelhakim Qbaich <abdelhakim@qbaich.com>
Co-authored-by: Pete LeVasseur <plevasseur@gmail.com>
2024-09-16 15:01:20 +02:00
Thorsten Ball
f986513d0d gpui: Remove debug print statement (#17878)
Release Notes:

- N/A
2024-09-16 15:00:20 +02:00
Nate Butler
02dfe08ce8 Welcome tweaks (#17874)
This PR adds "Open Settings" and "Open Docs" to the welcome page, as
well as some minor design polish.

The welcome page needs a full redesign at some point so I didn't too to
much here in terms of structure/content.

Before | After:

![CleanShot 2024-09-16 at 08 12
23@2x](https://github.com/user-attachments/assets/722175ec-d129-4060-827f-f02f572115da)

---

Release Notes:

- Improved welcome page design and added additional links.
2024-09-16 08:29:46 -04:00
Danilo Leal
4e1bb68620 Use buffer font for the inline assistant (#17875)
Closes https://github.com/zed-industries/zed/issues/17738

This PR is motivated by having also noticed what the issue above
mentions. Looked it further and it does seem the inline assistant had a
slightly bigger font-size even though it was using the configured UI
font-size as per https://github.com/zed-industries/zed/pull/17542. I'm
not sure why that was, technically speaking. However, I ended up
realizing that, given we're within the editor, it'd make more sense to
use the buffer font instead. With this change, the size mismatch seems
to be gone.

<img width="900" alt="Screenshot 2024-09-16 at 2 13 28 PM"
src="https://github.com/user-attachments/assets/fe2f3096-d5dd-4986-ba96-f2ca7578d84d">

Release Notes:

- N/A
2024-09-16 09:28:27 -03:00
Danilo Leal
96a5daaf3f Refine symbolic link project tooltip (#17869)
This PR uses the tooltip with meta to display the info that a project
panel item is actually a symbolic link.

| Before | After |
|--------|--------|
| <img width="826" alt="Screenshot 2024-09-16 at 11 20 15 AM"
src="https://github.com/user-attachments/assets/7823f1f2-ed92-4b9a-b95e-c0777cb32387">
| <img width="638" alt="Screenshot 2024-09-16 at 11 19 12 AM"
src="https://github.com/user-attachments/assets/5d441ba5-6741-482c-bf2a-ec7e172953df">
|

Release Notes:

- N/A
2024-09-16 08:12:24 -03:00
Danilo Leal
29a5def12c Refine assistant config UI (#17871)
This PR does a little bit of a touch-up on the copywriting on the
assistant config UI. I had friends reporting to me that some of the
writing could be clearer, and hopefully, this goes into that direction!

Release Notes:

- N/A
2024-09-16 08:12:07 -03:00
Nate Butler
cdc3791544 Fix incorrect icons (#17856) 2024-09-16 00:24:18 -04:00
Chris Veness
524a1a6fec Note in initial_user_settings.json how to access the command palette (#17854)
Newcomers might not know / remember how to access the command palette.

Release Notes:

- N/A
2024-09-15 19:45:48 -04:00
Peter Tripp
4f251429c7 Add perplexity extension readme (#17861)
Release Notes:

- N/A
2024-09-15 19:45:06 -04:00
Marshall Bowers
6f337de440 ui: Clean up doc comments for Vector (#17834)
This PR cleans up the doc comments for the `Vector` component.

Release Notes:

- N/A
2024-09-14 17:26:56 -04:00
Marshall Bowers
d56fa25830 context_servers: Hide actions when no context servers are configured (#17833)
This PR filters out the context servers actions from the command palette
when no context servers are configured.

Release Notes:

- N/A
2024-09-14 17:00:37 -04:00
Junkui Zhang
d5268c5197 docs: Add proxy settings (#17797)
I'm not sure if I placed `Network Proxy` in the correct position. What I
noticed is that the first half of the documentation seems to be
organized alphabetically, but the second half is not. I tried to
position `Network Proxy` in a spot that seemed reasonable while
maintaining alphabetical order. If there's a better suggestion, I'd be
happy to make adjustments.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-14 16:17:38 -04:00
krizej
40a00fb224 Add missing operators and keywords to the C syntax highlighting (#17541)
Based on https://en.cppreference.com/w/c/language/expressions#Operators

Release Notes:

- Added missing operators and keywords to the C syntax highlighting
2024-09-14 16:06:03 -04:00
Zhang
00c0a7254a gpui: Allow TextInput example to lose and gain focus (#17823)
Improved the input.rs example file in gpui crate.

The new code 
* allow this text field to lose and gain input focus.
* change TextInput's height from full to fix.

Release Notes:

- N/A
2024-09-14 15:49:53 -04:00
330 changed files with 8976 additions and 4174 deletions

23
.github/workflows/bump_nightly_tag.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Update Nightly Tag
on:
schedule:
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
- cron: "0 7 * * *"
jobs:
update-nightly-tag:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
fetch-depth: 0
- name: Update nightly tag
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git tag -f nightly
git push origin nightly --force

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,8 +101,7 @@ jobs:
timeout-minutes: 60
name: (Linux) Run Clippy and tests
runs-on:
- self-hosted
- deploy
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -121,6 +111,15 @@ 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
- name: cargo clippy
run: ./script/clippy
@@ -145,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`.
@@ -271,24 +271,20 @@ jobs:
timeout-minutes: 60
name: Create a Linux bundle
runs-on:
- self-hosted
- deploy
- buildjet-16vcpu-ubuntu-2204
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
clean: false
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Install Linux dependencies
run: ./script/linux
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
@@ -343,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:
@@ -354,26 +350,9 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
clean: false
- name: "Setup jq"
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb # v2
- name: Set up Clang
run: |
sudo apt-get update
sudo apt-get install -y llvm-15 clang-15 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-15/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
with:
mold-version: 2.32.0
- name: rustup
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
- name: Install Linux dependencies
run: ./script/linux
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}

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
@@ -75,9 +74,6 @@ jobs:
with:
clean: false
- name: Set up default .cargo/config.toml
run: cp ./.cargo/collab-config.toml ./.cargo/config.toml
- name: Build docker image
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
@@ -92,8 +88,7 @@ jobs:
needs:
- publish
runs-on:
- self-hosted
- deploy
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Sign into Kubernetes

View File

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

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

@@ -1,9 +1,6 @@
name: Release Nightly
on:
schedule:
# Fire every day at 7:00am UTC (Roughly before EU workday and after US workday)
- cron: "0 7 * * *"
push:
tags:
- "nightly"
@@ -100,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 }}
@@ -117,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
@@ -148,23 +150,8 @@ jobs:
with:
clean: false
- name: "Setup jq"
uses: dcarbone/install-jq-action@8867ddb4788346d7c22b72ea2e2ffe4d514c7bcb # v2
- name: Set up Clang
run: |
sudo apt-get update
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
with:
mold-version: 2.32.0
- name: rustup
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
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

611
Cargo.lock generated

File diff suppressed because it is too large Load Diff

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",
@@ -225,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" }
@@ -393,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"] }
@@ -415,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"
@@ -430,43 +435,43 @@ tiny_http = "0.8"
toml = "0.8"
tokio = { version = "1", features = ["full"] }
tower-http = "0.4.4"
tree-sitter = { version = "0.22", features = ["wasm"] }
tree-sitter-bash = "0.21"
tree-sitter-c = "0.21"
tree-sitter-cpp = "0.22"
tree-sitter-css = "0.21"
tree-sitter-elixir = "0.2"
tree-sitter-embedded-template = "0.20.0"
tree-sitter-go = "0.21"
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "1f55029bacd0a6a11f6eb894c4312d429dcf735c", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work", rev = "dcbabff454703c3a4bc98a23cf8778d4be46fd22" }
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "6dd0303acf7138dd2b9b432a229e16539581c701" }
tree-sitter = { version = "0.23", features = ["wasm"] }
tree-sitter-bash = "0.23"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23"
tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-go = "0.23"
tree-sitter-go-mod = { git = "https://github.com/zed-industries/tree-sitter-go-mod", rev = "a9aea5e358cde4d0f8ff20b7bc4fa311e359c7ca", package = "tree-sitter-gomod" }
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex", rev = "1dd45142fbb05562e35b2040c6129c9bca346592" }
tree-sitter-html = "0.20"
tree-sitter-jsdoc = "0.21"
tree-sitter-json = "0.21"
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "e3855e37f8f2c71aa7513c18a9c95fb7461b1b10" }
protols-tree-sitter-proto = "0.2"
tree-sitter-python = "0.21"
tree-sitter-regex = "0.21"
tree-sitter-ruby = "0.21"
tree-sitter-rust = "0.21"
tree-sitter-typescript = "0.21"
tree-sitter-yaml = "0.6"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.23"
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
protols-tree-sitter-proto = { git = "https://github.com/zed-industries/tree-sitter-proto", rev = "0848bd30a64be48772e15fbb9d5ba8c0cc5772ad" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.23"
tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unindent = "0.1.7"
unicase = "2.6"
unicode-segmentation = "1.10"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
wasmparser = "0.201"
wasm-encoder = "0.201"
wasmtime = { version = "21.0.1", default-features = false, features = [
wasmparser = "0.215"
wasm-encoder = "0.215"
wasmtime = { version = "24", default-features = false, features = [
"async",
"demangle",
"runtime",
"cranelift",
"component-model",
] }
wasmtime-wasi = "21.0.1"
wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
@@ -489,7 +494,6 @@ features = [
"implement",
"Foundation_Numerics",
"Storage",
"System",
"System_Threading",
"UI_ViewManagement",
"Wdk_System_SystemServices",
@@ -520,13 +524,10 @@ features = [
"Win32_UI_Input_Ime",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging",
]
[patch.crates-io]
# Patch Tree-sitter for updated wasmtime.
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f4a57817d58a2f134fe863674acad6bbf007228" }
[profile.dev]
split-debuginfo = "unpacked"
debug = "limited"

View File

@@ -4,11 +4,38 @@ FROM rust:1.81-bookworm as builder
WORKDIR app
COPY . .
# Replace the Cargo configuration with the one used by collab.
COPY ./.cargo/collab-config.toml ./.cargo/config.toml
# Compile collab server
ARG CARGO_PROFILE_RELEASE_PANIC=abort
ARG GITHUB_SHA
ENV GITHUB_SHA=$GITHUB_SHA
# At some point in the past 3 weeks, additional dependencies on `xkbcommon` and
# `xkbcommon-x11` were introduced into collab.
#
# A `git bisect` points to this commit as being the culprit: `b8e6098f60e5dabe98fe8281f993858dacc04a55`.
#
# Now when we try to build collab for the Docker image, it fails with the following
# error:
#
# ```
# 985.3 = note: /usr/bin/ld: cannot find -lxkbcommon: No such file or directory
# 985.3 /usr/bin/ld: cannot find -lxkbcommon-x11: No such file or directory
# 985.3 collect2: error: ld returned 1 exit status
# ```
#
# The last successful deploys were at:
# - Staging: `4f408ec65a3867278322a189b4eb20f1ab51f508`
# - Production: `fc4c533d0a8c489e5636a4249d2b52a80039fbd7`
#
# Installing these as a temporary workaround, but I think ideally we'd want to figure
# out what caused them to be included in the first place.
RUN apt-get update; \
apt-get install -y --no-install-recommends libxkbcommon-dev libxkbcommon-x11-dev
RUN --mount=type=cache,target=./script/node_modules \
--mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-select"><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h1"/><path d="M14 3h1"/><path d="M14 21h1"/><path d="M3 9v1"/><path d="M21 9v1"/><path d="M3 14v1"/><path d="M21 14v1"/><line x1="7" x2="15" y1="8" y2="8"/><line x1="7" x2="17" y1="12" y2="12"/><line x1="7" x2="13" y1="16" y2="16"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 345 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>

Before

Width:  |  Height:  |  Size: 345 B

View File

@@ -0,0 +1,6 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4H8" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 10L11 10" stroke="black" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4" cy="10" r="1.875" stroke="black" stroke-width="1.75"/>
<circle cx="10" cy="4" r="1.875" stroke="black" stroke-width="1.75"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.6665 14V9.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.6665 6.66667V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 14V8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5.33333V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.3335 14V10.6667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.3335 8V2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.3335 9.33333H5.00016" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.6665 5.33334H9.33317" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 10.6667H13.6667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-select"><path d="M5 3a2 2 0 0 0-2 2"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M9 3h1"/><path d="M9 21h1"/><path d="M14 3h1"/><path d="M14 21h1"/><path d="M3 9v1"/><path d="M21 9v1"/><path d="M3 14v1"/><path d="M21 14v1"/><line x1="7" x2="15" y1="8" y2="8"/><line x1="7" x2="17" y1="12" y2="12"/><line x1="7" x2="13" y1="16" y2="16"/></svg>

After

Width:  |  Height:  |  Size: 610 B

View File

@@ -56,6 +56,7 @@
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose",
"alt-q": "editor::Rewrap",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
"shift-delete": "editor::Cut",
@@ -165,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

@@ -51,6 +51,7 @@
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-t": "editor::Transpose",
"alt-q": "editor::Rewrap",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-delete": "editor::DeleteToEndOfLine",
"alt-backspace": "editor::DeleteToPreviousWordStart",
@@ -187,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

@@ -124,6 +124,7 @@
"g i": "vim::InsertAtPrevious",
"g ,": "vim::ChangeListNewer",
"g ;": "vim::ChangeListOlder",
"g q": "editor::Rewrap",
"shift-h": "vim::WindowTop",
"shift-m": "vim::WindowMiddle",
"shift-l": "vim::WindowBottom",

View File

@@ -111,6 +111,18 @@
"use_system_path_prompts": true,
// Whether the cursor blinks in the editor.
"cursor_blink": true,
// Cursor shape for the default editor.
// 1. A vertical bar
// "bar"
// 2. A block that surrounds the following character
// "block"
// 3. An underline that runs along the following character
// "underscore"
// 4. A box drawn around the following character
// "hollow"
//
// Default: not set, defaults to "bar"
"cursor_shape": null,
// How to highlight the current line in the editor.
//
// 1. Don't highlight the current line:
@@ -306,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,
@@ -698,7 +714,7 @@
// to the current working directory. We recommend overriding this
// in your project's settings, rather than globally.
"directories": [".env", "env", ".venv", "venv"],
// Can also be `csh`, `fish`, and `nushell`
// Can also be `csh`, `fish`, `nushell` and `power_shell`
"activate_script": "default"
}
},

View File

@@ -5,7 +5,7 @@
//
// To see all of Zed's default settings without changing your
// custom settings, run `zed: open default settings` from the
// command palette
// command palette (cmd-shift-p / ctrl-shift-p)
{
"ui_font_size": 16,
"buffer_font_size": 16,

View File

@@ -65,6 +65,7 @@ proto.workspace = true
regex.workspace = true
release_channel.workspace = true
rope.workspace = true
rpc.workspace = true
schemars.workspace = true
search.workspace = true
semantic_index.workspace = true
@@ -93,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

@@ -41,9 +41,9 @@ use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use slash_command::{
auto_command, context_server_command, default_command, diagnostics_command, docs_command,
fetch_command, file_command, now_command, project_command, prompt_command, search_command,
symbols_command, tab_command, terminal_command, workflow_command,
auto_command, context_server_command, default_command, delta_command, diagnostics_command,
docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
search_command, symbols_command, tab_command, terminal_command, workflow_command,
};
use std::path::PathBuf;
use std::sync::Arc;
@@ -58,6 +58,7 @@ actions!(
[
Assist,
Split,
CopyCode,
CycleMessageRole,
QuoteSelection,
InsertIntoEditor,
@@ -367,6 +368,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
let slash_command_registry = SlashCommandRegistry::global(cx);
slash_command_registry.register_command(file_command::FileSlashCommand, true);
slash_command_registry.register_command(delta_command::DeltaSlashCommand, true);
slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
slash_command_registry.register_command(tab_command::TabSlashCommand, true);
slash_command_registry.register_command(project_command::ProjectSlashCommand, true);

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,
@@ -1906,7 +1919,22 @@ impl ContextEditor {
cx: &mut ViewContext<Self>,
) {
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
let output = command.run(arguments, workspace, self.lsp_adapter_delegate.clone(), cx);
let context = self.context.read(cx);
let sections = context
.slash_command_output_sections()
.into_iter()
.filter(|section| section.is_valid(context.buffer().read(cx)))
.cloned()
.collect::<Vec<_>>();
let snapshot = context.buffer().read(cx).snapshot();
let output = command.run(
arguments,
&sections,
snapshot,
workspace,
self.lsp_adapter_delegate.clone(),
cx,
);
self.context.update(cx, |context, cx| {
context.insert_command_output(
command_range,
@@ -3085,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,
@@ -3103,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);
@@ -3121,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,
@@ -3267,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)) =
@@ -3634,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();
@@ -4117,9 +4247,11 @@ impl ContextEditor {
.child(Label::new(label)),
)
.child(
Button::new("open-configuration", "Open configuration")
Button::new("open-configuration", "Configure Providers")
.size(ButtonSize::Compact)
.icon(Some(IconName::SlidersVertical))
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
.style(ButtonStyle::Filled)
.on_click({
let focus_handle = self.focus_handle(cx).clone();
@@ -4198,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,
@@ -5235,7 +5409,7 @@ fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) ->
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::CursorIBeam))
.child(Icon::new(IconName::TextSnippet))
.child(Label::new(title.clone()).single_line())
.on_click(move |_, cx| {
editor
@@ -5367,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>)
})
})
}
@@ -5482,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,9 +46,9 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use telemetry_events::AssistantKind;
use telemetry_events::{AssistantKind, AssistantPhase};
use text::BufferSnapshot;
use util::{post_inc, TryFutureExt};
use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
@@ -162,6 +162,9 @@ impl ContextOperation {
)?,
icon: section.icon_name.parse()?,
label: section.label.into(),
metadata: section
.metadata
.and_then(|metadata| serde_json::from_str(&metadata).log_err()),
})
})
.collect::<Result<Vec<_>>>()?,
@@ -242,6 +245,9 @@ impl ContextOperation {
)),
icon_name: icon_name.to_string(),
label: section.label.to_string(),
metadata: section.metadata.as_ref().and_then(|metadata| {
serde_json::to_string(metadata).log_err()
}),
}
})
.collect(),
@@ -635,12 +641,13 @@ impl Context {
.slash_command_output_sections
.iter()
.filter_map(|section| {
let range = section.range.to_offset(buffer);
if section.range.start.is_valid(buffer) && !range.is_empty() {
if section.is_valid(buffer) {
let range = section.range.to_offset(buffer);
Some(assistant_slash_command::SlashCommandOutputSection {
range,
icon: section.icon,
label: section.label.clone(),
metadata: section.metadata.clone(),
})
} else {
None
@@ -997,14 +1004,14 @@ impl Context {
fn handle_buffer_event(
&mut self,
_: Model<Buffer>,
event: &language::Event,
event: &language::BufferEvent,
cx: &mut ModelContext<Self>,
) {
match event {
language::Event::Operation(operation) => cx.emit(ContextEvent::Operation(
language::BufferEvent::Operation(operation) => cx.emit(ContextEvent::Operation(
ContextOperation::BufferOperation(operation.clone()),
)),
language::Event::Edited => {
language::BufferEvent::Edited => {
self.count_remaining_tokens(cx);
self.reparse(cx);
// Use `inclusive = true` to invalidate a step when an edit occurs
@@ -1825,6 +1832,7 @@ impl Context {
..buffer.anchor_before(start + section.range.end),
icon: section.icon,
label: section.label,
metadata: section.metadata,
})
.collect::<Vec<_>>();
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
@@ -2126,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,
@@ -2977,6 +2986,7 @@ impl SavedContext {
..buffer.anchor_before(section.range.end),
icon: section.icon,
label: section.label,
metadata: section.metadata,
}
})
.collect(),

View File

@@ -12,7 +12,7 @@ use assistant_slash_command::{
use collections::HashSet;
use fs::FakeFs;
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
use language::{Buffer, LanguageRegistry, LspAdapterDelegate};
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
use project::Project;
@@ -1089,6 +1089,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
range: section_start..section_end,
icon: ui::IconName::Ai,
label: "section".into(),
metadata: None,
});
}
@@ -1425,6 +1426,8 @@ impl SlashCommand for FakeSlashCommand {
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,

View File

@@ -2,7 +2,6 @@ use crate::{
prompts::PromptBuilder, Context, ContextEvent, ContextId, ContextOperation, ContextVersion,
SavedContext, SavedContextMetadata,
};
use ::proto::AnyProtoClient;
use anyhow::{anyhow, Context as _, Result};
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
use clock::ReplicaId;
@@ -16,6 +15,7 @@ use language::LanguageRegistry;
use paths::contexts_dir;
use project::Project;
use regex::Regex;
use rpc::AnyProtoClient;
use std::{
cmp::Reverse,
ffi::OsStr,

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 {
@@ -1465,7 +1493,7 @@ impl Render for PromptEditor {
.border_y_1()
.border_color(cx.theme().status().info_border)
.size_full()
.py(cx.line_height() / 2.)
.py(cx.line_height() / 2.5)
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
@@ -1918,12 +1946,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: settings.ui_font_size.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(
@@ -2559,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,
@@ -3360,7 +3388,7 @@ mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(
r#"

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

@@ -22,6 +22,7 @@ use workspace::Workspace;
pub mod auto_command;
pub mod context_server_command;
pub mod default_command;
pub mod delta_command;
pub mod diagnostics_command;
pub mod docs_command;
pub mod fetch_command;

View File

@@ -1,7 +1,7 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::ArgumentCompletion;
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
@@ -87,6 +87,8 @@ impl SlashCommand for AutoCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,

View File

@@ -9,7 +9,7 @@ use context_servers::{
protocol::PromptInfo,
};
use gpui::{Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use text::LineEnding;
@@ -96,7 +96,6 @@ impl SlashCommand for ContextServerSlashCommand {
replace_previous_arguments: false,
})
.collect();
Ok(completions)
})
} else {
@@ -107,6 +106,8 @@ impl SlashCommand for ContextServerSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -141,6 +142,7 @@ impl SlashCommand for ContextServerSlashCommand {
.description
.unwrap_or(format!("Result from {}", prompt_name)),
),
metadata: None,
}],
text: prompt,
run_commands_in_text: false,

View File

@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use gpui::{Task, WeakView};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use std::{
fmt::Write,
sync::{atomic::AtomicBool, Arc},
@@ -43,6 +43,8 @@ impl SlashCommand for DefaultSlashCommand {
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -70,6 +72,7 @@ impl SlashCommand for DefaultSlashCommand {
range: 0..text.len(),
icon: IconName::Library,
label: "Default".into(),
metadata: None,
}],
text,
run_commands_in_text: true,

View File

@@ -0,0 +1,109 @@
use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use collections::HashSet;
use futures::future;
use gpui::{Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use text::OffsetRangeExt;
use workspace::Workspace;
pub(crate) struct DeltaSlashCommand;
impl SlashCommand for DeltaSlashCommand {
fn name(&self) -> String {
"delta".into()
}
fn description(&self) -> String {
"re-insert changed files".into()
}
fn menu_text(&self) -> String {
"Re-insert Changed Files".into()
}
fn requires_argument(&self) -> bool {
false
}
fn complete_argument(
self: Arc<Self>,
_arguments: &[String],
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
unimplemented!()
}
fn run(
self: Arc<Self>,
_arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new();
let mut file_command_new_outputs = Vec::new();
for section in context_slash_command_output_sections.iter().rev() {
if let Some(metadata) = section
.metadata
.as_ref()
.and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
{
if paths.insert(metadata.path.clone()) {
file_command_old_outputs.push(
context_buffer
.as_rope()
.slice(section.range.to_offset(&context_buffer)),
);
file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
&[metadata.path.clone()],
context_slash_command_output_sections,
context_buffer.clone(),
workspace.clone(),
delegate.clone(),
cx,
));
}
}
}
cx.background_executor().spawn(async move {
let mut output = SlashCommandOutput::default();
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
for (old_text, new_output) in file_command_old_outputs
.into_iter()
.zip(file_command_new_outputs)
{
if let Ok(new_output) = new_output {
if let Some(file_command_range) = new_output.sections.first() {
let new_text = &new_output.text[file_command_range.range.clone()];
if old_text.chars().ne(new_text.chars()) {
output.sections.extend(new_output.sections.into_iter().map(
|section| SlashCommandOutputSection {
range: output.text.len() + section.range.start
..output.text.len() + section.range.end,
icon: section.icon,
label: section.label,
metadata: section.metadata,
},
));
output.text.push_str(&new_output.text);
}
}
}
}
Ok(output)
})
}
}

View File

@@ -9,10 +9,9 @@ use language::{
};
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
use rope::Point;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::{
ops::Range,
fmt::Write,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
use ui::prelude::*;
@@ -163,6 +162,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -175,68 +176,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
cx.spawn(move |_| async move {
let Some((text, sections)) = task.await? else {
return Ok(SlashCommandOutput {
sections: vec![SlashCommandOutputSection {
range: 0..1,
icon: IconName::Library,
label: "No Diagnostics".into(),
}],
text: "\n".to_string(),
run_commands_in_text: true,
});
};
let sections = sections
.into_iter()
.map(|(range, placeholder_type)| SlashCommandOutputSection {
range,
icon: match placeholder_type {
PlaceholderType::Root(_, _) => IconName::Warning,
PlaceholderType::File(_) => IconName::File,
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => IconName::XCircle,
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
IconName::Warning
}
},
label: match placeholder_type {
PlaceholderType::Root(summary, source) => {
let mut label = String::new();
label.push_str("Diagnostics");
if let Some(source) = source {
write!(label, " ({})", source).unwrap();
}
if summary.error_count > 0 || summary.warning_count > 0 {
label.push(':');
if summary.error_count > 0 {
write!(label, " {} errors", summary.error_count).unwrap();
if summary.warning_count > 0 {
label.push_str(",");
}
}
if summary.warning_count > 0 {
write!(label, " {} warnings", summary.warning_count).unwrap();
}
}
label.into()
}
PlaceholderType::File(file_path) => file_path.into(),
PlaceholderType::Diagnostic(_, message) => message.into(),
},
})
.collect();
Ok(SlashCommandOutput {
text,
sections,
run_commands_in_text: false,
})
})
cx.spawn(move |_| async move { task.await?.ok_or_else(|| anyhow!("No diagnostics found")) })
}
}
@@ -277,7 +217,7 @@ fn collect_diagnostics(
project: Model<Project>,
options: Options,
cx: &mut AppContext,
) -> Task<Result<Option<(String, Vec<(Range<usize>, PlaceholderType)>)>>> {
) -> Task<Result<Option<SlashCommandOutput>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1);
Some(path_matcher.sources().first().cloned().unwrap_or_default())
@@ -318,13 +258,13 @@ fn collect_diagnostics(
.collect();
cx.spawn(|mut cx| async move {
let mut text = String::new();
let mut output = SlashCommandOutput::default();
if let Some(error_source) = error_source.as_ref() {
writeln!(text, "diagnostics: {}", error_source).unwrap();
writeln!(output.text, "diagnostics: {}", error_source).unwrap();
} else {
writeln!(text, "diagnostics").unwrap();
writeln!(output.text, "diagnostics").unwrap();
}
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
let mut project_summary = DiagnosticSummary::default();
for (project_path, path, summary) in diagnostic_summaries {
@@ -341,10 +281,10 @@ fn collect_diagnostics(
continue;
}
let last_end = text.len();
let last_end = output.text.len();
let file_path = path.to_string_lossy().to_string();
if !glob_is_exact_file_match {
writeln!(&mut text, "{file_path}").unwrap();
writeln!(&mut output.text, "{file_path}").unwrap();
}
if let Some(buffer) = project_handle
@@ -352,75 +292,73 @@ fn collect_diagnostics(
.await
.log_err()
{
collect_buffer_diagnostics(
&mut text,
&mut sections,
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?,
options.include_warnings,
);
let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
}
if !glob_is_exact_file_match {
sections.push((
last_end..text.len().saturating_sub(1),
PlaceholderType::File(file_path),
))
output.sections.push(SlashCommandOutputSection {
range: last_end..output.text.len().saturating_sub(1),
icon: IconName::File,
label: file_path.into(),
metadata: None,
});
}
}
// No diagnostics found
if sections.is_empty() {
if output.sections.is_empty() {
return Ok(None);
}
sections.push((
0..text.len(),
PlaceholderType::Root(project_summary, error_source),
));
Ok(Some((text, sections)))
let mut label = String::new();
label.push_str("Diagnostics");
if let Some(source) = error_source {
write!(label, " ({})", source).unwrap();
}
if project_summary.error_count > 0 || project_summary.warning_count > 0 {
label.push(':');
if project_summary.error_count > 0 {
write!(label, " {} errors", project_summary.error_count).unwrap();
if project_summary.warning_count > 0 {
label.push_str(",");
}
}
if project_summary.warning_count > 0 {
write!(label, " {} warnings", project_summary.warning_count).unwrap();
}
}
output.sections.insert(
0,
SlashCommandOutputSection {
range: 0..output.text.len(),
icon: IconName::Warning,
label: label.into(),
metadata: None,
},
);
Ok(Some(output))
})
}
pub fn buffer_has_error_diagnostics(snapshot: &BufferSnapshot) -> bool {
for (_, group) in snapshot.diagnostic_groups(None) {
let entry = &group.entries[group.primary_ix];
if entry.diagnostic.severity == DiagnosticSeverity::ERROR {
return true;
}
}
false
}
pub fn write_single_file_diagnostics(
output: &mut String,
path: Option<&Path>,
pub fn collect_buffer_diagnostics(
output: &mut SlashCommandOutput,
snapshot: &BufferSnapshot,
) -> bool {
if let Some(path) = path {
if buffer_has_error_diagnostics(&snapshot) {
output.push_str("/diagnostics ");
output.push_str(&path.to_string_lossy());
return true;
}
}
false
}
fn collect_buffer_diagnostics(
text: &mut String,
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
snapshot: BufferSnapshot,
include_warnings: bool,
) {
for (_, group) in snapshot.diagnostic_groups(None) {
let entry = &group.entries[group.primary_ix];
collect_diagnostic(text, sections, entry, &snapshot, include_warnings)
collect_diagnostic(output, entry, &snapshot, include_warnings)
}
}
fn collect_diagnostic(
text: &mut String,
sections: &mut Vec<(Range<usize>, PlaceholderType)>,
output: &mut SlashCommandOutput,
entry: &DiagnosticEntry<Anchor>,
snapshot: &BufferSnapshot,
include_warnings: bool,
@@ -428,17 +366,17 @@ fn collect_diagnostic(
const EXCERPT_EXPANSION_SIZE: u32 = 2;
const MAX_MESSAGE_LENGTH: usize = 2000;
let ty = match entry.diagnostic.severity {
let (ty, icon) = match entry.diagnostic.severity {
DiagnosticSeverity::WARNING => {
if !include_warnings {
return;
}
DiagnosticType::Warning
("warning", IconName::Warning)
}
DiagnosticSeverity::ERROR => DiagnosticType::Error,
DiagnosticSeverity::ERROR => ("error", IconName::XCircle),
_ => return,
};
let prev_len = text.len();
let prev_len = output.text.len();
let range = entry.range.to_point(snapshot);
let diagnostic_row_number = range.start.row + 1;
@@ -448,11 +386,11 @@ fn collect_diagnostic(
let excerpt_range =
Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot);
text.push_str("```");
output.text.push_str("```");
if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) {
text.push_str(&language_name);
output.text.push_str(&language_name);
}
text.push('\n');
output.text.push('\n');
let mut buffer_text = String::new();
for chunk in snapshot.text_for_range(excerpt_range) {
@@ -461,46 +399,26 @@ fn collect_diagnostic(
for (i, line) in buffer_text.lines().enumerate() {
let line_number = start_row + i as u32 + 1;
writeln!(text, "{}", line).unwrap();
writeln!(output.text, "{}", line).unwrap();
if line_number == diagnostic_row_number {
text.push_str("//");
let prev_len = text.len();
write!(text, " {}: ", ty.as_str()).unwrap();
let padding = text.len() - prev_len;
output.text.push_str("//");
let prev_len = output.text.len();
write!(output.text, " {}: ", ty).unwrap();
let padding = output.text.len() - prev_len;
let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
.replace('\n', format!("\n//{:padding$}", "").as_str());
writeln!(text, "{message}").unwrap();
writeln!(output.text, "{message}").unwrap();
}
}
writeln!(text, "```").unwrap();
sections.push((
prev_len..text.len().saturating_sub(1),
PlaceholderType::Diagnostic(ty, entry.diagnostic.message.clone()),
))
}
#[derive(Clone)]
pub enum PlaceholderType {
Root(DiagnosticSummary, Option<String>),
File(String),
Diagnostic(DiagnosticType, String),
}
#[derive(Copy, Clone)]
pub enum DiagnosticType {
Warning,
Error,
}
impl DiagnosticType {
pub fn as_str(&self) -> &'static str {
match self {
DiagnosticType::Warning => "warning",
DiagnosticType::Error => "error",
}
}
writeln!(output.text, "```").unwrap();
output.sections.push(SlashCommandOutputSection {
range: prev_len..output.text.len().saturating_sub(1),
icon,
label: entry.diagnostic.message.clone().into(),
metadata: None,
});
}

View File

@@ -12,7 +12,7 @@ use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use ui::prelude::*;
use util::{maybe, ResultExt};
@@ -269,6 +269,8 @@ impl SlashCommand for DocsSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -349,6 +351,7 @@ impl SlashCommand for DocsSlashCommand {
range,
icon: IconName::FileDoc,
label: format!("docs ({provider}): {key}",).into(),
metadata: None,
})
.collect(),
run_commands_in_text: false,

View File

@@ -11,7 +11,7 @@ use futures::AsyncReadExt;
use gpui::{Task, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -128,6 +128,8 @@ impl SlashCommand for FetchSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -161,6 +163,7 @@ impl SlashCommand for FetchSlashCommand {
range,
icon: IconName::AtSign,
label: format!("fetch {}", url).into(),
metadata: None,
}],
run_commands_in_text: false,
})

View File

@@ -1,13 +1,14 @@
use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
use super::{diagnostics_command::collect_buffer_diagnostics, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
use std::{
fmt::Write,
ops::Range,
ops::{Range, RangeInclusive},
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
@@ -175,6 +176,8 @@ impl SlashCommand for FileSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -187,54 +190,15 @@ impl SlashCommand for FileSlashCommand {
return Task::ready(Err(anyhow!("missing path")));
};
let task = collect_files(workspace.read(cx).project().clone(), arguments, cx);
cx.foreground_executor().spawn(async move {
let output = task.await?;
Ok(SlashCommandOutput {
text: output.completion_text,
sections: output
.files
.into_iter()
.map(|file| {
build_entry_output_section(
file.range_in_text,
Some(&file.path),
file.entry_type == EntryType::Directory,
None,
)
})
.collect(),
run_commands_in_text: true,
})
})
collect_files(workspace.read(cx).project().clone(), arguments, cx)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum EntryType {
File,
Directory,
}
#[derive(Clone, PartialEq, Debug)]
struct FileCommandOutput {
completion_text: String,
files: Vec<OutputFile>,
}
#[derive(Clone, PartialEq, Debug)]
struct OutputFile {
range_in_text: Range<usize>,
path: PathBuf,
entry_type: EntryType,
}
fn collect_files(
project: Model<Project>,
glob_inputs: &[String],
cx: &mut AppContext,
) -> Task<Result<FileCommandOutput>> {
) -> Task<Result<SlashCommandOutput>> {
let Ok(matchers) = glob_inputs
.into_iter()
.map(|glob_input| {
@@ -254,8 +218,7 @@ fn collect_files(
.collect::<Vec<_>>();
cx.spawn(|mut cx| async move {
let mut text = String::new();
let mut ranges = Vec::new();
let mut output = SlashCommandOutput::default();
for snapshot in snapshots {
let worktree_id = snapshot.id();
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
@@ -279,11 +242,12 @@ fn collect_files(
break;
}
let (_, entry_name, start) = directory_stack.pop().unwrap();
ranges.push(OutputFile {
range_in_text: start..text.len().saturating_sub(1),
path: PathBuf::from(entry_name),
entry_type: EntryType::Directory,
});
output.sections.push(build_entry_output_section(
start..output.text.len().saturating_sub(1),
Some(&PathBuf::from(entry_name)),
true,
None,
));
}
let filename = entry
@@ -315,21 +279,23 @@ fn collect_files(
continue;
}
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
let entry_start = text.len();
let entry_start = output.text.len();
if prefix_paths.is_empty() {
if is_top_level_directory {
text.push_str(&path_including_worktree_name.to_string_lossy());
output
.text
.push_str(&path_including_worktree_name.to_string_lossy());
is_top_level_directory = false;
} else {
text.push_str(&filename);
output.text.push_str(&filename);
}
directory_stack.push((entry.path.clone(), filename, entry_start));
} else {
let entry_name = format!("{}/{}", prefix_paths, &filename);
text.push_str(&entry_name);
output.text.push_str(&entry_name);
directory_stack.push((entry.path.clone(), entry_name, entry_start));
}
text.push('\n');
output.text.push('\n');
} else if entry.is_file() {
let Some(open_buffer_task) = project_handle
.update(&mut cx, |project, cx| {
@@ -340,28 +306,13 @@ fn collect_files(
continue;
};
if let Some(buffer) = open_buffer_task.await.log_err() {
let buffer_snapshot =
cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
let prev_len = text.len();
collect_file_content(
&mut text,
&buffer_snapshot,
path_including_worktree_name.to_string_lossy().to_string(),
);
text.push('\n');
if !write_single_file_diagnostics(
&mut text,
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
append_buffer_to_output(
&snapshot,
Some(&path_including_worktree_name),
&buffer_snapshot,
) {
text.pop();
}
ranges.push(OutputFile {
range_in_text: prev_len..text.len(),
path: path_including_worktree_name,
entry_type: EntryType::File,
});
text.push('\n');
&mut output,
)
.log_err();
}
}
}
@@ -371,43 +322,30 @@ fn collect_files(
let mut root_path = PathBuf::new();
root_path.push(snapshot.root_name());
root_path.push(&dir);
ranges.push(OutputFile {
range_in_text: start..text.len(),
path: root_path,
entry_type: EntryType::Directory,
});
output.sections.push(build_entry_output_section(
start..output.text.len(),
Some(&root_path),
true,
None,
));
} else {
ranges.push(OutputFile {
range_in_text: start..text.len(),
path: PathBuf::from(entry.as_str()),
entry_type: EntryType::Directory,
});
output.sections.push(build_entry_output_section(
start..output.text.len(),
Some(&PathBuf::from(entry.as_str())),
true,
None,
));
}
}
}
Ok(FileCommandOutput {
completion_text: text,
files: ranges,
})
Ok(output)
})
}
fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
let mut content = snapshot.text();
LineEnding::normalize(&mut content);
buffer.reserve(filename.len() + content.len() + 9);
buffer.push_str(&codeblock_fence_for_path(
Some(&PathBuf::from(filename)),
None,
));
buffer.push_str(&content);
if !buffer.ends_with('\n') {
buffer.push('\n');
}
buffer.push_str("```");
}
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();
@@ -422,13 +360,18 @@ 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');
text
}
#[derive(Serialize, Deserialize)]
pub struct FileCommandMetadata {
pub path: String,
}
pub fn build_entry_output_section(
range: Range<usize>,
path: Option<&Path>,
@@ -454,6 +397,16 @@ pub fn build_entry_output_section(
range,
icon,
label: label.into(),
metadata: if is_directory {
None
} else {
path.and_then(|path| {
serde_json::to_value(FileCommandMetadata {
path: path.to_string_lossy().to_string(),
})
.ok()
})
},
}
}
@@ -539,6 +492,36 @@ mod custom_path_matcher {
}
}
pub fn append_buffer_to_output(
buffer: &BufferSnapshot,
path: Option<&Path>,
output: &mut SlashCommandOutput,
) -> Result<()> {
let prev_len = output.text.len();
let mut content = buffer.text();
LineEnding::normalize(&mut content);
output.text.push_str(&codeblock_fence_for_path(path, None));
output.text.push_str(&content);
if !output.text.ends_with('\n') {
output.text.push('\n');
}
output.text.push_str("```");
output.text.push('\n');
let section_ix = output.sections.len();
collect_buffer_diagnostics(output, buffer, false);
output.sections.insert(
section_ix,
build_entry_output_section(prev_len..output.text.len(), path, false, None),
);
output.text.push('\n');
Ok(())
}
#[cfg(test)]
mod test {
use fs::FakeFs;
@@ -591,9 +574,9 @@ mod test {
.await
.unwrap();
assert!(result_1.completion_text.starts_with("root/dir"));
assert!(result_1.text.starts_with("root/dir"));
// 4 files + 2 directories
assert_eq!(6, result_1.files.len());
assert_eq!(result_1.sections.len(), 6);
let result_2 = cx
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
@@ -607,9 +590,9 @@ mod test {
.await
.unwrap();
assert!(result.completion_text.starts_with("root/dir"));
assert!(result.text.starts_with("root/dir"));
// 5 files + 2 directories
assert_eq!(7, result.files.len());
assert_eq!(result.sections.len(), 7);
// Ensure that the project lasts until after the last await
drop(project);
@@ -654,36 +637,27 @@ mod test {
.unwrap();
// Sanity check
assert!(result.completion_text.starts_with("zed/assets/themes\n"));
assert_eq!(7, result.files.len());
assert!(result.text.starts_with("zed/assets/themes\n"));
assert_eq!(result.sections.len(), 7);
// Ensure that full file paths are included in the real output
assert!(result
.completion_text
.contains("zed/assets/themes/andromeda/LICENSE"));
assert!(result
.completion_text
.contains("zed/assets/themes/ayu/LICENSE"));
assert!(result
.completion_text
.contains("zed/assets/themes/summercamp/LICENSE"));
assert!(result.text.contains("zed/assets/themes/andromeda/LICENSE"));
assert!(result.text.contains("zed/assets/themes/ayu/LICENSE"));
assert!(result.text.contains("zed/assets/themes/summercamp/LICENSE"));
assert_eq!("summercamp", result.files[5].path.to_string_lossy());
assert_eq!(result.sections[5].label, "summercamp");
// Ensure that things are in descending order, with properly relativized paths
assert_eq!(
"zed/assets/themes/andromeda/LICENSE",
result.files[0].path.to_string_lossy()
result.sections[0].label,
"zed/assets/themes/andromeda/LICENSE"
);
assert_eq!("andromeda", result.files[1].path.to_string_lossy());
assert_eq!(result.sections[1].label, "andromeda");
assert_eq!(result.sections[2].label, "zed/assets/themes/ayu/LICENSE");
assert_eq!(result.sections[3].label, "ayu");
assert_eq!(
"zed/assets/themes/ayu/LICENSE",
result.files[2].path.to_string_lossy()
);
assert_eq!("ayu", result.files[3].path.to_string_lossy());
assert_eq!(
"zed/assets/themes/summercamp/LICENSE",
result.files[4].path.to_string_lossy()
result.sections[4].label,
"zed/assets/themes/summercamp/LICENSE"
);
// Ensure that the project lasts until after the last await
@@ -723,27 +697,24 @@ mod test {
.await
.unwrap();
assert!(result.completion_text.starts_with("zed/assets/themes\n"));
assert!(result.text.starts_with("zed/assets/themes\n"));
assert_eq!(result.sections[0].label, "zed/assets/themes/LICENSE");
assert_eq!(
"zed/assets/themes/LICENSE",
result.files[0].path.to_string_lossy()
result.sections[1].label,
"zed/assets/themes/summercamp/LICENSE"
);
assert_eq!(
"zed/assets/themes/summercamp/LICENSE",
result.files[1].path.to_string_lossy()
result.sections[2].label,
"zed/assets/themes/summercamp/subdir/LICENSE"
);
assert_eq!(
"zed/assets/themes/summercamp/subdir/LICENSE",
result.files[2].path.to_string_lossy()
result.sections[3].label,
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE"
);
assert_eq!(
"zed/assets/themes/summercamp/subdir/subsubdir/LICENSE",
result.files[3].path.to_string_lossy()
);
assert_eq!("subsubdir", result.files[4].path.to_string_lossy());
assert_eq!("subdir", result.files[5].path.to_string_lossy());
assert_eq!("summercamp", result.files[6].path.to_string_lossy());
assert_eq!("zed/assets/themes", result.files[7].path.to_string_lossy());
assert_eq!(result.sections[4].label, "subsubdir");
assert_eq!(result.sections[5].label, "subdir");
assert_eq!(result.sections[6].label, "summercamp");
assert_eq!(result.sections[7].label, "zed/assets/themes");
// Ensure that the project lasts until after the last await
drop(project);

View File

@@ -7,7 +7,7 @@ use assistant_slash_command::{
};
use chrono::Local;
use gpui::{Task, WeakView};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -43,6 +43,8 @@ impl SlashCommand for NowSlashCommand {
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
@@ -57,6 +59,7 @@ impl SlashCommand for NowSlashCommand {
range,
icon: IconName::CountdownTimer,
label: now.to_rfc2822().into(),
metadata: None,
}],
run_commands_in_text: false,
}))

View File

@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use std::{
fmt::Write,
@@ -118,6 +118,8 @@ impl SlashCommand for ProjectSlashCommand {
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -140,6 +142,7 @@ impl SlashCommand for ProjectSlashCommand {
range,
icon: IconName::FileTree,
label: "Project".into(),
metadata: None,
}],
run_commands_in_text: false,
})

View File

@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use gpui::{Task, WeakView};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use ui::prelude::*;
use workspace::Workspace;
@@ -56,6 +56,8 @@ impl SlashCommand for PromptSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -95,6 +97,7 @@ impl SlashCommand for PromptSlashCommand {
range,
icon: IconName::Library,
label: title,
metadata: None,
}],
run_commands_in_text: true,
})

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;
@@ -60,6 +58,8 @@ impl SlashCommand for SearchSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -105,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();
@@ -159,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),
));
}
@@ -168,6 +144,7 @@ impl SlashCommand for SearchSlashCommand {
range: 0..text.len(),
icon: IconName::MagnifyingGlass,
label: query,
metadata: None,
});
SlashCommandOutput {

View File

@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use editor::Editor;
use gpui::{Task, WeakView};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool};
use ui::{IconName, WindowContext};
@@ -41,6 +41,8 @@ impl SlashCommand for OutlineSlashCommand {
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -77,6 +79,7 @@ impl SlashCommand for OutlineSlashCommand {
range: 0..outline_text.len(),
icon: IconName::ListTree,
label: path.to_string_lossy().to_string().into(),
metadata: None,
}],
text: outline_text,
run_commands_in_text: false,

View File

@@ -1,21 +1,17 @@
use super::{
diagnostics_command::write_single_file_diagnostics,
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
use anyhow::{Context, Result};
use assistant_slash_command::ArgumentCompletion;
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
use gpui::{Entity, Task, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
use std::{
fmt::Write,
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{ActiveTheme, WindowContext};
use util::ResultExt;
use workspace::Workspace;
pub(crate) struct TabSlashCommand;
@@ -131,6 +127,8 @@ impl SlashCommand for TabSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -144,40 +142,11 @@ impl SlashCommand for TabSlashCommand {
);
cx.background_executor().spawn(async move {
let mut sections = Vec::new();
let mut text = String::new();
let mut has_diagnostics = false;
let mut output = SlashCommandOutput::default();
for (full_path, buffer, _) in tab_items_search.await? {
let section_start_ix = text.len();
text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
for chunk in buffer.as_rope().chunks() {
text.push_str(chunk);
}
if !text.ends_with('\n') {
text.push('\n');
}
writeln!(text, "```").unwrap();
if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
has_diagnostics = true;
}
if !text.ends_with('\n') {
text.push('\n');
}
let section_end_ix = text.len() - 1;
sections.push(build_entry_output_section(
section_start_ix..section_end_ix,
full_path.as_deref(),
false,
None,
));
append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
}
Ok(SlashCommandOutput {
text,
sections,
run_commands_in_text: has_diagnostics,
})
Ok(output)
})
}
}

View File

@@ -6,7 +6,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use gpui::{AppContext, Task, View, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::prelude::*;
use workspace::{dock::Panel, Workspace};
@@ -57,6 +57,8 @@ impl SlashCommand for TerminalSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -91,6 +93,7 @@ impl SlashCommand for TerminalSlashCommand {
range,
icon: IconName::Terminal,
label: "Terminal".into(),
metadata: None,
}],
run_commands_in_text: false,
}))

View File

@@ -8,7 +8,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use gpui::{Task, WeakView};
use language::LspAdapterDelegate;
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -53,6 +53,8 @@ impl SlashCommand for WorkflowSlashCommand {
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -68,6 +70,7 @@ impl SlashCommand for WorkflowSlashCommand {
range,
icon: IconName::Route,
label: "Workflow".into(),
metadata: None,
}],
run_commands_in_text: false,
})

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,4 +19,5 @@ gpui.workspace = true
language.workspace = true
parking_lot.workspace = true
serde.workspace = true
serde_json.workspace = true
workspace.workspace = true

View File

@@ -2,7 +2,7 @@ mod slash_command_registry;
use anyhow::Result;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
use serde::{Deserialize, Serialize};
pub use slash_command_registry::*;
use std::{
@@ -77,6 +77,8 @@ pub trait SlashCommand: 'static + Send + Sync {
fn run(
self: Arc<Self>,
arguments: &[String],
context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
// TODO: We're just using the `LspAdapterDelegate` here because that is
// what the extension API is already expecting.
@@ -94,7 +96,7 @@ pub type RenderFoldPlaceholder = Arc<
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
>;
#[derive(Debug, Default)]
#[derive(Debug, Default, PartialEq)]
pub struct SlashCommandOutput {
pub text: String,
pub sections: Vec<SlashCommandOutputSection<usize>>,
@@ -106,4 +108,11 @@ pub struct SlashCommandOutputSection<T> {
pub range: Range<T>,
pub icon: IconName,
pub label: SharedString,
pub metadata: Option<serde_json::Value>,
}
impl SlashCommandOutputSection<language::Anchor> {
pub fn is_valid(&self, buffer: &language::TextBuffer) -> bool {
self.range.start.is_valid(buffer) && !self.range.to_offset(buffer).is_empty()
}
}

View File

@@ -18,5 +18,5 @@ collections.workspace = true
derive_more.workspace = true
gpui.workspace = true
parking_lot.workspace = true
rodio = { version = "0.17.1", default-features = false, features = ["wav"] }
rodio = { version = "0.19.0", default-features = false, features = ["wav"] }
util.workspace = true

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

@@ -1200,6 +1200,7 @@ impl Room {
room_id: self.id(),
worktrees: vec![],
dev_server_project_id: Some(dev_server_project_id.0),
is_ssh_project: false,
})
} else {
if let Some(project_id) = project.read(cx).remote_id() {
@@ -1210,6 +1211,7 @@ impl Room {
room_id: self.id(),
worktrees: project.read(cx).worktree_metadata_protos(cx),
dev_server_project_id: None,
is_ssh_project: project.read(cx).is_via_ssh(),
})
};

View File

@@ -5,8 +5,8 @@ use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
use language::proto::serialize_version;
use rpc::{
proto::{self, AnyProtoClient, PeerId},
TypedEnvelope,
proto::{self, PeerId},
AnyProtoClient, TypedEnvelope,
};
use std::{sync::Arc, time::Duration};
use text::BufferId;
@@ -171,11 +171,11 @@ impl ChannelBuffer {
fn on_buffer_update(
&mut self,
_: Model<language::Buffer>,
event: &language::Event,
event: &language::BufferEvent,
cx: &mut ModelContext<Self>,
) {
match event {
language::Event::Operation(operation) => {
language::BufferEvent::Operation(operation) => {
if *ZED_ALWAYS_ACTIVE {
if let language::Operation::UpdateSelections { selections, .. } = operation {
if selections.is_empty() {
@@ -191,7 +191,7 @@ impl ChannelBuffer {
})
.log_err();
}
language::Event::Edited => {
language::BufferEvent::Edited => {
cx.emit(ChannelBufferEvent::BufferEdited);
}
_ => {}

View File

@@ -11,7 +11,7 @@ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
};
use rand::prelude::*;
use rpc::proto::AnyProtoClient;
use rpc::AnyProtoClient;
use std::{
ops::{ControlFlow, Range},
sync::Arc,
@@ -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
@@ -34,7 +34,9 @@ parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
release_channel.workspace = true
rpc.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

@@ -22,7 +22,6 @@ use gpui::{actions, AppContext, AsyncAppContext, Global, Model, Task, WeakModel}
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use parking_lot::RwLock;
use postage::watch;
use proto::{AnyProtoClient, EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet};
use rand::prelude::*;
use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
@@ -241,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),
@@ -530,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 {
@@ -1146,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

@@ -154,11 +154,13 @@ spec:
secretKeyRef:
name: runpod
key: api_key
optional: true
- name: RUNPOD_API_SUMMARY_URL
valueFrom:
secretKeyRef:
name: runpod
key: summary
optional: true
- name: BLOB_STORE_ACCESS_KEY
valueFrom:
secretKeyRef:

View File

@@ -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

@@ -30,6 +30,7 @@ impl Database {
room_id: RoomId,
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
is_ssh_project: bool,
dev_server_project_id: Option<DevServerProjectId>,
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
self.room_transaction(room_id, |tx| async move {
@@ -121,12 +122,14 @@ impl Database {
.await?;
}
let replica_id = if is_ssh_project { 1 } else { 0 };
project_collaborator::ActiveModel {
project_id: ActiveValue::set(project.id),
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(ReplicaId(0)),
replica_id: ActiveValue::set(ReplicaId(replica_id)),
is_host: ActiveValue::set(true),
..Default::default()
}

View File

@@ -540,18 +540,18 @@ async fn test_project_count(db: &Arc<Database>) {
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], None)
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], None)
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
// Projects shared by admins aren't counted.
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], None)
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, None)
.await
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);

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;
@@ -1996,6 +1997,7 @@ async fn share_project(
RoomId::from_proto(request.room_id),
session.connection_id,
&request.worktrees,
request.is_ssh_project,
request
.dev_server_project_id
.map(DevServerProjectId::from_proto),

View File

@@ -63,6 +63,6 @@ fn rust_lang() -> Arc<Language> {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
))
}

View File

@@ -284,7 +284,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -552,7 +552,7 @@ async fn test_collaborating_with_code_actions(
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
.register_fake_lsp("Rust", FakeLspAdapter::default());
client_a
.fs()
@@ -757,7 +757,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
// Set up a fake language server.
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -982,7 +982,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
cx_b.update(editor::init);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
name: "the-language-server",
@@ -1268,7 +1268,7 @@ async fn test_on_input_format_from_host_to_guest(
let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -1388,7 +1388,7 @@ async fn test_on_input_format_from_guest_to_host(
let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -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,
})
});
});
@@ -1545,7 +1547,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -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,
})
});
});
@@ -1807,7 +1811,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
client_a.language_registry().add(rust_lang());
client_b.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {

View File

@@ -2273,7 +2273,7 @@ async fn test_propagate_saves_and_fs_changes(
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let javascript = Arc::new(Language::new(
LanguageConfig {
@@ -2284,7 +2284,7 @@ async fn test_propagate_saves_and_fs_changes(
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
for client in [&client_a, &client_b, &client_c] {
client.language_registry().add(rust.clone());
@@ -3855,11 +3855,11 @@ async fn test_collaborating_with_diagnostics(
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
.register_fake_lsp("Rust", Default::default());
// Share a project as client A
client_a
@@ -4126,7 +4126,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
.await;
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
@@ -4349,7 +4349,7 @@ async fn test_formatting_buffer(
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", FakeLspAdapter::default());
.register_fake_lsp("Rust", FakeLspAdapter::default());
// Here we insert a fake tree with a directory that exists on disk. This is needed
// because later we'll invoke a command, which requires passing a working directory
@@ -4458,9 +4458,9 @@ async fn test_prettier_formatting_buffer(
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"TypeScript",
FakeLspAdapter {
prettier_plugins: vec![test_plugin],
@@ -4576,7 +4576,7 @@ async fn test_definition(
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
.register_fake_lsp("Rust", Default::default());
client_a.language_registry().add(rust_lang());
client_a
@@ -4712,7 +4712,7 @@ async fn test_references(
let active_call_a = cx_a.read(ActiveCall::global);
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
name: "my-fake-lsp-adapter",
@@ -4983,7 +4983,7 @@ async fn test_document_highlights(
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
.register_fake_lsp("Rust", Default::default());
client_a.language_registry().add(rust_lang());
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
@@ -5079,28 +5079,30 @@ async fn test_lsp_hover(
client_a.language_registry().add(rust_lang());
let language_server_names = ["rust-analyzer", "CrabLang-ls"];
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
name: "rust-analyzer",
capabilities: lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
let mut language_servers = [
client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
name: "rust-analyzer",
capabilities: lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
},
..FakeLspAdapter::default()
},
);
let _other_server = client_a.language_registry().register_fake_lsp_adapter(
"Rust",
FakeLspAdapter {
name: "CrabLang-ls",
capabilities: lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
),
client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
name: "CrabLang-ls",
capabilities: lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
..FakeLspAdapter::default()
},
..FakeLspAdapter::default()
},
);
),
];
let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
let project_id = active_call_a
@@ -5115,7 +5117,7 @@ async fn test_lsp_hover(
let mut servers_with_hover_requests = HashMap::default();
for i in 0..language_server_names.len() {
let new_server = fake_language_servers.next().await.unwrap_or_else(|| {
let new_server = language_servers[i].next().await.unwrap_or_else(|| {
panic!(
"Failed to get language server #{i} with name {}",
&language_server_names[i]
@@ -5260,7 +5262,7 @@ async fn test_project_symbols(
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
.register_fake_lsp("Rust", Default::default());
client_a
.fs()
@@ -5362,7 +5364,7 @@ async fn test_open_buffer_while_getting_definition_pointing_to_it(
client_a.language_registry().add(rust_lang());
let mut fake_language_servers = client_a
.language_registry()
.register_fake_lsp_adapter("Rust", Default::default());
.register_fake_lsp("Rust", Default::default());
client_a
.fs()

View File

@@ -1047,7 +1047,7 @@ impl RandomizedTest for ProjectCollaborationTest {
},
None,
)));
client.language_registry().register_fake_lsp_adapter(
client.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
name: "the-fake-language-server",

View File

@@ -228,10 +228,10 @@ impl MessageEditor {
fn on_buffer_event(
&mut self,
buffer: Model<Buffer>,
event: &language::Event,
event: &language::BufferEvent,
cx: &mut ViewContext<Self>,
) {
if let language::Event::Reparsed | language::Event::Edited = event {
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
let buffer = buffer.read(cx).snapshot();
self.mentions_task = Some(cx.spawn(|this, cx| async move {
cx.background_executor()

View File

@@ -14,6 +14,7 @@ path = "src/context_servers.rs"
[dependencies]
anyhow.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
futures.workspace = true
gpui.workspace = true
log.workspace = true

View File

@@ -12,6 +12,9 @@ pub use registry::*;
actions!(context_servers, [Restart]);
/// The namespace for the context servers actions.
const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
pub fn init(cx: &mut AppContext) {
log::info!("initializing context server client");
manager::init(cx);

View File

@@ -15,6 +15,7 @@
//! and react to changes in settings.
use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
use log;
use parking_lot::RwLock;
@@ -24,6 +25,7 @@ use settings::{Settings, SettingsSources, SettingsStore};
use std::path::Path;
use std::sync::Arc;
use crate::CONTEXT_SERVERS_NAMESPACE;
use crate::{
client::{self, Client},
types,
@@ -148,26 +150,28 @@ impl ContextServerManager {
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
let server_id = config.id.clone();
let server_id2 = config.id.clone();
if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) {
return Task::ready(Ok(()));
}
let task = cx.spawn(|this, mut cx| async move {
let server = Arc::new(ContextServer::new(config));
server.start(&cx).await?;
this.update(&mut cx, |this, cx| {
this.servers.insert(server_id.clone(), server);
this.pending_servers.remove(&server_id);
cx.emit(Event::ServerStarted {
server_id: server_id.clone(),
});
})?;
Ok(())
});
let task = {
let server_id = server_id.clone();
cx.spawn(|this, mut cx| async move {
let server = Arc::new(ContextServer::new(config));
server.start(&cx).await?;
this.update(&mut cx, |this, cx| {
this.servers.insert(server_id.clone(), server);
this.pending_servers.remove(&server_id);
cx.emit(Event::ServerStarted {
server_id: server_id.clone(),
});
})?;
Ok(())
})
};
self.pending_servers.insert(server_id2);
self.pending_servers.insert(server_id);
task
}
@@ -243,15 +247,20 @@ impl GlobalContextServerManager {
pub fn init(cx: &mut AppContext) {
ContextServerSettings::register(cx);
GlobalContextServerManager::register(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
});
cx.observe_global::<SettingsStore>(|cx| {
let manager = ContextServerManager::global(cx);
cx.update_model(&manager, |manager, cx| {
let settings = ContextServerSettings::get_global(cx);
let current_servers: HashMap<String, ServerConfig> = manager
let current_servers = manager
.servers()
.into_iter()
.map(|server| (server.id.clone(), server.config.clone()))
.collect();
.collect::<HashMap<_, _>>();
let new_servers = settings
.servers
@@ -279,6 +288,15 @@ pub fn init(cx: &mut AppContext) {
for id in servers_to_remove {
manager.remove_server(&id, cx).detach_and_log_err(cx);
}
let has_any_context_servers = !manager.servers().is_empty();
CommandPaletteFilter::update_global(cx, |filter, _cx| {
if has_any_context_servers {
filter.show_namespace(CONTEXT_SERVERS_NAMESPACE);
} else {
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
}
});
})
})
.detach();

View File

@@ -691,17 +691,17 @@ impl Copilot {
fn handle_buffer_event(
&mut self,
buffer: Model<Buffer>,
event: &language::Event,
event: &language::BufferEvent,
cx: &mut ModelContext<Self>,
) -> Result<()> {
if let Ok(server) = self.server.as_running() {
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.entity_id())
{
match event {
language::Event::Edited => {
language::BufferEvent::Edited => {
drop(registered_buffer.report_changes(&buffer, cx));
}
language::Event::Saved => {
language::BufferEvent::Saved => {
server
.lsp
.notify::<lsp::notification::DidSaveTextDocument>(
@@ -713,7 +713,8 @@ impl Copilot {
},
)?;
}
language::Event::FileHandleChanged | language::Event::LanguageChanged => {
language::BufferEvent::FileHandleChanged
| language::BufferEvent::LanguageChanged => {
let new_language_id = id_for_language(buffer.read(cx).language());
let new_uri = uri_for_buffer(&buffer, cx);
if new_uri != registered_buffer.uri

View File

@@ -1,14 +1,14 @@
use crate::{Completion, Copilot};
use anyhow::Result;
use client::telemetry::Telemetry;
use editor::{Direction, InlineCompletionProvider};
use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
Buffer, OffsetRangeExt, ToOffset,
};
use settings::Settings;
use std::{ops::Range, path::Path, sync::Arc, time::Duration};
use std::{path::Path, sync::Arc, time::Duration};
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
@@ -237,7 +237,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
buffer: &Model<Buffer>,
cursor_position: language::Anchor,
cx: &'a AppContext,
) -> Option<(&'a str, Option<Range<language::Anchor>>)> {
) -> Option<CompletionProposal> {
let buffer_id = buffer.entity_id();
let buffer = buffer.read(cx);
let completion = self.active_completion()?;
@@ -267,7 +267,14 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
if completion_text.trim().is_empty() {
None
} else {
Some((completion_text, None))
Some(CompletionProposal {
inlays: vec![InlayProposal::Suggestion(
cursor_position.bias_right(buffer),
completion_text.into(),
)],
text: completion_text.into(),
delete_range: None,
})
}
} else {
None

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

@@ -645,37 +645,42 @@ impl Item for ProjectDiagnosticsEditor {
}
fn tab_content(&self, params: TabContentParams, _: &WindowContext) -> AnyElement {
if self.summary.error_count == 0 && self.summary.warning_count == 0 {
Label::new("No problems")
.color(params.text_color())
.into_any_element()
} else {
h_flex()
.gap_1()
.when(self.summary.error_count > 0, |then| {
h_flex()
.gap_1()
.when(
self.summary.error_count == 0 && self.summary.warning_count == 0,
|then| {
then.child(
h_flex()
.gap_1()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(
Label::new(self.summary.error_count.to_string())
.color(params.text_color()),
),
.child(Icon::new(IconName::Check).color(Color::Success))
.child(Label::new("No problems").color(params.text_color())),
)
})
.when(self.summary.warning_count > 0, |then| {
then.child(
h_flex()
.gap_1()
.child(Icon::new(IconName::Warning).color(Color::Warning))
.child(
Label::new(self.summary.warning_count.to_string())
.color(params.text_color()),
),
)
})
.into_any_element()
}
},
)
.when(self.summary.error_count > 0, |then| {
then.child(
h_flex()
.gap_1()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(
Label::new(self.summary.error_count.to_string())
.color(params.text_color()),
),
)
})
.when(self.summary.warning_count > 0, |then| {
then.child(
h_flex()
.gap_1()
.child(Icon::new(IconName::Warning).color(Color::Warning))
.child(
Label::new(self.summary.warning_count.to_string())
.color(params.text_color()),
),
)
})
.into_any_element()
}
fn telemetry_event_text(&self) -> Option<&'static str> {

View File

@@ -61,6 +61,7 @@ schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
similar.workspace = true
smallvec.workspace = true
smol.workspace = true
snippet.workspace = true

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,
@@ -289,17 +289,20 @@ gpui::actions!(
ReverseLines,
RevertFile,
RevertSelectedHunks,
Rewrap,
ScrollCursorBottom,
ScrollCursorCenter,
ScrollCursorTop,
ScrollCursorCenterTopBottom,
ScrollCursorTop,
SelectAll,
SelectAllMatches,
SelectDown,
SelectLargerSyntaxNode,
SelectEnclosingSymbol,
SelectLargerSyntaxNode,
SelectLeft,
SelectLine,
SelectPageDown,
SelectPageUp,
SelectRight,
SelectSmallerSyntaxNode,
SelectToBeginning,
@@ -311,8 +314,6 @@ gpui::actions!(
SelectToPreviousWordStart,
SelectToStartOfParagraph,
SelectUp,
SelectPageDown,
SelectPageUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShowSignatureHelp,
@@ -326,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();
@@ -1645,7 +1646,7 @@ pub mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_highlights_query(
r#"
@@ -1750,7 +1751,7 @@ pub mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_highlights_query(
r#"
@@ -1833,7 +1834,7 @@ pub mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_highlights_query(
r#"

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

@@ -98,6 +98,7 @@ use language::{
};
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
use similar::{ChangeTag, TextDiff};
use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
@@ -411,8 +412,37 @@ 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)]
struct CompletionState {
// render_inlay_ids represents the inlay hints that are inserted
// for rendering the inline completions. They may be discontinuous
// in the event that the completion provider returns some intersection
// with the existing content.
render_inlay_ids: Vec<InlayId>,
// text is the resulting rope that is inserted when the user accepts a completion.
text: Rope,
// position is the position of the cursor when the completion was triggered.
position: multi_buffer::Anchor,
// delete_range is the range of text that this completion state covers.
// if the completion is accepted, this range should be deleted.
delete_range: Option<Range<multi_buffer::Anchor>>,
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
struct EditorActionId(usize);
@@ -556,7 +586,7 @@ pub struct Editor {
gutter_hovered: bool,
hovered_link_state: Option<HoveredLinkState>,
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
active_inline_completion: Option<(Inlay, Option<Range<Anchor>>)>,
active_inline_completion: Option<CompletionState>,
// enable_inline_completions is a switch that Vim can use to disable
// inline completions based on its mode.
enable_inline_completions: bool,
@@ -1887,7 +1917,9 @@ impl Editor {
linked_editing_range_task: Default::default(),
pending_rename: Default::default(),
searchable: true,
cursor_shape: Default::default(),
cursor_shape: EditorSettings::get_global(cx)
.cursor_shape
.unwrap_or_default(),
current_line_highlight: None,
autoindent_mode: Some(AutoindentMode::EachLine),
collapse_matches: false,
@@ -5068,7 +5100,7 @@ impl Editor {
_: &AcceptInlineCompletion,
cx: &mut ViewContext<Self>,
) {
let Some((completion, delete_range)) = self.take_active_inline_completion(cx) else {
let Some(completion) = self.take_active_inline_completion(cx) else {
return;
};
if let Some(provider) = self.inline_completion_provider() {
@@ -5080,7 +5112,7 @@ impl Editor {
text: completion.text.to_string().into(),
});
if let Some(range) = delete_range {
if let Some(range) = completion.delete_range {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&completion.text.to_string(), None, cx);
@@ -5094,7 +5126,7 @@ impl Editor {
cx: &mut ViewContext<Self>,
) {
if self.selections.count() == 1 && self.has_active_inline_completion(cx) {
if let Some((completion, delete_range)) = self.take_active_inline_completion(cx) {
if let Some(completion) = self.take_active_inline_completion(cx) {
let mut partial_completion = completion
.text
.chars()
@@ -5115,7 +5147,7 @@ impl Editor {
text: partial_completion.clone().into(),
});
if let Some(range) = delete_range {
if let Some(range) = completion.delete_range {
self.change_selections(None, cx, |s| s.select_ranges([range]))
}
self.insert_with_autoindent_mode(&partial_completion, None, cx);
@@ -5141,7 +5173,7 @@ impl Editor {
pub fn has_active_inline_completion(&self, cx: &AppContext) -> bool {
if let Some(completion) = self.active_inline_completion.as_ref() {
let buffer = self.buffer.read(cx).read(cx);
completion.0.position.is_valid(&buffer)
completion.position.is_valid(&buffer)
} else {
false
}
@@ -5150,14 +5182,15 @@ impl Editor {
fn take_active_inline_completion(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<(Inlay, Option<Range<Anchor>>)> {
) -> Option<CompletionState> {
let completion = self.active_inline_completion.take()?;
let render_inlay_ids = completion.render_inlay_ids.clone();
self.display_map.update(cx, |map, cx| {
map.splice_inlays(vec![completion.0.id], Default::default(), cx);
map.splice_inlays(render_inlay_ids, Default::default(), cx);
});
let buffer = self.buffer.read(cx).read(cx);
if completion.0.position.is_valid(&buffer) {
if completion.position.is_valid(&buffer) {
Some(completion)
} else {
None
@@ -5178,31 +5211,50 @@ impl Editor {
if let Some((buffer, cursor_buffer_position)) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)
{
if let Some((text, text_anchor_range)) =
if let Some(proposal) =
provider.active_completion_text(&buffer, cursor_buffer_position, cx)
{
let text = Rope::from(text);
let mut to_remove = Vec::new();
if let Some(completion) = self.active_inline_completion.take() {
to_remove.push(completion.0.id);
to_remove.extend(completion.render_inlay_ids.iter());
}
let completion_inlay =
Inlay::suggestion(post_inc(&mut self.next_inlay_id), cursor, text);
let to_add = proposal
.inlays
.iter()
.filter_map(|inlay| {
let snapshot = self.buffer.read(cx).snapshot(cx);
let id = post_inc(&mut self.next_inlay_id);
match inlay {
InlayProposal::Hint(position, hint) => {
let position =
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
Some(Inlay::hint(id, position, hint))
}
InlayProposal::Suggestion(position, text) => {
let position =
snapshot.anchor_in_excerpt(excerpt_id, *position)?;
Some(Inlay::suggestion(id, position, text.clone()))
}
}
})
.collect_vec();
let multibuffer_anchor_range = text_anchor_range.and_then(|range| {
let snapshot = self.buffer.read(cx).snapshot(cx);
Some(
snapshot.anchor_in_excerpt(excerpt_id, range.start)?
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?,
)
self.active_inline_completion = Some(CompletionState {
position: cursor,
text: proposal.text,
delete_range: proposal.delete_range.and_then(|range| {
let snapshot = self.buffer.read(cx).snapshot(cx);
let start = snapshot.anchor_in_excerpt(excerpt_id, range.start);
let end = snapshot.anchor_in_excerpt(excerpt_id, range.end);
Some(start?..end?)
}),
render_inlay_ids: to_add.iter().map(|i| i.id).collect(),
});
self.active_inline_completion =
Some((completion_inlay.clone(), multibuffer_anchor_range));
self.display_map.update(cx, move |map, cx| {
map.splice_inlays(to_remove, vec![completion_inlay], cx)
});
self.display_map
.update(cx, move |map, cx| map.splice_inlays(to_remove, to_add, cx));
cx.notify();
return;
}
@@ -6659,6 +6711,161 @@ impl Editor {
});
}
pub fn rewrap(&mut self, _: &Rewrap, cx: &mut ViewContext<Self>) {
let buffer = self.buffer.read(cx).snapshot(cx);
let selections = self.selections.all::<Point>(cx);
let mut selections = selections.iter().peekable();
let mut edits = Vec::new();
let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
while let Some(selection) = selections.next() {
let mut start_row = selection.start.row;
let mut end_row = selection.end.row;
// Skip selections that overlap with a range that has already been rewrapped.
let selection_range = start_row..end_row;
if rewrapped_row_ranges
.iter()
.any(|range| range.overlaps(&selection_range))
{
continue;
}
let mut should_rewrap = false;
if let Some(language_scope) = buffer.language_scope_at(selection.head()) {
match language_scope.language_name().0.as_ref() {
"Markdown" | "Plain Text" => {
should_rewrap = true;
}
_ => {}
}
}
let row = selection.head().row;
let indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
let indent_end = Point::new(row, indent_size.len);
let mut line_prefix = indent_size.chars().collect::<String>();
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)
&& buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
{
start_row = prev_row;
} else {
break 'expand_upwards;
}
}
'expand_downwards: while end_row < buffer.max_point().row {
let next_row = end_row + 1;
if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
&& buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
{
end_row = next_row;
} else {
break 'expand_downwards;
}
}
}
if !should_rewrap {
continue;
}
let start = Point::new(start_row, 0);
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
let selection_text = buffer.text_for_range(start..end).collect::<String>();
let unwrapped_text = selection_text
.lines()
.map(|line| line.strip_prefix(&line_prefix).unwrap())
.join(" ");
let wrap_column = buffer
.settings_at(Point::new(start_row, 0), cx)
.preferred_line_length as usize;
let mut wrapped_text = String::new();
let mut current_line = line_prefix.clone();
for word in unwrapped_text.split_whitespace() {
if current_line.len() + word.len() >= wrap_column {
wrapped_text.push_str(&current_line);
wrapped_text.push('\n');
current_line.truncate(line_prefix.len());
}
if current_line.len() > line_prefix.len() {
current_line.push(' ');
}
current_line.push_str(word);
}
if !current_line.is_empty() {
wrapped_text.push_str(&current_line);
}
let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
let mut offset = start.to_offset(&buffer);
let mut moved_since_edit = true;
for change in diff.iter_all_changes() {
let value = change.value();
match change.tag() {
ChangeTag::Equal => {
offset += value.len();
moved_since_edit = true;
}
ChangeTag::Delete => {
let start = buffer.anchor_after(offset);
let end = buffer.anchor_before(offset + value.len());
if moved_since_edit {
edits.push((start..end, String::new()));
} else {
edits.last_mut().unwrap().0.end = end;
}
offset += value.len();
moved_since_edit = false;
}
ChangeTag::Insert => {
if moved_since_edit {
let anchor = buffer.anchor_after(offset);
edits.push((anchor..anchor, value.to_string()));
} else {
edits.last_mut().unwrap().1.push_str(value);
}
moved_since_edit = false;
}
}
}
rewrapped_row_ranges.push(start_row..=end_row);
}
self.buffer
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
let mut text = String::new();
let buffer = self.buffer.read(cx).snapshot(cx);
@@ -9840,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),
@@ -11628,6 +11834,9 @@ impl Editor {
cx,
);
let editor_settings = EditorSettings::get_global(cx);
if let Some(cursor_shape) = editor_settings.cursor_shape {
self.cursor_shape = cursor_shape;
}
self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
@@ -12795,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

@@ -1,4 +1,5 @@
use gpui::AppContext;
use language::CursorShape;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
@@ -6,6 +7,7 @@ use settings::{Settings, SettingsSources};
#[derive(Deserialize, Clone)]
pub struct EditorSettings {
pub cursor_blink: bool,
pub cursor_shape: Option<CursorShape>,
pub current_line_highlight: CurrentLineHighlight,
pub hover_popover_enabled: bool,
pub show_completions_on_input: bool,
@@ -177,6 +179,11 @@ pub struct EditorSettingsContent {
///
/// Default: true
pub cursor_blink: Option<bool>,
/// Cursor shape for the default editor.
/// Can be "bar", "block", "underscore", or "hollow".
///
/// Default: None
pub cursor_shape: Option<CursorShape>,
/// How to highlight the current line in the editor.
///
/// Default: all

View File

@@ -2322,7 +2322,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
let language = Arc::new(
Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
.unwrap(),
@@ -2370,7 +2370,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
let language = Arc::new(
Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
.unwrap(),
@@ -2524,7 +2524,7 @@ async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAp
let language = Arc::new(
Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
.unwrap(),
@@ -2585,7 +2585,7 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
let language = Arc::new(
Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(r#"(_ "{" "}" @end) @indent"#)
.unwrap(),
@@ -3979,6 +3979,278 @@ fn test_transpose(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_rewrap(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
{
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 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(
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 non-contiguous selections are treated separately.
{
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 different comment prefixes are supported.
{
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 rewrapping is ignored outside of comments in most languages.
{
let language = Arc::new(Language::new(
LanguageConfig {
line_comments: vec!["// ".into(), "/// ".into()],
..LanguageConfig::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
let unwrapped_text = indoc! {"
/// Adds two numbers.
/// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
fn add(a: u32, b: u32) -> u32 {
a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
}
"};
let wrapped_text = indoc! {"
/// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
/// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
fn add(a: u32, b: u32) -> u32 {
a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
}
"};
cx.set_state(unwrapped_text);
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
}
// Test that rewrapping works in Markdown and Plain Text languages.
{
let markdown_language = Arc::new(Language::new(
LanguageConfig {
name: "Markdown".into(),
..LanguageConfig::default()
},
None,
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
let unwrapped_text = indoc! {"
# Hello
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.
"};
let wrapped_text = indoc! {"
# Hello
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.ˇ
"};
cx.set_state(unwrapped_text);
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
let plaintext_language = Arc::new(Language::new(
LanguageConfig {
name: "Plain Text".into(),
..LanguageConfig::default()
},
None,
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_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.
"};
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.ˇ
"};
cx.set_state(unwrapped_text);
cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
cx.assert_editor_state(wrapped_text);
}
}
#[gpui::test]
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -4072,7 +4344,7 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
@@ -4783,7 +5055,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let text = r#"
@@ -4992,7 +5264,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(
r#"
@@ -5085,7 +5357,7 @@ async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.language_registry().add(language.clone());
@@ -5257,7 +5529,7 @@ async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestA
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.language_registry().add(language.clone());
@@ -5397,7 +5669,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
autoclose_before: "})]>".into(),
..Default::default()
},
Some(tree_sitter_typescript::language_tsx()),
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
));
cx.language_registry().add(html_language.clone());
@@ -5572,7 +5844,7 @@ async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
autoclose_before: "})]>".into(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_override_query("(string_literal) @string")
.unwrap(),
@@ -5677,7 +5949,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let text = r#"
@@ -5826,7 +6098,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
autoclose_before: "}".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let text = r#"
@@ -5953,7 +6225,7 @@ async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppC
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.language_registry().add(language.clone());
@@ -6023,7 +6295,7 @@ async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
@@ -6205,7 +6477,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -6361,7 +6633,7 @@ async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -6557,7 +6829,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -6698,7 +6970,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
},
..LanguageConfig::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
update_test_language_settings(cx, |settings| {
// Enable Prettier formatting for the same buffer, and ensure
@@ -6708,7 +6980,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
..PrettierSettings::default()
});
});
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -7033,7 +7305,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
);
let language = Arc::new(language);
@@ -7175,7 +7447,7 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
autoclose_before: "})]".to_string(),
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
);
let language = Arc::new(language);
@@ -7968,7 +8240,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
@@ -8089,7 +8361,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
line_comments: vec!["// ".into()],
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let mut cx = EditorTestContext::new(cx).await;
@@ -8242,7 +8514,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
line_comments: vec!["// ".into()],
..Default::default()
},
Some(tree_sitter_typescript::language_tsx()),
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
));
cx.language_registry().add(html_language.clone());
@@ -8650,7 +8922,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query("")
.unwrap(),
@@ -9486,9 +9758,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -9599,9 +9871,9 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
name: language_server_name,
@@ -9832,7 +10104,7 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
.collect(),
..Default::default()
},
Some(tree_sitter_typescript::language_tsx()),
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
)
.with_override_query("(jsx_self_closing_element) @element")
.unwrap(),
@@ -9935,7 +10207,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
update_test_language_settings(cx, |settings| {
settings.defaults.prettier = Some(PrettierSettings {
@@ -9945,7 +10217,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
});
let test_plugin = "test_plugin";
let _ = language_registry.register_fake_lsp_adapter(
let _ = language_registry.register_fake_lsp(
"TypeScript",
FakeLspAdapter {
prettier_plugins: vec![test_plugin],
@@ -13652,7 +13924,7 @@ pub(crate) fn rust_lang() -> Arc<Language> {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
))
}

View File

@@ -216,6 +216,7 @@ impl EditorElement {
register_action(view, cx, Editor::move_line_up);
register_action(view, cx, Editor::move_line_down);
register_action(view, cx, Editor::transpose);
register_action(view, cx, Editor::rewrap);
register_action(view, cx, Editor::cut);
register_action(view, cx, Editor::copy);
register_action(view, cx, Editor::paste);
@@ -2078,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;
}
@@ -121,12 +129,12 @@ impl GitBlame {
);
let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
language::Event::DirtyChanged => {
language::BufferEvent::DirtyChanged => {
if !buffer.read(cx).is_dirty() {
this.generate(cx);
}
}
language::Event::Edited => {
language::BufferEvent::Edited => {
this.regenerate_on_edit(cx);
}
_ => {}
@@ -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

@@ -78,7 +78,7 @@ mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_brackets_query(indoc! {r#"
("{" @open "}" @close)

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,
})
});
@@ -1575,9 +1578,9 @@ pub mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)));
let fake_servers = language_registry.register_fake_lsp_adapter(
let fake_servers = language_registry.register_fake_lsp(
name,
FakeLspAdapter {
name,
@@ -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,
})
});
@@ -2273,7 +2282,7 @@ pub mod tests {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -2551,6 +2560,7 @@ pub mod tests {
show_type_hints: true,
show_parameter_hints: true,
show_other_hints: true,
show_background: false,
})
});
@@ -2569,7 +2579,7 @@ pub mod tests {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let language = crate::editor_tests::rust_lang();
language_registry.add(language);
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -2902,6 +2912,7 @@ pub mod tests {
show_type_hints: false,
show_parameter_hints: false,
show_other_hints: false,
show_background: false,
})
});
@@ -2919,7 +2930,7 @@ pub mod tests {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -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,
})
});
@@ -3148,7 +3161,7 @@ pub mod tests {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
@@ -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();
@@ -3389,7 +3404,7 @@ pub mod tests {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(crate::editor_tests::rust_lang());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {

View File

@@ -2,6 +2,18 @@ use crate::Direction;
use gpui::{AppContext, Model, ModelContext};
use language::Buffer;
use std::ops::Range;
use text::{Anchor, Rope};
pub enum InlayProposal {
Hint(Anchor, project::InlayHint),
Suggestion(Anchor, Rope),
}
pub struct CompletionProposal {
pub inlays: Vec<InlayProposal>,
pub text: Rope,
pub delete_range: Option<Range<Anchor>>,
}
pub trait InlineCompletionProvider: 'static + Sized {
fn name() -> &'static str;
@@ -32,7 +44,7 @@ pub trait InlineCompletionProvider: 'static + Sized {
buffer: &Model<Buffer>,
cursor_position: language::Anchor,
cx: &'a AppContext,
) -> Option<(&'a str, Option<Range<language::Anchor>>)>;
) -> Option<CompletionProposal>;
}
pub trait InlineCompletionProviderHandle {
@@ -63,7 +75,7 @@ pub trait InlineCompletionProviderHandle {
buffer: &Model<Buffer>,
cursor_position: language::Anchor,
cx: &'a AppContext,
) -> Option<(&'a str, Option<Range<language::Anchor>>)>;
) -> Option<CompletionProposal>;
}
impl<T> InlineCompletionProviderHandle for Model<T>
@@ -118,7 +130,7 @@ where
buffer: &Model<Buffer>,
cursor_position: language::Anchor,
cx: &'a AppContext,
) -> Option<(&'a str, Option<Range<language::Anchor>>)> {
) -> Option<CompletionProposal> {
self.read(cx)
.active_completion_text(buffer, cursor_position, cx)
}

View File

@@ -1599,7 +1599,7 @@ mod tests {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
))
}

View File

@@ -1,6 +1,18 @@
use serde::Deserialize;
use ui::{px, Pixels};
#[derive(Debug)]
pub enum ScrollDirection {
Upwards,
Downwards,
}
impl ScrollDirection {
pub fn is_upwards(&self) -> bool {
matches!(self, ScrollDirection::Upwards)
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum ScrollAmount {
// Scroll N lines (positive is towards the end of the document)
@@ -15,7 +27,7 @@ impl ScrollAmount {
Self::Line(count) => *count,
Self::Page(count) => {
// for full pages subtract one to leave an anchor line
if count.abs() == 1.0 {
if self.is_full_page() {
visible_line_count -= 1.0
}
(visible_line_count * count).trunc()
@@ -29,4 +41,19 @@ impl ScrollAmount {
ScrollAmount::Page(x) => px(height.0 * x),
}
}
pub fn is_full_page(&self) -> bool {
match self {
ScrollAmount::Page(count) if count.abs() == 1.0 => true,
_ => false,
}
}
pub fn direction(&self) -> ScrollDirection {
match self {
Self::Line(amount) if amount.is_sign_positive() => ScrollDirection::Downwards,
Self::Page(amount) if amount.is_sign_positive() => ScrollDirection::Downwards,
_ => ScrollDirection::Upwards,
}
}
}

View File

@@ -57,7 +57,7 @@ impl EditorLspTestContext {
let project = Project::test(app_state.fs.clone(), [], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
let mut fake_servers = language_registry.register_fake_lsp(
language.name(),
FakeLspAdapter {
capabilities,
@@ -125,7 +125,7 @@ impl EditorLspTestContext {
},
..Default::default()
},
Some(tree_sitter_rust::language()),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_queries(LanguageQueries {
indents: Some(Cow::from(indoc! {r#"
@@ -184,7 +184,7 @@ impl EditorLspTestContext {
word_characters,
..Default::default()
},
Some(tree_sitter_typescript::language_typescript()),
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
)
.with_queries(LanguageQueries {
brackets: Some(Cow::from(indoc! {r#"

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