Compare commits

..

206 Commits

Author SHA1 Message Date
Conrad Irwin
db28a0ecde Format markdowns 2024-12-03 09:58:15 -07:00
Conrad Irwin
2223e6e5e1 Format markdown by default 2024-12-03 09:09:59 -07:00
Marshall Bowers
f3140f54d8 assistant2: Wire up error messages (#21426)
This PR wires up the error messages for Assistant 2 so that they are
shown to the user:

<img width="1138" alt="Screenshot 2024-12-02 at 4 28 02 PM"
src="https://github.com/user-attachments/assets/d8a5b9bd-0cef-4304-b561-b2edadbc70ef">
<img width="1138" alt="Screenshot 2024-12-02 at 4 29 09 PM"
src="https://github.com/user-attachments/assets/0dd70841-0d5a-4de6-bebe-82c563246b65">
<img width="1138" alt="Screenshot 2024-12-02 at 4 32 49 PM"
src="https://github.com/user-attachments/assets/a8838866-fad1-43a9-8935-490dc1936016">

@danilo-leal I kept the existing UX from Assistant 1, as I didn't see
any errors in the design prototype, but we can revisit if another
approach would work better.

Release Notes:

- N/A
2024-12-02 16:54:46 -05:00
yoleuh
72afe684b8 assistant: Use a smaller icon for the "New Chat" button (#21425)
Assistant new chat icon is slightly larger than editor pane new icon.

Changes:
Adds `IconSize::Small` to assistant default size new chat icon, not
really noticeable, but matches the new icon in editor pane, and the
assistant dropdown menu that have icon size small.

|old|new|
|---|---|

|![image](https://github.com/user-attachments/assets/cbef5054-a465-4957-9409-b4a73e703363)|![image](https://github.com/user-attachments/assets/baee66ea-76d6-43b4-a4b9-ead34991ff85)|

Release Notes:

- N/A
2024-12-02 16:48:20 -05:00
Piotr Osiewicz
59dc6cf523 toolchains: Run listing tasks on background thread (#21414)
Potentially fixes #21404

This is a speculative fix, as while I was trying to repro this issue
I've noticed that introducing artificial delays in ToolchainLister::list
could impact apps responsiveness. These delays were essentially there to
stimulate PET taking a while to find venvs.

Release Notes:

- Improved app responsiveness in environments with multiple Python
virtual environments
2024-12-02 21:03:31 +01:00
Marshall Bowers
b88daae67b assistant2: Add support for using tools provided by context servers (#21418)
This PR adds support to Assistant 2 for using tools provided by context
servers.

As part of this I introduced a new `ThreadStore`.

Release Notes:

- N/A

---------

Co-authored-by: Cole <cole@zed.dev>
2024-12-02 15:01:18 -05:00
Piotr Osiewicz
f32ffcf5bb workspace: Sanitize pinned tab count before usage (#21417)
Fixes all sorts of panics around usage of incorrect pinned tab count
that has been fixed in app itself, yet persists in user db.

Closes #ISSUE

Release Notes:

- N/A
2024-12-02 19:56:52 +01:00
Piotr Osiewicz
95a047c11b tasks/rust: Add support for running examples as binary targets (#21412)
Closes #21044

Release Notes:

- Added support for running Rust examples as tasks.
2024-12-02 19:53:51 +01:00
Kirill Bulatov
dbe41823d9 Use proper terminal item for splitting context (#21415)
Closes https://github.com/zed-industries/zed/issues/21411

Release Notes:

- N/A
2024-12-02 20:46:28 +02:00
Conrad Irwin
7c40824783 Fix macOS IME overlay positioning (#21416)
Release Notes:

- Improved positioning of macOS IME overlay

---------

Co-authored-by: Richard Feldman <richard@zed.dev>
2024-12-02 11:46:14 -07:00
Conrad Irwin
4e12f0580a Fix dismissing the IME viewer with escape (#21413)
Co-Authored-By: Richard Feldman <richard@zed.dev>

Closes #21392

Release Notes:

- Fixed dismissing the macOS IME menu with escape when no marked text
was present

---------

Co-authored-by: Richard Feldman <richard@zed.dev>
2024-12-02 11:20:27 -07:00
Danilo Leal
f795ce9623 Add language icons to the language selector (#21298)
Closes https://github.com/zed-industries/zed/issues/21290

This is a first attempt to show the language icons to the selector.
Ideally, I wouldn't like to have yet another place mapping extensions to
icons, as we already have the `file_types.json` file doing that, but I'm
not so sure how to pull from it yet. Maybe in a future pass we'll
improve this and make it more solid.

<img width="700" alt="Screenshot 2024-11-28 at 16 10 27"
src="https://github.com/user-attachments/assets/683c3bef-5389-470f-a41e-3d510b927b61">

Release Notes:

- N/A

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-12-02 15:01:09 -03:00
uncenter
995b40f149 Add "Copy Extension ID" action to extension card dropdown (#21395)
Adds a new "Copy Extension ID" action to the dropdown of remote
extension cards in the extensions list UI. Would have liked for it to be
a context menu where you could click anywhere on the card, but couldn't
figure out how to integrate that with the existing setup.

I've been missing this from VSCode's extension panel, which allows this
on right click:

![CleanShot 2024-12-01 at 22 03
14](https://github.com/user-attachments/assets/64796f96-1a37-4ba2-bfe1-971b939aa50a)

This is useful if you, say, want to add some extensions to
https://zed.dev/docs/configuring-zed#auto-install-extensions, where you
need the IDs.

Release Notes:

- Added "Copy Extension ID" action to extension card dropdown

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-12-02 11:19:42 -05:00
Piotr Osiewicz
89e46396f6 workspace: Serialize active panel even if it's not visible (#21408)
Fixes #21285

Closes #21285

Release Notes:

- Fixed workspace serialization of collapsed panels
2024-12-02 17:08:16 +01:00
Finn Evers
3987d0d731 Treat .pcss files as CSS (#21402)
This addresses
https://github.com/zed-industries/zed/pull/19416#discussion_r1865019293
and also follows the [associated PostCSS file extensions for VS
Code](5d003170c5/package.json (L37)).

Release Notes:

- `.pcss` files are now recognized as CSS

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-12-02 10:56:47 -05:00
loczek
6cb758a1cd theme_importer: Add more mappings (#21393)
This PR adds `search_match_background` and `editor_document_highlight_bracket_background` color mappings as they appear to be missing.
2024-12-02 09:37:41 -05:00
Delyan Angelov
0cb3a6ed0e Add V file icon (#20017)
Here is a preview of the new `v.svg` in comparison with some of the
existing icons:

![image](https://github.com/user-attachments/assets/451762ff-b13a-42b9-89ac-695f25a43a84)

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2024-12-02 10:51:28 -03:00
Danilo Leal
2300f40cd9 Add consistent placeholder text for terminal inline assist (#21398)
Ensuring it is consistent with the buffer inline assistant. Just thought
of not having "Transform" here as that felt it made less sense for
terminal-related prompts, where arguably more frequently, one would be
suggesting for actual commands rather than code transformation.

<img width="700" alt="Screenshot 2024-12-02 at 09 11 00"
src="https://github.com/user-attachments/assets/ad96d03e-0366-46e8-8056-581066712d59">

Release Notes:

- N/A
2024-12-02 09:28:46 -03:00
Danilo Leal
dacd919e27 Add setting for making the tab's close button always visible (#21352)
Closes https://github.com/zed-industries/zed/issues/20422

<img width="700" alt="Screenshot 2024-11-29 at 22 00 20"
src="https://github.com/user-attachments/assets/4a17d00c-d64f-4b33-97a7-a57766ce6d17">

Release Notes:

- N/A
2024-12-02 07:48:10 -03:00
Danilo Leal
740ba7817b Fine-tune terminal tab bar actions spacing (#21391)
Just quickly reducing the spacing between the terminal tab bar actions
so they're tighter and matching other similar components.

| Before | After |
|--------|--------|
| <img width="800" alt="Screenshot 2024-12-01 at 19 20 50"
src="https://github.com/user-attachments/assets/938336df-9ce1-42d3-8f3d-9c26b8e88453">
| <img width="800" alt="Screenshot 2024-12-01 at 19 18 19"
src="https://github.com/user-attachments/assets/0a2b5915-f37c-4b8e-af2c-b8018c4750ab">
|

Release Notes:

- N/A
2024-12-02 07:47:57 -03:00
fred-sch
380679fcc2 Fix: Copilot Chat is logged out (#21360)
Closes #21255

Release Notes:

- Fixed Copilot Chat OAuth Token parsing

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-12-02 10:35:29 +01:00
moskirathe
89a56968f6 Fix typos in key-bindings documentation (#21390)
Release Notes:

Fixes two minor typos in the key-bindings documentation.
2024-12-01 19:02:12 -03:00
Kirill Bulatov
5f6b200d8d Do not change selections when opening FS entries (#21382)
Follow-up of https://github.com/zed-industries/zed/pull/21375

When changing selections for FS entries, outline panel will be forced to
change item to the first excerpt which is not what we want.

Release Notes:

- N/A
2024-12-01 14:28:48 +02:00
yoleuh
4d5415273e Docs: Update developing zed docs to match (#21379)
Some changes just so the build docs for the different os matches each
other :)

macos:
- moved `rust wasm toolchain install` up under `rust install` (match
windows docs)
- add instructions to update rust if already installed (match windows
and linux docs)

windows:
- add `(required by a dependency)` to cmake install (match macos docs)

Release Notes:

- N/A
2024-12-01 10:59:29 +02:00
Kirill Bulatov
bf569d720e Always change editor selection when navigating outline panel entries (#21375)
Also scroll to the center when doing so.

This way, related editor's breadcrumbs always update, bringing more
information.

Release Notes:

- Adjust outline panel item opening behavior to always change the editor
selection, and center it
2024-12-01 01:49:41 +02:00
Kirill Bulatov
28849dd2a8 Fix item closing overly triggering save dialogues (#21374)
Closes https://github.com/zed-industries/zed/issues/12029

Allows to introspect project items inside items more deeply, checking
them for being dirty.
For that:
* renames `project::Item` into `project::ProjectItem`
* adds an `is_dirty(&self) -> bool` method to the renamed trait
* changes the closing logic to only care about dirty project items when
checking for save prompts conditions
* save prompts are raised only if the item is singleton without a
project path; or if the item has dirty project items that are not open
elsewhere

Release Notes:

- Fixed item closing overly triggering save dialogues
2024-12-01 01:48:31 +02:00
Agustin Gomes
c2cd84a749 Add musl-gcc as dependency (#21366)
This addition comes after attempting building Zed from source.

As part of the process, one of the components (a crate I presume) called
`ring` failed to compile due to the following sequence of console
messages:

```log
warning: ring@0.17.8: Compiler family detection failed due to error: ToolNotFound: Failed to find tool. Is `musl-gcc` installed?
warning: ring@0.17.8: Compiler family detection failed due to error: ToolNotFound: Failed to find tool. Is `musl-gcc` installed?

error: failed to run custom build command for `ring v0.17.8`
```

Adding this library should help fix the issue on Fedora 41 at least, and
possibly will help fixing it for other RedHat based distributions as
well.

Closes #ISSUE

Release Notes:

- Add musl-gcc as dependency

Signed-off-by: Agustin Gomes <me@agustingomes.com>
2024-11-30 13:20:31 -08:00
tims
d609931e1c linux: Fix mouse cursor size and blur on Wayland (#21373)
Closes #15788, #13258

This is a long-standing issue with a few previous attempts to fix it,
such as [this one](https://github.com/zed-industries/zed/pull/17496).
However, that fix was later reverted because it resolved the blur issue
but caused a size issue. Currently, both blur and size issues persist
when you set a custom cursor size from GNOME Settings and use fractional
scaling.

This PR addresses both issues.

---

### Context

A new Wayland protocol,
[cursor-shape-v1](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/194),
allows the compositor to handle rendering the cursor at the correct size
and shape. This protocol is implemented by KDE, wlroots (Sway-like
environments), etc. Zed supports this protocol, so there are no issues
on these desktop environments.

However, GNOME has not yet
[adopted](https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6212) this
protocol. As a result, apps must fall back to manually rendering the
cursor by specifying the theme, size, scale, etc., themselves. Zed also
implements this fallback but does not correctly account for the display
scale.

---

### Scale Fix

For example, if your cursor size is `64px` and you’re using fractional
scaling (e.g., `150%`), the display scale reported by the window query
will be an integer value, `2` in this case. Why `2` if the scale is
`150%`? That’s what the new protocol aims to improve. However, since
GNOME Wayland uses this integer scale everywhere, it’s sufficient for
our use case.

To fix the issue, we set the `buffer_scale` to this value. But that
alone doesn’t solve the problem. We also need to generate a matching
theme cursor size for this scaled version. This can be calculated as
`64px` * `2`, resulting in `128px` as the theme cursor size.

---

### Size Fix

The XDG Desktop Portal’s `cursor-size` event fails to read the cursor
size because it expects an `i32` but encounters a type error with `u32`.
Due to this, the cursor size was interpreted as the default `24px`
instead of the actual size set via user.

---

### Tested

This fix has been tested with all possible combinations of the
following:

- [x] GNOME Normal Scale (100%, 200%, etc.)
- [x] GNOME Fractional Scaling (125%, 150%, etc.)
- [x] GNOME Cursor Sizes (**Settings > Accessibility > Seeing**, e.g.,
`24px`, `64px`, etc.)
- [x] GNOME Experimental Feature `scale-monitor-framebuffer` (both
enabled and disabled)
- [x] KDE (`cursor-shape-v1` protocol)

---

**Result:**

64px custom cursor size + 150% Fractional Scale:


https://github.com/user-attachments/assets/cf3b1a0f-9a25-45d0-ab03-75059d3305e7

---

Release Notes:

- Fixed mouse cursor size and blur issues on Wayland
2024-11-30 13:19:44 -08:00
Remco Smits
fd71801346 Improve JavaScript runnable detection followup (#21363)
Followup: https://github.com/zed-industries/zed/pull/21246

**Before**
<img width="545" alt="Screenshot 2024-11-30 at 13 27 15"
src="https://github.com/user-attachments/assets/3346e485-96c8-482d-b5fd-85b86f37d662">
**After**
<img width="537" alt="Screenshot 2024-11-30 at 13 27 36"
src="https://github.com/user-attachments/assets/3cedcaa5-e285-47fb-909d-16d37d9844ca">

We did not need to add the `*` as it was already matching one of them,
we actually need at least one of them, so making it optional was a
mistake.

Don't think we need to add release notes, as the change is only on main
the branch now.

Release Notes:

- N/A
2024-11-30 13:55:14 +01:00
Haru Kim
c1de606581 Fix the autoscroll_on_clicks setting working incorrectly (#21362) 2024-11-30 14:30:27 +02:00
Kirill Bulatov
57a45d80ad Add a keybinding to the Go to Line button (#21350)
Release Notes:

- N/A
2024-11-30 00:50:38 +02:00
tims
5f29f214c3 linux: Fix file not opening from file explorer (#21137)
Closes #20070

Release Notes:

- Fixed issue where files wouldn't open from the file explorer.
- Fixed "Open a new workspace" option on the desktop entry right-click
menu.

Context:

Zed consists of two binaries:

- `zed` (CLI component, located at `crates/cli/main.rs`)
- `zed-editor` (GUI component, located at `crates/zed/main.rs`)

When `zed` is used in the terminal, it checks if an existing instance is
running. If one is found, it sends data via a socket to open the
specified file. Otherwise, it launches a new instance of `zed-editor`.
For more details, see the `detect` and `boot_background` functions in
`crates/cli/main.rs`.

Root Cause:

Install process creates directories like `.local/zed.app` and
`.local/zed-preview.app`, which contain desktop entries for the
corresponding release. For example, `.local/zed.app/share/applications`
contains `zed.desktop`.

This desktop entry includes a generic `Exec` field, which is correct by
default:

```sh
Comment=A high-performance, multiplayer code editor.
TryExec=zed
StartupNotify=true
```

The issue is in the `install.sh` script. This script copies the above
desktop file to the common directory for desktop entries
(.local/share/applications). During this process, it replaces the
`TryExec` value from `zed` with the exact binary path to avoid relying
on the shell's PATH resolution and to make it explicit.

However, replacement incorrectly uses the path for `zed-editor` instead
of the `zed` CLI binary. This results in not opening a file as if you
use `zed-editor` directly to do this it will throw `zed is already
running` error on production and open new instance on dev.


Note: This PR solves it for new users. For existing users, they will
either have to update `.desktop` file manually, or use `install.sh`
script again. I'm not aware of zed auto-update method, if it runs
`install.sh` under the hood.
2024-11-29 23:01:29 +02:00
tims
4bf59393ec linux: Fix Zed not visible in "Open With" list in file manager for Flatpak (#21177)
- Closes #19030

When `%U` is used in desktop entries, file managers pick this and use
it:

- When you right-click a file and choose "Open with..."
- When you drag and drop files onto an application icon

<img
src="https://github.com/user-attachments/assets/ea5aa008-b81c-4f10-9302-b82332f6b174"
width="200px" alt="image">

Adding it to CLI args, changes Flatpak desktop entry `Exec` from:

```diff
- Exec=/usr/bin/flatpak run --branch=master --arch=x86_64 --command=zed dev.zed.ZedDev --foreground
+ Exec=/usr/bin/flatpak run --branch=master --arch=x86_64 --command=zed --file-forwarding dev.zed.ZedDev --foreground @@u %U @@
```

This is Flatpak's way of doing `%U`, by adding `--file-forwarding` and
wrapping arg with `@@u` and `@@`.
Read more below
([source](https://docs.flatpak.org/en/latest/flatpak-command-reference.html)):

> --file-forwarding
>
> If this option is specified, the remaining arguments are scanned, and
all arguments that are enclosed between a pair of '@@' arguments are
interpreted as file paths, exported in the document store, and passed to
the command in the form of the resulting document path. Arguments
between "@@u" and "@@" are considered URIs, and any "file:" URIs are
exported. The exports are non-persistent and with read and write
permissions for the application.

Release Notes:

- Fixed Zed not visible in the "Open with" list in the file manager for
Flatpak.
2024-11-29 22:59:04 +02:00
moshyfawn
aea6fa0c09 Remove project panel trash action for remote projects (#21300)
Closes #20845

I'm uncertain about my placement for the logic to remove actions from
the command palette list. If anyone has insights or alternative
approaches, I'm open to changing the code.

Release Notes:

- Removed project panel `Trash` action for remote projects.

---------

Co-authored-by: Finn Evers <dev@bahn.sh>
2024-11-29 22:37:24 +02:00
Danilo Leal
4137d1adb9 Make project search landing page scrollable if too small (#21338)
Address
https://github.com/zed-industries/zed/issues/21317#issuecomment-2508011556


https://github.com/user-attachments/assets/089844fc-a485-44a6-8e8b-d294f28e9ea2

Release Notes:

- N/A
2024-11-29 12:45:08 -03:00
Danilo Leal
1903a29cca Expose "Column Git Blame" in the editor controls menu (#21336)
Closes https://github.com/zed-industries/zed/issues/10196

I think having this action exposed in the editor controls menu, close to
the inline Git Blame option, makes more sense than a more prominent item
somewhere else in the app. Maybe having it there will increase its
discoverability. I myself didn't know this until a few weeks ago! Next
steps would be ensuring the menu exposes its keybindings.

(Quick note about the menu item name: I think maybe "_Git Blame Column_"
would make more sense and feel grammatically more correct, but then we
would have two Git Blame-related options, one with "Git Blame" at the
start (Inline...) and another with "Git Blame" at the end (... Column).
I guess one had to be sacrificed for the sake of consistency 😅.)

<img width="750" alt="Screenshot 2024-11-29 at 12 01 33"
src="https://github.com/user-attachments/assets/2f3324ec-a2f0-4303-9582-714d0ee6bd31">

Release Notes:

- N/A
2024-11-29 12:38:12 -03:00
Danilo Leal
69c761f5a5 Adjust project search landing page layout (#21332)
Closes https://github.com/zed-industries/zed/issues/21317


https://github.com/user-attachments/assets/a4970c08-9715-4c90-ad48-8f6e80c6fcd0

Release Notes:

- N/A
2024-11-29 11:39:02 -03:00
Kirill Bulatov
0306bdc695 Use a single action for toggling the language (#21331)
Follow-up of https://github.com/zed-industries/zed/pull/21299

Release Notes:

- N/A
2024-11-29 16:02:57 +02:00
yoleuh
de55bd8307 Status bar: Reduce right tools lateral margin (#21329)
Closes #21316

| Before | After |
|--------|-------|
|
![image](https://github.com/user-attachments/assets/525d16b0-c1f0-4d93-9a8e-19112b927e78)|
![image](https://github.com/user-attachments/assets/c6947c3e-6b46-4498-a672-5f418f5faad0)|

Changes:
changed `Base08` to `Base04` in `render_right_tools`

Release Notes:

- N/A
2024-11-29 10:56:32 -03:00
Kirill Bulatov
a593a04da4 Update the lockfile after a recent dependency update (#21328)
Follow-up of https://github.com/zed-industries/zed/pull/21288

Release Notes:

- N/A
2024-11-29 15:39:18 +02:00
наб
74f265e5cf Update to embed-resource 3.0 (fixes build below windows \?\ path) (#21288)
Accd'g to
https://github.com/zed-industries/zed/pull/9009#issuecomment-1983599232
the manifest is required

Followup for
https://github.com/nabijaczleweli/rust-embed-resource/issues/71

Release Notes:
- N/A
2024-11-29 14:43:40 +02:00
Haru Kim
f9d5de834a Disable editor autoscroll on mouse clicks (#20287)
Closes #18148

Release Notes:

- Stop scrolling when clicking to the edges of the visible text area.
Use `autoscroll_on_clicks` to configure this behavior.


https://github.com/user-attachments/assets/3afd5cbb-5957-4e39-94c6-cd2e927038fd

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-11-29 13:51:36 +02:00
Haru Kim
eadb107339 Allow workspace::ActivatePaneInDirection to navigate out of the terminal panel (#21313)
Enhancement for #21238

Release Notes:

- N/A
2024-11-29 13:04:58 +02:00
Stanislav Alekseev
94faf9dd56 nix: Return to building with crane (#21292)
This removes .envrc, putting it into gitignore as well as building with
crane, as it does not require an up to date hash for a FOD.

Release Notes:

- N/A

cc @mrnugget @jaredramirez
2024-11-29 10:09:33 +01:00
Kirill Bulatov
73f546ea5f Force ashpd crate to not use tokio (#21315)
https://github.com/zed-industries/zed/issues/21304

Fixes a regression after
https://github.com/zed-industries/zed/pull/20939

Release Notes:

- N/A
2024-11-29 11:02:56 +02:00
Danilo Leal
eb2c0b33df Fine-tune status bar left-side spacing (#21306)
Closes https://github.com/zed-industries/zed/issues/21291

This PR also adds a small divider separating the panel-opening controls
from the other items that appear on the left side of the status bar. The
spacing was a bit bigger before because all three items on the left open
panels, whereas each other item does different things (e.g., open the
diagnostics tab, update the app, display language server status, etc.).
Therefore, they needed to be separated somehow to communicate the
difference in behavior. Hopefully, now, the border will help sort of
figuring this out.

| With error | Normal state |
|--------|--------|
| <img width="1179" alt="Screenshot 2024-11-28 at 18 52 58"
src="https://github.com/user-attachments/assets/bf4bad19-5588-481a-9d08-91b2227e44e6">
| <img width="1234" alt="Screenshot 2024-11-28 at 18 53 03"
src="https://github.com/user-attachments/assets/4443a16a-9982-44ce-9005-64d4df46f4f0">
|

Release Notes:

- N/A
2024-11-28 19:15:30 -03:00
Danilo Leal
3458687300 Add keybinding to the language selector tooltip (#21299)
Just making sure sure we're always making keyboard navigation
discoverable.

<img width="700" alt="Screenshot 2024-11-28 at 16 05 40"
src="https://github.com/user-attachments/assets/bd7611f0-190c-4e3b-ad69-9552060e37ea">

Release Notes:

- N/A
2024-11-28 18:28:20 -03:00
Danilo Leal
e76589107d Improve the "go to line" modal (#21301)
Just a small, mostly visual refinement to this component.

<img width="700" alt="Screenshot 2024-11-28 at 16 30 27"
src="https://github.com/user-attachments/assets/d5bbed63-202c-4cd3-b4b0-b7ed23466309">

Release Notes:

- N/A
2024-11-28 18:28:05 -03:00
Danilo Leal
ae85ecba2d Make fetch slash command visible in the command selector (#21302)
The `/fetch` command is naturally already accessible via the completion
menu when you type / in the assistant panel, but it wasn't on the "Add
Context" command selector. I think it should! It's a super nice/powerful
one, and I've seen folks not knowing it existed. Side-note: maybe, in
the near future, it'd be best to rename it to "`/web`, as that's an
easier name to parse and assume what it does.

<img width="700" alt="Screenshot 2024-11-28 at 16 52 07"
src="https://github.com/user-attachments/assets/37134e1c-c788-48ca-81ae-d7416e8c8706">

Release Notes:

- N/A
2024-11-28 18:26:59 -03:00
Kirill Bulatov
0acd98a07e Do not show cursor position for empty files (#21295)
Closes https://github.com/zed-industries/zed/issues/21289

Fixes most of the issues: does not display cursor position in empty
multi buffers and on non-full editors.

Does not fix the startup issue, as it's caused by the AssistantPanel's
`ContextEditor` acting as an `Editor`, so whenever default prompts are
added, those are registered as added editors, and Zed shows some line
numbers for them.

We cannot replace `item.act_as::<Editor>(cx)` with
`item.downcast::<Editor>()` as then multi bufers' navigation will fall
off (arguably, those line numbers do not make that much sense too, but
still seem useful).
This will will fix itself in the future, when assistant panel gets
reworked into readonly view by default, as `assistant2` crate already
shows (there's no `act_as` impl there and nothing cause issue).

Since the remaining issue is minor and will go away on any focus change,
and future changes will alter this, closing the original issue.

Release Notes:

- Improved cursor position display
2024-11-28 20:42:57 +02:00
Matin Aniss
4a96db026c gpui: Implement hover for Windows (#20894) 2024-11-28 18:45:10 +02:00
Danilo Leal
301a8900a5 Add consistency between buffer and project search design (#20754)
Follow up to https://github.com/zed-industries/zed/pull/20242

This PR adds the `SearchInputWidth` util, which sets a threshold
container size in which an input's width stops filling the available
space. In practice, this is in place to make the buffer and project
search input fill the whole container width up to a certain point (where
this point is really an arbitrary number that can be fine-tuned per
taste). For folks using huge monitors, the UX isn't excellent if you
have a gigantic input.

In the future, upon further review, maybe it makes more sense to
reorganize this code better, baking it in as a default behavior of the
input component. Or even exposing this is a function many other
components could use, given we may want to have dynamic width in
different scenarios.

For now, I just wanted to make the design of these search UIs better and
more consistent.

| Buffer Search | Project Search |
|--------|--------|
| <img width="1042" alt="Screenshot 2024-11-15 at 20 39 21"
src="https://github.com/user-attachments/assets/f9cbf0b3-8c58-46d1-8380-e89cd9c89699">
| <img width="1042" alt="Screenshot 2024-11-15 at 20 39 24"
src="https://github.com/user-attachments/assets/ed244a51-ea55-4fe3-a719-a3d9cd119aa9">
|

Release Notes:

- N/A
2024-11-28 13:39:49 -03:00
Kirill Bulatov
f30944543e Do less resolves when showing the completion menu (#21286)
Closes https://github.com/zed-industries/zed/issues/21205

Zed does completion resolve on every menu item selection and when
applying the edit, so resolving all completion menu list is excessive
indeed.

In addition to that, removes the documentation-centric approach of menu
resolves, as we're actually resolving these for more than that, e.g.
additionalTextEdits and have to do that always, even if we do not show
the documentation.

Potentially, we can omit the second resolve too, but that seems
relatively dangerous, and many servers remove the `data` after the first
resolve, so a 2nd one is not that harmful given that we used to do much
more

Release Notes:

- Reduced the amount of `completionItem/resolve` calls done in the
completion menu
2024-11-28 18:16:37 +02:00
Gowtham K
6cba467a4e project-panel: Fix playback GIF images (#21274) 2024-11-28 03:20:10 -08:00
CharlesChen0823
cacec06db6 search: Treat non-word char as whole-char when searching (#19152)
when search somethings like `clone(`, with search options `match case
sensitively` and `match whole words` in zed code base, only `clone(cx)`
hit match, `clone()` will not hit math.

Release Notes:

- Improved buffer search for queries ending with non-letter characters
2024-11-28 11:06:48 +02:00
Zach Bruggeman
3ac119ac4e Fix hovered links underline not showing when using cmd_or_ctrl for multi_cursor_modifier (#20949)
I use `cmd_or_ctrl` for `multi_cursor_modifier`, but noticed that if I
hovered a code reference while holding alt, it wouldn't show the
underline. Instead, it would only show when pressing cmd. Looking at the
code, it seems like this was just a small oversight on always checking
for `modifiers.secondary`, instead of reading from the
`multi_cursor_modifier` setting to determine which button was invoking
link handling.


---

Release Notes:

- Fixed underline when hovering a code link not showing when
`multi_cursor_modifier` is `cmd_or_ctrl`
2024-11-28 11:00:45 +02:00
Jaagup Averin
b12a508ed9 python: Fix highlighting for forward references (#20766)
[PEP484](https://peps.python.org/pep-0484/) defines "Forward references"
for undefined types. This PR treats such annotations as types rather
than strings.
Release Notes:

- Added Python syntax highlighting for forward references.
2024-11-28 09:59:10 +01:00
renovate[bot]
1739de59d4 Update Rust crate proc-macro2 to v1.0.92 (#20967)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [proc-macro2](https://redirect.github.com/dtolnay/proc-macro2) |
dependencies | patch | `1.0.89` -> `1.0.92` |

---

### Release Notes

<details>
<summary>dtolnay/proc-macro2 (proc-macro2)</summary>

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

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

- Improve compiler/fallback mismatch panic message
([#&#8203;487](https://redirect.github.com/dtolnay/proc-macro2/issues/487))

###
[`v1.0.91`](https://redirect.github.com/dtolnay/proc-macro2/releases/tag/1.0.91)

[Compare
Source](https://redirect.github.com/dtolnay/proc-macro2/compare/1.0.90...1.0.91)

- Fix panic *"compiler/fallback mismatch 949"* when using
TokenStream::from_str from inside a proc macro to parse a string
containing doc comment
([#&#8203;484](https://redirect.github.com/dtolnay/proc-macro2/issues/484))

###
[`v1.0.90`](https://redirect.github.com/dtolnay/proc-macro2/releases/tag/1.0.90)

[Compare
Source](https://redirect.github.com/dtolnay/proc-macro2/compare/1.0.89...1.0.90)

- Improve error recovery in TokenStream's and Literal's FromStr
implementations to work around
[https://github.com/rust-lang/rust/issues/58736](https://redirect.github.com/rust-lang/rust/issues/58736)
such that rustc does not poison compilation on codepaths that should be
recoverable errors
([#&#8203;477](https://redirect.github.com/dtolnay/proc-macro2/issues/477),
[#&#8203;478](https://redirect.github.com/dtolnay/proc-macro2/issues/478),
[#&#8203;479](https://redirect.github.com/dtolnay/proc-macro2/issues/479),
[#&#8203;480](https://redirect.github.com/dtolnay/proc-macro2/issues/480),
[#&#8203;481](https://redirect.github.com/dtolnay/proc-macro2/issues/481),
[#&#8203;482](https://redirect.github.com/dtolnay/proc-macro2/issues/482))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:58:53 +02:00
renovate[bot]
4aa47a9063 Update Rust crate rodio to 0.20.0 (#20955)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v0.20.1`](https://redirect.github.com/RustAudio/rodio/blob/HEAD/CHANGELOG.md#Version-0201-2024-11-08)

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

##### Fixed

-   Builds without the `symphonia` feature did not compile

###
[`v0.20.0`](https://redirect.github.com/RustAudio/rodio/blob/HEAD/CHANGELOG.md#Version-0200-2024-11-08)

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

##### Added

-   Support for *ALAC/AIFF*
- Add `automatic_gain_control` source for dynamic audio level
adjustment.
-   New test signal generator sources:
- `SignalGenerator` source generates a sine, triangle, square wave or
sawtooth
        of a given frequency and sample rate.
    -   `Chirp` source generates a sine wave with a linearly-increasing
        frequency over a given frequency range and duration.
- `white` and `pink` generate white or pink noise, respectively. These
sources depend on the `rand` crate and are guarded with the "noise"
        feature.
- Documentation for the "noise" feature has been added to `lib.rs`.
-   New Fade and Crossfade sources:
    -   `fade_out` fades an input out using a linear gain fade.
- `linear_gain_ramp` applies a linear gain change to a sound over a
given duration. `fade_out` is implemented as a `linear_gain_ramp` and
        `fade_in` has been refactored to use the `linear_gain_ramp`
        implementation.

##### Fixed

- `Sink.try_seek` now updates `controls.position` before returning.
Calls to `Sink.get_pos`
    done immediately after a seek will now return the correct value.

##### Changed

-   `SamplesBuffer` is now `Clone`

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:58:36 +02:00
renovate[bot]
fe30a03921 Update Rust crate ipc-channel to 0.19 (#20951)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ipc-channel](https://redirect.github.com/servo/ipc-channel) |
dependencies | minor | `0.18` -> `0.19` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:58:10 +02:00
renovate[bot]
38900c2321 Update Rust crate bytemuck to v1.20.0 (#20947)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [bytemuck](https://redirect.github.com/Lokathor/bytemuck) |
dependencies | minor | `1.19.0` -> `1.20.0` |

---

### Release Notes

<details>
<summary>Lokathor/bytemuck (bytemuck)</summary>

###
[`v1.20.0`](https://redirect.github.com/Lokathor/bytemuck/compare/v1.19.0...v1.20.0)

[Compare
Source](https://redirect.github.com/Lokathor/bytemuck/compare/v1.19.0...v1.20.0)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:56:36 +02:00
renovate[bot]
6927512e34 Update Rust crate ashpd to 0.10.0 (#20939)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ashpd](https://redirect.github.com/bilelmoussaoui/ashpd) |
workspace.dependencies | minor | `0.9.1` -> `0.10.0` |

---

### Release Notes

<details>
<summary>bilelmoussaoui/ashpd (ashpd)</summary>

###
[`v0.10.2`](https://redirect.github.com/bilelmoussaoui/ashpd/releases/tag/0.10.2)

[Compare
Source](https://redirect.github.com/bilelmoussaoui/ashpd/compare/0.10.1...0.10.2)

-   Add `backend` feature to docs.rs

###
[`v0.10.1`](https://redirect.github.com/bilelmoussaoui/ashpd/releases/tag/0.10.1)

[Compare
Source](https://redirect.github.com/bilelmoussaoui/ashpd/compare/0.10.0...0.10.1)

#### What's Changed

- desktop/activation-token: Add helper for retriving the token from a
`gtk::Widget` or a `WlSurface`
-   desktop/secret: Close the socket after done reading
-   desktop/input-capture: Fix barrier-id type
-   desktop: Use a Pid alias all over the codebase
-   desktop/notification: Support v2 of the interface
- Introduce backend implementation support, allowing to write a portal
implementation in pure Rust. Currently, we don't support Session based
portals. The backend feature is considered experimental as we might
possibly introduce API breaking changes in the future but it should be
good enough for getting started. Examples of how a portal can be
implemented can be found in
[backend-demo](https://redirect.github.com/bilelmoussaoui/ashpd/tree/master/backend-demo)

**Note**: The 0.10.0 release has been yanked from crates.io as it
contained a build error when the `glib` feature is enabled.

###
[`v0.10.0`](https://redirect.github.com/bilelmoussaoui/ashpd/compare/0.9.2...0.10.0)

[Compare
Source](https://redirect.github.com/bilelmoussaoui/ashpd/compare/0.9.2...0.10.0)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:56:21 +02:00
renovate[bot]
4342a93d22 Update Rust crate tree-sitter-c to v0.23.2 (#20938)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

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

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:55:57 +02:00
renovate[bot]
28640ac076 Update astral-sh/setup-uv digest to caf0cab (#20927)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | digest | `2e657c1` -> `caf0cab` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 10:55:46 +02:00
feeiyu
c2c968f2de Enable clangd's dot-to-arrow feature (#21142)
Closes #20815


![dot2arrow1127](https://github.com/user-attachments/assets/d825f9bf-52ae-47ee-b3a3-5f952b6e8979)

Release Notes:
- Enabled clangd's dot-to-arrow feature
2024-11-28 10:43:25 +02:00
Stanislav Alekseev
a4584c9d13 Add an uninstall script (#21213)
Closes #14306

This looks at what #16660 did and install.sh script as a base for the
uninstall.sh script. The script is bundled with the cli by default
unless the cli/no-bundled-uninstall feature is selected which is done,
so package managers could build zed without bundling a useless feature
and increasing binary size.

I don't have capabilities to test this right now, so any help with that
is appreciated.

Release Notes:

- Added an uninstall script for Zed installations done via zed.dev. To
uninstall zed, run `zed --uninstall` via the CLI binary.
2024-11-28 10:31:12 +02:00
Jason Lee
e9e260776b gpui: Fix default colors blue, red, green to match in CSS default colors (#20851)
Release Notes:

- N/A

---

This change to let the default colors to 100% match with CSS default
colors.

And update the methods to as `const`.

Here is an example:

<img width="338" alt="image"
src="https://github.com/user-attachments/assets/dd17b46a-3ad4-4122-8dca-e800644c75b0">

https://codepen.io/huacnlee/pen/ZEgNXJZ

But the before version for example blue: `h: 0.6 * 360 = 216`, but we
expected `240`, `240 / 360 = 0.666666666`, so the before version are
lose the precision. (Here is a test tool: https://hslpicker.com/#0000FF)

## After Update

```bash
cargo run -p gpui --example hello_world
```

<img width="612" alt="image"
src="https://github.com/user-attachments/assets/97d479d8-9c71-4be3-95e0-09af45fe47e2">
2024-11-28 10:08:07 +02:00
Jared Ramirez
461ab24a06 Update nix cargo hash (#21257)
Closes https://github.com/zed-industries/zed/issues/21256

Release Notes:

- N/A
2024-11-28 08:04:11 +02:00
Remco Smits
04ff9f060c Improve runnable detection for JavaScript files (#21246)
Closes #21242

![Screenshot 2024-11-27 at 18 52
51](https://github.com/user-attachments/assets/d096197c-33d2-41b9-963d-3e1a9bbdc035)
![Screenshot 2024-11-27 at 18 53
08](https://github.com/user-attachments/assets/b3202b00-3f68-4d9d-acc2-1b86c081fc34)

Release Notes:

- Improved runnable detection for JavaScript/Typescript files.
2024-11-28 00:54:01 +01:00
Kirill Bulatov
66ba9d5b4b Use item context for pane tab context menu (#21254)
This allows to show proper override values for terminal tabs in Linux
and Windows.

Release Notes:

- Fixed incorrect "close tab" keybinding shown in context menu of the
terminal panel tabs on Linux and Windows
2024-11-28 00:30:33 +02:00
Kirill Bulatov
e803815b16 Use proper context to show terminal split menu bindings (#21253)
Follow-up of https://github.com/zed-industries/zed/pull/21251

Show proper keybindings on the terminal split button:

<img width="249" alt="image"
src="https://github.com/user-attachments/assets/b51b183f-788a-4e8f-9fec-3ec07f084bd4">

Release Notes:

- N/A
2024-11-28 00:06:23 +02:00
Kirill Bulatov
34ed48e14b Add a split button to terminal panes (#21251)
Follow-up of https://github.com/zed-industries/zed/pull/21238

<img width="873" alt="image"
src="https://github.com/user-attachments/assets/8cf2d8ea-a1df-4a6a-95d6-5867e0ee287d">

Release Notes:

- N/A
2024-11-27 23:17:44 +02:00
Mikayla Maki
0c8e5550e7 Make Markdown images layout vertically instead of horizontally (#21247)
Release Notes:

- Fixed a bug in the Markdown preview where images in the same paragraph
would be rendered next to each other
2024-11-27 10:47:23 -08:00
张小白
cff9ae0bbc Better absolute path handling (#19727)
Closes #19866

This PR supersedes #19228, as #19228 encountered too many merge
conflicts.

After some exploration, I found that for paths with the `\\?\` prefix,
we can safely remove it and consistently use the clean paths in all
cases. Previously, in #19228, I thought we would still need the `\\?\`
prefix for IO operations to handle long paths better. However, this
turns out to be unnecessary because Rust automatically manages this for
us when calling IO-related APIs. For details, refer to Rust's internal
function
[`get_long_path`](017ae1b21f/library/std/src/sys/path/windows.rs (L225-L233)).

Therefore, we can always store and use paths without the `\\?\` prefix.

This PR introduces a `SanitizedPath` structure, which represents a path
stripped of the `\\?\` prefix. To prevent untrimmed paths from being
mistakenly passed into `Worktree`, the type of `Worktree`’s `abs_path`
member variable has been changed to `SanitizedPath`.

Additionally, this PR reverts the changes of #15856 and #18726. After
testing, it appears that the issues those PRs addressed can be resolved
by this PR.

### Existing Issue
To keep the scope of modifications manageable, `Worktree::abs_path` has
retained its current signature as `fn abs_path(&self) -> Arc<Path>`,
rather than returning a `SanitizedPath`. Updating the method to return
`SanitizedPath`—which may better resolve path inconsistencies—would
likely introduce extensive changes similar to those in #19228.

Currently, the limitation is as follows:

```rust
let abs_path: &Arc<Path> = snapshot.abs_path();
let some_non_trimmed_path = Path::new("\\\\?\\C:\\Users\\user\\Desktop\\project"); 
// The caller performs some actions here:
some_non_trimmed_path.strip_prefix(abs_path);  // This fails
some_non_trimmed_path.starts_with(abs_path);   // This fails too
```

The final two lines will fail because `snapshot.abs_path()` returns a
clean path without the `\\?\` prefix. I have identified two relevant
instances that may face this issue:
-
[lsp_store.rs#L3578](0173479d18/crates/project/src/lsp_store.rs (L3578))
-
[worktree.rs#L4338](0173479d18/crates/worktree/src/worktree.rs (L4338))

Switching `Worktree::abs_path` to return `SanitizedPath` would resolve
these issues but would also lead to many code changes.

Any suggestions or feedback on this approach are very welcome.

cc @SomeoneToIgnore 

Release Notes:

- N/A
2024-11-27 20:22:58 +02:00
Kirill Bulatov
d0bafce86b Allow splitting the terminal panel (#21238)
Closes https://github.com/zed-industries/zed/issues/4351


![it_splits](https://github.com/user-attachments/assets/40de03c9-2173-4441-ba96-8e91537956e0)

Applies the same splitting mechanism, as Zed's central pane has, to the
terminal panel.
Similar navigation, splitting and (de)serialization capabilities are
supported.

Notable caveats:
* zooming keeps the terminal splits' ratio, rather expanding the
terminal pane
* on macOs, central panel is split with `cmd-k up/down/etc.` but `cmd-k`
is a "standard" terminal clearing keybinding on macOS, so terminal panel
splitting is done via `ctrl-k up/down/etc.`
* task terminals are "split" into regular terminals, and also not
persisted (same as currently in the terminal)

Seems ok for the initial version, we can revisit and polish things
later.

Release Notes:

- Added the ability to split the terminal panel
2024-11-27 20:22:39 +02:00
Stanislav Alekseev
4564da2875 Improve Nix package and shell (#21075)
With an addition of useFetchCargoVendor, crane becomes less necessary
for our use. This reuses the package from nixpkgs as well as creating a
better devshell that both work on macOS.

I use Xcode's SDKROOT and DEVELOPER_DIR to point the swift in the
livekit client crate to a correct sdk when using a devshell. Devshell
should work without that once apple releases sources for the 15.1 SDK
but for now this is an easy fix

This also replaces fenix with rust-overlay because of issues with the
out-of-sandbox access I've noticed fenix installed toolchains have

Release Notes:

- N/A
2024-11-27 20:22:17 +02:00
Peter Tripp
c021ee60d6 v0.165.x dev 2024-11-27 09:48:40 -05:00
Peter Tripp
6736806924 docs: Move install rustup callup to top of developing-extensions.md (#21239) 2024-11-27 09:25:43 -05:00
Kirill Bulatov
ce6782f4c8 Use eslint from the fork (#21233)
Part of https://github.com/zed-industries/zed/issues/21220

Microsoft had decided to switch over to a different releasing strategy,
autogenerating all releases and not publishing tarballs anymore.
But it was not enough, and they had also removed old tarballs, including
a relatively old `2.4.4` version release's tarballs, which broke Zed
downloads.
See https://github.com/microsoft/vscode-eslint/issues/1954

This PR uses
https://github.com/zed-industries/vscode-eslint/releases/tag/2.4.4 from
Zed's fork, manually released for the same tag.

This approach is merely a stub before more sustainable solution is
found, and I think we need to pivot into downloading *.vsix from
https://open-vsx.org/extension/dbaeumer/vscode-eslint but this is quite
a change so not done right now.

Release Notes:

- Fixed eslint 404 downloads
2024-11-27 12:02:39 +02:00
Peter Tripp
e865b6c524 Fix cmd-shift-e (reveal in project panel) to match vscode (#21228)
Release Notes:

- Fixed cmd-shift-e / ctrl-shift-e (`pane::RevealInProjectPanel` /
`project_panel::ToggleFocus`) to better my VSCode behavior
2024-11-26 19:56:51 -05:00
Conrad Irwin
4e720be41c Add ctrl-w _ and ctrl-w = (#21227)
Closes #ISSUE

Release Notes:

- vim: Add support for `ctrl-w _` and `ctrl-w =`
2024-11-26 16:45:38 -08:00
AidanV
f702575255 Add support for resizing panes using vim motions (#21038)
Closes #8628

Release Notes:

- Added support for resizing the current pane using vim keybinds with
the intention to follow the functionality of vim
  - "ctrl-w +" to make a pane taller 
  - "ctrl-w -" to make the pane shorter
  - "ctrl-w >" to make a pane wider
  - "ctrl-w <" to make the pane narrower
- Changed vim pre_count and post_count to globals to allow for other
crates to use the vim count. In this case, it allows for resizing by
more than one unit. For example, "10 ctrl-w -" will decrease the height
of the pane 10 times more than "ctrl-w -"
- This pr does **not** add keybinds for making all panes in an axis
equal size and does **not** add support for resizing docks. This is
mentioned because these could be implied by the original issue

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-11-26 16:24:29 -08:00
tims
d75d34576a Fix file missing or duplicated when copying multiple items in project panel + Fix marked files not being deselected after selecting a directory (#20859)
Closes #20858

This fix depends on the sanitization logic implemented in PR #20577.
Since that branch may undergo further changes, this branch will be
periodically rebased on it. Once #20577 is merged, the dependency will
no longer apply.

Release Notes:

- Fix missing or duplicated files when copying multiple items in the
project panel.
- Fix marked files not being deselected after selecting a directory on
primary click.
- Fix "copy path" and "copy path relative" with multiple items selected
in project panel.

**Problem**:

In this case, `dir1` is selected while `dir2`, `dir3`, and `dir1/file`
are marked. Using the `marked_entries` function results in only `dir1`,
which is incorrect.

<img height="120"
src="https://github.com/user-attachments/assets/d4d92cc5-c998-4948-9a58-25c4f54167f2"
/>

Currently, the `marked_entries` function is used in five actions, which
all produce incorrect results:

1. Delete (via the disjoint function)
2. Copy 
3. Cut
4. Copy Path
5. Copy Path Relative

**Solution**:

1. `marked_entries` function should not use "When currently selected
entry is not marked, it's treated as the only marked entry." logic.
There is no grand scheme behind this logic as confirmed by piotr
[here](https://github.com/zed-industries/zed/issues/17746#issuecomment-2464765963).
2. `copy` and `cut` actions should use the disjoint function to prevent
obivous failures.
3. `copy path` and `copy path relative` should keep using *fixed*
`marked_entries` as that is expected behavior for these actions.

---

1. Before/After: Partial Copy

Select `dir1` and `c.txt` (in that order, reverse order works!), and
copy it and paste in `dir2`. `c.txt` is not copied in `dir2`.

<img height="170"
src="https://github.com/user-attachments/assets/a09fcb40-f38f-46ef-b0b4-e44ec01dda18"
/>
<img height="170"
src="https://github.com/user-attachments/assets/bb87dbe5-8e2e-4ca4-a565-42be5755ec8a"
/>

---
2. Before/After: Duplicate Copy

Select `a.txt`, `dir1` and `c.txt` (in that order), and copy it and
paste in `dir2`. `a.txt` is duplicated in `dir2`.

<img height="170"
src="https://github.com/user-attachments/assets/6f999d22-3607-48d7-9ff6-2e27494002f8"
/>
<img height="170"
src="https://github.com/user-attachments/assets/b4b6ff7d-0df7-45ea-83e4-50a0acb18457"
/>

---
3. Before/After: Directory Selection

Simply primary click on any file, now primary click on any dir. That
previous file is still marked.

<img height="170"
src="https://github.com/user-attachments/assets/9f1948ce-7445-4377-9733-06490ed6a324"
/>
<img height="170"
src="https://github.com/user-attachments/assets/e78203bc-96ba-424b-b588-c038992a9f0e"
/>

---
4. Before/After: Copy Path and Copy Path Relative

Upon `copy path` (ctrl + alt + c):

Before: Only `/home/tims/test/dir2/a.txt` was copied.
After: All three paths `/home/tims/test/dir2`, `/home/tims/test/c.txt`
and `/home/tims/test/dir2/a.txt` are copied.

This is also how VSCode also copies path when multiple are selected.

<img height="170"
src="https://github.com/user-attachments/assets/e20423ea-1682-4efd-b208-631e2edd3771"
/>
2024-11-27 01:23:01 +02:00
Helge Mahrt
57e4540759 vim: Add "unmatched" motions ]}, ]), [{ and [( (#21098)
Closes #20791

Release Notes:

- Added vim ["unmatched"
motions](1d87e11a1e/runtime/doc/motion.txt (L1238-L1255))
`]}`, `])`, `[{` and `[(`

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-11-26 14:08:54 -08:00
vultix
597e5f8304 vim: Add indent text object (#21121)
Added support for the popular vim
[indent-text-object](https://github.com/michaeljsmith/vim-indent-object).
This is especially useful in indentation-sensitive languages like
python.

Release Notes:

- vim: Added `vii`, `vai` and `vaI` for selecting
[indent-text-object](https://github.com/michaeljsmith/vim-indent-object)s.
2024-11-26 12:54:36 -08:00
Max Brunsfeld
64708527e7 Revert "Styling for Apply/Discard buttons (#21017)"
This reverts commit 884748038e.
2024-11-26 10:19:13 -08:00
yoleuh
6dbe2ef10c docs: Fix default value for relative_line_numbers in vim (#21196)
![image](https://github.com/user-attachments/assets/91c00938-f056-4778-8999-6a805bc12247)

Changes:
`true` to `false`

Reasoning:
matches zed default settings as well as the settings changes portion of
the vim docs


![image](https://github.com/user-attachments/assets/cb3240bc-8c55-4802-88c0-dd069992ca30)


![image](https://github.com/user-attachments/assets/747fbe8a-b24c-45f2-b3ab-f09bccdb4ec3)

Release Notes:
- N/A
2024-11-26 18:10:28 +02:00
Richard Feldman
884748038e Styling for Apply/Discard buttons (#21017)
Change the "Apply" and "Discard" buttons to match @danilo-leal's design!
Here are some different states:

### Cursor in the first hunk

Now that the cursor is in a particular hunk, we show the "Apply" and
"Discard" names, and the keyboard shortcut. If I press the keyboard
shortcut, it will only apply to this hunk.

<img width="759" alt="Screenshot 2024-11-23 at 10 54 45 PM"
src="https://github.com/user-attachments/assets/68e0f109-9493-4ca2-a99c-dfcbb4d1ce0c">

### Cursor in the second hunk

Moving the cursor to a different hunk changes which buttons get the
keyboard shortcut treatment. Now the keyboard shortcut is shown next to
the hunk that will actually be affected if you press that shortcut.

<img width="749" alt="Screenshot 2024-11-23 at 10 56 27 PM"
src="https://github.com/user-attachments/assets/59c2ace3-6972-4a60-b806-f45e8c25eaae">


Release Notes:

- Restyled Apply/Discard buttons

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2024-11-26 11:09:43 -05:00
Marshall Bowers
8f1ec3d11b assistant2: Add a checkbox to control tool use (#21215)
This PR adds a checkbox to the `assistant2` message editor to control
whether tools should be used for a given message.

Release Notes:

- N/A
2024-11-26 10:48:48 -05:00
Peter Tripp
fdc17c57d7 macos: Keybind improvements for binds involving shift (#21207)
Fix cmd-pipe
Remove redudnant jetbrains/sublime keybinds (these exist as `cmd-{` and `cmd-}` under default vscode keymap) and were broken as part of the recent keybind changes.
Remove excess JSON whitespace from tests to make them more readable.
2024-11-26 07:58:45 -05:00
Kirill Bulatov
9999c31859 Avoid endless loop of the diagnostic updates (#21209)
Follow-up of https://github.com/zed-industries/zed/pull/21173

Rust-analyzer with `checkOnSave` enabled will push diagnostics for a
file after each diagnostics refresh (e.g. save, file open, file close).

If there's a file that is not open in any pane and has only warnings,
and the diagnostics editor has warnings toggled off, then

0. rust-analyzer will push the corresponding warnings after initial load
1. the diagnostics editor code registers
`project::Event::DiagnosticsUpdated` for the corresponding file path and
opens the corresponding buffer to read its associated diagnostics from
the snapshot
2. opening the buffer would send `textDocument/didOpen` which would
trigger rust-analyzer to push the same diagnostics
3. meanwhile, the diagnostics editor would filter out all diagnostics
for that buffer, dropping the open buffer and effectively closing it
4. closing the buffer will send `textDocument/didClose` which would
trigger rust-analyzer to push the same diagnostics again, as those are
`cargo check` ones, still present in the file
5. GOTO 1

Release Notes:

- Fixed diagnostics editor not scrolling properly under certain
conditions
2024-11-26 14:29:54 +02:00
Kirill Bulatov
7d67bb4cf6 Properly use lsp::CompletionList defaults (#21202)
- Closes https://github.com/zed-industries/zed/issues/21185

Release Notes:

- Fixed incorrect handling of the completion list defaults
2024-11-26 12:23:38 +02:00
Marshall Bowers
968ffaa3fd assistant2: Restructure storage of tool uses and results (#21194)
This PR restructures the storage of the tool uses and results in
`assistant2` so that they don't live on the individual messages.

It also introduces a `LanguageModelToolUseId` newtype for better type
safety.

Release Notes:

- N/A
2024-11-25 21:53:27 -05:00
Marshall Bowers
7e418cc8af assistant2: Style messages (#21191)
This PR styles the messages in `assistant2` so they don't look quite as
rough:

<img width="1138" alt="Screenshot 2024-11-25 at 8 36 32 PM"
src="https://github.com/user-attachments/assets/9cc423fa-feff-4c69-9d2b-d28970559547">

Release Notes:

- N/A
2024-11-25 20:49:03 -05:00
Marshall Bowers
f059b6a24b assistant2: Add support for using tools (#21190)
This PR adds rudimentary support for using tools to `assistant2`. There
are currently no visual affordances for tool use.

This is gated behind the `assistant-tool-use` feature flag.

<img width="1079" alt="Screenshot 2024-11-25 at 7 21 31 PM"
src="https://github.com/user-attachments/assets/64d6ca29-c592-4474-8e9d-c344f855bc63">

Release Notes:

- N/A
2024-11-25 19:44:34 -05:00
Marshall Bowers
3901d46101 Factor tool definitions out of assistant (#21189)
This PR factors the tool definitions out of the `assistant` crate so
that they can be shared between `assistant` and `assistant2`.

`ToolWorkingSet` now lives in `assistant_tool`. The tool definitions
themselves live in `assistant_tools`, with the exception of the
`ContextServerTool`, which has been moved to the `context_server` crate.

As part of this refactoring I needed to extract the
`ContextServerSettings` to a separate `context_server_settings` crate so
that the `extension_host`—which is referenced by the `remote_server`—can
name the `ContextServerSettings` type without pulling in some undesired
dependencies.

Release Notes:

- N/A
2024-11-25 18:26:34 -05:00
Marshall Bowers
321fd19763 assistant2: Wire up assistant2::NewThread action (#21187)
This PR wires up the `assistant2::NewThread` action so that you can
create new threads.

Release Notes:

- N/A
2024-11-25 17:24:25 -05:00
Marshall Bowers
cc5daa22bd assistant2: Improve tracking of pending completions (#21186)
This PR improves the tracking of pending completions in `assistant2`
such that we actually remove ones that have been completed.

Release Notes:

- N/A
2024-11-25 17:07:55 -05:00
Marshall Bowers
2b9250843c assistant2: Include previous messages in the thread in the completion request (#21184)
This PR makes it so previous messages in the thread are included when
constructing the completion request, instead of only sending up the most
recent user message.

Release Notes:

- N/A
2024-11-25 16:51:32 -05:00
Marshall Bowers
e7b0047562 assistant2: Remove unnecessary Pane (#21183)
This PR removes an unnecessary `Pane` that was copied over from
`assistant::AssistantPanel` to `assistant2::AssistantPanel`.

Release Notes:

- N/A
2024-11-25 16:28:38 -05:00
Marshall Bowers
9ee1aba80a assistant2: Stream in completion text (#21182)
This PR makes it so that the completion text streams into the message
list rather than being buffered until the end.

Release Notes:

- N/A
2024-11-25 16:13:27 -05:00
Max Brunsfeld
91a565f5fa Simplify BufferStore's local vs remote state (#21180)
This is a pure refactor, motivated by wanting to introduce to the
BufferStore new logic for opening staged and committed changes.

I found the `BufferStoreImpl` trait a little bit confusing, particularly
how the different implementors of the trait held a handle back to the
owning buffer store. I was able to reduce the amount of code
considerably (-78 lines) by using a two-variant enum instead, similar to
what we do for `LspStore`, `WorktreeStore` and `Worktree`.

Release Notes:

- N/A
2024-11-25 12:53:23 -08:00
Marshall Bowers
a02684b2f7 assistant2: Add rudimentary chat functionality (#21178)
This PR adds in rudimentary functionality for sending messages to the
LLM in `assistant2`.

<img width="1079" alt="Screenshot 2024-11-25 at 1 49 11 PM"
src="https://github.com/user-attachments/assets/5accb749-c034-4fb2-bf55-3ae5bc9529ad">

Release Notes:

- N/A
2024-11-25 14:08:40 -05:00
Kirill Bulatov
bd02b35ba9 Avoid excessive diagnostics refreshes (#21173)
Attempts to reduce the diagnostics flicker, when editing very
fundamental parts of the large code base in Rust.


https://github.com/user-attachments/assets/dc3f9c21-8c6e-48db-967b-040649fd00da

Release Notes:

- N/A
2024-11-25 19:21:30 +02:00
teapo
28142be5e9 Update Luau docs (#21174)
Formatter arguments & Tree-sitter grammar changed.

Release Notes:

- N/A
2024-11-25 12:11:23 -05:00
Marshall Bowers
389422cbf3 docs: Fix broken link to context servers docs (#21172)
This PR fixes a broken link to the context server docs.

Release Notes:

- N/A
2024-11-25 11:25:37 -05:00
Bennet Bo Fenner
93533ed235 Remove assistant hints (#21171)
This reverts #20824 and #20899. After adding them last week we came to
the conclusion that the hints are too distracting in everyday use, see
#21128 for more details.

Release Notes:

- N/A
2024-11-25 17:19:33 +01:00
Marshall Bowers
385c447bbe docs: Document context servers (#21170)
This PR adds documentation for context servers.

Release Notes:

- N/A
2024-11-25 11:05:14 -05:00
Kirill Bulatov
b83f104f6e Do not reuse render cache for nested items whose parents are re-rendered (#21165)
Fixes a bug with terminal splits panicking during writing a command in
the command input

Release Notes:

- N/A

Co-authored-by: Antonio Scandurra <antonio@zed.dev>
2024-11-25 15:58:45 +02:00
Nathan Sobo
08b214dfb9 Rename 'chat' to 'thread' in assistant2 (#21141)
Release Notes:

- N/A
2024-11-25 07:27:35 -05:00
Piotr Osiewicz
aa58cab766 Fix offline workspace deserialization with assistant2 (#21159)
Closes #21156 
/cc @maxdeviant 

Release Notes:

- N/A
2024-11-25 12:21:32 +01:00
Nathan Sobo
5b0fa6e585 Hide AI hints on line ends so we can discuss more (#21128)
@bennetbo @as-cii @mrnugget I'm really not liking the hints about AI on
every line. It feels too distracting to me and damaging to the user
experience. I'm wondering if we can hide them and work with design for
other ideas. Or at least talk it through.

Release Notes:

- N/A
2024-11-25 09:48:43 +01:00
Piotr Osiewicz
e85848a695 pylsp: Prefer version from user venv (#21069)
Closes #ISSUE

Release Notes:

- pylsp will now use version installed in user venv, if one is
available.
2024-11-25 00:54:47 +01:00
Carroll Wainwright
20bffaf93f python: Highlight docstrings for classes and modules (#20486)
Release Notes:

- Add `string.doc` python syntax highlighting to class and module-level
docstrings.

Previously, only docstrings inside python functions were labeled as
`string.doc`, but docstrings can exist at the class or module level too.
This adds the more specific string type for each of those.

*Before*:
<img width="288" alt="image"
src="https://github.com/user-attachments/assets/5a6c4c9d-709c-40e2-8316-31c95084a1a9">

*After*:
<img width="294" alt="image"
src="https://github.com/user-attachments/assets/8212bfa2-2288-4623-aa63-f748a2295ada">
2024-11-25 00:52:11 +01:00
Joseph T. Lyons
3dcb94c204 Correct more app event inconsistencies (#21129)
- Unify welcome page event type string structure
- Differentiate between metric telemetry event and diagnostic telemetry
event

Release Notes:

- N/A
2024-11-24 00:34:02 -05:00
Joseph T. Lyons
0395d1b037 Clean up app event transformations (#21115)
This needs scrutinized. Detailed breakdown of what events I kept and
threw out here:

https://zed.dev/channel/app-events-17178/notes

I also removed a few fake events and tossed out json properties that
were being inserted for things we don't have logic to track. See PR
review comments below.

I think the only bad data we have are that we were identifying all node,
pnpm, and yarn projects as 'node' in the `project_type` property, so a
few days of lost data there.

Release Notes:

- N/A
2024-11-23 23:11:45 -05:00
Marshall Bowers
628b96f297 assistant2: Sketch in chat editor (#21116)
This PR sketches in the chat editor for `assistant2`.

<img width="1136" alt="Screenshot 2024-11-23 at 1 56 23 PM"
src="https://github.com/user-attachments/assets/6e979995-c0cf-4a46-8694-fc4a8646336f">

Release Notes:

- N/A
2024-11-23 14:09:15 -05:00
Marshall Bowers
2a23db6e05 assistant2: Sketch in toolbar (#21114)
This PR sketches in the toolbar for `assistant2`.

<img width="1136" alt="Screenshot 2024-11-23 at 12 39 49 PM"
src="https://github.com/user-attachments/assets/ed56fc36-54c8-48d4-8446-6c1f182fcef2">

Release Notes:

- N/A
2024-11-23 12:46:11 -05:00
Marshall Bowers
3a0408953d Factor out language model selector into its own crate (#21113)
This PR factors the language model selector out into its own
`language_model_selector` crate so that it can be reused in
`assistant2`.

Also renamed it from `ModelSelector` to `LanguageModelSelector` to be a
bit more specific.

Release Notes:

- N/A
2024-11-23 12:11:31 -05:00
Marshall Bowers
9adc3b4e82 Break ground on assistant2 (#21109)
This PR breaks ground on a new `assistant2` crate.

In order to see this new version of the assistant, both of the following
must be true:
1. The `assistant2` feature flag is enabled for your user
   - It is **not** currently enabled for all staff.
2. You are running a development build of Zed

The intent here is to enable the folks working on `assistant2` to
incrementally land work onto `main` without breaking use of the current
Assistant for anyone.

<img width="1136" alt="Screenshot 2024-11-23 at 10 46 08 AM"
src="https://github.com/user-attachments/assets/5723a13f-5be1-4486-9460-ead7329ba78e">

Release Notes:

- N/A
2024-11-23 11:24:52 -05:00
Peter Tripp
f30de4852a docs: Proto Language is by extension not native (#21096)
Fixes docs to reflect that Protobuf support is via extension.
Comment out references ProtoLS formatter.
Need to test both protols and protobuf-language-server to ensure both work.
2024-11-23 01:17:39 -05:00
Kyle Kelley
2177e833d8 Upgrade jupyter websocket client (#21095)
Upgrade to changes from https://github.com/runtimed/runtimed/pull/158 

Release Notes:

- N/A
2024-11-22 22:11:20 -08:00
Peter Tripp
8a9c53524a docs: Add JSON Schema settings for json-language-server (#21084)
Add json-language server docs
Recognize `.vscode/*` files as JSONC by default
2024-11-23 00:38:00 -05:00
Joseph T. Lyons
43f0ea759b Remove non-existent call event types (#21093)
These are not real (from Clickhouse):

```
    ┌─operation────────────┬──────c─┐
 1. │ join channel         │ 136221 │
 2. │ open channel notes   │  95529 │
 3. │ hang up              │  66264 │
 4. │ disable microphone   │  34116 │
 5. │ enable microphone    │  25090 │
 6. │ enable screen share  │  20751 │
 7. │ invite               │  15827 │
 8. │ share project        │  14580 │
 9. │ accept incoming      │  13708 │
10. │ disable screen share │  10440 │
11. │ unshare project      │   9556 │
12. │ decline incoming     │    455 │
13. │ enable camera        │      6 │
14. │ disable camera       │      4 │
    └──────────────────────┴────────┘
```

Release Notes:

- N/A
2024-11-22 23:49:53 -05:00
Conrad Irwin
984bb192ba Send llm events to snowflake too (#21091)
Closes #ISSUE

Release Notes:

- N/A
2024-11-22 20:40:39 -07:00
Kyle Kelley
5766afe710 Pass through remote kernel's language on legacy selection (#21088)
When selecting an active kernel based on legacy usage, have remote
kernels defer to language within kernelspec.

Release Notes:

- N/A
2024-11-22 16:31:11 -08:00
Kirill Bulatov
9833756224 Fix file finder menu actions (#21087)
Closes https://github.com/zed-industries/zed/issues/21077

* BREAKING: rename `file_finder::OpenMenu` into
`file_finder::ToggleMenu`
* Display the keybinding for menu toggling when the menu is open
* Fix `enter` not working in the menu

Release Notes:

- Fixed enter not working and menu toggle binding not shown in the file
finder menu
2024-11-23 02:21:19 +02:00
Marshall Bowers
1cfcdfa7ac Overhaul extension registration (#21083)
This PR overhauls extension registration in order to make it more
modular.

The `extension` crate now contains an `ExtensionHostProxy` that can be
used to register various proxies that the extension host can use to
interact with the rest of the system.

There are now a number of different proxy traits representing the
various pieces of functionality that can be provided by an extension.
The respective crates that provide this functionality can implement
their corresponding proxy trait in order to register a proxy that the
extension host will use to register the bits of functionality provided
by the extension.

Release Notes:

- N/A
2024-11-22 19:02:32 -05:00
Michael Sloan
c9f2c2792c Improve error handling and resource cleanup in linux/x11/window.rs (#21079)
* Fixes registration of event handler for xinput-2 device changes,
revealed by this improvement.

* Pushes `.unwrap()` panic-ing outwards to callers.

* Includes a description of what the X11 call was doing when a failure
was encountered.

* Fixes a variety of places where the X11 reply wasn't being inspected
for failures.

* Destroys windows on failure during setup. New structure makes it
possible for the caller of `open_window` to carry on despite failures,
and so partially initialized window should be removed (though all calls
I looked at also panic currently).

Considered pushing this through `linux/x11/client.rs` too but figured
it'd be nice to minimize merge conflicts with #20853.

Release Notes:

- N/A
2024-11-22 16:03:46 -07:00
Mikayla Maki
8240a52a39 Prevent panels from being resized past the edge of the workspace (#20637)
Closes #20593

Release Notes:

- Fixed a bug where it is possible to get in near-unrecoverable panel
state by resizing the panel past the edge of the workspace.

Co-authored-by: Trace <violet.white.batt@gmail.com>
2024-11-22 14:59:40 -08:00
teapo
c28f5b11f8 Allow overrides for json-language-server settings (#20748)
Closes #20739

The JSON LSP adapter now merges user settings with cached settings, and
util::merge_json_value_into pushes array contents from source to target.
2024-11-22 17:50:25 -05:00
Mikayla Maki
96854c68ea Markdown preview image rendering (#21082)
Closes https://github.com/zed-industries/zed/issues/13246

Supersedes: https://github.com/zed-industries/zed/pull/16192

I couldn't push to the git fork this user was using, so here's the exact
same PR but with some style nits implemented.


Release Notes:

- Added image rendering to the Markdown preview

---------

Co-authored-by: dovakin0007 <dovakin0007@gmail.com>
Co-authored-by: dovakin0007 <73059450+dovakin0007@users.noreply.github.com>
2024-11-22 14:49:26 -08:00
Peter Tripp
becc36380f Cleanup file_scan_inclusions in default.json (#21073) 2024-11-22 17:46:14 -05:00
Peter Tripp
1a0a8a9559 Fix picker new_path_prompt throwing "file exists" when saving (#21080)
Fix for getting File exists "os error 17" with `"use_system_path_prompts": false,`

This was reproducible when the first worktree is a non-folder worktree
(e.g. setting.json) so we were trying to create the new file with a path
under ~/.config/zed/settings.json/newfile.ext

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-22 17:45:03 -05:00
Peter Tripp
2fd210bc9a Fix stale Discord invite links (#21074) 2024-11-22 21:10:51 +00:00
Peter Tripp
23321be2ce docs: Improve Dart language docs (#21071) 2024-11-22 13:58:24 -05:00
Hugo Cardante
659b1c9dcf Add the option to hide both the task and command lines in the task output (#20920)
The goal is to be able to hide these lines from a task output:

```sh
⏵ Task `...` finished successfully
⏵ Command: ...
```

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-22 13:45:42 -05:00
Marshall Bowers
cb8028c092 Use Extension trait when registering extension context servers (#21070)
This PR updates the extension context server registration to go through
the `Extension` trait for interacting with extensions rather than going
through the `WasmHost` directly.

Release Notes:

- N/A
2024-11-22 13:21:30 -05:00
william341
ca76948044 gpui: Add drop_image (#19772)
This PR adds a function, WindowContext::drop_image, to manually remove a
RenderImage from the sprite atlas. In addition, PlatformAtlas::remove
was added to support this behavior. Previously, there was no way to
request a RenderImage to be removed from the sprite atlas, and since
they are not removed automatically the sprite would remain in video
memory once added until the window was closed. This PR allows a
developer to request the image be dropped from memory manually, however
it does not add automatic removal.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-11-22 09:51:26 -08:00
Conrad Irwin
852fb51528 Canonicalize paths when opening workspaces (#21039)
Closes #17161

Release Notes:

- Added symlink resolution when opening projects from within Zed.
Previously this only happened within zed's cli, but that broke file
watching on Linux when opening a symlinked directory.
2024-11-22 09:20:49 -07:00
Marshall Bowers
d489f96aef Don't name ExtensionLspAdapter in ExtensionRegistrationHooks (#21064)
This PR updates the `ExtensionRegistrationHooks` trait to not name the
`ExtensionLspAdapter` type.

This helps decouple the two.

Release Notes:

- N/A
2024-11-22 10:58:11 -05:00
Peter Tripp
b4659bb44e Fix inaccurate Ollama context length for qwen2.5 models (#20933)
Since Ollama/llama.cpp do not currently YARN for context length
extension, the context length is limited to `32768`. This can be
confirmed by the Ollama model card.
See corresponding issue on Ollama repo : 
https://github.com/ollama/ollama/issues/6865

Co-authored-by: Patrick Samson <1416027+patricksamson@users.noreply.github.com>
2024-11-22 10:10:01 -05:00
Techatrix
d5f2bca382 Filter LSP code actions based on the requested kinds (#20847)
I've observed that Zed's implementation of [Code Actions On
Format](https://zed.dev/docs/configuring-zed#code-actions-on-format)
uses the
[CodeActionContext.only](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext)
parameter to request specific code action kinds from the server. The
issue is that it does not filter out code actions from the response,
believing that the server will do it.

The [LSP
specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext)
says that the client is responsible for filtering out unwanted code
actions:

```js
/**
* Requested kind of actions to return.
*
* Actions not of this kind are filtered out by the client before being
* shown. So servers can omit computing them.
*/
only?: CodeActionKind[];
```

This PR will filter out unwanted code action on the client side.

I have initially encountered this issue because the [ZLS language
server](https://github.com/zigtools/zls) (until
https://github.com/zigtools/zls/pull/2087) does not filter code action
based on `CodeActionContext.only` so Zed runs all received code actions
even if they are explicitly disabled in the `code_actions_on_format`
setting.

Release Notes:

- Fix the `code_actions_on_format` setting when used with a language
server like ZLS

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-11-22 13:01:00 +01:00
tims
114c462143 Maintain selection on file/dir deletion in project panel (#20577)
Closes #20444

- Focus on next file/dir on deletion.
- Focus on prev file/dir in case where it's last item in worktree.
- Tested when multiple files/dirs are being deleted.

Release Notes:

- Maintain selection on file/dir deletion in project panel.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-11-22 12:59:04 +02:00
Joseph T. Lyons
933c11a9b2 Remove dead snowflake code (#21041)
Co-authored-by: Nathan Sobo <1789+nathansobo@users.noreply.github.com>

Release Notes:

- N/A
2024-11-21 23:06:03 -05:00
Michael Sloan
14ea4621ab Add fs::MTime newtype to encourage != instead of > (#20830)
See ["mtime comparison considered
harmful"](https://apenwarr.ca/log/20181113) for details of why
comparators other than equality/inequality should not be used with
mtime.

Release Notes:

- N/A
2024-11-21 19:21:18 -07:00
Piotr Osiewicz
477c6e6833 pylsp: Update mypy plugin name (#21031)
Follow-up to #21025

Release Notes:

- N/A
2024-11-22 01:13:48 +01:00
Marshall Bowers
6c470748ac zed: Remove unnecessary #[allow(non_snake_case)] attribute (#21030)
This PR removes the `#[allow(non_snake_case)]` attribute from the `zed`
crate, as it wasn't actually doing anything.

Release Notes:

- N/A
2024-11-21 18:47:44 -05:00
Marshall Bowers
e0245b3f30 Merge quick_action_bar into zed (#21026)
This PR merges the `quick_action_bar` crate into the `zed` crate.

We weren't really gaining anything by having it be a separate crate, and
it was introducing an additional step in the dependency graph that was
getting in the way.

It's only ~850 LOC, so the impact on the compilation speed of the `zed`
crate itself is negligible.

Release Notes:

- N/A
2024-11-21 18:33:11 -05:00
张小白
9211e699ee Follow-up on #18447: Unintentional deletion during merge-conflicts resolution (#20991)
After #18447 was merged, I reviewed the PR code as usual. During this
review, I realized that some code was unintentionally removed when I was
resolving merge conflicts in #18447.

Sorry!

Release Notes:

- N/A
2024-11-21 15:32:49 -08:00
Piotr Osiewicz
0663bf2a53 pylsp: Tweak default user settings (#21025)
I've also looked into not creating temp dirs in project directories and
succeeded at that for Mypy; no dice for rope though, I'll have to send a
patch to pylsp to fix that.
Closes #20646

Release Notes:

- Python: tweaked default pylsp settings to be less noisy (mypy and
pycodestyle are no longer enabled by default).
2024-11-22 00:25:30 +01:00
Marshall Bowers
9d95da56c3 welcome: Remove dependency on theme_selector (#21024)
This PR removes the dependency on `theme_selector` from `welcome`, as we
can just dispatch the action instead.

Release Notes:

- N/A
2024-11-21 17:50:22 -05:00
Piotr Osiewicz
5ee5a1a51e chore: Do not produce universal binaries for our releases (#21014)
Closes #ISSUE

Release Notes:

- We no longer provide universal binaries for our releases on macOS.
2024-11-21 23:16:49 +01:00
Kyle Kelley
72613b7668 Implement RunningKernel trait for native and remote kernels (#20934)
This PR introduces a unified interface for both native and remote
kernels through the `RunningKernel` trait. When either the native kernel
or the remote kernels are started, they return a `Box<dyn
RunningKernel>` to make it easier to work with the session. As a bonus
of this refactor, I've dropped some of the mpsc channels to instead opt
for passing messages directly to `session.route(message)`. There was a
lot of simplification of `Session` by moving responsibilities to
`NativeRunningKernel`.

No release notes yet until this is finalized.

* [x] Detect remote kernelspecs from configured remote servers
* [x] Launch kernel on demand

For now, this allows you to set env vars `JUPYTER_SERVER` and
`JUPYTER_TOKEN` to access a remote server. `JUPYTER_SERVER` should be a
base path like `http://localhost:8888` or
`https://notebooks.gesis.org/binder/jupyter/user/rubydata-binder-w6igpy4l/`

Release Notes:

- N/A
2024-11-21 14:00:19 -08:00
Michael Sloan
f74f670865 Fix panics from spawn_local tasks dropped on other threads in remote server (#21022)
Closes #21020

Release Notes:

- Fixed remote server panic of "local task dropped by a thread that
didn't spawn it"
2024-11-21 14:50:38 -07:00
Marshall Bowers
af34953bc3 extensions_ui: Remove dependency on theme_selector (#21023)
This PR removes the dependency on `theme_selector` from `extensions_ui`,
as we can just dispatch the action instead.

Release Notes:

- N/A
2024-11-21 16:48:25 -05:00
Marshall Bowers
b102a40e04 Extract VimModeSetting to its own crate (#21019)
This PR extracts the `VimModeSetting` out of the `vim` crate and into
its own `vim_mode_setting` crate.

A number of crates were depending on the entirety of the `vim` crate
just to reference `VimModeSetting`, which was not ideal.

Release Notes:

- N/A
2024-11-21 16:24:38 -05:00
Marshall Bowers
790fdcf737 collab_ui: Remove dependency on vcs_menu (#21016)
This PR removes the `vcs_menu` dependency from `collab_ui`.

We were only depending on this to call `vcs_menu::init`, which isn't
necessary to do here.

Release Notes:

- N/A
2024-11-21 15:48:35 -05:00
Marshall Bowers
2868b67286 title_bar: Remove dependency on feedback (#21013)
This PR removes the `title_bar` crate's dependency on the `feedback`
crate.

The `feedback::GiveFeedback` action now resides at
`zed_actions::feedback::GiveFeedback`.

`title_bar` now no longer depends on `editor` 🥳 

Release Notes:

- N/A
2024-11-21 15:24:04 -05:00
Peter Tripp
614b3b979b macos: Add default keybind for ctrl-home / ctrl-end (#21007)
This matches the default behavior on native macos apps.
ctrl-fn-left == ctrl-home == MoveToBeginning
ctrl-fn-right == ctrl-end == MoveToEnd
2024-11-21 15:03:50 -05:00
Marshall Bowers
4c7b48b35d title_bar: Remove dependency on vcs_menu (#21011)
This PR removes the `title_bar` crate's dependency on the `vcs_menu`.

The `vcs_menu::OpenRecent` action now resides at
`zed_actions::branches::OpenRecent`.

Release Notes:

- N/A
2024-11-21 14:56:02 -05:00
Marshall Bowers
6b2f1cc543 title_bar: Remove dependency on theme_selector (#21009)
This PR removes the `title_bar` crate's dependency on the
`theme_selector`.

The `theme_selector::Toggle` action now resides at
`zed_actions::theme_selector::Toggle`.

Release Notes:

- N/A
2024-11-21 14:33:58 -05:00
Marshall Bowers
f62ccf9c8a Extract auto_update_ui crate (#21008)
This PR extracts an `auto_update_ui` crate out of the `auto_update`
crate.

This allows `auto_update` to not depend on heavier crates like `editor`,
which in turn allows other downstream crates to start building sooner.

Release Notes:

- N/A
2024-11-21 14:11:57 -05:00
Conrad Irwin
841d3221b3 Auto release preview patch releases (#20886)
This should make the process of releasing patch releases to preview less
toilful

Release Notes:

- N/A
2024-11-21 11:59:02 -07:00
Conrad Irwin
02447a8552 Use our own git clone in draft release notes (#20956)
It turns out that messing with the git repo created by the github action
is
tricky, so we'll just clone our own.

On my machine, a shallow tree-less clone takes <500ms

Release Notes:

- N/A
2024-11-21 11:55:22 -07:00
Marshall Bowers
c16dfc1a39 title_bar: Remove dependency on command_palette (#21006)
This PR removes the `title_bar` crate's dependency on the
`command_palette`.

The `command_palette::Toggle` action now resides at
`zed_actions::command_palette::Toggle`.

Release Notes:

- N/A
2024-11-21 13:37:34 -05:00
Peter Tripp
268ac4c047 Implement readline/emacs/macos style ctrl-k cut and ctrl-y yank (#21003)
- Added support for ctrl-k / ctrl-y alternate cut/yank buffer on macos.

Co-authored-by: Conrad Irwin <conrad@zed.dev>
2024-11-21 13:10:25 -05:00
Nils Koch
571c7d4f66 Improve project_panel diagnostic icon knockout colors (#20760)
Closes #20572

Release Notes:

- N/A

cc @danilo-leal @WeetHet
2024-11-21 15:03:40 -03:00
Kirill Bulatov
5ff49db92f Only show breadcrumbs for terminals when there's a title (#20997)
Closes https://github.com/zed-industries/zed/issues/20475

Release Notes:

- Fixed terminal title and breadcrumbs behavior

---------

Co-authored-by: Thorsten Ball <thorsten@zed.dev>
2024-11-21 19:57:09 +02:00
Conrad Irwin
395e25be25 Fix keybindings on a Spanish ISO keyboard (#20995)
Co-Authored-By: Peter <peter@zed.dev>

Also reformatted the mappings to be easier to read/edit by hand.

Release Notes:

- Fixed keyboard shortcuts on Spanish ISO keyboards

---------

Co-authored-by: Peter <peter@zed.dev>
2024-11-21 10:18:54 -07:00
Conrad Irwin
74223c1b00 vim: Fix shortcuts that require shift+punct (#20990)
Fixes a bug I introduced in #20953

Release Notes:

- N/A
2024-11-21 09:05:00 -07:00
Piotr Osiewicz
0b373d43dc toolchains: Use language-specific terms in UI (#20985)
Closes #ISSUE

Release Notes:

- N/A
2024-11-21 15:57:22 +01:00
Piotr Osiewicz
75c545aa1e toolchains: Expose raw JSON representation of a toolchain (#20721)
Closes #ISSUE

Release Notes:

- N/A
2024-11-21 13:27:25 +01:00
Adam Richardson
6ab4b46984 rope: Minor optimization for tab indices (#20911)
This is a follow up on https://github.com/zed-industries/zed/pull/20289
and optimises the tabs by replacing branches with an XOR.

I saw this after watching the latest zed decoded episode so thank you
for those videos!

Release Notes:

- N/A
2024-11-21 08:48:13 +01:00
Conrad Irwin
ebaa270baf Clip UTF-16 offsets in text for range (#20968)
When launching the Pinyin keyboard, macOS will sometimes try to peek one
character back in the string.

This caused a panic if the preceding character was an emoji. The docs
say
"don't assume the range is valid", so now we don't.

Release Notes:

- (macOS) Fixed a panic when using the Pinyin keyboard with emojis
2024-11-20 22:04:26 -07:00
Conrad Irwin
7285cdb955 Drop platform lock when setting menu (#20962)
Turns out setting the menu (sometimes) calls `selected_range` on the
input
handler.

https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1732160078058279

Release Notes:

- Fixed a panic when reloading keymaps
2024-11-20 21:24:31 -07:00
Conrad Irwin
e062f30d9e Rename ime_key -> key_char and update behavior (#20953)
As part of the recent changes to keyboard support, ime_key is no longer
populated by the IME; but instead by the keyboard.

As part of #20877 I changed some code to assume that falling back to key
was
ok, but this was not ok; instead we need to populate this more similarly
to how
it was done before #20336.

The alternative fix could be to instead of simulating these events in
our own
code to push a fake native event back to the platform input handler.

Closes #ISSUE

Release Notes:

- Fixed a bug where tapping `shift` coudl type "shift" if you had a
binding on "shift shift"
2024-11-20 20:29:47 -07:00
Conrad Irwin
37a59d6b2e vim: Fix : on welcome screen (#20937)
Release Notes:

- vim: Fixed `:` on the welcome screen
2024-11-20 19:21:22 -07:00
Mikayla Maki
a03770837e Add extensions to the remote server (#20049)
TODO:

- [x] Double check strange PHP env detection
- [x] Clippy & etc.

Release Notes:

- Added support for extension languages on the remote server

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-11-20 19:21:09 -07:00
Ryan Hawkins
0e62b6dddd Add file_scan_inclusions setting to customize Zed file indexing (#16852)
Closes #4745

Release Notes:

- Added a new `file_scan_inclusions` setting to force Zed to index files
that match the provided globs, even if they're gitignored.

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-11-20 17:00:21 -08:00
张小白
95ace03706 windows: Set CREATE_NO_WINDOW for commands (#18447)
- Closes: #18371

Release Notes:

- N/A
2024-11-20 16:52:38 -08:00
张小白
49ed932c1f Fix line truncate crash on Windows (#17271)
Closes #17267

We should update the `len` of `runs` when truncating. cc @huacnlee 

Release Notes:

- N/A
2024-11-20 16:47:55 -08:00
renovate[bot]
33e84da657 Update Rust crate cargo_metadata to 0.19 (#20948)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [cargo_metadata](https://redirect.github.com/oli-obk/cargo_metadata) |
workspace.dependencies | minor | `0.18` -> `0.19` |

---

### Release Notes

<details>
<summary>oli-obk/cargo_metadata (cargo_metadata)</summary>

###
[`v0.19.0`](https://redirect.github.com/oli-obk/cargo_metadata/blob/HEAD/CHANGELOG.md#0190---2024-11-20)

[Compare
Source](https://redirect.github.com/oli-obk/cargo_metadata/compare/0.18.1...0.19.0)

##### Added

-   Re-exported `semver` crate directly.
-   Added implementation of `std::ops::Index<&PackageId>` for `Resolve`.
-   Added `pub fn is_kind(&self, name: TargetKind) -> bool` to `Target`.
- Added derived implementations of `PartialEq`, `Eq` and `Hash` for
`Metadata` and its members' types.
-   Added default fields to `PackageBuilder`.
- Added `pub fn new(name:version:id:path:) -> Self` to `PackageBuilder`
for providing all required fields upfront.

##### Changed

-   Bumped MSRV from `1.42.0` to `1.56.0`.
- Made `parse_stream` more versatile by accepting anything that
implements `Read`.
-   Converted `TargetKind` and `CrateType` to an enum representation.

##### Removed

- Removed re-exports for `BuildMetadata` and `Prerelease` from `semver`
crate.
- Removed `.is_lib(…)`, `.is_bin(…)`, `.is_example(…)`, `.is_test(…)`,
`.is_bench(…)`, `.is_custom_build(…)`, and `.is_proc_macro(…)` from
`Target` (in favor of adding `.is_kind(…)`).

##### Fixed

- Added missing `manifest_path` field to `Artifact`. Fixes
[#&#8203;187](https://redirect.github.com/oli-obk/cargo_metadata/issues/187).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 19:39:49 -05:00
Piotr Osiewicz
536d7e5355 chore: Sever terminal_view <-> tasks_ui dependency (#20946)
Closes #ISSUE

Release Notes:

- N/A
2024-11-21 01:07:14 +01:00
Marshall Bowers
cbba44900d Add language_models crate to house language model providers (#20945)
This PR adds a new `language_models` crate to house the various language
model providers.

By extracting the provider definitions out of `language_model`, we're
able to remove `language_model`'s dependency on `editor`, which improves
incremental compilation when changing `editor`.

Release Notes:

- N/A
2024-11-20 18:49:34 -05:00
Piotr Osiewicz
335b112abd title_bar: Remove dependency on recent_projects (#20942)
Use actions defined in zed_actions to interface with that crate instead.
One drawback of this is that we now hide call controls when any modal is
visible (we used to hide them just when ssh modal was deployed).

Release Notes:

- N/A
2024-11-21 00:43:03 +01:00
renovate[bot]
33bed8d680 Update Rust crate ctor to v0.2.9 (#20928)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ctor](https://redirect.github.com/mmastrac/rust-ctor) |
workspace.dependencies | patch | `0.2.8` -> `0.2.9` |

---

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:36:27 -07:00
renovate[bot]
6d4a5f9ad2 Update Rust crate libc to v0.2.164 (#20931)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v0.2.164`](https://redirect.github.com/rust-lang/libc/blob/HEAD/CHANGELOG.md#02164---2024-11-16)

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

##### MSRV

This release increases the MSRV of `libc` to 1.63.

##### Other

- CI: remove tests with rust < 1.63
[#&#8203;4051](https://redirect.github.com/rust-lang/libc/pull/4051)
- MSRV: document the MSRV of the stable channel to be 1.63
[#&#8203;4040](https://redirect.github.com/rust-lang/libc/pull/4040)
- MacOS: move ifconf to s_no_extra_traits
[#&#8203;4051](https://redirect.github.com/rust-lang/libc/pull/4051)

###
[`v0.2.163`](https://redirect.github.com/rust-lang/libc/blob/HEAD/CHANGELOG.md#02163---2024-11-16)

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

##### Added

- Aix: add more `dlopen` flags
[#&#8203;4044](https://redirect.github.com/rust-lang/libc/pull/4044)
- Android: add group calls
[#&#8203;3499](https://redirect.github.com/rust-lang/libc/pull/3499)
- FreeBSD: add `TCP_FUNCTION_BLK` and `TCP_FUNCTION_ALIAS`
[#&#8203;4047](https://redirect.github.com/rust-lang/libc/pull/4047)
- Linux: add `confstr`
[#&#8203;3612](https://redirect.github.com/rust-lang/libc/pull/3612)
- Solarish: add `aio`
[#&#8203;4033](https://redirect.github.com/rust-lang/libc/pull/4033)
- Solarish: add `arc4random*`
[#&#8203;3944](https://redirect.github.com/rust-lang/libc/pull/3944)

##### Changed

- Emscripten: upgrade emsdk to 3.1.68
[#&#8203;3962](https://redirect.github.com/rust-lang/libc/pull/3962)
- Hurd: use more standard types
[#&#8203;3733](https://redirect.github.com/rust-lang/libc/pull/3733)
- Hurd: use the standard `ssize_t = isize`
[#&#8203;4029](https://redirect.github.com/rust-lang/libc/pull/4029)
- Solaris: fix `confstr` and `ucontext_t`
[#&#8203;4035](https://redirect.github.com/rust-lang/libc/pull/4035)

##### Other

- CI: add Solaris
[#&#8203;4035](https://redirect.github.com/rust-lang/libc/pull/4035)
- CI: add `i686-unknown-freebsd`
[#&#8203;3997](https://redirect.github.com/rust-lang/libc/pull/3997)
- CI: ensure that calls to `sort` do not depend on locale
[#&#8203;4026](https://redirect.github.com/rust-lang/libc/pull/4026)
- Specify `rust-version` in `Cargo.toml`
[#&#8203;4041](https://redirect.github.com/rust-lang/libc/pull/4041)

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:35:08 -07:00
renovate[bot]
427c2017c3 Update Rust crate serde_json to v1.0.133 (#20932)
This PR contains the following updates:

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

---

### Release Notes

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

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

[Compare
Source](https://redirect.github.com/serde-rs/json/compare/v1.0.132...v1.0.133)

- Implement From<\[T; N]> for serde_json::Value
([#&#8203;1215](https://redirect.github.com/serde-rs/json/issues/1215))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:34:59 -07:00
Conrad Irwin
ebca6a8f3d Send os_version and country to amplitude (#20936)
Release Notes:

- N/A
2024-11-20 15:34:24 -07:00
Marshall Bowers
29c9f0f6a1 Extract InlineCompletionProvider to its own crate (#20935)
This PR extracts the `InlineCompletionProvider` trait and its related
types out of `editor` and into a new `inline_completion` crate.

By doing so we're able to remove a dependency on `editor` from the
`copilot` and `supermaven` crates.

We did have to move `editor::Direction` into the `inline_completion`
crate, as it is referenced by the `InlineCompletionProvider`. This
should find a better home, at some point.

Release Notes:

- N/A
2024-11-20 16:51:13 -05:00
Marshall Bowers
e076f55d78 language_model: Remove dependency on inline_completion_button (#20930)
This PR removes a dependency on the `inline_completion_button` crate
from the `language_model` crate.

We were taking on this dependency solely to call `initiate_sign_in`,
which can easily be moved to the `copilot` crate.

This allows `language_model` to move up in the crate dependency graph.

Release Notes:

- N/A
2024-11-20 16:19:20 -05:00
Marshall Bowers
e31f44450e title_bar: Remove dependency on extensions_ui (#20929)
This PR removes a dependency on the `extensions_ui` from the `title_bar`
crate.

This dependency only existed to reference the `Extensions` action, which
has now been moved to the `zed_actions` crate.

This allows `title_bar` to move up in the crate dependency graph.

Release Notes:

- N/A
2024-11-20 16:05:43 -05:00
Conrad Irwin
e0761db62d Revert: "a" for "vim::AngleBrackets" (#20918)
The replacement "g" didn't seem to work for everyone.

Closes #20912
Updates #20104

Release Notes:

- vim: Restores `dia` to mean "delete in argument" instead of "delete
within angle brackets". To keep this in your own keymap use:
    ```
    {
"context": "vim_operator == a || vim_operator == i || vim_operator ==
cs",
        "use_layout_keys": true,
        "bindings": {
            "a": "vim::AngleBrackets"
        }
    }
    ```
2024-11-20 13:46:44 -07:00
Peter Tripp
8c342ef706 Bump JSON schemas: package.json, tsconfig.json (#20910)
Add script/update-json-schemas
Updated JSON schemas to
[SchemaStore/schemastore@569a343](569a343137)
(2024-11-19)
2024-11-20 13:35:00 -05:00
Peter Tripp
7e67753d51 ci: Fix for checkout action with fetch-tags (#20917) 2024-11-20 13:32:05 -05:00
Conrad Irwin
1475a7000f Don't re-render the menu so often (#20914)
Closes #20710

Release Notes:

- Fixes opening the menu when Chinese Pinyin keyboard is in use
2024-11-20 10:27:50 -07:00
Marshall Bowers
41fd9189e3 context_servers: Document settings (#20907)
This PR documents the settings type for context servers so that the
documentation shows up when editing the `settings.json` file.

Release Notes:

- N/A
2024-11-20 11:30:14 -05:00
Marshall Bowers
973498e075 context_servers: Make settings field show up in settings completions (#20905)
This PR fixes an issue where the `settings` field for a context server
would not show up in the completions when editing the Zed settings.

It seems that `schemars` doesn't like the `serde_json::Value` as a
setting type when generating the JSON Schema. To address this, we are
using a custom schema of an empty object (as we don't yet have any other
information as to the structure of a given context server's settings).

Release Notes:

- context_servers: Fixed `settings` field not being suggested in
completions when editing `settings.json`.
2024-11-20 10:53:51 -05:00
Joseph T. Lyons
b63394f4bd v0.164.x dev 2024-11-20 10:45:44 -05:00
Thorsten Ball
743165fa6c Fix assistant hints showing up when selecting \n in Vim mode (#20899)
We also need to check whether the selection is empty, not just whether
its head is on an empty line.

Release Notes:

- N/A

Co-authored-by: Antonio <antonio@zed.dev>
2024-11-20 14:38:56 +01:00
Piotr Osiewicz
e03968f538 pane: Fix panic when dragging non-pinned item onto it's pinned copy in another pane (#20900)
Closes #20889

Release Notes:

- N/A
2024-11-20 14:22:07 +01: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
421 changed files with 16604 additions and 7317 deletions

View File

@@ -3,15 +3,6 @@ export default {
const url = new URL(request.url);
url.hostname = "docs-anw.pages.dev";
// These pages were removed, but may still be served due to Cloudflare's
// [asset retention](https://developers.cloudflare.com/pages/configuration/serving-pages/#asset-retention).
if (
url.pathname === "/docs/assistant/context-servers" ||
url.pathname === "/docs/assistant/model-context-protocol"
) {
return await fetch("https://zed.dev/404");
}
let res = await fetch(url, request);
if (res.status === 404) {

View File

@@ -245,6 +245,7 @@ jobs:
# 25 was chosen arbitrarily.
fetch-depth: 25
clean: false
ref: ${{ github.ref }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
@@ -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
@@ -268,18 +272,12 @@ jobs:
- name: Create macOS app bundle
run: script/bundle-mac
- name: Rename single-architecture binaries
- name: Rename binaries
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
run: |
mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (universal) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
@@ -305,8 +303,6 @@ jobs:
target/zed-remote-server-macos-aarch64.gz
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 }}
@@ -402,18 +398,15 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
auto-publish-release:
timeout-minutes: 60
name: Create a Linux bundle
auto-release-preview:
name: Auto release preview
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
needs: [bundle-mac, bundle-linux, bundle-linux-aarch64]
runs-on:
- self-hosted
if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }}
needs: [bundle-mac, bundle-linux-aarch64, bundle-linux]
- bundle
steps:
- name: Upload app bundle to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
draft: false
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
- name: gh release
run: gh release edit $GITHUB_REF_NAME --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 # v3
with:
version: "latest"
enable-cache: true

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/.direnv
.envrc
.idea
**/target
**/cargo-target

View File

@@ -2,7 +2,8 @@
"languages": {
"Markdown": {
"tab_size": 2,
"formatter": "prettier"
"formatter": "prettier",
"format_on_save": "on"
},
"TOML": {
"formatter": "prettier",

595
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,13 @@ members = [
"crates/anthropic",
"crates/assets",
"crates/assistant",
"crates/assistant2",
"crates/assistant_slash_command",
"crates/assistant_tool",
"crates/assistant_tools",
"crates/audio",
"crates/auto_update",
"crates/auto_update_ui",
"crates/breadcrumbs",
"crates/call",
"crates/channel",
@@ -20,7 +23,8 @@ members = [
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
"crates/context_servers",
"crates/context_server",
"crates/context_server_settings",
"crates/copilot",
"crates/db",
"crates/diagnostics",
@@ -49,11 +53,15 @@ members = [
"crates/http_client",
"crates/image_viewer",
"crates/indexed_docs",
"crates/inline_completion",
"crates/inline_completion_button",
"crates/install_cli",
"crates/journal",
"crates/language",
"crates/language_extension",
"crates/language_model",
"crates/language_model_selector",
"crates/language_models",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
@@ -78,7 +86,6 @@ members = [
"crates/project_panel",
"crates/project_symbols",
"crates/proto",
"crates/quick_action_bar",
"crates/recent_projects",
"crates/refineable",
"crates/refineable/derive_refineable",
@@ -114,6 +121,7 @@ members = [
"crates/terminal_view",
"crates/text",
"crates/theme",
"crates/theme_extension",
"crates/theme_importer",
"crates/theme_selector",
"crates/time_format",
@@ -126,6 +134,7 @@ members = [
"crates/util",
"crates/vcs_menu",
"crates/vim",
"crates/vim_mode_setting",
"crates/welcome",
"crates/workspace",
"crates/worktree",
@@ -181,10 +190,13 @@ ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" }
assistant2 = { path = "crates/assistant2" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_tool = { path = "crates/assistant_tool" }
assistant_tools = { path = "crates/assistant_tools" }
audio = { path = "crates/audio" }
auto_update = { path = "crates/auto_update" }
auto_update_ui = { path = "crates/auto_update_ui" }
breadcrumbs = { path = "crates/breadcrumbs" }
call = { path = "crates/call" }
channel = { path = "crates/channel" }
@@ -196,7 +208,8 @@ collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
context_servers = { path = "crates/context_servers" }
context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
diagnostics = { path = "crates/diagnostics" }
@@ -215,17 +228,23 @@ git = { path = "crates/git" }
git_hosting_providers = { path = "crates/git_hosting_providers" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
gpui = { path = "crates/gpui", default-features = false, features = [
"http_client",
] }
gpui_macros = { path = "crates/gpui_macros" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion = { path = "crates/inline_completion" }
inline_completion_button = { path = "crates/inline_completion_button" }
install_cli = { path = "crates/install_cli" }
journal = { path = "crates/journal" }
language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_model_selector = { path = "crates/language_model_selector" }
language_models = { path = "crates/language_models" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
@@ -252,7 +271,6 @@ project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
proto = { path = "crates/proto" }
quick_action_bar = { path = "crates/quick_action_bar" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" }
@@ -287,6 +305,7 @@ terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
theme = { path = "crates/theme" }
theme_extension = { path = "crates/theme_extension" }
theme_importer = { path = "crates/theme_importer" }
theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
@@ -298,6 +317,7 @@ ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
welcome = { path = "crates/welcome" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
@@ -313,7 +333,7 @@ alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
ashpd = "0.9.1"
ashpd = { version = "0.10", default-features = false, features = ["async-std"]}
async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
@@ -332,7 +352,7 @@ blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.18"
cargo_metadata = "0.19"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
@@ -368,14 +388,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" }
jupyter-protocol = { version = "0.3.0" }
jupyter-websocket-client = { version = "0.5.0" }
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.6.0"
nbformat = { version = "0.7.0" }
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
@@ -385,10 +405,10 @@ parking_lot = "0.12.1"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
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" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
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 = { version = "1.3.0", features = ["unstable"] }
profiling = "1"
@@ -409,7 +429,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.21.0", default-features = false, features = [
runtimelib = { version = "0.22.0", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-cursor"><path d="M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1"/><path d="M7 22h1a4 4 0 0 0 4-4v-1"/><path d="M7 2h1a4 4 0 0 1 4 4v1"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 20H16C14.9391 20 13.9217 19.6629 13.1716 19.0627C12.4214 18.4626 12 17.6487 12 16.8V7.2C12 6.35131 12.4214 5.53737 13.1716 4.93726C13.9217 4.33714 14.9391 4 16 4H17" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 20H8C9.06087 20 10.0783 19.5786 10.8284 18.8284C11.5786 18.0783 12 17.0609 12 16V15" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 4H8C9.06087 4 10.0783 4.42143 10.8284 5.17157C11.5786 5.92172 12 6.93913 12 8V9" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 715 B

View File

@@ -0,0 +1,5 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 3L8.5 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 6.5H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 13H12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -34,6 +34,7 @@
"dat": "storage",
"db": "storage",
"dbf": "storage",
"diff": "diff",
"dll": "storage",
"doc": "document",
"docx": "document",
@@ -112,6 +113,7 @@
"mkv": "video",
"ml": "ocaml",
"mli": "ocaml",
"mod": "go",
"mov": "video",
"mp3": "audio",
"mp4": "video",
@@ -127,6 +129,7 @@
"ogg": "audio",
"opus": "audio",
"otf": "font",
"pcss": "css",
"pdb": "storage",
"pdf": "document",
"php": "php",
@@ -173,6 +176,9 @@
"tsx": "react",
"ttf": "font",
"txt": "document",
"v": "v",
"vsh": "v",
"vv": "v",
"vue": "vue",
"wav": "audio",
"webm": "video",
@@ -181,6 +187,7 @@
"wmv": "video",
"woff": "font",
"woff2": "font",
"work": "go",
"wv": "audio",
"xls": "document",
"xlsx": "document",
@@ -235,6 +242,9 @@
"default": {
"icon": "icons/file_icons/file.svg"
},
"diff": {
"icon": "icons/file_icons/diff.svg"
},
"docker": {
"icon": "icons/file_icons/docker.svg"
},
@@ -379,6 +389,9 @@
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"v": {
"icon": "icons/file_icons/v.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},

View File

@@ -0,0 +1,4 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" d="M10.0469 12.8661L13.3884 3.31889C13.4386 3.1754 13.3167 3.03055 13.1667 3.05554L10.7292 3.46179C10.5875 3.48542 10.4693 3.58324 10.4197 3.71807L7.24789 12.3271C7.12763 12.6536 7.36919 13 7.71706 13H9.8581C9.94309 13 10.0188 12.9463 10.0469 12.8661Z" fill="black"/>
<path d="M6.90625 12.7321L3.61161 3.31889C3.56139 3.1754 3.6833 3.03055 3.83326 3.05554L6.27076 3.46179C6.4125 3.48542 6.53067 3.58324 6.58034 3.71807L9.90084 12.7309C9.94895 12.8614 9.85232 13 9.71317 13H7.28379C7.11381 13 6.9624 12.8926 6.90625 12.7321Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 663 B

1
assets/icons/globe.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-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@@ -405,7 +405,7 @@
"ctrl-shift-p": "command_palette::Toggle",
"f1": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"ctrl-alt-s": "workspace::SaveAll",
@@ -594,6 +594,7 @@
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
@@ -649,11 +650,16 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"bindings": {
"ctrl": "file_finder::ToggleMenu"
}
},
{
"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",

View File

@@ -49,8 +49,9 @@
"ctrl-d": "editor::Delete",
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-t": "editor::Transpose",
"ctrl-k": "editor::KillRingCut",
"ctrl-y": "editor::KillRingYank",
"cmd-k q": "editor::Rewrap",
"cmd-k cmd-q": "editor::Rewrap",
"cmd-backspace": "editor::DeleteToBeginningOfLine",
@@ -92,6 +93,8 @@
"ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
"ctrl-shift-p": "editor::SelectUp",
"shift-down": "editor::SelectDown",
@@ -206,6 +209,18 @@
"alt-enter": "editor::Newline"
}
},
{
"context": "AssistantPanel2",
"bindings": {
"cmd-n": "assistant2::NewThread"
}
},
{
"context": "MessageEditor > Editor",
"bindings": {
"cmd-enter": "assistant2::Chat"
}
},
{
"context": "PromptLibrary",
"bindings": {
@@ -340,7 +355,7 @@
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
"cmd-|": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"cmd-k cmd-l": "editor::ToggleFold",
@@ -431,7 +446,7 @@
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-shift-b": "outline_panel::ToggleFocus",
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
@@ -601,6 +616,7 @@
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
@@ -647,11 +663,16 @@
"tab": "channel_modal::ToggleMode"
}
},
{
"context": "FileFinder",
"bindings": {
"cmd": "file_finder::ToggleMenu"
}
},
{
"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",
@@ -711,7 +732,11 @@
"cmd-end": "terminal::ScrollToBottom",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom",
"ctrl-shift-space": "terminal::ToggleViMode"
"ctrl-shift-space": "terminal::ToggleViMode",
"ctrl-k up": "pane::SplitUp",
"ctrl-k down": "pane::SplitDown",
"ctrl-k left": "pane::SplitLeft",
"ctrl-k right": "pane::SplitRight"
}
}
]

View File

@@ -55,6 +55,10 @@
"n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching",
"] }": ["vim::UnmatchedForward", { "char": "}" } ],
"[ {": ["vim::UnmatchedBackward", { "char": "{" } ],
"] )": ["vim::UnmatchedForward", { "char": ")" } ],
"[ (": ["vim::UnmatchedBackward", { "char": "(" } ],
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
@@ -381,8 +385,9 @@
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::AngleBrackets",
"g": "vim::Argument"
"a": "vim::Argument",
"i": "vim::IndentObj",
"shift-i": ["vim::IndentObj", { "includeBelow": true }]
}
},
{
@@ -552,6 +557,12 @@
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w >": ["vim::ResizePane", "Widen"],
"ctrl-w <": ["vim::ResizePane", "Narrow"],
"ctrl-w -": ["vim::ResizePane", "Shorten"],
"ctrl-w +": ["vim::ResizePane", "Lengthen"],
"ctrl-w _": "vim::MaximizePane",
"ctrl-w =": "vim::ResetPaneSizes",
"ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePrevItem",
@@ -578,7 +589,7 @@
}
},
{
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"use_layout_keys": true,
"bindings": {
":": "command_palette::Toggle",

View File

@@ -300,6 +300,8 @@
"scroll_beyond_last_line": "one_page",
// The number of lines to keep above/below the cursor when scrolling.
"vertical_scroll_margin": 3,
// Whether to scroll when clicking near the edge of the visible text area.
"autoscroll_on_clicks": false,
// Scroll sensitivity multiplier. This multiplier is applied
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
@@ -490,9 +492,6 @@
"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'.
@@ -560,6 +559,8 @@
"close_position": "right",
// Whether to show the file icon for a tab.
"file_icons": false,
// Whether to always show the close button on tabs.
"always_show_close_button": false,
// What to do after closing the current tab.
//
// 1. Activate the tab that was open previously (default)
@@ -668,7 +669,7 @@
},
// Add files or globs of files that will be excluded by Zed entirely:
// they will be skipped during FS scan(s), file tree and file search
// will lack the corresponding file entries.
// will lack the corresponding file entries. Overrides `file_scan_inclusions`.
"file_scan_exclusions": [
"**/.git",
"**/.svn",
@@ -679,6 +680,11 @@
"**/.classpath",
"**/.settings"
],
// Add files or globs of files that will be included by Zed, even when
// ignored by git. This is useful for files that are not tracked by git,
// but are still important to your project. Note that globs that are
// overly broad can slow down Zed's file scanning. Overridden by `file_scan_exclusions`.
"file_scan_inclusions": [".env*"],
// Git gutter behavior configuration.
"git": {
// Control whether the git gutter is shown. May take 2 values:
@@ -839,8 +845,12 @@
}
},
"toolbar": {
// Whether to display the terminal title in its toolbar.
"title": true
// Whether to display the terminal title in its toolbar's breadcrumbs.
// Only shown if the terminal title is not empty.
//
// The shell running in the terminal needs to be configured to emit the title.
// Example: `echo -e "\e]2;New Title\007";`
"breadcrumbs": true
}
// Set the terminal's font size. If this option is not included,
// the terminal will default to matching the buffer's font size.
@@ -876,7 +886,7 @@
//
"file_types": {
"Plain Text": ["txt"],
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json"],
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
"Shell Script": [".env.*"]
},
/// By default use a recent system version of node, or install our own.

View File

@@ -33,7 +33,7 @@ client.workspace = true
clock.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
context_servers.workspace = true
context_server.workspace = true
db.workspace = true
editor.workspace = true
feature_flags.workspace = true
@@ -50,6 +50,8 @@ indexed_docs.workspace = true
indoc.workspace = true
language.workspace = true
language_model.workspace = true
language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true

View File

@@ -5,7 +5,6 @@ pub mod assistant_settings;
mod context;
pub mod context_store;
mod inline_assistant;
mod model_selector;
mod patch;
mod prompt_library;
mod prompts;
@@ -15,16 +14,12 @@ 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;
use assistant_tool::ToolRegistry;
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub use context::*;
@@ -33,12 +28,10 @@ use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::impl_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
use indexed_docs::IndexedDocsRegistry;
pub(crate) use inline_assistant::*;
use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
};
pub(crate) use model_selector::*;
pub use patch::*;
pub use prompts::PromptBuilder;
use prompts::PromptLoadingParams;
@@ -249,7 +242,7 @@ pub fn init(
assistant_slash_command::init(cx);
assistant_tool::init(cx);
assistant_panel::init(cx);
context_servers::init(cx);
context_server::init(cx);
let prompt_builder = prompts::PromptBuilder::new(Some(PromptLoadingParams {
fs: fs.clone(),
@@ -262,7 +255,6 @@ pub fn init(
.map(Arc::new)
.unwrap_or_else(|| Arc::new(prompts::PromptBuilder::new(None).unwrap()));
register_slash_commands(Some(prompt_builder.clone()), cx);
register_tools(cx);
inline_assistant::init(
fs.clone(),
prompt_builder.clone(),
@@ -275,7 +267,7 @@ pub fn init(
client.telemetry().clone(),
cx,
);
IndexedDocsRegistry::init_global(cx);
indexed_docs::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(Assistant::NAMESPACE);
@@ -350,8 +342,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
slash_command_registry.register_command(now_command::NowSlashCommand, false);
slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
slash_command_registry.register_command(fetch_command::FetchSlashCommand, true);
if let Some(prompt_builder) = prompt_builder {
cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
@@ -426,11 +417,6 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
}
}
fn register_tools(cx: &mut AppContext) {
let tool_registry = ToolRegistry::global(cx);
tool_registry.register_tool(tools::now_tool::NowTool);
}
pub fn humanize_token_count(count: usize) -> String {
match count {
0..=999 => count.to_string(),

View File

@@ -12,17 +12,17 @@ use crate::{
},
slash_command_picker,
terminal_inline_assistant::TerminalInlineAssistant,
Assist, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context, ContextEvent,
ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory,
DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, InsertIntoEditor,
InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata,
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, ParsedSlashCommand,
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, RequestType,
SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context,
ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus,
QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
ToggleModelSelector,
};
use crate::{PatchId, ResolvedPatch, ToolWorkingSet};
use anyhow::Result;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
use assistant_tool::ToolWorkingSet;
use client::{proto, zed_urls, Client, Status};
use collections::{hash_map, BTreeSet, HashMap, HashSet};
use editor::{
@@ -50,11 +50,12 @@ use indexed_docs::IndexedDocsStore;
use language::{
language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset,
};
use language_model::{
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
LanguageModelRegistry, Role,
};
use language_model::{LanguageModelImage, LanguageModelToolUse};
use language_model::{
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
ZED_CLOUD_PROVIDER_ID,
};
use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector};
use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate};
use project::lsp_store::LocalLspAdapterDelegate;
@@ -142,7 +143,7 @@ pub struct AssistantPanel {
languages: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>,
subscriptions: Vec<Subscription>,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
model_summary_editor: View<Editor>,
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
configuration_subscription: Option<Subscription>,
@@ -415,7 +416,6 @@ impl AssistantPanel {
ControlFlow::Break(())
});
pane.set_can_split(false, cx);
pane.set_can_navigate(true, cx);
pane.display_nav_history_buttons(None);
pane.set_should_display_tab_bar(|_| true);
@@ -450,6 +450,7 @@ impl AssistantPanel {
.gap(DynamicSpacing::Base02.rems(cx))
.child(
IconButton::new("new-chat", IconName::Plus)
.icon_size(IconSize::Small)
.on_click(
cx.listener(|_, _, cx| {
cx.dispatch_action(NewContext.boxed_clone())
@@ -664,7 +665,7 @@ impl AssistantPanel {
// If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
// the provider, we want to show a nudge to sign in.
let show_zed_ai_notice = client_status.is_signed_out()
&& active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
&& active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID);
self.show_zed_ai_notice = show_zed_ai_notice;
cx.notify();
@@ -1315,7 +1316,7 @@ impl AssistantPanel {
fn restart_context_servers(
workspace: &mut Workspace,
_action: &context_servers::Restart,
_action: &context_server::Restart,
cx: &mut ViewContext<Workspace>,
) {
let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
@@ -1481,15 +1482,13 @@ struct ScrollPosition {
struct PatchViewState {
crease_id: CreaseId,
multibuffer_range: Range<Anchor>,
height: u32,
editor: Option<PatchEditorState>,
update_task: Option<Task<()>>,
}
struct PatchEditorState {
editor: WeakView<ProposedChangesEditor>,
opened_patch: ResolvedPatch,
opened_patch: AssistantPatch,
}
type MessageHeader = MessageMetadata;
@@ -1519,8 +1518,8 @@ pub struct ContextEditor {
invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>,
pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
_subscriptions: Vec<Subscription>,
patches: HashMap<PatchId, PatchViewState>,
active_patch: Option<PatchId>,
patches: HashMap<Range<language::Anchor>, PatchViewState>,
active_patch: Option<Range<language::Anchor>>,
assistant_panel: WeakView<AssistantPanel>,
last_error: Option<AssistError>,
show_accept_terms: bool,
@@ -1575,7 +1574,7 @@ impl ContextEditor {
];
let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ids = context.read(cx).patch_ids().collect::<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 {
@@ -1606,9 +1605,7 @@ impl ContextEditor {
this.update_message_headers(cx);
this.update_image_blocks(cx);
this.insert_slash_command_output_sections(sections, false, cx);
for patch_id in patch_ids {
this.patch_updated(patch_id, cx);
}
this.patches_updated(&Vec::new(), &patch_ranges, cx);
this
}
@@ -1640,7 +1637,7 @@ impl ContextEditor {
}
fn focus_active_patch(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(patch) = self.active_patch() {
if let Some((_range, patch)) = self.active_patch() {
if let Some(editor) = patch
.editor
.as_ref()
@@ -1928,7 +1925,7 @@ impl ContextEditor {
Content::ToolUse {
range: tool_use.source_range.clone(),
tool_use: LanguageModelToolUse {
id: tool_use.id.to_string(),
id: tool_use.id.clone(),
name: tool_use.name.clone(),
input: tool_use.input.clone(),
},
@@ -1960,8 +1957,9 @@ impl ContextEditor {
);
});
}
ContextEvent::PatchUpdated(patch_id) => self.patch_updated(*patch_id, cx),
ContextEvent::PatchRemoved(patch_id) => self.patch_removed(*patch_id, cx),
ContextEvent::PatchesUpdated { removed, updated } => {
self.patches_updated(removed, updated, cx);
}
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
@@ -2223,124 +2221,125 @@ impl ContextEditor {
});
}
fn patch_updated(&mut self, patch_id: PatchId, cx: &mut ViewContext<ContextEditor>) {
fn patches_updated(
&mut self,
removed: &Vec<Range<text::Anchor>>,
updated: &Vec<Range<text::Anchor>>,
cx: &mut ViewContext<ContextEditor>,
) {
let this = cx.view().downgrade();
let mut editors_to_close = Vec::new();
self.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let multibuffer = &snapshot.buffer_snapshot;
let (&excerpt_id, _, _) = multibuffer.as_singleton().unwrap();
let Some(patch) = self.context.read(cx).patch_for_id(patch_id, cx) else {
return;
};
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();
let path_count = patch.path_count();
let patch_start = multibuffer
.anchor_in_excerpt(excerpt_id, patch.range.start)
.unwrap();
let patch_end = multibuffer
.anchor_in_excerpt(excerpt_id, patch.range.end)
.unwrap();
let render_block: RenderBlock = Arc::new({
let this = this.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_block(
patch_id,
max_width,
gutter_width,
block_id,
selected,
cx,
)
})
.ok()
.flatten()
.unwrap_or_else(|| Empty.into_any())
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);
}
});
let mut height = path_count as u32 + 1;
if patch.status == AssistantPatchStatus::Pending {
height += 1;
}
editor.unfold_ranges(&ranges_to_unfold, true, false, cx);
editor.remove_creases(removed_crease_ids, cx);
let crease = Crease::block(
patch_start..patch_end,
height,
BlockStyle::Flex,
render_block.clone(),
);
for range in updated {
let Some(patch) = self.context.read(cx).patch_for_range(&range, cx).cloned() else {
continue;
};
let should_refold;
if let Some(state) = self.patches.get_mut(&patch_id) {
if state.multibuffer_range != (patch_start..patch_end) || state.height != height {
editor.remove_creases([state.crease_id], cx);
state.crease_id = editor.insert_creases([crease.clone()], cx)[0];
state.height = height;
state.multibuffer_range = patch_start..patch_end;
should_refold = snapshot.intersects_fold(patch_start.to_offset(&multibuffer));
} else {
should_refold = false;
}
if state.editor.is_some() {
let resolved_patch = self.context.read(cx).resolve_patch(patch_id, cx);
state.update_task = Some({
let this = this.clone();
cx.spawn(|_, cx| async move {
if let Some(resolved_patch) = resolved_patch.await.log_err() {
Self::update_patch_editor(
this.clone(),
patch_id,
resolved_patch,
cx,
)
.log_err();
}
let path_count = patch.path_count();
let patch_start = multibuffer
.anchor_in_excerpt(excerpt_id, patch.range.start)
.unwrap();
let patch_end = multibuffer
.anchor_in_excerpt(excerpt_id, patch.range.end)
.unwrap();
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_block(
patch_range.clone(),
max_width,
gutter_width,
block_id,
selected,
cx,
)
})
});
}
} else {
let crease_id = editor.insert_creases([crease.clone()], cx)[0];
self.patches.insert(
patch_id,
PatchViewState {
crease_id,
multibuffer_range: patch_start..patch_end,
height,
editor: None,
update_task: None,
},
.ok()
.flatten()
.unwrap_or_else(|| Empty.into_any())
}
});
let height = path_count as u32 + 1;
let crease = Crease::block(
patch_start..patch_end,
height,
BlockStyle::Flex,
render_block.clone(),
);
should_refold = true;
}
let should_refold;
if let Some(state) = self.patches.get_mut(&range) {
if let Some(editor_state) = &state.editor {
if editor_state.opened_patch != patch {
state.update_task = Some({
let this = this.clone();
cx.spawn(|_, cx| async move {
Self::update_patch_editor(this.clone(), patch, cx)
.await
.log_err();
})
});
}
}
if should_refold {
editor.unfold_ranges(&[patch_start..patch_end], true, false, cx);
editor.fold_creases(vec![crease], false, cx);
should_refold =
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
} else {
let crease_id = editor.insert_creases([crease.clone()], cx)[0];
self.patches.insert(
range.clone(),
PatchViewState {
crease_id,
editor: None,
update_task: None,
},
);
should_refold = true;
}
if should_refold {
editor.unfold_ranges(&[patch_start..patch_end], true, false, cx);
editor.fold_creases(vec![crease], false, cx);
}
}
});
self.update_active_patch(cx);
}
fn patch_removed(&mut self, patch_id: PatchId, cx: &mut ViewContext<ContextEditor>) {
let Some(state) = self.patches.remove(&patch_id) else {
return;
};
self.editor.update(cx, |editor, cx| {
editor.unfold_ranges(&[state.multibuffer_range], true, false, cx);
editor.remove_creases([state.crease_id], cx);
});
if let Some(editor) = state.editor.and_then(|state| state.editor.upgrade()) {
for editor in editors_to_close {
self.close_patch_editor(editor, cx);
}
self.update_active_patch(cx);
}
@@ -2421,8 +2420,9 @@ impl ContextEditor {
cx.emit(event.clone());
}
fn active_patch(&self) -> Option<&PatchViewState> {
self.patches.get(self.active_patch.as_ref()?)
fn active_patch(&self) -> Option<(Range<text::Anchor>, &PatchViewState)> {
let patch = self.active_patch.as_ref()?;
Some((patch.clone(), self.patches.get(&patch)?))
}
fn update_active_patch(&mut self, cx: &mut ViewContext<Self>) {
@@ -2431,8 +2431,9 @@ impl ContextEditor {
});
let context = self.context.read(cx);
let new_patch_id = context.patch_containing(newest_cursor, cx);
if new_patch_id == self.active_patch {
let new_patch = context.patch_containing(newest_cursor, cx).cloned();
if new_patch.as_ref().map(|p| &p.range) == self.active_patch.as_ref() {
return;
}
@@ -2446,14 +2447,16 @@ impl ContextEditor {
}
}
if let Some(patch_id) = new_patch_id {
self.active_patch = Some(patch_id);
if let Some(new_patch) = new_patch {
self.active_patch = Some(new_patch.range.clone());
if let Some(patch_state) = self.patches.get_mut(&patch_id) {
let editor = patch_state
.editor
.as_ref()
.and_then(|state| state.editor.upgrade());
if let Some(patch_state) = self.patches.get_mut(&new_patch.range) {
let mut editor = None;
if let Some(state) = &patch_state.editor {
if let Some(opened_editor) = state.editor.upgrade() {
editor = Some(opened_editor);
}
}
if let Some(editor) = editor {
self.workspace
@@ -2462,13 +2465,8 @@ impl ContextEditor {
})
.ok();
} else {
let resolved_patch = self.context.read(cx).resolve_patch(patch_id, cx);
patch_state.update_task = Some(cx.spawn(move |this, cx| async move {
if let Some(resolved_patch) = resolved_patch.await.log_err() {
Self::open_patch_editor(this, patch_id, resolved_patch, cx)
.await
.log_err();
}
Self::open_patch_editor(this, new_patch, cx).await.log_err();
}));
}
}
@@ -2497,25 +2495,35 @@ impl ContextEditor {
async fn open_patch_editor(
this: WeakView<Self>,
patch_id: PatchId,
patch: ResolvedPatch,
patch: AssistantPatch,
mut cx: AsyncWindowContext,
) -> Result<()> {
let project = this.update(&mut cx, |this, _| this.project.clone())?;
let resolved_patch = patch.resolve(project.clone(), &mut cx).await;
let editor = cx.new_view(|cx| {
let mut editor = ProposedChangesEditor::new(
SharedString::from(""),
Vec::<ProposedChangeLocation<usize>>::new(),
let editor = ProposedChangesEditor::new(
patch.title.clone(),
resolved_patch
.edit_groups
.iter()
.map(|(buffer, groups)| ProposedChangeLocation {
buffer: buffer.clone(),
ranges: groups
.iter()
.map(|group| group.context_range.clone())
.collect(),
})
.collect(),
Some(project.clone()),
cx,
);
patch.apply(&mut editor, None, cx);
resolved_patch.apply(&editor, cx);
editor
})?;
this.update(&mut cx, |this, cx| {
if let Some(patch_state) = this.patches.get_mut(&patch_id) {
if let Some(patch_state) = this.patches.get_mut(&patch.range) {
patch_state.editor = Some(PatchEditorState {
editor: editor.downgrade(),
opened_patch: patch,
@@ -2533,18 +2541,34 @@ impl ContextEditor {
Ok(())
}
fn update_patch_editor(
async fn update_patch_editor(
this: WeakView<Self>,
patch_id: PatchId,
patch: ResolvedPatch,
patch: AssistantPatch,
mut cx: AsyncWindowContext,
) -> Result<()> {
let project = this.update(&mut cx, |this, _| this.project.clone())?;
let resolved_patch = patch.resolve(project.clone(), &mut cx).await;
this.update(&mut cx, |this, cx| {
let patch_state = this.patches.get_mut(&patch_id)?;
let patch_state = this.patches.get_mut(&patch.range)?;
let locations = resolved_patch
.edit_groups
.iter()
.map(|(buffer, groups)| ProposedChangeLocation {
buffer: buffer.clone(),
ranges: groups
.iter()
.map(|group| group.context_range.clone())
.collect(),
})
.collect();
if let Some(state) = &mut patch_state.editor {
if let Some(editor) = state.editor.upgrade() {
editor.update(cx, |editor, cx| {
patch.apply(editor, Some(&state.opened_patch), cx);
editor.set_title(patch.title.clone(), cx);
editor.reset_locations(locations, cx);
resolved_patch.apply(editor, cx);
});
state.opened_patch = patch;
@@ -2553,6 +2577,7 @@ impl ContextEditor {
}
}
patch_state.update_task.take();
Some(())
})?;
Ok(())
@@ -3427,16 +3452,23 @@ impl ContextEditor {
fn render_patch_block(
&mut self,
patch_id: PatchId,
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));
let (excerpt_id, _buffer_id, _) = snapshot.buffer_snapshot.as_singleton().unwrap();
let excerpt_id = *excerpt_id;
let anchor = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap();
let theme = cx.theme().clone();
let patch = self.context.read(cx).patch_for_id(patch_id, cx)?;
let anchor = self.patches.get(&patch_id)?.multibuffer_range.start;
let patch = self.context.read(cx).patch_for_range(&range, cx)?;
let paths = patch
.paths()
.map(|p| SharedString::from(p.to_string()))
@@ -4426,13 +4458,13 @@ pub struct ContextEditorToolbarItem {
fs: Arc<dyn Fs>,
active_context_editor: Option<WeakView<ContextEditor>>,
model_summary_editor: View<Editor>,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
}
impl ContextEditorToolbarItem {
pub fn new(
workspace: &Workspace,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
model_summary_editor: View<Editor>,
) -> Self {
Self {
@@ -4528,8 +4560,17 @@ impl Render for ContextEditorToolbarItem {
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
// })
.child(
ModelSelector::new(
self.fs.clone(),
LanguageModelSelector::new(
{
let fs = self.fs.clone();
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
}
},
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(

View File

@@ -5,13 +5,12 @@ use anthropic::Model as AnthropicModel;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::{AppContext, Pixels};
use language_model::provider::open_ai;
use language_model::settings::{
AnthropicSettingsContent, AnthropicSettingsContentV1, OllamaSettingsContent,
OpenAiSettingsContent, OpenAiSettingsContentV1, VersionedAnthropicSettingsContent,
VersionedOpenAiSettingsContent,
use language_model::{CloudModel, LanguageModel};
use language_models::{
provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent,
AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent,
OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent,
};
use language_model::{settings::AllLanguageModelSettings, CloudModel, LanguageModel};
use ollama::Model as OllamaModel;
use schemars::{schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
@@ -60,7 +59,6 @@ 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 {
@@ -203,7 +201,6 @@ 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,
@@ -244,7 +241,6 @@ impl AssistantSettingsContent {
},
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
enabled: None,
show_hints: None,
button: settings.button,
dock: settings.dock,
default_width: settings.default_width,
@@ -357,7 +353,6 @@ impl Default for VersionedAssistantSettingsContent {
fn default() -> Self {
Self::V2(AssistantSettingsContentV2 {
enabled: None,
show_hints: None,
button: None,
dock: None,
default_width: None,
@@ -375,11 +370,6 @@ 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
@@ -514,7 +504,6 @@ 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(
@@ -585,7 +574,6 @@ mod tests {
}),
inline_alternatives: None,
enabled: None,
show_hints: None,
button: None,
dock: None,
default_width: None,

View File

@@ -1,22 +1,21 @@
#[cfg(test)]
mod context_tests;
use crate::patch::{PatchId, PatchStore};
use crate::slash_command_working_set::SlashCommandWorkingSet;
use crate::{
prompts::PromptBuilder,
slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
};
use crate::{PatchStoreEvent, ResolvedPatch, ToolWorkingSet};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
};
use assistant_tool::ToolWorkingSet;
use client::{self, proto, telemetry::Telemetry};
use clock::ReplicaId;
use collections::{HashMap, HashSet};
use feature_flags::{FeatureFlag, FeatureFlagAppExt};
use feature_flags::{FeatureFlagAppExt, ToolUseFeatureFlag};
use fs::{Fs, RemoveOptions};
use futures::{future::Shared, FutureExt, StreamExt};
use gpui::{
@@ -26,12 +25,14 @@ use gpui::{
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
logging::report_assistant_event,
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role,
StopReason,
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse,
LanguageModelToolUseId, MessageContent, Role, StopReason,
};
use language_models::{
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
report_assistant_event,
};
use open_ai::Model as OpenAiModel;
use paths::contexts_dir;
@@ -368,8 +369,10 @@ pub enum ContextEvent {
MessagesEdited,
SummaryChanged,
StreamedCompletion,
PatchUpdated(PatchId),
PatchRemoved(PatchId),
PatchesUpdated {
removed: Vec<Range<language::Anchor>>,
updated: Vec<Range<language::Anchor>>,
},
InvokedSlashCommandChanged {
command_id: InvokedSlashCommandId,
},
@@ -382,7 +385,7 @@ pub enum ContextEvent {
},
UsePendingTools,
ToolFinished {
tool_use_id: Arc<str>,
tool_use_id: LanguageModelToolUseId,
output_range: Range<language::Anchor>,
},
Operation(ContextOperation),
@@ -476,7 +479,7 @@ pub enum Content {
},
ToolResult {
range: Range<language::Anchor>,
tool_use_id: Arc<str>,
tool_use_id: LanguageModelToolUseId,
},
}
@@ -543,7 +546,7 @@ pub struct Context {
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>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
message_anchors: Vec<MessageAnchor>,
contents: Vec<Content>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
@@ -559,10 +562,9 @@ pub struct Context {
_subscriptions: Vec<Subscription>,
telemetry: Option<Arc<Telemetry>>,
language_registry: Arc<LanguageRegistry>,
patches: Vec<(Range<text::Anchor>, PatchId)>,
patch_store: Model<PatchStore>,
patches: Vec<AssistantPatch>,
xml_tags: Vec<XmlTag>,
project: Model<Project>,
project: Option<Model<Project>>,
prompt_builder: Arc<PromptBuilder>,
}
@@ -576,9 +578,9 @@ impl ContextAnnotation for ParsedSlashCommand {
}
}
impl ContextAnnotation for (Range<language::Anchor>, PatchId) {
impl ContextAnnotation for AssistantPatch {
fn range(&self) -> &Range<language::Anchor> {
&self.0
&self.range
}
}
@@ -593,7 +595,7 @@ impl EventEmitter<ContextEvent> for Context {}
impl Context {
pub fn local(
language_registry: Arc<LanguageRegistry>,
project: Model<Project>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
@@ -623,7 +625,7 @@ impl Context {
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Model<Project>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
) -> Self {
@@ -639,14 +641,6 @@ impl Context {
});
let edits_since_last_slash_command_parse =
buffer.update(cx, |buffer, _| buffer.subscribe());
let patch_store = cx.new_model(|_| PatchStore::new(project.clone()));
cx.subscribe(&patch_store, |_, _, event, cx| {
cx.emit(match event {
PatchStoreEvent::PatchUpdated(id) => ContextEvent::PatchUpdated(*id),
PatchStoreEvent::PatchRemoved(id) => ContextEvent::PatchRemoved(*id),
})
})
.detach();
let mut this = Self {
id,
timestamp: clock::Lamport::new(replica_id),
@@ -671,7 +665,6 @@ impl Context {
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
pending_save: Task::ready(Ok(())),
path: None,
patch_store,
buffer,
telemetry,
project,
@@ -755,7 +748,7 @@ impl Context {
prompt_builder: Arc<PromptBuilder>,
slash_commands: Arc<SlashCommandWorkingSet>,
tools: Arc<ToolWorkingSet>,
project: Model<Project>,
project: Option<Model<Project>>,
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
) -> Self {
@@ -1038,7 +1031,7 @@ impl Context {
self.language_registry.clone()
}
pub fn project(&self) -> Model<Project> {
pub fn project(&self) -> Option<Model<Project>> {
self.project.clone()
}
@@ -1054,46 +1047,50 @@ impl Context {
self.summary.as_ref()
}
pub(crate) fn patch_containing<'a>(
&'a self,
position: Point,
cx: &'a AppContext,
) -> Option<PatchId> {
let buffer = self.buffer.read(cx);
let index = self
.patches
.binary_search_by(|patch| {
let patch_range = patch.0.to_point(&buffer);
if position < patch_range.start {
Ordering::Greater
} else if position > patch_range.end {
Ordering::Less
} else {
Ordering::Equal
}
})
.ok()?;
Some(self.patches[index].1)
}
pub fn patch_ids(&self) -> impl Iterator<Item = PatchId> + '_ {
self.patches.iter().map(|patch| patch.1)
}
pub(crate) fn patch_for_id<'a>(
&'a self,
id: PatchId,
cx: &'a AppContext,
) -> Option<&AssistantPatch> {
self.patch_store.read(cx).get(id)
}
pub(crate) fn resolve_patch(
pub(crate) fn patch_containing(
&self,
id: PatchId,
position: Point,
cx: &AppContext,
) -> Task<Result<ResolvedPatch>> {
self.patch_store.read(cx).resolve_patch(id, cx)
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.patches.binary_search_by(|patch| {
let patch_range = patch.range.to_point(&buffer);
if position < patch_range.start {
Ordering::Greater
} else if position > patch_range.end {
Ordering::Less
} else {
Ordering::Equal
}
});
if let Ok(ix) = index {
Some(&self.patches[ix])
} else {
None
}
}
pub fn patch_ranges(&self) -> impl Iterator<Item = Range<language::Anchor>> + '_ {
self.patches.iter().map(|patch| patch.range.clone())
}
pub(crate) fn patch_for_range(
&self,
range: &Range<language::Anchor>,
cx: &AppContext,
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx);
let index = self.patch_index_for_range(range, buffer).ok()?;
Some(&self.patches[index])
}
fn patch_index_for_range(
&self,
tagged_range: &Range<text::Anchor>,
buffer: &text::BufferSnapshot,
) -> Result<usize, usize> {
self.patches
.binary_search_by(|probe| probe.range.cmp(&tagged_range, buffer))
}
pub fn parsed_slash_commands(&self) -> &[ParsedSlashCommand] {
@@ -1129,7 +1126,7 @@ impl Context {
self.pending_tool_uses_by_id.values().collect()
}
pub fn get_tool_use_by_id(&self, id: &Arc<str>) -> Option<&PendingToolUse> {
pub fn get_tool_use_by_id(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> {
self.pending_tool_uses_by_id.get(id)
}
@@ -1391,6 +1388,8 @@ 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 {
@@ -1415,7 +1414,13 @@ impl Context {
cx,
);
self.invalidate_pending_slash_commands(&buffer, cx);
self.reparse_patches_in_range(start..end, &buffer, cx);
self.reparse_patches_in_range(
start..end,
&buffer,
&mut updated_patches,
&mut removed_patches,
cx,
);
}
if !updated_parsed_slash_commands.is_empty()
@@ -1426,6 +1431,13 @@ 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(
@@ -1520,6 +1532,8 @@ impl Context {
&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.
@@ -1535,13 +1549,13 @@ impl Context {
// Reparse all tags after the last unchanged patch before the change.
let mut tags_start_ix = 0;
if let Some((preceding_unchanged_patch_range, _)) =
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)
.cmp(&preceding_unchanged_patch.range.end, buffer)
.then(Ordering::Less)
}) {
Ok(ix) | Err(ix) => ix,
@@ -1550,28 +1564,13 @@ impl Context {
// Rebuild the patches in the range.
let new_patches = self.parse_patches(tags_start_ix, range.end, buffer, cx);
self.patch_store.update(cx, |patch_store, cx| {
let mut removed_entries = self.patches[intersecting_patches_range.clone()]
.iter()
.map(|(range, id)| (range.start, *id))
.collect::<HashMap<_, _>>();
let added_entries = new_patches.into_iter().map(|patch| {
let id;
let range = patch.range.clone();
if let Some(existing_id) = removed_entries.remove(&range.start) {
id = existing_id;
patch_store.update(id, patch, cx).ok();
} else {
id = patch_store.insert(patch.clone(), cx);
};
(range, id)
});
self.patches
.splice(intersecting_patches_range, added_entries);
for id in removed_entries.into_values() {
patch_store.remove(id, 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(
@@ -1649,7 +1648,7 @@ impl Context {
if tag.kind == XmlTagKind::Patch && tag.is_open_tag {
patch_tag_depth += 1;
let patch_start = tag.range.start;
let mut edits = Vec::<AssistantEdit>::new();
let mut edits = Vec::<Result<AssistantEdit>>::new();
let mut patch = AssistantPatch {
range: patch_start..patch_start,
title: String::new().into(),
@@ -1661,7 +1660,7 @@ impl Context {
if tag.kind == XmlTagKind::Patch && !tag.is_open_tag {
patch_tag_depth -= 1;
if patch_tag_depth == 0 {
patch.range.end = tag.range.end.bias_right(buffer);
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);
@@ -1678,7 +1677,13 @@ impl Context {
}
}
edits.sort_unstable_by(|a, b| a.path.cmp(&b.path));
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);
@@ -1708,17 +1713,13 @@ impl Context {
while let Some(tag) = tags.next() {
if tag.kind == XmlTagKind::Edit && !tag.is_open_tag {
if let Some(edit) = AssistantEdit::new(
edits.push(AssistantEdit::new(
path,
operation,
old_text,
new_text,
description,
)
.log_err()
{
edits.push(edit);
}
));
break;
}
@@ -2152,7 +2153,7 @@ impl Context {
pub fn insert_tool_output(
&mut self,
tool_use_id: Arc<str>,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut ModelContext<Self>,
) {
@@ -2339,11 +2340,10 @@ impl Context {
let source_range = buffer.anchor_after(start_ix)
..buffer.anchor_after(end_ix);
let tool_use_id: Arc<str> = tool_use.id.into();
this.pending_tool_uses_by_id.insert(
tool_use_id.clone(),
tool_use.id.clone(),
PendingToolUse {
id: tool_use_id,
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
status: PendingToolUseStatus::Idle,
@@ -2602,8 +2602,14 @@ impl Context {
}
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, cx);
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 })
}
}
@@ -3194,19 +3200,9 @@ pub enum PendingSlashCommandStatus {
Error(String),
}
pub(crate) struct ToolUseFeatureFlag;
impl FeatureFlag for ToolUseFeatureFlag {
const NAME: &'static str = "assistant-tool-use";
fn enabled_for_staff() -> bool {
false
}
}
#[derive(Debug, Clone)]
pub struct PendingToolUse {
pub id: Arc<str>,
pub id: LanguageModelToolUseId,
pub name: String,
pub input: serde_json::Value,
pub status: PendingToolUseStatus,

View File

@@ -5,12 +5,12 @@ use crate::{
Context, ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId,
MessageStatus, PromptBuilder,
};
use crate::{PatchId, ToolWorkingSet};
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
};
use assistant_tool::ToolWorkingSet;
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{
@@ -26,7 +26,6 @@ use project::Project;
use rand::prelude::*;
use serde_json::json;
use settings::SettingsStore;
use std::mem;
use std::{
cell::RefCell,
env,
@@ -46,7 +45,23 @@ use workspace::Workspace;
#[gpui::test]
fn test_inserting_and_removing_messages(cx: &mut AppContext) {
let context = init_test(cx);
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
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(),
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();
@@ -171,7 +186,24 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
#[gpui::test]
fn test_message_splitting(cx: &mut AppContext) {
let context = init_test(cx);
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
LanguageModelRegistry::test(cx);
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.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();
@@ -259,7 +291,23 @@ fn test_message_splitting(cx: &mut AppContext) {
#[gpui::test]
fn test_messages_for_offsets(cx: &mut AppContext) {
let context = init_test(cx);
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
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(),
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();
@@ -338,7 +386,13 @@ fn test_messages_for_offsets(cx: &mut AppContext) {
#[gpui::test]
async fn test_slash_commands(cx: &mut TestAppContext) {
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test);
cx.update(Project::init_settings);
cx.update(assistant_panel::init);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
"/test",
json!({
@@ -352,11 +406,24 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
}),
)
.await;
let context = cx.update(|cx| init_test_with_fs(fs, cx));
let slash_command_registry = cx.update(SlashCommandRegistry::default_global);
slash_command_registry.register_command(file_command::FileSlashCommand, false);
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(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
#[derive(Default)]
struct ContextRanges {
parsed_commands: HashSet<Range<language::Anchor>>,
@@ -613,7 +680,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
}
#[gpui::test]
async fn test_patch_parsing(cx: &mut TestAppContext) {
async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
cx.update(prompt_library::init);
let mut settings_store = cx.update(SettingsStore::test);
cx.update(|cx| {
@@ -639,7 +706,7 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
let context = cx.new_model(|cx| {
Context::local(
registry.clone(),
project.clone(),
Some(project),
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
@@ -648,17 +715,6 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
)
});
let events = Rc::new(RefCell::new(Vec::new()));
context.update(cx, |_, cx| {
let events = events.clone();
cx.subscribe(&context, move |_, _, event, _cx| match event {
ContextEvent::PatchUpdated(id) => events.borrow_mut().push(("updated", *id)),
ContextEvent::PatchRemoved(id) => events.borrow_mut().push(("removed", *id)),
_ => {}
})
.detach();
});
// Insert an assistant message to simulate a response.
let assistant_message_id = context.update(cx, |context, cx| {
let user_message_id = context.messages(cx).next().unwrap().id;
@@ -688,9 +744,8 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
&[],
cx,
);
assert_eq!(mem::take(&mut *events.borrow_mut()), &[]);
// Partial patch tag is added
// Partial edit step tag is added
edit(
&context,
"
@@ -726,7 +781,7 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
<edit>»",
cx,
);
let patch_ids = expect_patches(
expect_patches(
&context,
"
@@ -738,12 +793,8 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
&[&[]],
cx,
);
assert_eq!(
mem::take(&mut *events.borrow_mut()),
&[("updated", patch_ids[0])]
);
// Add one edit to the patch
// The full patch is added
edit(
&context,
"
@@ -760,59 +811,7 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
<new_text>
fn two() {}
</new_text>
</edit>»",
cx,
);
let patch_ids = expect_patches(
&context,
"
one
two
«<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<old_text>fn one</old_text>
<new_text>
fn two() {}
</new_text>
</edit>»",
&[&[AssistantEdit {
path: "src/lib.rs".into(),
kind: AssistantEditKind::InsertAfter {
old_text: "fn one".into(),
new_text: "fn two() {}".into(),
description: Some("add a `two` function".into()),
},
}]],
cx,
);
assert_eq!(
mem::take(&mut *events.borrow_mut()),
&[("updated", patch_ids[0])]
);
// The full patch is added
edit(
&context,
"
one
two
<patch>
<edit>
<description>add a `two` function</description>
<path>src/lib.rs</path>
<operation>insert_after</operation>
<old_text>fn one</old_text>
<new_text>
fn two() {}
</new_text>
</edit>«
</edit>
</patch>
also,»",
@@ -848,10 +847,6 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
}]],
cx,
);
assert_eq!(
mem::take(&mut *events.borrow_mut()),
&[("updated", patch_ids[0])]
);
// The step is manually edited.
edit(
@@ -906,10 +901,6 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
}]],
cx,
);
assert_eq!(
mem::take(&mut *events.borrow_mut()),
&[("updated", patch_ids[0])]
);
// When setting the message role to User, the steps are cleared.
context.update(cx, |context, cx| {
@@ -939,10 +930,6 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
&[],
cx,
);
assert_eq!(
mem::take(&mut *events.borrow_mut()),
&[("removed", patch_ids[0])]
);
// When setting the message role back to Assistant, the steps are reparsed.
context.update(cx, |context, cx| {
@@ -989,7 +976,7 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
project,
None,
None,
cx,
)
@@ -1040,28 +1027,26 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
expected_marked_text: &str,
expected_suggestions: &[&[AssistantEdit]],
cx: &mut TestAppContext,
) -> Vec<PatchId> {
) {
let expected_marked_text = expected_marked_text.unindent();
let (expected_text, _) = marked_text_ranges(&expected_marked_text, false);
let (buffer_text, ranges, patch_ids, patches) = context.update(cx, |context, cx| {
let patch_store = context.patch_store.read(cx);
let (buffer_text, ranges, patches) = context.update(cx, |context, cx| {
context.buffer.read_with(cx, |buffer, _| {
let ranges = context
.patches
.iter()
.map(|entry| entry.0.to_offset(buffer))
.map(|entry| entry.range.to_offset(buffer))
.collect::<Vec<_>>();
let patch_ids = context
.patches
.iter()
.map(|(_, id)| *id)
.collect::<Vec<_>>();
let patches = patch_ids
.iter()
.map(|id| patch_store.get(*id).unwrap().clone())
.collect::<Vec<_>>();
(buffer.text(), ranges, patch_ids, patches)
(
buffer.text(),
ranges,
context
.patches
.iter()
.map(|step| step.edits.clone())
.collect::<Vec<_>>(),
)
})
});
@@ -1075,24 +1060,41 @@ async fn test_patch_parsing(cx: &mut TestAppContext) {
.iter()
.map(|patch| {
patch
.edits
.iter()
.map(|edit| AssistantEdit {
path: edit.path.clone(),
kind: edit.kind.clone(),
.map(|edit| {
let edit = edit.as_ref().unwrap();
AssistantEdit {
path: edit.path.clone(),
kind: edit.kind.clone(),
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
expected_suggestions
);
patch_ids
}
}
#[gpui::test]
async fn test_serialization(cx: &mut TestAppContext) {
let context = cx.update(init_test);
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test);
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(),
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| {
@@ -1130,11 +1132,11 @@ async fn test_serialization(cx: &mut TestAppContext) {
Context::deserialize(
serialized_context,
Default::default(),
context.read(cx).language_registry.clone(),
context.read(cx).prompt_builder.clone(),
registry.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
context.read(cx).project.clone(),
None,
None,
cx,
)
@@ -1170,9 +1172,8 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
cx.update(Project::init_settings);
cx.update(assistant_panel::init);
let slash_commands = cx.update(SlashCommandRegistry::default_global);
slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false);
@@ -1186,8 +1187,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
let context_id = ContextId::new();
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
for i in 0..num_peers {
let project =
cx.update(|cx| Project::empty(FakeFs::new(cx.background_executor().clone()), cx));
let context = cx.new_model(|cx| {
Context::new(
context_id.clone(),
@@ -1197,7 +1196,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
project,
None,
None,
cx,
)
@@ -1444,7 +1443,23 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
#[gpui::test]
fn test_mark_cache_anchors(cx: &mut AppContext) {
let context = init_test(cx);
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
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(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
});
let buffer = context.read(cx).buffer.clone();
// Create a test cache configuration
@@ -1588,32 +1603,6 @@ fn test_mark_cache_anchors(cx: &mut AppContext) {
);
}
fn init_test(cx: &mut AppContext) -> Model<Context> {
init_test_with_fs(FakeFs::new(cx.background_executor().clone()), cx)
}
fn init_test_with_fs(fs: Arc<FakeFs>, cx: &mut AppContext) -> Model<Context> {
let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let project = Project::empty(fs, cx);
cx.new_model(|cx| {
Context::local(
registry,
project,
None,
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
Arc::new(ToolWorkingSet::default()),
cx,
)
})
}
fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> {
context
.read(cx)

View File

@@ -1,15 +1,16 @@
use crate::slash_command::context_server_command;
use crate::SlashCommandId;
use crate::{
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 assistant_tool::{ToolId, ToolWorkingSet};
use client::{proto, telemetry::Telemetry, Client, TypedEnvelope};
use clock::ReplicaId;
use collections::HashMap;
use context_servers::manager::ContextServerManager;
use context_servers::ContextServerFactoryRegistry;
use context_server::manager::ContextServerManager;
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use fs::Fs;
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
@@ -363,7 +364,7 @@ impl ContextStore {
let context = cx.new_model(|cx| {
Context::local(
self.languages.clone(),
self.project.clone(),
Some(self.project.clone()),
Some(self.telemetry.clone()),
self.prompt_builder.clone(),
self.slash_commands.clone(),
@@ -406,7 +407,7 @@ impl ContextStore {
prompt_builder,
slash_commands,
tools,
project,
Some(project),
Some(telemetry),
cx,
)
@@ -468,7 +469,7 @@ impl ContextStore {
prompt_builder,
slash_commands,
tools,
project,
Some(project),
Some(telemetry),
cx,
)
@@ -548,7 +549,7 @@ impl ContextStore {
prompt_builder,
slash_commands,
tools,
project,
Some(project),
Some(telemetry),
cx,
)
@@ -770,7 +771,7 @@ impl ContextStore {
contexts.push(SavedContextMetadata {
title: title.to_string(),
path,
mtime: metadata.mtime.into(),
mtime: metadata.mtime.timestamp_for_user().into(),
});
}
}
@@ -808,13 +809,13 @@ impl ContextStore {
fn handle_context_server_event(
&mut self,
context_server_manager: Model<ContextServerManager>,
event: &context_servers::manager::Event,
event: &context_server::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 } => {
context_server::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({
@@ -825,7 +826,7 @@ impl ContextStore {
return;
};
if protocol.capable(context_servers::protocol::ServerCapability::Prompts) {
if protocol.capable(context_server::protocol::ServerCapability::Prompts) {
if let Some(prompts) = protocol.list_prompts().await.log_err() {
let slash_command_ids = prompts
.into_iter()
@@ -853,12 +854,12 @@ impl ContextStore {
}
}
if protocol.capable(context_servers::protocol::ServerCapability::Tools) {
if protocol.capable(context_server::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(
Arc::new(ContextServerTool::new(
context_server_manager.clone(),
server.id(),
tool,
@@ -880,7 +881,7 @@ impl ContextStore {
.detach();
}
}
context_servers::manager::Event::ServerStopped { server_id } => {
context_server::manager::Event::ServerStopped { server_id } => {
if let Some(slash_command_ids) =
self.context_server_slash_command_ids.remove(server_id)
{

View File

@@ -1,7 +1,7 @@
use crate::{
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff,
CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff,
};
use anyhow::{anyhow, Context as _, Result};
use client::{telemetry::Telemetry, ErrorExt};
@@ -30,14 +30,16 @@ use gpui::{
};
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
use language_model::{
logging::report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role,
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelTextStream, Role,
};
use language_model_selector::LanguageModelSelector;
use language_models::report_assistant_event;
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction};
use rope::Rope;
use settings::{Settings, SettingsStore};
use settings::{update_settings_file, Settings, SettingsStore};
use smol::future::FutureExt;
use std::{
cmp,
@@ -1499,8 +1501,17 @@ impl Render for PromptEditor {
.justify_center()
.gap_2()
.child(
ModelSelector::new(
self.fs.clone(),
LanguageModelSelector::new(
{
let fs = self.fs.clone();
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
}
},
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
@@ -1520,7 +1531,7 @@ impl Render for PromptEditor {
)
}),
)
.with_info_text(
.info_text(
"Inline edits use context\n\
from the currently selected\n\
assistant panel tab.",

File diff suppressed because it is too large Load Diff

View File

@@ -1,608 +0,0 @@
use super::*;
use fs::FakeFs;
use gpui::{AppContext, Context, TestAppContext};
use language::{
language_settings::AllLanguageSettings, Buffer, Language, LanguageConfig, LanguageMatcher,
};
use serde_json::json;
use settings::SettingsStore;
use ui::BorrowAppContext;
use unindent::Unindent as _;
use util::test::{generate_marked_text, marked_text_ranges};
#[gpui::test]
async fn test_patch_store(cx: &mut TestAppContext) {
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(language::init);
cx.update(Project::init_settings);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
"/root",
json!({
"src": {
"lib.rs": "
fn one() -> usize {
1
}
fn two() -> usize {
2
}
fn three() -> usize {
3
}
".unindent(),
"main.rs": "
use crate::one;
fn main() { one(); }
".unindent(),
}
}),
)
.await;
let project = Project::test(fs, [Path::new("/root")], cx).await;
project.update(cx, |project, _| {
project.languages().add(Arc::new(rust_lang()));
});
let patch_store = cx.new_model(|_| PatchStore::new(project.clone()));
let context_buffer = cx.new_model(|cx| Buffer::local("hello", cx));
let context_buffer = context_buffer.read_with(cx, |buffer, _| buffer.snapshot());
let range = context_buffer.anchor_before(0)..context_buffer.anchor_before(1);
let patch_id = patch_store.update(cx, |store, cx| {
store.insert(
AssistantPatch {
range: range.clone(),
title: "first patch".into(),
edits: vec![AssistantEdit {
path: "src/lib.rs".into(),
kind: AssistantEditKind::Update {
old_text: "1".into(),
new_text: "100".into(),
description: None,
},
}]
.into(),
status: AssistantPatchStatus::Pending,
},
cx,
)
});
cx.run_until_parked();
let patch = patch_store
.update(cx, |store, cx| store.resolve_patch(patch_id, cx))
.await
.unwrap();
assert_apply_patch(
&patch,
cx,
&[(
Path::new("src/lib.rs").into(),
"
fn one() -> usize {
100
}
fn two() -> usize {
2
}
fn three() -> usize {
3
}
"
.unindent(),
)],
);
patch_store.update(cx, |store, cx| {
store
.update(
patch_id,
AssistantPatch {
range: range.clone(),
title: "first patch".into(),
edits: vec![
AssistantEdit {
path: "src/lib.rs".into(),
kind: AssistantEditKind::Update {
old_text: "1".into(),
new_text: "100".into(),
description: None,
},
},
AssistantEdit {
path: "src/lib.rs".into(),
kind: AssistantEditKind::Update {
old_text: "3".into(),
new_text: "300".into(),
description: None,
},
},
]
.into(),
status: AssistantPatchStatus::Pending,
},
cx,
)
.unwrap();
});
cx.run_until_parked();
let patch = patch_store
.update(cx, |store, cx| store.resolve_patch(patch_id, cx))
.await
.unwrap();
assert_apply_patch(
&patch,
cx,
&[(
Path::new("src/lib.rs").into(),
"
fn one() -> usize {
100
}
fn two() -> usize {
2
}
fn three() -> usize {
300
}
"
.unindent(),
)],
);
}
#[gpui::test]
fn test_resolve_location(cx: &mut AppContext) {
assert_location_resolution(
concat!(
" Lorem\n",
"« ipsum\n",
" dolor sit amet»\n",
" consecteur",
),
"ipsum\ndolor",
cx,
);
assert_location_resolution(
&"
«fn foo1(a: usize) -> usize {
40
fn foo2(b: usize) -> usize {
42
}
"
.unindent(),
"fn foo1(b: usize) {\n40\n}",
cx,
);
assert_location_resolution(
&"
fn main() {
« Foo
.bar()
.baz()
.qux()»
}
fn foo2(b: usize) -> usize {
42
}
"
.unindent(),
"Foo.bar.baz.qux()",
cx,
);
assert_location_resolution(
&"
class Something {
one() { return 1; }
« two() { return 2222; }
three() { return 333; }
four() { return 4444; }
five() { return 5555; }
six() { return 6666; }
» seven() { return 7; }
eight() { return 8; }
}
"
.unindent(),
&"
two() { return 2222; }
four() { return 4444; }
five() { return 5555; }
six() { return 6666; }
"
.unindent(),
cx,
);
}
#[gpui::test]
async fn test_resolve_edits(cx: &mut TestAppContext) {
cx.update(init_test);
assert_edits(
"
/// A person
struct Person {
name: String,
age: usize,
}
/// A dog
struct Dog {
weight: f32,
}
impl Person {
fn name(&self) -> &str {
&self.name
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "
name: String,
"
.unindent(),
new_text: "
first_name: String,
last_name: String,
"
.unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "
fn name(&self) -> &str {
&self.name
}
"
.unindent(),
new_text: "
fn name(&self) -> String {
format!(\"{} {}\", self.first_name, self.last_name)
}
"
.unindent(),
description: None,
},
],
"
/// A person
struct Person {
first_name: String,
last_name: String,
age: usize,
}
/// A dog
struct Dog {
weight: f32,
}
impl Person {
fn name(&self) -> String {
format!(\"{} {}\", self.first_name, self.last_name)
}
}
"
.unindent(),
cx,
)
.await;
// Ensure InsertBefore merges correctly with Update of the same text
assert_edits(
"
fn foo() {
}
"
.unindent(),
vec![
AssistantEditKind::InsertBefore {
old_text: "
fn foo() {"
.unindent(),
new_text: "
fn bar() {
qux();
}"
.unindent(),
description: Some("implement bar".into()),
},
AssistantEditKind::Update {
old_text: "
fn foo() {
}"
.unindent(),
new_text: "
fn foo() {
bar();
}"
.unindent(),
description: Some("call bar in foo".into()),
},
AssistantEditKind::InsertAfter {
old_text: "
fn foo() {
}
"
.unindent(),
new_text: "
fn qux() {
// todo
}
"
.unindent(),
description: Some("implement qux".into()),
},
],
"
fn bar() {
qux();
}
fn foo() {
bar();
}
fn qux() {
// todo
}
"
.unindent(),
cx,
)
.await;
// Correctly indent new text when replacing multiple adjacent indented blocks.
assert_edits(
"
impl Numbers {
fn one() {
1
}
fn two() {
2
}
fn three() {
3
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "
fn one() {
1
}
"
.unindent(),
new_text: "
fn one() {
101
}
"
.unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "
fn two() {
2
}
"
.unindent(),
new_text: "
fn two() {
102
}
"
.unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "
fn three() {
3
}
"
.unindent(),
new_text: "
fn three() {
103
}
"
.unindent(),
description: None,
},
],
"
impl Numbers {
fn one() {
101
}
fn two() {
102
}
fn three() {
103
}
}
"
.unindent(),
cx,
)
.await;
assert_edits(
"
impl Person {
fn set_name(&mut self, name: String) {
self.name = name;
}
fn name(&self) -> String {
return self.name;
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "self.name = name;".unindent(),
new_text: "self._name = name;".unindent(),
description: None,
},
AssistantEditKind::Update {
old_text: "return self.name;\n".unindent(),
new_text: "return self._name;\n".unindent(),
description: None,
},
],
"
impl Person {
fn set_name(&mut self, name: String) {
self._name = name;
}
fn name(&self) -> String {
return self._name;
}
}
"
.unindent(),
cx,
)
.await;
}
fn init_test(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
language::init(cx);
Project::init_settings(cx);
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, |_| {});
});
}
#[track_caller]
fn assert_apply_patch(
patch: &ResolvedPatch,
cx: &mut TestAppContext,
expected_output: &[(Arc<Path>, String)],
) {
let mut actual_output = Vec::new();
for (buffer, edit_groups) in &patch.edit_groups {
let branch = buffer.update(cx, |buffer, cx| buffer.branch(cx));
cx.update(|cx| {
ResolvedPatch::apply_buffer_edits(&Vec::new(), edit_groups, &branch, cx);
actual_output.push((
buffer.read(cx).file().unwrap().path().clone(),
branch.read(cx).text(),
));
});
}
pretty_assertions::assert_eq!(actual_output, expected_output);
}
#[track_caller]
fn assert_location_resolution(text_with_expected_range: &str, query: &str, cx: &mut AppContext) {
let (text, _) = marked_text_ranges(text_with_expected_range, false);
let buffer = cx.new_model(|cx| Buffer::local(text.clone(), cx));
let snapshot = buffer.read(cx).snapshot();
let range = AssistantEditKind::resolve_location(snapshot.as_rope(), query).to_offset(&snapshot);
let text_with_actual_range = generate_marked_text(&text, &[range], false);
pretty_assertions::assert_eq!(text_with_actual_range, text_with_expected_range);
}
async fn assert_edits(
old_text: String,
edits: Vec<AssistantEditKind>,
new_text: String,
cx: &mut TestAppContext,
) {
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/root", json!({"file.rs": old_text})).await;
let project = Project::test(fs, [Path::new("/root")], cx).await;
project.update(cx, |project, _| {
project.languages().add(Arc::new(rust_lang()));
});
let patch_store = cx.new_model(|_| PatchStore::new(project));
let patch_range = language::Anchor::MIN..language::Anchor::MAX;
let patch_id = patch_store.update(cx, |patch_store, cx| {
patch_store.insert(
AssistantPatch {
range: patch_range.clone(),
title: "test-patch".into(),
edits: edits
.into_iter()
.map(|kind| AssistantEdit {
path: "file.rs".into(),
kind,
})
.collect(),
status: AssistantPatchStatus::Ready,
},
cx,
)
});
cx.run_until_parked();
let patch = patch_store
.update(cx, |patch_store, cx| {
patch_store.resolve_patch(patch_id, cx)
})
.await
.unwrap();
let (buffer, edit_groups) = patch.edit_groups.into_iter().next().unwrap();
cx.update(|cx| ResolvedPatch::apply_buffer_edits(&Vec::new(), &edit_groups, &buffer, cx));
let actual_new_text = buffer.read_with(cx, |buffer, _| buffer.text());
pretty_assertions::assert_eq!(actual_new_text, new_text);
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
..Default::default()
},
Some(language::tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(
r#"
(call_expression) @indent
(field_expression) @indent
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
"#,
)
.unwrap()
}

View File

@@ -4,7 +4,7 @@ use assistant_slash_command::{
SlashCommandOutputSection, SlashCommandResult,
};
use collections::HashMap;
use context_servers::{
use context_server::{
manager::{ContextServer, ContextServerManager},
types::Prompt,
};
@@ -95,9 +95,9 @@ impl SlashCommand for ContextServerSlashCommand {
let completion_result = protocol
.completion(
context_servers::types::CompletionReference::Prompt(
context_servers::types::PromptReference {
r#type: context_servers::types::PromptReferenceType::Prompt,
context_server::types::CompletionReference::Prompt(
context_server::types::PromptReference {
r#type: context_server::types::PromptReferenceType::Prompt,
name: prompt_name,
},
),
@@ -152,7 +152,7 @@ impl SlashCommand for ContextServerSlashCommand {
if result
.messages
.iter()
.any(|msg| !matches!(msg.role, context_servers::types::Role::User))
.any(|msg| !matches!(msg.role, context_server::types::Role::User))
{
return Err(anyhow!(
"Prompt contains non-user roles, which is not supported"
@@ -164,7 +164,7 @@ impl SlashCommand for ContextServerSlashCommand {
.messages
.into_iter()
.filter_map(|msg| match msg.content {
context_servers::types::MessageContent::Text { text } => Some(text),
context_server::types::MessageContent::Text { text } => Some(text),
_ => None,
})
.collect::<Vec<String>>()

View File

@@ -108,6 +108,10 @@ impl SlashCommand for FetchSlashCommand {
"Insert fetched URL contents".into()
}
fn icon(&self) -> IconName {
IconName::Globe
}
fn menu_text(&self) -> String {
self.description()
}
@@ -162,7 +166,7 @@ impl SlashCommand for FetchSlashCommand {
text,
sections: vec![SlashCommandOutputSection {
range,
icon: IconName::AtSign,
icon: IconName::Globe,
label: format!("fetch {}", url).into(),
metadata: None,
}],

View File

@@ -1,6 +1,7 @@
use crate::assistant_settings::AssistantSettings;
use crate::{
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent,
ModelSelector, RequestType, DEFAULT_CONTEXT_LINES,
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType,
DEFAULT_CONTEXT_LINES,
};
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
@@ -17,10 +18,11 @@ use gpui::{
};
use language::Buffer;
use language_model::{
logging::report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role,
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use settings::Settings;
use language_model_selector::LanguageModelSelector;
use language_models::report_assistant_event;
use settings::{update_settings_file, Settings};
use std::{
cmp,
sync::Arc,
@@ -30,7 +32,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{prelude::*, IconButtonShape, Tooltip};
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};
use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -612,8 +614,17 @@ impl Render for PromptEditor {
.w_12()
.justify_center()
.gap_2()
.child(ModelSelector::new(
self.fs.clone(),
.child(LanguageModelSelector::new(
{
let fs = self.fs.clone();
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
}
},
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
@@ -693,7 +704,7 @@ impl PromptEditor {
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text("Add a prompt…", cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor
});
@@ -726,6 +737,14 @@ impl PromptEditor {
this
}
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} • ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions

View File

@@ -1,2 +0,0 @@
pub mod context_server_tool;
pub mod now_tool;

View File

@@ -0,0 +1,39 @@
[package]
name = "assistant2"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant.rs"
doctest = false
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
context_server.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
language_model.workspace = true
language_model_selector.workspace = true
language_models.workspace = true
log.workspace = true
project.workspace = true
proto.workspace = true
settings.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -0,0 +1,46 @@
mod assistant_panel;
mod message_editor;
mod thread;
mod thread_store;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
use gpui::{actions, AppContext};
pub use crate::assistant_panel::AssistantPanel;
actions!(
assistant2,
[ToggleFocus, NewThread, ToggleModelSelector, Chat]
);
const NAMESPACE: &str = "assistant2";
/// Initializes the `assistant2` crate.
pub fn init(cx: &mut AppContext) {
assistant_panel::init(cx);
feature_gate_assistant2_actions(cx);
}
fn feature_gate_assistant2_actions(cx: &mut AppContext) {
const ASSISTANT1_NAMESPACE: &str = "assistant";
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE);
});
cx.observe_flag::<Assistant2FeatureFlag, _>(move |is_enabled, cx| {
if is_enabled {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.show_namespace(NAMESPACE);
filter.hide_namespace(ASSISTANT1_NAMESPACE);
});
} else {
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(NAMESPACE);
filter.show_namespace(ASSISTANT1_NAMESPACE);
});
}
})
.detach();
}

View File

@@ -0,0 +1,512 @@
use std::sync::Arc;
use anyhow::Result;
use assistant_tool::ToolWorkingSet;
use client::zed_urls;
use gpui::{
prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter, FocusHandle,
FocusableView, FontWeight, Model, Pixels, Subscription, Task, View, ViewContext, WeakView,
WindowContext,
};
use language_model::{LanguageModelRegistry, Role};
use language_model_selector::LanguageModelSelector;
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip};
use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use crate::message_editor::MessageEditor;
use crate::thread::{Message, Thread, ThreadError, ThreadEvent};
use crate::thread_store::ThreadStore;
use crate::{NewThread, ToggleFocus, ToggleModelSelector};
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
});
},
)
.detach();
}
pub struct AssistantPanel {
workspace: WeakView<Workspace>,
#[allow(unused)]
thread_store: Model<ThreadStore>,
thread: Model<Thread>,
message_editor: View<MessageEditor>,
tools: Arc<ToolWorkingSet>,
last_error: Option<ThreadError>,
_subscriptions: Vec<Subscription>,
}
impl AssistantPanel {
pub fn load(
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move {
let tools = Arc::new(ToolWorkingSet::default());
let thread_store = workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
ThreadStore::new(project, tools.clone(), cx)
})?
.await?;
workspace.update(&mut cx, |workspace, cx| {
cx.new_view(|cx| Self::new(workspace, thread_store, tools, cx))
})
})
}
fn new(
workspace: &Workspace,
thread_store: Model<ThreadStore>,
tools: Arc<ToolWorkingSet>,
cx: &mut ViewContext<Self>,
) -> Self {
let thread = cx.new_model(|cx| Thread::new(tools.clone(), cx));
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
cx.subscribe(&thread, Self::handle_thread_event),
];
Self {
workspace: workspace.weak_handle(),
thread_store,
thread: thread.clone(),
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
tools,
last_error: None,
_subscriptions: subscriptions,
}
}
fn new_thread(&mut self, cx: &mut ViewContext<Self>) {
let tools = self.thread.read(cx).tools().clone();
let thread = cx.new_model(|cx| Thread::new(tools, cx));
let subscriptions = vec![
cx.observe(&thread, |_, _, cx| cx.notify()),
cx.subscribe(&thread, Self::handle_thread_event),
];
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread.clone(), cx));
self.thread = thread;
self._subscriptions = subscriptions;
self.message_editor.focus_handle(cx).focus(cx);
}
fn handle_thread_event(
&mut self,
_: Model<Thread>,
event: &ThreadEvent,
cx: &mut ViewContext<Self>,
) {
match event {
ThreadEvent::ShowError(error) => {
self.last_error = Some(error.clone());
}
ThreadEvent::StreamedCompletion => {}
ThreadEvent::UsePendingTools => {
let pending_tool_uses = self
.thread
.read(cx)
.pending_tool_uses()
.into_iter()
.filter(|tool_use| tool_use.status.is_idle())
.cloned()
.collect::<Vec<_>>();
for tool_use in pending_tool_uses {
if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
let task = tool.run(tool_use.input, self.workspace.clone(), cx);
self.thread.update(cx, |thread, cx| {
thread.insert_tool_output(
tool_use.assistant_message_id,
tool_use.id.clone(),
task,
cx,
);
});
}
}
}
ThreadEvent::ToolFinished { .. } => {}
}
}
}
impl FocusableView for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.message_editor.focus_handle(cx)
}
}
impl EventEmitter<PanelEvent> for AssistantPanel {}
impl Panel for AssistantPanel {
fn persistent_name() -> &'static str {
"AssistantPanel2"
}
fn position(&self, _cx: &WindowContext) -> DockPosition {
DockPosition::Right
}
fn position_is_valid(&self, _: DockPosition) -> bool {
true
}
fn set_position(&mut self, _position: DockPosition, _cx: &mut ViewContext<Self>) {}
fn size(&self, _cx: &WindowContext) -> Pixels {
px(640.)
}
fn set_size(&mut self, _size: Option<Pixels>, _cx: &mut ViewContext<Self>) {}
fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
fn remote_id() -> Option<proto::PanelId> {
Some(proto::PanelId::AssistantPanel)
}
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
Some(IconName::ZedAssistant)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
Some("Assistant Panel")
}
fn toggle_action(&self) -> Box<dyn Action> {
Box::new(ToggleFocus)
}
}
impl AssistantPanel {
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
h_flex()
.id("assistant-toolbar")
.justify_between()
.gap(DynamicSpacing::Base08.rems(cx))
.h(Tab::container_height(cx))
.px(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(h_flex().child(Label::new("Thread Title Goes Here")))
.child(
h_flex()
.gap(DynamicSpacing::Base08.rems(cx))
.child(self.render_language_model_selector(cx))
.child(Divider::vertical())
.child(
IconButton::new("new-thread", IconName::Plus)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"New Thread",
&NewThread,
&focus_handle,
cx,
)
}
})
.on_click(move |_event, _cx| {
println!("New Thread");
}),
)
.child(
IconButton::new("open-history", IconName::HistoryRerun)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Open History", cx))
.on_click(move |_event, _cx| {
println!("Open History");
}),
)
.child(
IconButton::new("configure-assistant", IconName::Settings)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| Tooltip::text("Configure Assistant", cx))
.on_click(move |_event, _cx| {
println!("Configure Assistant");
}),
),
)
}
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
let active_model = LanguageModelRegistry::read_global(cx).active_model();
LanguageModelSelector::new(
|model, _cx| {
println!("Selected {:?}", model.name());
},
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match (active_provider, active_model) {
(Some(provider), Some(model)) => h_flex()
.gap_1()
.child(
Icon::new(
model.icon().unwrap_or_else(|| provider.icon()),
)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)),
)
}
fn render_message(&self, message: Message, cx: &mut ViewContext<Self>) -> impl IntoElement {
let (role_icon, role_name) = match message.role {
Role::User => (IconName::Person, "You"),
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
Role::System => (IconName::Settings, "System"),
};
v_flex()
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_md()
.child(
h_flex()
.justify_between()
.p_1p5()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(
h_flex()
.gap_2()
.child(Icon::new(role_icon).size(IconSize::Small))
.child(Label::new(role_name).size(LabelSize::Small)),
),
)
.child(v_flex().p_1p5().child(Label::new(message.text.clone())))
}
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?;
Some(
div()
.absolute()
.right_3()
.bottom_12()
.max_w_96()
.py_2()
.px_3()
.elevation_2(cx)
.occlude()
.child(match last_error {
ThreadError::PaymentRequired => self.render_payment_required_error(cx),
ThreadError::MaxMonthlySpendReached => {
self.render_max_monthly_spend_reached_error(cx)
}
ThreadError::Message(error_message) => {
self.render_error_message(error_message, cx)
}
})
.into_any(),
)
}
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
)
.child(
div()
.id("error-message")
.max_h_24()
.overflow_y_scroll()
.child(Label::new(ERROR_MESSAGE)),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.open_url(&zed_urls::account_url(cx));
cx.notify();
},
)))
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
}
fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
)
.child(
div()
.id("error-message")
.max_h_24()
.overflow_y_scroll()
.child(Label::new(ERROR_MESSAGE)),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
cx.listener(|this, _, cx| {
this.last_error = None;
cx.open_url(&zed_urls::account_url(cx));
cx.notify();
}),
),
)
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
}
fn render_error_message(
&self,
error_message: &SharedString,
cx: &mut ViewContext<Self>,
) -> AnyElement {
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::XCircle).color(Color::Error))
.child(
Label::new("Error interacting with language model")
.weight(FontWeight::MEDIUM),
),
)
.child(
div()
.id("error-message")
.max_h_32()
.overflow_y_scroll()
.child(Label::new(error_message.clone())),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
}
}
impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let messages = self.thread.read(cx).messages().cloned().collect::<Vec<_>>();
v_flex()
.key_context("AssistantPanel2")
.justify_between()
.size_full()
.on_action(cx.listener(|this, _: &NewThread, cx| {
this.new_thread(cx);
}))
.child(self.render_toolbar(cx))
.child(
v_flex()
.id("message-list")
.gap_2()
.size_full()
.p_2()
.overflow_y_scroll()
.bg(cx.theme().colors().panel_background)
.children(
messages
.into_iter()
.map(|message| self.render_message(message, cx)),
),
)
.child(
h_flex()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(self.message_editor.clone()),
)
.children(self.render_last_error(cx))
}
}

View File

@@ -0,0 +1,167 @@
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{AppContext, FocusableView, Model, TextStyle, View};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use settings::Settings;
use theme::ThemeSettings;
use ui::{prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding};
use crate::thread::{RequestKind, Thread};
use crate::Chat;
pub struct MessageEditor {
thread: Model<Thread>,
editor: View<Editor>,
use_tools: bool,
}
impl MessageEditor {
pub fn new(thread: Model<Thread>, cx: &mut ViewContext<Self>) -> Self {
Self {
thread,
editor: cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
editor.set_placeholder_text("Ask anything…", cx);
editor
}),
use_tools: false,
}
}
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx);
}
fn send_to_model(
&mut self,
request_kind: RequestKind,
cx: &mut ViewContext<Self>,
) -> Option<()> {
let provider = LanguageModelRegistry::read_global(cx).active_provider();
if provider
.as_ref()
.map_or(false, |provider| provider.must_accept_terms(cx))
{
cx.notify();
return None;
}
let model_registry = LanguageModelRegistry::read_global(cx);
let model = model_registry.active_model()?;
let user_message = self.editor.update(cx, |editor, cx| {
let text = editor.text(cx);
editor.clear(cx);
text
});
self.thread.update(cx, |thread, cx| {
thread.insert_user_message(user_message);
let mut request = thread.to_completion_request(request_kind, cx);
if self.use_tools {
request.tools = thread
.tools()
.tools(cx)
.into_iter()
.map(|tool| LanguageModelRequestTool {
name: tool.name(),
description: tool.description(),
input_schema: tool.input_schema(),
})
.collect();
}
thread.stream_completion(request, model, cx)
});
None
}
}
impl FocusableView for MessageEditor {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
self.editor.focus_handle(cx)
}
}
impl Render for MessageEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
let focus_handle = self.editor.focus_handle(cx);
v_flex()
.key_context("MessageEditor")
.on_action(cx.listener(Self::chat))
.size_full()
.gap_2()
.p_2()
.bg(cx.theme().colors().editor_background)
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
EditorElement::new(
&self.editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
})
.child(
h_flex()
.justify_between()
.child(
h_flex()
.child(
Button::new("add-context", "Add Context")
.style(ButtonStyle::Filled)
.icon(IconName::Plus)
.icon_position(IconPosition::Start),
)
.child(CheckboxWithLabel::new(
"use-tools",
Label::new("Tools"),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
Selection::Selected => true,
Selection::Unselected | Selection::Indeterminate => false,
};
}),
)),
)
.child(
h_flex()
.gap_2()
.child(Button::new("codebase", "Codebase").style(ButtonStyle::Filled))
.child(Label::new("or"))
.child(
ButtonLike::new("chat")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Chat"))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
}),
),
),
)
}
}

View File

@@ -0,0 +1,354 @@
use std::sync::Arc;
use anyhow::Result;
use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
use language_model::{
LanguageModel, LanguageModelCompletionEvent, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelToolResult, LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
StopReason,
};
use language_models::provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError};
use serde::{Deserialize, Serialize};
use util::post_inc;
#[derive(Debug, Clone, Copy)]
pub enum RequestKind {
Chat,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct MessageId(usize);
impl MessageId {
fn post_inc(&mut self) -> Self {
Self(post_inc(&mut self.0))
}
}
/// A message in a [`Thread`].
#[derive(Debug, Clone)]
pub struct Message {
pub id: MessageId,
pub role: Role,
pub text: String,
}
/// A thread of conversation with the LLM.
pub struct Thread {
messages: Vec<Message>,
next_message_id: MessageId,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
tools: Arc<ToolWorkingSet>,
tool_uses_by_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
tool_results_by_message: HashMap<MessageId, Vec<LanguageModelToolResult>>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
}
impl Thread {
pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut ModelContext<Self>) -> Self {
Self {
messages: Vec::new(),
next_message_id: MessageId(0),
completion_count: 0,
pending_completions: Vec::new(),
tools,
tool_uses_by_message: HashMap::default(),
tool_results_by_message: HashMap::default(),
pending_tool_uses_by_id: HashMap::default(),
}
}
pub fn messages(&self) -> impl Iterator<Item = &Message> {
self.messages.iter()
}
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
&self.tools
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect()
}
pub fn insert_user_message(&mut self, text: impl Into<String>) {
self.messages.push(Message {
id: self.next_message_id.post_inc(),
role: Role::User,
text: text.into(),
});
}
pub fn to_completion_request(
&self,
_request_kind: RequestKind,
_cx: &AppContext,
) -> LanguageModelRequest {
let mut request = LanguageModelRequest {
messages: vec![],
tools: Vec::new(),
stop: Vec::new(),
temperature: None,
};
for message in &self.messages {
let mut request_message = LanguageModelRequestMessage {
role: message.role,
content: Vec::new(),
cache: false,
};
if let Some(tool_results) = self.tool_results_by_message.get(&message.id) {
for tool_result in tool_results {
request_message
.content
.push(MessageContent::ToolResult(tool_result.clone()));
}
}
if !message.text.is_empty() {
request_message
.content
.push(MessageContent::Text(message.text.clone()));
}
if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) {
for tool_use in tool_uses {
request_message
.content
.push(MessageContent::ToolUse(tool_use.clone()));
}
}
request.messages.push(request_message);
}
request
}
pub fn stream_completion(
&mut self,
request: LanguageModelRequest,
model: Arc<dyn LanguageModel>,
cx: &mut ModelContext<Self>,
) {
let pending_completion_id = post_inc(&mut self.completion_count);
let task = cx.spawn(|thread, mut cx| async move {
let stream = model.stream_completion(request, &cx);
let stream_completion = async {
let mut events = stream.await?;
let mut stop_reason = StopReason::EndTurn;
while let Some(event) = events.next().await {
let event = event?;
thread.update(&mut cx, |thread, cx| {
match event {
LanguageModelCompletionEvent::StartMessage { .. } => {
thread.messages.push(Message {
id: thread.next_message_id.post_inc(),
role: Role::Assistant,
text: String::new(),
});
}
LanguageModelCompletionEvent::Stop(reason) => {
stop_reason = reason;
}
LanguageModelCompletionEvent::Text(chunk) => {
if let Some(last_message) = thread.messages.last_mut() {
if last_message.role == Role::Assistant {
last_message.text.push_str(&chunk);
}
}
}
LanguageModelCompletionEvent::ToolUse(tool_use) => {
if let Some(last_assistant_message) = thread
.messages
.iter()
.rfind(|message| message.role == Role::Assistant)
{
thread
.tool_uses_by_message
.entry(last_assistant_message.id)
.or_default()
.push(tool_use.clone());
thread.pending_tool_uses_by_id.insert(
tool_use.id.clone(),
PendingToolUse {
assistant_message_id: last_assistant_message.id,
id: tool_use.id,
name: tool_use.name,
input: tool_use.input,
status: PendingToolUseStatus::Idle,
},
);
}
}
}
cx.emit(ThreadEvent::StreamedCompletion);
cx.notify();
})?;
smol::future::yield_now().await;
}
thread.update(&mut cx, |thread, _cx| {
thread
.pending_completions
.retain(|completion| completion.id != pending_completion_id);
})?;
anyhow::Ok(stop_reason)
};
let result = stream_completion.await;
thread
.update(&mut cx, |_thread, cx| match result.as_ref() {
Ok(stop_reason) => match stop_reason {
StopReason::ToolUse => {
cx.emit(ThreadEvent::UsePendingTools);
}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
},
Err(error) => {
if error.is::<PaymentRequiredError>() {
cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
} else if error.is::<MaxMonthlySpendReachedError>() {
cx.emit(ThreadEvent::ShowError(ThreadError::MaxMonthlySpendReached));
} else {
let error_message = error
.chain()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(ThreadEvent::ShowError(ThreadError::Message(
SharedString::from(error_message.clone()),
)));
}
}
})
.ok();
});
self.pending_completions.push(PendingCompletion {
id: pending_completion_id,
_task: task,
});
}
pub fn insert_tool_output(
&mut self,
assistant_message_id: MessageId,
tool_use_id: LanguageModelToolUseId,
output: Task<Result<String>>,
cx: &mut ModelContext<Self>,
) {
let insert_output_task = cx.spawn(|thread, mut cx| {
let tool_use_id = tool_use_id.clone();
async move {
let output = output.await;
thread
.update(&mut cx, |thread, cx| {
// The tool use was requested by an Assistant message,
// so we want to attach the tool results to the next
// user message.
let next_user_message = MessageId(assistant_message_id.0 + 1);
let tool_results = thread
.tool_results_by_message
.entry(next_user_message)
.or_default();
match output {
Ok(output) => {
tool_results.push(LanguageModelToolResult {
tool_use_id: tool_use_id.to_string(),
content: output,
is_error: false,
});
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
}
Err(err) => {
tool_results.push(LanguageModelToolResult {
tool_use_id: tool_use_id.to_string(),
content: err.to_string(),
is_error: true,
});
if let Some(tool_use) =
thread.pending_tool_uses_by_id.get_mut(&tool_use_id)
{
tool_use.status = PendingToolUseStatus::Error(err.to_string());
}
}
}
})
.ok();
}
});
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
tool_use.status = PendingToolUseStatus::Running {
_task: insert_output_task.shared(),
};
}
}
}
#[derive(Debug, Clone)]
pub enum ThreadError {
PaymentRequired,
MaxMonthlySpendReached,
Message(SharedString),
}
#[derive(Debug, Clone)]
pub enum ThreadEvent {
ShowError(ThreadError),
StreamedCompletion,
UsePendingTools,
ToolFinished {
#[allow(unused)]
tool_use_id: LanguageModelToolUseId,
},
}
impl EventEmitter<ThreadEvent> for Thread {}
struct PendingCompletion {
id: usize,
_task: Task<()>,
}
#[derive(Debug, Clone)]
pub struct PendingToolUse {
pub id: LanguageModelToolUseId,
/// The ID of the Assistant message in which the tool use was requested.
pub assistant_message_id: MessageId,
pub name: String,
pub input: serde_json::Value,
pub status: PendingToolUseStatus,
}
#[derive(Debug, Clone)]
pub enum PendingToolUseStatus {
Idle,
Running { _task: Shared<Task<()>> },
Error(#[allow(unused)] String),
}
impl PendingToolUseStatus {
pub fn is_idle(&self) -> bool {
matches!(self, PendingToolUseStatus::Idle)
}
}

View File

@@ -0,0 +1,114 @@
use std::sync::Arc;
use anyhow::Result;
use assistant_tool::{ToolId, ToolWorkingSet};
use collections::HashMap;
use context_server::manager::ContextServerManager;
use context_server::{ContextServerFactoryRegistry, ContextServerTool};
use gpui::{prelude::*, AppContext, Model, ModelContext, Task};
use project::Project;
use util::ResultExt as _;
pub struct ThreadStore {
#[allow(unused)]
project: Model<Project>,
tools: Arc<ToolWorkingSet>,
context_server_manager: Model<ContextServerManager>,
context_server_tool_ids: HashMap<Arc<str>, Vec<ToolId>>,
}
impl ThreadStore {
pub fn new(
project: Model<Project>,
tools: Arc<ToolWorkingSet>,
cx: &mut AppContext,
) -> Task<Result<Model<Self>>> {
cx.spawn(|mut cx| async move {
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 this = Self {
project,
tools,
context_server_manager,
context_server_tool_ids: HashMap::default(),
};
this.register_context_server_handlers(cx);
this
})?;
Ok(this)
})
}
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_server::manager::Event,
cx: &mut ModelContext<Self>,
) {
let tool_working_set = self.tools.clone();
match event {
context_server::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_server::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(
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_server::manager::Event::ServerStopped { server_id } => {
if let Some(tool_ids) = self.context_server_tool_ids.remove(server_id) {
tool_working_set.remove(&tool_ids);
}
}
}
}
}

View File

@@ -18,6 +18,7 @@ use workspace::{ui::IconName, Workspace};
pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
extension_slash_command::init(cx);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]

View File

@@ -3,17 +3,39 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::Result;
use async_trait::async_trait;
use extension::{Extension, WorktreeDelegate};
use gpui::{Task, WeakView, WindowContext};
use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate};
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
use crate::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
SlashCommandRegistry, SlashCommandResult,
};
pub fn init(cx: &mut AppContext) {
let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_slash_command_proxy(SlashCommandRegistryProxy {
slash_command_registry: SlashCommandRegistry::global(cx),
});
}
struct SlashCommandRegistryProxy {
slash_command_registry: Arc<SlashCommandRegistry>,
}
impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
fn register_slash_command(
&self,
extension: Arc<dyn Extension>,
command: extension::SlashCommand,
) {
self.slash_command_registry
.register_command(ExtensionSlashCommand::new(extension, command), false)
}
}
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
struct WorktreeDelegateAdapter(Arc<dyn LspAdapterDelegate>);

View File

@@ -1,4 +1,5 @@
mod tool_registry;
mod tool_working_set;
use std::sync::Arc;
@@ -6,7 +7,8 @@ use anyhow::Result;
use gpui::{AppContext, Task, WeakView, WindowContext};
use workspace::Workspace;
pub use tool_registry::*;
pub use crate::tool_registry::*;
pub use crate::tool_working_set::*;
pub fn init(cx: &mut AppContext) {
ToolRegistry::default_global(cx);

View File

@@ -1,8 +1,10 @@
use assistant_tool::{Tool, ToolRegistry};
use std::sync::Arc;
use collections::HashMap;
use gpui::AppContext;
use parking_lot::Mutex;
use std::sync::Arc;
use crate::{Tool, ToolRegistry};
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct ToolId(usize);

View File

@@ -0,0 +1,22 @@
[package]
name = "assistant_tools"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/assistant_tools.rs"
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
gpui.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
workspace.workspace = true

View File

@@ -0,0 +1,13 @@
mod now_tool;
use assistant_tool::ToolRegistry;
use gpui::AppContext;
use crate::now_tool::NowTool;
pub fn init(cx: &mut AppContext) {
assistant_tool::init(cx);
let registry = ToolRegistry::global(cx);
registry.register_tool(NowTool);
}

View File

@@ -30,7 +30,7 @@ impl Tool for NowTool {
}
fn description(&self) -> String {
"Returns the current datetime in RFC 3339 format.".into()
"Returns the current datetime in RFC 3339 format. Only use this tool when the user specifically asks for it or the current task would benefit from knowing the current datetime.".into()
}
fn input_schema(&self) -> serde_json::Value {

View File

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

View File

@@ -16,21 +16,16 @@ doctest = false
anyhow.workspace = true
client.workspace = true
db.workspace = true
editor.workspace = true
gpui.workspace = true
http_client.workspace = true
log.workspace = true
markdown_preview.workspace = true
menu.workspace = true
paths.workspace = true
release_channel.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
tempfile.workspace = true
util.workspace = true
which.workspace = true
workspace.workspace = true

View File

@@ -1,27 +1,19 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, TelemetrySettings};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use editor::{Editor, MultiBuffer};
use gpui::{
actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext,
SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext,
SemanticVersion, Task, WindowContext,
};
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use paths::remote_servers_dir;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use smol::{fs, io::AsyncReadExt};
use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs::File, process::Command};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use paths::remote_servers_dir;
use release_channel::{AppCommitSha, ReleaseChannel};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs, io::AsyncReadExt};
use smol::{fs::File, process::Command};
use std::{
env::{
self,
@@ -32,24 +24,13 @@ use std::{
sync::Arc,
time::Duration,
};
use update_notification::UpdateNotification;
use util::ResultExt;
use which::which;
use workspace::notifications::NotificationId;
use workspace::Workspace;
const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
actions!(
auto_update,
[
Check,
DismissErrorMessage,
ViewReleaseNotes,
ViewReleaseNotesLocally
]
);
actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes,]);
#[derive(Serialize)]
struct UpdateRequestBody {
@@ -146,12 +127,6 @@ struct GlobalAutoUpdate(Option<Model<AutoUpdater>>);
impl Global for GlobalAutoUpdate {}
#[derive(Deserialize)]
struct ReleaseNotesBody {
title: String,
release_notes: String,
}
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
AutoUpdateSetting::register(cx);
@@ -161,10 +136,6 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
workspace.register_action(|_, action, cx| {
view_release_notes(action, cx);
});
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
view_release_notes_locally(workspace, cx);
});
})
.detach();
@@ -264,121 +235,6 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
None
}
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let url = match release_channel {
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
_ => None,
};
if let Some(url) = url {
cx.open_url(url);
return;
}
let version = AppVersion::global(cx).to_string();
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(
"/api/release_notes/v2/{}/{}",
release_channel.dev_name(),
version
));
let markdown = workspace
.app_state()
.languages
.language_for_name("Markdown");
workspace
.with_local_workspace(cx, move |_, cx| {
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else {
return;
};
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await.ok();
let body: serde_json::Result<ReleaseNotesBody> =
serde_json::from_slice(body.as_slice());
if let Ok(body) = body {
workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx)
});
let language_registry = project.read(cx).languages().clone();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx)
});
let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
language_registry,
Some(tab_description),
cx,
);
workspace.add_item_to_active_pane(
Box::new(view.clone()),
None,
true,
cx,
);
cx.notify();
})
.log_err();
}
})
.detach();
})
.detach();
}
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version;
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn(|workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
let workspace_handle = workspace.weak_handle();
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
);
updater.update(cx, |updater, cx| {
updater
.set_should_show_update_notification(false, cx)
.detach_and_log_err(cx);
});
})?;
}
anyhow::Ok(())
})
.detach();
None
}
impl AutoUpdater {
pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
cx.default_global::<GlobalAutoUpdate>().0.clone()
@@ -423,6 +279,10 @@ impl AutoUpdater {
}));
}
pub fn current_version(&self) -> SemanticVersion {
self.current_version
}
pub fn status(&self) -> AutoUpdateStatus {
self.status.clone()
}
@@ -646,7 +506,7 @@ impl AutoUpdater {
Ok(())
}
fn set_should_show_update_notification(
pub fn set_should_show_update_notification(
&self,
should_show: bool,
cx: &AppContext,
@@ -668,7 +528,7 @@ impl AutoUpdater {
})
}
fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
pub fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
cx.background_executor().spawn(async move {
Ok(KEY_VALUE_STORE
.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?

View File

@@ -0,0 +1,28 @@
[package]
name = "auto_update_ui"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/auto_update_ui.rs"
[dependencies]
anyhow.workspace = true
auto_update.workspace = true
client.workspace = true
editor.workspace = true
gpui.workspace = true
http_client.workspace = true
markdown_preview.workspace = true
menu.workspace = true
release_channel.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
util.workspace = true
workspace.workspace = true

View File

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

View File

@@ -0,0 +1,147 @@
mod update_notification;
use auto_update::AutoUpdater;
use editor::{Editor, MultiBuffer};
use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext};
use http_client::HttpClient;
use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView};
use release_channel::{AppVersion, ReleaseChannel};
use serde::Deserialize;
use smol::io::AsyncReadExt;
use util::ResultExt as _;
use workspace::notifications::NotificationId;
use workspace::Workspace;
use crate::update_notification::UpdateNotification;
actions!(auto_update, [ViewReleaseNotesLocally]);
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| {
view_release_notes_locally(workspace, cx);
});
})
.detach();
}
#[derive(Deserialize)]
struct ReleaseNotesBody {
title: String,
release_notes: String,
}
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let url = match release_channel {
ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"),
ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"),
_ => None,
};
if let Some(url) = url {
cx.open_url(url);
return;
}
let version = AppVersion::global(cx).to_string();
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(
"/api/release_notes/v2/{}/{}",
release_channel.dev_name(),
version
));
let markdown = workspace
.app_state()
.languages
.language_for_name("Markdown");
workspace
.with_local_workspace(cx, move |_, cx| {
cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err();
let response = client.get(&url, Default::default(), true).await;
let Some(mut response) = response.log_err() else {
return;
};
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await.ok();
let body: serde_json::Result<ReleaseNotesBody> =
serde_json::from_slice(body.as_slice());
if let Ok(body) = body {
workspace
.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone();
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("", markdown, cx)
});
buffer.update(cx, |buffer, cx| {
buffer.edit([(0..0, body.release_notes)], None, cx)
});
let language_registry = project.read(cx).languages().clone();
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
let tab_description = SharedString::from(body.title.to_string());
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), true, cx)
});
let workspace_handle = workspace.weak_handle();
let view: View<MarkdownPreviewView> = MarkdownPreviewView::new(
MarkdownPreviewMode::Default,
editor,
workspace_handle,
language_registry,
Some(tab_description),
cx,
);
workspace.add_item_to_active_pane(
Box::new(view.clone()),
None,
true,
cx,
);
cx.notify();
})
.log_err();
}
})
.detach();
})
.detach();
}
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
let updater = AutoUpdater::get(cx)?;
let version = updater.read(cx).current_version();
let should_show_notification = updater.read(cx).should_show_update_notification(cx);
cx.spawn(|workspace, mut cx| async move {
let should_show_notification = should_show_notification.await?;
if should_show_notification {
workspace.update(&mut cx, |workspace, cx| {
let workspace_handle = workspace.weak_handle();
workspace.show_notification(
NotificationId::unique::<UpdateNotification>(),
cx,
|cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)),
);
updater.update(cx, |updater, cx| {
updater
.set_should_show_update_notification(false, cx)
.detach_and_log_err(cx);
});
})?;
}
anyhow::Ok(())
})
.detach();
None
}

View File

@@ -16,11 +16,15 @@ doctest = false
name = "cli"
path = "src/main.rs"
[features]
no-bundled-uninstall = []
default = []
[dependencies]
anyhow.workspace = true
clap.workspace = true
collections.workspace = true
ipc-channel = "0.18"
ipc-channel = "0.19"
once_cell.workspace = true
parking_lot.workspace = true
paths.workspace = true

5
crates/cli/build.rs Normal file
View File

@@ -0,0 +1,5 @@
fn main() {
if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() {
println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#);
}
}

View File

@@ -59,6 +59,13 @@ struct Args {
/// Run zed in dev-server mode
#[arg(long)]
dev_server_token: Option<String>,
/// Uninstall Zed from user system
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
not(feature = "no-bundled-uninstall")
))]
#[arg(long)]
uninstall: bool,
}
fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
@@ -119,6 +126,29 @@ fn main() -> Result<()> {
return Ok(());
}
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
not(feature = "no-bundled-uninstall")
))]
if args.uninstall {
static UNINSTALL_SCRIPT: &[u8] = include_bytes!("../../../script/uninstall.sh");
let tmp_dir = tempfile::tempdir()?;
let script_path = tmp_dir.path().join("uninstall.sh");
fs::write(&script_path, UNINSTALL_SCRIPT)?;
use std::os::unix::fs::PermissionsExt as _;
fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?;
let status = std::process::Command::new("sh")
.arg(&script_path)
.env("ZED_CHANNEL", &*release_channel::RELEASE_CHANNEL_NAME)
.status()
.context("Failed to execute uninstall script")?;
std::process::exit(status.code().unwrap_or(1));
}
let (server, server_name) =
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
let url = format!("zed-cli://{server_name}");

View File

@@ -1067,6 +1067,8 @@ impl Client {
let proxy = http.proxy().cloned();
let credentials = credentials.clone();
let rpc_url = self.rpc_url(http, release_channel);
let system_id = self.telemetry.system_id();
let metrics_id = self.telemetry.metrics_id();
cx.background_executor().spawn(async move {
use HttpOrHttps::*;
@@ -1118,6 +1120,12 @@ impl Client {
"x-zed-release-channel",
HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?,
);
if let Some(system_id) = system_id {
request_headers.insert("x-zed-system-id", HeaderValue::from_str(&system_id)?);
}
if let Some(metrics_id) = metrics_id {
request_headers.insert("x-zed-metrics-id", HeaderValue::from_str(&metrics_id)?);
}
match url_scheme {
Https => {

View File

@@ -224,6 +224,8 @@ impl Telemetry {
cx.background_executor()
.spawn({
let state = state.clone();
let os_version = os_version();
state.lock().os_version = Some(os_version.clone());
async move {
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
state.lock().log_file = Some(tempfile);
@@ -531,6 +533,10 @@ impl Telemetry {
self.state.lock().metrics_id.clone()
}
pub fn system_id(self: &Arc<Self>) -> Option<Arc<str>> {
self.state.lock().system_id.clone()
}
pub fn installation_id(self: &Arc<Self>) -> Option<Arc<str>> {
self.state.lock().installation_id.clone()
}

View File

@@ -79,7 +79,8 @@ uuid.workspace = true
[dev-dependencies]
assistant = { workspace = true, features = ["test-support"] }
context_servers.workspace = true
assistant_tool.workspace = true
context_server.workspace = true
async-trait.workspace = true
audio.workspace = true
call = { workspace = true, features = ["test-support"] }
@@ -90,6 +91,7 @@ collections = { workspace = true, features = ["test-support"] }
ctor.workspace = true
editor = { workspace = true, features = ["test-support"] }
env_logger.workspace = true
extension.workspace = true
file_finder.workspace = true
fs = { workspace = true, features = ["test-support"] }
git = { workspace = true, features = ["test-support"] }

View File

@@ -61,6 +61,39 @@ impl std::fmt::Display for CloudflareIpCountryHeader {
}
}
pub struct SystemIdHeader(String);
impl Header for SystemIdHeader {
fn name() -> &'static HeaderName {
static SYSTEM_ID_HEADER: OnceLock<HeaderName> = OnceLock::new();
SYSTEM_ID_HEADER.get_or_init(|| HeaderName::from_static("x-zed-system-id"))
}
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
where
Self: Sized,
I: Iterator<Item = &'i axum::http::HeaderValue>,
{
let system_id = values
.next()
.ok_or_else(axum::headers::Error::invalid)?
.to_str()
.map_err(|_| axum::headers::Error::invalid())?;
Ok(Self(system_id.to_string()))
}
fn encode<E: Extend<axum::http::HeaderValue>>(&self, _values: &mut E) {
unimplemented!()
}
}
impl std::fmt::Display for SystemIdHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub fn routes(rpc_server: Arc<rpc::Server>) -> Router<(), Body> {
Router::new()
.route("/user", get(get_authenticated_user))

View File

@@ -1420,8 +1420,6 @@ fn for_snowflake(
"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),
};
@@ -1444,65 +1442,64 @@ fn for_snowflake(
Event::App(e) => {
let mut properties = json!({});
let event_type = match e.operation.trim() {
"extensions: install extension" => "Extension Installed".to_string(),
// App
"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" => "App First Opened".to_string(),
"first open for release channel" => {
properties["is_first_for_channel"] = json!(true);
"App First Opened For Release Channel".to_string()
}
"close" => "App Closed".to_string(),
// Project
"open project" => "Project Opened".to_string(),
"open node project" => {
properties["project_type"] = json!("node");
"Project Opened".to_string()
}
"open pnpm project" => {
properties["project_type"] = json!("pnpm");
"Project Opened".to_string()
}
"open yarn project" => {
properties["project_type"] = json!("yarn");
"Project Opened".to_string()
}
// SSH
"create ssh server" => "SSH Server Created".to_string(),
"create ssh project" => "SSH Project Created".to_string(),
"open ssh project" => "SSH Project Opened".to_string(),
// Welcome Page
"welcome page: change keymap" => "Welcome Keymap Changed".to_string(),
"welcome page: change theme" => "Welcome Theme Changed".to_string(),
"welcome page: close" => "Welcome Page Closed".to_string(),
"welcome page: edit settings" => "Welcome Settings Edited".to_string(),
"welcome page: install cli" => "Welcome CLI Installed".to_string(),
"welcome page: open" => "Welcome Page Opened".to_string(),
"welcome page: open extensions" => "Welcome Extensions Page Opened".to_string(),
"welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(),
"welcome page: toggle diagnostic telemetry" => {
"Welcome Diagnostic Telemetry Toggled".to_string()
}
"welcome page: toggle metric telemetry" => {
"Welcome Metric Telemetry Toggled".to_string()
}
"welcome page: toggle vim" => "Welcome Vim Mode Toggled".to_string(),
"welcome page: view docs" => "Welcome Documentation Viewed".to_string(),
// Extensions
"extensions page: open" => "Extensions Page Opened".to_string(),
"extensions: install extension" => "Extension Installed".to_string(),
"extensions: uninstall extension" => "Extension Uninstalled".to_string(),
// Misc
"markdown preview: open" => "Markdown Preview Opened".to_string(),
"project diagnostics: open" => "Project Diagnostics Opened".to_string(),
"project search: open" => "Project Search Opened".to_string(),
"repl sessions: open" => "REPL Session Started".to_string(),
// Feature Upsell
"feature upsell: toggle vim" => {
properties["source"] = json!("Feature Upsell");
"Vim Mode Toggled".to_string()
@@ -1555,10 +1552,13 @@ fn for_snowflake(
);
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());
map.insert("country".to_string(), country_code.clone().into());
}
}
// NOTE: most amplitude user properties are read out of our event_properties
// dictionary. See https://app.amplitude.com/data/zed/Zed/sources/detail/production/falcon%3A159998
// for how that is configured.
let user_properties = Some(serde_json::json!({
"is_staff": body.is_staff,
}));
@@ -1575,8 +1575,8 @@ fn for_snowflake(
})
}
#[derive(Serialize, Deserialize)]
struct SnowflakeRow {
#[derive(Serialize, Deserialize, Debug)]
pub struct SnowflakeRow {
pub time: chrono::DateTime<chrono::Utc>,
pub user_id: Option<String>,
pub device_id: Option<String>,
@@ -1586,47 +1586,41 @@ struct SnowflakeRow {
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,
impl SnowflakeRow {
pub fn new(
event_type: impl Into<String>,
metrics_id: Option<Uuid>,
is_staff: bool,
system_id: Option<String>,
event_properties: serde_json::Value,
) -> Self {
Self {
time: chrono::Utc::now(),
event_type: event_type.into(),
device_id: system_id,
user_id: metrics_id.map(|id| id.to_string()),
insert_id: Some(uuid::Uuid::new_v4().to_string()),
event_properties,
user_properties: Some(json!({"is_staff": is_staff})),
}
}
#[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>,
pub async fn write(
self,
client: &Option<aws_sdk_kinesis::Client>,
stream: &Option<String>,
) -> anyhow::Result<()> {
let Some((client, stream)) = client.as_ref().zip(stream.as_ref()) else {
return Ok(());
};
let row = serde_json::to_vec(&self)?;
client
.put_record()
.stream_name(stream)
.partition_key(&self.user_id.unwrap_or_default())
.data(row.into())
.send()
.await?;
Ok(())
}
}

View File

@@ -1,3 +1,5 @@
use serde::Serialize;
/// A number of cents.
#[derive(
Debug,
@@ -12,6 +14,7 @@
derive_more::AddAssign,
derive_more::Sub,
derive_more::SubAssign,
Serialize,
)]
pub struct Cents(pub u32);

View File

@@ -3,9 +3,11 @@ pub mod db;
mod telemetry;
mod token;
use crate::api::events::SnowflakeRow;
use crate::api::CloudflareIpCountryHeader;
use crate::build_kinesis_client;
use crate::{
api::CloudflareIpCountryHeader, build_clickhouse_client, db::UserId, executor::Executor, Cents,
Config, Error, Result,
build_clickhouse_client, db::UserId, executor::Executor, Cents, Config, Error, Result,
};
use anyhow::{anyhow, Context as _};
use authorization::authorize_access_to_language_model;
@@ -28,6 +30,7 @@ use rpc::{
proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME,
};
use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME};
use serde_json::json;
use std::{
pin::Pin,
sync::Arc,
@@ -45,6 +48,7 @@ pub struct LlmState {
pub executor: Executor,
pub db: Arc<LlmDatabase>,
pub http_client: ReqwestClient,
pub kinesis_client: Option<aws_sdk_kinesis::Client>,
pub clickhouse_client: Option<clickhouse::Client>,
active_user_count_by_model:
RwLock<HashMap<(LanguageModelProvider, String), (DateTime<Utc>, ActiveUserCount)>>,
@@ -77,6 +81,11 @@ impl LlmState {
executor,
db,
http_client,
kinesis_client: if config.kinesis_access_key.is_some() {
build_kinesis_client(&config).await.log_err()
} else {
None
},
clickhouse_client: config
.clickhouse_url
.as_ref()
@@ -521,25 +530,50 @@ async fn check_usage_limit(
UsageMeasure::TokensPerDay => "tokens_per_day",
};
if let Some(client) = state.clickhouse_client.as_ref() {
tracing::info!(
target: "user rate limit",
user_id = claims.user_id,
login = claims.github_user_login,
authn.jti = claims.jti,
is_staff = claims.is_staff,
provider = provider.to_string(),
model = model.name,
requests_this_minute = usage.requests_this_minute,
tokens_this_minute = usage.tokens_this_minute,
tokens_this_day = usage.tokens_this_day,
users_in_recent_minutes = users_in_recent_minutes,
users_in_recent_days = users_in_recent_days,
max_requests_per_minute = per_user_max_requests_per_minute,
max_tokens_per_minute = per_user_max_tokens_per_minute,
max_tokens_per_day = per_user_max_tokens_per_day,
);
tracing::info!(
target: "user rate limit",
user_id = claims.user_id,
login = claims.github_user_login,
authn.jti = claims.jti,
is_staff = claims.is_staff,
provider = provider.to_string(),
model = model.name,
requests_this_minute = usage.requests_this_minute,
tokens_this_minute = usage.tokens_this_minute,
tokens_this_day = usage.tokens_this_day,
users_in_recent_minutes = users_in_recent_minutes,
users_in_recent_days = users_in_recent_days,
max_requests_per_minute = per_user_max_requests_per_minute,
max_tokens_per_minute = per_user_max_tokens_per_minute,
max_tokens_per_day = per_user_max_tokens_per_day,
);
SnowflakeRow::new(
"Language Model Rate Limited",
claims.metrics_id,
claims.is_staff,
claims.system_id.clone(),
json!({
"usage": usage,
"users_in_recent_minutes": users_in_recent_minutes,
"users_in_recent_days": users_in_recent_days,
"max_requests_per_minute": per_user_max_requests_per_minute,
"max_tokens_per_minute": per_user_max_tokens_per_minute,
"max_tokens_per_day": per_user_max_tokens_per_day,
"plan": match claims.plan {
Plan::Free => "free".to_string(),
Plan::ZedPro => "zed_pro".to_string(),
},
"model": model.name.clone(),
"provider": provider.to_string(),
"usage_measure": resource.to_string(),
}),
)
.write(&state.kinesis_client, &state.config.kinesis_stream)
.await
.log_err();
if let Some(client) = state.clickhouse_client.as_ref() {
report_llm_rate_limit(
client,
LlmRateLimitEventRow {
@@ -652,6 +686,27 @@ impl<S> Drop for TokenCountingStream<S> {
tokens_this_minute = usage.tokens_this_minute,
);
let properties = json!({
"plan": match claims.plan {
Plan::Free => "free".to_string(),
Plan::ZedPro => "zed_pro".to_string(),
},
"model": model,
"provider": provider,
"usage": usage,
"tokens": tokens
});
SnowflakeRow::new(
"Language Model Used",
claims.metrics_id,
claims.is_staff,
claims.system_id.clone(),
properties,
)
.write(&state.kinesis_client, &state.config.kinesis_stream)
.await
.log_err();
if let Some(clickhouse_client) = state.clickhouse_client.as_ref() {
report_llm_usage(
clickhouse_client,

View File

@@ -9,7 +9,7 @@ use strum::IntoEnumIterator as _;
use super::*;
#[derive(Debug, PartialEq, Clone, Copy, Default)]
#[derive(Debug, PartialEq, Clone, Copy, Default, serde::Serialize)]
pub struct TokenUsage {
pub input: usize,
pub input_cache_creation: usize,
@@ -23,7 +23,7 @@ impl TokenUsage {
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
pub struct Usage {
pub requests_this_minute: usize,
pub tokens_this_minute: usize,

View File

@@ -8,6 +8,7 @@ use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -16,6 +17,10 @@ pub struct LlmTokenClaims {
pub exp: u64,
pub jti: String,
pub user_id: u64,
#[serde(default)]
pub system_id: Option<String>,
#[serde(default)]
pub metrics_id: Option<Uuid>,
pub github_user_login: String,
pub is_staff: bool,
pub has_llm_closed_beta_feature_flag: bool,
@@ -36,6 +41,7 @@ impl LlmTokenClaims {
has_llm_closed_beta_feature_flag: bool,
has_llm_subscription: bool,
plan: rpc::proto::Plan,
system_id: Option<String>,
config: &Config,
) -> Result<String> {
let secret = config
@@ -49,6 +55,8 @@ impl LlmTokenClaims {
exp: (now + LLM_TOKEN_LIFETIME).timestamp() as u64,
jti: uuid::Uuid::new_v4().to_string(),
user_id: user.id.to_proto(),
system_id,
metrics_id: Some(user.metrics_id),
github_user_login: user.github_login.clone(),
is_staff,
has_llm_closed_beta_feature_flag,

View File

@@ -1,6 +1,6 @@
mod connection_pool;
use crate::api::CloudflareIpCountryHeader;
use crate::api::{CloudflareIpCountryHeader, SystemIdHeader};
use crate::llm::LlmTokenClaims;
use crate::{
auth,
@@ -137,6 +137,7 @@ struct Session {
/// The GeoIP country code for the user.
#[allow(unused)]
geoip_country_code: Option<String>,
system_id: Option<String>,
_executor: Executor,
}
@@ -682,6 +683,7 @@ impl Server {
principal: Principal,
zed_version: ZedVersion,
geoip_country_code: Option<String>,
system_id: Option<String>,
send_connection_id: Option<oneshot::Sender<ConnectionId>>,
executor: Executor,
) -> impl Future<Output = ()> {
@@ -737,6 +739,7 @@ impl Server {
app_state: this.app_state.clone(),
http_client,
geoip_country_code,
system_id,
_executor: executor.clone(),
supermaven_client,
};
@@ -1056,6 +1059,7 @@ pub fn routes(server: Arc<Server>) -> Router<(), Body> {
.layer(Extension(server))
}
#[allow(clippy::too_many_arguments)]
pub async fn handle_websocket_request(
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
app_version_header: Option<TypedHeader<AppVersionHeader>>,
@@ -1063,6 +1067,7 @@ pub async fn handle_websocket_request(
Extension(server): Extension<Arc<Server>>,
Extension(principal): Extension<Principal>,
country_code_header: Option<TypedHeader<CloudflareIpCountryHeader>>,
system_id_header: Option<TypedHeader<SystemIdHeader>>,
ws: WebSocketUpgrade,
) -> axum::response::Response {
if protocol_version != rpc::PROTOCOL_VERSION {
@@ -1104,6 +1109,7 @@ pub async fn handle_websocket_request(
principal,
version,
country_code_header.map(|header| header.to_string()),
system_id_header.map(|header| header.to_string()),
None,
Executor::Production,
)
@@ -4053,6 +4059,7 @@ async fn get_llm_api_token(
has_llm_closed_beta_feature_flag,
has_llm_subscription,
session.current_plan(&db).await?,
session.system_id.clone(),
&session.app_state.config,
)?;
response.send(proto::GetLlmTokenResponse { token })?;

View File

@@ -6,7 +6,8 @@ use crate::{
},
};
use anyhow::{anyhow, Result};
use assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet, ToolWorkingSet};
use assistant::{ContextStore, PromptBuilder, SlashCommandWorkingSet};
use assistant_tool::ToolWorkingSet;
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet};
@@ -6486,8 +6487,8 @@ 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);
cx_a.update(context_server::init);
cx_b.update(context_server::init);
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context_store_a = cx_a
.update(|cx| {

View File

@@ -835,7 +835,7 @@ impl RandomizedTest for ProjectCollaborationTest {
.map_ok(|_| ())
.boxed(),
LspRequestKind::CodeAction => project
.code_actions(&buffer, offset..offset, cx)
.code_actions(&buffer, offset..offset, None, cx)
.map(|_| Ok(()))
.boxed(),
LspRequestKind::Definition => project

View File

@@ -1,6 +1,7 @@
use crate::tests::TestServer;
use call::ActiveCall;
use collections::HashSet;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs as _};
use futures::StreamExt as _;
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _};
@@ -81,6 +82,7 @@ async fn test_sharing_an_ssh_remote_project(
http_client: remote_http_client,
node_runtime: node,
languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
},
cx,
)
@@ -243,6 +245,7 @@ async fn test_ssh_collaboration_git_branches(
http_client: remote_http_client,
node_runtime: node,
languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
},
cx,
)
@@ -400,6 +403,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
http_client: remote_http_client,
node_runtime: NodeRuntime::unavailable(),
languages,
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
},
cx,
)

View File

@@ -244,6 +244,7 @@ impl TestServer {
Principal::User(user),
ZedVersion(SemanticVersion::new(1, 0, 0)),
None,
None,
Some(connection_id_tx),
Executor::Deterministic(cx.background_executor().clone()),
))

View File

@@ -58,12 +58,11 @@ settings.workspace = true
smallvec.workspace = true
story = { workspace = true, optional = true }
theme.workspace = true
time_format.workspace = true
time.workspace = true
time_format.workspace = true
title_bar.workspace = true
ui.workspace = true
util.workspace = true
vcs_menu.workspace = true
workspace.workspace = true
[dev-dependencies]

View File

@@ -33,7 +33,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_panel::init(cx);
notifications::init(app_state, cx);
title_bar::init(cx);
vcs_menu::init(cx);
}
fn notification_window_options(

View File

@@ -11,7 +11,7 @@ use command_palette_hooks::{
};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global,
ParentElement, Render, Styled, Task, UpdateGlobal, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
@@ -21,9 +21,7 @@ use settings::Settings;
use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace, WorkspaceSettings};
use zed_actions::OpenZedUrl;
actions!(command_palette, [Toggle]);
use zed_actions::{command_palette::Toggle, OpenZedUrl};
pub fn init(cx: &mut AppContext) {
client::init_settings(cx);

View File

@@ -1,5 +1,5 @@
[package]
name = "context_servers"
name = "context_server"
version = "0.1.0"
edition = "2021"
publish = false
@@ -9,22 +9,26 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/context_servers.rs"
path = "src/context_server.rs"
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
context_server_settings.workspace = true
extension.workspace = true
futures.workspace = true
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
settings.workspace = true
smol.workspace = true
ui.workspace = true
url = { workspace = true, features = ["serde"] }
util.workspace = true
workspace.workspace = true

View File

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

View File

@@ -9,7 +9,7 @@ use serde_json::{value::RawValue, Value};
use smol::{
channel,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
process::{self, Child},
process::Child,
};
use std::{
fmt,
@@ -152,7 +152,7 @@ impl Client {
&binary.args
);
let mut command = process::Command::new(&binary.executable);
let mut command = util::command::new_smol_command(&binary.executable);
command
.args(&binary.args)
.envs(binary.env.unwrap_or_default())

View File

@@ -1,14 +1,16 @@
pub mod client;
mod context_server_tool;
mod extension_context_server;
pub mod manager;
pub mod protocol;
mod registry;
pub mod types;
use command_palette_hooks::CommandPaletteFilter;
pub use context_server_settings::{ContextServerSettings, ServerCommand, ServerConfig};
use gpui::{actions, AppContext};
use settings::Settings;
use crate::manager::ContextServerSettings;
pub use crate::context_server_tool::ContextServerTool;
pub use crate::registry::ContextServerFactoryRegistry;
actions!(context_servers, [Restart]);
@@ -17,8 +19,9 @@ actions!(context_servers, [Restart]);
pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers";
pub fn init(cx: &mut AppContext) {
ContextServerSettings::register(cx);
context_server_settings::init(cx);
ContextServerFactoryRegistry::default_global(cx);
extension_context_server::init(cx);
CommandPaletteFilter::update_global(cx, |filter, _cx| {
filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE);

View File

@@ -2,10 +2,11 @@ use std::sync::Arc;
use anyhow::{anyhow, bail};
use assistant_tool::Tool;
use context_servers::manager::ContextServerManager;
use context_servers::types;
use gpui::{Model, Task};
use crate::manager::ContextServerManager;
use crate::types;
pub struct ContextServerTool {
server_manager: Model<ContextServerManager>,
server_id: Arc<str>,

View File

@@ -0,0 +1,77 @@
use std::sync::Arc;
use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate};
use gpui::{AppContext, Model};
use crate::{ContextServerFactoryRegistry, ServerCommand};
struct ExtensionProject {
worktree_ids: Vec<u64>,
}
impl ProjectDelegate for ExtensionProject {
fn worktree_ids(&self) -> Vec<u64> {
self.worktree_ids.clone()
}
}
pub fn init(cx: &mut AppContext) {
let proxy = ExtensionHostProxy::default_global(cx);
proxy.register_context_server_proxy(ContextServerFactoryRegistryProxy {
context_server_factory_registry: ContextServerFactoryRegistry::global(cx),
});
}
struct ContextServerFactoryRegistryProxy {
context_server_factory_registry: Model<ContextServerFactoryRegistry>,
}
impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy {
fn register_context_server(
&self,
extension: Arc<dyn Extension>,
id: Arc<str>,
cx: &mut AppContext,
) {
self.context_server_factory_registry
.update(cx, |registry, _| {
registry.register_server_factory(
id.clone(),
Arc::new({
move |project, cx| {
log::info!(
"loading command for context server {id} from extension {}",
extension.manifest().id
);
let id = id.clone();
let extension = extension.clone();
cx.spawn(|mut cx| async move {
let extension_project =
project.update(&mut cx, |project, cx| {
Arc::new(ExtensionProject {
worktree_ids: project
.visible_worktrees(cx)
.map(|worktree| worktree.read(cx).id().to_proto())
.collect(),
})
})?;
let command = extension
.context_server_command(id.clone(), extension_project)
.await?;
log::info!("loaded command for context server {id}: {command:?}");
Ok(ServerCommand {
path: command.command,
args: command.args,
env: Some(command.env.into_iter().collect()),
})
})
}
}),
)
});
}
}

View File

@@ -24,48 +24,16 @@ use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Subscription, Tas
use log;
use parking_lot::RwLock;
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use settings::{Settings, SettingsStore};
use util::ResultExt as _;
use crate::{ContextServerSettings, ServerConfig};
use crate::{
client::{self, Client},
types, ContextServerFactoryRegistry, CONTEXT_SERVERS_NAMESPACE,
};
#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
pub struct ContextServerSettings {
#[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 ServerCommand {
pub path: String,
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,
}
impl Settings for ContextServerSettings {
const KEY: Option<&'static str> = None;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
sources.json_merge()
}
}
pub struct ContextServer {
pub id: Arc<str>,
pub config: Arc<ServerConfig>,

View File

@@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ReadGlobal, Task};
use project::Project;
use crate::manager::ServerCommand;
use crate::ServerCommand;
pub type ContextServerFactory = Arc<
dyn Fn(Model<Project>, &AsyncAppContext) -> Task<Result<ServerCommand>> + Send + Sync + 'static,

View File

@@ -0,0 +1,21 @@
[package]
name = "context_server_settings"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/context_server_settings.rs"
[dependencies]
anyhow.workspace = true
collections.workspace = true
gpui.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true

View File

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

View File

@@ -0,0 +1,61 @@
use std::sync::Arc;
use collections::HashMap;
use gpui::AppContext;
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
pub fn init(cx: &mut AppContext) {
ContextServerSettings::register(cx);
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
pub struct ServerConfig {
/// The command to run this context server.
///
/// This will override the command set by an extension.
pub command: Option<ServerCommand>,
/// The settings for this context server.
///
/// Consult the documentation for the context server to see what settings
/// are supported.
#[schemars(schema_with = "server_config_settings_json_schema")]
pub settings: Option<serde_json::Value>,
}
fn server_config_settings_json_schema(_generator: &mut SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
..Default::default()
})
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
pub struct ServerCommand {
pub path: String,
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,
}
#[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)]
pub struct ContextServerSettings {
/// Settings for context servers used in the Assistant.
#[serde(default)]
pub context_servers: HashMap<Arc<str>, ServerConfig>,
}
impl Settings for ContextServerSettings {
const KEY: Option<&'static str> = None;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
sources.json_merge()
}
}

View File

@@ -29,14 +29,14 @@ anyhow.workspace = true
async-compression.workspace = true
async-tar.workspace = true
chrono.workspace = true
collections.workspace = true
client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
editor.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
inline_completion.workspace = true
language.workspace = true
lsp.workspace = true
menu.workspace = true
@@ -44,12 +44,12 @@ node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
project.workspace = true
schemars = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
schemars = { workspace = true, optional = true }
strum.workspace = true
settings.workspace = true
smol.workspace = true
strum.workspace = true
task.workspace = true
ui.workspace = true
util.workspace = true

View File

@@ -38,8 +38,8 @@ use std::{
};
use util::{fs::remove_matching, maybe, ResultExt};
pub use copilot_completion_provider::CopilotCompletionProvider;
pub use sign_in::CopilotCodeVerification;
pub use crate::copilot_completion_provider::CopilotCompletionProvider;
pub use crate::sign_in::{initiate_sign_in, CopilotCodeVerification};
actions!(
copilot,
@@ -1231,7 +1231,7 @@ mod tests {
fn disk_state(&self) -> language::DiskState {
language::DiskState::Present {
mtime: std::time::UNIX_EPOCH,
mtime: ::fs::MTime::from_seconds_and_nanos(100, 42),
}
}

View File

@@ -197,7 +197,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &mut AppContext) {
cx.set_global(GlobalCopilotChat(copilot_chat));
}
fn copilot_chat_config_path() -> &'static PathBuf {
fn copilot_chat_config_dir() -> &'static PathBuf {
static COPILOT_CHAT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
COPILOT_CHAT_CONFIG_DIR.get_or_init(|| {
@@ -207,10 +207,14 @@ fn copilot_chat_config_path() -> &'static PathBuf {
home_dir().join(".config")
}
.join("github-copilot")
.join("hosts.json")
})
}
fn copilot_chat_config_paths() -> [PathBuf; 2] {
let base_dir = copilot_chat_config_dir();
[base_dir.join("hosts.json"), base_dir.join("apps.json")]
}
impl CopilotChat {
pub fn global(cx: &AppContext) -> Option<gpui::Model<Self>> {
cx.try_global::<GlobalCopilotChat>()
@@ -218,13 +222,24 @@ impl CopilotChat {
}
pub fn new(fs: Arc<dyn Fs>, client: Arc<dyn HttpClient>, cx: &AppContext) -> Self {
let mut config_file_rx = watch_config_file(
cx.background_executor(),
fs,
copilot_chat_config_path().clone(),
);
let config_paths = copilot_chat_config_paths();
let resolve_config_path = {
let fs = fs.clone();
async move {
for config_path in config_paths.iter() {
if fs.metadata(config_path).await.is_ok_and(|v| v.is_some()) {
return config_path.clone();
}
}
config_paths[0].clone()
}
};
cx.spawn(|cx| async move {
let config_file = resolve_config_path.await;
let mut config_file_rx = watch_config_file(cx.background_executor(), fs, config_file);
while let Some(contents) = config_file_rx.next().await {
let oauth_token = extract_oauth_token(contents);
@@ -318,9 +333,15 @@ async fn request_api_token(oauth_token: &str, client: Arc<dyn HttpClient>) -> Re
fn extract_oauth_token(contents: String) -> Option<String> {
serde_json::from_str::<serde_json::Value>(&contents)
.map(|v| {
v["github.com"]["oauth_token"]
.as_str()
.map(|v| v.to_string())
v.as_object().and_then(|obj| {
obj.iter().find_map(|(key, value)| {
if key.starts_with("github.com") {
value["oauth_token"].as_str().map(|v| v.to_string())
} else {
None
}
})
})
})
.ok()
.flatten()

View File

@@ -1,8 +1,8 @@
use crate::{Completion, Copilot};
use anyhow::Result;
use client::telemetry::Telemetry;
use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use gpui::{AppContext, EntityId, Model, ModelContext, Task};
use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider};
use language::{
language_settings::{all_language_settings, AllLanguageSettings},
Buffer, OffsetRangeExt, ToOffset,

View File

@@ -5,10 +5,79 @@ use gpui::{
Styled, Subscription, ViewContext,
};
use ui::{prelude::*, Button, Label, Vector, VectorName};
use workspace::ModalView;
use util::ResultExt as _;
use workspace::notifications::NotificationId;
use workspace::{ModalView, Toast, Workspace};
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
struct CopilotStartingToast;
pub fn initiate_sign_in(cx: &mut WindowContext) {
let Some(copilot) = Copilot::global(cx) else {
return;
};
let status = copilot.read(cx).status();
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
match status {
Status::Starting { task } => {
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
let Ok(workspace) = workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotStartingToast>(),
"Copilot is starting...",
),
cx,
);
workspace.weak_handle()
}) else {
return;
};
cx.spawn(|mut cx| async move {
task.await;
if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() {
workspace
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
Status::Authorized => workspace.show_toast(
Toast::new(
NotificationId::unique::<CopilotStartingToast>(),
"Copilot has started!",
),
cx,
),
_ => {
workspace.dismiss_toast(
&NotificationId::unique::<CopilotStartingToast>(),
cx,
);
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
.detach_and_log_err(cx);
}
})
.log_err();
}
})
.detach();
}
_ => {
copilot.update(cx, |this, cx| this.sign_in(cx)).detach();
workspace
.update(cx, |this, cx| {
this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx));
})
.ok();
}
}
}
pub struct CopilotCodeVerification {
status: Status,
connect_clicked: bool,

View File

@@ -33,6 +33,7 @@ use std::{
mem,
ops::Range,
sync::Arc,
time::Duration,
};
use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls;
@@ -82,6 +83,8 @@ struct DiagnosticGroupState {
impl EventEmitter<EditorEvent> for ProjectDiagnosticsEditor {}
const DIAGNOSTICS_UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
impl Render for ProjectDiagnosticsEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let child = if self.path_states.is_empty() {
@@ -131,16 +134,27 @@ impl ProjectDiagnosticsEditor {
language_server_id,
path,
} => {
this.paths_to_update
.insert((path.clone(), Some(*language_server_id)));
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.emit(EditorEvent::TitleChanged);
let max_severity = this.max_severity();
let has_diagnostics_to_display = project.read(cx).lsp_store().read(cx).diagnostics_for_buffer(path)
.into_iter().flatten()
.filter(|(server_id, _)| language_server_id == server_id)
.flat_map(|(_, diagnostics)| diagnostics)
.any(|diagnostic| diagnostic.diagnostic.severity <= max_severity);
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
if has_diagnostics_to_display {
this.paths_to_update
.insert((path.clone(), Some(*language_server_id)));
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.emit(EditorEvent::TitleChanged);
if this.editor.focus_handle(cx).contains_focused(cx) || this.focus_handle.contains_focused(cx) {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
} else {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
this.update_stale_excerpts(cx);
}
} else {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
this.update_stale_excerpts(cx);
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. no diagnostics to display");
}
}
_ => {}
@@ -198,6 +212,9 @@ impl ProjectDiagnosticsEditor {
}
let project_handle = self.project.clone();
self.update_excerpts_task = Some(cx.spawn(|this, mut cx| async move {
cx.background_executor()
.timer(DIAGNOSTICS_UPDATE_DEBOUNCE)
.await;
loop {
let Some((path, language_server_id)) = this.update(&mut cx, |this, _| {
let Some((path, language_server_id)) = this.paths_to_update.pop_first() else {
@@ -323,16 +340,12 @@ impl ProjectDiagnosticsEditor {
ExcerptId::min()
};
let max_severity = self.max_severity();
let path_state = &mut self.path_states[path_ix];
let mut new_group_ixs = Vec::new();
let mut blocks_to_add = Vec::new();
let mut blocks_to_remove = HashSet::default();
let mut first_excerpt_id = None;
let max_severity = if self.include_warnings {
DiagnosticSeverity::WARNING
} else {
DiagnosticSeverity::ERROR
};
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, cx| {
let mut old_groups = mem::take(&mut path_state.diagnostic_groups)
.into_iter()
@@ -621,6 +634,14 @@ impl ProjectDiagnosticsEditor {
prev_path = Some(path);
}
}
fn max_severity(&self) -> DiagnosticSeverity {
if self.include_warnings {
DiagnosticSeverity::WARNING
} else {
DiagnosticSeverity::ERROR
}
}
}
impl FocusableView for ProjectDiagnosticsEditor {
@@ -695,7 +716,7 @@ impl Item for ProjectDiagnosticsEditor {
fn for_each_project_item(
&self,
cx: &AppContext,
f: &mut dyn FnMut(gpui::EntityId, &dyn project::Item),
f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem),
) {
self.editor.for_each_project_item(cx, f)
}
@@ -776,7 +797,7 @@ impl Item for ProjectDiagnosticsEditor {
}
}
fn breadcrumb_location(&self) -> ToolbarItemLocation {
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft
}

View File

@@ -155,7 +155,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
});
let editor = view.update(cx, |view, _| view.editor.clone());
view.next_notification(cx).await;
view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx)
.await;
assert_eq!(
editor_blocks(&editor, cx),
[
@@ -240,7 +241,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
lsp_store.disk_based_diagnostics_finished(language_server_id, cx);
});
view.next_notification(cx).await;
view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx)
.await;
assert_eq!(
editor_blocks(&editor, cx),
[
@@ -352,7 +354,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
lsp_store.disk_based_diagnostics_finished(language_server_id, cx);
});
view.next_notification(cx).await;
view.next_notification(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10), cx)
.await;
assert_eq!(
editor_blocks(&editor, cx),
[
@@ -491,6 +494,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
});
// Only the first language server's diagnostics are shown.
cx.executor()
.advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10));
cx.executor().run_until_parked();
assert_eq!(
editor_blocks(&editor, cx),
@@ -537,6 +542,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
});
// Both language server's diagnostics are shown.
cx.executor()
.advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10));
cx.executor().run_until_parked();
assert_eq!(
editor_blocks(&editor, cx),
@@ -603,6 +610,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
});
// Only the first language server's diagnostics are updated.
cx.executor()
.advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10));
cx.executor().run_until_parked();
assert_eq!(
editor_blocks(&editor, cx),
@@ -659,6 +668,8 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
});
// Both language servers' diagnostics are updated.
cx.executor()
.advance_clock(DIAGNOSTICS_UPDATE_DEBOUNCE + Duration::from_millis(10));
cx.executor().run_until_parked();
assert_eq!(
editor_blocks(&editor, cx),

View File

@@ -1,7 +1,7 @@
use editor::Editor;
use gpui::{
rems, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View,
ViewContext, WeakView,
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
WeakView,
};
use language::Diagnostic;
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
@@ -77,8 +77,10 @@ impl Render for DiagnosticIndicator {
};
h_flex()
.h(rems(1.375))
.gap_2()
.pl_1()
.border_l_1()
.border_color(cx.theme().colors().border)
.child(
ButtonLike::new("diagnostic-indicator")
.child(diagnostic_indicator)

View File

@@ -42,10 +42,12 @@ emojis.workspace = true
file_icons.workspace = true
futures.workspace = true
fuzzy.workspace = true
fs.workspace = true
git.workspace = true
gpui.workspace = true
http_client.workspace = true
indoc.workspace = true
inline_completion.workspace = true
itertools.workspace = true
language.workspace = true
linkify.workspace = true

View File

@@ -271,6 +271,8 @@ gpui::actions!(
Hover,
Indent,
JoinLines,
KillRingCut,
KillRingYank,
LineDown,
LineUp,
MoveDown,

View File

@@ -1067,11 +1067,8 @@ impl DisplaySnapshot {
self.block_snapshot.block_for_id(id)
}
pub fn intersects_fold<T: ToPoint>(&self, point: T) -> bool {
let point = point.to_point(&self.buffer_snapshot);
self.block_snapshot
.is_line_replaced(MultiBufferRow(point.row))
|| self.fold_snapshot.intersects_fold(point)
pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
self.fold_snapshot.intersects_fold(offset)
}
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {

View File

@@ -28,7 +28,6 @@ mod hover_popover;
mod hunk_diff;
mod indent_guides;
mod inlay_hint_cache;
mod inline_completion_provider;
pub mod items;
mod linked_editing_ranges;
mod lsp_ext;
@@ -75,7 +74,7 @@ use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
FocusableView, FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, ModelContext, MouseButton, PaintQuad, ParentElement, Pixels, Render,
ScrollStrategy, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
@@ -87,7 +86,8 @@ pub(crate) use hunk_diff::HoveredHunk;
use hunk_diff::{diff_hunk_to_display, ExpandedHunks};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use inline_completion_provider::*;
pub use inline_completion::Direction;
use inline_completion::{InlayProposal, InlineCompletionProvider, InlineCompletionProviderHandle};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
@@ -125,8 +125,8 @@ use parking_lot::{Mutex, RwLock};
use project::{
lsp_store::{FormatTarget, FormatTrigger},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Item, Location,
LocationLink, Project, ProjectTransaction, TaskSourceKind,
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
Project, ProjectItem, ProjectTransaction, TaskSourceKind,
};
use rand::prelude::*;
use rpc::{proto::*, ErrorExt};
@@ -273,12 +273,6 @@ enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
enum InputComposition {}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Direction {
Prev,
Next,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Navigated {
Yes,
@@ -540,15 +534,6 @@ pub enum IsVimMode {
No,
}
pub trait ActiveLineTrailerProvider {
fn render_active_line_trailer(
&mut self,
style: &EditorStyle,
focus_handle: &FocusHandle,
cx: &mut WindowContext,
) -> Option<AnyElement>;
}
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
///
/// See the [module level documentation](self) for more information.
@@ -611,7 +596,6 @@ pub struct Editor {
auto_signature_help: Option<bool>,
find_all_references_task_sources: Vec<Anchor>,
next_completion_id: CompletionId,
completion_documentation_pre_resolve_debounce: DebouncedDelay,
available_code_actions: Option<(Location, Arc<[AvailableCodeAction]>)>,
code_actions_task: Option<Task<Result<()>>>,
document_highlights_task: Option<Task<()>>,
@@ -676,7 +660,6 @@ pub struct Editor {
next_scroll_position: NextScrollCursorCenterTopBottom,
addons: HashMap<TypeId, Box<dyn Addon>>,
_scroll_cursor_center_top_bottom_task: Task<()>,
active_line_trailer_provider: Option<Box<dyn ActiveLineTrailerProvider>>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1022,7 +1005,7 @@ struct CompletionsMenu {
matches: Arc<[StringMatch]>,
selected_item: usize,
scroll_handle: UniformListScrollHandle,
selected_completion_documentation_resolve_debounce: Option<Arc<Mutex<DebouncedDelay>>>,
selected_completion_resolve_debounce: Option<Arc<Mutex<DebouncedDelay>>>,
}
impl CompletionsMenu {
@@ -1054,9 +1037,7 @@ impl CompletionsMenu {
matches: Vec::new().into(),
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
selected_completion_documentation_resolve_debounce: Some(Arc::new(Mutex::new(
DebouncedDelay::new(),
))),
selected_completion_resolve_debounce: Some(Arc::new(Mutex::new(DebouncedDelay::new()))),
}
}
@@ -1109,15 +1090,12 @@ impl CompletionsMenu {
matches,
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
selected_completion_documentation_resolve_debounce: Some(Arc::new(Mutex::new(
DebouncedDelay::new(),
))),
selected_completion_resolve_debounce: Some(Arc::new(Mutex::new(DebouncedDelay::new()))),
}
}
fn suppress_documentation_resolution(mut self) -> Self {
self.selected_completion_documentation_resolve_debounce
.take();
self.selected_completion_resolve_debounce.take();
self
}
@@ -1129,7 +1107,7 @@ impl CompletionsMenu {
self.selected_item = 0;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.attempt_resolve_selected_completion_documentation(provider, cx);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
@@ -1145,7 +1123,7 @@ impl CompletionsMenu {
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.attempt_resolve_selected_completion_documentation(provider, cx);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
@@ -1161,7 +1139,7 @@ impl CompletionsMenu {
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.attempt_resolve_selected_completion_documentation(provider, cx);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
@@ -1173,58 +1151,20 @@ impl CompletionsMenu {
self.selected_item = self.matches.len() - 1;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.attempt_resolve_selected_completion_documentation(provider, cx);
self.resolve_selected_completion(provider, cx);
cx.notify();
}
fn pre_resolve_completion_documentation(
buffer: Model<Buffer>,
completions: Arc<RwLock<Box<[Completion]>>>,
matches: Arc<[StringMatch]>,
editor: &Editor,
cx: &mut ViewContext<Editor>,
) -> Task<()> {
let settings = EditorSettings::get_global(cx);
if !settings.show_completion_documentation {
return Task::ready(());
}
let Some(provider) = editor.completion_provider.as_ref() else {
return Task::ready(());
};
let resolve_task = provider.resolve_completions(
buffer,
matches.iter().map(|m| m.candidate_id).collect(),
completions.clone(),
cx,
);
cx.spawn(move |this, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
}
})
}
fn attempt_resolve_selected_completion_documentation(
fn resolve_selected_completion(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
let settings = EditorSettings::get_global(cx);
if !settings.show_completion_documentation {
return;
}
let completion_index = self.matches[self.selected_item].candidate_id;
let Some(provider) = provider else {
return;
};
let Some(documentation_resolve) = self
.selected_completion_documentation_resolve_debounce
.as_ref()
else {
let Some(completion_resolve) = self.selected_completion_resolve_debounce.as_ref() else {
return;
};
@@ -1239,7 +1179,7 @@ impl CompletionsMenu {
EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
let delay = Duration::from_millis(delay_ms);
documentation_resolve.lock().fire_new(delay, cx, |_, cx| {
completion_resolve.lock().fire_new(delay, cx, |_, cx| {
cx.spawn(move |this, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
@@ -2134,7 +2074,6 @@ impl Editor {
auto_signature_help: None,
find_all_references_task_sources: Vec::new(),
next_completion_id: 0,
completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
next_inlay_id: 0,
code_action_providers,
available_code_actions: Default::default(),
@@ -2215,7 +2154,6 @@ impl Editor {
addons: HashMap::default(),
_scroll_cursor_center_top_bottom_task: Task::ready(()),
text_style_refinement: None,
active_line_trailer_provider: None,
};
this.tasks_update_task = Some(this.refresh_runnables(cx));
this._subscriptions.extend(project_subscriptions);
@@ -2504,16 +2442,6 @@ impl Editor {
self.refresh_inline_completion(false, false, cx);
}
pub fn set_active_line_trailer_provider<T>(
&mut self,
provider: Option<T>,
_cx: &mut ViewContext<Self>,
) where
T: ActiveLineTrailerProvider + 'static,
{
self.active_line_trailer_provider = provider.map(|provider| Box::new(provider) as Box<_>);
}
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
self.placeholder_text.as_deref()
}
@@ -3003,7 +2931,7 @@ impl Editor {
let start;
let end;
let mode;
let auto_scroll;
let mut auto_scroll;
match click_count {
1 => {
start = buffer.anchor_before(position.to_point(&display_map));
@@ -3039,6 +2967,7 @@ impl Editor {
auto_scroll = false;
}
}
auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
let point_to_delete: Option<usize> = {
let selected_points: Vec<Selection<Point>> =
@@ -4550,9 +4479,9 @@ impl Editor {
let sort_completions = provider.sort_completions();
let id = post_inc(&mut self.next_completion_id);
let task = cx.spawn(|this, mut cx| {
let task = cx.spawn(|editor, mut cx| {
async move {
this.update(&mut cx, |this, _| {
editor.update(&mut cx, |this, _| {
this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
})?;
let completions = completions.await.log_err();
@@ -4570,34 +4499,14 @@ impl Editor {
if menu.matches.is_empty() {
None
} else {
this.update(&mut cx, |editor, cx| {
let completions = menu.completions.clone();
let matches = menu.matches.clone();
let delay_ms = EditorSettings::get_global(cx)
.completion_documentation_secondary_query_debounce;
let delay = Duration::from_millis(delay_ms);
editor
.completion_documentation_pre_resolve_debounce
.fire_new(delay, cx, |editor, cx| {
CompletionsMenu::pre_resolve_completion_documentation(
buffer,
completions,
matches,
editor,
cx,
)
});
})
.ok();
Some(menu)
}
} else {
None
};
this.update(&mut cx, |this, cx| {
let mut context_menu = this.context_menu.write();
editor.update(&mut cx, |editor, cx| {
let mut context_menu = editor.context_menu.write();
match context_menu.as_ref() {
None => {}
@@ -4610,19 +4519,20 @@ impl Editor {
_ => return,
}
if this.focus_handle.is_focused(cx) && menu.is_some() {
let menu = menu.unwrap();
if editor.focus_handle.is_focused(cx) && menu.is_some() {
let mut menu = menu.unwrap();
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
*context_menu = Some(ContextMenu::Completions(menu));
drop(context_menu);
this.discard_inline_completion(false, cx);
editor.discard_inline_completion(false, cx);
cx.notify();
} else if this.completion_tasks.len() <= 1 {
} else if editor.completion_tasks.len() <= 1 {
// If there are no more completion tasks and the last menu was
// empty, we should hide it. If it was already hidden, we should
// also show the copilot completion when available.
drop(context_menu);
if this.hide_context_menu(cx).is_none() {
this.update_visible_inline_completion(cx);
if editor.hide_context_menu(cx).is_none() {
editor.update_visible_inline_completion(cx);
}
}
})?;
@@ -7370,7 +7280,7 @@ impl Editor {
.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
pub fn cut_common(&mut self, cx: &mut ViewContext<Self>) -> ClipboardItem {
let mut text = String::new();
let buffer = self.buffer.read(cx).snapshot(cx);
let mut selections = self.selections.all::<Point>(cx);
@@ -7414,11 +7324,38 @@ impl Editor {
s.select(selections);
});
this.insert("", cx);
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
text,
clipboard_selections,
));
});
ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
}
pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
let item = self.cut_common(cx);
cx.write_to_clipboard(item);
}
pub fn kill_ring_cut(&mut self, _: &KillRingCut, cx: &mut ViewContext<Self>) {
self.change_selections(None, cx, |s| {
s.move_with(|snapshot, sel| {
if sel.is_empty() {
sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
}
});
});
let item = self.cut_common(cx);
cx.set_global(KillRing(item))
}
pub fn kill_ring_yank(&mut self, _: &KillRingYank, cx: &mut ViewContext<Self>) {
let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
(kill_ring.text().to_string(), kill_ring.metadata_json())
} else {
return;
}
} else {
return;
};
self.do_paste(&text, metadata, false, cx);
}
pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
@@ -11859,6 +11796,10 @@ impl Editor {
self.blame.as_ref()
}
pub fn show_git_blame_gutter(&self) -> bool {
self.show_git_blame_gutter
}
pub fn render_git_blame_gutter(&mut self, cx: &mut WindowContext) -> bool {
self.show_git_blame_gutter && self.has_blame_entries(cx)
}
@@ -11870,21 +11811,6 @@ impl Editor {
&& self.has_blame_entries(cx)
}
pub fn render_active_line_trailer(
&mut self,
style: &EditorStyle,
cx: &mut WindowContext,
) -> Option<AnyElement> {
if !self.newest_selection_head_on_empty_line(cx) || self.has_active_inline_completion(cx) {
return None;
}
let focus_handle = self.focus_handle.clone();
self.active_line_trailer_provider
.as_mut()?
.render_active_line_trailer(style, &focus_handle, cx)
}
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
self.blame()
.map_or(false, |blame| blame.read(cx).has_generated_entries())
@@ -13782,7 +13708,9 @@ impl CodeActionProvider for Model<Project> {
range: Range<text::Anchor>,
cx: &mut WindowContext,
) -> Task<Result<Vec<CodeAction>>> {
self.update(cx, |project, cx| project.code_actions(buffer, range, cx))
self.update(cx, |project, cx| {
project.code_actions(buffer, range, None, cx)
})
}
fn apply_code_action(
@@ -14426,15 +14354,16 @@ impl ViewInputHandler for Editor {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
adjusted_range: &mut Option<Range<usize>>,
cx: &mut ViewContext<Self>,
) -> Option<String> {
Some(
self.buffer
.read(cx)
.read(cx)
.text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
.collect(),
)
let snapshot = self.buffer.read(cx).read(cx);
let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
if (start.0..end.0) != range_utf16 {
adjusted_range.replace(start.0..end.0);
}
Some(snapshot.text_for_range(start..end).collect())
}
fn selected_text_range(
@@ -14682,7 +14611,8 @@ impl ViewInputHandler for Editor {
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
+ self.gutter_dimensions.width;
+ self.gutter_dimensions.width
+ self.gutter_dimensions.margin;
let y = line_height * (start.row().as_f32() - scroll_position.y);
Some(Bounds {
@@ -15142,4 +15072,7 @@ fn check_multiline_range(buffer: &Buffer, range: Range<usize>) -> Range<usize> {
}
}
pub struct KillRing(ClipboardItem);
impl Global for KillRing {}
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);

View File

@@ -18,6 +18,7 @@ pub struct EditorSettings {
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f32,
pub autoscroll_on_clicks: bool,
pub scroll_sensitivity: f32,
pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
@@ -222,6 +223,10 @@ pub struct EditorSettingsContent {
///
/// Default: 3.
pub vertical_scroll_margin: Option<f32>,
/// Whether to scroll when clicking near the edge of the visible text area.
///
/// Default: false
pub autoscroll_on_clicks: Option<bool>,
/// Scroll sensitivity multiplier. This multiplier is applied
/// to both the horizontal and vertical delta values while scrolling.
///

View File

@@ -31,8 +31,8 @@ use project::{
project_settings::{LspSettings, ProjectSettings},
};
use serde_json::{self, json};
use std::sync::atomic;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::{self, AtomicBool};
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
use unindent::Unindent;
use util::{
@@ -10541,6 +10541,221 @@ async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
}
#[gpui::test]
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
resolve_provider: Some(true),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
cx.simulate_keystroke(".");
let default_commit_characters = vec!["?".to_string()];
let default_data = json!({ "very": "special"});
let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
let default_edit_range = lsp::Range {
start: lsp::Position {
line: 0,
character: 5,
},
end: lsp::Position {
line: 0,
character: 5,
},
};
let resolve_requests_number = Arc::new(AtomicUsize::new(0));
let expect_first_item = Arc::new(AtomicBool::new(true));
cx.lsp
.server
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
let closure_default_data = default_data.clone();
let closure_resolve_requests_number = resolve_requests_number.clone();
let closure_expect_first_item = expect_first_item.clone();
let closure_default_commit_characters = default_commit_characters.clone();
move |item_to_resolve, _| {
closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
let default_data = closure_default_data.clone();
let default_commit_characters = closure_default_commit_characters.clone();
let expect_first_item = closure_expect_first_item.clone();
async move {
if expect_first_item.load(atomic::Ordering::Acquire) {
assert_eq!(
item_to_resolve.label, "Some(2)",
"Should have selected the first item"
);
assert_eq!(
item_to_resolve.data,
Some(json!({ "very": "special"})),
"First item should bring its own data for resolving"
);
assert_eq!(
item_to_resolve.commit_characters,
Some(default_commit_characters),
"First item had no own commit characters and should inherit the default ones"
);
assert!(
matches!(
item_to_resolve.text_edit,
Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
),
"First item should bring its own edit range for resolving"
);
assert_eq!(
item_to_resolve.insert_text_format,
Some(default_insert_text_format),
"First item had no own insert text format and should inherit the default one"
);
assert_eq!(
item_to_resolve.insert_text_mode,
Some(lsp::InsertTextMode::ADJUST_INDENTATION),
"First item should bring its own insert text mode for resolving"
);
Ok(item_to_resolve)
} else {
assert_eq!(
item_to_resolve.label, "vec![2]",
"Should have selected the last item"
);
assert_eq!(
item_to_resolve.data,
Some(default_data),
"Last item has no own resolve data and should inherit the default one"
);
assert_eq!(
item_to_resolve.commit_characters,
Some(default_commit_characters),
"Last item had no own commit characters and should inherit the default ones"
);
assert_eq!(
item_to_resolve.text_edit,
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: default_edit_range,
new_text: "vec![2]".to_string()
})),
"Last item had no own edit range and should inherit the default one"
);
assert_eq!(
item_to_resolve.insert_text_format,
Some(lsp::InsertTextFormat::PLAIN_TEXT),
"Last item should bring its own insert text format for resolving"
);
assert_eq!(
item_to_resolve.insert_text_mode,
Some(default_insert_text_mode),
"Last item had no own insert text mode and should inherit the default one"
);
Ok(item_to_resolve)
}
}
}
}).detach();
let completion_data = default_data.clone();
let completion_characters = default_commit_characters.clone();
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let default_data = completion_data.clone();
let default_commit_characters = completion_characters.clone();
async move {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
items: vec![
lsp::CompletionItem {
label: "Some(2)".into(),
insert_text: Some("Some(2)".into()),
data: Some(json!({ "very": "special"})),
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
lsp::InsertReplaceEdit {
new_text: "Some(2)".to_string(),
insert: lsp::Range::default(),
replace: lsp::Range::default(),
},
)),
..lsp::CompletionItem::default()
},
lsp::CompletionItem {
label: "vec![2]".into(),
insert_text: Some("vec![2]".into()),
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
..lsp::CompletionItem::default()
},
],
item_defaults: Some(lsp::CompletionListItemDefaults {
data: Some(default_data.clone()),
commit_characters: Some(default_commit_characters.clone()),
edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
default_edit_range,
)),
insert_text_format: Some(default_insert_text_format),
insert_text_mode: Some(default_insert_text_mode),
}),
..lsp::CompletionList::default()
})))
}
})
.next()
.await;
cx.condition(|editor, _| editor.context_menu_visible())
.await;
cx.run_until_parked();
cx.update_editor(|editor, _| {
let menu = editor.context_menu.read();
match menu.as_ref().expect("should have the completions menu") {
ContextMenu::Completions(completions_menu) => {
assert_eq!(
completions_menu
.matches
.iter()
.map(|c| c.string.as_str())
.collect::<Vec<_>>(),
vec!["Some(2)", "vec![2]"]
);
}
ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
}
});
assert_eq!(
resolve_requests_number.load(atomic::Ordering::Acquire),
1,
"While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
);
cx.update_editor(|editor, cx| {
editor.context_menu_first(&ContextMenuFirst, cx);
});
cx.run_until_parked();
assert_eq!(
resolve_requests_number.load(atomic::Ordering::Acquire),
2,
"After re-selecting the first item, another resolve request should have been sent"
);
expect_first_item.store(false, atomic::Ordering::Release);
cx.update_editor(|editor, cx| {
editor.context_menu_last(&ContextMenuLast, cx);
});
cx.run_until_parked();
assert_eq!(
resolve_requests_number.load(atomic::Ordering::Acquire),
3,
"After selecting the other item, another resolve request should have been sent"
);
}
#[gpui::test]
async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});

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