Compare commits

..

239 Commits

Author SHA1 Message Date
Richard Feldman
c6724d598e Render our code edit tool use differently 2024-11-20 11:42:09 -05:00
Richard Feldman
61a516e95f Replace assistant XML parsing with tool use 2024-11-20 11:03:37 -05:00
Richard Feldman
eb1754a091 Use tool call with Suggest Edits 2024-11-20 10:42:08 -05:00
Richard Feldman
2386595de5 wip 2024-11-20 01:01:36 -05:00
Richard Feldman
b36ed56443 wip 2024-11-20 00:49:22 -05:00
Richard Feldman
1b72c5402d Revert "Try cusotomizing RootSchema (decided against this approach)"
This reverts commit e82987db19286a18779f9e2e9d634d9cf98672ee.
2024-11-20 00:45:50 -05:00
Richard Feldman
a143fdc630 Try cusotomizing RootSchema (decided against this approach) 2024-11-20 00:43:08 -05:00
Richard Feldman
1e9666649e Add preliminary tool use for code edits 2024-11-20 00:39:59 -05:00
Conrad Irwin
3c57a4071c vim: Fix jj to exit insert mode (#20890)
Release Notes:

- (Preview only) fixed binding `jj` to exit insert mode
2024-11-19 20:00:11 -07:00
Conrad Irwin
ad6a07e574 Remove comments from discord release announcements (#20888)
Release Notes:

- N/A
2024-11-19 20:00:03 -07:00
Conrad Irwin
c2668bc953 Fix draft-releaase-notes (#20885)
Turns out this was broken because (a) we didn't have tags fetched,
and (b) because the gh-release action we use is buggy.

Release Notes:

- N/A
2024-11-19 19:08:33 -07:00
Conrad Irwin
705a06c3dd Send Country/OS/Version amplitude style (#20884)
Release Notes:

- N/A
2024-11-19 16:38:14 -07:00
Conrad Irwin
f77b6ab79c Fix space repeating in terminal (#20877)
This is broken because of the way we try to emulate macOS's
ApplePressAndHoldEnabled.

Release Notes:

- Fixed holding down space in the terminal (preview only)
2024-11-19 13:43:24 -07:00
Conrad Irwin
ea5131ce0a Country Code To Snowflake (#20875)
Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
2024-11-19 12:52:00 -07:00
Peter Tripp
1c2b3ad782 Add editor::SelectAllMatches to SublimeText base keymap (#20866)
`alt-f3` on Linux
`ctrl-cmd-g` on MacOS

Co-authored-by: Roman Seidelsohn <rseidelsohn@gmail.com>
2024-11-19 14:33:35 -05:00
Conrad Irwin
496dae968b Remove old CPU/Memory events (#20865)
Release Notes:

- Telemetry: stop reporting CPU/RAM on a timer
2024-11-19 12:25:16 -07:00
Piotr Osiewicz
5c6565a9e0 editor: Use completion filter_range for fuzzy matching (#20869)
Fixes regression from #13958

Closes #20868

Release Notes:

- N/A
2024-11-19 19:49:36 +01:00
Jaagup Averin
7853e32f80 python: Highlight attribute docstrings (#20763)
Adds more docstring highlights missing from #20486.
[PEP257](https://peps.python.org/pep-0257/) defines attribute docstrings
as
> String literals occurring immediately after a simple assignment at the
top level of a module, class, or __init__ method are called “attribute
docstrings”.

This PR adds `@string.doc` for such cases.
Before:

![Screenshot_20241116_162257](https://github.com/user-attachments/assets/6b471cff-717e-4755-9291-d596da927dc6)
After:

![Screenshot_20241116_162457](https://github.com/user-attachments/assets/96674157-9c86-45b6-8ce9-e433ca0ae8ea)

Release Notes:

- Added Python syntax highlighting for attribute docstrings.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-11-19 18:53:36 +01:00
Marshall Bowers
f5cbfa718e assistant: Fix evaluating slash commands in slash command output (like /default) (#20864)
This PR fixes an issue where slash commands in the output of other slash
commands were not being evaluated when configured to do so.

Closes https://github.com/zed-industries/zed/issues/20820.

Release Notes:

- Fixed slash commands from other slash commands (like `/default`) not
being evaluated (Preview only).
2024-11-19 11:20:30 -05:00
Conrad Irwin
6a2c712990 Use Instant not chrono for telemetry (#20756)
We occasionally see dates in the future appearing in our telemetry. One
hypothesis is that this is caused by a clock change while Zed is running
causing date math based on chrono to be incorrect.

Instant *should* be a more stable source of relative timestamps.

Release Notes:

- N/A
2024-11-19 08:23:12 -07:00
Egor Krugletsov
9454f0f1c7 clangd: Use Url::to_file_path() to get actual file path for header/source (#20856)
Using `Url::path()` seems fine on POSIX systems as it will leave forward
slash (given hostname is empty). On Windows it will result in error.

Release Notes:

- N/A
2024-11-19 15:49:21 +02:00
Julian de Ruiter
5b0c15d8c4 Add pytest-based test discovery and runnables for Python (#18824)
Closes  #12080, #18649.

Screenshot:

<img width="1499" alt="image"
src="https://github.com/user-attachments/assets/2644c2fc-19cf-4d2c-a992-5c56cb22deed">

Still in progress:

1. I'd like to add configuration options for selecting a Python test
runner (either pytest or unittest) so that users can explicitly choose
which runner they'd like to use for running their tests. This preference
has to be configured as unittest-style tests can also be run by pytest,
meaning we can't rely on auto-discovery to choose the desired test
runner.
2. I'd like to add venv auto-discovery similar to the feature currently
provided by the terminal using detect_venv.
3. Unit tests.

Unfortunately I'm struggling a bit with how to add settings in the
appropriate location (e.g. Python language settings). Can anyone provide
me with some pointers and/or examples on how to either add extra
settings or to re-use the existing ones?

My rust programming level is OK-ish but I'm not very familiar with the
Zed project structure and could use some help.

I'm also open for pair programming as mentioned on the website if that
helps!

Release Notes:

- Added pytest-based test discovery and runnables for Python.
- Adds a configurable option for switching between unittest and pytest
as a test runner under Python language settings. Set "TASK_RUNNER" to
"unittest" under task settings for Python if you wish to use unittest to
run Python tasks; the default is pytest.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-11-19 14:34:56 +01:00
Bennet Bo Fenner
aae39071ef editor: Show hints for using AI features on empty lines (#20824)
Co-Authored-by: Thorsten <thorsten@zed.dev>
Co-Authored-by: Antonio <antonio@zed.dev>

Screenshot:

![screenshot-2024-11-18-17 11
08@2x](https://github.com/user-attachments/assets/610fd7db-7476-4b9b-9465-a3d55df12340)

TODO:
- [x] docs

Release Notes:

- Added inline hints that guide users on how to invoke the inline
assistant and open the assistant panel. (These hints can be disabled by
setting `{"assistant": {"show_hints": false}}`.)

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
2024-11-19 09:41:44 +01:00
Nate Butler
a35b73e63e Revert "remove usages of theme::color_alpha"
This reverts commit c0d11be75f.
2024-11-19 00:24:48 -05:00
Nate Butler
c0d11be75f remove usages of theme::color_alpha 2024-11-19 00:24:37 -05:00
uncenter
0e26d22fea Add HTML injections for markdown (#20527)
Closes https://github.com/zed-industries/extensions/issues/1588.

| Before | After |
| --- | --- |
| ![CleanShot 2024-11-11 at 22 48
43](https://github.com/user-attachments/assets/9470e6a8-6a37-4b8f-8daa-5c8c5ed2bb17)
| ![CleanShot 2024-11-11 at 22 49
43](https://github.com/user-attachments/assets/f2b858d0-9274-4332-b30e-61c13ac347c6)
|

Release Notes:

- Added HTML injections for markdown syntax highlighting
2024-11-18 20:19:24 -07:00
Kyle Kelley
bd0f197415 Create RunningKernel trait to allow for native and remote jupyter kernels (#20842)
Starts setting up a `RunningKernel` trait to make the remote kernel
implementation easy to get started with. No release notes until this is
all hooked up.

Release Notes:

- N/A
2024-11-18 18:12:23 -08:00
Peter Tripp
343c88574a Improve file_types in default.json (#20429)
Detect .env.* as Shell Script
Move non glob json/jsonc/toml file_types into langauges/*/config.toml
2024-11-18 19:56:45 -05:00
Conrad Irwin
e7a0890086 Don't call setAllowsAutomaticKeyEquivalentLocalization on Big Sur (#20844)
Closes #20821

Release Notes:

- Fixed a crash on Big Sur (preview only)
2024-11-18 16:47:36 -07:00
Conrad Irwin
d4c5c0f05e Don't render invisibles with elements (#20841)
Turns out that in the case you have a somehow valid utf-8 file that
contains almost all ascii control characters, we run out of element
arena space.

Fixes: #20652

Release Notes:

- Fixed a crash when opening a file containing a very large number of
ascii control characters on one line.
2024-11-18 16:47:25 -07:00
lord
f0c7e62adc Leave goal_x unchanged when moving by rows past the start or end of the document (#20705)
Perhaps this was intentional behavior, but if not, I've attempted to
write this hacky fix — I noticed using the vertical arrow keys to move
past the document start/end would reset the goal_x to either zero (for
moving upwards) or the line width (for moving downwards). This change
makes Zed match most native text fields (at least on macOS) which leave
goal_x unchanged, even when hitting the end of the document.

I tested this change manually. Would be happy to add automatic tests for
it too, but couldn't find any existing cursor movement tests.

Release Notes:

- Behavior when moving vertically past the start or end of a document
now matches native text fields; it no longer resets the selection goal
2024-11-18 16:32:43 -07:00
Marshall Bowers
80d50f56f3 collab: Add feature flag to bypass account age check (#20843)
This PR adds a `bypass-account-age-check` feature flag that can be used
to bypass the minimum account age check.

Release Notes:

- N/A
2024-11-18 18:20:32 -05:00
Carroll Wainwright
fb6c987e3e python: Improve function syntax highlighting (#20487)
Release Notes:

- Differentiate between function and method calls and definitions.
`function.definition` matches the highlight for e.g. rust,
`function.call` is new.
- Likewise differentiate between class calls and class definitions.
- Better highlighting of function decorators (the `@` symbol is
punctuation, and now the decorator itself has a `function.decorator`
tag)
- Make `cls` a special variable (like `self`)
- Add `ellipsis` as a built-in constant

Note that most themes do not currently make use of the
`function.definition` tags, and none make use of the
`type.class.definition` tag. Hopefully more themes will pick this up.

*Before:*
<img width="248" alt="image"
src="https://github.com/user-attachments/assets/550ccd3d-594c-413a-b543-ef9caf39eee1">


*After:*
<img width="245" alt="image"
src="https://github.com/user-attachments/assets/47aa43b1-006b-4f9f-9029-510880f390ea">
2024-11-19 00:05:39 +01:00
Michael Sloan
b4c2f29c8b Remove use of current File for new buffers that never have File (#20832)
`create_buffer` calls `Buffer::local` which sets `file` to `None`
[here](f12981db32/crates/language/src/buffer.rs (L629)).
So there's no point in then immediately attempting to update maps that
rely on `file` being present.

Release Notes:

- N/A
2024-11-18 14:30:38 -08:00
Peter Tripp
8666ec95ba ssh: Fix SSH to mac remotes (#20838)
Restore ability to SSH to macOS arm remotes (`uname -m` on mac == `arm64`).
Fix regression introduced in https://github.com/zed-industries/zed/pull/20618
2024-11-18 17:17:24 -05:00
Anthony Eid
889aac9c03 Snippet choices (#13958)
Closes: #12739

Release Notes:

Solves #12739 by
- Enable snippet parsing to successfully parse snippets with choices
- Show completion menu when tabbing to a snippet variable with multiple
choices

Todo:
 - [x] Parse snippet choices
- [x] Open completion menu when tabbing to a snippet variable with
several choices (Thank you Piotr)
- [x] Get snippet choices to reappear when tabbing back to a previous
tabstop in a snippet
 - [x] add snippet unit tests
- [x] Add fuzzy search to snippet choice completion menu & update
completion menu based on choices
 - [x] add completion menu unit tests

Current State:

Using these custom snippets

```json
  "my snippet": {
      "prefix": "log",
      "body": ["type ${1|i32, u32|} = $2"],
      "description": "Expand `log` to `console.log()`"
  },
  "my snippet2": {
      "prefix": "snip",
      "body": [
        "type ${1|i,i8,i16,i64,i32|} ${2|test,test_again,test_final|} = $3"
      ],
      "description": "snippet choice tester"
    }
```

Using snippet choices:



https://github.com/user-attachments/assets/d29fb1a2-7632-4071-944f-daeaa243e3ac

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-11-18 22:56:34 +01:00
Peter Tripp
5b9916e34b ci: Add shellcheck for scripts (#20631)
Fixes shellcheck errors in script/*
Adds a couple trailing newlines.
Adds `script/shellcheck-scripts` and associated CI machinery.
Current set ultra-conservative, does not output warnings, only errors.
2024-11-18 16:41:22 -05:00
Peter Tripp
5b317f60df Improve install-cmake script (#20836)
- Don't output junk to stderr when cmake unavailable
- Kitware PPA does not include up to date bins for all distros (e.g.
Ubuntu 24 only has 3.30.2 although 3.30.4 has been out for a while) so
don't try to force install a specific version. Take the best we can get.
2024-11-18 16:39:57 -05:00
Marshall Bowers
e2552b9add collab: Bypass account age check for users with active LLM subscriptions (#20837)
This PR makes it so users with an active LLM subscription can bypass the
account age check.

Release Notes:

- N/A
2024-11-18 16:37:28 -05:00
Danilo Leal
37899187c6 Adjust file finder width configuration (#20819)
Follow up to: https://github.com/zed-industries/zed/pull/18682

This PR tweaks the setting value, so it's clear we're referring to
`max-width`, meaning the width will change up to a specific value
depending on the available window size. Then, it also makes `Small` the
default value, which, in practice, makes the modal size the same as it
was before the original PR linked above.

Release Notes:

- N/A

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-11-18 16:32:16 -03:00
Michael Sloan
d265e44209 Don't treat absence of a file on fs as conflict for new files from CLI (#20828)
Closes #20827

Release Notes:

- Fixes bug where save for new files created via CLI would report a
conflict and ask about overwriting.
2024-11-18 10:55:44 -08:00
Peter Tripp
f12981db32 docs: Add Language extension config TBDs (To Be Documented) (#20829)
Release Notes:

- N/A
2024-11-18 13:31:24 -05:00
Michael Sloan
d99f5fe83e Add File.disk_state enum to clarify filesystem states (#20776)
Motivation for this is to make things more understandable while figuring
out #20775.

This is intended to be a refactoring that does not affect behavior, but
there are a few tricky spots:

* Previously `File.mtime()` (now `File.disk_state().mtime()`) would
return last known modification time for deleted files. Looking at uses,
I believe this will not affect anything. If there are behavior changes
here I believe they would be improvements.

* `BufferEvent::DirtyChanged` is now only emitted if dirtiness actually
changed, rather than if it may have changed. This should only be an
efficiency improvement.

Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-11-18 10:30:08 -08:00
Marshall Bowers
df1d0dec0a ocaml: Extract to zed-extensions/ocaml repository (#20825)
This PR extracts the OCaml extensions to the
[zed-extensions/ocaml](https://github.com/zed-extensions/ocaml)
repository.

Release Notes:

- N/A
2024-11-18 11:30:45 -05:00
Marshall Bowers
ad94ad511a ocaml: Bump to v0.1.1 (#20823)
This PR bumps the OCaml extension to v0.1.1.

Changes:

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

Release Notes:

- N/A
2024-11-18 11:02:32 -05:00
Danilo Leal
0e7770a9a2 theme: Add color darken function (#20746)
This PR adds a `darken` function that allows to reduce the lightness of
a color by a certain factor. This popped up as I wanted to add hover
styles to tinted-colors buttons.

Release Notes:

- N/A
2024-11-18 12:44:49 -03:00
Danilo Leal
3f905d57e5 assistant: Adjust title summarization prompt (#20822)
Meant to avoid the excessive use of "Here's a concise 3-7 word title..."
and "Title:" instances we've been seeing lately.
Follow up to: https://github.com/zed-industries/zed/pull/19530

Release Notes:

- Improve prompt for generating title summaries, avoiding preambles
2024-11-18 12:44:06 -03:00
Peter Tripp
f01a86c644 Support for Back/Forward multimedia keys (#20695)
- Added Support for Back/Forward multimedia keys (Linux)
2024-11-18 09:36:08 -05:00
Peter Tripp
5fd7afb9da docs: More language extension config.toml key documentation (#20818)
Release Notes:

- N/A
2024-11-18 09:23:29 -05:00
Michael Sloan
9260abafba Use HashMap<ProjectEntryId, Entry> instead of HashSet<Entry> in outline_panel (#20780)
Came across this because I noticed that `Entry` implements `Hash`, which
was surprising to me. I believe that `ProjectEntryId` should be unique
and so it seems better to dedupe based on this.

Release Notes:

- N/A
2024-11-18 14:38:31 +02:00
Kirill Bulatov
d92166f9f6 Revert "Use livekit's Rust SDK instead of their swift SDK (#13343)" (#20809)
Issues found:

* audio does not work well with various set-ups using USB
* switching audio during initial join may leave the client with no audio
at all
* audio streaming is done on the main thread, beachballing certain
set-ups
* worse screenshare quality (seems that there's no dynamic scaling
anymore, compared to the Swift SDK)

This reverts commit 1235d0808e.

Release Notes:

- N/A
2024-11-18 11:43:53 +02:00
moshyfawn
59a355da74 docs: Update Svelte extension link (#20804)
Closes #20768

Release Notes:

- N/A
2024-11-18 09:51:47 +02:00
Nathan Sobo
ee207ab77e Map "feature upsell" events to the new "Noun Verbed" format (#20787)
Release Notes:

- N/A
2024-11-17 07:38:30 -07:00
Isaac Donaldson
31566cb5a0 Add width setting for the file finder (#18682)
This PR adds the ability to adjust the width of the file finder popup. I
found when searching my projects the default width was not always wide
enough and there was no option to change it.

It allows values `small`, `medium` (default), `large`, `xlarge`, and
`full`

Release Notes:

- Added a setting to adjust the width of the file finder modal


Example Setting:
```json
  "file_finder": {
    "modal_width": "medium"
  },
```

Screenshots can be found in the comments below.
2024-11-16 20:52:43 +02:00
Lu Wan
2d3476530e lsp: Retrieve links to documentation for the given symbol (#19233)
Closes #18924 

Release Notes:

- Added an `editor:OpenDocs` action to open links to documentation via
rust-analyzer
2024-11-16 20:23:49 +02:00
Nathan Sobo
f9990b42fa Send events to Snowflake in the format they're expected by Amplitude (#20765)
This will allow us to use the events table directly in Amplitude, which
lets us use the newer event ingestion flow that detects changes to the
table. Otherwise we'll need a transformation.

I think Amplitude's API is probably a pretty good example to follow for
the raw event schema, even if we don't end up using their product. They
also recommend a "Noun Verbed" format for naming events, so I think we
should go with this. This will help us be consistent and encourage the
author of events to think more clearly about what event they're
reporting.

cc @ConradIrwin 

Release Notes:

- N/A
2024-11-16 09:58:19 -07:00
Siddharth M. Bhatia
97e9137cb7 Update references of Ollama Llama 3.1 to model Llama 3.2 (#20757)
Release Notes:

- N/A
2024-11-16 11:18:53 -05:00
Jason Lee
932c7e23c8 gpui: Fix SVG color render, when color have alpha (#20537)
Release Notes:

- N/A


## Demo

- [Source
SVG](https://github.com/user-attachments/assets/1c681e01-baba-4613-a3e7-ea5cb3015406)
click here open in browser.

| Before | After |
| --- | --- |
| <img width="1212" alt="image"
src="https://github.com/user-attachments/assets/ba323b13-538b-4a34-bb64-9dcf490aface">
| <img width="1212" alt="image"
src="https://github.com/user-attachments/assets/4635926a-843e-426d-89a1-4e9b4f4cc37e">
|

---------

Co-authored-by: Floyd Wang <gassnake999@gmail.com>
2024-11-16 16:53:57 +02:00
Aaron Ruan
65a9c8d994 Dynamic tab bar height (#19076)
Tracking issue: https://github.com/zed-industries/zed/issues/18078

Release Notes:

- Change tab bar height according to `ui-density`

---------

Signed-off-by: Aaron Ruan <aaron212cn@outlook.com>
2024-11-16 13:48:25 +02:00
狐狸
33f09bad60 Highlight ? and : in ternary expressions as operator in JavaScript, TypeScript, and TSX (#20573)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2024-11-16 13:42:10 +02:00
Jason Lee
792c1e4710 gpui: Add paint_path example (#20499)
Release Notes:

- N/A

---

```
cargo run -p gpui --example painting
```

I added this demo to verify the detailed support of Path drawing in
GPUI.

Because of, when we actually used GPUI to draw a 2D line chart, we found
that the straight line Path#line_to did not support `anti-aliasing`, and
the drawn line looked very bad.

As shown in the demo image, if we zoom in on the image, we can clearly
see that all the lines are jagged.

I read and tried to make some appropriate adjustments to the functions
in Path, but since I have no experience in the graphics field, I still
cannot achieve anti-aliasing support so far.

I don't know if I used it wrong somewhere. I checked `curve_to` and
found that the curves drawn have anti-aliasing effects, as shown in the
arc part of the figure below.

<img width="1136" alt="image"
src="https://github.com/user-attachments/assets/4dfb7603-e746-43e9-b737-cff56b56329f">
2024-11-16 13:36:13 +02:00
Gowtham K
b421ffafb5 Windows: Add transparency effect (#20400)
Release Notes:

- Added Transparency effect for Windows #19405 


![image](https://github.com/user-attachments/assets/b0750020-5a89-48c9-b26e-13b30874cf8d)


![image](https://github.com/user-attachments/assets/80609a14-b8c3-4159-b909-1e61f4c3eac3)

---------

Co-authored-by: thedeveloper-sharath <35845141+thedeveloper-sharath@users.noreply.github.com>
2024-11-16 13:24:30 +02:00
SweetPPro
21c785ede4 Add more common Prettier plugin base paths (#20758)
Closes #19024

Release Notes:
- Added some more common Prettier plugin base paths
2024-11-16 13:20:52 +02:00
Mikayla Maki
516f7b3642 Add Loading and Fallback States to Image Elements (via StyledImage) (#20371)
@iamnbutler edit:

This pull request enhances the image element by introducing the ability
to display loading and fallback states.

Changes:

- Implemented the loading and fallback states for image elements using
`.with_loading` and `.with_fallback` respectively.
- Introduced the `StyledImage` trait and `ImageStyle` to enable a fluent
API for changing image styles across image types (`Img`,
`Stateful<Img>`, etc).

Example Usage:

```rust
fn loading_element() -> impl IntoElement {
    div().size_full().flex_none().p_0p5().rounded_sm().child(
        div().size_full().with_animation(
            "loading-bg",
            Animation::new(Duration::from_secs(3))
                .repeat()
                .with_easing(pulsating_between(0.04, 0.24)),
            move |this, delta| this.bg(black().opacity(delta)),
        ),
    )
}

fn fallback_element() -> impl IntoElement {
    let fallback_color: Hsla = black().opacity(0.5);

    div().size_full().flex_none().p_0p5().child(
        div()
            .size_full()
            .flex()
            .items_center()
            .justify_center()
            .rounded_sm()
            .text_sm()
            .text_color(fallback_color)
            .border_1()
            .border_color(fallback_color)
            .child("?"),
    )
}

impl Render for ImageLoadingExample {
    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
        img("some/image/path")
            .id("image-1")
            .with_fallback(|| Self::fallback_element().into_any_element())
            .with_loading(|| Self::loading_element().into_any_element())
    }
}
```

Note:

An `Img` must have an `id` to be able to add a loading state.

Release Notes:

- N/A

---------

Co-authored-by: nate <nate@zed.dev>
Co-authored-by: michael <michael@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
2024-11-15 19:12:01 -08:00
Michael Sloan
f34877334e Fix tab strikethrough logic (#20755)
This fix was in downstream commits before splitting out #20711, should
have tested locally before merging.

Release Notes:

- N/A
2024-11-15 17:14:49 -07:00
Peter Tripp
6e296eb4b6 ssh: Use openbsd nc on macOS (#20751)
Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-15 17:27:45 -05:00
Wes Higbee
4c8c6c08fe docs: Fix assistant prompt_overrides template directory (#20434)
Update docs to reflect the correct path for prompt handlebars templates.
Link to git repo for prompts rather than including an out of date version inline.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-15 17:26:50 -05:00
Michael Sloan
050ce919ba Rename "Overwrite" to "Save" for prompt about recreating a deleted file (#20750)
Release Notes:

- N/A
2024-11-15 15:12:40 -07:00
Michael Sloan
369828f51c Require save confirmation and prevent autosave for deleted files (#20742)
* `has_conflict` will now return true if the file has been deleted on
disk.  This is for treating multi-buffers as conflicted, and also
blocks auto-save.

* `has_deleted_file` is added so that the single-file buffer save can
specifically mention the delete conflict. This does not yet handle
discard (#20745).

Closes #9101
Closes #9568
Closes #20462

Release Notes:

- Improved handling of externally deleted files: auto-save will be
disabled, multibuffers will treat this as a save conflict, and single
buffers will ask for restore confirmation.

Co-authored-by: Conrad <conrad@zed.dev>
2024-11-15 15:01:16 -07:00
Conrad Irwin
ac5ecf5487 Don't log every value (#20744)
Release Notes:

- N/A
2024-11-15 14:36:58 -07:00
Max Brunsfeld
1235d0808e Use livekit's Rust SDK instead of their swift SDK (#13343)
See https://github.com/livekit/rust-sdks/pull/355

Todo:

* [x] make `call` / `live_kit_client` crates use the livekit rust sdk
* [x] create a fake version of livekit rust API for integration tests
* [x] capture local audio
* [x] play remote audio
* [x] capture local video tracks
* [x] play remote video tracks
* [x] tests passing
* bugs
* [x] deafening does not work
(https://github.com/livekit/rust-sdks/issues/359)
* [x] mute and speaking status are not replicated properly:
(https://github.com/livekit/rust-sdks/issues/358)
* [x] **linux** - crash due to symbol conflict between WebRTC's
BoringSSL and libcurl's openssl
(https://github.com/livekit/rust-sdks/issues/89)
* [x] **linux** - libwebrtc-sys adds undesired dependencies on `libGL`
and `libXext`
* [x] **windows** - linker error, maybe related to the C++ stdlib
(https://github.com/livekit/rust-sdks/issues/364)
        ```
libwebrtc_sys-54978c6ad5066a35.rlib(video_frame.obj) : error LNK2038:
mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't
match value 'MD_DynamicRelease' in
libtree_sitter_yaml-df6b0adf8f009e8f.rlib(2e40c9e35e9506f4-scanner.o)
        ```
    * [x] audio problems

Release Notes:

- Switch from Swift to Rust LiveKit SDK 🦀

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Conrad Irwin <conrad@zed.dev>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
2024-11-15 23:18:50 +02:00
Conrad Irwin
6ff69faf37 Start to send data to Snowflake too (#20698)
This PR adds support for sending telemetry events to AWS Kinesis.

In our AWS account we now have three new things:
* The [Kinesis data
stream](https://us-east-1.console.aws.amazon.com/kinesis/home?region=us-east-1#/streams/details/zed-telemetry/monitoring)
that we will actually write to.
* A [Firehose for
Axiom](https://us-east-1.console.aws.amazon.com/firehose/home?region=us-east-1#/details/telemetry-to-axiom/monitoring)
that sends events from that stream to Axiom for ad-hoc queries over
recent data.
* A [Firehose for
Snowflake](https://us-east-1.console.aws.amazon.com/firehose/home?region=us-east-1#/details/telemetry-to-snowflake/monitoring)
that sends events from that stream to Snowflake for long-term retention.
This Firehose also backs up data into an S3 bucket in case we want to
change how the system works in the future.

In a follow-up PR, we'll add support for ad-hoc telemetry events; and
slowly move away from the current Clickhouse defined schemas; though we
won't move off click house until we have what we need in Snowflake.

Co-Authored-By: Nathan <nathan@zed.dev>

Release Notes:

- N/A
2024-11-15 12:58:00 -07:00
renovate[bot]
f449e8d3d3 Migrate Renovate config (#20741)
The Renovate config in this repository needs migrating. Typically this
is because one or more configuration options you are using have been
renamed.

You don't need to merge this PR right away, because Renovate will
continue to migrate these fields internally each time it runs. But later
some of these fields may be fully deprecated and the migrations removed.
So it's a good idea to merge this migration PR soon.





🔕 **Ignore**: Close this PR and you won't be reminded about config
migration again, but one day your current config may no longer be valid.

 Got questions? Does something look wrong to you? Please don't hesitate
to [request help
here](https://redirect.github.com/renovatebot/renovate/discussions).


---

Release Notes:

- N/A

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-11-15 14:37:39 -05:00
Marshall Bowers
da09cbd055 assistant: Show more details for assist errors (#20740)
This PR updates the Assistant to show more detailed error messages when
the user encounters an assist error.

Here are some examples:

<img width="415" alt="Screenshot 2024-11-15 at 1 47 03 PM"
src="https://github.com/user-attachments/assets/5e7c5d5f-bd78-4af3-86ed-af4c6712770f">

<img width="417" alt="Screenshot 2024-11-15 at 2 11 14 PM"
src="https://github.com/user-attachments/assets/02cb659b-1239-4e24-865f-3a512703a94f">

The notification will scroll if the error lines overflow the set maximum
height.

Release Notes:

- Updated the Assistant to show more details in error cases.
2024-11-15 14:23:46 -05:00
Caleb Heydon
4327459d2a Fix Rust LSP adapter on FreeBSD (#20736)
This PR fixes the Rust LSP adapter on FreeBSD. This issue was creating
build errors.

Release Notes:

- Fixed Rust LSP adapter on FreeBSD
2024-11-15 11:03:55 -07:00
Michael Sloan
cc601bd770 Use strikethrough style in label implementation (#20735)
Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <richard@zed.dev>
Co-authored-by: Marshall Bowers <marshall@zed.dev>
2024-11-15 10:57:23 -07:00
Marshall Bowers
c491b75e07 assistant: Fix panic when using /project (#20733)
This PR fixes a panic when using `/project` (which is staff-flagged).

We weren't initializing the `SemanticDb` global if the
`project-slash-command` feature flag was enabled.

Closes #20563.

Release Notes:

- N/A
2024-11-15 12:25:29 -05:00
Marshall Bowers
3420ebb428 util: Remove unused code (#20734)
This PR removes the `with_clone` macro from `util`, as it wasn't used
(and isn't needed).

Release Notes:

- N/A
2024-11-15 12:25:18 -05:00
Marshall Bowers
b23d72ec4f gpui: Clean up Styled doc comments (#20731)
This PR cleans up the doc comments on the `Styled` trait to make them
more consistent.

Release Notes:

- N/A
2024-11-15 11:27:49 -05:00
Peter Tripp
e25a03cd3c docs: Variable escaping in tasks (#20730) 2024-11-15 11:14:30 -05:00
Marshall Bowers
9e8ff3f198 Update .mailmap (#20729)
This PR updates the `.mailmap` file to merge some more commit authors.

Release Notes:

- N/A
2024-11-15 11:05:41 -05:00
Marshall Bowers
6d80d5b74b gpui: Add line_through method to Styled (#20728)
This PR adds a `.line_through` method to the `Styled` trait that mirrors
the corresponding Tailwind class.

Release Notes:

- N/A
2024-11-15 10:51:09 -05:00
TOULAR
7137bdee02 Fix scrollbar not always being on top (#20665)
Set the elevation of the scrollbar to 1 borderless, so that the blue
outline is no longer above the scrollbar.

Closes #19875

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-15 16:47:17 +01:00
Kirill Bulatov
98403aa994 Disable signature help shown by default (#20726)
Closes https://github.com/zed-industries/zed/issues/20725

Stop showing the pop-up that gets an issue open every now and then.

Release Notes:

- Stopped showing signature help after completions by default
2024-11-15 17:19:11 +02:00
lord
794ad1af75 ocaml: Improve highlighting and bracketing (#20700)
Some small improvements to OCaml. Would happily split these into smaller
changes, discard anything, etc.

Before:
<img width="441" alt="before"
src="https://github.com/user-attachments/assets/2fb82b03-0d45-4c59-a94d-6f48d634fe19">

After:
<img width="448" alt="after"
src="https://github.com/user-attachments/assets/aa232d8f-4b1b-48f8-93e2-2147de37a20d">

OCaml highlighting and bracketing improvements
    - Fixed bug where `<` was automatically closed with `>`.
    - Typing `{|` now automatically closes with `|}`
- Type variables are now colored with `variable.special` instead of
`variable`.
- Argument names in function declarations and application are now
colored with `label` instead of `property`, even if they are punned.
- `[@@` and `[%` in macros are now colored as bracket punctuation to
match the closing `]`, rather than colored as `attribute`

Release Notes:

- N/A
2024-11-15 10:17:46 -05:00
Conrad Irwin
4b1f0c033b html: Bump to v0.1.4 (#20692)
Changes:

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

Release Notes:

- N/A
2024-11-15 10:10:25 -05:00
Danilo Leal
3796b4a55c project panel: Update decoration icon active color (#20723)
Just so that the icon decoration knockout color matches the background
of a selected/market item.

<img width="600" alt="Screenshot 2024-11-15 at 10 50 24"
src="https://github.com/user-attachments/assets/0037fe5a-f42d-47e8-8559-97ca11ff2d97">

Release Notes:

- N/A
2024-11-15 11:48:26 -03:00
Piotr Osiewicz
8c02929710 pane: Fix rough edges around pinning of dropped project entries (#20722)
Closes #20485

Release Notes:

- Fixed quirks around dropping project entries into tab bar which
might've led to tabs being pinned sometimes.
2024-11-15 13:51:40 +01:00
Kirill Bulatov
1e14697bb6 Fix Linux rust-analyzer downloads in Preview (#20718)
Follow-up of https://github.com/zed-industries/zed/pull/20408

Release Notes:

- (Preview) Fixed broken rust-analyzer downloads
2024-11-15 11:57:54 +02:00
Adam Wolff
f619a872b5 python: Prefer conda environments that match CONDA_PREFIX (#20702)
Release Notes:

- Improved Python toolchain selection for conda environments
2024-11-15 09:12:35 +01:00
Michael Sloan
c03f5b351b Use strikethrough on tab label to indicate file deletion (#20711)
Closes #5364

Release Notes:

- Added indication of deleted files. Files deleted outside of Zed will
have a strikethrough in the title of the tab.
2024-11-15 00:39:09 -07:00
Conrad Irwin
a8df0642a8 vim: Allow :cpplink for CopyPermalinkToLine (#20707)
Release Notes:

- vim: Added `:<range>cpplink` to copy a permanent git link to the
highlighted range to the clipboard
2024-11-14 23:44:40 -07:00
Thorsten Ball
aee01f2c50 assistant: Remove low_speed_timeout (#20681)
This removes the `low_speed_timeout` setting from all providers as a
response to issue #19509.

Reason being that the original `low_speed_timeout` was only as part of
#9913 because users wanted to _get rid of timeouts_. They wanted to bump
the default timeout from 5sec to a lot more.

Then, in the meantime, the meaning of `low_speed_timeout` changed in
#19055 and was changed to a normal `timeout`, which is a different thing
and breaks slower LLMs that don't reply with a complete response in the
configured timeout.

So we figured: let's remove the whole thing and replace it with a
default _connect_ timeout to make sure that we can connect to a server
in 10s, but then give the server as long as it wants to complete its
response.

Closes #19509

Release Notes:

- Removed the `low_speed_timeout` setting from LLM provider settings,
since it was only used to _increase_ the timeout to give LLMs more time,
but since we don't have any other use for it, we simply remove the
setting to give LLMs as long as they need.

---------

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-15 07:37:31 +01:00
chottolabs
c9546070ac docs: Update Zig Tree-sitter grammar link (#20708)
lines up with
https://github.com/zed-industries/zed/blob/main/extensions/zig/extension.toml

Release Notes:

- N/A
2024-11-15 07:45:36 +02:00
Marshall Bowers
1855a312d0 Use Extension trait in ExtensionLspAdapter (#20704)
This PR updates the `ExtensionLspAdapter` to go through the `Extension`
trait for interacting with extensions rather than going through the
`WasmHost` directly.

Release Notes:

- N/A
2024-11-14 20:44:57 -05:00
Dairon M.
332b33716a erlang: Update tree-sitter grammar (#20699)
erlang: Update tree-sitter grammar for new OTP 27 features:
- -moduledoc and -doc attributes
- Sigils
- Triple quoted strings

<img width="717" alt="Screenshot 2024-11-14 at 5 18 08 PM"
src="https://github.com/user-attachments/assets/24812b17-4e64-47f3-a6ab-6bc7260cd53f">

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-11-14 18:10:00 -05:00
Conrad Irwin
acf25324be Delete a.html (#20691)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2024-11-14 13:00:03 -07:00
hrou0003
f0882f44a7 vim: Enable % to jump between tags (#20536)
Closes #12986

Release Notes:

- Enable `%` to jump between pairs of tags

---------

Co-authored-by: Harrison <hrouillard@sfi.com.au>
2024-11-14 12:41:53 -07:00
Harsh Narayan Jha
189a034e71 docs: Document exec flags for GDScript (#20688)
While looking up the GDScript extension docs, I noticed that the
original extension repo mentions of `{line}:{col}` placeholders too in
addition to `{project} {file}` that the Zed docs suggest adding.

This PR Improves the docs to add those missing options to the suggested
flags.
2024-11-14 14:28:25 -05:00
Conrad Irwin
7f52071513 Use the project env when running LSPs (#20641)
This change ensures we always run LSPs with the project environment (in
addition to any overrides they provide). This helps ensure the
environment is
set correctly on remotes where we don't load the login shell environment
and
assign it to the current process.

Also fixed the go language to use the project env to find the go
command.

Release Notes:

- Improved environment variable handling for SSH remotes
2024-11-14 12:26:55 -07:00
Piotr Osiewicz
56c93be4de project panel: Fix rendering of groups of dragged project panel entries (#20686)
This PR introduces a new parameter for `on_drag` in gpui, which is an
offset from the element origin to the mouse event origin.

Release Notes:

- Fixed rendering of dragged project panel entries
2024-11-14 19:29:18 +01:00
Marshall Bowers
43999c47e1 client: Remove unneeded return (#20685)
This PR removes an unneeded `return` that was introduced in #19928.

Release Notes:

- N/A
2024-11-14 13:16:55 -05:00
David Soria Parra
690a725667 context_servers: Upgrade protocol to version 2024-11-05 (#20615)
This updates context servers to the most recent version

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-11-14 13:03:30 -05:00
Marshall Bowers
b5ce8e7aa5 zed_extension_api: Release v0.2.0 (#20683)
This PR releases v0.2.0 of the Zed extension API.

Support for this version of the extension API will land in Zed v0.162.x.

Release Notes:

- N/A
2024-11-14 12:44:10 -05:00
Marshall Bowers
d177a1d4e5 Move ExtensionStore tests back to extension_host (#20682)
This PR moves the tests for the `ExtensionStore` back into the
`extension_host` crate.

We now have a separate `TestExtensionRegistrationHooks` to use in the
test that implements the minimal required functionality needed for the
tests. This means that we can depend on the `theme` crate only in the
tests.

Release Notes:

- N/A
2024-11-14 12:09:41 -05:00
renovate[bot]
5d17cfab31 Update Rust crate wasmtime to v24.0.2 [SECURITY] (#20614)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [wasmtime](https://redirect.github.com/bytecodealliance/wasmtime) |
workspace.dependencies | patch | `24.0.1` -> `24.0.2` |

### GitHub Vulnerability Alerts

####
[CVE-2024-51745](https://redirect.github.com/bytecodealliance/wasmtime/security/advisories/GHSA-c2f5-jxjv-2hh8)

### Impact

Wasmtime's filesystem sandbox implementation on Windows blocks access to
special device filenames such as "COM1", "COM2", "LPT0", "LPT1", and so
on, however it did not block access to the special device filenames
which use superscript digits, such as "COM¹", "COM²", "LPT⁰", "LPT¹",
and so on. Untrusted Wasm programs that are given access to any
filesystem directory could bypass the sandbox and access devices through
those special device filenames with superscript digits, and through them
gain access peripheral devices connected to the computer, or network
resources mapped to those devices. This can include modems, printers,
network printers, and any other device connected to a serial or parallel
port, including emulated USB serial ports.

### Patches

Patch releases for Wasmtime have been issued as 24.0.2, 25.0.3, and
26.0.1. Users of Wasmtime 23.0.x and prior are recommended to upgrade to
one of these patched versions.

### Workarounds

There are no known workarounds for this issue. Affected Windows users
are recommended to upgrade.

### References

- [Microsoft's
documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions)
of the special device filenames
 - [ISO-8859-1](https://en.wikipedia.org/wiki/ISO/IEC_8859-1)
- [The original PR reporting the
issue](https://redirect.github.com/bytecodealliance/cap-std/pull/371)

---

### Release Notes

<details>
<summary>bytecodealliance/wasmtime (wasmtime)</summary>

###
[`v24.0.2`](https://redirect.github.com/bytecodealliance/wasmtime/releases/tag/v24.0.2)

[Compare
Source](https://redirect.github.com/bytecodealliance/wasmtime/compare/v24.0.1...v24.0.2)

#### 24.0.2

Released 2024-11-05.

##### Fixed

- Update to cap-std 3.4.1, for
[#&#8203;9559](https://redirect.github.com/bytecodealliance/wasmtime/issues/9559),
which fixes a wasi-filesystem sandbox
    escape on Windows.

[CVE-2024-51745](https://redirect.github.com/bytecodealliance/wasmtime/security/advisories/GHSA-c2f5-jxjv-2hh8).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" 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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMS41IiwidXBkYXRlZEluVmVyIjoiMzkuMTEuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:35:31 +02:00
renovate[bot]
404ddeebc5 Update Rust crate serde_json to v1.0.132 (#20638)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

- Improve binary size and compile time for JSON array and JSON object
deserialization by about 50%
([#&#8203;1205](https://redirect.github.com/serde-rs/json/issues/1205))
- Improve performance of JSON array and JSON object deserialization by
about 8%
([#&#8203;1206](https://redirect.github.com/serde-rs/json/issues/1206))

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

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

- Implement Deserializer and IntoDeserializer for `Map<String, Value>`
and `&Map<String, Value>`
([#&#8203;1135](https://redirect.github.com/serde-rs/json/issues/1135),
thanks [@&#8203;swlynch99](https://redirect.github.com/swlynch99))

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

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

- Support converting and deserializing `Number` from i128 and u128
([#&#8203;1141](https://redirect.github.com/serde-rs/json/issues/1141),
thanks [@&#8203;druide](https://redirect.github.com/druide))

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

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

- Add
[`serde_json::Map::sort_keys`](https://docs.rs/serde_json/1/serde_json/struct.Map.html#method.sort_keys)
and
[`serde_json::Value::sort_all_objects`](https://docs.rs/serde_json/1/serde_json/enum.Value.html#method.sort_all_objects)
([#&#8203;1199](https://redirect.github.com/serde-rs/json/issues/1199))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:35:13 +02:00
renovate[bot]
ad370ed986 Update Rust crate sys-locale to v0.3.2 (#20639)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sys-locale](https://redirect.github.com/1Password/sys-locale) |
workspace.dependencies | patch | `0.3.1` -> `0.3.2` |

---

### Release Notes

<details>
<summary>1Password/sys-locale (sys-locale)</summary>

###
[`v0.3.2`](https://redirect.github.com/1Password/sys-locale/releases/tag/v0.3.2)

[Compare
Source](https://redirect.github.com/1Password/sys-locale/compare/v0.3.1...v0.3.2)

#### What's Changed

##### Added

- Support for all other Apple targets, such as watchOS and tvOS by
[@&#8203;complexspaces](https://redirect.github.com/complexspaces) in
[https://github.com/1Password/sys-locale/pull/38](https://redirect.github.com/1Password/sys-locale/pull/38).
- Support for ignoring POSIX modifiers in UNIX locales with them present
by [@&#8203;pasabanov](https://redirect.github.com/pasabanov) in
[https://github.com/1Password/sys-locale/pull/33](https://redirect.github.com/1Password/sys-locale/pull/33).
    -   Parsing support/recognition may come at a later date.
- Support for returning a list of user locales on Linux/BSD UNIX
platforms by [@&#8203;pasabanov](https://redirect.github.com/pasabanov)
in
[https://github.com/1Password/sys-locale/pull/35](https://redirect.github.com/1Password/sys-locale/pull/35).

##### Fixed

- No longer use `LC_CTYPE` when determining the locale; the crate now
uses `LC_MESSAGES` in its place by
[@&#8203;pasabanov](https://redirect.github.com/pasabanov) in
[https://github.com/1Password/sys-locale/pull/35](https://redirect.github.com/1Password/sys-locale/pull/35).
- Skip empty locale environment variables on UNIX platforms by
[@&#8203;complexspaces](https://redirect.github.com/complexspaces) in
[https://github.com/1Password/sys-locale/pull/29](https://redirect.github.com/1Password/sys-locale/pull/29).
- Corrected types mentioned and improved the public API documentation by
[@&#8203;pasabanov](https://redirect.github.com/pasabanov) in
[https://github.com/1Password/sys-locale/pull/37](https://redirect.github.com/1Password/sys-locale/pull/37).

##### Changed

- Improved crate download size by excluding unused directories and files
by [@&#8203;pasabanov](https://redirect.github.com/pasabanov).
- Very slight improvement to locale fetching performance on Windows by
[@&#8203;complexspaces](https://redirect.github.com/complexspaces) in
[https://github.com/1Password/sys-locale/pull/29](https://redirect.github.com/1Password/sys-locale/pull/29).
- Increased MSRV to Rust 1.56, which is 3 years old as of this release
by [@&#8203;complexspaces](https://redirect.github.com/complexspaces).

#### New Contributors

- [@&#8203;pasabanov](https://redirect.github.com/pasabanov) made their
first contribution in
[https://github.com/1Password/sys-locale/pull/30](https://redirect.github.com/1Password/sys-locale/pull/30)

**Full Changelog**:
https://github.com/1Password/sys-locale/compare/v0.3.1...v0.3.2

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:35:05 +02:00
renovate[bot]
ced9045591 Update Rust crate thiserror to v1.0.69 (#20643)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

-   Backport 2.0.2 fixes

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

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

- Handle incomplete expressions more robustly in format arguments, such
as while code is being typed
([#&#8203;341](https://redirect.github.com/dtolnay/thiserror/issues/341),
[#&#8203;344](https://redirect.github.com/dtolnay/thiserror/issues/344))

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

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

- Improve expression syntax support inside format arguments
([#&#8203;335](https://redirect.github.com/dtolnay/thiserror/issues/335),
[#&#8203;337](https://redirect.github.com/dtolnay/thiserror/issues/337),
[#&#8203;339](https://redirect.github.com/dtolnay/thiserror/issues/339),
[#&#8203;340](https://redirect.github.com/dtolnay/thiserror/issues/340))

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

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

- Improve compile error on malformed format attribute
([#&#8203;327](https://redirect.github.com/dtolnay/thiserror/issues/327))

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

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

- Ensure OUT_DIR is left with deterministic contents after build script
execution
([#&#8203;325](https://redirect.github.com/dtolnay/thiserror/issues/325))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 18:34:58 +02:00
Kirill Bulatov
0d9bcbba25 Use vim-like keybindings for splitting out of the file finder (#20680)
Follow-up of https://github.com/zed-industries/zed/pull/20507

Release Notes:

- (breaking Preview) Adjusted file finder split keybindings to be less
conflicting

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-14 18:32:04 +02:00
Peter Tripp
c650ba4e72 docs: Proto formatter example (#20677) 2024-11-14 11:25:37 -05:00
Marshall Bowers
5fab3ca5ba Format workspace Cargo.toml (#20679)
This PR does some formatting of the workspace `Cargo.toml`.

Release Notes:

- N/A
2024-11-14 11:10:58 -05:00
zachcp
621a200d2f docs: Remove duplicate text in the Clojure page (#20635) 2024-11-14 10:36:43 -05:00
Shiny
2544fad8a4 Fix tab switch behavior for the Sublime Text keymap (#20547)
Don't override tab switching behavior, default has correct behavior.
2024-11-14 09:50:29 -05:00
Piotr Osiewicz
49eb865e8a python: Improve handling of triple-quoted strings (#20664)
Closes #13998

/cc @notpeter would you mind giving this branch a go to see if this is
pleasant to use? This impl is not quite what VSC has, but I think it
feels okay?

In this PR, the sequence goes as follows:
1st keypress: "|"
2nd keypress: ""|
3rd keypress: """|"""

Release Notes:

- Improved handling of triple-quote strings in Python.
2024-11-14 15:12:46 +01:00
Piotr Osiewicz
a650fe0d77 python: Fix detection of Poetry environments (#20669)
We were missing a .configure call, which let to discrepancies with PET
output.

Release Notes:

- Improved detection of Poetry-based environments
2024-11-14 14:54:53 +01:00
Danilo Leal
204a989758 assistant: Add tiny design tweaks to the patch block (#20636)
Adjusting really tiny things, most notably ensuring that the header text
doesn't overflow out of the block.

<img width="600" alt="Screenshot 2024-11-13 at 20 11 08"
src="https://github.com/user-attachments/assets/26656203-92c6-49e5-a732-bae010f96b2d">


Release Notes:

- N/A
2024-11-14 10:53:00 -03:00
Thorsten Ball
776cfe44d7 environment: Log stderr too if command fails to run (#20659)
Release Notes:

- N/A
2024-11-14 14:44:32 +01:00
Thorsten Ball
35798212c4 Update copilot to Copilot.vim 1.41.0 (#20520)
Release Notes:

- Update Copilot's underlying version to [Copilot.vim
1.41.0](8703812380)

Co-authored-by: Antonio <antonio@zed.dev>
2024-11-14 14:44:13 +01:00
Piotr Osiewicz
89f9a506f9 tasks: Add ability to query active toolchains for languages (#20667)
Closes #18649

Release Notes:

- Python tasks now use active toolchain to run.
2024-11-14 14:37:37 +01:00
Nate Butler
04ba75e2e5 Add ui::ContentGroup (#20666)
TL;DR our version of [HIG's
Box](https://developer.apple.com/design/human-interface-guidelines/boxes)

We can't use the name `Box` (because rust) or `ContentBox` (because
taffy/styles/css).

---

This PR introduces the `ContentGroup` component, a flexible container
inspired by HIG's `Box` component. It's designed to hold and organize
various UI elements with options to toggle borders and background fills.

**Example usage**:

```rust
ContentGroup::new()
    .flex_1()
    .items_center()
    .justify_center()
    .h_48()
    .child(Label::new("Flexible ContentBox"))
```

Here are some configurations:

- Default: Includes both border and fill.
- Borderless: No border for a clean look.
- Unfilled: No background fill for a transparent appearance.

**Preview**:

![CleanShot 2024-11-14 at 07 05
15@2x](https://github.com/user-attachments/assets/c838371e-e24f-46f0-94b4-43c078e8f14e)

---

_This PR was written by a large language model with input from the
author._

Release Notes:

- N/A
2024-11-14 08:25:48 -05:00
renovate[bot]
f7b4431659 Update Rust crate libc to v0.2.162 (#20625)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

##### Added

- Android: fix the alignment of `uc_mcontext` on arm64
[#&#8203;3894](https://redirect.github.com/rust-lang/libc/pull/3894)
- Apple: add `host_cpu_load_info`
[#&#8203;3916](https://redirect.github.com/rust-lang/libc/pull/3916)
- ESP-IDF: add a time flag
[#&#8203;3993](https://redirect.github.com/rust-lang/libc/pull/3993)
- FreeBSD: add the `CLOSE_RANGE_CLOEXEC`
flag[#&#8203;3996](https://redirect.github.com/rust-lang/libc/pull/3996)
- FreeBSD: fix test errors regarding `__gregset_t`
[#&#8203;3995](https://redirect.github.com/rust-lang/libc/pull/3995)
- FreeBSD: fix tests on x86 FreeBSD 15
[#&#8203;3948](https://redirect.github.com/rust-lang/libc/pull/3948)
- FreeBSD: make `ucontext_t` and `mcontext_t` available on all
architectures
[#&#8203;3848](https://redirect.github.com/rust-lang/libc/pull/3848)
- Haiku: add `getentropy`
[#&#8203;3991](https://redirect.github.com/rust-lang/libc/pull/3991)
- Illumos: add `syncfs`
[#&#8203;3990](https://redirect.github.com/rust-lang/libc/pull/3990)
- Illumos: add some recently-added constants
[#&#8203;3999](https://redirect.github.com/rust-lang/libc/pull/3999)
- Linux: add `ioctl` flags
[#&#8203;3960](https://redirect.github.com/rust-lang/libc/pull/3960)
- Linux: add epoll busy polling parameters
[#&#8203;3922](https://redirect.github.com/rust-lang/libc/pull/3922)
- NuttX: add `pthread_[get/set]name_np`
[#&#8203;4003](https://redirect.github.com/rust-lang/libc/pull/4003)
- RTEMS: add `arc4random_buf`
[#&#8203;3989](https://redirect.github.com/rust-lang/libc/pull/3989)
- Trusty OS: add initial support
[#&#8203;3942](https://redirect.github.com/rust-lang/libc/pull/3942)
- WASIp2: expand socket support
[#&#8203;3981](https://redirect.github.com/rust-lang/libc/pull/3981)

##### Fixed

- Emscripten: don't pass `-lc`
[#&#8203;4002](https://redirect.github.com/rust-lang/libc/pull/4002)
- Hurd: change `st_fsid` field to `st_dev`
[#&#8203;3785](https://redirect.github.com/rust-lang/libc/pull/3785)
- Hurd: fix the definition of `utsname`
[#&#8203;3992](https://redirect.github.com/rust-lang/libc/pull/3992)
- Illumos/Solaris: fix `FNM_CASEFOLD` definition
[#&#8203;4004](https://redirect.github.com/rust-lang/libc/pull/4004)
- Solaris: fix all tests
[#&#8203;3864](https://redirect.github.com/rust-lang/libc/pull/3864)

##### Other

- CI: Add loongarch64
[#&#8203;4000](https://redirect.github.com/rust-lang/libc/pull/4000)
- CI: Check that semver files are sorted
[#&#8203;4018](https://redirect.github.com/rust-lang/libc/pull/4018)
- CI: Re-enable the FreeBSD 15 job
[#&#8203;3988](https://redirect.github.com/rust-lang/libc/pull/3988)
- Clean up imports and `extern crate` usage
[#&#8203;3897](https://redirect.github.com/rust-lang/libc/pull/3897)
- Convert `mode_t` constants to octal
[#&#8203;3634](https://redirect.github.com/rust-lang/libc/pull/3634)
- Remove the `wasm32-wasi` target that has been deleted upstream
[#&#8203;4013](https://redirect.github.com/rust-lang/libc/pull/4013)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 12:07:38 +02:00
renovate[bot]
6b9eba2109 Update Rust crate linkme to v0.3.31 (#20626)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [linkme](https://redirect.github.com/dtolnay/linkme) | dependencies |
patch | `0.3.29` -> `0.3.31` |

---

### Release Notes

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

###
[`v0.3.31`](https://redirect.github.com/dtolnay/linkme/releases/tag/0.3.31)

[Compare
Source](https://redirect.github.com/dtolnay/linkme/compare/0.3.30...0.3.31)

- Prevent `ref_option_ref` pedantic clippy lint from triggering inside
generated code
([#&#8203;103](https://redirect.github.com/dtolnay/linkme/issues/103))
- Implement Debug for DistributedSlice
([#&#8203;105](https://redirect.github.com/dtolnay/linkme/issues/105))

###
[`v0.3.30`](https://redirect.github.com/dtolnay/linkme/releases/tag/0.3.30)

[Compare
Source](https://redirect.github.com/dtolnay/linkme/compare/0.3.29...0.3.30)

- Support Rust 2024 edition
([#&#8203;102](https://redirect.github.com/dtolnay/linkme/issues/102))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 12:07:27 +02:00
renovate[bot]
58e3b788dc Update Rust crate mdbook to v0.4.42 (#20629)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [mdbook](https://redirect.github.com/rust-lang/mdBook) | dependencies
| patch | `0.4.40` -> `0.4.42` |

---

### Release Notes

<details>
<summary>rust-lang/mdBook (mdbook)</summary>

###
[`v0.4.42`](https://redirect.github.com/rust-lang/mdBook/blob/HEAD/CHANGELOG.md#mdBook-0442)

[Compare
Source](https://redirect.github.com/rust-lang/mdBook/compare/v0.4.41...v0.4.42)


[v0.4.41...v0.4.42](https://redirect.github.com/rust-lang/mdBook/compare/v0.4.41...v0.4.42)

##### Fixed

-   Fixed chapter list folding.
[#&#8203;2473](https://redirect.github.com/rust-lang/mdBook/pull/2473)

###
[`v0.4.41`](https://redirect.github.com/rust-lang/mdBook/blob/HEAD/CHANGELOG.md#mdBook-0441)

[Compare
Source](https://redirect.github.com/rust-lang/mdBook/compare/v0.4.40...v0.4.41)


[v0.4.40...v0.4.41](https://redirect.github.com/rust-lang/mdBook/compare/v0.4.40...v0.4.41)

**Note:** If you have a custom `index.hbs` theme file, you will need to
update it to the latest version.

##### Added

-   Added preliminary support for Rust 2024 edition.
[#&#8203;2398](https://redirect.github.com/rust-lang/mdBook/pull/2398)
-   Added a full example of the remove-emphasis preprocessor.
[#&#8203;2464](https://redirect.github.com/rust-lang/mdBook/pull/2464)

##### Changed

-   Adjusted styling of clipboard/play icons.
[#&#8203;2421](https://redirect.github.com/rust-lang/mdBook/pull/2421)
-   Updated to handlebars v6.
[#&#8203;2416](https://redirect.github.com/rust-lang/mdBook/pull/2416)
-   Attr and section rules now have specific code highlighting.
[#&#8203;2448](https://redirect.github.com/rust-lang/mdBook/pull/2448)
- The sidebar is now loaded from a common file, significantly reducing
the book size when there are many chapters.
[#&#8203;2414](https://redirect.github.com/rust-lang/mdBook/pull/2414)
-   Updated dependencies.
[#&#8203;2470](https://redirect.github.com/rust-lang/mdBook/pull/2470)

##### Fixed

-   Improved theme support when JavaScript is disabled.
[#&#8203;2454](https://redirect.github.com/rust-lang/mdBook/pull/2454)
-   Fixed broken themes when localStorage has an invalid theme id.
[#&#8203;2463](https://redirect.github.com/rust-lang/mdBook/pull/2463)
- Adjusted the line-height of superscripts (and footnotes) to avoid
adding extra space between lines.
[#&#8203;2465](https://redirect.github.com/rust-lang/mdBook/pull/2465)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 12:07:17 +02:00
renovate[bot]
9fd971d8c9 Update Rust crate pulldown-cmark to v0.12.2 (#20630)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [pulldown-cmark](https://redirect.github.com/raphlinus/pulldown-cmark)
| workspace.dependencies | patch | `0.12.1` -> `0.12.2` |

---

### Release Notes

<details>
<summary>raphlinus/pulldown-cmark (pulldown-cmark)</summary>

###
[`v0.12.2`](https://redirect.github.com/pulldown-cmark/pulldown-cmark/releases/tag/v0.12.2):
0.12.2

[Compare
Source](https://redirect.github.com/raphlinus/pulldown-cmark/compare/v0.12.1...v0.12.2)

#### What's Changed

- Fix compiilation error in fuzzers by
[@&#8203;kdarkhan](https://redirect.github.com/kdarkhan) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/947](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/947)
- Make `fuzz` dir part of the workspace by
[@&#8203;kdarkhan](https://redirect.github.com/kdarkhan) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/948](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/948)
- Fix and improve `bench` by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/950](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/950)
- Reuse a couple hash maps across blocks by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/951](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/951)
- Reuse outer indent between item list, def list, and blockquote by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/952](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/952)
- Add instructions on fixing fuzz build by
[@&#8203;kdarkhan](https://redirect.github.com/kdarkhan) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/953](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/953)
- Account for definition list fixups while popping containers by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/954](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/954)
- Use byte range instead of char count for delim run bounds by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/956](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/956)
- CI improvements by
[@&#8203;kdarkhan](https://redirect.github.com/kdarkhan) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/955](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/955)
- Fix a problem that causes multiple dt's to be parsed by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/958](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/958)
- fix: emit `InlineHtml` for inline HTML inside blockquote instead of
`Html` by [@&#8203;rhysd](https://redirect.github.com/rhysd) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/961](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/961)
- Complete the list of block item bodies by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/962](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/962)
- Implement into_static for CowStr and Event in pulldown-cmark by
[@&#8203;Atreyagaurav](https://redirect.github.com/Atreyagaurav) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/967](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/967)
- Enforce cargo fmt by
[@&#8203;ollpu](https://redirect.github.com/ollpu) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/971](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/971)
- Respect line starts when trimming header endings by
[@&#8203;notriddle](https://redirect.github.com/notriddle) in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/969](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/969)

#### New Contributors

- [@&#8203;Atreyagaurav](https://redirect.github.com/Atreyagaurav) made
their first contribution in
[https://github.com/pulldown-cmark/pulldown-cmark/pull/967](https://redirect.github.com/pulldown-cmark/pulldown-cmark/pull/967)

**Full Changelog**:
https://github.com/pulldown-cmark/pulldown-cmark/compare/v0.12.1...v0.12.2

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 12:07:03 +02:00
renovate[bot]
cf7679e6a0 Update Rust crate clap to v4.5.21 (#20620)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v4.5.21`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4521---2024-11-13)

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

##### Fixes

- *(parser)* Ensure defaults are filled in on error with
`ignore_errors(true)`

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 11:12:14 +02:00
renovate[bot]
07c0c54c28 Update Rust crate anyhow to v1.0.93 (#20619)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

-   Update dev-dependencies to `thiserror` v2

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

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

- Support Rust 1.82's `&raw const` and `&raw mut` syntax inside
`ensure!`
([#&#8203;390](https://redirect.github.com/dtolnay/anyhow/issues/390))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 11:11:52 +02:00
Max Brunsfeld
093c9cc87b Avoid creating occlusions for editor blocks, since these block mouse wheel events (#20649)
Just block mouse down events, and in the case of the inline assist
prompt, set the default cursor.

Release Notes:

- N/A

Co-authored-by: Richard <richard@zed.dev>
2024-11-13 21:02:54 -08:00
Bret Comnes
6b3c909155 Clarify rustup is requried (#20642)
Clarify that rustup is required to build developer extensions. Developer
extensions fail silently to the logs because rustup isn't found, even
when rust is installed.

Release Notes:

- N/A
2024-11-13 17:20:20 -08:00
Conrad Irwin
7e349e52b1 Don't try and run on armv5/6/7 (#20618)
Updates: #20523

Release Notes:

- SSH Remoting: correctly show an error when SSH'ing into a 32-bit arm
system
2024-11-13 16:18:53 -07:00
Max Brunsfeld
84d17fb191 Fix completions for non-built-in slash commands (#20632)
Release Notes:

- N/A

Co-authored-by: Marshall <marshall@zed.dev>
2024-11-13 15:11:50 -08:00
Max Brunsfeld
d3d408d47d Improve context server lifecycle management (#20622)
This optimizes and fixes bugs in our logic for maintaining a set of
running context servers, based on the combination of the user's
`context_servers` settings and their installed extensions.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-11-13 13:55:06 -08:00
Conrad Irwin
6e477bbf56 Don't double-localize menu shortcuts (#20623)
Release Notes:

- Don't have macOS localize our menu shortcuts that we already
localized.
2024-11-13 13:56:56 -07:00
Conrad Irwin
3c2dcf50fa Don't send key equivalents to the input hanlder (#20621)
Release Notes:

- Fix `cmd-backtick` to change windows
2024-11-13 13:42:31 -07:00
David Soria Parra
a15f408f0c anthropic: Remove stable headers (#20595)
The tool and context length headers are now stable and no longer needed.

Release Notes:

- N/A
2024-11-13 15:04:37 -05:00
Axel Carlsson
b1cd9e4d24 vim: Add support for temporary normal mode (ctrl-o) within insert mode (#19454)
Support has been added for the ctrl-o command within insert mode. Ctrl-o
is used to partially enter normal mode for 1 motion to then return back
into insert mode.

Release Notes:

- vim: Added support for `ctrl-o` in insert mode to enter temporary
normal mode

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-11-13 12:44:41 -07:00
Marshall Bowers
254ce74036 Extract ExtensionSlashCommand to assistant_slash_command crate (#20617)
This PR extracts the `ExtensionSlashCommand` implementation to the
`assistant_slash_command` crate.

The slash command related methods have been added to the `Extension`
trait. We also create separate data types for the slash command data
within the `extension` crate so that we can talk about them without
depending on the `extension_host` or `assistant_slash_command`.

Release Notes:

- N/A
2024-11-13 14:34:58 -05:00
Kyle Kelley
b913cf2e02 Allow base64 encoded images to be decoded with or without padding (#20616)
The R kernel doesn't use base64 padding whereas the Python kernel (via
matplotlib) sometimes uses padding. We have to use the `base64` crate's
`Indifferent` mode.

/cherry-pick v0.161.x

Release Notes:

- N/A
2024-11-13 11:26:42 -08:00
Antonio Scandurra
92613a8904 Use replace blocks for patches (#20605)
Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
2024-11-13 18:55:23 +01:00
Conrad Irwin
96deabfb78 Deadkeys 2 (#20612)
Re-land of #20515 with less brokenness

In particular it turns out that for control, the .characters() method
returns the control code. This mostly didn't make a difference, except
when the control code matched tab/enter/escape (for
ctrl-y,ctrl-[/ctrl-c) as we interpreted the key incorrectly.

Secondly, we were setting IME key too aggressively. This led to (in vim
mode) cmd-shift-{ being interpreted as [, so vim would wait for a second
[ before letting you change tab.

Release Notes:

- N/A
2024-11-13 10:42:08 -07:00
Peter Tripp
ad31aacb7a Fix bad quote in script/determine-release-channel (#20613) 2024-11-13 12:41:50 -05:00
Marshall Bowers
a04c2ecff7 Decouple extension Worktree resource from LspAdapterDelegate (#20611)
This PR decouples the extension `Worktree` resource from the
`LspAdapterDelegate`.

We now have a `WorktreeDelegate` trait that corresponds to the methods
on the resource.

We then create a `WorktreeDelegateAdapter` that can wrap an
`LspAdapterDelegate` and implement the `WorktreeDelegate` trait.

Release Notes:

- N/A
2024-11-13 12:24:27 -05:00
Joseph T. Lyons
f96b29ca54 v0.163.x dev 2024-11-13 11:47:52 -05:00
Danilo Leal
9d2fc691de project panel: Don't change label color even when file has errors (#20600)
With this PR, Git status is now the only thing that can change an item's
label color. So, the summary of how status colors operate in the project
panel is:

- Item icon color is, by default, never changed, _not_ affected by
either diagnostics or Git status
  - This should become configurable in the near future, though
- However, a little x or triangle icon shows up on top of the file type
icon to display diagnostics status
- Label color is _not_ affected by diagnostics but it _is_ affected by
Git status

This aims to reduce color noise and clarify/simplify how each element is
affected.

Release Notes:

- N/A
2024-11-13 13:35:20 -03:00
Marshall Bowers
b084d53f8e Extract ExtensionIndexedDocsProvider to indexed_docs crate (#20607)
This PR extracts the `ExtensionIndexedDocsProvider` implementation to
the `indexed_docs` crate.

To achieve this, we introduce a new `Extension` trait that provides an
abstracted interface for calling an extension. This trait resides in the
`extension` crate, which has minimal dependencies and can be depended on
by other crates, like `indexed_docs`.

We're then able to implement the `ExtensionIndexedDocsProvider` without
having any knowledge of the Wasm-specific internals of the extension
system.

Release Notes:

- N/A
2024-11-13 11:19:55 -05:00
Askar
7832883c74 terminal: Fix detection of ignored python venv (#20227)
Closes #19227

Since items listed in `.gitignore` file are not included in a worktree,
python virtual environment cannot be detected until venv directory is
unfolded from project panel and forcefully added into worktree. I didn't
come up with anything better than scanning fs directly. I'm not sure how
it will affect remote development. if at all.

Release Notes:

- Fixed detection of `detect_venv.directories` ignored by a worktree
2024-11-13 17:19:25 +01:00
Peter Tripp
eb4e7472e6 Improve terminal.working_directory for non-project files (#18251) 2024-11-13 10:40:36 -05:00
Thorsten Ball
27dfb48a7b project panel: Fix entries being marked when switching between tabs (#20596)
This is a follow-up (or related to) #20412.

It fixes entries being marked when navigating between tabs with `cmd-[`
and `cmd-]`.

Turns out that deep in the bowels of the project panel, we check whether
a `shift` modifier was pressed - which is the case with `cmd-[` on a US
ANSI layout - and if so mark an entry.

I think that's a left-over, because all the other code paths that
select/reveal an entry mark it explicitly too.

Release Notes:

- Fixed entries in project panel being marked when navigating between
tabs with keybinding that uses `shift` modifier.
2024-11-13 15:32:19 +01:00
Peter Tripp
3a319e6cbe docs: Improve formatter docs. Examples for C/C++ (#20553) 2024-11-13 09:24:50 -05:00
Kirill Bulatov
84e47fb80b Fix context menus not cycling over its edge when selecting items (#20592) 2024-11-13 15:14:23 +01:00
Kirill Bulatov
7e82ca8082 Always allow rerunning the tasks using the tab button (#20591)
Release Notes:

- Improved task tabs to always rerun tasks on click
2024-11-13 14:57:30 +01:00
Danilo Leal
b44078781d Standardize button design in modal footers (#20585)
- Making sure this design and properties are the same across different
places
- No need for the `ButtonLike` here, we can use `Button` as it supports
`key_binding` and makes it for a cleaner code!
- Also, that ensures the binding is always to the right of the label,
which makes more sense
- Title-case the labels for consistency with other buttons across the
app

| File finder | Project finder |
|--------|--------|
| <img width="1136" alt="Screenshot 2024-11-13 at 09 21 06"
src="https://github.com/user-attachments/assets/dd051514-d873-4b65-a08f-af0920f2c010">
| <img width="1136" alt="Screenshot 2024-11-13 at 09 21 12"
src="https://github.com/user-attachments/assets/f958e3e7-4bfb-4752-839e-2bbc01334643">
|

Release Notes:

- N/A
2024-11-13 09:36:08 -03:00
Piotr Osiewicz
3b1f12af75 chore: Cleanup dev_server_projects leftover files (#20581)
Closes #ISSUE

Release Notes:

- N/A
2024-11-13 13:04:12 +01:00
Piotr Osiewicz
b8cf0a1ed1 chore: use codegen-units=1 for small crates (#20558)
These 500ms we'll save with this change will surely pay off.

Release Notes:

- N/A
2024-11-13 12:51:30 +01:00
Kirill Bulatov
3f224274da Restore the ability to navigate into project search input with the keyboard (#20579) 2024-11-13 12:45:30 +01:00
Kirill Bulatov
56cf32cb91 Add a way to use splits when opening in file finder (#20507) 2024-11-13 11:58:42 +01:00
Markus Wüstenberg
90ffd65a10 Document use of allow_concurrent_runs with long-running tasks (#20539) 2024-11-13 09:43:17 +01:00
Max Brunsfeld
55cd99cdc4 Revert "macOS: Improve deadkeys (#20515)" (#20570)
This reverts commit https://github.com/zed-industries/zed/pull/20515

I'm reverting for now to fix issues with key bindings on Nightly:
* `ctrl-c` and `ctrl-m` are being treated as `ctrl-enter`
* `ctrl-[` isn't working in vim mode
* there's a delay before `cmd-shift-[` switches tabs w/ vim mode enabled

Release Notes:

- N/A
2024-11-12 22:32:14 -08:00
Kyle Kelley
0547748c48 Compare explicitly to YES/NO instead relying on booliness for objc (#20567)
Just x86_64-apple-darwin things. Fixes the build for nightly.

Release Notes:

- N/A
2024-11-12 18:35:58 -08:00
Kyle Kelley
54735199a4 Be more explicit about BOOL (#20564)
On x86 Macs BOOL is not the same within the `objc` crate we're using.
This only comes up on nightly builds (not on CI), so I don't think I
have a way to trigger this issue locally.

Release Notes:

- N/A
2024-11-12 18:01:32 -08:00
Kyle Kelley
466d3316a0 Fix issue with image output from Jupyter kernels that didn't use base64 padding (#20561)
This upgrades `nbformat` and `runtimelib` to handle jupyter types with
even more validation and flexiblity. This also processes any multiline
string data coming from the kernel, including with image data (like
`image/png`). While I was at it I also fixed a longstanding issue around
images by eliminating all whitespace (something `atob` does) and using
the no pad decoder.

Fixes: #17956

Before:

<img width="741" alt="image"
src="https://github.com/user-attachments/assets/37ec2cae-ce78-4475-aaa3-4d785e4015d0">

After:

<img width="727" alt="image"
src="https://github.com/user-attachments/assets/e2431ba2-048b-4205-9898-54f357795a9c">


Release Notes:

- Fixed issue with image output from REPL kernels that didn't use base64
padding
2024-11-12 16:25:49 -08:00
Marshall Bowers
3ebb64ea1d Expose context server settings to extensions (#20555)
This PR exposes context server settings to extensions.

Extensions can use `ContextServerSettings::for_project` to get the
context server settings for the current project.

The `experimental.context_servers` setting has been removed and replaced
with the `context_servers` setting (which is now an object instead of an
array).

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-11-12 17:21:58 -05:00
Nils Koch
0a9c78a58d Show error and warning indicators in project panel items (#18182)
Closes #5016

Release Notes:

- Add setting to display error and warning indicators in project panel
items.


https://github.com/user-attachments/assets/8f8031e6-ca47-42bf-a7eb-718eb1067f36

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2024-11-12 18:58:59 -03:00
Danilo Leal
a7eb3a9b9f Add "Book Onboarding" action across the app (#20503)
This PR adds a small UI touch-up to the welcome page so we can introduce
the "Book Onboarding" over there, as well as adding it to the user menu
(both in the signed in and signed out states). The actual URL these
buttons take to will still be updated to the correct destination.

<img width="700" alt="Screenshot 2024-11-12 at 12 45 27"
src="https://github.com/user-attachments/assets/9933bf94-f57a-43e2-8da3-bfbfd9fd24d0">

Release Notes:

- N/A
2024-11-12 18:41:20 -03:00
Conrad Irwin
47ca3401ce Disable ligatures in SSH connection dialogue (#20549)
Closes #20381

Release Notes:

- Fixed password dialog for remote server showing ligatures
2024-11-12 11:33:22 -07:00
Piotr Osiewicz
d1e2c6e657 python: Add pylsp as the secondary language server (#20358)
Closes #ISSUE

Release Notes:

- Added python-lsp-server as a secondary built-in language server.
2024-11-12 17:23:40 +01:00
Arthur Brussee
181c3724aa windows: Fix rust-analyzer download (#20408)
After https://github.com/rust-lang/rust-analyzer/pull/18412, there is no
longer a .gz file for windows rust-analyzer targets, and the rust
analyzer LSP fails to download. This fixes it by using the .zip version
on windows.

The .zip also extracts to a _folder_ containing rust-analyzer.exe rather
than just a file. I've handled it in this code, but am not 100% sure if
other parts of the code need too be aware of it.

Release Notes:

- N/A
2024-11-12 12:52:52 +01:00
Michael Sloan
aad3ed7f91 Fix window drawing when switching X11 workspaces by presenting when expose events occur (#20535)
Closes #18184

Release Notes:

- Fix window drawing when switching X11 workspaces, particularly for tiling window managers such as i3wm and XMonad.
2024-11-12 01:20:25 -07:00
Conrad Irwin
ad3171d16d Expose active key equivalents in the keymap context view (#20530)
Release Notes:

- Added macOS key equivalents to cmd-shift-p `debug: Open Key Context
View`
2024-11-11 23:04:15 -07:00
Conrad Irwin
e4bf586cff SSH Remoting: Improve unsupported error messages (#20529)
Updates #19697

Release Notes:

- SSH Remoting: Improved error message on unsupported OS/Arch.
2024-11-11 22:26:05 -07:00
Michael Sloan
ab20681818 Use char level diff for Rewrap action for cursor preservation + not reinserting all text (#20368)
Closes #18896

Release Notes:

- Fixed #18896 - `editor::Rewrap` now preserves cursors and only inserts
whitespace by using character-level diff instead of line-level diff.
2024-11-11 22:24:04 -07:00
ifsheldon
6819108bcb Bump rustls and async-tungstenite to remove child dep ring 0.16.20 (#20489)
Closes #18891 

Tests:
- I have cargo checked and built `remote-server` on my riscv64 dev
board.
- I have cargo checked and tested on my M1 Mac
- Almost all test passed. Some (LLM tests, Postgres tests)failed due to,
IMO, irrelevant reasons

Release Notes:

- Improved support of Zed remote server compilation for riscv64

cc @ConradIrwin 

PS: I didn't include the changes of `Cargo.lock`, which may look messy.
Probably I should include these? Or, should a reviewer do this? The
expected Cargo.lock should have only one version of `ring` with version
> 0.17.0
2024-11-11 21:29:55 -07:00
Michael Sloan
2521ef7bc4 Document why join_lines has a special case for single-line selections (#20528)
Release Notes:

- N/A
2024-11-11 21:28:26 -07:00
Michael Sloan
b08ac2ae3e Heaptrack documentation (#20266)
Release Notes:

- N/A
2024-11-11 21:03:46 -07:00
Conrad Irwin
2ea4ede08e macOS: Improve deadkeys (#20515)
Closes #19738

This change refactors how we handle input on macOS to avoid simulating
our own IME. This fixes a number of small edge-cases, and also lets us
remove a bunch of code that had been added to work around bugs in the
previous version.

Release Notes:

- On macOS: Keyboard shortcuts are now handled before activating the IME
system, this enables using vim's default mode on keyboards that use IME
menus (like Japanese).
- On macOS: Improvements to handling of dead-keys. For example when
typing `""` on a Brazillian keyboard, you now get a committed " and a
new marked ", as happens in other apps. Also, you can now type cmd-^ on
an AZERTY keyboard for indent; and ^ on a QWERTZ keyboard now goes to
the beginning of line in vim normal mode, or `d i "` no requires no
space to delete within quotes on Brazilian keyboards (though `d f "
space` is still required as `f` relies on the input handler, not a
binding).
- On macOS: In the terminal pane, holding down a key will now repeat
that key (as happens in iTerm2) instead of opening the character
selector.
2024-11-11 16:34:36 -07:00
Kirill Bulatov
38f2a919f8 Fix default project panel autofold docs (#20517)
Closes https://github.com/zed-industries/zed/issues/20511

Release Notes:

- N/A
2024-11-11 23:47:48 +01:00
Danilo Leal
82427e1ffb Add new DecoratedIcon component (#20516)
This PR creates a new, revamped `DecoratedIcon` component that enables
using different SVGs, one for the knockout background and another for
the actual icon. That's different than what we were doing before—copying
the SVG and using slightly different positioning—because we wanted to
unlock an aligned knockout effect, which was particularly hard to do
with non-simple shapes such as an X.

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <1714999+iamnbutler@users.noreply.github.com>
2024-11-11 19:09:02 -03:00
Max Brunsfeld
149e5fde36 Don't try to make project search and outline order match project panel (#20513)
A straight alphabetical order is arguably clearer, and avoids a large
initial delay when searching large repos.

Release Notes:

- Fixed a long initial delay when performing a project search in a large
repository.
2024-11-11 12:20:04 -08:00
Kyle Kelley
97b542b22a Discover available python environments with Jupyter kernel support (#20467)
![image](https://github.com/user-attachments/assets/7c042bc9-88be-4d7b-b63d-e5b555d54b18)

Closes #18291
Closes #16757
Closes #15563

Release Notes:

- Added support for kernelspecs based on python environments
2024-11-11 10:19:05 -08:00
Nate Butler
6152230152 Fix DynamicSpacing docs (#20509)
In #20504 the CustomSpacing enum variants ended up not having docs. This
PR fixes that, now docs correctly show for variants.


https://github.com/user-attachments/assets/8cc409c9-7b71-4c21-a538-3fd5dded3e00

Release Notes:

- N/A
2024-11-11 12:58:39 -05:00
Caleb Heydon
a47759fd03 Add initial FreeBSD support (#20480)
This PR adds initial support for FreeBSD
(https://github.com/zed-industries/zed/issues/15309). While there is
still work left to be done, it seems to be usable. As discussed by
@syobocat (https://github.com/zed-industries/zed/discussions/10247), the
changes were just adding ```target_os = "freebsd"``` to wherever it
checks if the OS is Linux.


![image](https://github.com/user-attachments/assets/80ea5b29-047f-4cbd-8263-42e5fa6c94b7)

Needs to be build with ```RUSTFLAGS="-C link-dead-code"```

Known Issues:
- There's an issue in ```crates/project/src/environment.rs``` where a
command fails because ```/bin/sh``` on FreeBSD doesn't support the
```-l``` option.

![image](https://github.com/user-attachments/assets/c3c38633-160f-4f47-8840-e3da67f6ebc8)
- The file/folder choosers provided by the ```ashpd``` crate don't work
on FreeBSD (at least with KDE). This isn't that bad since a fallback
dialog is used.

![image](https://github.com/user-attachments/assets/29373006-1eb9-4ed0-bd52-2d0047fab418)
 - Moving to trash won't work.
- Numerous tests fail (when running on FreeBSD). While I haven't looked
into this much, it appears that the corresponding features seem to work
fine.

Release Notes:

- Added initial support for FreeBSD
2024-11-11 18:39:05 +01:00
Michael Sloan
b5da1198f5 Refactor rewrap test to use a test utility (#20424)
Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2024-11-11 10:15:58 -07:00
Thorsten Ball
ba743a1bd9 Add setting to disable inline completions in language scopes (#20508)
This adds a setting to disable inline completions in language scopes to,
for example, disable them in comments or strings.

This setting can be made per language.

Examples:

```json
{
  "languages": {
    "Go": {
      "inline_completions_disabled_in": ["comment", "string"]
    }
  }
}
```

```json
{
  "inline_completions_disabled_in": ["comment"]
}
```

Closes #9133

Release Notes:

- Added language setting to disable inline comments in certain scopes.
Example: `{"languages": {"Go": {"inline_completions_disabled_in":
["comment", "string"]}}}`

Co-authored-by: Bennet <bennet@zed.dev>
2024-11-11 18:09:05 +01:00
Thorsten Ball
9e7afe870a tailwind: Allow configuring the rootFontSize (#20500)
This addresses this comment:
https://github.com/zed-industries/zed/pull/13923#issuecomment-2467213210

With the change in here it's now possible to use the following settings:

```json
{
  "lsp": {
    "tailwindcss-language-server": {
      "settings": {
        "rootFontSize": 50
      }
    }
  }
}
```

Closes https://github.com/zed-industries/zed/issues/10840

Release Notes:

- Added ability to configure `rootFontSize` for the
`tailwindcss-language-server`. Example settings: `{"lsp":
{"tailwindcss-language-server": {"settings": { "rootFontSize": 50}}}}`

Co-authored-by: Bennet <bennet@zed.dev>
2024-11-11 17:13:00 +01:00
Nate Butler
94d8ead270 Refactor Spacing into DynamicSpacing using proc macro (#20504)
Density tracking issue: #18078 

This PR refactors our spacing system to use a more flexible and
maintainable approach. We've replaced the static `Spacing` enum with a
dynamically generated `DynamicSpacing` enum using a proc macro.

Enum variants now use a `BaseXX` format, where XX = the pixel value @
default rem size and the default UI density.

For example:

`CustomSpacing::Base16` would return 16px at the default UI scale &
density.

I'd love to find another name other than `Base` that is clear (to avoid
base_10, etc confusion), let me know if you have any ideas!

Changes:

- Introduced a new `derive_dynamic_spacing` proc macro to generate the
`DynamicSpacing` enum
- Updated all usages of `Spacing` to use the new `DynamicSpacing`
- Removed the `custom_spacing` function, mapping previous usages to
appropriate `DynamicSpacing` variants
- Improved documentation and type safety for spacing values

New usage example:

```rust
.child(
    div()
        .flex()
        .flex_none()
        .m(DynamicSpacing::Base04.px(cx))
        .size(DynamicSpacing::Base16.rems(cx))
        .children(icon),
)
```

vs old usage example:

```
.child(
    div()
        .flex()
        .flex_none()
        .m(Spacing::Small.px(cx))
        .size(custom_spacing(px(16.)))
        .children(icon),
)
```

Release Notes:

- N/A
2024-11-11 11:08:55 -05:00
Thorsten Ball
93ab6ad922 logs: Reduce log noise by using more debug logs (#20498)
Release Notes:

- N/A
2024-11-11 15:11:20 +01:00
Piotr Osiewicz
45bbfe077a python: Fix toolchains not getting picked up after workspace deserialization (#20488)
Closes #20476

Release Notes:

- Fixed a bug in toolchain selector that caused it to not pick up venvs
for tabs before user interacted with them.
- Fixed a bug in language selector that caused it to pick up Markdown as
the language for a buffer up until the tab was interacted with.
2024-11-11 14:35:01 +01:00
Thorsten Ball
be8cc1146a language server logs: Fix missing menu entries when using remoting (#20495)
This fixes the language server log menu only showing a single entry when
using SSH remoting.

Culprit was the `return menu;` statement that should've been a
`continue;`

Rest of the change is just refactoring.

Release Notes:

- Fixed `language server logs` menu only showing a single entry when
using SSH remoting.

Co-authored-by: Bennet <bennet@zed.dev>
2024-11-11 11:26:00 +01:00
Thorsten Ball
a97ab5eb3d language servers: Fix wrong language server name (#20428)
This fixes the issue of multiple language servers showing up as `node`
in the language server logs dropdown.

It does this by changing `language_server.name()` to return the
adapter's name, not the binary name, and changing types to make sure
that we always use this.

Release Notes:

- Fixed language server names showing up only as `"node"`

---------

Co-authored-by: Sam Rose <hello@samwho.dev>
Co-authored-by: Bennet <bennet@zed.dev>
2024-11-11 10:18:38 +01:00
Kirill Bulatov
f4024cc602 Fix excerpt jumps using selections, not the match data (#20491)
Fixes
https://github.com/zed-industries/zed/pull/20469#issuecomment-2466805325

Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-11-11 10:17:32 +01:00
Maksim Bondarenkov
1460249a70 docs: Update msys2 section (#20478)
now Zed is available in MINGW64 repository as well

Release Notes:

- N/A
2024-11-10 18:24:28 +01:00
Adam Wolff
da7670cd6f assistant: Track completions for each CodegenAlternative (#19999)
Release Notes:

- N/A

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-11-10 17:22:24 +01:00
Joseph T. Lyons
ebdc255b73 Add additional instructions to issue templates (#20473)
Release Notes:

- N/A
2024-11-10 08:34:23 -05:00
Bennet Bo Fenner
4d49a851ef image viewer: Ensure images can never be loaded twice (#20472)
Follow up to #20374, this prevents a race condition where we could load
images twice.

Release Notes:

- N/A
2024-11-10 13:17:19 +01:00
Will Bradley
0dbda71423 Enable reload of images in image viewer (#20374)
Closes #11529

Release Notes:

- Fixed an issue where the image preview would not update when the
underlying file changed

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-11-10 11:37:02 +01:00
Piotr Osiewicz
f3320998a8 lsp: Track completion triggers for each language separately (#20471)
This PR improves how we handle completions in buffers with multiple
LSPs.

Context: while working on
https://github.com/zed-industries/zed/issues/19777 with @mgsloan we
noticed that completion triggers coming from language servers are not
tracked properly. Namely, each buffer has `completion_triggers` field
which is read from the configuration of a language server. The problem
is, there can be multiple language servers for a single buffer, in which
case we'd just stick to the one that was registered last.

This PR makes the tracking a bit more fine-grained. We now track not
only what the completion triggers are, but also their origin server id.
Whenever completion triggers are updated, we recreate the completion
triggers set.
Release Notes:

- Fixed completions not triggering when multiple language servers are
used for a single file.
2024-11-10 10:29:10 +01:00
Kirill Bulatov
2b7ee1e872 Debounce runnable refreshes in the editor (#20470) 2024-11-10 11:06:22 +02:00
Kirill Bulatov
767a82527a Open all kinds of files from multi buffers' headers (#20469)
Closes https://github.com/zed-industries/zed/issues/11661

Release Notes:

- Fixed multi buffer headers not able to jump to untitled files
2024-11-10 09:03:11 +01:00
Antar
ba8f027c18 editor: Fix toggle_comment in readonly mode (#20464)
Closes #20459

Release Notes:

- Fixed comments toggle in readonly mode
2024-11-10 08:47:30 +01:00
Nate Butler
0a28800049 Add preview for Checkbox with Label (#20448)
Add previews for Checkbox with Label.

Merge checkbox components.

Release Notes:

- N/A
2024-11-08 22:53:15 -05:00
Nate Butler
31a6ee0229 Add ui::table (#20447)
This PR adds the `ui::Table` component.

It has a rather simple API, but cells can contain either strings or
elements, allowing for some complex uses.

Example usage:

```rust
Table::new(vec!["Product", "Price", "Stock"])
    .width(px(600.))
    .striped()
    .row(vec!["Laptop", "$999", "In Stock"])
    .row(vec!["Phone", "$599", "Low Stock"])
    .row(vec!["Tablet", "$399", "Out of Stock"])
```

For more complex use cases, the table supports mixed content:

```rust
Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
    .width(px(840.))
    .row(vec![
        element_cell(Indicator::dot().color(Color::Success).into_any_element()),
        string_cell("Project A"),
        string_cell("High"),
        string_cell("2023-12-31"),
        element_cell(Button::new("view_a", "View").style(ButtonStyle::Filled).full_width().into_any_element()),
    ])
    // ... more rows
```

Preview:

![CleanShot 2024-11-08 at 20 53
04@2x](https://github.com/user-attachments/assets/b39122f0-a29b-423b-8e24-86ab4c42bac2)

This component is pretty basic, improvements are welcome!

Release Notes:

- N/A
2024-11-08 21:10:15 -05:00
Kyle Kelley
1f974d074e Set up editor actions after workspace not on stack (#20445)
Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-08 17:11:43 -08:00
Elias Müller
72125949d9 Add shortcuts for 'open settings' and 'revert selected hunks' to Jetbrains keymap (#20414) 2024-11-08 19:34:52 -05:00
Kirill Bulatov
d605d192af Use a different keybinding for editor::AcceptPartialInlineCompletion action (Linux) (#20443)
Follow-up of https://github.com/zed-industries/zed/pull/20419

Release Notes:

- - (breaking change) Use `ctrl-right` instead of `cmd-right` as a macOS
default for `editor::AcceptPartialInlineCompletion` (Linux)
2024-11-09 01:38:18 +02:00
Marshall Bowers
f92e6e9a95 Add support for context server extensions (#20250)
This PR adds support for context servers provided by extensions.

To provide a context server from an extension, you need to list the
context servers in your `extension.toml`:

```toml
[context_servers.my-context-server]
```

And then implement the `context_server_command` method to return the
command that will be used to start the context server:

```rs
use zed_extension_api::{self as zed, Command, ContextServerId, Result};

struct ExampleContextServerExtension;

impl zed::Extension for ExampleContextServerExtension {
    fn new() -> Self {
        ExampleContextServerExtension
    }

    fn context_server_command(&mut self, _context_server_id: &ContextServerId) -> Result<Command> {
        Ok(Command {
            command: "node".to_string(),
            args: vec!["/path/to/example-context-server/index.js".to_string()],
            env: Vec::new(),
        })
    }
}

zed::register_extension!(ExampleContextServerExtension);
```

Release Notes:

- N/A
2024-11-08 16:39:21 -05:00
Conrad Irwin
ff4f67993b macOS: Add key equivalents for non-Latin layouts (#20401)
Closes  #16343
Closes #10972

Release Notes:

- (breaking change) On macOS when using a keyboard that supports an
extended Latin character set (e.g. French, German, ...) keyboard
shortcuts are automatically updated so that they can be typed without
`option`. This fixes several long-standing problems where some keyboards
could not type some shortcuts.
- This mapping works the same way as
[macOS](https://developer.apple.com/documentation/swiftui/view/keyboardshortcut(_:modifiers:localization:)).
For example on a German keyboard shortcuts like `cmd->` become `cmd-:`,
`cmd-[` and `cmd-]` become `cmd-ö` and `cmd-ä`. This mapping happens at
the time keyboard layout files are read so the keybindings are visible
in the command palette. To opt out of this behavior for your custom
keyboard shortcuts, set `"use_layout_keys": true` in your binding
section. For the mappings used for each layout [see
here](a890df1863/crates/settings/src/key_equivalents.rs (L7)).

---------

Co-authored-by: Will <will@zed.dev>
2024-11-08 13:05:10 -07:00
Conrad Irwin
07821083df macOS: Allow non-cmd keyboard shortcuts to work on non-Latin layouts (#20336)
Updates #10972

Release Notes:

- Fixed builtin keybindings that don't require cmd on macOS, non-Latin,
ANSI layouts. For example you can now use ctrl-ա (equivalent to ctrl-a)
on an Armenian keyboard to get to the beginning of the line.

---------

Co-authored-by: Will <will@zed.dev>
2024-11-08 11:49:13 -07:00
Marshall Bowers
09c599385a Put context servers behind a trait (#20432)
This PR puts context servers behind the `ContextServer` trait to allow
us to provide context servers from an extension.

Release Notes:

- N/A
2024-11-08 13:36:41 -05:00
Richard Feldman
01503511ad Fix line number whitespace (#20427)
Closes #14025


https://github.com/user-attachments/assets/24b3f321-8246-4203-9510-66a7cf3d22f0

Release Notes:

- Fixed bug where toggling line numbers would incorrectly hide
whitespace indicators.
2024-11-08 13:25:48 -05:00
Marshall Bowers
8bc5bcf0a6 assistant: Fix completions for slash commands provided by context servers (#20423)
This PR fixes an issue introduced in #20372 that was causing slash
commands provided by context servers to not show up in the completions
menu.

Release Notes:

- N/A
2024-11-08 11:35:39 -05:00
Kirill Bulatov
983bb5c5fc Use a different keybinding for editor::AcceptPartialInlineCompletion action (#20419)
Both `editor::AcceptPartialInlineCompletion` and the keybinding for
`editor::MoveToEndOfLine` had the same keybinding inside the editor, and
with Supermaven's fast proposals, it's been very frequently used
incorrectly.

Closes #ISSUE

Release Notes:

- (breaking change) Use `ctrl-right` instead of `cmd-right` as a macOS
default for `editor::AcceptPartialInlineCompletion`
2024-11-08 18:07:38 +02:00
Thorsten Ball
653b2dc676 project panel: Stop flickering border when preview tabs disabled (#20417)
PR #20154 changed the project panel to focus the editor on click in case
preview tabs are disabled.

That lead to a flickering behavior: on mouse-down the border of the
still-selected entry in the project panel would flash, only to disappear
as soon as the entry was opened and editor focused.

This change fixes it by manually keeping track of the mouse-down state,
because we couldn't find a better solution that allows us to simply not
show the border while a "click" is going on.

Release Notes:

- Fixed project panel entries flickering the border when user clicks on
another entry to open it (when preview tabs are disabled.)

Co-authored-by: Piotr <piotr@zed.dev>
2024-11-08 16:53:39 +01:00
Joseph T. Lyons
7142d3777f Add edit events for assistant panel and inline assist (#20418)
Release Notes:

- N/A
2024-11-08 10:37:10 -05:00
Thorsten Ball
01e12c0d3c project panel: Mark entries when opening in project panel (#20412)
This addresses #17746 by marking entries when they're opened in the
project panel.

I think that was the original intention behind the code too, because it
explicitly marks entries before opening them. An event that is emitted
by the workspace reset the mark though.

So what I did was try to emulate the logic I saw in VS Code: when
opening the file, mark it, when the active entry changes, unmark it,
except if you explicitly marked a group of files.

Closes #17746

Release Notes:

- Changed project panel to mark files when opening them, which should
make it more intuitive to mark multiple files after opening a single
one.
2024-11-08 16:29:10 +01:00
Kyle Kelley
706c385c24 Register repl actions with editor after session started (#20396)
Makes repl actions that are specific to running kernels only come up
after a session has been started at least once for the editor.

Release Notes:

- Only show session oriented `repl::` actions for editors after a
session has been created
2024-11-08 06:58:44 -08:00
Thorsten Ball
edb89d8d11 project panel: Fix preview tabs not working when enabled (#20416)
PR #20154 introduced a regression and essentially disabled preview tabs
in code.

This fixes it and restores the old preview tabs behavior.

Release Notes:

- Fixed preview tabs being disabled in code, even if they were enabled
in the settings.

Co-authored-by: Piotr <piotr@zed.dev>
2024-11-08 15:46:53 +01:00
Kyle Kelley
09675d43b3 Disable repl in non-local projects (#20397)
Release Notes:

- Disable REPL buttons and actions for remote projects and collaboration
(only the host should have access).
2024-11-08 06:29:07 -08:00
Danilo Leal
187356ab9b assistant: Show only configured models in the model picker (#20392)
Closes https://github.com/zed-industries/zed/issues/16568

This PR introduces some changes to how we display models in the model
selector within the assistant panel. Basically, it comes down to this:

- If you don't have any provider configured, you should see _all_
available models in the picker
- But, once you've configured some, you should _only_ see models from
them in the picker

Visually, nothing's changed much aside from the added "Configured
Models" label at the top to ensure the understanding that that's a list
of, well, configured models only. 😬

<img width="700" alt="Screenshot 2024-11-07 at 23 42 41"
src="https://github.com/user-attachments/assets/219ed386-2318-43a6-abea-1de0cda8dc53">

Release Notes:

- Change model selector in the assistant panel to only show configured
models
2024-11-08 10:08:59 -03:00
Danilo Leal
435708b615 assistant: Fine-tune crease feedback design (#20395)
Closes https://github.com/zed-industries/zed/issues/13414

Just polishing up how some of these look. Ever since the issue was
opened, we added the "Error" label to the button, as well as
automatically popping open the toast error whenever that happens.
Lastly, there's a tooltip in there as well indicating that you can click
on it to see the details of the error.

<img width="700" alt="Screenshot 2024-11-08 at 00 26 27"
src="https://github.com/user-attachments/assets/ad0962e6-4621-4e8b-be0d-103d71fcf2e6">

Release Notes:

- N/A
2024-11-08 10:08:49 -03:00
Antonio Scandurra
2fe9cd8faa Fix regression in producing sections when converting SlashCommandOutput to event stream (#20404)
Closes #20243 

Release Notes:

- N/A
2024-11-08 09:29:14 +01:00
Conrad Irwin
8cc3ce1f17 Fix extension tests on release branches (#20307)
Co-Authored-By: Max <max@zed.dev>
cc @maxdeviant

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-11-07 22:19:36 -07:00
Conrad Irwin
6ad8c4a0f4 Restore ctrl-b/f (#20399)
Follow up from #20378

Release Notes:

- N/A
2024-11-07 22:18:28 -07:00
David Soria Parra
30a94fa59b context_servers: Fix tool/list and prompt/list (#20387)
There are two issues with too/list and prompt/list at the moment. We
serialize params to `null`, which is not correct according to
context_server spec. While it IS allowed by JSON RPC spec to omit
params, it turns out some servers currently missbehave and don't respect
this. So we do two things

- We omit params if it would be a null value in json.
- We explicitly set params to {} for tool/list and prompt/list to avoid
it being omitted.

Release Notes:

- N/A
2024-11-07 19:09:55 -08:00
Kyle Kelley
36fe364c05 Show kernel options in a picker (#20274)
Closes #18341

* [x] Remove "Change Kernel" Doc link from REPL menu
* [x] Remove chevron
* [x] Set a higher min width
* [x] Include the language along with the kernel name

Future PRs will address

* Add support for Python envs (#18291, #16757, #15563)
* Add support for Remote kernels
* Project settings support (#16898)

Release Notes:

- Added kernel picker for repl

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
2024-11-07 17:59:53 -08:00
Finn Evers
f6d4a73c34 terminal: Prevent [] from being sanitized into clickable file link (#20386)
This PR prevents `[]` from being sanitized into an empty string and thus
becoming a "valid", clickable file link in the integrated terminal.


Whenever you type `[]` into the terminal and hover over it while
pressing `cmd`, an empty popup appears and the cursor indicates that
this is a clickable element. Once you click on the brackets, the
worktree root is selected and focused within the file picker.

<img width="87" alt="grafik"
src="https://github.com/user-attachments/assets/01790323-88be-4373-a1ec-a345bcf2521e">


This is because in #2906 support was added for sanititzing file links
like `[/some/path/[slug].tsx]` to `/some/path/[slug].tsx`. In the case
`[]` where an empty string is returned from the sanitation, the string
is considered a valid file path and thus `[]` becomes a valid and
clickable navigation target.

Given that this an edge-case just for this specific one set of brackets
and otherwise no empty strings are matched from the regexes `URL_REGEX`
and `WORD_REGEX`, it seemed that this was the best place to fix this
bug.

Release Notes:

- `[]` is no longer considered a clickable link in the terminal
2024-11-08 02:41:30 +02:00
Marshall Bowers
7e7f25df6c Scope slash commands, context servers, and tools to individual Assistant Panel instances (#20372)
This PR reworks how the Assistant Panel references slash commands,
context servers, and tools.

Previously we were always reading them from the global registries, but
now we store individual collections on each Assistant Panel instance so
that there can be different ones registered for each project.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-11-07 18:23:25 -05:00
Kirill Bulatov
176314bfd2 Improve outline panel keyboard navigation (#20385)
Closes https://github.com/zed-industries/zed/issues/20187

Make outline panel more eager to open its entries:

* scroll editor to selected outline entries (before it required an extra
`"outline_panel::Open", { "change_selection": false }` action call)
* make any `Open` action call to behave like `"outline_panel::Open", {
"change_selection": true }` and remove the redundant parameter.
Now opening an entry is equal to double clicking the same entry: the
editor gets scrolled and its selection changes
* add a way to open entries the same way as excerpts are open in multi
buffers (will open the entire file, scroll and place the caret)

* additionally, fix another race issue that caused wrong entry to be
revealed after the selection change

Release Notes:

- Improved outline panel keyboard navigation
2024-11-08 01:19:54 +02:00
Peter Tripp
6606e6e37f ci: No GitHub Actions stale check on forks (#20382) 2024-11-07 16:11:18 -05:00
dhaus67
c350321318 Update Copilot Chat max_tokens soft limits (#20363)
Closes #20362 

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-07 16:03:12 -05:00
Conrad Irwin
9da040da3f Stop using alt- shortcuts (#20378)
Closes #7688

Release Notes:

- (breaking change) Stop binding keyboard shortcuts to alt-[a-z]. These
get in the way of typing characters. This is usually not an issue for
English speakers because we don't use many characters; but for other
Latin-based languages with diacritics our shortcuts prevent them typing
what they need to type.

This primarily affects Zed's extra features:
* `alt-q` => `cmd-k q` on maOS, `ctrl-k q` on Linux for `editor::Rewrap`
* `alt-z` => `cmd-k z` on macOS `ctrl-k z` on Linux for
`editor::ToggleSoftWrap`
  * `alt-m` => `ctrl-shift-m` for `assistant::ToggleModelSelector`
* `alt-v` => `ctrl-shift-v` for `["editor::MovePageUp", {
"center_cursor": true }]` (macOS only)
* `alt-t` => `cmd-shift-r` on maOS, `ctrl-shift-r` on Linux for
`task::Spawn` (The previous binding for `editor::RevealInFileManager`
now only applies in the project panel)
* `alt-shift-t` => `alt-cmd-r` on maOS, `ctrl-alt-r` on Linux for
`task::Rerun`
* `alt-shift-f` => `ctrl-shift-f` for
`project_panel::SearchInDirectory`.
 
But also overrides some bindings from Readline.
  * `alt-h` => `alt-backspace` for `editor::DeleteToPreviousWordStart`
  * `alt-d` => `alt-delete` for `editor::DeleteToNextWordEnd`
* `alt-f` => `ctrl-f` for `editor:: MoveToNextWordEnd` (`ctrl-f` was
previously `editor::MoveRight`)
* `alt-b` => `ctrl-b` for `editor::MoveToNextWordStart` (`ctrl-b` was
previously `editor::MoveLeft`)

Note that `alt-t` and `alt-shift-t` have been kept as aliases (because
no-one complained about `t` yet; but we may remove them completely in
the future).
2024-11-07 13:52:13 -07:00
Max Brunsfeld
f6385221c5 Add abstract classes to typescript outline query (#20377)
Closes #4553

Release Notes:

- Fixed a bug where abstract classes weren't shown correctly in the
outline view when editing Typescript code.
2024-11-07 12:44:34 -08:00
Douglas Ronne
999853fee0 Add emacs keymap (#19605)
Release Notes:

- Added Emacs (beta) base keymap
2024-11-07 15:22:53 -05:00
Mathias
f924c3ef00 Fix folder expand when dropped on itself in project_panel (#20365)
Closes #13093

Release Notes:

- Fixed folder expand when dropped on itself in project_panel
2024-11-07 20:03:55 +01:00
Michael Sloan
2c4984091c Revert "Use correct context path for focused element in WindowContext::bindings_for_action (#18843)" (#20367)
@JosephTLyons found that this broke display of keybindings in the recent
projects modal.

Release Notes:

- N/A
2024-11-07 09:45:23 -07:00
Richard Feldman
453c41205b Show warning when deleting files with unsaved changes (#20172)
Closes #9905

<img width="172" alt="Screenshot 2024-11-04 at 10 16 34 AM"
src="https://github.com/user-attachments/assets/5fa84e06-bcb9-471d-adab-e06881fbd3ca">
<img width="172" alt="Screenshot 2024-11-04 at 10 16 22 AM"
src="https://github.com/user-attachments/assets/d7def162-e910-4061-a160-6178c9d344e5">
<img width="172" alt="Screenshot 2024-11-04 at 10 17 17 AM"
src="https://github.com/user-attachments/assets/43c7e4fe-1b71-4786-bc05-44f34ed15dc5">
<img width="172" alt="Screenshot 2024-11-04 at 10 17 09 AM"
src="https://github.com/user-attachments/assets/17263782-c706-44b2-acbc-c3d2d14c20ac">


Release Notes:

- When deleting or trashing files, the confirmation prompt now warns if
files have unsaved changes.
2024-11-07 11:40:33 -05:00
Michael Sloan
e85ab077be Add missing field to impl Hash for RenderGlyphParams (#20316)
Unlikely to cause any behavior change but seems more correct to include
`is_emoji` in the hash.

Release Notes:

- N/A
2024-11-07 09:34:54 -07:00
Will Bradley
daa35e98f1 Enable look-around in Project Search using fancy-regex crate (#20308)
Closes #13486 

Release Notes:

- Added support for look-around in project search

Co-authored-by: Max <max@zed.dev>
2024-11-07 09:18:09 -07:00
Will Bradley
de70852497 Fix a test flake involving zeroed out group_intervals (#20328)
Release Notes:

- N/A
2024-11-07 09:17:43 -07:00
Thorsten Ball
454c9dc06d project panel: Add RemoveFromProject action (#20360)
This fixes #15995 by adding a `project panel: remove from project`
action that can be used in a keybinding.

Release Notes:

- Added a `project panel: remove from project` action so that users can
now add a keybinding to trigger it: `project_panel::RemoveFromProject`.
2024-11-07 16:15:09 +01:00
435 changed files with 15237 additions and 8020 deletions

View File

@@ -19,7 +19,7 @@ body:
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
validations:
required: true
- type: textarea

View File

@@ -21,7 +21,7 @@ body:
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
validations:
required: true
- type: textarea

View File

@@ -20,7 +20,7 @@ body:
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
validations:
required: true
- type: textarea

View File

@@ -244,6 +244,7 @@ jobs:
#
# 25 was chosen arbitrarily.
fetch-depth: 25
fetch-tags: true
clean: false
- name: Limit target directory size
@@ -261,6 +262,9 @@ jobs:
mkdir -p target/
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
script/create-draft-release target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate license file
run: script/generate-licenses
@@ -306,7 +310,6 @@ jobs:
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
target/release/Zed.dmg
body_path: target/release-notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -353,7 +356,6 @@ jobs:
files: |
target/zed-remote-server-linux-x86_64.gz
target/release/zed-linux-x86_64.tar.gz
body: ""
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -400,6 +402,5 @@ jobs:
files: |
target/zed-remote-server-linux-aarch64.gz
target/release/zed-linux-aarch64.tar.gz
body: ""
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -6,6 +6,7 @@ on:
jobs:
stale:
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9

21
.github/workflows/script_checks.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Script
on:
pull_request:
paths:
- "script/**"
push:
branches:
- main
jobs:
shellcheck:
name: "ShellCheck Scripts"
if: github.repository_owner == 'zed-industries'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Shellcheck ./scripts
run: |
./script/shellcheck-scripts error

View File

@@ -22,10 +22,14 @@ Antonio Scandurra <me@as-cii.com> <antonio@zed.dev>
Bennet Bo Fenner <bennet@zed.dev>
Bennet Bo Fenner <bennet@zed.dev> <53836821+bennetbo@users.noreply.github.com>
Bennet Bo Fenner <bennet@zed.dev> <bennetbo@gmx.de>
Boris Cherny <boris@anthropic.com>
Boris Cherny <boris@anthropic.com> <boris@performancejs.com>
Chris Hayes <chris+git@hayes.software>
Christian Bergschneider <christian.bergschneider@gmx.de>
Christian Bergschneider <christian.bergschneider@gmx.de> <magiclake@gmx.de>
Conrad Irwin <conrad@zed.dev>
Conrad Irwin <conrad@zed.dev> <conrad.irwin@gmail.com>
Dairon Medina <dairon.medina@gmail.com>
Danilo Leal <danilo@zed.dev>
Danilo Leal <danilo@zed.dev> <67129314+danilo-leal@users.noreply.github.com>
Evren Sen <nervenes@icloud.com>
@@ -35,6 +39,7 @@ Fernando Tagawa <tagawafernando@gmail.com>
Fernando Tagawa <tagawafernando@gmail.com> <fernando.tagawa.gamail.com@gmail.com>
Greg Morenz <greg-morenz@droid.cafe>
Greg Morenz <greg-morenz@droid.cafe> <morenzg@gmail.com>
Ihnat Aŭtuška <autushka.ihnat@gmail.com>
Ivan Žužak <izuzak@gmail.com>
Ivan Žužak <izuzak@gmail.com> <ivan.zuzak@github.com>
Joseph T. Lyons <JosephTLyons@gmail.com>
@@ -61,10 +66,13 @@ Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
Max Linke <maxlinke88@gmail.com>
Max Linke <maxlinke88@gmail.com> <kain88-de@users.noreply.github.com>
Michael Sloan <michael@zed.dev>
Michael Sloan <michael@zed.dev> <mgsloan@gmail.com>
Michael Sloan <michael@zed.dev> <mgsloan@google.com>
Mikayla Maki <mikayla@zed.dev>
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>
Muhammad Talal Anwar <mail@talal.io>
Muhammad Talal Anwar <mail@talal.io> <talalanwar@outlook.com>
Nate Butler <iamnbutler@gmail.com>
Nate Butler <iamnbutler@gmail.com> <nate@zed.dev>
Nathan Sobo <nathan@zed.dev>
@@ -88,7 +96,11 @@ Robert Clover <git@clo4.net>
Robert Clover <git@clo4.net> <robert@clover.gdn>
Roy Williams <roy.williams.iii@gmail.com>
Roy Williams <roy.williams.iii@gmail.com> <roy@anthropic.com>
Sebastijan Kelnerič <sebastijan.kelneric@sebba.dev>
Sebastijan Kelnerič <sebastijan.kelneric@sebba.dev> <sebastijan.kelneric@vichava.com>
Sergey Onufrienko <sergey@onufrienko.com>
Shish <webmaster@shishnet.org>
Shish <webmaster@shishnet.org> <shish@shishnet.org>
Thorben Kröger <dev@thorben.net>
Thorben Kröger <dev@thorben.net> <thorben.kroeger@hexagon.com>
Thorsten Ball <thorsten@zed.dev>

1045
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -148,7 +148,6 @@ members = [
"extensions/haskell",
"extensions/html",
"extensions/lua",
"extensions/ocaml",
"extensions/php",
"extensions/perplexity",
"extensions/prisma",
@@ -323,7 +322,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8
async-recursion = "1.0.0"
async-tar = "0.5.0"
async-trait = "0.1"
async-tungstenite = "0.24"
async-tungstenite = "0.28"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
@@ -339,6 +338,7 @@ chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
clickhouse = "0.11.6"
cocoa = "0.26"
cocoa-foundation = "0.2.0"
convert_case = "0.6.0"
core-foundation = "0.9.3"
core-foundation-sys = "0.8.6"
@@ -350,6 +350,7 @@ ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
exec = "0.3.1"
fancy-regex = "0.14.0"
fork = "0.2.0"
futures = "0.3"
futures-batch = "0.6.1"
@@ -367,12 +368,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "2"
itertools = "0.13.0"
jsonwebtoken = "9.3"
jupyter-protocol = { version = "0.2.0" }
jupyter-websocket-client = { version = "0.4.1" }
libc = "0.2"
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = "0.3.2"
nbformat = "0.6.0"
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
@@ -387,7 +390,7 @@ pet-core = { git = "https://github.com/microsoft/python-environment-tools.git",
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
profiling = "1"
prost = "0.9"
prost-build = "0.9"
@@ -406,12 +409,12 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.16.1", default-features = false, features = [
runtimelib = { version = "0.21.0", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustls = "0.20.3"
rustls = "0.21.12"
rustls-native-certs = "0.8.0"
schemars = { version = "0.8", features = ["impl_json_schema"] }
semver = "1.0"
@@ -560,6 +563,46 @@ rustybuzz = { opt-level = 3 }
ttf-parser = { opt-level = 3 }
wasmtime-cranelift = { opt-level = 3 }
wasmtime = { opt-level = 3 }
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
activity_indicator = { codegen-units = 1 }
assets = { codegen-units = 1 }
breadcrumbs = { codegen-units = 1 }
collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 }
evals = { codegen-units = 1 }
extension_cli = { codegen-units = 1 }
feature_flags = { codegen-units = 1 }
file_icons = { codegen-units = 1 }
fsevent = { codegen-units = 1 }
image_viewer = { codegen-units = 1 }
inline_completion_button = { codegen-units = 1 }
install_cli = { codegen-units = 1 }
journal = { codegen-units = 1 }
menu = { codegen-units = 1 }
notifications = { codegen-units = 1 }
ollama = { codegen-units = 1 }
outline = { codegen-units = 1 }
paths = { codegen-units = 1 }
prettier = { codegen-units = 1 }
project_symbols = { codegen-units = 1 }
refineable = { codegen-units = 1 }
release_channel = { codegen-units = 1 }
reqwest_client = { codegen-units = 1 }
rich_text = { codegen-units = 1 }
semantic_version = { codegen-units = 1 }
session = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }
sqlez_macros = { codegen-units = 1 }
story = { codegen-units = 1 }
supermaven_api = { codegen-units = 1 }
telemetry_events = { codegen-units = 1 }
theme_selector = { codegen-units = 1 }
time_format = { codegen-units = 1 }
ui_input = { codegen-units = 1 }
vcs_menu = { codegen-units = 1 }
zed_actions = { codegen-units = 1 }
[profile.release]
debug = "limited"

1
assets/icons/blocks.svg Normal file
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-blocks"><rect width="7" height="7" x="14" y="3" rx="1"/><path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/></svg>

After

Width:  |  Height:  |  Size: 368 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4662 14.9152C13.5801 15.0291 13.7648 15.0291 13.8787 14.9152L14.9145 13.8793C15.0284 13.7654 15.0284 13.5807 14.9145 13.4667L12.9483 11.5004L14.9145 9.53392C15.0285 9.42004 15.0285 9.23533 14.9145 9.12137L13.8787 8.08547C13.7648 7.97154 13.5801 7.97154 13.4662 8.08547L11.5 10.0519L9.53376 8.08545C9.41988 7.97152 9.23517 7.97152 9.12124 8.08545L8.08543 9.12136C7.97152 9.23533 7.97152 9.42004 8.08543 9.53392L10.0517 11.5004L8.08545 13.4667C7.97155 13.5807 7.97155 13.7654 8.08545 13.8793L9.12126 14.9152C9.23517 15.0292 9.41988 15.0292 9.53376 14.9152L11.5 12.9489L13.4662 14.9152Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 756 B

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-keyboard"><path d="M10 8h.01"/><path d="M12 12h.01"/><path d="M14 8h.01"/><path d="M16 12h.01"/><path d="M18 8h.01"/><path d="M6 8h.01"/><path d="M7 16h10"/><path d="M8 12h.01"/><rect width="20" height="16" x="2" y="4" rx="2"/></svg>

After

Width:  |  Height:  |  Size: 436 B

View File

@@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.1" y="2.1" width="6.8" height="6.8" rx="3.4" stroke="black" stroke-width="1.8"/>
</svg>

After

Width:  |  Height:  |  Size: 195 B

View File

@@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="3" width="5" height="5" rx="2.5" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 166 B

View File

@@ -0,0 +1,10 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2080_948)">
<path d="M1.52492 8.72287L1.04243 9.55H2H9H9.95757L9.47508 8.72287L5.97508 2.72287L5.5 1.90845L5.02492 2.72287L1.52492 8.72287Z" stroke="black" stroke-width="1.1"/>
</g>
<defs>
<clipPath id="clip0_2080_948">
<rect width="11" height="11" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 9H9L5.5 3L2 9Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 146 B

View File

@@ -0,0 +1,10 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2050_903)">
<path d="M1.83327 2.89393L1.19687 3.53033L1.83327 4.16672L3.16654 5.5L1.83327 6.83327L1.19687 7.46967L1.83327 8.10606L2.89393 9.16672L3.53033 9.80312L4.16672 9.16672L5.5 7.83345L6.83327 9.16672L7.46967 9.80312L8.10606 9.16672L9.16672 8.10606L9.80312 7.46967L9.16672 6.83327L7.83345 5.5L9.16672 4.16672L9.80312 3.53033L9.16672 2.89393L8.10606 1.83327L7.46967 1.19687L6.83327 1.83327L5.5 3.16654L4.16672 1.83327L3.53033 1.19687L2.89393 1.83327L1.83327 2.89393Z" stroke="black" stroke-width="1.8"/>
</g>
<defs>
<clipPath id="clip0_2050_903">
<rect width="11" height="11" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 743 B

View File

@@ -0,0 +1,3 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3L5.5 5.5M8 8L5.5 5.5M5.5 5.5L3 8M5.5 5.5L8 3" stroke="black" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 198 B

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-phone-incoming"><polyline points="16 2 16 8 22 8"/><line x1="22" x2="16" y1="2" y2="8"/><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>

After

Width:  |  Height:  |  Size: 594 B

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-swatch-book"><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>

After

Width:  |  Height:  |  Size: 456 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 4L4 12H12L8 4Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 155 B

3
assets/icons/x.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@@ -56,7 +56,8 @@
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose",
"alt-q": "editor::Rewrap",
"ctrl-k ctrl-q": "editor::Rewrap",
"ctrl-k q": "editor::Rewrap",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
"shift-delete": "editor::Cut",
@@ -126,7 +127,8 @@
"shift-enter": "editor::Newline",
"ctrl-enter": "editor::NewlineAbove",
"ctrl-shift-enter": "editor::NewlineBelow",
"alt-z": "editor::ToggleSoftWrap",
"ctrl-k ctrl-z": "editor::ToggleSoftWrap",
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
@@ -140,7 +142,7 @@
"bindings": {
"alt-]": "editor::NextInlineCompletion",
"alt-[": "editor::PreviousInlineCompletion",
"ctrl-right": "editor::AcceptPartialInlineCompletion"
"alt-right": "editor::AcceptPartialInlineCompletion"
}
},
{
@@ -169,7 +171,7 @@
"ctrl-k c": "assistant::CopyCode",
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPrevMatch",
"alt-m": "assistant::ToggleModelSelector",
"ctrl-shift-m": "assistant::ToggleModelSelector",
"ctrl-k h": "assistant::DeployHistory",
"ctrl-k l": "assistant::DeployPromptLibrary",
"ctrl-n": "assistant::NewContext"
@@ -249,6 +251,8 @@
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"back": "pane::GoBack",
"forward": "pane::GoForward",
"ctrl-w": "pane::CloseActiveItem",
"ctrl-f4": "pane::CloseActiveItem",
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
@@ -326,7 +330,6 @@
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"alt-ctrl-r": "editor::RevealInFileManager",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
"ctrl-\\": "pane::SplitRight",
@@ -417,6 +420,8 @@
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-shift-x": "zed::Extensions",
"ctrl-shift-r": "task::Rerun",
"ctrl-alt-r": "task::Rerun",
"alt-t": "task::Rerun",
"alt-shift-t": "task::Spawn"
}
@@ -564,9 +569,11 @@
"ctrl-alt-c": "outline_panel::CopyPath",
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
"alt-ctrl-r": "outline_panel::RevealInFileManager",
"space": ["outline_panel::Open", { "change_selection": false }],
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev"
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -587,7 +594,7 @@
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"escape": "menu::Cancel"
@@ -643,8 +650,24 @@
}
},
{
"context": "FileFinder",
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
"context": "FileFinder && !menu_open",
"bindings": {
"ctrl-shift-p": "file_finder::SelectPrev",
"ctrl": "file_finder::OpenMenu",
"ctrl-j": "pane::SplitDown",
"ctrl-k": "pane::SplitUp",
"ctrl-h": "pane::SplitLeft",
"ctrl-l": "pane::SplitRight"
}
},
{
"context": "FileFinder && menu_open",
"bindings": {
"j": "pane::SplitDown",
"k": "pane::SplitUp",
"h": "pane::SplitLeft",
"l": "pane::SplitRight"
}
},
{
"context": "TabSwitcher",

View File

@@ -51,14 +51,13 @@
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-t": "editor::Transpose",
"alt-q": "editor::Rewrap",
"cmd-k q": "editor::Rewrap",
"cmd-k cmd-q": "editor::Rewrap",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-delete": "editor::DeleteToEndOfLine",
"alt-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-w": "editor::DeleteToPreviousWordStart",
"alt-delete": "editor::DeleteToNextWordEnd",
"alt-h": "editor::DeleteToPreviousWordStart",
"alt-d": "editor::DeleteToNextWordEnd",
"cmd-x": "editor::Cut",
"cmd-c": "editor::Copy",
"cmd-v": "editor::Paste",
@@ -86,9 +85,7 @@
"ctrl-f": "editor::MoveRight",
"ctrl-l": "editor::ScrollCursorCenter",
"alt-left": "editor::MoveToPreviousWordStart",
"alt-b": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd",
"alt-f": "editor::MoveToNextWordEnd",
"cmd-left": "editor::MoveToBeginningOfLine",
"ctrl-a": "editor::MoveToBeginningOfLine",
"cmd-right": "editor::MoveToEndOfLine",
@@ -104,9 +101,7 @@
"shift-right": "editor::SelectRight",
"ctrl-shift-f": "editor::SelectRight",
"alt-shift-left": "editor::SelectToPreviousWordStart", // cursorWordLeftSelect
"alt-shift-b": "editor::SelectToPreviousWordStart",
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
"alt-shift-f": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
"cmd-shift-up": "editor::SelectToBeginning",
@@ -121,7 +116,7 @@
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"ctrl-v": ["editor::MovePageDown", { "center_cursor": true }],
"alt-v": ["editor::MovePageUp", { "center_cursor": true }],
"ctrl-shift-v": ["editor::MovePageUp", { "center_cursor": true }],
"ctrl-cmd-space": "editor::ShowCharacterPalette",
"cmd-;": "editor::ToggleLineNumbers",
"cmd-alt-z": "editor::RevertSelectedHunks",
@@ -140,7 +135,7 @@
"shift-enter": "editor::Newline",
"cmd-enter": "editor::NewlineBelow",
"cmd-shift-enter": "editor::NewlineAbove",
"alt-z": "editor::ToggleSoftWrap",
"cmd-k z": "editor::ToggleSoftWrap",
"cmd-f": "buffer_search::Deploy",
"cmd-alt-f": ["buffer_search::Deploy", { "replace_enabled": true }],
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
@@ -155,7 +150,7 @@
"bindings": {
"alt-]": "editor::NextInlineCompletion",
"alt-[": "editor::PreviousInlineCompletion",
"cmd-right": "editor::AcceptPartialInlineCompletion"
"ctrl-right": "editor::AcceptPartialInlineCompletion"
}
},
{
@@ -191,7 +186,7 @@
"cmd-k c": "assistant::CopyCode",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch",
"alt-m": "assistant::ToggleModelSelector",
"cmd-shift-m": "assistant::ToggleModelSelector",
"cmd-k h": "assistant::DeployHistory",
"cmd-k l": "assistant::DeployPromptLibrary",
"cmd-n": "assistant::NewContext"
@@ -364,7 +359,6 @@
"cmd-k cmd-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"cmd-.": "editor::ToggleCodeActions",
"alt-cmd-r": "editor::RevealInFileManager",
"cmd-k r": "editor::RevealInFileManager",
"cmd-k p": "editor::CopyPath",
"cmd-\\": "pane::SplitRight",
@@ -457,7 +451,9 @@
{
"context": "Workspace && !Terminal",
"bindings": {
"alt-t": "task::Rerun",
"cmd-shift-r": "task::Spawn",
"cmd-alt-r": "task::Rerun",
"alt-t": "task::Spawn",
"alt-shift-t": "task::Spawn"
}
},
@@ -577,9 +573,11 @@
"cmd-alt-c": "outline_panel::CopyPath",
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
"alt-cmd-r": "outline_panel::RevealInFileManager",
"space": ["outline_panel::Open", { "change_selection": false }],
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev"
"shift-up": "menu::SelectPrev",
"alt-enter": "editor::OpenExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit"
}
},
{
@@ -604,7 +602,7 @@
"alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-shift-f": "project_panel::NewSearchInDirectory",
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
"escape": "menu::Cancel"
@@ -650,8 +648,24 @@
}
},
{
"context": "FileFinder",
"bindings": { "cmd-shift-p": "file_finder::SelectPrev" }
"context": "FileFinder && !menu_open",
"bindings": {
"cmd-shift-p": "file_finder::SelectPrev",
"cmd": "file_finder::OpenMenu",
"cmd-j": "pane::SplitDown",
"cmd-k": "pane::SplitUp",
"cmd-h": "pane::SplitLeft",
"cmd-l": "pane::SplitRight"
}
},
{
"context": "FileFinder && menu_open",
"bindings": {
"j": "pane::SplitDown",
"k": "pane::SplitUp",
"h": "pane::SplitLeft",
"l": "pane::SplitRight"
}
},
{
"context": "TabSwitcher",

63
assets/keymaps/linux/emacs.json Executable file
View File

@@ -0,0 +1,63 @@
// documentation: https://zed.dev/docs/key-bindings
//
// To see the default key bindings run `zed: open default keymap`
// from the command palette.
[
{
"context": "Editor",
"bindings": {
"ctrl-g": "editor::Cancel",
"ctrl-shift-g": "go_to_line::Toggle",
//"ctrl-space": "editor::SetMark",
"ctrl-x u": "editor::Undo",
"ctrl-x ctrl-u": "editor::Redo",
"ctrl-f": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown",
"ctrl-p": "editor::MoveUp",
"ctrl-a": "editor::MoveToBeginningOfLine",
"ctrl-e": "editor::MoveToEndOfLine",
"alt-f": "editor::MoveToNextSubwordEnd",
"alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-d": "editor::Delete",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-w": "editor::Cut",
"alt-w": "editor::Copy",
"ctrl-y": "editor::Paste",
"ctrl-_": "editor::Undo",
"ctrl-v": "editor::MovePageDown",
"alt-v": "editor::MovePageUp",
"ctrl-x ]": "editor::MoveToEnd",
"ctrl-x [": "editor::MoveToBeginning",
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
"ctrl-s": "buffer_search::Deploy",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-shift-r": "editor::Rename"
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-x k": "pane::CloseActiveItem",
"ctrl-x ctrl-c": "workspace::CloseWindow",
"ctrl-x o": "workspace::ActivateNextPane",
"ctrl-x b": "tab_switcher::Toggle",
"ctrl-x 0": "pane::CloseActiveItem",
"ctrl-x 1": "pane::CloseInactiveItems",
"ctrl-x 2": "pane::SplitVertical",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-x ctrl-s": "workspace::Save",
"ctrl-x ctrl-w": "workspace::SaveAs",
"ctrl-x s": "workspace::SaveAll",
"shift shift": "file_finder::Toggle"
}
},
{
"context": "Pane",
"bindings": {
"ctrl-alt-left": "pane::GoBack",
"ctrl-alt-right": "pane::GoForward"
}
}
]

View File

@@ -1,6 +1,7 @@
[
{
"bindings": {
"ctrl-alt-s": "zed::OpenSettings",
"ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-shift-]": "pane::ActivateNextItem"
}
@@ -43,6 +44,7 @@
"shift-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPrevHunk",
"ctrl-alt-z": "editor::RevertSelectedHunks",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"ctrl-shift-home": "editor::SelectToBeginning",

View File

@@ -4,9 +4,7 @@
"ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-shift-]": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePrevItem"
"ctrl-pagedown": "pane::ActivateNextItem"
}
},
{
@@ -18,6 +16,7 @@
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
"ctrl-shift-d": "editor::DuplicateLineDown",
"alt-f3": "editor::SelectAllMatches", // find_all_under
"f12": "editor::GoToDefinition",
"ctrl-f12": "editor::GoToDefinitionSplit",
"shift-f12": "editor::FindAllReferences",

63
assets/keymaps/macos/emacs.json Executable file
View File

@@ -0,0 +1,63 @@
// documentation: https://zed.dev/docs/key-bindings
//
// To see the default key bindings run `zed: open default keymap`
// from the command palette.
[
{
"context": "Editor",
"bindings": {
"ctrl-g": "editor::Cancel",
"ctrl-shift-g": "go_to_line::Toggle",
//"ctrl-space": "editor::SetMark",
"ctrl-x u": "editor::Undo",
"ctrl-x ctrl-u": "editor::Redo",
"ctrl-f": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown",
"ctrl-p": "editor::MoveUp",
"ctrl-a": "editor::MoveToBeginningOfLine",
"ctrl-e": "editor::MoveToEndOfLine",
"alt-f": "editor::MoveToNextSubwordEnd",
"alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-d": "editor::Delete",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-w": "editor::Cut",
"alt-w": "editor::Copy",
"ctrl-y": "editor::Paste",
"ctrl-_": "editor::Undo",
"ctrl-v": "editor::MovePageDown",
"alt-v": "editor::MovePageUp",
"ctrl-x ]": "editor::MoveToEnd",
"ctrl-x [": "editor::MoveToBeginning",
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
"ctrl-s": "buffer_search::Deploy",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-shift-r": "editor::Rename"
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-x k": "pane::CloseActiveItem",
"ctrl-x ctrl-c": "workspace::CloseWindow",
"ctrl-x o": "workspace::ActivateNextPane",
"ctrl-x b": "tab_switcher::Toggle",
"ctrl-x 0": "pane::CloseActiveItem",
"ctrl-x 1": "pane::CloseInactiveItems",
"ctrl-x 2": "pane::SplitVertical",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-x ctrl-s": "workspace::Save",
"ctrl-x ctrl-w": "workspace::SaveAs",
"ctrl-x s": "workspace::SaveAll",
"shift shift": "file_finder::Toggle"
}
},
{
"context": "Pane",
"bindings": {
"ctrl-alt-left": "pane::GoBack",
"ctrl-alt-right": "pane::GoForward"
}
}
]

View File

@@ -4,9 +4,7 @@
"cmd-shift-[": "pane::ActivatePrevItem",
"cmd-shift-]": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivatePrevItem"
"ctrl-pagedown": "pane::ActivateNextItem"
}
},
{
@@ -21,6 +19,7 @@
"cmd-shift-l": "editor::SplitSelectionIntoLines",
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
"cmd-shift-d": "editor::DuplicateLineDown",
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
"shift-f12": "editor::FindAllReferences",
"alt-cmd-down": "editor::GoToDefinition",
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",

View File

@@ -1,6 +1,7 @@
[
{
"context": "VimControl && !menu",
"use_layout_keys": true,
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
@@ -171,6 +172,7 @@
},
{
"context": "vim_mode == normal",
"use_layout_keys": true,
"bindings": {
"escape": "editor::Cancel",
"ctrl-[": "editor::Cancel",
@@ -224,6 +226,7 @@
},
{
"context": "VimControl && VimCount",
"use_layout_keys": true,
"bindings": {
"0": ["vim::Number", 0],
":": "vim::CountCommand"
@@ -231,6 +234,7 @@
},
{
"context": "vim_mode == visual",
"use_layout_keys": true,
"bindings": {
":": "vim::VisualCommand",
"u": "vim::ConvertToLowerCase",
@@ -279,6 +283,7 @@
},
{
"context": "vim_mode == insert",
"use_layout_keys": true,
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
@@ -299,11 +304,13 @@
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-r": ["vim::PushOperator", "Register"],
"insert": "vim::ToggleReplace"
"insert": "vim::ToggleReplace",
"ctrl-o": "vim::TemporaryNormal"
}
},
{
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
"use_layout_keys": true,
"bindings": {
"ctrl-p": "editor::ShowCompletions",
"ctrl-n": "editor::ShowCompletions"
@@ -311,6 +318,7 @@
},
{
"context": "vim_mode == replace",
"use_layout_keys": true,
"bindings": {
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
@@ -328,6 +336,7 @@
},
{
"context": "vim_mode == waiting",
"use_layout_keys": true,
"bindings": {
"tab": "vim::Tab",
"enter": "vim::Enter",
@@ -341,6 +350,7 @@
},
{
"context": "vim_mode == operator",
"use_layout_keys": true,
"bindings": {
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
@@ -349,6 +359,7 @@
},
{
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
"use_layout_keys": true,
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
@@ -376,6 +387,7 @@
},
{
"context": "vim_operator == c",
"use_layout_keys": true,
"bindings": {
"c": "vim::CurrentLine",
"d": "editor::Rename", // zed specific
@@ -384,6 +396,7 @@
},
{
"context": "vim_operator == d",
"use_layout_keys": true,
"bindings": {
"d": "vim::CurrentLine",
"s": ["vim::PushOperator", "DeleteSurrounds"],
@@ -393,6 +406,7 @@
},
{
"context": "vim_operator == gu",
"use_layout_keys": true,
"bindings": {
"g u": "vim::CurrentLine",
"u": "vim::CurrentLine"
@@ -400,6 +414,7 @@
},
{
"context": "vim_operator == gU",
"use_layout_keys": true,
"bindings": {
"g shift-u": "vim::CurrentLine",
"shift-u": "vim::CurrentLine"
@@ -407,6 +422,7 @@
},
{
"context": "vim_operator == g~",
"use_layout_keys": true,
"bindings": {
"g ~": "vim::CurrentLine",
"~": "vim::CurrentLine"
@@ -414,6 +430,7 @@
},
{
"context": "vim_operator == gq",
"use_layout_keys": true,
"bindings": {
"g q": "vim::CurrentLine",
"q": "vim::CurrentLine",
@@ -423,6 +440,7 @@
},
{
"context": "vim_operator == y",
"use_layout_keys": true,
"bindings": {
"y": "vim::CurrentLine",
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
@@ -430,30 +448,35 @@
},
{
"context": "vim_operator == ys",
"use_layout_keys": true,
"bindings": {
"s": "vim::CurrentLine"
}
},
{
"context": "vim_operator == >",
"use_layout_keys": true,
"bindings": {
">": "vim::CurrentLine"
}
},
{
"context": "vim_operator == <",
"use_layout_keys": true,
"bindings": {
"<": "vim::CurrentLine"
}
},
{
"context": "vim_operator == gc",
"use_layout_keys": true,
"bindings": {
"c": "vim::CurrentLine"
}
},
{
"context": "vim_mode == literal",
"use_layout_keys": true,
"bindings": {
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
@@ -497,6 +520,7 @@
},
{
"context": "BufferSearchBar && !in_replace",
"use_layout_keys": true,
"bindings": {
"enter": "vim::SearchSubmit",
"escape": "buffer_search::Dismiss"
@@ -504,6 +528,7 @@
},
{
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"use_layout_keys": true,
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
@@ -554,6 +579,7 @@
},
{
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"use_layout_keys": true,
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
@@ -562,6 +588,7 @@
{
// netrw compatibility
"context": "ProjectPanel && not_editing",
"use_layout_keys": true,
"bindings": {
":": "command_palette::Toggle",
"%": "project_panel::NewFile",
@@ -589,6 +616,7 @@
},
{
"context": "OutlinePanel && not_editing",
"use_layout_keys": true,
"bindings": {
"j": "menu::SelectNext",
"k": "menu::SelectPrev",

View File

@@ -1,206 +0,0 @@
<task_description>
The user of a code editor wants to make a change to their codebase.
You must describe the change using the following XML structure:
- <patch> - A group of related code changes.
Child tags:
- <title> (required) - A high-level description of the changes. This should be as short
as possible, possibly using common abbreviations.
- <edit> (1 or more) - An edit to make at a particular range within a file.
Includes the following child tags:
- <path> (required) - The path to the file that will be changed.
- <description> (optional) - An arbitrarily-long comment that describes the purpose
of this edit.
- <old_text> (optional) - An excerpt from the file's current contents that uniquely
identifies a range within the file where the edit should occur. If this tag is not
specified, then the entire file will be used as the range.
- <new_text> (required) - The new text to insert into the file.
- <operation> (required) - The type of change that should occur at the given range
of the file. Must be one of the following values:
- `update`: Replaces the entire range with the new text.
- `insert_before`: Inserts the new text before the range.
- `insert_after`: Inserts new text after the range.
- `create`: Creates a new file with the given path and the new text.
- `delete`: Deletes the specified range from the file.
<guidelines>
- Never provide multiple edits whose ranges intersect each other. Instead, merge them into one edit.
- Prefer multiple edits to smaller, disjoint ranges, rather than one edit to a larger range.
- There's no need to escape angle brackets within XML tags.
- Always ensure imports are added if you're referencing symbols that are not in scope.
</guidelines>
Here are some concrete examples.
<example>
<message role="user">
```rs src/shapes.rs
pub mod rectangle;
pub mod circle;
```
```rs src/shapes/rectangle.rs
pub struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
pub fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
}
```
```rs src/shapes/circle.rs
pub struct Circle {
radius: f64,
}
impl Circle {
pub fn new(radius: f64) -> Self {
Circle { radius }
}
}
```
Update all shapes to store their origin as an (x, y) tuple and implement Display.
</message>
<message role="assistant">
We'll need to update both the rectangle and circle modules.
<patch>
<title>Add origins and display impls to shapes</title>
<edit>
<path>src/shapes/rectangle.rs</path>
<description>Add the origin field to Rectangle struct</description>
<operation>insert_after</operation>
<old_text>
pub struct Rectangle {
</old_text>
<new_text>
origin: (f64, f64),
</new_text>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>update</operation>
<old_text>
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
</old_text>
<new_text>
fn new(origin: (f64, f64), width: f64, height: f64) -> Self {
Rectangle { origin, width, height }
}
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<description>Add the origin field to Circle struct</description>
<operation>insert_after</operation>
<old_text>
pub struct Circle {
radius: f64,
</old_text>
<new_text>
origin: (f64, f64),
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>update</operation>
<old_text>
fn new(radius: f64) -> Self {
Circle { radius }
}
</old_text>
<new_text>
fn new(origin: (f64, f64), radius: f64) -> Self {
Circle { origin, radius }
}
</new_text>
</edit>
</step>
<edit>
<path>src/shapes/rectangle.rs</path>
<operation>insert_before</operation>
<old_text>
struct Rectangle {
</old_text>
<new_text>
use std::fmt;
</new_text>
</edit>
<edit>
<path>src/shapes/rectangle.rs</path>
<description>
Add a manual Display implementation for Rectangle.
Currently, this is the same as a derived Display implementation.
</description>
<operation>insert_after</operation>
<old_text>
Rectangle { width, height }
}
}
</old_text>
<new_text>
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.format_struct(f, "Rectangle")
.field("origin", &self.origin)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_before</operation>
<old_text>
struct Circle {
</old_text>
<new_text>
use std::fmt;
</new_text>
</edit>
<edit>
<path>src/shapes/circle.rs</path>
<operation>insert_after</operation>
<old_text>
Circle { radius }
}
}
</old_text>
<new_text>
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.format_struct(f, "Rectangle")
.field("origin", &self.origin)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
</new_text>
</edit>
</patch>
</message>
</example>
</task_description>

View File

@@ -157,7 +157,7 @@
"auto_signature_help": false,
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": true,
"show_signature_help_after_edits": false,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -193,6 +193,9 @@
// Controls whether inline completions are shown immediately (true)
// or manually by triggering `editor::ShowInlineCompletion` (false).
"show_inline_completions": true,
// Controls whether inline completions are shown in a given language scope.
// Example: ["string", "comment"]
"inline_completions_disabled_in": [],
// Whether to show tabs and spaces in the editor.
// This setting can take three values:
//
@@ -380,6 +383,16 @@
/// "never"
"show": null
},
/// Which files containing diagnostic errors/warnings to mark in the project panel.
/// This setting can take the following three values:
///
/// 1. Do not mark any files:
/// "off"
/// 2. Only mark files with errors:
/// "errors"
/// 3. Mark files with errors and warnings:
/// "all"
"show_diagnostics": "all",
// Settings related to indent guides in the project panel.
"indent_guides": {
// When to show indent guides in the project panel.
@@ -477,6 +490,9 @@
"version": "2",
// Whether the assistant is enabled.
"enabled": true,
// Whether to show inline hints showing the keybindings to use the inline assistant and the
// assistant panel.
"show_hints": true,
// Whether to show the assistant panel button in the status bar.
"button": true,
// Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'.
@@ -567,7 +583,23 @@
// Settings related to the file finder.
"file_finder": {
// Whether to show file icons in the file finder.
"file_icons": true
"file_icons": true,
// Determines how much space the file finder can take up in relation to the available window width.
// There are 5 possible width values:
//
// 1. Small: This value is essentially a fixed width.
// "modal_width": "small"
// 2. Medium:
// "modal_width": "medium"
// 3. Large:
// "modal_width": "large"
// 4. Extra Large:
// "modal_width": "xlarge"
// 5. Fullscreen: This value removes any horizontal padding, as it consumes the whole viewport width.
// "modal_width": "full"
//
// Default: small
"modal_max_width": "small"
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -844,15 +876,8 @@
//
"file_types": {
"Plain Text": ["txt"],
"JSON": ["flake.lock"],
"JSONC": [
"**/.zed/**/*.json",
"**/zed/**/*.json",
"**/Zed/**/*.json",
"tsconfig.json",
"pyrightconfig.json"
],
"TOML": ["uv.lock"]
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json"],
"Shell Script": [".env.*"]
},
/// By default use a recent system version of node, or install our own.
/// You can override this to use a version of node that is not in $PATH with:
@@ -1040,13 +1065,11 @@
"api_url": "https://generativelanguage.googleapis.com"
},
"ollama": {
"api_url": "http://localhost:11434",
"low_speed_timeout_in_seconds": 60
"api_url": "http://localhost:11434"
},
"openai": {
"version": "1",
"api_url": "https://api.openai.com/v1",
"low_speed_timeout_in_seconds": 600
"api_url": "https://api.openai.com/v1"
}
},
// Zed's Prettier integration settings.
@@ -1169,15 +1192,6 @@
// }
// ]
"ssh_connections": [],
// Configures the Context Server Protocol binaries
//
// Examples:
// {
// "id": "server-1",
// "executable": "/path",
// "args": ['arg1", "args2"]
// }
"experimental.context_servers": {
"servers": []
}
// Configures context servers for use in the Assistant.
"context_servers": {}
}

View File

@@ -20,6 +20,7 @@ extension_host.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
lsp.workspace = true
project.workspace = true
smallvec.workspace = true
ui.workspace = true

View File

@@ -7,9 +7,8 @@ use gpui::{
InteractiveElement as _, Model, ParentElement as _, Render, SharedString,
StatefulInteractiveElement, Styled, Transformation, View, ViewContext, VisualContext as _,
};
use language::{
LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId, LanguageServerName,
};
use language::{LanguageRegistry, LanguageServerBinaryStatus, LanguageServerId};
use lsp::LanguageServerName;
use project::{EnvironmentErrorMessage, LanguageServerProgress, Project, WorktreeId};
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc, time::Duration};

View File

@@ -1,14 +1,12 @@
mod supported_countries;
use std::time::Duration;
use std::{pin::Pin, str::FromStr};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, Stream, StreamExt};
use http_client::http::{HeaderMap, HeaderValue};
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use serde::{Deserialize, Serialize};
use std::{pin::Pin, str::FromStr};
use strum::{EnumIter, EnumString};
use thiserror::Error;
use util::ResultExt as _;
@@ -161,10 +159,7 @@ pub async fn complete(
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header(
"Anthropic-Beta",
"tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
)
.header("Anthropic-Beta", "prompt-caching-2024-07-31")
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
@@ -210,9 +205,8 @@ pub async fn stream_completion(
api_url: &str,
api_key: &str,
request: Request,
low_speed_timeout: Option<Duration>,
) -> Result<BoxStream<'static, Result<Event, AnthropicError>>, AnthropicError> {
stream_completion_with_rate_limit_info(client, api_url, api_key, request, low_speed_timeout)
stream_completion_with_rate_limit_info(client, api_url, api_key, request)
.await
.map(|output| output.0)
}
@@ -264,7 +258,6 @@ pub async fn stream_completion_with_rate_limit_info(
api_url: &str,
api_key: &str,
request: Request,
low_speed_timeout: Option<Duration>,
) -> Result<
(
BoxStream<'static, Result<Event, AnthropicError>>,
@@ -277,7 +270,7 @@ pub async fn stream_completion_with_rate_limit_info(
stream: true,
};
let uri = format!("{api_url}/v1/messages");
let mut request_builder = HttpRequest::builder()
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
@@ -287,9 +280,6 @@ pub async fn stream_completion_with_rate_limit_info(
)
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let serialized_request =
serde_json::to_string(&request).context("failed to serialize request")?;
let request = request_builder

View File

@@ -12,10 +12,15 @@ mod prompts;
mod slash_command;
pub(crate) mod slash_command_picker;
pub mod slash_command_settings;
mod slash_command_working_set;
mod streaming_diff;
mod terminal_inline_assistant;
mod tool_working_set;
mod tools;
use crate::slash_command::project_command::ProjectSlashCommandFeatureFlag;
pub use crate::slash_command_working_set::{SlashCommandId, SlashCommandWorkingSet};
pub use crate::tool_working_set::{ToolId, ToolWorkingSet};
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
use assistant_settings::AssistantSettings;
use assistant_slash_command::SlashCommandRegistry;
@@ -23,12 +28,11 @@ use assistant_tool::ToolRegistry;
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub use context::*;
use context_servers::ContextServerRegistry;
pub use context_store::*;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::impl_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use gpui::{impl_actions, Context as _};
use indexed_docs::IndexedDocsRegistry;
pub(crate) use inline_assistant::*;
use language_model::{
@@ -43,10 +47,9 @@ use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use slash_command::search_command::SearchSlashCommandFeatureFlag;
use slash_command::{
auto_command, cargo_workspace_command, context_server_command, default_command, delta_command,
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
prompt_command, search_command, selection_command, symbols_command, tab_command,
terminal_command,
auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
search_command, selection_command, symbols_command, tab_command, terminal_command,
};
use std::path::PathBuf;
use std::sync::Arc;
@@ -213,23 +216,32 @@ pub fn init(
});
}
if cx.has_flag::<SearchSlashCommandFeatureFlag>() {
cx.spawn(|mut cx| {
let client = client.clone();
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticDb::new(
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
.await?;
cx.spawn(|mut cx| {
let client = client.clone();
async move {
let is_search_slash_command_enabled = cx
.update(|cx| cx.wait_for_flag::<SearchSlashCommandFeatureFlag>())?
.await;
let is_project_slash_command_enabled = cx
.update(|cx| cx.wait_for_flag::<ProjectSlashCommandFeatureFlag>())?
.await;
cx.update(|cx| cx.set_global(semantic_index))
if !is_search_slash_command_enabled && !is_project_slash_command_enabled {
return Ok(());
}
})
.detach();
}
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticDb::new(
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
.await?;
cx.update(|cx| cx.set_global(semantic_index))
}
})
.detach();
context_store::init(&client.clone().into());
prompt_library::init(cx);
@@ -281,116 +293,9 @@ pub fn init(
})
.detach();
register_context_server_handlers(cx);
prompt_builder
}
fn register_context_server_handlers(cx: &mut AppContext) {
cx.subscribe(
&context_servers::manager::ContextServerManager::global(cx),
|manager, event, cx| match event {
context_servers::manager::Event::ServerStarted { server_id } => {
cx.update_model(
&manager,
|manager: &mut context_servers::manager::ContextServerManager, cx| {
let slash_command_registry = SlashCommandRegistry::global(cx);
let context_server_registry = ContextServerRegistry::global(cx);
if let Some(server) = manager.get_server(server_id) {
cx.spawn(|_, _| async move {
let Some(protocol) = server.client.read().clone() else {
return;
};
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
if let Some(prompts) = protocol.list_prompts().await.log_err() {
for prompt in prompts
.into_iter()
.filter(context_server_command::acceptable_prompt)
{
log::info!(
"registering context server command: {:?}",
prompt.name
);
context_server_registry.register_command(
server.id.clone(),
prompt.name.as_str(),
);
slash_command_registry.register_command(
context_server_command::ContextServerSlashCommand::new(
&server, prompt,
),
true,
);
}
}
}
})
.detach();
}
},
);
cx.update_model(
&manager,
|manager: &mut context_servers::manager::ContextServerManager, cx| {
let tool_registry = ToolRegistry::global(cx);
let context_server_registry = ContextServerRegistry::global(cx);
if let Some(server) = manager.get_server(server_id) {
cx.spawn(|_, _| async move {
let Some(protocol) = server.client.read().clone() else {
return;
};
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
if let Some(tools) = protocol.list_tools().await.log_err() {
for tool in tools.tools {
log::info!(
"registering context server tool: {:?}",
tool.name
);
context_server_registry.register_tool(
server.id.clone(),
tool.name.as_str(),
);
tool_registry.register_tool(
tools::context_server_tool::ContextServerTool::new(
server.id.clone(),
tool
),
);
}
}
}
})
.detach();
}
},
);
}
context_servers::manager::Event::ServerStopped { server_id } => {
let slash_command_registry = SlashCommandRegistry::global(cx);
let context_server_registry = ContextServerRegistry::global(cx);
if let Some(commands) = context_server_registry.get_commands(server_id) {
for command_name in commands {
slash_command_registry.unregister_command_by_name(&command_name);
context_server_registry.unregister_command(&server_id, &command_name);
}
}
if let Some(tools) = context_server_registry.get_tools(server_id) {
let tool_registry = ToolRegistry::global(cx);
for tool_name in tools {
tool_registry.unregister_tool_by_name(&tool_name);
context_server_registry.unregister_tool(&server_id, &tool_name);
}
}
}
},
)
.detach();
}
fn init_language_model_settings(cx: &mut AppContext) {
update_active_language_model_from_settings(cx);

View File

@@ -1,4 +1,7 @@
use crate::slash_command::file_command::codeblock_fence_for_path;
use crate::slash_command_working_set::SlashCommandWorkingSet;
use crate::tools::code_edits_tool::{CodeEditsTool, CodeEditsToolInput};
use crate::ToolWorkingSet;
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings},
humanize_token_count,
@@ -7,21 +10,20 @@ use crate::{
slash_command::{
default_command::DefaultSlashCommand,
docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
file_command, SlashCommandCompletionProvider, SlashCommandRegistry,
file_command, SlashCommandCompletionProvider,
},
slash_command_picker,
terminal_inline_assistant::TerminalInlineAssistant,
Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context,
ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
InsertIntoEditor, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata,
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, ParsedSlashCommand,
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, RequestType,
SavedContextMetadata, SlashCommandId, Split, ToggleFocus, ToggleModelSelector,
InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext,
ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
};
use anyhow::Result;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
use assistant_tool::ToolRegistry;
use client::{proto, zed_urls, Client, Status};
use collections::{hash_map, BTreeSet, HashMap, HashSet};
use editor::{
@@ -112,7 +114,8 @@ pub fn init(cx: &mut AppContext) {
.register_action(ContextEditor::copy_code)
.register_action(ContextEditor::insert_dragged_files)
.register_action(AssistantPanel::show_configuration)
.register_action(AssistantPanel::create_new_context);
.register_action(AssistantPanel::create_new_context)
.register_action(AssistantPanel::restart_context_servers);
},
)
.detach();
@@ -315,10 +318,12 @@ impl AssistantPanel {
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let slash_commands = Arc::new(SlashCommandWorkingSet::default());
let tools = Arc::new(ToolWorkingSet::default());
let context_store = workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
ContextStore::new(project, prompt_builder.clone(), cx)
ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx)
})?
.await?;
@@ -443,7 +448,7 @@ impl AssistantPanel {
);
let _pane = cx.view().clone();
let right_children = h_flex()
.gap(Spacing::XSmall.rems(cx))
.gap(DynamicSpacing::Base02.rems(cx))
.child(
IconButton::new("new-chat", IconName::Plus)
.on_click(
@@ -1080,7 +1085,21 @@ impl AssistantPanel {
self.show_updated_summary(&context_editor, cx);
cx.notify()
}
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
EditorEvent::Edited { .. } => {
self.workspace
.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
workspace
.client()
.telemetry()
.log_edit_event("assistant panel", is_via_ssh);
})
.log_err();
cx.emit(AssistantPanelEvent::ContextEdited)
}
_ => {}
}
}
@@ -1294,6 +1313,24 @@ impl AssistantPanel {
.active_provider()
.map_or(None, |provider| Some(provider.authenticate(cx)))
}
fn restart_context_servers(
workspace: &mut Workspace,
_action: &context_servers::Restart,
cx: &mut ViewContext<Workspace>,
) {
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
assistant_panel.update(cx, |assistant_panel, cx| {
assistant_panel
.context_store
.update(cx, |context_store, cx| {
context_store.restart_context_servers(cx);
});
});
}
}
impl Render for AssistantPanel {
@@ -1444,7 +1481,6 @@ struct ScrollPosition {
}
struct PatchViewState {
footer_block_id: CustomBlockId,
crease_id: CreaseId,
editor: Option<PatchEditorState>,
update_task: Option<Task<()>>,
@@ -1468,6 +1504,8 @@ enum AssistError {
pub struct ContextEditor {
context: Model<Context>,
fs: Arc<dyn Fs>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
workspace: WeakView<Workspace>,
project: Model<Project>,
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
@@ -1477,7 +1515,7 @@ pub struct ContextEditor {
scroll_position: Option<ScrollPosition>,
remote_id: Option<workspace::ViewId>,
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
invoked_slash_command_creases: HashMap<SlashCommandId, CreaseId>,
invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>,
pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
_subscriptions: Vec<Subscription>,
patches: HashMap<Range<language::Anchor>, PatchViewState>,
@@ -1509,6 +1547,7 @@ impl ContextEditor {
cx: &mut ViewContext<Self>,
) -> Self {
let completion_provider = SlashCommandCompletionProvider::new(
context.read(cx).slash_commands.clone(),
Some(cx.view().downgrade()),
Some(workspace.clone()),
);
@@ -1536,8 +1575,12 @@ impl ContextEditor {
let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands.clone();
let tools = context.read(cx).tools.clone();
let mut this = Self {
context,
slash_commands,
tools,
editor,
lsp_adapter_delegate,
blocks: Default::default(),
@@ -1688,7 +1731,7 @@ impl ContextEditor {
}
pub fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
if let Some(command) = self.slash_commands.command(name, cx) {
self.editor.update(cx, |editor, cx| {
editor.transact(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
@@ -1770,7 +1813,7 @@ impl ContextEditor {
workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>,
) {
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
if let Some(command) = self.slash_commands.command(name, cx) {
let context = self.context.read(cx);
let sections = context
.slash_command_output_sections()
@@ -1856,47 +1899,111 @@ impl ContextEditor {
let creases = new_tool_uses
.iter()
.map(|tool_use| {
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(
cx.view().downgrade(),
IconName::PocketKnife,
tool_use.name.clone().into(),
),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
if &tool_use.name == CodeEditsTool::TOOL_NAME {
// If this is a Code Edit tool,
match serde_json::from_value::<CodeEditsToolInput>(
tool_use.input.clone(),
) {
Ok(CodeEditsToolInput { title, edits }) => {
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(
cx.view().downgrade(),
IconName::Sparkle,
title.into(),
),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _cx: &mut WindowContext| {
Empty.into_any()
};
let start = buffer
.anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
.unwrap();
let start = buffer
.anchor_in_excerpt(
excerpt_id,
tool_use.source_range.start,
)
.unwrap();
let end = buffer
.anchor_in_excerpt(
excerpt_id,
tool_use.source_range.end,
)
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
let buffer_row =
MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
self.context.update(cx, |context, cx| {
context.insert_content(
Content::ToolUse {
range: tool_use.source_range.clone(),
tool_use: LanguageModelToolUse {
id: tool_use.id.to_string(),
name: tool_use.name.clone(),
input: tool_use.input.clone(),
self.context.update(cx, |context, cx| {
context.insert_content(
Content::ToolUse {
range: tool_use.source_range.clone(),
tool_use: LanguageModelToolUse {
id: tool_use.id.to_string(),
name: tool_use.name.clone(),
input: tool_use.input.clone(),
},
},
cx,
);
});
Crease::inline(
start..end,
placeholder,
fold_toggle("tool-use"),
render_trailer,
)
}
Err(json_err) => {
// TODO gracefully handle malformed JSON (should distinguish from "errored out" vs "not done streaming yet")
todo!();
}
}
} else {
let placeholder = FoldPlaceholder {
render: render_fold_icon_button(
cx.view().downgrade(),
IconName::PocketKnife,
tool_use.name.clone().into(),
),
..Default::default()
};
let render_trailer =
move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
let start = buffer
.anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
.unwrap();
let end = buffer
.anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
self.context.update(cx, |context, cx| {
context.insert_content(
Content::ToolUse {
range: tool_use.source_range.clone(),
tool_use: LanguageModelToolUse {
id: tool_use.id.to_string(),
name: tool_use.name.clone(),
input: tool_use.input.clone(),
},
},
},
cx,
);
});
cx,
);
});
Crease::new(
start..end,
placeholder,
fold_toggle("tool-use"),
render_trailer,
)
Crease::inline(
start..end,
placeholder,
fold_toggle("tool-use"),
render_trailer,
)
}
})
.collect::<Vec<_>>();
@@ -1989,7 +2096,7 @@ impl ContextEditor {
let end = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.end)
.unwrap();
Crease::new(start..end, placeholder, render_toggle, render_trailer)
Crease::inline(start..end, placeholder, render_toggle, render_trailer)
}),
cx,
);
@@ -2008,30 +2115,6 @@ impl ContextEditor {
ContextEvent::SlashCommandOutputSectionAdded { section } => {
self.insert_slash_command_output_sections([section.clone()], false, cx);
}
ContextEvent::SlashCommandFinished {
output_range: _output_range,
run_commands_in_ranges,
} => {
for range in run_commands_in_ranges {
let commands = self.context.update(cx, |context, cx| {
context.reparse(cx);
context
.pending_commands_for_range(range.clone(), cx)
.to_vec()
});
for command in commands {
self.run_command(
command.source_range,
&command.name,
&command.arguments,
false,
self.workspace.clone(),
cx,
);
}
}
}
ContextEvent::UsePendingTools => {
let pending_tool_uses = self
.context
@@ -2043,8 +2126,7 @@ impl ContextEditor {
.collect::<Vec<_>>();
for tool_use in pending_tool_uses {
let tool_registry = ToolRegistry::global(cx);
if let Some(tool) = tool_registry.tool(&tool_use.name) {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
self.context.update(cx, |context, cx| {
@@ -2082,7 +2164,7 @@ impl ContextEditor {
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
let crease = Crease::new(
let crease = Crease::inline(
start..end,
placeholder,
fold_toggle("tool-use"),
@@ -2108,9 +2190,40 @@ impl ContextEditor {
fn update_invoked_slash_command(
&mut self,
command_id: SlashCommandId,
command_id: InvokedSlashCommandId,
cx: &mut ViewContext<Self>,
) {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
{
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
let run_commands_in_ranges = invoked_slash_command
.run_commands_in_ranges
.iter()
.cloned()
.collect::<Vec<_>>();
for range in run_commands_in_ranges {
let commands = self.context.update(cx, |context, cx| {
context.reparse(cx);
context
.pending_commands_for_range(range.clone(), cx)
.to_vec()
});
for command in commands {
self.run_command(
command.source_range,
&command.name,
&command.arguments,
false,
self.workspace.clone(),
cx,
);
}
}
}
}
self.editor.update(cx, |editor, cx| {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
@@ -2150,18 +2263,14 @@ impl ContextEditor {
let crease_end = buffer
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
.unwrap();
let fold_placeholder =
invoked_slash_command_fold_placeholder(command_id, context);
let crease_ids = editor.insert_creases(
[Crease::new(
crease_start..crease_end,
fold_placeholder.clone(),
fold_toggle("invoked-slash-command"),
|_row, _folded, _cx| Empty.into_any(),
)],
cx,
let crease = Crease::inline(
crease_start..crease_end,
invoked_slash_command_fold_placeholder(command_id, context),
fold_toggle("invoked-slash-command"),
|_row, _folded, _cx| Empty.into_any(),
);
editor.fold_ranges([(crease_start..crease_end, fold_placeholder)], false, cx);
let crease_ids = editor.insert_creases([crease.clone()], cx);
editor.fold_creases(vec![crease], false, cx);
entry.insert(crease_ids[0]);
} else {
cx.notify()
@@ -2183,23 +2292,32 @@ impl ContextEditor {
cx: &mut ViewContext<ContextEditor>,
) {
let this = cx.view().downgrade();
let mut removed_crease_ids = Vec::new();
let mut removed_block_ids = HashSet::default();
let mut editors_to_close = Vec::new();
for range in removed {
if let Some(state) = self.patches.remove(range) {
editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade()));
removed_block_ids.insert(state.footer_block_id);
removed_crease_ids.push(state.crease_id);
}
}
self.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let multibuffer = &snapshot.buffer_snapshot;
let (&excerpt_id, _, _) = multibuffer.as_singleton().unwrap();
let mut replaced_blocks = HashMap::default();
let mut removed_crease_ids = Vec::new();
let mut ranges_to_unfold: Vec<Range<Anchor>> = Vec::new();
for range in removed {
if let Some(state) = self.patches.remove(range) {
let patch_start = multibuffer
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap();
let patch_end = multibuffer
.anchor_in_excerpt(excerpt_id, range.end)
.unwrap();
editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade()));
ranges_to_unfold.push(patch_start..patch_end);
removed_crease_ids.push(state.crease_id);
}
}
editor.unfold_ranges(&ranges_to_unfold, true, false, cx);
editor.remove_creases(removed_crease_ids, cx);
for range in updated {
let Some(patch) = self.context.read(cx).patch_for_range(&range, cx).cloned() else {
continue;
@@ -2212,19 +2330,21 @@ impl ContextEditor {
let patch_end = multibuffer
.anchor_in_excerpt(excerpt_id, patch.range.end)
.unwrap();
let render_block: RenderBlock = Box::new({
let render_block: RenderBlock = Arc::new({
let this = this.clone();
let patch_range = range.clone();
move |cx: &mut BlockContext<'_, '_>| {
let max_width = cx.max_width;
let gutter_width = cx.gutter_dimensions.full_width();
let block_id = cx.block_id;
let selected = cx.selected;
this.update(&mut **cx, |this, cx| {
this.render_patch_footer(
this.render_patch_block(
patch_range.clone(),
max_width,
gutter_width,
block_id,
selected,
cx,
)
})
@@ -2234,25 +2354,16 @@ impl ContextEditor {
}
});
let header_placeholder = FoldPlaceholder {
render: {
let this = this.clone();
let patch_range = range.clone();
Arc::new(move |fold_id, _range, cx| {
this.update(cx, |this, cx| {
this.render_patch_header(patch_range.clone(), fold_id, cx)
})
.ok()
.flatten()
.unwrap_or_else(|| Empty.into_any())
})
},
..Default::default()
};
let height = path_count as u32 + 1;
let crease = Crease::block(
patch_start..patch_end,
height,
BlockStyle::Flex,
render_block.clone(),
);
let should_refold;
if let Some(state) = self.patches.get_mut(&range) {
replaced_blocks.insert(state.footer_block_id, render_block);
if let Some(editor_state) = &state.editor {
if editor_state.opened_patch != patch {
state.update_task = Some({
@@ -2269,33 +2380,11 @@ impl ContextEditor {
should_refold =
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
} else {
let block_ids = editor.insert_blocks(
[BlockProperties {
height: path_count as u32 + 1,
style: BlockStyle::Flex,
render: render_block,
placement: BlockPlacement::Below(patch_start),
priority: 0,
}],
None,
cx,
);
let new_crease_ids = editor.insert_creases(
[Crease::new(
patch_start..patch_end,
header_placeholder.clone(),
fold_toggle("patch-header"),
|_, _, _| Empty.into_any_element(),
)],
cx,
);
let crease_id = editor.insert_creases([crease.clone()], cx)[0];
self.patches.insert(
range.clone(),
PatchViewState {
footer_block_id: block_ids[0],
crease_id: new_crease_ids[0],
crease_id,
editor: None,
update_task: None,
},
@@ -2306,13 +2395,9 @@ impl ContextEditor {
if should_refold {
editor.unfold_ranges(&[patch_start..patch_end], true, false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
editor.fold_creases(vec![crease], false, cx);
}
}
editor.remove_creases(removed_crease_ids, cx);
editor.remove_blocks(removed_block_ids, None, cx);
editor.replace_blocks(replaced_blocks, None, cx);
});
for editor in editors_to_close {
@@ -2343,7 +2428,7 @@ impl ContextEditor {
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
creases.push(
Crease::new(
Crease::inline(
start..end,
FoldPlaceholder {
render: render_fold_icon_button(
@@ -2632,7 +2717,7 @@ impl ContextEditor {
let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
let render_block = |message: MessageMetadata| -> RenderBlock {
Box::new({
Arc::new({
let context = self.context.clone();
move |cx| {
@@ -2733,7 +2818,7 @@ impl ContextEditor {
.h_11()
.w_full()
.relative()
.gap_1()
.gap_1p5()
.child(sender)
.children(match &message.cache {
Some(cache) if cache.is_final_anchor => match cache.status {
@@ -2747,7 +2832,7 @@ impl ContextEditor {
)
.tooltip(|cx| {
Tooltip::with_meta(
"Context cached",
"Context Cached",
None,
"Large messages cached to optimize performance",
cx,
@@ -2775,16 +2860,9 @@ impl ContextEditor {
.selected_icon_color(Color::Error)
.icon(IconName::XCircle)
.icon_color(Color::Error)
.icon_size(IconSize::Small)
.icon_size(IconSize::XSmall)
.icon_position(IconPosition::Start)
.tooltip(move |cx| {
Tooltip::with_meta(
"Error interacting with language model",
None,
"Click for more details",
cx,
)
})
.tooltip(move |cx| Tooltip::text("View Details", cx))
.on_click({
let context = context.clone();
let error = error.clone();
@@ -2799,21 +2877,19 @@ impl ContextEditor {
.into_any_element(),
),
MessageStatus::Canceled => Some(
ButtonLike::new("canceled")
.child(Icon::new(IconName::XCircle).color(Color::Disabled))
h_flex()
.gap_1()
.items_center()
.child(
Icon::new(IconName::XCircle)
.color(Color::Disabled)
.size(IconSize::XSmall),
)
.child(
Label::new("Canceled")
.size(LabelSize::Small)
.color(Color::Disabled),
)
.tooltip(move |cx| {
Tooltip::with_meta(
"Canceled",
None,
"Interaction with the assistant was canceled",
cx,
)
})
.into_any_element(),
),
_ => None,
@@ -3094,7 +3170,7 @@ impl ContextEditor {
crease_title,
cx.view().downgrade(),
);
let crease = Crease::new(
let crease = Crease::inline(
anchor_before..anchor_after,
fold_placeholder,
render_quote_selection_output_toggle,
@@ -3184,31 +3260,29 @@ impl ContextEditor {
&snapshot,
)
.filter_map(|crease| {
if let Some(metadata) = &crease.metadata {
let start = crease
.range
if let Crease::Inline {
range, metadata, ..
} = &crease
{
let metadata = metadata.as_ref()?;
let start = range
.start
.to_offset(&snapshot)
.saturating_sub(selection_start);
let end = crease
.range
let end = range
.end
.to_offset(&snapshot)
.saturating_sub(selection_start);
let range_relative_to_selection = start..end;
if range_relative_to_selection.is_empty() {
None
} else {
Some(SelectedCreaseMetadata {
if !range_relative_to_selection.is_empty() {
return Some(SelectedCreaseMetadata {
range_relative_to_selection,
crease: metadata.clone(),
})
});
}
} else {
None
}
None
})
.collect::<Vec<_>>()
}),
@@ -3289,7 +3363,7 @@ impl ContextEditor {
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
Crease::new(
Crease::inline(
start..end,
FoldPlaceholder {
render: render_fold_icon_button(
@@ -3331,7 +3405,8 @@ impl ContextEditor {
self.context.update(cx, |context, cx| {
for image in images {
let Some(render_image) = image.to_image_data(cx).log_err() else {
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
else {
continue;
};
let image_id = image.id();
@@ -3382,7 +3457,7 @@ impl ContextEditor {
placement: BlockPlacement::Above(anchor),
height: MAX_HEIGHT_IN_LINES,
style: BlockStyle::Sticky,
render: Box::new(move |cx| {
render: Arc::new(move |cx| {
let image_size = size_for_image(
&image,
size(
@@ -3439,33 +3514,13 @@ impl ContextEditor {
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
}
fn render_patch_header(
&self,
range: Range<text::Anchor>,
_id: FoldId,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let patch = self.context.read(cx).patch_for_range(&range, cx)?;
let theme = cx.theme().clone();
Some(
h_flex()
.px_1()
.py_0p5()
.border_b_1()
.border_color(theme.status().info_border)
.gap_1()
.child(Icon::new(IconName::Diff).size(IconSize::Small))
.child(Label::new(patch.title.clone()).size(LabelSize::Small))
.into_any(),
)
}
fn render_patch_footer(
fn render_patch_block(
&mut self,
range: Range<text::Anchor>,
max_width: Pixels,
gutter_width: Pixels,
id: BlockId,
selected: bool,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
@@ -3476,10 +3531,7 @@ impl ContextEditor {
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap();
if !snapshot.intersects_fold(anchor) {
return None;
}
let theme = cx.theme().clone();
let patch = self.context.read(cx).patch_for_range(&range, cx)?;
let paths = patch
.paths()
@@ -3489,9 +3541,18 @@ impl ContextEditor {
Some(
v_flex()
.id(id)
.pl(gutter_width)
.w(max_width)
.py_2()
.bg(theme.colors().editor_background)
.ml(gutter_width)
.pb_1()
.w(max_width - gutter_width)
.rounded_md()
.border_1()
.border_color(theme.colors().border_variant)
.overflow_hidden()
.hover(|style| style.border_color(theme.colors().text_accent))
.when(selected, |this| {
this.border_color(theme.colors().text_accent)
})
.cursor(CursorStyle::PointingHand)
.on_click(cx.listener(move |this, _, cx| {
this.editor.update(cx, |editor, cx| {
@@ -3501,24 +3562,60 @@ impl ContextEditor {
});
this.focus_active_patch(cx);
}))
.child(
div()
.px_2()
.py_1()
.overflow_hidden()
.text_ellipsis()
.border_b_1()
.border_color(theme.colors().border_variant)
.bg(theme.colors().element_background)
.child(
Label::new(patch.title.clone())
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.children(paths.into_iter().map(|path| {
h_flex()
.pl_1()
.gap_1()
.px_2()
.pt_1()
.gap_1p5()
.child(Icon::new(IconName::File).size(IconSize::Small))
.child(Label::new(path).size(LabelSize::Small))
}))
.when(patch.status == AssistantPatchStatus::Pending, |div| {
div.child(
Label::new("Generating")
.color(Color::Muted)
.size(LabelSize::Small)
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.4, 1.)),
|label, delta| label.alpha(delta),
h_flex()
.pt_1()
.px_2()
.gap_1()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(percentage(
delta,
)))
},
),
)
.child(
Label::new("Generating…")
.color(Color::Muted)
.size(LabelSize::Small)
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.4, 0.8)),
|label, delta| label.alpha(delta),
),
),
)
})
@@ -3719,6 +3816,19 @@ impl ContextEditor {
})
}
fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
slash_command_picker::SlashCommandSelector::new(
self.slash_commands.clone(),
cx.view().downgrade(),
Button::new("trigger", "Add Context")
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.tooltip(|cx| Tooltip::text("Type / to insert via keyboard", cx)),
)
}
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?;
@@ -3883,7 +3993,7 @@ impl ContextEditor {
.child(
div()
.id("error-message")
.max_h_24()
.max_h_32()
.overflow_y_scroll()
.child(Label::new(error_message.clone())),
)
@@ -4133,11 +4243,7 @@ impl Render for ContextEditor {
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.gap_1()
.child(render_inject_context_menu(cx.view().downgrade(), cx)),
)
.child(h_flex().gap_1().child(self.render_inject_context_menu(cx)))
.child(
h_flex()
.w_full()
@@ -4419,24 +4525,6 @@ pub struct ContextEditorToolbarItem {
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
}
fn render_inject_context_menu(
active_context_editor: WeakView<ContextEditor>,
cx: &mut WindowContext<'_>,
) -> impl IntoElement {
let commands = SlashCommandRegistry::global(cx);
slash_command_picker::SlashCommandSelector::new(
commands.clone(),
active_context_editor,
Button::new("trigger", "Add Context")
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.tooltip(|cx| Tooltip::text("Type / to insert via keyboard", cx)),
)
}
impl ContextEditorToolbarItem {
pub fn new(
workspace: &Workspace,
@@ -4498,7 +4586,6 @@ impl Render for ContextEditorToolbarItem {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let left_side = h_flex()
.group("chat-title-group")
.pl_0p5()
.gap_1()
.items_center()
.flex_grow()
@@ -4589,6 +4676,7 @@ impl Render for ContextEditorToolbarItem {
.children(self.render_remaining_tokens(cx));
h_flex()
.px_0p5()
.size_full()
.gap_2()
.justify_between()
@@ -4814,7 +4902,7 @@ impl ConfigurationView {
)
.child(
div()
.p(Spacing::Large.rems(cx))
.p(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().surface_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
@@ -4848,7 +4936,7 @@ impl Render for ConfigurationView {
.overflow_y_scroll()
.child(
v_flex()
.p(Spacing::XXLarge.rems(cx))
.p(DynamicSpacing::Base16.rems(cx))
.border_b_1()
.border_color(cx.theme().colors().border)
.gap_1()
@@ -4862,7 +4950,7 @@ impl Render for ConfigurationView {
)
.child(
v_flex()
.p(Spacing::XXLarge.rems(cx))
.p(DynamicSpacing::Base16.rems(cx))
.mt_1()
.gap_6()
.flex_1()
@@ -5095,7 +5183,7 @@ fn make_lsp_adapter_delegate(
enum PendingSlashCommand {}
fn invoked_slash_command_fold_placeholder(
command_id: SlashCommandId,
command_id: InvokedSlashCommandId,
context: WeakModel<Context>,
) -> FoldPlaceholder {
FoldPlaceholder {

View File

@@ -35,20 +35,17 @@ pub enum AssistantProviderContentV1 {
OpenAi {
default_model: Option<OpenAiModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
available_models: Option<Vec<OpenAiModel>>,
},
#[serde(rename = "anthropic")]
Anthropic {
default_model: Option<AnthropicModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
},
#[serde(rename = "ollama")]
Ollama {
default_model: Option<OllamaModel>,
api_url: Option<String>,
low_speed_timeout_in_seconds: Option<u64>,
},
}
@@ -63,6 +60,7 @@ pub struct AssistantSettings {
pub inline_alternatives: Vec<LanguageModelSelection>,
pub using_outdated_settings_version: bool,
pub enable_experimental_live_diffs: bool,
pub show_hints: bool,
}
impl AssistantSettings {
@@ -115,47 +113,41 @@ impl AssistantSettingsContent {
if let VersionedAssistantSettingsContent::V1(settings) = settings {
if let Some(provider) = settings.provider.clone() {
match provider {
AssistantProviderContentV1::Anthropic {
api_url,
low_speed_timeout_in_seconds,
..
} => update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.anthropic.is_none() {
content.anthropic = Some(AnthropicSettingsContent::Versioned(
VersionedAnthropicSettingsContent::V1(
AnthropicSettingsContentV1 {
api_url,
low_speed_timeout_in_seconds,
available_models: None,
},
),
));
}
},
),
AssistantProviderContentV1::Ollama {
api_url,
low_speed_timeout_in_seconds,
..
} => update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.ollama.is_none() {
content.ollama = Some(OllamaSettingsContent {
api_url,
low_speed_timeout_in_seconds,
available_models: None,
});
}
},
),
AssistantProviderContentV1::Anthropic { api_url, .. } => {
update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.anthropic.is_none() {
content.anthropic =
Some(AnthropicSettingsContent::Versioned(
VersionedAnthropicSettingsContent::V1(
AnthropicSettingsContentV1 {
api_url,
available_models: None,
},
),
));
}
},
)
}
AssistantProviderContentV1::Ollama { api_url, .. } => {
update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.ollama.is_none() {
content.ollama = Some(OllamaSettingsContent {
api_url,
available_models: None,
});
}
},
)
}
AssistantProviderContentV1::OpenAi {
api_url,
low_speed_timeout_in_seconds,
available_models,
..
} => update_settings_file::<AllLanguageModelSettings>(
@@ -188,7 +180,6 @@ impl AssistantSettingsContent {
VersionedOpenAiSettingsContent::V1(
OpenAiSettingsContentV1 {
api_url,
low_speed_timeout_in_seconds,
available_models,
},
),
@@ -212,6 +203,7 @@ impl AssistantSettingsContent {
AssistantSettingsContent::Versioned(settings) => match settings {
VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 {
enabled: settings.enabled,
show_hints: None,
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
@@ -252,6 +244,7 @@ impl AssistantSettingsContent {
},
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
enabled: None,
show_hints: None,
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
@@ -298,54 +291,41 @@ impl AssistantSettingsContent {
log::warn!("attempted to set zed.dev model on outdated settings");
}
"anthropic" => {
let (api_url, low_speed_timeout_in_seconds) = match &settings.provider {
Some(AssistantProviderContentV1::Anthropic {
api_url,
low_speed_timeout_in_seconds,
..
}) => (api_url.clone(), *low_speed_timeout_in_seconds),
_ => (None, None),
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::Anthropic { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::Anthropic {
default_model: AnthropicModel::from_id(&model).ok(),
api_url,
low_speed_timeout_in_seconds,
});
}
"ollama" => {
let (api_url, low_speed_timeout_in_seconds) = match &settings.provider {
Some(AssistantProviderContentV1::Ollama {
api_url,
low_speed_timeout_in_seconds,
..
}) => (api_url.clone(), *low_speed_timeout_in_seconds),
_ => (None, None),
let api_url = match &settings.provider {
Some(AssistantProviderContentV1::Ollama { api_url, .. }) => {
api_url.clone()
}
_ => None,
};
settings.provider = Some(AssistantProviderContentV1::Ollama {
default_model: Some(ollama::Model::new(&model, None, None)),
api_url,
low_speed_timeout_in_seconds,
});
}
"openai" => {
let (api_url, low_speed_timeout_in_seconds, available_models) =
match &settings.provider {
Some(AssistantProviderContentV1::OpenAi {
api_url,
low_speed_timeout_in_seconds,
available_models,
..
}) => (
api_url.clone(),
*low_speed_timeout_in_seconds,
available_models.clone(),
),
_ => (None, None, None),
};
let (api_url, available_models) = match &settings.provider {
Some(AssistantProviderContentV1::OpenAi {
api_url,
available_models,
..
}) => (api_url.clone(), available_models.clone()),
_ => (None, None),
};
settings.provider = Some(AssistantProviderContentV1::OpenAi {
default_model: OpenAiModel::from_id(&model).ok(),
api_url,
low_speed_timeout_in_seconds,
available_models,
});
}
@@ -377,6 +357,7 @@ impl Default for VersionedAssistantSettingsContent {
fn default() -> Self {
Self::V2(AssistantSettingsContentV2 {
enabled: None,
show_hints: None,
button: None,
dock: None,
default_width: None,
@@ -394,6 +375,11 @@ pub struct AssistantSettingsContentV2 {
///
/// Default: true
enabled: Option<bool>,
/// Whether to show inline hints that show keybindings for inline assistant
/// and assistant panel.
///
/// Default: true
show_hints: Option<bool>,
/// Whether to show the assistant panel button in the status bar.
///
/// Default: true
@@ -528,6 +514,7 @@ impl Settings for AssistantSettings {
let value = value.upgrade();
merge(&mut settings.enabled, value.enabled);
merge(&mut settings.show_hints, value.show_hints);
merge(&mut settings.button, value.button);
merge(&mut settings.dock, value.dock);
merge(
@@ -598,6 +585,7 @@ mod tests {
}),
inline_alternatives: None,
enabled: None,
show_hints: None,
button: None,
dock: None,
default_width: None,

View File

@@ -1,17 +1,19 @@
#[cfg(test)]
mod context_tests;
use crate::slash_command_working_set::SlashCommandWorkingSet;
use crate::ToolWorkingSet;
use crate::{
prompts::PromptBuilder,
slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
tools::code_edits_tool::CodeEditsTool,
AssistantPatch, MessageId, MessageStatus,
};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandRegistry,
SlashCommandResult,
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
};
use assistant_tool::ToolRegistry;
use assistant_tool::Tool;
use client::{self, proto, telemetry::Telemetry};
use clock::ReplicaId;
use collections::{HashMap, HashSet};
@@ -43,7 +45,6 @@ use std::{
iter, mem,
ops::Range,
path::{Path, PathBuf},
str::FromStr as _,
sync::Arc,
time::{Duration, Instant},
};
@@ -95,13 +96,13 @@ pub enum ContextOperation {
version: clock::Global,
},
SlashCommandStarted {
id: SlashCommandId,
id: InvokedSlashCommandId,
output_range: Range<language::Anchor>,
name: String,
version: clock::Global,
},
SlashCommandFinished {
id: SlashCommandId,
id: InvokedSlashCommandId,
timestamp: clock::Lamport,
error_message: Option<String>,
version: clock::Global,
@@ -167,7 +168,7 @@ impl ContextOperation {
}),
proto::context_operation::Variant::SlashCommandStarted(message) => {
Ok(Self::SlashCommandStarted {
id: SlashCommandId(language::proto::deserialize_timestamp(
id: InvokedSlashCommandId(language::proto::deserialize_timestamp(
message.id.context("invalid id")?,
)),
output_range: language::proto::deserialize_anchor_range(
@@ -198,7 +199,7 @@ impl ContextOperation {
}
proto::context_operation::Variant::SlashCommandCompleted(message) => {
Ok(Self::SlashCommandFinished {
id: SlashCommandId(language::proto::deserialize_timestamp(
id: InvokedSlashCommandId(language::proto::deserialize_timestamp(
message.id.context("invalid id")?,
)),
timestamp: language::proto::deserialize_timestamp(
@@ -372,7 +373,7 @@ pub enum ContextEvent {
updated: Vec<Range<language::Anchor>>,
},
InvokedSlashCommandChanged {
command_id: SlashCommandId,
command_id: InvokedSlashCommandId,
},
ParsedSlashCommandsUpdated {
removed: Vec<Range<language::Anchor>>,
@@ -381,10 +382,6 @@ pub enum ContextEvent {
SlashCommandOutputSectionAdded {
section: SlashCommandOutputSection<language::Anchor>,
},
SlashCommandFinished {
output_range: Range<language::Anchor>,
run_commands_in_ranges: Vec<Range<language::Anchor>>,
},
UsePendingTools,
ToolFinished {
tool_use_id: Arc<str>,
@@ -513,7 +510,7 @@ struct PendingCompletion {
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct SlashCommandId(clock::Lamport);
pub struct InvokedSlashCommandId(clock::Lamport);
#[derive(Clone, Debug)]
pub struct XmlTag {
@@ -543,8 +540,10 @@ pub struct Context {
operations: Vec<ContextOperation>,
buffer: Model<Buffer>,
parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<SlashCommandId, InvokedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription,
pub(crate) slash_commands: Arc<SlashCommandWorkingSet>,
pub(crate) tools: Arc<ToolWorkingSet>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pending_tool_uses_by_id: HashMap<Arc<str>, PendingToolUse>,
message_anchors: Vec<MessageAnchor>,
@@ -563,7 +562,6 @@ pub struct Context {
telemetry: Option<Arc<Telemetry>>,
language_registry: Arc<LanguageRegistry>,
patches: Vec<AssistantPatch>,
xml_tags: Vec<XmlTag>,
project: Option<Model<Project>>,
prompt_builder: Arc<PromptBuilder>,
}
@@ -598,6 +596,8 @@ impl Context {
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
cx: &mut ModelContext<Self>,
) -> Self {
Self::new(
@@ -606,6 +606,8 @@ impl Context {
language::Capability::ReadWrite,
language_registry,
prompt_builder,
slash_commands,
tools,
project,
telemetry,
cx,
@@ -619,6 +621,8 @@ impl Context {
capability: language::Capability,
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
@@ -663,8 +667,9 @@ impl Context {
telemetry,
project,
language_registry,
slash_commands,
tools,
patches: Vec::new(),
xml_tags: Vec::new(),
prompt_builder,
};
@@ -738,6 +743,8 @@ impl Context {
path: PathBuf,
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
@@ -749,6 +756,8 @@ impl Context {
language::Capability::ReadWrite,
language_registry,
prompt_builder,
slash_commands,
tools,
project,
telemetry,
cx,
@@ -902,6 +911,7 @@ impl Context {
InvokedSlashCommand {
name: name.into(),
range: output_range,
run_commands_in_ranges: Vec::new(),
status: InvokedSlashCommandStatus::Running(Task::ready(())),
transaction: None,
timestamp: id.0,
@@ -951,7 +961,6 @@ impl Context {
}
if !changed_messages.is_empty() {
self.message_roles_updated(changed_messages, cx);
cx.emit(ContextEvent::MessagesEdited);
cx.notify();
}
@@ -1086,7 +1095,7 @@ impl Context {
pub fn invoked_slash_command(
&self,
command_id: &SlashCommandId,
command_id: &InvokedSlashCommandId,
) -> Option<&InvokedSlashCommand> {
self.invoked_slash_commands.get(command_id)
}
@@ -1375,8 +1384,6 @@ impl Context {
let mut removed_parsed_slash_command_ranges = Vec::new();
let mut updated_parsed_slash_commands = Vec::new();
let mut removed_patches = Vec::new();
let mut updated_patches = Vec::new();
while let Some(mut row_range) = row_ranges.next() {
while let Some(next_row_range) = row_ranges.peek() {
if row_range.end >= next_row_range.start {
@@ -1401,13 +1408,6 @@ impl Context {
cx,
);
self.invalidate_pending_slash_commands(&buffer, cx);
self.reparse_patches_in_range(
start..end,
&buffer,
&mut updated_patches,
&mut removed_patches,
cx,
);
}
if !updated_parsed_slash_commands.is_empty()
@@ -1418,13 +1418,6 @@ impl Context {
updated: updated_parsed_slash_commands,
});
}
if !updated_patches.is_empty() || !removed_patches.is_empty() {
cx.emit(ContextEvent::PatchesUpdated {
removed: removed_patches,
updated: updated_patches,
});
}
}
fn reparse_slash_commands_in_range(
@@ -1455,7 +1448,7 @@ impl Context {
})
.map(ToOwned::to_owned)
.collect::<SmallVec<_>>();
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
if let Some(command) = self.slash_commands.command(name, cx) {
if !command.requires_argument() || !arguments.is_empty() {
let start_ix = offset + command_line.name.start - 1;
let end_ix = offset
@@ -1515,267 +1508,6 @@ impl Context {
}
}
fn reparse_patches_in_range(
&mut self,
range: Range<text::Anchor>,
buffer: &BufferSnapshot,
updated: &mut Vec<Range<text::Anchor>>,
removed: &mut Vec<Range<text::Anchor>>,
cx: &mut ModelContext<Self>,
) {
// Rebuild the XML tags in the edited range.
let intersecting_tags_range =
self.indices_intersecting_buffer_range(&self.xml_tags, range.clone(), cx);
let new_tags = self.parse_xml_tags_in_range(buffer, range.clone(), cx);
self.xml_tags
.splice(intersecting_tags_range.clone(), new_tags);
// Find which patches intersect the changed range.
let intersecting_patches_range =
self.indices_intersecting_buffer_range(&self.patches, range.clone(), cx);
// Reparse all tags after the last unchanged patch before the change.
let mut tags_start_ix = 0;
if let Some(preceding_unchanged_patch) =
self.patches[..intersecting_patches_range.start].last()
{
tags_start_ix = match self.xml_tags.binary_search_by(|tag| {
tag.range
.start
.cmp(&preceding_unchanged_patch.range.end, buffer)
.then(Ordering::Less)
}) {
Ok(ix) | Err(ix) => ix,
};
}
// Rebuild the patches in the range.
let new_patches = self.parse_patches(tags_start_ix, range.end, buffer, cx);
updated.extend(new_patches.iter().map(|patch| patch.range.clone()));
let removed_patches = self.patches.splice(intersecting_patches_range, new_patches);
removed.extend(
removed_patches
.map(|patch| patch.range)
.filter(|range| !updated.contains(&range)),
);
}
fn parse_xml_tags_in_range(
&self,
buffer: &BufferSnapshot,
range: Range<text::Anchor>,
cx: &AppContext,
) -> Vec<XmlTag> {
let mut messages = self.messages(cx).peekable();
let mut tags = Vec::new();
let mut lines = buffer.text_for_range(range).lines();
let mut offset = lines.offset();
while let Some(line) = lines.next() {
while let Some(message) = messages.peek() {
if offset < message.offset_range.end {
break;
} else {
messages.next();
}
}
let is_assistant_message = messages
.peek()
.map_or(false, |message| message.role == Role::Assistant);
if is_assistant_message {
for (start_ix, _) in line.match_indices('<') {
let mut name_start_ix = start_ix + 1;
let closing_bracket_ix = line[start_ix..].find('>').map(|i| start_ix + i);
if let Some(closing_bracket_ix) = closing_bracket_ix {
let end_ix = closing_bracket_ix + 1;
let mut is_open_tag = true;
if line[name_start_ix..closing_bracket_ix].starts_with('/') {
name_start_ix += 1;
is_open_tag = false;
}
let tag_inner = &line[name_start_ix..closing_bracket_ix];
let tag_name_len = tag_inner
.find(|c: char| c.is_whitespace())
.unwrap_or(tag_inner.len());
if let Ok(kind) = XmlTagKind::from_str(&tag_inner[..tag_name_len]) {
tags.push(XmlTag {
range: buffer.anchor_after(offset + start_ix)
..buffer.anchor_before(offset + end_ix),
is_open_tag,
kind,
});
};
}
}
}
offset = lines.offset();
}
tags
}
fn parse_patches(
&mut self,
tags_start_ix: usize,
buffer_end: text::Anchor,
buffer: &BufferSnapshot,
cx: &AppContext,
) -> Vec<AssistantPatch> {
let mut new_patches = Vec::new();
let mut pending_patch = None;
let mut patch_tag_depth = 0;
let mut tags = self.xml_tags[tags_start_ix..].iter().peekable();
'tags: while let Some(tag) = tags.next() {
if tag.range.start.cmp(&buffer_end, buffer).is_gt() && patch_tag_depth == 0 {
break;
}
if tag.kind == XmlTagKind::Patch && tag.is_open_tag {
patch_tag_depth += 1;
let patch_start = tag.range.start;
let mut edits = Vec::<Result<AssistantEdit>>::new();
let mut patch = AssistantPatch {
range: patch_start..patch_start,
title: String::new().into(),
edits: Default::default(),
status: crate::AssistantPatchStatus::Pending,
};
while let Some(tag) = tags.next() {
if tag.kind == XmlTagKind::Patch && !tag.is_open_tag {
patch_tag_depth -= 1;
if patch_tag_depth == 0 {
patch.range.end = tag.range.end;
// Include the line immediately after this <patch> tag if it's empty.
let patch_end_offset = patch.range.end.to_offset(buffer);
let mut patch_end_chars = buffer.chars_at(patch_end_offset);
if patch_end_chars.next() == Some('\n')
&& patch_end_chars.next().map_or(true, |ch| ch == '\n')
{
let messages = self.messages_for_offsets(
[patch_end_offset, patch_end_offset + 1],
cx,
);
if messages.len() == 1 {
patch.range.end = buffer.anchor_before(patch_end_offset + 1);
}
}
edits.sort_unstable_by(|a, b| {
if let (Ok(a), Ok(b)) = (a, b) {
a.path.cmp(&b.path)
} else {
Ordering::Equal
}
});
patch.edits = edits.into();
patch.status = AssistantPatchStatus::Ready;
new_patches.push(patch);
continue 'tags;
}
}
if tag.kind == XmlTagKind::Title && tag.is_open_tag {
let content_start = tag.range.end;
while let Some(tag) = tags.next() {
if tag.kind == XmlTagKind::Title && !tag.is_open_tag {
let content_end = tag.range.start;
patch.title =
trimmed_text_in_range(buffer, content_start..content_end)
.into();
break;
}
}
}
if tag.kind == XmlTagKind::Edit && tag.is_open_tag {
let mut path = None;
let mut old_text = None;
let mut new_text = None;
let mut operation = None;
let mut description = None;
while let Some(tag) = tags.next() {
if tag.kind == XmlTagKind::Edit && !tag.is_open_tag {
edits.push(AssistantEdit::new(
path,
operation,
old_text,
new_text,
description,
));
break;
}
if tag.is_open_tag
&& [
XmlTagKind::Path,
XmlTagKind::OldText,
XmlTagKind::NewText,
XmlTagKind::Operation,
XmlTagKind::Description,
]
.contains(&tag.kind)
{
let kind = tag.kind;
let content_start = tag.range.end;
if let Some(tag) = tags.peek() {
if tag.kind == kind && !tag.is_open_tag {
let tag = tags.next().unwrap();
let content_end = tag.range.start;
let content = trimmed_text_in_range(
buffer,
content_start..content_end,
);
match kind {
XmlTagKind::Path => path = Some(content),
XmlTagKind::Operation => operation = Some(content),
XmlTagKind::OldText => {
old_text = Some(content).filter(|s| !s.is_empty())
}
XmlTagKind::NewText => {
new_text = Some(content).filter(|s| !s.is_empty())
}
XmlTagKind::Description => {
description =
Some(content).filter(|s| !s.is_empty())
}
_ => {}
}
}
}
}
}
}
}
patch.edits = edits.into();
pending_patch = Some(patch);
}
}
if let Some(mut pending_patch) = pending_patch {
let patch_start = pending_patch.range.start.to_offset(buffer);
if let Some(message) = self.message_for_offset(patch_start, cx) {
if message.anchor_range.end == text::Anchor::MAX {
pending_patch.range.end = text::Anchor::MAX;
} else {
let message_end = buffer.anchor_after(message.offset_range.end - 1);
pending_patch.range.end = message_end;
}
} else {
pending_patch.range.end = text::Anchor::MAX;
}
new_patches.push(pending_patch);
}
new_patches
}
pub fn pending_command_for_position(
&mut self,
position: language::Anchor,
@@ -1847,7 +1579,7 @@ impl Context {
cx: &mut ModelContext<Self>,
) {
let version = self.version.clone();
let command_id = SlashCommandId(self.next_timestamp());
let command_id = InvokedSlashCommandId(self.next_timestamp());
const PENDING_OUTPUT_END_MARKER: &str = "";
@@ -1900,7 +1632,6 @@ impl Context {
}
let mut pending_section_stack: Vec<PendingSection> = Vec::new();
let mut run_commands_in_ranges: Vec<Range<language::Anchor>> = Vec::new();
let mut last_role: Option<Role> = None;
let mut last_section_range = None;
@@ -1966,10 +1697,16 @@ impl Context {
let end = this.buffer.read(cx).anchor_before(insert_position);
if run_commands_in_text {
run_commands_in_ranges.push(start..end);
if let Some(invoked_slash_command) =
this.invoked_slash_commands.get_mut(&command_id)
{
invoked_slash_command
.run_commands_in_ranges
.push(start..end);
}
}
}
SlashCommandEvent::EndSection { metadata } => {
SlashCommandEvent::EndSection => {
if let Some(pending_section) = pending_section_stack.pop() {
let offset_range = (pending_section.start..insert_position)
.to_offset(this.buffer.read(cx));
@@ -1983,7 +1720,7 @@ impl Context {
range: range.clone(),
icon: pending_section.icon,
label: pending_section.label,
metadata: metadata.or(pending_section.metadata),
metadata: pending_section.metadata,
},
cx,
);
@@ -2086,6 +1823,7 @@ impl Context {
InvokedSlashCommand {
name: name.to_string().into(),
range: command_range.clone(),
run_commands_in_ranges: Vec::new(),
status: InvokedSlashCommandStatus::Running(insert_output_task),
transaction: Some(first_transaction),
timestamp: command_id.0,
@@ -2227,9 +1965,9 @@ impl Context {
let mut request = self.to_completion_request(request_type, cx);
if cx.has_flag::<ToolUseFeatureFlag>() {
let tool_registry = ToolRegistry::global(cx);
request.tools = tool_registry
.tools()
request.tools = self
.tools
.tools(cx)
.into_iter()
.map(|tool| LanguageModelRequestTool {
name: tool.name(),
@@ -2369,7 +2107,11 @@ impl Context {
});
Some(error.to_string())
} else {
let error_message = error.to_string().trim().to_string();
let error_message = error
.chain()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
error_message.clone(),
)));
@@ -2450,9 +2192,22 @@ impl Context {
}
}
let tools = if let RequestType::SuggestEdits = request_type {
vec![{
let tool = CodeEditsTool;
LanguageModelRequestTool {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
}
}]
} else {
Vec::new()
};
let mut completion_request = LanguageModelRequest {
messages: Vec::new(),
tools: Vec::new(),
tools,
stop: Vec::new(),
temperature: None,
};
@@ -2525,25 +2280,6 @@ impl Context {
completion_request.messages.push(request_message);
}
if let RequestType::SuggestEdits = request_type {
if let Ok(preamble) = self.prompt_builder.generate_suggest_edits_prompt() {
let last_elem_index = completion_request.messages.len();
completion_request
.messages
.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![MessageContent::Text(preamble)],
cache: false,
});
// The preamble message should be sent right before the last actual user message.
completion_request
.messages
.swap(last_elem_index, last_elem_index.saturating_sub(1));
}
}
completion_request
}
@@ -2567,28 +2303,6 @@ impl Context {
self.update_metadata(*id, cx, |metadata| metadata.role = role);
}
}
self.message_roles_updated(ids, cx);
}
fn message_roles_updated(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
let mut ranges = Vec::new();
for message in self.messages(cx) {
if ids.contains(&message.id) {
ranges.push(message.anchor_range.clone());
}
}
let buffer = self.buffer.read(cx).text_snapshot();
let mut updated = Vec::new();
let mut removed = Vec::new();
for range in ranges {
self.reparse_patches_in_range(range, &buffer, &mut updated, &mut removed, cx);
}
if !updated.is_empty() || !removed.is_empty() {
cx.emit(ContextEvent::PatchesUpdated { removed, updated })
}
}
pub fn update_metadata(
@@ -2873,7 +2587,7 @@ impl Context {
request.messages.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![
"Generate a concise 3-7 word title for this conversation, omitting punctuation"
"Generate a concise 3-7 word title for this conversation, omitting punctuation. Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`"
.into(),
],
cache: false,
@@ -3158,6 +2872,7 @@ pub struct ParsedSlashCommand {
pub struct InvokedSlashCommand {
pub name: SharedString,
pub range: Range<language::Anchor>,
pub run_commands_in_ranges: Vec<Range<language::Anchor>>,
pub status: InvokedSlashCommandStatus,
pub transaction: Option<language::TransactionId>,
timestamp: clock::Lamport,

View File

@@ -1,9 +1,11 @@
use super::{AssistantEdit, MessageCacheMetadata};
use super::MessageCacheMetadata;
use crate::slash_command_working_set::SlashCommandWorkingSet;
use crate::{
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
Context, ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
SlashCommandId,
Context, ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId,
MessageStatus, PromptBuilder,
};
use crate::{AssistantEdit, ToolWorkingSet};
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
@@ -49,8 +51,17 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context =
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
let context = cx.new_model(|cx| {
Context::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
let buffer = context.read(cx).buffer.clone();
let message_1 = context.read(cx).message_anchors[0].clone();
@@ -182,8 +193,17 @@ fn test_message_splitting(cx: &mut AppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context =
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
let buffer = context.read(cx).buffer.clone();
let message_1 = context.read(cx).message_anchors[0].clone();
@@ -277,8 +297,17 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context =
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
let context = cx.new_model(|cx| {
Context::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
let buffer = context.read(cx).buffer.clone();
let message_1 = context.read(cx).message_anchors[0].clone();
@@ -383,13 +412,22 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context =
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
#[derive(Default)]
struct ContextRanges {
parsed_commands: HashSet<Range<language::Anchor>>,
command_outputs: HashMap<SlashCommandId, Range<language::Anchor>>,
command_outputs: HashMap<InvokedSlashCommandId, Range<language::Anchor>>,
output_sections: HashSet<Range<language::Anchor>>,
}
@@ -545,7 +583,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
);
command_output_tx
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))
.unbounded_send(Ok(SlashCommandEvent::EndSection))
.unwrap();
cx.run_until_parked();
assert_text_and_context_ranges(
@@ -671,6 +709,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
Some(project),
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
@@ -934,6 +974,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
Default::default(),
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1042,8 +1084,17 @@ async fn test_serialization(cx: &mut TestAppContext) {
cx.update(assistant_panel::init);
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context =
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
let buffer = context.read_with(cx, |context, _| context.buffer.clone());
let message_0 = context.read_with(cx, |context, _| context.message_anchors[0].id);
let message_1 = context.update(cx, |context, cx| {
@@ -1083,6 +1134,8 @@ async fn test_serialization(cx: &mut TestAppContext) {
Default::default(),
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1141,6 +1194,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
language::Capability::ReadWrite,
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
None,
None,
cx,
@@ -1255,7 +1310,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
text: output_text[section_start..section_end].to_string(),
run_commands_in_text: false,
})));
events.push(Ok(SlashCommandEvent::EndSection { metadata: None }));
events.push(Ok(SlashCommandEvent::EndSection));
section_start = section_end;
}
@@ -1394,8 +1449,17 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context =
cx.new_model(|cx| Context::local(registry, None, None, prompt_builder.clone(), cx));
let context = cx.new_model(|cx| {
Context::local(
registry,
None,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
let buffer = context.read(cx).buffer.clone();
// Create a test cache configuration

View File

@@ -1,10 +1,15 @@
use crate::slash_command::context_server_command;
use crate::{
prompts::PromptBuilder, Context, ContextEvent, ContextId, ContextOperation, ContextVersion,
SavedContext, SavedContextMetadata,
prompts::PromptBuilder, slash_command_working_set::SlashCommandWorkingSet, Context,
ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext, SavedContextMetadata,
};
use crate::{tools, SlashCommandId, ToolId, ToolWorkingSet};
use anyhow::{anyhow, Context as _, Result};
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
use clock::ReplicaId;
use collections::HashMap;
use context_servers::manager::ContextServerManager;
use context_servers::ContextServerFactoryRegistry;
use fs::Fs;
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
@@ -43,9 +48,14 @@ pub struct RemoteContextMetadata {
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
context_server_manager: Model<ContextServerManager>,
context_server_slash_command_ids: HashMap<Arc<str>, Vec<SlashCommandId>>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
host_contexts: Vec<RemoteContextMetadata>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
telemetry: Arc<Telemetry>,
_watch_updates: Task<Option<()>>,
client: Arc<Client>,
@@ -87,6 +97,8 @@ impl ContextStore {
pub fn new(
project: Model<Project>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
let fs = project.read(cx).fs().clone();
@@ -97,12 +109,22 @@ impl ContextStore {
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new_model(|cx: &mut ModelContext<Self>| {
let context_server_factory_registry =
ContextServerFactoryRegistry::default_global(cx);
let context_server_manager = cx.new_model(|cx| {
ContextServerManager::new(context_server_factory_registry, project.clone(), cx)
});
let mut this = Self {
contexts: Vec::new(),
contexts_metadata: Vec::new(),
context_server_manager,
context_server_slash_command_ids: HashMap::default(),
context_server_tool_ids: HashMap::default(),
host_contexts: Vec::new(),
fs,
languages,
slash_commands,
tools,
telemetry,
_watch_updates: cx.spawn(|this, mut cx| {
async move {
@@ -125,13 +147,15 @@ impl ContextStore {
project: project.clone(),
prompt_builder,
};
this.handle_project_changed(project, cx);
this.handle_project_changed(project.clone(), cx);
this.synchronize_contexts(cx);
this.register_context_server_handlers(cx);
this
})?;
this.update(&mut cx, |this, cx| this.reload(cx))?
.await
.log_err();
Ok(this)
})
}
@@ -342,6 +366,8 @@ impl ContextStore {
Some(self.project.clone()),
Some(self.telemetry.clone()),
self.prompt_builder.clone(),
self.slash_commands.clone(),
self.tools.clone(),
cx,
)
});
@@ -364,6 +390,8 @@ impl ContextStore {
let project = self.project.clone();
let telemetry = self.telemetry.clone();
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
let tools = self.tools.clone();
let request = self.client.request(proto::CreateContext { project_id });
cx.spawn(|this, mut cx| async move {
let response = request.await?;
@@ -376,6 +404,8 @@ impl ContextStore {
capability,
language_registry,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -425,6 +455,8 @@ impl ContextStore {
}
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
let tools = self.tools.clone();
cx.spawn(|this, mut cx| async move {
let saved_context = load.await?;
@@ -434,6 +466,8 @@ impl ContextStore {
path.clone(),
languages,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -500,6 +534,8 @@ impl ContextStore {
context_id: context_id.to_proto(),
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
let tools = self.tools.clone();
cx.spawn(|this, mut cx| async move {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
@@ -510,6 +546,8 @@ impl ContextStore {
capability,
language_registry,
prompt_builder,
slash_commands,
tools,
Some(project),
Some(telemetry),
cx,
@@ -745,4 +783,114 @@ impl ContextStore {
})
})
}
pub fn restart_context_servers(&mut self, cx: &mut ModelContext<Self>) {
cx.update_model(
&self.context_server_manager,
|context_server_manager, cx| {
for server in context_server_manager.servers() {
context_server_manager
.restart_server(&server.id(), cx)
.detach_and_log_err(cx);
}
},
);
}
fn register_context_server_handlers(&self, cx: &mut ModelContext<Self>) {
cx.subscribe(
&self.context_server_manager.clone(),
Self::handle_context_server_event,
)
.detach();
}
fn handle_context_server_event(
&mut self,
context_server_manager: Model<ContextServerManager>,
event: &context_servers::manager::Event,
cx: &mut ModelContext<Self>,
) {
let slash_command_working_set = self.slash_commands.clone();
let tool_working_set = self.tools.clone();
match event {
context_servers::manager::Event::ServerStarted { server_id } => {
if let Some(server) = context_server_manager.read(cx).get_server(server_id) {
let context_server_manager = context_server_manager.clone();
cx.spawn({
let server = server.clone();
let server_id = server_id.clone();
|this, mut cx| async move {
let Some(protocol) = server.client() else {
return;
};
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
if let Some(prompts) = protocol.list_prompts().await.log_err() {
let slash_command_ids = prompts
.into_iter()
.filter(context_server_command::acceptable_prompt)
.map(|prompt| {
log::info!(
"registering context server command: {:?}",
prompt.name
);
slash_command_working_set.insert(Arc::new(
context_server_command::ContextServerSlashCommand::new(
context_server_manager.clone(),
&server,
prompt,
),
))
})
.collect::<Vec<_>>();
this.update(&mut cx, |this, _cx| {
this.context_server_slash_command_ids
.insert(server_id.clone(), slash_command_ids);
})
.log_err();
}
}
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
if let Some(tools) = protocol.list_tools().await.log_err() {
let tool_ids = tools.tools.into_iter().map(|tool| {
log::info!("registering context server tool: {:?}", tool.name);
tool_working_set.insert(
Arc::new(tools::context_server_tool::ContextServerTool::new(
context_server_manager.clone(),
server.id(),
tool,
)),
)
}).collect::<Vec<_>>();
this.update(&mut cx, |this, _cx| {
this.context_server_tool_ids
.insert(server_id, tool_ids);
})
.log_err();
}
}
}
})
.detach();
}
}
context_servers::manager::Event::ServerStopped { server_id } => {
if let Some(slash_command_ids) =
self.context_server_slash_command_ids.remove(server_id)
{
slash_command_working_set.remove(&slash_command_ids);
}
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
tool_working_set.remove(&tool_ids);
}
}
}
}
}

View File

@@ -24,9 +24,9 @@ use futures::{
join, SinkExt, Stream, StreamExt,
};
use gpui::{
anchored, deferred, point, AnyElement, AppContext, ClickEvent, EventEmitter, FocusHandle,
FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext, Subscription, Task,
TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
anchored, deferred, point, AnyElement, AppContext, ClickEvent, CursorStyle, EventEmitter,
FocusHandle, FocusableView, FontWeight, Global, HighlightStyle, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
};
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
use language_model::{
@@ -86,7 +86,7 @@ pub struct InlineAssistant {
confirmed_assists: HashMap<InlineAssistId, Model<CodegenAlternative>>,
prompt_history: VecDeque<String>,
prompt_builder: Arc<PromptBuilder>,
telemetry: Option<Arc<Telemetry>>,
telemetry: Arc<Telemetry>,
fs: Arc<dyn Fs>,
}
@@ -107,7 +107,7 @@ impl InlineAssistant {
confirmed_assists: HashMap::default(),
prompt_history: VecDeque::default(),
prompt_builder,
telemetry: Some(telemetry),
telemetry,
fs,
}
}
@@ -243,19 +243,17 @@ impl InlineAssistant {
codegen_ranges.push(start..end);
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
if let Some(telemetry) = self.telemetry.as_ref() {
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Invoked,
message_id: None,
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name: buffer.language().map(|language| language.name().to_proto()),
});
}
self.telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Invoked,
message_id: None,
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name: buffer.language().map(|language| language.name().to_proto()),
});
}
}
@@ -462,7 +460,7 @@ impl InlineAssistant {
style: BlockStyle::Sticky,
placement: BlockPlacement::Below(range.end),
height: 0,
render: Box::new(|cx| {
render: Arc::new(|cx| {
v_flex()
.h_full()
.w_full()
@@ -818,7 +816,7 @@ impl InlineAssistant {
error_message: None,
language_name: language_name.map(|name| name.to_proto()),
},
self.telemetry.clone(),
Some(self.telemetry.clone()),
cx.http_client(),
model.api_key(cx),
cx.background_executor(),
@@ -1199,8 +1197,9 @@ impl InlineAssistant {
placement: BlockPlacement::Above(new_row),
height,
style: BlockStyle::Flex,
render: Box::new(move |cx| {
render: Arc::new(move |cx| {
div()
.block_mouse_down()
.bg(cx.theme().status().deleted_background)
.size_full()
.h(height as f32 * cx.line_height())
@@ -1319,7 +1318,7 @@ impl InlineAssistGroup {
fn build_assist_editor_renderer(editor: &View<PromptEditor>) -> RenderBlock {
let editor = editor.clone();
Box::new(move |cx: &mut BlockContext| {
Arc::new(move |cx: &mut BlockContext| {
*editor.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
editor.clone().into_any_element()
})
@@ -1482,6 +1481,8 @@ impl Render for PromptEditor {
h_flex()
.key_context("PromptEditor")
.bg(cx.theme().colors().editor_background)
.block_mouse_down()
.cursor(CursorStyle::Arrow)
.border_y_1()
.border_color(cx.theme().status().info_border)
.size_full()
@@ -1759,6 +1760,20 @@ impl PromptEditor {
) {
match event {
EditorEvent::Edited { .. } => {
if let Some(workspace) = cx.window_handle().downcast::<Workspace>() {
workspace
.update(cx, |workspace, cx| {
let is_via_ssh = workspace
.project()
.update(cx, |project, _| project.is_via_ssh());
workspace
.client()
.telemetry()
.log_edit_event("inline assist", is_via_ssh);
})
.log_err();
}
let prompt = self.editor.read(cx).text(cx);
if self
.prompt_history_ix
@@ -2337,7 +2352,7 @@ pub struct Codegen {
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
telemetry: Option<Arc<Telemetry>>,
telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>,
is_insertion: bool,
}
@@ -2347,7 +2362,7 @@ impl Codegen {
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
initial_transaction_id: Option<TransactionId>,
telemetry: Option<Arc<Telemetry>>,
telemetry: Arc<Telemetry>,
builder: Arc<PromptBuilder>,
cx: &mut ModelContext<Self>,
) -> Self {
@@ -2356,7 +2371,7 @@ impl Codegen {
buffer.clone(),
range.clone(),
false,
telemetry.clone(),
Some(telemetry.clone()),
builder.clone(),
cx,
)
@@ -2447,7 +2462,7 @@ impl Codegen {
self.buffer.clone(),
self.range.clone(),
false,
self.telemetry.clone(),
Some(self.telemetry.clone()),
self.builder.clone(),
cx,
)
@@ -2550,6 +2565,7 @@ pub struct CodegenAlternative {
line_operations: Vec<LineOperation>,
request: Option<LanguageModelRequest>,
elapsed_time: Option<f64>,
completion: Option<String>,
message_id: Option<String>,
}
@@ -2625,6 +2641,7 @@ impl CodegenAlternative {
range,
request: None,
elapsed_time: None,
completion: None,
}
}
@@ -2837,6 +2854,9 @@ impl CodegenAlternative {
self.diff = Diff::default();
self.status = CodegenStatus::Pending;
let mut edit_start = self.range.start.to_offset(&snapshot);
let completion = Arc::new(Mutex::new(String::new()));
let completion_clone = completion.clone();
self.generation = cx.spawn(|codegen, mut cx| {
async move {
let stream = stream.await;
@@ -2868,6 +2888,7 @@ impl CodegenAlternative {
response_latency = Some(request_start.elapsed());
}
let chunk = chunk?;
completion_clone.lock().push_str(&chunk);
let mut lines = chunk.split('\n').peekable();
while let Some(line) = lines.next() {
@@ -3037,6 +3058,7 @@ impl CodegenAlternative {
this.status = CodegenStatus::Done;
}
this.elapsed_time = Some(elapsed_time);
this.completion = Some(completion.lock().clone());
cx.emit(CodegenEvent::Finished);
cx.notify();
})

View File

@@ -1,21 +1,17 @@
use feature_flags::ZedPro;
use gpui::Action;
use gpui::DismissEvent;
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
use proto::Plan;
use workspace::ShowConfiguration;
use std::sync::Arc;
use ui::ListItemSpacing;
use crate::assistant_settings::AssistantSettings;
use fs::Fs;
use gpui::SharedString;
use gpui::Task;
use gpui::{Action, AnyElement, DismissEvent, SharedString, Task};
use picker::{Picker, PickerDelegate};
use settings::update_settings_file;
use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
@@ -85,14 +81,36 @@ impl PickerDelegate for ModelPickerDelegate {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let all_models = self.all_models.clone();
let llm_registry = LanguageModelRegistry::global(cx);
let configured_models: Vec<_> = llm_registry
.read(cx)
.providers()
.iter()
.filter(|provider| provider.is_authenticated(cx))
.map(|provider| provider.id())
.collect();
cx.spawn(|this, mut cx| async move {
let filtered_models = cx
.background_executor()
.spawn(async move {
if query.is_empty() {
let displayed_models = if configured_models.is_empty() {
all_models
} else {
all_models
.into_iter()
.filter(|model_info| {
configured_models.contains(&model_info.model.provider_id())
})
.collect::<Vec<_>>()
};
if query.is_empty() {
displayed_models
} else {
displayed_models
.into_iter()
.filter(|model_info| {
model_info
@@ -141,6 +159,29 @@ impl PickerDelegate for ModelPickerDelegate {
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
fn render_header(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
let configured_models_count = LanguageModelRegistry::global(cx)
.read(cx)
.providers()
.iter()
.filter(|provider| provider.is_authenticated(cx))
.count();
if configured_models_count > 0 {
Some(
Label::new("Configured Models")
.size(LabelSize::Small)
.color(Color::Muted)
.mt_1()
.mb_0p5()
.ml_3()
.into_any_element(),
)
} else {
None
}
}
fn render_match(
&self,
ix: usize,
@@ -148,9 +189,10 @@ impl PickerDelegate for ModelPickerDelegate {
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
use feature_flags::FeatureFlagAppExt;
let model_info = self.filtered_models.get(ix)?;
let show_badges = cx.has_flag::<ZedPro>();
let provider_name: String = model_info.model.provider_name().0.into();
let model_info = self.filtered_models.get(ix)?;
let provider_name: String = model_info.model.provider_name().0.clone().into();
Some(
ListItem::new(ix)
@@ -165,27 +207,32 @@ impl PickerDelegate for ModelPickerDelegate {
),
)
.child(
h_flex().w_full().justify_between().min_w(px(200.)).child(
h_flex()
.gap_1p5()
.child(Label::new(model_info.model.name().0.clone()))
.child(
Label::new(provider_name)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.children(match model_info.availability {
LanguageModelAvailability::Public => None,
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
show_badges.then(|| {
Label::new("Pro")
.size(LabelSize::XSmall)
.color(Color::Muted)
})
}
}),
),
h_flex()
.w_full()
.items_center()
.gap_1p5()
.min_w(px(200.))
.child(Label::new(model_info.model.name().0.clone()))
.child(
h_flex()
.gap_0p5()
.child(
Label::new(provider_name)
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.children(match model_info.availability {
LanguageModelAvailability::Public => None,
LanguageModelAvailability::RequiresPlan(Plan::Free) => None,
LanguageModelAvailability::RequiresPlan(Plan::ZedPro) => {
show_badges.then(|| {
Label::new("Pro")
.size(LabelSize::XSmall)
.color(Color::Muted)
})
}
}),
),
)
.end_slot(div().when(model_info.is_selected, |this| {
this.child(
@@ -213,7 +260,7 @@ impl PickerDelegate for ModelPickerDelegate {
.justify_between()
.when(cx.has_flag::<ZedPro>(), |this| {
this.child(match plan {
// Already a zed pro subscriber
// Already a Zed Pro subscriber
Plan::ZedPro => Button::new("zed-pro", "Zed Pro")
.icon(IconName::ZedAssistant)
.icon_size(IconSize::Small)
@@ -254,6 +301,7 @@ impl<T: PopoverTrigger> RenderOnce for ModelSelector<T> {
let selected_provider = LanguageModelRegistry::read_global(cx)
.active_provider()
.map(|m| m.id());
let selected_model = LanguageModelRegistry::read_global(cx)
.active_model()
.map(|m| m.id());

View File

@@ -1,3 +1,4 @@
use crate::SlashCommandWorkingSet;
use crate::{slash_command::SlashCommandCompletionProvider, AssistantPanel, InlineAssistant};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
@@ -522,7 +523,11 @@ impl PromptLibrary {
editor.set_use_modal_editing(false);
editor.set_current_line_highlight(Some(CurrentLineHighlight::None));
editor.set_completion_provider(Some(Box::new(
SlashCommandCompletionProvider::new(None, None),
SlashCommandCompletionProvider::new(
Arc::new(SlashCommandWorkingSet::default()),
None,
None,
),
)));
if focus {
editor.focus(cx);
@@ -825,7 +830,7 @@ impl PromptLibrary {
.overflow_x_hidden()
.child(
h_flex()
.p(Spacing::Small.rems(cx))
.p(DynamicSpacing::Base04.rems(cx))
.h_9()
.w_full()
.flex_none()
@@ -866,17 +871,17 @@ impl PromptLibrary {
.size_full()
.relative()
.overflow_hidden()
.pl(Spacing::XXLarge.rems(cx))
.pt(Spacing::Large.rems(cx))
.pl(DynamicSpacing::Base16.rems(cx))
.pt(DynamicSpacing::Base08.rems(cx))
.on_click(cx.listener(move |_, _, cx| {
cx.focus(&focus_handle);
}))
.child(
h_flex()
.group("active-editor-header")
.pr(Spacing::XXLarge.rems(cx))
.pt(Spacing::XSmall.rems(cx))
.pb(Spacing::Large.rems(cx))
.pr(DynamicSpacing::Base16.rems(cx))
.pt(DynamicSpacing::Base02.rems(cx))
.pb(DynamicSpacing::Base08.rems(cx))
.justify_between()
.child(
h_flex().gap_1().child(
@@ -938,13 +943,13 @@ impl PromptLibrary {
.child(
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.gap(DynamicSpacing::Base16.rems(cx))
.child(div()),
)
.child(
h_flex()
.h_full()
.gap(Spacing::XXLarge.rems(cx))
.gap(DynamicSpacing::Base16.rems(cx))
.children(prompt_editor.token_count.map(
|token_count| {
let token_count: SharedString =

View File

@@ -149,7 +149,7 @@ impl PromptBuilder {
if file_path.to_string_lossy().ends_with(".hbs") {
if let Ok(content) = params.fs.load(&file_path).await {
let file_name = file_path.file_stem().unwrap().to_string_lossy();
log::info!("Registering prompt template override: {}", file_name);
log::debug!("Registering prompt template override: {}", file_name);
handlebars.lock().register_template_string(&file_name, content).log_err();
}
}
@@ -194,7 +194,7 @@ impl PromptBuilder {
for path in Assets.list("prompts")? {
if let Some(id) = path.split('/').last().and_then(|s| s.strip_suffix(".hbs")) {
if let Some(prompt) = Assets.load(path.as_ref()).log_err().flatten() {
log::info!("Registering built-in prompt template: {}", id);
log::debug!("Registering built-in prompt template: {}", id);
let prompt = String::from_utf8_lossy(prompt.as_ref());
handlebars.register_template_string(id, LineEnding::normalize_cow(prompt))?
}
@@ -310,10 +310,6 @@ impl PromptBuilder {
.render("terminal_assistant_prompt", &context)
}
pub fn generate_suggest_edits_prompt(&self) -> Result<String, RenderError> {
self.handlebars.lock().render("suggest_edits", &())
}
pub fn generate_project_slash_command_prompt(
&self,
context_buffer: String,

View File

@@ -1,7 +1,8 @@
use crate::assistant_panel::ContextEditor;
use crate::SlashCommandWorkingSet;
use anyhow::Result;
use assistant_slash_command::AfterCompletion;
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandRegistry};
pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
@@ -39,6 +40,7 @@ pub mod terminal_command;
pub(crate) struct SlashCommandCompletionProvider {
cancel_flag: Mutex<Arc<AtomicBool>>,
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
}
@@ -52,11 +54,13 @@ pub(crate) struct SlashCommandLine {
impl SlashCommandCompletionProvider {
pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>,
editor: Option<WeakView<ContextEditor>>,
workspace: Option<WeakView<Workspace>>,
) -> Self {
Self {
cancel_flag: Mutex::new(Arc::new(AtomicBool::new(false))),
slash_commands,
editor,
workspace,
}
@@ -69,9 +73,9 @@ impl SlashCommandCompletionProvider {
name_range: Range<Anchor>,
cx: &mut WindowContext,
) -> Task<Result<Vec<project::Completion>>> {
let commands = SlashCommandRegistry::global(cx);
let candidates = commands
.command_names()
let slash_commands = self.slash_commands.clone();
let candidates = slash_commands
.command_names(cx)
.into_iter()
.enumerate()
.map(|(ix, def)| StringMatchCandidate {
@@ -98,7 +102,7 @@ impl SlashCommandCompletionProvider {
matches
.into_iter()
.filter_map(|mat| {
let command = commands.command(&mat.string)?;
let command = slash_commands.command(&mat.string, cx)?;
let mut new_text = mat.string.clone();
let requires_argument = command.requires_argument();
let accepts_arguments = command.accepts_arguments();
@@ -167,8 +171,7 @@ impl SlashCommandCompletionProvider {
let mut flag = self.cancel_flag.lock();
flag.store(true, SeqCst);
*flag = new_cancel_flag.clone();
let commands = SlashCommandRegistry::global(cx);
if let Some(command) = commands.command(command_name) {
if let Some(command) = self.slash_commands.command(command_name, cx) {
let completions = command.complete_argument(
arguments,
new_cancel_flag.clone(),

View File

@@ -8,7 +8,7 @@ use context_servers::{
manager::{ContextServer, ContextServerManager},
types::Prompt,
};
use gpui::{AppContext, Task, WeakView, WindowContext};
use gpui::{AppContext, Model, Task, WeakView, WindowContext};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -19,15 +19,21 @@ use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub struct ContextServerSlashCommand {
server_id: String,
server_manager: Model<ContextServerManager>,
server_id: Arc<str>,
prompt: Prompt,
}
impl ContextServerSlashCommand {
pub fn new(server: &Arc<ContextServer>, prompt: Prompt) -> Self {
pub fn new(
server_manager: Model<ContextServerManager>,
server: &Arc<ContextServer>,
prompt: Prompt,
) -> Self {
Self {
server_id: server.id.clone(),
server_id: server.id(),
prompt,
server_manager,
}
}
}
@@ -74,20 +80,16 @@ impl SlashCommand for ContextServerSlashCommand {
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let Ok((arg_name, arg_value)) = completion_argument(&self.prompt, arguments) else {
return Task::ready(Err(anyhow!("Failed to complete argument")));
};
let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone();
let manager = ContextServerManager::global(cx);
let manager = manager.read(cx);
let (arg_name, arg_val) = match completion_argument(&self.prompt, arguments) {
Ok(tp) => tp,
Err(e) => {
return Task::ready(Err(e));
}
};
if let Some(server) = manager.get_server(&server_id) {
if let Some(server) = self.server_manager.read(cx).get_server(&server_id) {
cx.foreground_executor().spawn(async move {
let Some(protocol) = server.client.read().clone() else {
let Some(protocol) = server.client() else {
return Err(anyhow!("Context server not initialized"));
};
@@ -100,7 +102,7 @@ impl SlashCommand for ContextServerSlashCommand {
},
),
arg_name,
arg_val,
arg_value,
)
.await?;
@@ -138,11 +140,10 @@ impl SlashCommand for ContextServerSlashCommand {
Err(e) => return Task::ready(Err(e)),
};
let manager = ContextServerManager::global(cx);
let manager = manager.read(cx);
let manager = self.server_manager.read(cx);
if let Some(server) = manager.get_server(&server_id) {
cx.foreground_executor().spawn(async move {
let Some(protocol) = server.client.read().clone() else {
let Some(protocol) = server.client() else {
return Err(anyhow!("Context server not initialized"));
};
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
@@ -151,7 +152,7 @@ impl SlashCommand for ContextServerSlashCommand {
if result
.messages
.iter()
.any(|msg| !matches!(msg.role, context_servers::types::SamplingRole::User))
.any(|msg| !matches!(msg.role, context_servers::types::Role::User))
{
return Err(anyhow!(
"Prompt contains non-user roles, which is not supported"
@@ -163,7 +164,7 @@ impl SlashCommand for ContextServerSlashCommand {
.messages
.into_iter()
.filter_map(|msg| match msg.content {
context_servers::types::SamplingContent::Text { text } => Some(text),
context_servers::types::MessageContent::Text { text } => Some(text),
_ => None,
})
.collect::<Vec<String>>()

View File

@@ -69,6 +69,10 @@ impl SlashCommand for DefaultSlashCommand {
text.push('\n');
}
if !text.ends_with('\n') {
text.push('\n');
}
Ok(SlashCommandOutput {
sections: vec![SlashCommandOutputSection {
range: 0..text.len(),

View File

@@ -256,8 +256,7 @@ fn collect_files(
break;
}
directory_stack.pop().unwrap();
events_tx
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "\n".into(),
@@ -362,7 +361,7 @@ fn collect_files(
}
while let Some(_) = directory_stack.pop() {
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
}
}

View File

@@ -84,7 +84,7 @@ impl SlashCommand for SelectionCommand {
text,
run_commands_in_text: false,
})));
events.push(Ok(SlashCommandEvent::EndSection { metadata: None }));
events.push(Ok(SlashCommandEvent::EndSection));
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".to_string(),
run_commands_in_text: false,

View File

@@ -74,7 +74,7 @@ impl SlashCommand for StreamingExampleSlashCommand {
run_commands_in_text: false,
},
)))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
Timer::after(Duration::from_secs(1)).await;
@@ -89,7 +89,7 @@ impl SlashCommand for StreamingExampleSlashCommand {
run_commands_in_text: false,
},
)))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
for n in 1..=10 {
Timer::after(Duration::from_secs(1)).await;
@@ -105,8 +105,7 @@ impl SlashCommand for StreamingExampleSlashCommand {
run_commands_in_text: false,
},
)))?;
events_tx
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection))?;
}
anyhow::Ok(())

View File

@@ -1,16 +1,15 @@
use std::sync::Arc;
use assistant_slash_command::SlashCommandRegistry;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
use crate::assistant_panel::ContextEditor;
use crate::SlashCommandWorkingSet;
#[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
registry: Arc<SlashCommandRegistry>,
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakView<ContextEditor>,
trigger: T,
}
@@ -51,12 +50,12 @@ pub(crate) struct SlashCommandDelegate {
impl<T: PopoverTrigger> SlashCommandSelector<T> {
pub(crate) fn new(
registry: Arc<SlashCommandRegistry>,
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakView<ContextEditor>,
trigger: T,
) -> Self {
SlashCommandSelector {
registry,
working_set,
active_context_editor,
trigger,
}
@@ -231,11 +230,11 @@ impl PickerDelegate for SlashCommandDelegate {
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let all_models = self
.registry
.featured_command_names()
.working_set
.featured_command_names(cx)
.into_iter()
.filter_map(|command_name| {
let command = self.registry.command(&command_name)?;
let command = self.working_set.command(&command_name, cx)?;
let menu_text = SharedString::from(Arc::from(command.menu_text()));
let label = command.label(cx);
let args = label.filter_range.end.ne(&label.text.len()).then(|| {

View File

@@ -0,0 +1,79 @@
use assistant_slash_command::{SlashCommand, SlashCommandRegistry};
use collections::HashMap;
use gpui::AppContext;
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct SlashCommandId(usize);
/// A working set of slash commands for use in one instance of the Assistant Panel.
#[derive(Default)]
pub struct SlashCommandWorkingSet {
state: Mutex<WorkingSetState>,
}
#[derive(Default)]
struct WorkingSetState {
context_server_commands_by_id: HashMap<SlashCommandId, Arc<dyn SlashCommand>>,
context_server_commands_by_name: HashMap<Arc<str>, Arc<dyn SlashCommand>>,
next_command_id: SlashCommandId,
}
impl SlashCommandWorkingSet {
pub fn command(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn SlashCommand>> {
self.state
.lock()
.context_server_commands_by_name
.get(name)
.cloned()
.or_else(|| SlashCommandRegistry::global(cx).command(name))
}
pub fn command_names(&self, cx: &AppContext) -> Vec<Arc<str>> {
let mut command_names = SlashCommandRegistry::global(cx).command_names();
command_names.extend(
self.state
.lock()
.context_server_commands_by_name
.keys()
.cloned(),
);
command_names
}
pub fn featured_command_names(&self, cx: &AppContext) -> Vec<Arc<str>> {
SlashCommandRegistry::global(cx).featured_command_names()
}
pub fn insert(&self, command: Arc<dyn SlashCommand>) -> SlashCommandId {
let mut state = self.state.lock();
let command_id = state.next_command_id;
state.next_command_id.0 += 1;
state
.context_server_commands_by_id
.insert(command_id, command.clone());
state.slash_commands_changed();
command_id
}
pub fn remove(&self, command_ids_to_remove: &[SlashCommandId]) {
let mut state = self.state.lock();
state
.context_server_commands_by_id
.retain(|id, _| !command_ids_to_remove.contains(id));
state.slash_commands_changed();
}
}
impl WorkingSetState {
fn slash_commands_changed(&mut self) {
self.context_server_commands_by_name.clear();
self.context_server_commands_by_name.extend(
self.context_server_commands_by_id
.values()
.map(|command| (command.name().into(), command.clone())),
);
}
}

View File

@@ -0,0 +1,75 @@
use assistant_tool::{Tool, ToolRegistry};
use collections::HashMap;
use gpui::AppContext;
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct ToolId(usize);
/// A working set of tools for use in one instance of the Assistant Panel.
#[derive(Default)]
pub struct ToolWorkingSet {
state: Mutex<WorkingSetState>,
}
#[derive(Default)]
struct WorkingSetState {
context_server_tools_by_id: HashMap<ToolId, Arc<dyn Tool>>,
context_server_tools_by_name: HashMap<String, Arc<dyn Tool>>,
next_tool_id: ToolId,
}
impl ToolWorkingSet {
pub fn tool(&self, name: &str, cx: &AppContext) -> Option<Arc<dyn Tool>> {
self.state
.lock()
.context_server_tools_by_name
.get(name)
.cloned()
.or_else(|| ToolRegistry::global(cx).tool(name))
}
pub fn tools(&self, cx: &AppContext) -> Vec<Arc<dyn Tool>> {
let mut tools = ToolRegistry::global(cx).tools();
tools.extend(
self.state
.lock()
.context_server_tools_by_id
.values()
.cloned(),
);
tools
}
pub fn insert(&self, command: Arc<dyn Tool>) -> ToolId {
let mut state = self.state.lock();
let command_id = state.next_tool_id;
state.next_tool_id.0 += 1;
state
.context_server_tools_by_id
.insert(command_id, command.clone());
state.tools_changed();
command_id
}
pub fn remove(&self, command_ids_to_remove: &[ToolId]) {
let mut state = self.state.lock();
state
.context_server_tools_by_id
.retain(|id, _| !command_ids_to_remove.contains(id));
state.tools_changed();
}
}
impl WorkingSetState {
fn tools_changed(&mut self) {
self.context_server_tools_by_name.clear();
self.context_server_tools_by_name.extend(
self.context_server_tools_by_id
.values()
.map(|command| (command.name(), command.clone())),
);
}
}

View File

@@ -1,2 +1,3 @@
pub mod code_edits_tool;
pub mod context_server_tool;
pub mod now_tool;

View File

@@ -0,0 +1,86 @@
use std::sync::Arc;
use anyhow::{anyhow, Result};
use assistant_tool::Tool;
use gpui::{Task, WeakView, WindowContext};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CodeEditsToolInput {
/// A high-level description of the code changes. This should be as short as possible, possibly using common abbreviations.
pub title: String,
/// An array of edits to be applied.
pub edits: Vec<Edit>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Edit {
/// The path to the file that this edit will change.
pub path: String,
/// An arbitrarily-long comment that describes the purpose of this edit.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// An excerpt from the file's current contents that uniquely identifies a range within the file where the edit should occur.
#[serde(skip_serializing_if = "Option::is_none")]
pub old_text: Option<String>,
/// The new text to insert into the file.
pub new_text: String,
/// The type of change that should occur at the given range of the file.
pub operation: Operation,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Operation {
/// Replaces the entire range with the new text.
Update,
/// Inserts the new text before the range.
InsertBefore,
/// Inserts new text after the range.
InsertAfter,
/// Creates a new file with the given path and the new text.
Create,
/// Deletes the specified range from the file.
Delete,
}
pub struct CodeEditsTool;
impl CodeEditsTool {
pub const TOOL_NAME: &str = "zed_code_edits";
}
impl Tool for CodeEditsTool {
fn name(&self) -> String {
Self::TOOL_NAME.to_string()
}
fn description(&self) -> String {
// Anthropic's best practices for tool descriptions:
// https://docs.anthropic.com/en/docs/build-with-claude/tool-use#best-practices-for-tool-definitions
include_str!("edit_tool_description.txt").to_string()
}
fn input_schema(&self) -> serde_json::Value {
let schema = schemars::schema_for!(CodeEditsToolInput);
serde_json::to_value(&schema).unwrap()
}
fn run(
self: Arc<Self>,
input: serde_json::Value,
_workspace: WeakView<workspace::Workspace>,
_cx: &mut WindowContext,
) -> Task<Result<String>> {
let input: CodeEditsToolInput = match serde_json::from_value(input) {
Ok(input) => input,
Err(err) => return Task::ready(Err(anyhow!(err))),
};
let text = format!("The tool returned {:?}.", input);
Task::ready(Ok(text))
}
}

View File

@@ -1,17 +1,25 @@
use std::sync::Arc;
use anyhow::{anyhow, bail};
use assistant_tool::Tool;
use context_servers::manager::ContextServerManager;
use context_servers::types;
use gpui::Task;
use gpui::{Model, Task};
pub struct ContextServerTool {
server_id: String,
server_manager: Model<ContextServerManager>,
server_id: Arc<str>,
tool: types::Tool,
}
impl ContextServerTool {
pub fn new(server_id: impl Into<String>, tool: types::Tool) -> Self {
pub fn new(
server_manager: Model<ContextServerManager>,
server_id: impl Into<Arc<str>>,
tool: types::Tool,
) -> Self {
Self {
server_manager,
server_id: server_id.into(),
tool,
}
@@ -45,13 +53,11 @@ impl Tool for ContextServerTool {
_workspace: gpui::WeakView<workspace::Workspace>,
cx: &mut ui::WindowContext,
) -> gpui::Task<gpui::Result<String>> {
let manager = ContextServerManager::global(cx);
let manager = manager.read(cx);
if let Some(server) = manager.get_server(&self.server_id) {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
cx.foreground_executor().spawn({
let tool_name = self.tool.name.clone();
async move {
let Some(protocol) = server.client.read().clone() else {
let Some(protocol) = server.client() else {
bail!("Context server not initialized");
};
@@ -68,11 +74,21 @@ impl Tool for ContextServerTool {
);
let response = protocol.run_tool(tool_name, arguments).await?;
let tool_result = match response.tool_result {
serde_json::Value::String(s) => s,
_ => serde_json::to_string(&response.tool_result)?,
};
Ok(tool_result)
let mut result = String::new();
for content in response.content {
match content {
types::ToolResponseContent::Text { text } => {
result.push_str(&text);
}
types::ToolResponseContent::Image { .. } => {
log::warn!("Ignoring image content from tool response");
}
types::ToolResponseContent::Resource { .. } => {
log::warn!("Ignoring resource content from tool response");
}
}
}
Ok(result)
}
})
} else {

View File

@@ -0,0 +1,15 @@
Describes the specific code changes that should be made to the files in a code base, based on the request the user made.
It should be used when the user requests making changes to the code base, but not when the user is asking an question about information
(including when asking for information about the code base) rather than requesting a change.
The tool will return an array of patches, each of which represents some related modifications to the code base.
Each patch contains a high-level summary of the changes (which will be displayed in the code editor),
as well as an array of specific edits to be made to specific individual files. The code editor will apply each of those edits to the code base, or not, at the discretion of the user of the editor.
Within each patch, the tool will never return multiple edits whose ranges intersect each other. Instead, it will merge them into one edit.
On the other hand, for ranges that do not intersect each other, the tool will prefer multiple edits to smaller ranges over one edit to a larger range.
Whenever edits reference symbols that would be out of scope, the tool will always include earlier edits which add any necessary imports to bring those symbols into scope.
The overall goal is that if the user of the code editor accepts all edits within all patches, the code will end up in a correct state, and
will successfully build and run without any further modifications from the user. It will also have correctly effected the changes to the code base that the user originally requested.

View File

@@ -13,8 +13,10 @@ path = "src/assistant_slash_command.rs"
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
collections.workspace = true
derive_more.workspace = true
extension.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true
@@ -22,6 +24,7 @@ language_model.workspace = true
parking_lot.workspace = true
serde.workspace = true
serde_json.workspace = true
ui.workspace = true
workspace.workspace = true
[dev-dependencies]

View File

@@ -1,5 +1,8 @@
mod extension_slash_command;
mod slash_command_registry;
pub use crate::extension_slash_command::*;
pub use crate::slash_command_registry::*;
use anyhow::Result;
use futures::stream::{self, BoxStream};
use futures::StreamExt;
@@ -7,7 +10,6 @@ use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, Wind
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role;
use serde::{Deserialize, Serialize};
pub use slash_command_registry::*;
use std::{
ops::Range,
sync::{atomic::AtomicBool, Arc},
@@ -133,9 +135,7 @@ pub enum SlashCommandEvent {
metadata: Option<serde_json::Value>,
},
Content(SlashCommandContent),
EndSection {
metadata: Option<serde_json::Value>,
},
EndSection,
}
#[derive(Debug, Default, PartialEq, Clone)]
@@ -164,43 +164,37 @@ impl SlashCommandOutput {
self.ensure_valid_section_ranges();
let mut events = Vec::new();
let mut last_section_end = 0;
let mut section_endpoints = Vec::new();
for section in self.sections {
if last_section_end < section.range.start {
section_endpoints.push((
section.range.start,
SlashCommandEvent::StartSection {
icon: section.icon,
label: section.label,
metadata: section.metadata,
},
));
section_endpoints.push((section.range.end, SlashCommandEvent::EndSection));
}
section_endpoints.sort_by_key(|(offset, _)| *offset);
let mut content_offset = 0;
for (endpoint_offset, endpoint) in section_endpoints {
if content_offset < endpoint_offset {
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: self
.text
.get(last_section_end..section.range.start)
.unwrap_or_default()
.to_string(),
text: self.text[content_offset..endpoint_offset].to_string(),
run_commands_in_text: self.run_commands_in_text,
})));
content_offset = endpoint_offset;
}
events.push(Ok(SlashCommandEvent::StartSection {
icon: section.icon,
label: section.label,
metadata: section.metadata.clone(),
}));
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: self
.text
.get(section.range.start..section.range.end)
.unwrap_or_default()
.to_string(),
run_commands_in_text: self.run_commands_in_text,
})));
events.push(Ok(SlashCommandEvent::EndSection {
metadata: section.metadata,
}));
last_section_end = section.range.end;
events.push(Ok(endpoint));
}
if last_section_end < self.text.len() {
if content_offset < self.text.len() {
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: self.text[last_section_end..].to_string(),
text: self.text[content_offset..].to_string(),
run_commands_in_text: self.run_commands_in_text,
})));
}
@@ -240,9 +234,8 @@ impl SlashCommandOutput {
section.range.end = output.text.len();
}
}
SlashCommandEvent::EndSection { metadata } => {
if let Some(mut section) = section_stack.pop() {
section.metadata = metadata;
SlashCommandEvent::EndSection => {
if let Some(section) = section_stack.pop() {
output.sections.push(section);
}
}
@@ -314,7 +307,7 @@ mod tests {
text: "Hello, world!".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection { metadata: None }
SlashCommandEvent::EndSection
]
);
@@ -366,7 +359,7 @@ mod tests {
text: "Apple\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection { metadata: None },
SlashCommandEvent::EndSection,
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "Cucumber\n".into(),
run_commands_in_text: false
@@ -380,7 +373,7 @@ mod tests {
text: "Banana\n".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection { metadata: None }
SlashCommandEvent::EndSection
]
);
@@ -444,9 +437,7 @@ mod tests {
text: "Line 1".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "a": true }))
},
SlashCommandEvent::EndSection,
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
@@ -460,9 +451,7 @@ mod tests {
text: "Line 2".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "b": true }))
},
SlashCommandEvent::EndSection,
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
@@ -476,9 +465,7 @@ mod tests {
text: "Line 3".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "c": true }))
},
SlashCommandEvent::EndSection,
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false
@@ -492,9 +479,7 @@ mod tests {
text: "Line 4".into(),
run_commands_in_text: false
}),
SlashCommandEvent::EndSection {
metadata: Some(json!({ "d": true }))
},
SlashCommandEvent::EndSection,
SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false

View File

@@ -0,0 +1,143 @@
use std::path::PathBuf;
use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result;
use async_trait::async_trait;
use extension::{Extension, WorktreeDelegate};
use gpui::{Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
use crate::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);
#[async_trait]
impl WorktreeDelegate for WorktreeDelegateAdapter {
fn id(&self) -> u64 {
self.0.worktree_id().to_proto()
}
fn root_path(&self) -> String {
self.0.worktree_root_path().to_string_lossy().to_string()
}
async fn read_text_file(&self, path: PathBuf) -> Result<String> {
self.0.read_text_file(path).await
}
async fn which(&self, binary_name: String) -> Option<String> {
self.0
.which(binary_name.as_ref())
.await
.map(|path| path.to_string_lossy().to_string())
}
async fn shell_env(&self) -> Vec<(String, String)> {
self.0.shell_env().await.into_iter().collect()
}
}
pub struct ExtensionSlashCommand {
extension: Arc<dyn Extension>,
command: extension::SlashCommand,
}
impl ExtensionSlashCommand {
pub fn new(extension: Arc<dyn Extension>, command: extension::SlashCommand) -> Self {
Self { extension, command }
}
}
impl SlashCommand for ExtensionSlashCommand {
fn name(&self) -> String {
self.command.name.clone()
}
fn description(&self) -> String {
self.command.description.clone()
}
fn menu_text(&self) -> String {
self.command.tooltip_text.clone()
}
fn requires_argument(&self) -> bool {
self.command.requires_argument
}
fn complete_argument(
self: Arc<Self>,
arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
let command = self.command.clone();
let arguments = arguments.to_owned();
cx.background_executor().spawn(async move {
let completions = self
.extension
.complete_slash_command_argument(command, arguments)
.await?;
anyhow::Ok(
completions
.into_iter()
.map(|completion| ArgumentCompletion {
label: completion.label.into(),
new_text: completion.new_text,
replace_previous_arguments: false,
after_completion: completion.run_command.into(),
})
.collect(),
)
})
}
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<SlashCommandResult> {
let command = self.command.clone();
let arguments = arguments.to_owned();
let output = cx.background_executor().spawn(async move {
let delegate =
delegate.map(|delegate| Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _);
let output = self
.extension
.run_slash_command(command, arguments, delegate)
.await?;
anyhow::Ok(output)
});
cx.foreground_executor().spawn(async move {
let output = output.await?;
Ok(SlashCommandOutput {
text: output.text,
sections: output
.sections
.into_iter()
.map(|section| SlashCommandOutputSection {
range: section.range,
icon: IconName::Code,
label: section.label.into(),
metadata: None,
})
.collect(),
run_commands_in_text: false,
}
.to_event_stream())
})
}
}

View File

@@ -343,7 +343,7 @@ fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx);
let clock = Arc::new(FakeSystemClock::default());
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let client = Client::new(clock, http.clone(), cx);
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));

View File

@@ -29,7 +29,7 @@ serde.workspace = true
util.workspace = true
tempfile.workspace = true
[target.'cfg(target_os = "linux")'.dependencies]
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
exec.workspace = true
fork.workspace = true

View File

@@ -1,4 +1,7 @@
#![cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
#![cfg_attr(
any(target_os = "linux", target_os = "freebsd", target_os = "windows"),
allow(dead_code)
)]
use anyhow::{Context, Result};
use clap::Parser;
@@ -88,7 +91,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
fn main() -> Result<()> {
// Exit flatpak sandbox if needed
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
flatpak::try_restart_to_host();
flatpak::ld_extra_libs();
@@ -106,7 +109,7 @@ fn main() -> Result<()> {
}
let args = Args::parse();
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
let args = flatpak::set_bin_if_no_escape(args);
let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?;
@@ -220,7 +223,7 @@ fn main() -> Result<()> {
Ok(())
}
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
mod linux {
use std::{
env,
@@ -344,7 +347,7 @@ mod linux {
}
}
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
mod flatpak {
use std::ffi::OsString;
use std::path::PathBuf;

View File

@@ -42,7 +42,6 @@ serde_json.workspace = true
settings.workspace = true
sha2.workspace = true
smol.workspace = true
sysinfo.workspace = true
telemetry_events.workspace = true
text.workspace = true
thiserror.workspace = true

View File

@@ -889,7 +889,7 @@ impl Client {
cx: &AsyncAppContext,
) -> Result<()> {
let executor = cx.background_executor();
log::info!("add connection to peer");
log::debug!("add connection to peer");
let (connection_id, handle_io, mut incoming) = self.peer.add_connection(conn, {
let executor = executor.clone();
move |duration| executor.timer(duration)
@@ -897,12 +897,12 @@ impl Client {
let handle_io = executor.spawn(handle_io);
let peer_id = async {
log::info!("waiting for server hello");
log::debug!("waiting for server hello");
let message = incoming
.next()
.await
.ok_or_else(|| anyhow!("no hello message received"))?;
log::info!("got server hello");
log::debug!("got server hello");
let hello_message_type_name = message.payload_type_name().to_string();
let hello = message
.into_any()
@@ -928,7 +928,7 @@ impl Client {
}
};
log::info!(
log::debug!(
"set status to connected (connection id: {:?}, peer id: {:?})",
connection_id,
peer_id
@@ -1780,7 +1780,7 @@ mod tests {
let user_id = 5;
let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_404_response(),
cx,
)
@@ -1821,7 +1821,7 @@ mod tests {
let user_id = 5;
let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_404_response(),
cx,
)
@@ -1900,7 +1900,7 @@ mod tests {
let dropped_auth_count = Arc::new(Mutex::new(0));
let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_404_response(),
cx,
)
@@ -1943,7 +1943,7 @@ mod tests {
let user_id = 5;
let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_404_response(),
cx,
)
@@ -2003,7 +2003,7 @@ mod tests {
let user_id = 5;
let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_404_response(),
cx,
)
@@ -2038,7 +2038,7 @@ mod tests {
let user_id = 5;
let client = cx.update(|cx| {
Client::new(
Arc::new(FakeSystemClock::default()),
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_404_response(),
cx,
)

View File

@@ -2,7 +2,6 @@ mod event_coalescer;
use crate::{ChannelId, TelemetrySettings};
use anyhow::Result;
use chrono::{DateTime, Utc};
use clock::SystemClock;
use collections::{HashMap, HashSet};
use futures::Future;
@@ -15,12 +14,11 @@ use settings::{Settings, SettingsStore};
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Write;
use std::time::Instant;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event,
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
SettingEvent,
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event,
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, ReplEvent, SettingEvent,
};
use util::{ResultExt, TryFutureExt};
use worktree::{UpdatedEntriesSet, WorktreeId};
@@ -46,7 +44,7 @@ struct TelemetryState {
flush_events_task: Option<Task<()>>,
log_file: Option<File>,
is_staff: Option<bool>,
first_event_date_time: Option<DateTime<Utc>>,
first_event_date_time: Option<Instant>,
event_coalescer: EventCoalescer,
max_queue_size: usize,
worktree_id_map: WorktreeIdMap,
@@ -100,7 +98,7 @@ pub fn os_name() -> String {
{
"macOS".to_string()
}
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
format!("Linux {}", gpui::guess_compositor())
}
@@ -129,7 +127,7 @@ pub fn os_version() -> String {
.to_string()
}
}
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
use std::path::Path;
@@ -293,55 +291,13 @@ impl Telemetry {
state.session_id = Some(session_id);
state.app_version = release_channel::AppVersion::global(cx).to_string();
state.os_name = os_name();
drop(state);
let this = self.clone();
cx.background_executor()
.spawn(async move {
let mut system = System::new_with_specifics(
RefreshKind::new().with_cpu(CpuRefreshKind::everything()),
);
let refresh_kind = ProcessRefreshKind::new().with_cpu().with_memory();
let current_process = Pid::from_u32(std::process::id());
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
const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(4 * 60);
loop {
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
let current_process = Pid::from_u32(std::process::id());
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"
);
// TODO: Fire an error telemetry event
return;
};
this.report_memory_event(process.memory(), process.virtual_memory());
this.report_cpu_event(process.cpu_usage(), system.cpus().len() as u32);
}
})
.detach();
}
pub fn metrics_enabled(self: &Arc<Self>) -> bool {
let state = self.state.lock();
let enabled = state.settings.metrics;
drop(state);
return enabled;
enabled
}
pub fn set_authenticated_user_info(
@@ -416,28 +372,6 @@ impl Telemetry {
self.report_event(event)
}
pub fn report_cpu_event(self: &Arc<Self>, usage_as_percentage: f32, core_count: u32) {
let event = Event::Cpu(CpuEvent {
usage_as_percentage,
core_count,
});
self.report_event(event)
}
pub fn report_memory_event(
self: &Arc<Self>,
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
) {
let event = Event::Memory(MemoryEvent {
memory_in_bytes,
virtual_memory_in_bytes,
});
self.report_event(event)
}
pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event {
let event = Event::App(AppEvent { operation });
@@ -469,7 +403,10 @@ impl Telemetry {
if let Some((start, end, environment)) = period_data {
let event = Event::Edit(EditEvent {
duration: end.timestamp_millis() - start.timestamp_millis(),
duration: end
.saturating_duration_since(start)
.min(Duration::from_secs(60 * 60 * 24))
.as_millis() as i64,
environment: environment.to_string(),
is_via_ssh,
});
@@ -567,9 +504,10 @@ impl Telemetry {
let date_time = self.clock.utc_now();
let milliseconds_since_first_event = match state.first_event_date_time {
Some(first_event_date_time) => {
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
}
Some(first_event_date_time) => date_time
.saturating_duration_since(first_event_date_time)
.min(Duration::from_secs(60 * 60 * 24))
.as_millis() as i64,
None => {
state.first_event_date_time = Some(date_time);
0
@@ -702,7 +640,6 @@ pub fn calculate_json_checksum(json: &impl AsRef<[u8]>) -> Option<String> {
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
use clock::FakeSystemClock;
use gpui::TestAppContext;
use http_client::FakeHttpClient;
@@ -710,9 +647,7 @@ mod tests {
#[gpui::test]
fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_200_response();
let system_id = Some("system_id".to_string());
let installation_id = Some("installation_id".to_string());
@@ -743,7 +678,7 @@ mod tests {
Some(first_date_time)
);
clock.advance(chrono::Duration::milliseconds(100));
clock.advance(Duration::from_millis(100));
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
@@ -759,7 +694,7 @@ mod tests {
Some(first_date_time)
);
clock.advance(chrono::Duration::milliseconds(100));
clock.advance(Duration::from_millis(100));
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
@@ -775,7 +710,7 @@ mod tests {
Some(first_date_time)
);
clock.advance(chrono::Duration::milliseconds(100));
clock.advance(Duration::from_millis(100));
// Adding a 4th event should cause a flush
let event = telemetry.report_app_event(operation.clone());
@@ -796,9 +731,7 @@ mod tests {
cx: &mut TestAppContext,
) {
init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_200_response();
let system_id = Some("system_id".to_string());
let installation_id = Some("installation_id".to_string());

View File

@@ -1,7 +1,6 @@
use std::sync::Arc;
use std::time;
use std::{sync::Arc, time::Instant};
use chrono::{DateTime, Duration, Utc};
use clock::SystemClock;
const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20);
@@ -10,8 +9,8 @@ const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from
#[derive(Debug, PartialEq)]
struct PeriodData {
environment: &'static str,
start: DateTime<Utc>,
end: Option<DateTime<Utc>>,
start: Instant,
end: Option<Instant>,
}
pub struct EventCoalescer {
@@ -27,9 +26,8 @@ impl EventCoalescer {
pub fn log_event(
&mut self,
environment: &'static str,
) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> {
) -> Option<(Instant, Instant, &'static str)> {
let log_time = self.clock.utc_now();
let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap();
let Some(state) = &mut self.state else {
self.state = Some(PeriodData {
@@ -43,7 +41,7 @@ impl EventCoalescer {
let period_end = state
.end
.unwrap_or(state.start + SIMULATED_DURATION_FOR_SINGLE_EVENT);
let within_timeout = log_time - period_end < coalesce_timeout;
let within_timeout = log_time - period_end < COALESCE_TIMEOUT;
let environment_is_same = state.environment == environment;
let should_coaelesce = !within_timeout || !environment_is_same;
@@ -70,16 +68,13 @@ impl EventCoalescer {
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use clock::FakeSystemClock;
use super::*;
#[test]
fn test_same_context_exceeding_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let clock = Arc::new(FakeSystemClock::new());
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new(clock.clone());
@@ -98,7 +93,7 @@ mod tests {
})
);
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
let within_timeout_adjustment = COALESCE_TIMEOUT / 2;
// Ensure that many calls within the timeout don't start a new period
for _ in 0..100 {
@@ -118,7 +113,7 @@ mod tests {
}
let period_end = clock.utc_now();
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
let exceed_timeout_adjustment = COALESCE_TIMEOUT * 2;
// Logging an event exceeding the timeout should start a new period
clock.advance(exceed_timeout_adjustment);
let new_period_start = clock.utc_now();
@@ -137,9 +132,7 @@ mod tests {
#[test]
fn test_different_environment_under_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let clock = Arc::new(FakeSystemClock::new());
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new(clock.clone());
@@ -158,7 +151,7 @@ mod tests {
})
);
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
let within_timeout_adjustment = COALESCE_TIMEOUT / 2;
clock.advance(within_timeout_adjustment);
let period_end = clock.utc_now();
let period_data = event_coalescer.log_event(environment_1);
@@ -193,9 +186,7 @@ mod tests {
#[test]
fn test_switching_environment_while_within_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let clock = Arc::new(FakeSystemClock::new());
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new(clock.clone());
@@ -214,7 +205,7 @@ mod tests {
})
);
let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap();
let within_timeout_adjustment = COALESCE_TIMEOUT / 2;
clock.advance(within_timeout_adjustment);
let period_end = clock.utc_now();
let environment_2 = "environment_2";
@@ -240,9 +231,7 @@ mod tests {
#[test]
fn test_switching_environment_while_exceeding_timeout() {
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(),
));
let clock = Arc::new(FakeSystemClock::new());
let environment_1 = "environment_1";
let mut event_coalescer = EventCoalescer::new(clock.clone());
@@ -261,7 +250,7 @@ mod tests {
})
);
let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap();
let exceed_timeout_adjustment = COALESCE_TIMEOUT * 2;
clock.advance(exceed_timeout_adjustment);
let period_end = clock.utc_now();
let environment_2 = "environment_2";

View File

@@ -16,7 +16,6 @@ doctest = false
test-support = ["dep:parking_lot"]
[dependencies]
chrono.workspace = true
parking_lot = { workspace = true, optional = true }
serde.workspace = true
smallvec.workspace = true

View File

@@ -1,21 +1,21 @@
use chrono::{DateTime, Utc};
use std::time::Instant;
pub trait SystemClock: Send + Sync {
/// Returns the current date and time in UTC.
fn utc_now(&self) -> DateTime<Utc>;
fn utc_now(&self) -> Instant;
}
pub struct RealSystemClock;
impl SystemClock for RealSystemClock {
fn utc_now(&self) -> DateTime<Utc> {
Utc::now()
fn utc_now(&self) -> Instant {
Instant::now()
}
}
#[cfg(any(test, feature = "test-support"))]
pub struct FakeSystemClockState {
now: DateTime<Utc>,
now: Instant,
}
#[cfg(any(test, feature = "test-support"))]
@@ -24,36 +24,30 @@ pub struct FakeSystemClock {
state: parking_lot::Mutex<FakeSystemClockState>,
}
#[cfg(any(test, feature = "test-support"))]
impl Default for FakeSystemClock {
fn default() -> Self {
Self::new(Utc::now())
}
}
#[cfg(any(test, feature = "test-support"))]
impl FakeSystemClock {
pub fn new(now: DateTime<Utc>) -> Self {
let state = FakeSystemClockState { now };
pub fn new() -> Self {
let state = FakeSystemClockState {
now: Instant::now(),
};
Self {
state: parking_lot::Mutex::new(state),
}
}
pub fn set_now(&self, now: DateTime<Utc>) {
pub fn set_now(&self, now: Instant) {
self.state.lock().now = now;
}
/// Advances the [`FakeSystemClock`] by the specified [`Duration`](chrono::Duration).
pub fn advance(&self, duration: chrono::Duration) {
pub fn advance(&self, duration: std::time::Duration) {
self.state.lock().now += duration;
}
}
#[cfg(any(test, feature = "test-support"))]
impl SystemClock for FakeSystemClock {
fn utc_now(&self) -> DateTime<Utc> {
fn utc_now(&self) -> Instant {
self.state.lock().now
}
}

View File

@@ -24,6 +24,7 @@ async-stripe.workspace = true
async-tungstenite.workspace = true
aws-config = { version = "1.1.5" }
aws-sdk-s3 = { version = "1.15.0" }
aws-sdk-kinesis = "1.51.0"
axum = { version = "0.6", features = ["json", "headers", "ws"] }
axum-extra = { version = "0.4", features = ["erased-json"] }
base64.workspace = true
@@ -78,6 +79,7 @@ uuid.workspace = true
[dev-dependencies]
assistant = { workspace = true, features = ["test-support"] }
context_servers.workspace = true
async-trait.workspace = true
audio.workspace = true
call = { workspace = true, features = ["test-support"] }

View File

@@ -174,6 +174,31 @@ spec:
secretKeyRef:
name: blob-store
key: bucket
- name: KINESIS_ACCESS_KEY
valueFrom:
secretKeyRef:
name: kinesis
key: access_key
- name: KINESIS_SECRET_KEY
valueFrom:
secretKeyRef:
name: kinesis
key: secret_key
- name: KINESIS_STREAM
valueFrom:
secretKeyRef:
name: kinesis
key: stream
- name: KINESIS_REGION
valueFrom:
secretKeyRef:
name: kinesis
key: region
- name: BLOB_STORE_BUCKET
valueFrom:
secretKeyRef:
name: blob-store
key: bucket
- name: CLICKHOUSE_URL
valueFrom:
secretKeyRef:

View File

@@ -11,9 +11,11 @@ use axum::{
routing::post,
Extension, Router, TypedHeader,
};
use chrono::Duration;
use rpc::ExtensionMetadata;
use semantic_version::SemanticVersion;
use serde::{Serialize, Serializer};
use serde::{Deserialize, Serialize, Serializer};
use serde_json::json;
use sha2::{Digest, Sha256};
use std::sync::{Arc, OnceLock};
use telemetry_events::{
@@ -21,6 +23,7 @@ use telemetry_events::{
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, Panic,
ReplEvent, SettingEvent,
};
use util::ResultExt;
use uuid::Uuid;
const CRASH_REPORTS_BUCKET: &str = "zed-crash-reports";
@@ -388,13 +391,6 @@ pub async fn post_events(
country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
body: Bytes,
) -> Result<()> {
let Some(clickhouse_client) = app.clickhouse_client.clone() else {
Err(Error::http(
StatusCode::NOT_IMPLEMENTED,
"not supported".into(),
))?
};
let Some(expected) = calculate_json_checksum(app.clone(), &body) else {
return Err(Error::http(
StatusCode::INTERNAL_SERVER_ERROR,
@@ -416,6 +412,34 @@ pub async fn post_events(
};
let country_code = country_code_header.map(|h| h.to_string());
let first_event_at = chrono::Utc::now()
- chrono::Duration::milliseconds(last_event.milliseconds_since_first_event);
if let Some(kinesis_client) = app.kinesis_client.clone() {
if let Some(stream) = app.config.kinesis_stream.clone() {
let mut request = kinesis_client.put_records().stream_name(stream);
for row in for_snowflake(request_body.clone(), first_event_at, country_code.clone()) {
if let Some(data) = serde_json::to_vec(&row).log_err() {
request = request.records(
aws_sdk_kinesis::types::PutRecordsRequestEntry::builder()
.partition_key(request_body.system_id.clone().unwrap_or_default())
.data(data.into())
.build()
.unwrap(),
);
}
}
request.send().await.log_err();
}
};
let Some(clickhouse_client) = app.clickhouse_client.clone() else {
Err(Error::http(
StatusCode::NOT_IMPLEMENTED,
"not supported".into(),
))?
};
let first_event_at = chrono::Utc::now()
- chrono::Duration::milliseconds(last_event.milliseconds_since_first_event);
@@ -459,20 +483,7 @@ pub async fn post_events(
checksum_matched,
))
}
Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event(
event.clone(),
wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event(
event.clone(),
wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Cpu(_) | Event::Memory(_) => continue,
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event(
event.clone(),
wrapper,
@@ -923,6 +934,7 @@ pub struct CpuEventRow {
}
impl CpuEventRow {
#[allow(unused)]
fn from_event(
event: CpuEvent,
wrapper: &EventWrapper,
@@ -977,6 +989,7 @@ pub struct MemoryEventRow {
}
impl MemoryEventRow {
#[allow(unused)]
fn from_event(
event: MemoryEvent,
wrapper: &EventWrapper,
@@ -1364,3 +1377,259 @@ pub fn calculate_json_checksum(app: Arc<AppState>, json: &impl AsRef<[u8]>) -> O
summer.update(checksum_seed);
Some(summer.finalize().into_iter().collect())
}
fn for_snowflake(
body: EventRequestBody,
first_event_at: chrono::DateTime<chrono::Utc>,
country_code: Option<String>,
) -> impl Iterator<Item = SnowflakeRow> {
body.events.into_iter().flat_map(move |event| {
let timestamp =
first_event_at + Duration::milliseconds(event.milliseconds_since_first_event);
let (event_type, mut event_properties) = match &event.event {
Event::Editor(e) => (
match e.operation.as_str() {
"open" => "Editor Opened".to_string(),
"save" => "Editor Saved".to_string(),
_ => format!("Unknown Editor Event: {}", e.operation),
},
serde_json::to_value(e).unwrap(),
),
Event::InlineCompletion(e) => (
format!(
"Inline Completion {}",
if e.suggestion_accepted {
"Accepted"
} else {
"Discarded"
}
),
serde_json::to_value(e).unwrap(),
),
Event::Call(e) => {
let event_type = match e.operation.trim() {
"unshare project" => "Project Unshared".to_string(),
"open channel notes" => "Channel Notes Opened".to_string(),
"share project" => "Project Shared".to_string(),
"join channel" => "Channel Joined".to_string(),
"hang up" => "Call Ended".to_string(),
"accept incoming" => "Incoming Call Accepted".to_string(),
"invite" => "Participant Invited".to_string(),
"disable microphone" => "Microphone Disabled".to_string(),
"enable microphone" => "Microphone Enabled".to_string(),
"enable screen share" => "Screen Share Enabled".to_string(),
"disable screen share" => "Screen Share Disabled".to_string(),
"decline incoming" => "Incoming Call Declined".to_string(),
"enable camera" => "Camera Enabled".to_string(),
"disable camera" => "Camera Disabled".to_string(),
_ => format!("Unknown Call Event: {}", e.operation),
};
(event_type, serde_json::to_value(e).unwrap())
}
Event::Assistant(e) => (
match e.phase {
telemetry_events::AssistantPhase::Response => "Assistant Responded".to_string(),
telemetry_events::AssistantPhase::Invoked => "Assistant Invoked".to_string(),
telemetry_events::AssistantPhase::Accepted => {
"Assistant Response Accepted".to_string()
}
telemetry_events::AssistantPhase::Rejected => {
"Assistant Response Rejected".to_string()
}
},
serde_json::to_value(e).unwrap(),
),
Event::Cpu(_) | Event::Memory(_) => return None,
Event::App(e) => {
let mut properties = json!({});
let event_type = match e.operation.trim() {
"extensions: install extension" => "Extension Installed".to_string(),
"open" => "App Opened".to_string(),
"project search: open" => "Project Search Opened".to_string(),
"first open" => {
properties["is_first_open"] = json!(true);
"App First Opened".to_string()
}
"extensions: uninstall extension" => "Extension Uninstalled".to_string(),
"welcome page: close" => "Welcome Page Closed".to_string(),
"open project" => {
properties["is_first_time"] = json!(false);
"Project Opened".to_string()
}
"welcome page: install cli" => "CLI Installed".to_string(),
"project diagnostics: open" => "Project Diagnostics Opened".to_string(),
"extensions page: open" => "Extensions Page Opened".to_string(),
"welcome page: change theme" => "Welcome Theme Changed".to_string(),
"welcome page: toggle metric telemetry" => {
properties["enabled"] = json!(false);
"Welcome Telemetry Toggled".to_string()
}
"welcome page: change keymap" => "Keymap Changed".to_string(),
"welcome page: toggle vim" => {
properties["enabled"] = json!(false);
"Welcome Vim Mode Toggled".to_string()
}
"welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(),
"welcome page: toggle diagnostic telemetry" => {
"Welcome Telemetry Toggled".to_string()
}
"welcome page: open" => "Welcome Page Opened".to_string(),
"close" => "App Closed".to_string(),
"markdown preview: open" => "Markdown Preview Opened".to_string(),
"welcome page: open extensions" => "Extensions Page Opened".to_string(),
"open node project" | "open pnpm project" | "open yarn project" => {
properties["project_type"] = json!("node");
properties["is_first_time"] = json!(false);
"Project Opened".to_string()
}
"repl sessions: open" => "REPL Session Started".to_string(),
"welcome page: toggle helix" => {
properties["enabled"] = json!(false);
"Helix Mode Toggled".to_string()
}
"welcome page: edit settings" => {
properties["changed_settings"] = json!([]);
"Settings Edited".to_string()
}
"welcome page: view docs" => "Documentation Viewed".to_string(),
"open ssh project" => {
properties["is_first_time"] = json!(false);
"SSH Project Opened".to_string()
}
"create ssh server" => "SSH Server Created".to_string(),
"create ssh project" => "SSH Project Created".to_string(),
"first open for release channel" => {
properties["is_first_for_channel"] = json!(true);
"App First Opened For Release Channel".to_string()
}
"feature upsell: toggle vim" => {
properties["source"] = json!("Feature Upsell");
"Vim Mode Toggled".to_string()
}
_ => e
.operation
.strip_prefix("feature upsell: viewed docs (")
.and_then(|s| s.strip_suffix(')'))
.map_or_else(
|| format!("Unknown App Event: {}", e.operation),
|docs_url| {
properties["url"] = json!(docs_url);
properties["source"] = json!("Feature Upsell");
"Documentation Viewed".to_string()
},
),
};
(event_type, properties)
}
Event::Setting(e) => (
"Settings Changed".to_string(),
serde_json::to_value(e).unwrap(),
),
Event::Extension(e) => (
"Extension Loaded".to_string(),
serde_json::to_value(e).unwrap(),
),
Event::Edit(e) => (
"Editor Edited".to_string(),
serde_json::to_value(e).unwrap(),
),
Event::Action(e) => (
"Action Invoked".to_string(),
serde_json::to_value(e).unwrap(),
),
Event::Repl(e) => (
"Kernel Status Changed".to_string(),
serde_json::to_value(e).unwrap(),
),
};
if let serde_json::Value::Object(ref mut map) = event_properties {
map.insert("app_version".to_string(), body.app_version.clone().into());
map.insert("os_name".to_string(), body.os_name.clone().into());
map.insert("os_version".to_string(), body.os_version.clone().into());
map.insert("architecture".to_string(), body.architecture.clone().into());
map.insert(
"release_channel".to_string(),
body.release_channel.clone().into(),
);
map.insert("signed_in".to_string(), event.signed_in.into());
if let Some(country_code) = country_code.as_ref() {
map.insert("country_code".to_string(), country_code.clone().into());
}
}
let user_properties = Some(serde_json::json!({
"is_staff": body.is_staff,
"Country": country_code.clone(),
"OS": format!("{} {}", body.os_name, body.os_version.clone().unwrap_or_default()),
"Version": body.app_version.clone(),
}));
Some(SnowflakeRow {
time: timestamp,
user_id: body.metrics_id.clone(),
device_id: body.system_id.clone(),
event_type,
event_properties,
user_properties,
insert_id: Some(Uuid::new_v4().to_string()),
})
})
}
#[derive(Serialize, Deserialize)]
struct SnowflakeRow {
pub time: chrono::DateTime<chrono::Utc>,
pub user_id: Option<String>,
pub device_id: Option<String>,
pub event_type: String,
pub event_properties: serde_json::Value,
pub user_properties: Option<serde_json::Value>,
pub insert_id: Option<String>,
}
#[derive(Serialize, Deserialize)]
struct SnowflakeData {
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
pub installation_id: Option<String>,
/// Identifier unique to each logged in Zed user (randomly generated on first sign in)
/// Identifier unique to each Zed session (differs for each time you open Zed)
pub session_id: Option<String>,
pub metrics_id: Option<String>,
/// True for Zed staff, otherwise false
pub is_staff: Option<bool>,
/// Zed version number
pub app_version: String,
pub os_name: String,
pub os_version: Option<String>,
pub architecture: String,
/// Zed release channel (stable, preview, dev)
pub release_channel: Option<String>,
pub signed_in: bool,
#[serde(flatten)]
pub editor_event: Option<EditorEvent>,
#[serde(flatten)]
pub inline_completion_event: Option<InlineCompletionEvent>,
#[serde(flatten)]
pub call_event: Option<CallEvent>,
#[serde(flatten)]
pub assistant_event: Option<AssistantEvent>,
#[serde(flatten)]
pub cpu_event: Option<CpuEvent>,
#[serde(flatten)]
pub memory_event: Option<MemoryEvent>,
#[serde(flatten)]
pub app_event: Option<AppEvent>,
#[serde(flatten)]
pub setting_event: Option<SettingEvent>,
#[serde(flatten)]
pub extension_event: Option<ExtensionEvent>,
#[serde(flatten)]
pub edit_event: Option<EditEvent>,
#[serde(flatten)]
pub repl_event: Option<ReplEvent>,
#[serde(flatten)]
pub action_event: Option<ActionEvent>,
}

View File

@@ -170,6 +170,10 @@ pub struct Config {
pub blob_store_access_key: Option<String>,
pub blob_store_secret_key: Option<String>,
pub blob_store_bucket: Option<String>,
pub kinesis_region: Option<String>,
pub kinesis_stream: Option<String>,
pub kinesis_access_key: Option<String>,
pub kinesis_secret_key: Option<String>,
pub zed_environment: Arc<str>,
pub openai_api_key: Option<Arc<str>>,
pub google_ai_api_key: Option<Arc<str>>,
@@ -238,6 +242,10 @@ impl Config {
stripe_api_key: None,
supermaven_admin_api_key: None,
user_backfiller_github_access_token: None,
kinesis_region: None,
kinesis_access_key: None,
kinesis_secret_key: None,
kinesis_stream: None,
}
}
}
@@ -276,6 +284,7 @@ pub struct AppState {
pub rate_limiter: Arc<RateLimiter>,
pub executor: Executor,
pub clickhouse_client: Option<::clickhouse::Client>,
pub kinesis_client: Option<::aws_sdk_kinesis::Client>,
pub config: Config,
}
@@ -332,6 +341,11 @@ impl AppState {
.clickhouse_url
.as_ref()
.and_then(|_| build_clickhouse_client(&config).log_err()),
kinesis_client: if config.kinesis_access_key.is_some() {
build_kinesis_client(&config).await.log_err()
} else {
None
},
config,
};
Ok(Arc::new(this))
@@ -381,6 +395,35 @@ async fn build_blob_store_client(config: &Config) -> anyhow::Result<aws_sdk_s3::
Ok(aws_sdk_s3::Client::new(&s3_config))
}
async fn build_kinesis_client(config: &Config) -> anyhow::Result<aws_sdk_kinesis::Client> {
let keys = aws_sdk_s3::config::Credentials::new(
config
.kinesis_access_key
.clone()
.ok_or_else(|| anyhow!("missing kinesis_access_key"))?,
config
.kinesis_secret_key
.clone()
.ok_or_else(|| anyhow!("missing kinesis_secret_key"))?,
None,
None,
"env",
);
let kinesis_config = aws_config::defaults(BehaviorVersion::latest())
.region(Region::new(
config
.kinesis_region
.clone()
.ok_or_else(|| anyhow!("missing blob_store_region"))?,
))
.credentials_provider(keys)
.load()
.await;
Ok(aws_sdk_kinesis::Client::new(&kinesis_config))
}
fn build_clickhouse_client(config: &Config) -> anyhow::Result<::clickhouse::Client> {
Ok(::clickhouse::Client::default()
.with_url(

View File

@@ -267,7 +267,6 @@ async fn perform_completion(
anthropic::ANTHROPIC_API_URL,
api_key,
request,
None,
)
.await
.map_err(|err| match err {
@@ -357,7 +356,6 @@ async fn perform_completion(
open_ai::OPEN_AI_API_URL,
api_key,
serde_json::from_str(params.provider_request.get())?,
None,
)
.await?;
@@ -390,7 +388,6 @@ async fn perform_completion(
google_ai::API_URL,
api_key,
serde_json::from_str(params.provider_request.get())?,
None,
)
.await?;

View File

@@ -3621,7 +3621,6 @@ async fn count_language_model_tokens(
google_ai::API_URL,
api_key,
serde_json::from_str(&request.request)?,
None,
)
.await?
}
@@ -4031,12 +4030,18 @@ async fn get_llm_api_token(
Err(anyhow!("terms of service not accepted"))?
}
let mut account_created_at = user.created_at;
if let Some(github_created_at) = user.github_user_created_at {
account_created_at = account_created_at.min(github_created_at);
}
if Utc::now().naive_utc() - account_created_at < MIN_ACCOUNT_AGE_FOR_LLM_USE {
Err(anyhow!("account too young"))?
let has_llm_subscription = session.has_llm_subscription(&db).await?;
let bypass_account_age_check =
has_llm_subscription || flags.iter().any(|flag| flag == "bypass-account-age-check");
if !bypass_account_age_check {
let mut account_created_at = user.created_at;
if let Some(github_created_at) = user.github_user_created_at {
account_created_at = account_created_at.min(github_created_at);
}
if Utc::now().naive_utc() - account_created_at < MIN_ACCOUNT_AGE_FOR_LLM_USE {
Err(anyhow!("account too young"))?
}
}
let billing_preferences = db.get_billing_preferences(user.id).await?;
@@ -4046,7 +4051,7 @@ async fn get_llm_api_token(
session.is_staff(),
billing_preferences,
has_llm_closed_beta_feature_flag,
session.has_llm_subscription(&db).await?,
has_llm_subscription,
session.current_plan(&db).await?,
&session.app_state.config,
)?;

View File

@@ -6,7 +6,7 @@ use crate::{
},
};
use anyhow::{anyhow, Result};
use assistant::{ContextStore, PromptBuilder};
use assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet, ToolWorkingSet};
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet};
@@ -5130,11 +5130,10 @@ async fn test_lsp_hover(
});
let new_server_name = new_server.server.name();
assert!(
!servers_with_hover_requests.contains_key(new_server_name),
!servers_with_hover_requests.contains_key(&new_server_name),
"Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
);
let new_server_name = new_server_name.to_string();
match new_server_name.as_str() {
match new_server_name.as_ref() {
"CrabLang-ls" => {
servers_with_hover_requests.insert(
new_server_name.clone(),
@@ -6487,13 +6486,31 @@ async fn test_context_collaboration_with_reconnect(
assert_eq!(project.collaborators().len(), 1);
});
cx_a.update(context_servers::init);
cx_b.update(context_servers::init);
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context_store_a = cx_a
.update(|cx| ContextStore::new(project_a.clone(), prompt_builder.clone(), cx))
.update(|cx| {
ContextStore::new(
project_a.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
})
.await
.unwrap();
let context_store_b = cx_b
.update(|cx| ContextStore::new(project_b.clone(), prompt_builder.clone(), cx))
.update(|cx| {
ContextStore::new(
project_b.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
})
.await
.unwrap();

View File

@@ -1323,11 +1323,8 @@ impl RandomizedTest for ProjectCollaborationTest {
match (host_file, guest_file) {
(Some(host_file), Some(guest_file)) => {
assert_eq!(guest_file.path(), host_file.path());
assert_eq!(guest_file.is_deleted(), host_file.is_deleted());
assert_eq!(
guest_file.mtime(),
host_file.mtime(),
"guest {} mtime does not match host {} for path {:?} in project {}",
assert_eq!(guest_file.disk_state(), host_file.disk_state(),
"guest {} disk_state does not match host {} for path {:?} in project {}",
guest_user_id,
host_user_id,
guest_file.path(),

View File

@@ -168,7 +168,7 @@ impl TestServer {
client::init_settings(cx);
});
let clock = Arc::new(FakeSystemClock::default());
let clock = Arc::new(FakeSystemClock::new());
let http = FakeHttpClient::with_404_response();
let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
{
@@ -512,6 +512,7 @@ impl TestServer {
rate_limiter: Arc::new(RateLimiter::new(test_db.db().clone())),
executor,
clickhouse_client: None,
kinesis_client: None,
config: Config {
http_port: 0,
database_url: "".into(),
@@ -550,6 +551,10 @@ impl TestServer {
stripe_api_key: None,
supermaven_admin_api_key: None,
user_backfiller_github_access_token: None,
kinesis_region: None,
kinesis_stream: None,
kinesis_access_key: None,
kinesis_secret_key: None,
},
})
}

View File

@@ -23,7 +23,7 @@ use std::{sync::Arc, time::Duration};
use time::{OffsetDateTime, UtcOffset};
use ui::{
prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu,
TabBar, Tooltip,
Tab, TabBar, Tooltip,
};
use util::{ResultExt, TryFutureExt};
use workspace::{
@@ -939,7 +939,7 @@ impl Render for ChatPanel {
TabBar::new("chat_header").child(
h_flex()
.w_full()
.h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS))
.h(Tab::container_height(cx))
.px_2()
.child(Label::new(
self.active_chat

View File

@@ -2521,7 +2521,7 @@ impl CollabPanel {
.flex()
.w_full()
.when(!channel.is_root_channel(), |el| {
el.on_drag(channel.clone(), move |channel, cx| {
el.on_drag(channel.clone(), move |channel, _, cx| {
cx.new_view(|_| DraggedChannelView {
channel: channel.clone(),
width,

View File

@@ -19,7 +19,9 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use std::{sync::Arc, time::Duration};
use time::{OffsetDateTime, UtcOffset};
use ui::{h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tooltip};
use ui::{
h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip,
};
use util::{ResultExt, TryFutureExt};
use workspace::notifications::NotificationId;
use workspace::{
@@ -588,7 +590,7 @@ impl Render for NotificationPanel {
.px_2()
.py_1()
// Match the height of the tab bar so they line up.
.h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS))
.h(Tab::container_height(cx))
.border_b_1()
.border_color(cx.theme().colors().border)
.child(Label::new("Notifications"))

View File

@@ -39,11 +39,13 @@ impl CommandPaletteFilter {
}
/// Updates the global [`CommandPaletteFilter`] using the given closure.
pub fn update_global<F, R>(cx: &mut AppContext, update: F) -> R
pub fn update_global<F>(cx: &mut AppContext, update: F)
where
F: FnOnce(&mut Self, &mut AppContext) -> R,
F: FnOnce(&mut Self, &mut AppContext),
{
cx.update_global(|this: &mut GlobalCommandPaletteFilter, cx| update(&mut this.0, cx))
if cx.has_global::<GlobalCommandPaletteFilter>() {
cx.update_global(|this: &mut GlobalCommandPaletteFilter, cx| update(&mut this.0, cx))
}
}
/// Returns whether the given [`Action`] is hidden by the filter.

View File

@@ -20,6 +20,7 @@ gpui.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
@@ -27,4 +28,3 @@ settings.workspace = true
smol.workspace = true
url = { workspace = true, features = ["serde"] }
util.workspace = true
workspace.workspace = true

View File

@@ -25,6 +25,13 @@ use util::TryFutureExt;
const JSON_RPC_VERSION: &str = "2.0";
const REQUEST_TIMEOUT: Duration = Duration::from_secs(60);
// Standard JSON-RPC error codes
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
type ResponseHandler = Box<dyn Send + FnOnce(Result<String, Error>)>;
type NotificationHandler = Box<dyn Send + FnMut(Value, AsyncAppContext)>;
@@ -53,13 +60,22 @@ pub struct Client {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ContextServerId(pub String);
pub struct ContextServerId(pub Arc<str>);
fn is_null_value<T: Serialize>(value: &T) -> bool {
if let Ok(Value::Null) = serde_json::to_value(value) {
true
} else {
false
}
}
#[derive(Serialize, Deserialize)]
struct Request<'a, T> {
jsonrpc: &'static str,
id: RequestId,
method: &'a str,
#[serde(skip_serializing_if = "is_null_value")]
params: T,
}

View File

@@ -1,40 +1,26 @@
use gpui::{actions, AppContext, Context, ViewContext};
use manager::ContextServerManager;
use workspace::Workspace;
pub mod client;
pub mod manager;
pub mod protocol;
mod registry;
pub mod types;
pub use registry::*;
use command_palette_hooks::CommandPaletteFilter;
use gpui::{actions, AppContext};
use settings::Settings;
use crate::manager::ContextServerSettings;
pub use crate::registry::ContextServerFactoryRegistry;
actions!(context_servers, [Restart]);
/// The namespace for the context servers actions.
const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
pub fn init(cx: &mut AppContext) {
log::info!("initializing context server client");
manager::init(cx);
ContextServerRegistry::register(cx);
ContextServerSettings::register(cx);
ContextServerFactoryRegistry::default_global(cx);
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(restart_servers);
},
)
.detach();
}
fn restart_servers(_workspace: &mut Workspace, _action: &Restart, cx: &mut ViewContext<Workspace>) {
let model = ContextServerManager::global(cx);
cx.update_model(&model, |manager, cx| {
for server in manager.servers() {
manager
.restart_server(&server.id, cx)
.detach_and_log_err(cx);
}
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);
});
}

View File

@@ -14,38 +14,47 @@
//! The module also includes initialization logic to set up the context server system
//! 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;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use std::path::Path;
use std::sync::Arc;
use crate::CONTEXT_SERVERS_NAMESPACE;
use anyhow::{bail, Result};
use collections::HashMap;
use command_palette_hooks::CommandPaletteFilter;
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Subscription, Task, WeakModel};
use log;
use parking_lot::RwLock;
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use util::ResultExt as _;
use crate::{
client::{self, Client},
types,
types, ContextServerFactoryRegistry, CONTEXT_SERVERS_NAMESPACE,
};
#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
pub struct ContextServerSettings {
pub servers: Vec<ServerConfig>,
#[serde(default)]
pub context_servers: HashMap<Arc<str>, ServerConfig>,
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
pub struct ServerConfig {
pub command: Option<ServerCommand>,
pub settings: Option<serde_json::Value>,
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
pub struct ServerConfig {
pub id: String,
pub executable: String,
pub struct ServerCommand {
pub path: String,
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,
}
impl Settings for ContextServerSettings {
const KEY: Option<&'static str> = Some("experimental.context_servers");
const KEY: Option<&'static str> = None;
type FileContent = Self;
@@ -58,28 +67,43 @@ impl Settings for ContextServerSettings {
}
pub struct ContextServer {
pub id: String,
pub config: ServerConfig,
pub id: Arc<str>,
pub config: Arc<ServerConfig>,
pub client: RwLock<Option<Arc<crate::protocol::InitializedContextServerProtocol>>>,
}
impl ContextServer {
fn new(config: ServerConfig) -> Self {
pub fn new(id: Arc<str>, config: Arc<ServerConfig>) -> Self {
Self {
id: config.id.clone(),
id,
config,
client: RwLock::new(None),
}
}
async fn start(&self, cx: &AsyncAppContext) -> anyhow::Result<()> {
log::info!("starting context server {}", self.config.id,);
pub fn id(&self) -> Arc<str> {
self.id.clone()
}
pub fn config(&self) -> Arc<ServerConfig> {
self.config.clone()
}
pub fn client(&self) -> Option<Arc<crate::protocol::InitializedContextServerProtocol>> {
self.client.read().clone()
}
pub async fn start(self: Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
log::info!("starting context server {}", self.id);
let Some(command) = &self.config.command else {
bail!("no command specified for server {}", self.id);
};
let client = Client::new(
client::ContextServerId(self.config.id.clone()),
client::ContextServerId(self.id.clone()),
client::ModelContextServerBinary {
executable: Path::new(&self.config.executable).to_path_buf(),
args: self.config.args.clone(),
env: self.config.env.clone(),
executable: Path::new(&command.path).to_path_buf(),
args: command.args.clone(),
env: command.env.clone(),
},
cx.clone(),
)?;
@@ -93,7 +117,7 @@ impl ContextServer {
log::debug!(
"context server {} initialized: {:?}",
self.config.id,
self.id,
initialized_protocol.initialize,
);
@@ -101,7 +125,7 @@ impl ContextServer {
Ok(())
}
async fn stop(&self) -> anyhow::Result<()> {
pub fn stop(&self) -> Result<()> {
let mut client = self.client.write();
if let Some(protocol) = client.take() {
drop(protocol);
@@ -110,107 +134,96 @@ impl ContextServer {
}
}
/// A Context server manager manages the starting and stopping
/// of all servers. To obtain a server to interact with, a crate
/// must go through the `GlobalContextServerManager` which holds
/// a model to the ContextServerManager.
pub struct ContextServerManager {
servers: HashMap<String, Arc<ContextServer>>,
pending_servers: HashSet<String>,
servers: HashMap<Arc<str>, Arc<ContextServer>>,
project: Model<Project>,
registry: Model<ContextServerFactoryRegistry>,
update_servers_task: Option<Task<Result<()>>>,
needs_server_update: bool,
_subscriptions: Vec<Subscription>,
}
pub enum Event {
ServerStarted { server_id: String },
ServerStopped { server_id: String },
ServerStarted { server_id: Arc<str> },
ServerStopped { server_id: Arc<str> },
}
impl Global for ContextServerManager {}
impl EventEmitter<Event> for ContextServerManager {}
impl Default for ContextServerManager {
fn default() -> Self {
Self::new()
}
}
impl ContextServerManager {
pub fn new() -> Self {
Self {
servers: HashMap::default(),
pending_servers: HashSet::default(),
}
}
pub fn global(cx: &AppContext) -> Model<Self> {
cx.global::<GlobalContextServerManager>().0.clone()
}
pub fn add_server(
&mut self,
config: ServerConfig,
pub fn new(
registry: Model<ContextServerFactoryRegistry>,
project: Model<Project>,
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
let server_id = config.id.clone();
if self.servers.contains_key(&server_id) || self.pending_servers.contains(&server_id) {
return Task::ready(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 {
let mut this = Self {
_subscriptions: vec![
cx.observe(&registry, |this, _registry, cx| {
this.available_context_servers_changed(cx);
}),
cx.observe_global::<SettingsStore>(|this, cx| {
this.available_context_servers_changed(cx);
}),
],
project,
registry,
needs_server_update: false,
servers: HashMap::default(),
update_servers_task: None,
};
this.available_context_servers_changed(cx);
this
}
self.pending_servers.insert(server_id);
task
fn available_context_servers_changed(&mut self, cx: &mut ModelContext<Self>) {
if self.update_servers_task.is_some() {
self.needs_server_update = true;
} else {
self.update_servers_task = Some(cx.spawn(|this, mut cx| async move {
this.update(&mut cx, |this, _| {
this.needs_server_update = false;
})?;
Self::maintain_servers(this.clone(), cx.clone()).await?;
this.update(&mut cx, |this, cx| {
let has_any_context_servers = !this.servers().is_empty();
if has_any_context_servers {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.show_namespace(CONTEXT_SERVERS_NAMESPACE);
});
}
this.update_servers_task.take();
if this.needs_server_update {
this.available_context_servers_changed(cx);
}
})?;
Ok(())
}));
}
}
pub fn get_server(&self, id: &str) -> Option<Arc<ContextServer>> {
self.servers.get(id).cloned()
}
pub fn remove_server(
&mut self,
id: &str,
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
let id = id.to_string();
cx.spawn(|this, mut cx| async move {
if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? {
server.stop().await?;
}
this.update(&mut cx, |this, cx| {
this.pending_servers.remove(&id);
cx.emit(Event::ServerStopped {
server_id: id.clone(),
})
})?;
Ok(())
})
self.servers
.get(id)
.filter(|server| server.client().is_some())
.cloned()
}
pub fn restart_server(
&mut self,
id: &str,
id: &Arc<str>,
cx: &mut ModelContext<Self>,
) -> Task<anyhow::Result<()>> {
let id = id.to_string();
let id = id.clone();
cx.spawn(|this, mut cx| async move {
if let Some(server) = this.update(&mut cx, |this, _cx| this.servers.remove(&id))? {
server.stop().await?;
let config = server.config.clone();
let new_server = Arc::new(ContextServer::new(config));
new_server.start(&cx).await?;
server.stop()?;
let config = server.config();
let new_server = Arc::new(ContextServer::new(id.clone(), config));
new_server.clone().start(&cx).await?;
this.update(&mut cx, |this, cx| {
this.servers.insert(id.clone(), new_server);
cx.emit(Event::ServerStopped {
@@ -226,78 +239,82 @@ impl ContextServerManager {
}
pub fn servers(&self) -> Vec<Arc<ContextServer>> {
self.servers.values().cloned().collect()
self.servers
.values()
.filter(|server| server.client().is_some())
.cloned()
.collect()
}
pub fn model(cx: &mut AppContext) -> Model<Self> {
cx.new_model(|_cx| ContextServerManager::new())
}
}
async fn maintain_servers(this: WeakModel<Self>, mut cx: AsyncAppContext) -> Result<()> {
let mut desired_servers = HashMap::default();
pub struct GlobalContextServerManager(Model<ContextServerManager>);
impl Global for GlobalContextServerManager {}
impl GlobalContextServerManager {
fn register(cx: &mut AppContext) {
let model = ContextServerManager::model(cx);
cx.set_global(Self(model));
}
}
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 = manager
.servers()
.into_iter()
.map(|server| (server.id.clone(), server.config.clone()))
.collect::<HashMap<_, _>>();
let new_servers = settings
.servers
.iter()
.map(|config| (config.id.clone(), config.clone()))
.collect::<HashMap<_, _>>();
let servers_to_add = new_servers
.values()
.filter(|config| !current_servers.contains_key(&config.id))
.cloned()
.collect::<Vec<_>>();
let servers_to_remove = current_servers
.keys()
.filter(|id| !new_servers.contains_key(*id))
.cloned()
.collect::<Vec<_>>();
log::trace!("servers_to_add={:?}", servers_to_add);
for config in servers_to_add {
manager.add_server(config, cx).detach_and_log_err(cx);
}
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);
let (registry, project) = this.update(&mut cx, |this, cx| {
let location = this.project.read(cx).worktrees(cx).next().map(|worktree| {
settings::SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: Path::new(""),
}
});
})
})
.detach();
let settings = ContextServerSettings::get(location, cx);
desired_servers = settings.context_servers.clone();
(this.registry.clone(), this.project.clone())
})?;
for (id, factory) in
registry.read_with(&cx, |registry, _| registry.context_server_factories())?
{
let config = desired_servers.entry(id).or_default();
if config.command.is_none() {
if let Some(extension_command) = factory(project.clone(), &cx).await.log_err() {
config.command = Some(extension_command);
}
}
}
let mut servers_to_start = HashMap::default();
let mut servers_to_stop = HashMap::default();
this.update(&mut cx, |this, _cx| {
this.servers.retain(|id, server| {
if desired_servers.contains_key(id) {
true
} else {
servers_to_stop.insert(id.clone(), server.clone());
false
}
});
for (id, config) in desired_servers {
let existing_config = this.servers.get(&id).map(|server| server.config());
if existing_config.as_deref() != Some(&config) {
let config = Arc::new(config);
let server = Arc::new(ContextServer::new(id.clone(), config));
servers_to_start.insert(id.clone(), server.clone());
let old_server = this.servers.insert(id.clone(), server);
if let Some(old_server) = old_server {
servers_to_stop.insert(id, old_server);
}
}
}
})?;
for (id, server) in servers_to_stop {
server.stop().log_err();
this.update(&mut cx, |_, cx| {
cx.emit(Event::ServerStopped { server_id: id })
})?;
}
for (id, server) in servers_to_start {
if server.start(&cx).await.log_err().is_some() {
this.update(&mut cx, |_, cx| {
cx.emit(Event::ServerStarted { server_id: id })
})?;
}
}
Ok(())
}
}

View File

@@ -11,8 +11,6 @@ use collections::HashMap;
use crate::client::Client;
use crate::types;
const PROTOCOL_VERSION: &str = "2024-10-07";
pub struct ModelContextProtocol {
inner: Client,
}
@@ -23,10 +21,9 @@ impl ModelContextProtocol {
}
fn supported_protocols() -> Vec<types::ProtocolVersion> {
vec![
types::ProtocolVersion::VersionString(PROTOCOL_VERSION.to_string()),
types::ProtocolVersion::VersionNumber(1),
]
vec![types::ProtocolVersion(
types::LATEST_PROTOCOL_VERSION.to_string(),
)]
}
pub async fn initialize(
@@ -34,11 +31,13 @@ impl ModelContextProtocol {
client_info: types::Implementation,
) -> Result<InitializedContextServerProtocol> {
let params = types::InitializeParams {
protocol_version: types::ProtocolVersion::VersionString(PROTOCOL_VERSION.to_string()),
protocol_version: types::ProtocolVersion(types::LATEST_PROTOCOL_VERSION.to_string()),
capabilities: types::ClientCapabilities {
experimental: None,
sampling: None,
roots: None,
},
meta: None,
client_info,
};
@@ -113,7 +112,10 @@ impl InitializedContextServerProtocol {
let response: types::PromptsListResponse = self
.inner
.request(types::RequestType::PromptsList.as_str(), ())
.request(
types::RequestType::PromptsList.as_str(),
serde_json::json!({}),
)
.await?;
Ok(response.prompts)
@@ -125,7 +127,10 @@ impl InitializedContextServerProtocol {
let response: types::ResourcesListResponse = self
.inner
.request(types::RequestType::ResourcesList.as_str(), ())
.request(
types::RequestType::ResourcesList.as_str(),
serde_json::json!({}),
)
.await?;
Ok(response)
@@ -142,6 +147,7 @@ impl InitializedContextServerProtocol {
let params = types::PromptsGetParams {
name: prompt.as_ref().to_string(),
arguments: Some(arguments),
meta: None,
};
let response: types::PromptsGetResponse = self
@@ -164,6 +170,7 @@ impl InitializedContextServerProtocol {
name: argument.into(),
value: value.into(),
},
meta: None,
};
let result: types::CompletionCompleteResponse = self
.inner
@@ -204,6 +211,7 @@ impl InitializedContextServerProtocol {
let params = types::CallToolParams {
name: tool.as_ref().to_string(),
arguments,
meta: None,
};
let response: types::CallToolResponse = self

View File

@@ -1,69 +1,62 @@
use std::sync::Arc;
use anyhow::Result;
use collections::HashMap;
use gpui::{AppContext, Global, ReadGlobal};
use parking_lot::RwLock;
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ReadGlobal, Task};
use project::Project;
struct GlobalContextServerRegistry(Arc<ContextServerRegistry>);
use crate::manager::ServerCommand;
impl Global for GlobalContextServerRegistry {}
pub type ContextServerFactory = Arc<
dyn Fn(Model<Project>, &AsyncAppContext) -> Task<Result<ServerCommand>> + Send + Sync + 'static,
>;
pub struct ContextServerRegistry {
command_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
tool_registry: RwLock<HashMap<String, Vec<Arc<str>>>>,
struct GlobalContextServerFactoryRegistry(Model<ContextServerFactoryRegistry>);
impl Global for GlobalContextServerFactoryRegistry {}
#[derive(Default)]
pub struct ContextServerFactoryRegistry {
context_servers: HashMap<Arc<str>, ContextServerFactory>,
}
impl ContextServerRegistry {
pub fn global(cx: &AppContext) -> Arc<Self> {
GlobalContextServerRegistry::global(cx).0.clone()
impl ContextServerFactoryRegistry {
/// Returns the global [`ContextServerFactoryRegistry`].
pub fn global(cx: &AppContext) -> Model<Self> {
GlobalContextServerFactoryRegistry::global(cx).0.clone()
}
pub fn register(cx: &mut AppContext) {
cx.set_global(GlobalContextServerRegistry(Arc::new(
ContextServerRegistry {
command_registry: RwLock::new(HashMap::default()),
tool_registry: RwLock::new(HashMap::default()),
},
)))
/// Returns the global [`ContextServerFactoryRegistry`].
///
/// Inserts a default [`ContextServerFactoryRegistry`] if one does not yet exist.
pub fn default_global(cx: &mut AppContext) -> Model<Self> {
if !cx.has_global::<GlobalContextServerFactoryRegistry>() {
let registry = cx.new_model(|_| Self::new());
cx.set_global(GlobalContextServerFactoryRegistry(registry));
}
cx.global::<GlobalContextServerFactoryRegistry>().0.clone()
}
pub fn register_command(&self, server_id: String, command_name: &str) {
let mut registry = self.command_registry.write();
registry
.entry(server_id)
.or_default()
.push(command_name.into());
}
pub fn unregister_command(&self, server_id: &str, command_name: &str) {
let mut registry = self.command_registry.write();
if let Some(commands) = registry.get_mut(server_id) {
commands.retain(|name| name.as_ref() != command_name);
pub fn new() -> Self {
Self {
context_servers: HashMap::default(),
}
}
pub fn get_commands(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
let registry = self.command_registry.read();
registry.get(server_id).cloned()
pub fn context_server_factories(&self) -> Vec<(Arc<str>, ContextServerFactory)> {
self.context_servers
.iter()
.map(|(id, factory)| (id.clone(), factory.clone()))
.collect()
}
pub fn register_tool(&self, server_id: String, tool_name: &str) {
let mut registry = self.tool_registry.write();
registry
.entry(server_id)
.or_default()
.push(tool_name.into());
/// Registers the provided [`ContextServerFactory`].
pub fn register_server_factory(&mut self, id: Arc<str>, factory: ContextServerFactory) {
self.context_servers.insert(id, factory);
}
pub fn unregister_tool(&self, server_id: &str, tool_name: &str) {
let mut registry = self.tool_registry.write();
if let Some(tools) = registry.get_mut(server_id) {
tools.retain(|name| name.as_ref() != tool_name);
}
}
pub fn get_tools(&self, server_id: &str) -> Option<Vec<Arc<str>>> {
let registry = self.tool_registry.read();
registry.get(server_id).cloned()
/// Unregisters the [`ContextServerFactory`] for the server with the given ID.
pub fn unregister_server_factory_by_id(&mut self, server_id: &str) {
self.context_servers.remove(server_id);
}
}

View File

@@ -2,8 +2,8 @@ use collections::HashMap;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05";
pub enum RequestType {
Initialize,
CallTool,
@@ -18,6 +18,7 @@ pub enum RequestType {
Ping,
ListTools,
ListResourceTemplates,
ListRoots,
}
impl RequestType {
@@ -36,16 +37,14 @@ impl RequestType {
RequestType::Ping => "ping",
RequestType::ListTools => "tools/list",
RequestType::ListResourceTemplates => "resources/templates/list",
RequestType::ListRoots => "roots/list",
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProtocolVersion {
VersionString(String),
VersionNumber(u32),
}
#[serde(transparent)]
pub struct ProtocolVersion(pub String);
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
@@ -53,6 +52,8 @@ pub struct InitializeParams {
pub protocol_version: ProtocolVersion,
pub capabilities: ClientCapabilities,
pub client_info: Implementation,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
@@ -61,30 +62,40 @@ pub struct CallToolParams {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<HashMap<String, serde_json::Value>>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesUnsubscribeParams {
pub uri: Url,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesSubscribeParams {
pub uri: Url,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesReadParams {
pub uri: Url,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoggingSetLevelParams {
pub level: LoggingLevel,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
@@ -93,6 +104,8 @@ pub struct PromptsGetParams {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<HashMap<String, String>>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
@@ -100,6 +113,8 @@ pub struct PromptsGetParams {
pub struct CompletionCompleteParams {
pub r#ref: CompletionReference,
pub argument: CompletionArgument,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize)]
@@ -145,12 +160,16 @@ pub struct InitializeResponse {
pub protocol_version: ProtocolVersion,
pub capabilities: ServerCapabilities,
pub server_info: Implementation,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesReadResponse {
pub contents: Vec<ResourceContent>,
pub contents: Vec<ResourceContents>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
@@ -159,29 +178,39 @@ pub struct ResourcesListResponse {
pub resources: Vec<Resource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SamplingMessage {
pub role: Role,
pub content: MessageContent,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SamplingMessage {
pub role: SamplingRole,
pub content: SamplingContent,
pub struct PromptMessage {
pub role: Role,
pub content: MessageContent,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SamplingRole {
pub enum Role {
User,
Assistant,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SamplingContent {
pub enum MessageContent {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { data: String, mime_type: String },
#[serde(rename = "resource")]
Resource { resource: ResourceContents },
}
#[derive(Debug, Deserialize)]
@@ -189,7 +218,9 @@ pub enum SamplingContent {
pub struct PromptsGetResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<SamplingMessage>,
pub messages: Vec<PromptMessage>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
@@ -198,12 +229,16 @@ pub struct PromptsListResponse {
pub prompts: Vec<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompletionCompleteResponse {
pub completion: CompletionResult,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
@@ -214,6 +249,8 @@ pub struct CompletionResult {
pub total: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize, Serialize)]
@@ -243,6 +280,8 @@ pub struct ClientCapabilities {
pub experimental: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub roots: Option<RootsCapabilities>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -283,6 +322,13 @@ pub struct ToolsCapabilities {
pub list_changed: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RootsCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tool {
@@ -312,14 +358,28 @@ pub struct Resource {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceContent {
pub struct ResourceContents {
pub uri: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextResourceContents {
pub uri: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
pub mime_type: Option<String>,
pub text: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlobResourceContents {
pub uri: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
pub mime_type: Option<String>,
pub blob: String,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -338,8 +398,32 @@ pub struct ResourceTemplate {
pub enum LoggingLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelPreferences {
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<ModelHint>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_priority: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speed_priority: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub intelligence_priority: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelHint {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Serialize)]
@@ -352,6 +436,7 @@ pub enum NotificationType {
ResourcesListChanged,
ToolsListChanged,
PromptsListChanged,
RootsListChanged,
}
impl NotificationType {
@@ -364,6 +449,7 @@ impl NotificationType {
NotificationType::ResourcesListChanged => "notifications/resources/list_changed",
NotificationType::ToolsListChanged => "notifications/tools/list_changed",
NotificationType::PromptsListChanged => "notifications/prompts/list_changed",
NotificationType::RootsListChanged => "notifications/roots/list_changed",
}
}
}
@@ -373,6 +459,14 @@ impl NotificationType {
pub enum ClientNotification {
Initialized,
Progress(ProgressParams),
RootsListChanged,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProgressToken {
String(String),
Number(f64),
}
#[derive(Debug, Serialize)]
@@ -382,10 +476,10 @@ pub struct ProgressParams {
pub progress: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<f64>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
pub type ProgressToken = String;
pub enum CompletionTotal {
Exact(u32),
HasMore,
@@ -410,7 +504,22 @@ pub struct Completion {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallToolResponse {
pub tool_result: serde_json::Value,
pub content: Vec<ToolResponseContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ToolResponseContent {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { data: String, mime_type: String },
#[serde(rename = "resource")]
Resource { resource: ResourceContents },
}
#[derive(Debug, Deserialize)]
@@ -419,4 +528,22 @@ pub struct ListToolsResponse {
pub tools: Vec<Tool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListRootsResponse {
pub roots: Vec<Root>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Root {
pub uri: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}

View File

@@ -14,14 +14,14 @@ use gpui::{
actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Global, Model,
ModelContext, Task, WeakModel,
};
use http_client::github::latest_github_release;
use http_client::github::get_release_by_tag_name;
use http_client::HttpClient;
use language::{
language_settings::{all_language_settings, language_settings, InlineCompletionProvider},
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
ToPointUtf16,
};
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification;
@@ -446,9 +446,11 @@ impl Copilot {
Path::new("/")
};
let server_name = LanguageServerName("copilot".into());
let server = LanguageServer::new(
Arc::new(Mutex::new(None)),
new_server_id,
server_name,
binary,
root_path,
None,
@@ -987,12 +989,12 @@ async fn clear_copilot_dir() {
}
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
const SERVER_PATH: &str = "dist/agent.js";
const SERVER_PATH: &str = "dist/language-server.js";
///Check for the latest copilot language server and download it if we haven't already
async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
let release =
latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
get_release_by_tag_name("zed-industries/copilot", "v0.7.0", http.clone()).await?;
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
@@ -1227,8 +1229,10 @@ mod tests {
Some(self)
}
fn mtime(&self) -> Option<std::time::SystemTime> {
unimplemented!()
fn disk_state(&self) -> language::DiskState {
language::DiskState::Present {
mtime: std::time::UNIX_EPOCH,
}
}
fn path(&self) -> &Arc<Path> {
@@ -1243,10 +1247,6 @@ mod tests {
unimplemented!()
}
fn is_deleted(&self) -> bool {
unimplemented!()
}
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!()
}
@@ -1272,5 +1272,9 @@ mod tests {
fn load(&self, _: &AppContext) -> Task<Result<String>> {
unimplemented!()
}
fn load_bytes(&self, _cx: &AppContext) -> Task<Result<Vec<u8>>> {
unimplemented!()
}
}
}

View File

@@ -1,13 +1,13 @@
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::OnceLock;
use std::{sync::Arc, time::Duration};
use anyhow::{anyhow, Result};
use chrono::DateTime;
use fs::Fs;
use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, Global};
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Request as HttpRequest};
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use paths::home_dir;
use serde::{Deserialize, Serialize};
use settings::watch_config_file;
@@ -29,38 +29,32 @@ pub enum Role {
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
#[default]
#[serde(rename = "gpt-4o")]
#[serde(alias = "gpt-4o", rename = "gpt-4o-2024-05-13")]
Gpt4o,
#[serde(rename = "gpt-4o-mini")]
Gpt4oMini,
#[serde(rename = "gpt-4")]
#[serde(alias = "gpt-4", rename = "gpt-4")]
Gpt4,
#[serde(rename = "gpt-4-turbo")]
Gpt4Turbo,
#[serde(rename = "gpt-3.5-turbo")]
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
Gpt3_5Turbo,
#[serde(rename = "o1-preview")]
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
O1Preview,
#[serde(rename = "o1-mini")]
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
O1Mini,
#[serde(rename = "claude-3.5-sonnet")]
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet,
}
impl Model {
pub fn uses_streaming(&self) -> bool {
match self {
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
Self::O1Mini | Self::O1Preview => false,
_ => true,
}
}
pub fn from_id(id: &str) -> Result<Self> {
match id {
"gpt-4o" => Ok(Self::Gpt4o),
"gpt-4o-mini" => Ok(Self::Gpt4oMini),
"gpt-4" => Ok(Self::Gpt4),
"gpt-4-turbo" => Ok(Self::Gpt4Turbo),
"gpt-3.5-turbo" => Ok(Self::Gpt3_5Turbo),
"o1-preview" => Ok(Self::O1Preview),
"o1-mini" => Ok(Self::O1Mini),
@@ -73,21 +67,18 @@ impl Model {
match self {
Self::Gpt3_5Turbo => "gpt-3.5-turbo",
Self::Gpt4 => "gpt-4",
Self::Gpt4Turbo => "gpt-4-turbo",
Self::Gpt4o => "gpt-4o",
Self::Gpt4oMini => "gpt-4o-mini",
Self::O1Mini => "o1-mini",
Self::O1Preview => "o1-preview",
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
}
}
pub fn display_name(&self) -> &'static str {
match self {
Self::Gpt3_5Turbo => "GPT-3.5",
Self::Gpt4 => "GPT-4",
Self::Gpt4Turbo => "GPT-4 Turbo",
Self::Gpt4o => "GPT-4o",
Self::Gpt4oMini => "GPT-4o Mini",
Self::O1Mini => "o1-mini",
Self::O1Preview => "o1-preview",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
@@ -97,9 +88,7 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::Gpt4o => 64000,
Self::Gpt4oMini => 12288,
Self::Gpt4 => 32768,
Self::Gpt4Turbo => 64000,
Self::Gpt3_5Turbo => 12288,
Self::O1Mini => 20000,
Self::O1Preview => 20000,
@@ -265,7 +254,6 @@ impl CopilotChat {
pub async fn stream_completion(
request: Request,
low_speed_timeout: Option<Duration>,
mut cx: AsyncAppContext,
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
let Some(this) = cx.update(|cx| Self::global(cx)).ok().flatten() else {
@@ -285,8 +273,7 @@ impl CopilotChat {
let token = match api_token {
Some(api_token) if api_token.remaining_seconds() > 5 * 60 => api_token.clone(),
_ => {
let token =
request_api_token(&oauth_token, client.clone(), low_speed_timeout).await?;
let token = request_api_token(&oauth_token, client.clone()).await?;
this.update(&mut cx, |this, cx| {
this.api_token = Some(token.clone());
cx.notify();
@@ -295,25 +282,17 @@ impl CopilotChat {
}
};
stream_completion(client.clone(), token.api_key, request, low_speed_timeout).await
stream_completion(client.clone(), token.api_key, request).await
}
}
async fn request_api_token(
oauth_token: &str,
client: Arc<dyn HttpClient>,
low_speed_timeout: Option<Duration>,
) -> Result<ApiToken> {
let mut request_builder = HttpRequest::builder()
async fn request_api_token(oauth_token: &str, client: Arc<dyn HttpClient>) -> Result<ApiToken> {
let request_builder = HttpRequest::builder()
.method(Method::GET)
.uri(COPILOT_CHAT_AUTH_URL)
.header("Authorization", format!("token {}", oauth_token))
.header("Accept", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let request = request_builder.body(AsyncBody::empty())?;
let mut response = client.send(request).await?;
@@ -351,9 +330,8 @@ async fn stream_completion(
client: Arc<dyn HttpClient>,
api_key: String,
request: Request,
low_speed_timeout: Option<Duration>,
) -> Result<BoxStream<'static, Result<ResponseEvent>>> {
let mut request_builder = HttpRequest::builder()
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(COPILOT_CHAT_COMPLETION_URL)
.header(
@@ -367,9 +345,6 @@ async fn stream_completion(
.header("Content-Type", "application/json")
.header("Copilot-Integration-Id", "vscode-chat");
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let is_streaming = request.stream;
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;

View File

@@ -32,6 +32,7 @@ use std::{
cmp::Ordering,
mem,
ops::Range,
sync::Arc,
};
use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
@@ -726,6 +727,10 @@ impl Item for ProjectDiagnosticsEditor {
self.excerpts.read(cx).is_dirty(cx)
}
fn has_deleted_file(&self, cx: &AppContext) -> bool {
self.excerpts.read(cx).has_deleted_file(cx)
}
fn has_conflict(&self, cx: &AppContext) -> bool {
self.excerpts.read(cx).has_conflict(cx)
}
@@ -790,10 +795,11 @@ const DIAGNOSTIC_HEADER: &str = "diagnostic header";
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
let message: SharedString = message;
Box::new(move |cx| {
Arc::new(move |cx| {
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
h_flex()
.id(DIAGNOSTIC_HEADER)
.block_mouse_down()
.h(2. * cx.line_height())
.pl_10()
.pr_5()

View File

@@ -297,6 +297,7 @@ gpui::actions!(
OpenExcerptsSplit,
OpenProposedChangesEditor,
OpenFile,
OpenDocs,
OpenPermalinkToLine,
OpenUrl,
Outdent,

View File

@@ -1,5 +1,3 @@
use std::path::PathBuf;
use anyhow::Context as _;
use gpui::{View, ViewContext, WindowContext};
use language::Language;
@@ -54,9 +52,9 @@ pub fn switch_source_header(
cx.spawn(|_editor, mut cx| async move {
let switch_source_header = switch_source_header_task
.await
.with_context(|| format!("Switch source/header LSP request for path \"{}\" failed", source_file))?;
.with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))?;
if switch_source_header.0.is_empty() {
log::info!("Clangd returned an empty string when requesting to switch source/header from \"{}\"", source_file);
log::info!("Clangd returned an empty string when requesting to switch source/header from \"{source_file}\"" );
return Ok(());
}
@@ -67,14 +65,17 @@ pub fn switch_source_header(
)
})?;
let path = goto.to_file_path().map_err(|()| {
anyhow::anyhow!("URL conversion to file path failed for \"{goto}\"")
})?;
workspace
.update(&mut cx, |workspace, view_cx| {
workspace.open_abs_path(PathBuf::from(goto.path()), false, view_cx)
workspace.open_abs_path(path, false, view_cx)
})
.with_context(|| {
format!(
"Switch source/header could not open \"{}\" in workspace",
goto.path()
"Switch source/header could not open \"{goto}\" in workspace"
)
})?
.await

View File

@@ -5,6 +5,7 @@ use gpui::{Task, ViewContext};
use crate::Editor;
#[derive(Debug)]
pub struct DebouncedDelay {
task: Option<Task<()>>,
cancel_channel: Option<oneshot::Sender<()>>,

View File

@@ -36,7 +36,7 @@ use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
pub use crease_map::*;
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
use fold_map::{FoldMap, FoldMapWriter, FoldOffset, FoldSnapshot};
use fold_map::{FoldMap, FoldSnapshot};
use gpui::{
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
};
@@ -65,8 +65,8 @@ use std::{
};
use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot};
use text::{Edit, LineIndent};
use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext};
use text::LineIndent;
use ui::{px, SharedString, WindowContext};
use unicode_segmentation::UnicodeSegmentation;
use wrap_map::{WrapMap, WrapSnapshot};
@@ -197,22 +197,86 @@ impl DisplayMap {
other
.folds_in_range(0..other.buffer_snapshot.len())
.map(|fold| {
(
Crease::simple(
fold.range.to_offset(&other.buffer_snapshot),
fold.placeholder.clone(),
)
}),
})
.collect(),
cx,
);
}
/// Creates folds for the given ranges.
pub fn fold<T: ToOffset>(
/// Creates folds for the given creases.
pub fn fold<T: Clone + ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
creases: Vec<Crease<T>>,
cx: &mut ModelContext<Self>,
) {
self.update_fold_map(cx, |fold_map| fold_map.fold(ranges))
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
let inline = creases.iter().filter_map(|crease| {
if let Crease::Inline {
range, placeholder, ..
} = crease
{
Some((range.clone(), placeholder.clone()))
} else {
None
}
});
let (snapshot, edits) = fold_map.fold(inline);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits);
let blocks = creases.into_iter().filter_map(|crease| {
if let Crease::Block {
range,
block_height,
render_block,
block_style,
block_priority,
..
} = crease
{
Some((
range,
render_block,
block_height,
block_style,
block_priority,
))
} else {
None
}
});
block_map.insert(
blocks
.into_iter()
.map(|(range, render, height, style, priority)| {
let start = buffer_snapshot.anchor_before(range.start);
let end = buffer_snapshot.anchor_after(range.end);
BlockProperties {
placement: BlockPlacement::Replace(start..end),
render,
height,
style,
priority,
}
}),
);
}
/// Removes any folds with the given ranges.
@@ -221,26 +285,6 @@ impl DisplayMap {
ranges: impl IntoIterator<Item = Range<T>>,
type_id: TypeId,
cx: &mut ModelContext<Self>,
) {
self.update_fold_map(cx, |fold_map| fold_map.remove_folds(ranges, type_id))
}
/// Removes any folds whose ranges intersect any of the given ranges.
pub fn unfold_intersecting<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
inclusive: bool,
cx: &mut ModelContext<Self>,
) {
self.update_fold_map(cx, |fold_map| {
fold_map.unfold_intersecting(ranges, inclusive)
})
}
fn update_fold_map(
&mut self,
cx: &mut ModelContext<Self>,
callback: impl FnOnce(&mut FoldMapWriter) -> (FoldSnapshot, Vec<Edit<FoldOffset>>),
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
@@ -252,17 +296,49 @@ impl DisplayMap {
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
let (snapshot, edits) = callback(&mut fold_map);
let (snapshot, edits) = fold_map.remove_folds(ranges, type_id);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.write(snapshot, edits);
}
/// Removes any folds whose ranges intersect any of the given ranges.
pub fn unfold_intersecting<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
inclusive: bool,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let offset_ranges = ranges
.into_iter()
.map(|range| range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot))
.collect::<Vec<_>>();
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
let (snapshot, edits) =
fold_map.unfold_intersecting(offset_ranges.iter().cloned(), inclusive);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
let mut block_map = self.block_map.write(snapshot, edits);
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
}
pub fn insert_creases(
&mut self,
creases: impl IntoIterator<Item = Crease>,
creases: impl IntoIterator<Item = Crease<Anchor>>,
cx: &mut ModelContext<Self>,
) -> Vec<CreaseId> {
let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -465,11 +541,17 @@ pub struct HighlightStyles {
pub suggestion: Option<HighlightStyle>,
}
#[derive(Clone)]
pub enum ChunkReplacement {
Renderer(ChunkRenderer),
Str(SharedString),
}
pub struct HighlightedChunk<'a> {
pub text: &'a str,
pub style: Option<HighlightStyle>,
pub is_tab: bool,
pub renderer: Option<ChunkRenderer>,
pub replacement: Option<ChunkReplacement>,
}
impl<'a> HighlightedChunk<'a> {
@@ -481,7 +563,7 @@ impl<'a> HighlightedChunk<'a> {
let mut text = self.text;
let style = self.style;
let is_tab = self.is_tab;
let renderer = self.renderer;
let renderer = self.replacement;
iter::from_fn(move || {
let mut prefix_len = 0;
while let Some(&ch) = chars.peek() {
@@ -497,30 +579,33 @@ impl<'a> HighlightedChunk<'a> {
text: prefix,
style,
is_tab,
renderer: renderer.clone(),
replacement: renderer.clone(),
});
}
chars.next();
let (prefix, suffix) = text.split_at(ch.len_utf8());
text = suffix;
if let Some(replacement) = replacement(ch) {
let background = editor_style.status.hint_background;
let underline = editor_style.status.hint;
let invisible_highlight = HighlightStyle {
background_color: Some(editor_style.status.hint_background),
underline: Some(UnderlineStyle {
color: Some(editor_style.status.hint),
thickness: px(1.),
wavy: false,
}),
..Default::default()
};
let invisible_style = if let Some(mut style) = style {
style.highlight(invisible_highlight);
style
} else {
invisible_highlight
};
return Some(HighlightedChunk {
text: prefix,
style: None,
style: Some(invisible_style),
is_tab: false,
renderer: Some(ChunkRenderer {
render: Arc::new(move |_| {
div()
.child(replacement)
.bg(background)
.text_decoration_1()
.text_decoration_color(underline)
.into_any_element()
}),
constrain_width: false,
}),
replacement: Some(ChunkReplacement::Str(replacement.into())),
});
} else {
let invisible_highlight = HighlightStyle {
@@ -543,7 +628,7 @@ impl<'a> HighlightedChunk<'a> {
text: prefix,
style: Some(invisible_style),
is_tab: false,
renderer: renderer.clone(),
replacement: renderer.clone(),
});
}
}
@@ -555,7 +640,7 @@ impl<'a> HighlightedChunk<'a> {
text: remainder,
style,
is_tab,
renderer: renderer.clone(),
replacement: renderer.clone(),
})
} else {
None
@@ -596,7 +681,7 @@ impl DisplaySnapshot {
) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
self.block_snapshot
.buffer_rows(BlockRow(start_row.0))
.map(|row| row.map(|row| MultiBufferRow(row.0)))
.map(|row| row.map(MultiBufferRow))
}
pub fn max_buffer_row(&self) -> MultiBufferRow {
@@ -819,7 +904,7 @@ impl DisplaySnapshot {
text: chunk.text,
style: highlight_style,
is_tab: chunk.is_tab,
renderer: chunk.renderer,
replacement: chunk.renderer.map(ChunkReplacement::Renderer),
}
.highlight_invisibles(editor_style)
})
@@ -987,7 +1072,12 @@ impl DisplaySnapshot {
}
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
self.fold_snapshot.is_line_folded(buffer_row)
self.block_snapshot.is_line_replaced(buffer_row)
|| self.fold_snapshot.is_line_folded(buffer_row)
}
pub fn is_line_replaced(&self, buffer_row: MultiBufferRow) -> bool {
self.block_snapshot.is_line_replaced(buffer_row)
}
pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
@@ -1061,19 +1151,42 @@ impl DisplaySnapshot {
.unwrap_or(false)
}
pub fn foldable_range(
&self,
buffer_row: MultiBufferRow,
) -> Option<(Range<Point>, FoldPlaceholder)> {
pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
if let Some(crease) = self
.crease_snapshot
.query_row(buffer_row, &self.buffer_snapshot)
{
Some((
crease.range.to_point(&self.buffer_snapshot),
crease.placeholder.clone(),
))
match crease {
Crease::Inline {
range,
placeholder,
render_toggle,
render_trailer,
metadata,
} => Some(Crease::Inline {
range: range.to_point(&self.buffer_snapshot),
placeholder: placeholder.clone(),
render_toggle: render_toggle.clone(),
render_trailer: render_trailer.clone(),
metadata: metadata.clone(),
}),
Crease::Block {
range,
block_height,
block_style,
render_block,
block_priority,
render_toggle,
} => Some(Crease::Block {
range: range.to_point(&self.buffer_snapshot),
block_height: *block_height,
block_style: *block_style,
render_block: render_block.clone(),
block_priority: *block_priority,
render_toggle: render_toggle.clone(),
}),
}
} else if self.starts_indent(MultiBufferRow(start.row))
&& !self.is_line_folded(MultiBufferRow(start.row))
{
@@ -1110,7 +1223,13 @@ impl DisplaySnapshot {
.line_len(MultiBufferRow(row_before_line_breaks.row)),
);
Some((start..row_before_line_breaks, self.fold_placeholder.clone()))
Some(Crease::Inline {
range: start..row_before_line_breaks,
placeholder: self.fold_placeholder.clone(),
render_toggle: None,
render_trailer: None,
metadata: None,
})
} else {
None
}
@@ -1418,7 +1537,7 @@ pub mod tests {
placement,
style: BlockStyle::Fixed,
height,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority,
}
})
@@ -1457,7 +1576,8 @@ pub mod tests {
map.fold(
ranges
.into_iter()
.map(|range| (range, FoldPlaceholder::test())),
.map(|range| Crease::simple(range, FoldPlaceholder::test()))
.collect(),
cx,
);
});
@@ -1832,7 +1952,7 @@ pub mod tests {
map.update(cx, |map, cx| {
map.fold(
vec![(
vec![Crease::simple(
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
FoldPlaceholder::test(),
)],
@@ -1922,7 +2042,7 @@ pub mod tests {
),
height: 1,
style: BlockStyle::Sticky,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
}],
cx,
@@ -2028,7 +2148,7 @@ pub mod tests {
),
height: 1,
style: BlockStyle::Sticky,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
}],
cx,
@@ -2104,7 +2224,7 @@ pub mod tests {
),
height: 4,
style: BlockStyle::Fixed,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
}],
cx,
@@ -2253,7 +2373,7 @@ pub mod tests {
map.update(cx, |map, cx| {
map.fold(
vec![(
vec![Crease::simple(
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
FoldPlaceholder::test(),
)],
@@ -2452,7 +2572,7 @@ pub mod tests {
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
map.crease_map.insert(
[Crease::new(
[Crease::inline(
range,
FoldPlaceholder::test(),
|_row, _status, _toggle, _cx| div(),

View File

@@ -7,7 +7,7 @@ use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
use language::{Chunk, Patch, Point};
use multi_buffer::{
Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, MultiBufferSnapshot, ToPoint as _,
Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, MultiBufferSnapshot, ToOffset, ToPoint as _,
};
use parking_lot::Mutex;
use std::{
@@ -77,7 +77,7 @@ pub struct BlockRow(pub(super) u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32);
pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum BlockPlacement<T> {
@@ -352,6 +352,13 @@ impl Block {
Block::ExcerptBoundary { next_excerpt, .. } => next_excerpt.is_none(),
}
}
fn is_replacement(&self) -> bool {
match self {
Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
Block::ExcerptBoundary { .. } => false,
}
}
}
impl Debug for Block {
@@ -1119,6 +1126,64 @@ impl<'a> BlockMapWriter<'a> {
.retain(|id, _| !block_ids.contains(id));
self.0.sync(wrap_snapshot, edits);
}
pub fn remove_intersecting_replace_blocks<T>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
inclusive: bool,
) where
T: ToOffset,
{
let wrap_snapshot = self.0.wrap_snapshot.borrow();
let mut blocks_to_remove = HashSet::default();
for range in ranges {
let range = range.start.to_offset(wrap_snapshot.buffer_snapshot())
..range.end.to_offset(wrap_snapshot.buffer_snapshot());
for block in self.blocks_intersecting_buffer_range(range, inclusive) {
if matches!(block.placement, BlockPlacement::Replace(_)) {
blocks_to_remove.insert(block.id);
}
}
}
drop(wrap_snapshot);
self.remove(blocks_to_remove);
}
fn blocks_intersecting_buffer_range(
&self,
range: Range<usize>,
inclusive: bool,
) -> &[Arc<CustomBlock>] {
let wrap_snapshot = self.0.wrap_snapshot.borrow();
let buffer = wrap_snapshot.buffer_snapshot();
let start_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
probe
.end()
.to_offset(buffer)
.cmp(&range.start)
.then(if inclusive {
Ordering::Greater
} else {
Ordering::Less
})
}) {
Ok(ix) | Err(ix) => ix,
};
let end_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
probe
.start()
.to_offset(buffer)
.cmp(&range.end)
.then(if inclusive {
Ordering::Less
} else {
Ordering::Greater
})
}) {
Ok(ix) | Err(ix) => ix,
};
&self.0.custom_blocks[start_block_ix..end_block_ix]
}
}
impl BlockSnapshot {
@@ -1298,6 +1363,21 @@ impl BlockSnapshot {
cursor.item().map_or(false, |t| t.block.is_some())
}
pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
let wrap_point = self
.wrap_snapshot
.make_wrap_point(Point::new(row.0, 0), Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
cursor.item().map_or(false, |transform| {
if let Some(Block::Custom(block)) = transform.block.as_ref() {
matches!(block.placement, BlockPlacement::Replace(_))
} else {
false
}
})
}
pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(point.row), Bias::Right, &());
@@ -1515,7 +1595,7 @@ impl<'a> Iterator for BlockChunks<'a> {
}
impl<'a> Iterator for BlockBufferRows<'a> {
type Item = Option<BlockRow>;
type Item = Option<u32>;
fn next(&mut self) -> Option<Self::Item> {
if self.started {
@@ -1538,16 +1618,25 @@ impl<'a> Iterator for BlockBufferRows<'a> {
}
}
if self.transforms.item()?.block.is_none() {
let transform = self.transforms.item()?;
if transform
.block
.as_ref()
.map_or(true, |block| block.is_replacement())
{
self.input_buffer_rows.seek(self.transforms.start().1 .0);
}
}
let transform = self.transforms.item()?;
if transform.block.is_some() {
Some(None)
if let Some(block) = transform.block.as_ref() {
if block.is_replacement() && self.transforms.start().0 == self.output_row {
Some(self.input_buffer_rows.next().unwrap())
} else {
Some(None)
}
} else {
Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
Some(self.input_buffer_rows.next().unwrap())
}
}
}
@@ -1709,21 +1798,21 @@ mod tests {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
height: 2,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
height: 3,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
]);
@@ -1821,10 +1910,7 @@ mod tests {
);
assert_eq!(
snapshot
.buffer_rows(BlockRow(0))
.map(|row| row.map(|r| r.0))
.collect::<Vec<_>>(),
snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
&[
Some(0),
None,
@@ -1960,21 +2046,21 @@ mod tests {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
height: 2,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
height: 3,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
]);
@@ -2062,14 +2148,14 @@ mod tests {
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
height: 1,
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
height: 1,
priority: 0,
},
@@ -2109,7 +2195,7 @@ mod tests {
..buffer_snapshot.anchor_before(Point::new(3, 1)),
),
height: 4,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
}]);
@@ -2162,14 +2248,14 @@ mod tests {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
]);
@@ -2183,21 +2269,21 @@ mod tests {
style: BlockStyle::Fixed,
placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
height: 1,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
},
]);
@@ -2302,7 +2388,7 @@ mod tests {
style: BlockStyle::Fixed,
placement,
height,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
}
})
@@ -2321,7 +2407,7 @@ mod tests {
placement: props.placement.clone(),
height: props.height,
style: props.style,
render: Box::new(|_| div().into_any()),
render: Arc::new(|_| div().into_any()),
priority: 0,
}));
}
@@ -2409,6 +2495,7 @@ mod tests {
let mut expected_buffer_rows = Vec::new();
let mut expected_text = String::new();
let mut expected_block_positions = Vec::new();
let mut expected_replaced_buffer_rows = HashSet::default();
let input_text = wraps_snapshot.text();
// Loop over the input lines, creating (N - 1) empty lines for
@@ -2422,6 +2509,9 @@ mod tests {
let mut block_row = 0;
while let Some((wrap_row, input_line)) = input_text_lines.next() {
let wrap_row = wrap_row as u32;
let multibuffer_row = wraps_snapshot
.to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
.row;
// Create empty lines for the above block
while let Some((placement, block)) = sorted_blocks_iter.peek() {
@@ -2451,30 +2541,33 @@ mod tests {
{
if wrap_row >= replace_range.start.0 {
is_in_replace_block = true;
if wrap_row == replace_range.start.0 {
expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
}
if wrap_row == replace_range.end.0 {
expected_block_positions.push((block_row, block.id()));
if block.height() > 0 {
let text = "\n".repeat((block.height() - 1) as usize);
if block_row > 0 {
expected_text.push('\n');
}
expected_text.push_str(&text);
for _ in 0..block.height() {
expected_buffer_rows.push(None);
}
block_row += block.height();
let text = "\n".repeat((block.height() - 1) as usize);
if block_row > 0 {
expected_text.push('\n');
}
expected_text.push_str(&text);
for _ in 1..block.height() {
expected_buffer_rows.push(None);
}
block_row += block.height();
sorted_blocks_iter.next();
}
}
}
if !is_in_replace_block {
let buffer_row = input_buffer_rows[wraps_snapshot
.to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
.row as usize];
if is_in_replace_block {
expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
} else {
let buffer_row = input_buffer_rows[multibuffer_row as usize];
let soft_wrapped = wraps_snapshot
.to_tab_point(WrapPoint::new(wrap_row, 0))
.column()
@@ -2543,9 +2636,10 @@ mod tests {
assert_eq!(
blocks_snapshot
.buffer_rows(BlockRow(start_row as u32))
.map(|row| row.map(|r| r.0))
.collect::<Vec<_>>(),
&expected_buffer_rows[start_row..]
&expected_buffer_rows[start_row..],
"incorrect buffer_rows starting at row {:?}",
start_row
);
}
@@ -2666,6 +2760,16 @@ mod tests {
block_point.column += c.len_utf8() as u32;
}
}
for buffer_row in 0..=buffer_snapshot.max_point().row {
let buffer_row = MultiBufferRow(buffer_row);
assert_eq!(
blocks_snapshot.is_line_replaced(buffer_row),
expected_replaced_buffer_rows.contains(&buffer_row),
"incorrect is_line_replaced({:?})",
buffer_row
);
}
}
}

View File

@@ -2,12 +2,12 @@ use collections::HashMap;
use gpui::{AnyElement, IntoElement};
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, ops::Range, sync::Arc};
use std::{cmp::Ordering, fmt::Debug, ops::Range, sync::Arc};
use sum_tree::{Bias, SeekTarget, SumTree};
use text::Point;
use ui::{IconName, SharedString, WindowContext};
use crate::FoldPlaceholder;
use crate::{BlockStyle, FoldPlaceholder, RenderBlock};
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct CreaseId(usize);
@@ -45,15 +45,15 @@ impl CreaseSnapshot {
&'a self,
row: MultiBufferRow,
snapshot: &'a MultiBufferSnapshot,
) -> Option<&'a Crease> {
) -> Option<&'a Crease<Anchor>> {
let start = snapshot.anchor_before(Point::new(row.0, 0));
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) {
match Ord::cmp(&item.crease.range().start.to_point(snapshot).row, &row.0) {
Ordering::Less => cursor.next(snapshot),
Ordering::Equal => {
if item.crease.range.start.is_valid(snapshot) {
if item.crease.range().start.is_valid(snapshot) {
return Some(&item.crease);
} else {
cursor.next(snapshot);
@@ -69,7 +69,7 @@ impl CreaseSnapshot {
&'a self,
range: Range<MultiBufferRow>,
snapshot: &'a MultiBufferSnapshot,
) -> impl 'a + Iterator<Item = &'a Crease> {
) -> impl 'a + Iterator<Item = &'a Crease<Anchor>> {
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>(snapshot);
cursor.seek(&start, Bias::Left, snapshot);
@@ -77,8 +77,9 @@ impl CreaseSnapshot {
std::iter::from_fn(move || {
while let Some(item) = cursor.item() {
cursor.next(snapshot);
let crease_start = item.crease.range.start.to_point(snapshot);
let crease_end = item.crease.range.end.to_point(snapshot);
let crease_range = item.crease.range();
let crease_start = crease_range.start.to_point(snapshot);
let crease_end = crease_range.end.to_point(snapshot);
if crease_end.row > range.end.0 {
continue;
}
@@ -99,8 +100,9 @@ impl CreaseSnapshot {
cursor.next(snapshot);
while let Some(item) = cursor.item() {
let start_point = item.crease.range.start.to_point(snapshot);
let end_point = item.crease.range.end.to_point(snapshot);
let crease_range = item.crease.range();
let start_point = crease_range.start.to_point(snapshot);
let end_point = crease_range.end.to_point(snapshot);
results.push((item.id, start_point..end_point));
cursor.next(snapshot);
}
@@ -123,12 +125,22 @@ type RenderTrailerFn =
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
#[derive(Clone)]
pub struct Crease {
pub range: Range<Anchor>,
pub placeholder: FoldPlaceholder,
pub render_toggle: RenderToggleFn,
pub render_trailer: RenderTrailerFn,
pub metadata: Option<CreaseMetadata>,
pub enum Crease<T> {
Inline {
range: Range<T>,
placeholder: FoldPlaceholder,
render_toggle: Option<RenderToggleFn>,
render_trailer: Option<RenderTrailerFn>,
metadata: Option<CreaseMetadata>,
},
Block {
range: Range<T>,
block_height: u32,
block_style: BlockStyle,
render_block: RenderBlock,
block_priority: usize,
render_toggle: Option<RenderToggleFn>,
},
}
/// Metadata about a [`Crease`], that is used for serialization.
@@ -138,9 +150,30 @@ pub struct CreaseMetadata {
pub label: SharedString,
}
impl Crease {
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
range: Range<Anchor>,
impl<T> Crease<T> {
pub fn simple(range: Range<T>, placeholder: FoldPlaceholder) -> Self {
Crease::Inline {
range,
placeholder,
render_toggle: None,
render_trailer: None,
metadata: None,
}
}
pub fn block(range: Range<T>, height: u32, style: BlockStyle, render: RenderBlock) -> Self {
Self::Block {
range,
block_height: height,
block_style: style,
render_block: render,
block_priority: 0,
render_toggle: None,
}
}
pub fn inline<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
range: Range<T>,
placeholder: FoldPlaceholder,
render_toggle: RenderToggle,
render_trailer: RenderTrailer,
@@ -164,37 +197,76 @@ impl Crease {
+ 'static,
TrailerElement: IntoElement,
{
Crease {
Crease::Inline {
range,
placeholder,
render_toggle: Arc::new(move |row, folded, toggle, cx| {
render_toggle: Some(Arc::new(move |row, folded, toggle, cx| {
render_toggle(row, folded, toggle, cx).into_any_element()
}),
render_trailer: Arc::new(move |row, folded, cx| {
})),
render_trailer: Some(Arc::new(move |row, folded, cx| {
render_trailer(row, folded, cx).into_any_element()
}),
})),
metadata: None,
}
}
pub fn with_metadata(mut self, metadata: CreaseMetadata) -> Self {
self.metadata = Some(metadata);
self
pub fn with_metadata(self, metadata: CreaseMetadata) -> Self {
match self {
Crease::Inline {
range,
placeholder,
render_toggle,
render_trailer,
..
} => Crease::Inline {
range,
placeholder,
render_toggle,
render_trailer,
metadata: Some(metadata),
},
Crease::Block { .. } => self,
}
}
pub fn range(&self) -> &Range<T> {
match self {
Crease::Inline { range, .. } => range,
Crease::Block { range, .. } => range,
}
}
}
impl std::fmt::Debug for Crease {
impl<T> std::fmt::Debug for Crease<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Crease")
.field("range", &self.range)
.finish()
match self {
Crease::Inline {
range, metadata, ..
} => f
.debug_struct("Crease::Inline")
.field("range", range)
.field("metadata", metadata)
.finish_non_exhaustive(),
Crease::Block {
range,
block_height,
..
} => f
.debug_struct("Crease::Block")
.field("range", range)
.field("height", block_height)
.finish_non_exhaustive(),
}
}
}
#[derive(Clone, Debug)]
struct CreaseItem {
id: CreaseId,
crease: Crease,
crease: Crease<Anchor>,
}
impl CreaseMap {
@@ -204,7 +276,7 @@ impl CreaseMap {
pub fn insert(
&mut self,
creases: impl IntoIterator<Item = Crease>,
creases: impl IntoIterator<Item = Crease<Anchor>>,
snapshot: &MultiBufferSnapshot,
) -> Vec<CreaseId> {
let mut new_ids = Vec::new();
@@ -212,11 +284,12 @@ impl CreaseMap {
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);
let crease_range = crease.range().clone();
new_creases.append(cursor.slice(&crease_range, Bias::Left, snapshot), snapshot);
let id = self.next_id;
self.next_id.0 += 1;
self.id_to_range.insert(id, crease.range.clone());
self.id_to_range.insert(id, crease_range);
new_creases.push(CreaseItem { crease, id }, snapshot);
new_ids.push(id);
}
@@ -293,7 +366,7 @@ impl sum_tree::Item for CreaseItem {
fn summary(&self, _cx: &MultiBufferSnapshot) -> Self::Summary {
ItemSummary {
range: self.crease.range.clone(),
range: self.crease.range().clone(),
}
}
}
@@ -326,13 +399,13 @@ mod test {
// Insert creases
let creases = [
Crease::new(
Crease::inline(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Crease::new(
Crease::inline(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
@@ -372,19 +445,19 @@ mod test {
let mut crease_map = CreaseMap::new(&snapshot);
let creases = [
Crease::new(
Crease::inline(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Crease::new(
Crease::inline(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Crease::new(
Crease::inline(
snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
@@ -402,12 +475,12 @@ mod test {
let range = MultiBufferRow(2)..MultiBufferRow(5);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 1);
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 3);
assert_eq!(creases[0].range().start.to_point(&snapshot).row, 3);
let range = MultiBufferRow(0)..MultiBufferRow(2);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 1);
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 1);
assert_eq!(creases[0].range().start.to_point(&snapshot).row, 1);
let range = MultiBufferRow(6)..MultiBufferRow(7);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();

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