Compare commits

...

140 Commits

Author SHA1 Message Date
Conrad Irwin
a89156cbbe expr returns 1 when it calculates 0, who knew... 2025-10-24 20:22:58 -06:00
Conrad Irwin
892e8cddcc Why are you failing? 2025-10-24 20:14:01 -06:00
Conrad Irwin
4ed39540e7 re-run 2025-10-24 19:50:56 -06:00
Conrad Irwin
91c509ea5e Clippppppy 2025-10-24 16:55:18 -06:00
Conrad Irwin
54ca5e7c91 Oops 2025-10-24 16:44:40 -06:00
Conrad Irwin
38db09157e New approach to installing nextest? 2025-10-24 16:29:21 -06:00
Conrad Irwin
8a4d85eb82 Try gh_workflow generation 2025-10-24 00:16:12 -06:00
Conrad Irwin
6a983f657a Cache more aggressively 2025-10-23 23:44:51 -06:00
Conrad Irwin
6439ea7fb8 build again 2025-10-23 23:22:20 -06:00
Conrad Irwin
59d8458a24 New cache? 2025-10-23 22:30:18 -06:00
Conrad Irwin
be7c18234d Try new namespace builder 2025-10-23 22:14:17 -06:00
Danilo Leal
f11a3dcc97 settings_ui: Add small UI adjustments (#41065)
Super tiny changes that impact mostly Windows/Linux.

Release Notes:

- N/A
2025-10-24 01:08:26 +00:00
Danilo Leal
5aa82887ec rules library: Improve empty state & fix quirks on Windows (#41064)
Just got a new Windows machine and realized that the rules library empty
state was completly busted. Ended up also adding some little UI tweaks
to make it better for both Windows and Linux.

Release Notes:

- N/A
2025-10-23 21:45:48 -03:00
warrenjokinen
fe730e9129 Fix typo in Font Features description, s/b "OpenType" (#41058)
Fix typo (regression?), 
"Opentype" should be "OpenType"

Closes #ISSUE

Release Notes:

- N/A
2025-10-23 21:19:21 -03:00
John Tur
59a98bae3a Add attribution for code sourced from Windows Terminal (#41061)
Release Notes:

- N/A
2025-10-24 00:12:06 +00:00
Agus Zubiaga
1cf765e126 Revert: Spawn terminal process on background executor (#41060)
Reverts https://github.com/zed-industries/zed/pull/40774 and
https://github.com/zed-industries/zed/pull/40824 since they introduce a
bug where Nushell processes are leaked and Ctrl+C doesn't kill the
current process.

Release Notes:

- Fix a bug where nushell processes wouldn't get killed after closing a
terminal tab
2025-10-23 23:37:23 +00:00
Tom Planche
79eff1fe05 Make cursor move to duplicated line when duplicating line up (#41004)
![Screen Recording 2025-10-23 at 14 50
26](https://github.com/user-attachments/assets/3427aa06-faf4-4f76-a604-bfc5af30f8ce)

Closes #40919
Follow-up of #39610

Release Notes:
- When duplicating line up, fixed cursor to move to the duplicated line
2025-10-24 02:20:16 +03:00
Kunall Banerjee
af0d2ad491 docs: Fix small grammatical typo in JSX section (#41055)
Happened to notice this typo while going through the docs.

Release Notes:

- N/A

---

💖
2025-10-24 00:38:32 +02:00
Anthony Eid
f0ac54e8a5 settings_ui: Fix memory leak (#41036)
Closes #40351

The leak mainly showed up in the appearance page because it had a lot of
dropdown menus. The problem occurred because the drop-down menus were
creating a new entity on each frame instead of using the
`window.use_state...` API.

Release Notes:

- settings ui: Fixed memory leak in UI

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-10-23 21:31:20 +00:00
Nia
68707ffc74 crashes: Avoid crash handler on detached threads (#40883)
Set a TLS bit to skip invoking the crash handler when a detached thread
panics.

cc @P1n3appl3 - is this at odds with what we need the crash handler to
do?

May close #39289, cannot repro without a nightly build

Release Notes:

- Fixed extension panics crashing Zed on Linux

Co-authored-by: dino <dinojoaocosta@gmail.com>
2025-10-23 21:04:22 +00:00
Conrad Irwin
1ce9a85a1a git: Only save dirty files during staging (#41047)
Closes #40581

Release Notes:

- git: No longer save clean files when staging (to avoid triggering
unnecessary rebuilds in external file watchers like vite)
2025-10-23 20:53:19 +00:00
Joseph T. Lyons
1966d4c818 Add an issue template for Git bugs (#41048)
Release Notes:

- N/A
2025-10-23 16:22:32 -04:00
Smit Barmase
b6a18671dc settings_ui: Fix settings window doesn't inherit Zed icon (#41031)
Closes #40946

Release Notes:

- N/A
2025-10-24 01:32:09 +05:30
Jakub Konka
c0ff8ef8e9 remote: Do not compress remote by default when building from source (#40994)
Release Notes:

- N/A
2025-10-23 21:26:34 +02:00
Lukas Wirth
66ec0fc0e1 Revert zero-width non-joiner insertion in text shaping (#41043)
Reverts parts of https://github.com/zed-industries/zed/pull/39928
Closes https://github.com/zed-industries/zed/issues/40987

Release Notes:

- Fixed some fonts rendering with absurd spacing on MacOS
2025-10-23 19:22:28 +00:00
Devdatta Talele
435eab6896 Fix ESLint linebreak-style errors by preserving line endings in LSP communication (#38773)
Closes https://github.com/zed-industries/zed/issues/38453

Current `Buffer` API only allows getting buffer text with `\n` line
breaks — even if the `\r\n` was used in the original file's text.

This it not correct in certain cases like LSP formatting, where language
servers need to have original document context for e.g. formatting
purposes.

Added new `Buffer` API, replaced all buffer LSP registration places with
the new one and added more tests.

Release Notes: 

- Fixed ESLint linebreak-style errors by preserving line endings in LSP
communication

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2025-10-23 19:11:48 +00:00
Sean Hagstrom
55bc679c19 docs: Ensure macOS and Linux keybindings are escaped in HTML (#39802)
Closes #39654

Release Notes:

- Fixed the formatting of macOS and Linux keybindings in the Zed docs to
escape the backslash character when templating.
2025-10-23 14:13:35 -04:00
Lukas Wirth
6aaf19f276 multi_buffer: Split multi_buffer into more modules (#41033)
There are a of separate APIs in this, partially interleaved making it
difficult to grasp.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 17:57:52 +00:00
Marshall Bowers
d83ed4e03e docs: Update docs for theme_overrides setting (#41038)
This PR updates the docs to reference the `theme_overrides` setting
instead of the old `experimental.theme_overrides` setting.

Release Notes:

- N/A
2025-10-23 17:52:35 +00:00
Bennet Fenner
11eba64e68 Rename assistant_context crate to assistant_text_thread (#41024)
Previously we had `Context` and `ContextStore` in both `agent_ui` (used
to store context for the inline assistant) and `assistant_context` (used
for text threads) which is confusing.
This PR makes it so that the `assistant_context` concepts are now called
`TextThread*`, the crate was renamed to `assistant_text_thread`

Release Notes:

- N/A
2025-10-23 17:17:41 +00:00
Cole Miller
63fe1eae59 Fix overly noisy direnv error notification (#41029)
Updates #40531, restoring the previous behavior which didn't surface an
error when no direnv binary was found.

Release Notes:

- N/A
2025-10-23 17:03:55 +00:00
Cole Miller
8b6f3ec647 Fix the project diff sometimes missing updates (#40662)
This PR does two related things:

- First, it gets rid of the undifferentiated `RepositoryEvent::Updated`
in favor of three new events that have clearer definitions:
`BranchChanged`, `StashEntriesChanged`, and `StatusesChanged`. An
implication of this is that we no longer emit a `RepositoryEvent` unless
some git state changed; previously we would emit `RepositoryUpdated`
after doing a git status scan even if no statuses changed.
- Second, it changes the subscription strategy of the project diff to
make it update more robustly. Previously, the project diff only
subscribed to the `GitStore`, so it relied on getting a `GitStoreEvent`
when some buffer's diff hunks changed, even if the git status of the
buffer's file didn't change (e.g. a second hunk in a file that was
already modified). After this PR, it also subscribes to the individual
`BufferDiff` entities for buffers that have a git status, so the
`GitStore` is freed from that responsibility. This also fixes some real
cases where the previous strategy was not effective in keeping the
project diff up to date (captured in a test).

Release Notes:

- Fixed some cases where the project diff would fail to update in
response to git events.
2025-10-23 16:46:27 +00:00
Lukas Wirth
a66098b485 gpui: Revert reuse_prepaint change of #40767 (#41025)
c95ae84d91 (r2455674159)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 16:23:35 +00:00
Lukas Wirth
b519ab2758 rope: Improve chunk slicing panic messages (#41023)
We still see a bunch of panics here but the default slicing panic
doesn't tell which side of the range is bad

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 16:17:11 +00:00
Jakub Konka
023ac1b649 Revert "Use ShellKind::try_quote whenever we need to quote shell args" (#41022)
Reverts zed-industries/zed#40912

Closes https://github.com/zed-industries/zed/issues/41010
2025-10-23 16:06:47 +00:00
Lukas Wirth
738e248109 gpui: Small perf optimizations (#40767)
Some random findings based on profiling

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 13:26:27 +00:00
Kirill Bulatov
4f0a44896a Fix anchor-related panic when gathering applicable inlay chunks (#41002)
Before, inlay chunks were retrieved from the cache based on actualized
anchor ranges, but using an old buffer snapshot. Now, update all chunks
and snapshot to the actual before returning the applicable ones.

Follow-up of https://github.com/zed-industries/zed/pull/40183

Release Notes:

- N/A
2025-10-23 12:51:07 +00:00
Viraj Bhartiya
9a6397fb17 Add keybindings for menu navigation in vim.json (#40877)
- Added "ctrl-p" for selecting the previous menu item
- Added "ctrl-n" for selecting the next menu item

Closes #40619 

Release Notes:
- Ctrl+P now moves to the previous result; Ctrl+N moves to the next.
2025-10-23 13:21:32 +02:00
paneutral
93ef1947b5 vim: Fix cursor movement after entering Helix normal mode (#40528)
Closes #40009 

Release Notes:

- `vim::NormalBefore` now enters `helix_normal` correctly.
2025-10-23 11:15:12 +00:00
Abderrahmane TAHRI JOUTI
8c1b4cb1cd Re-order Helix keymaps and add alt-o/i/p/n (#40527)
Release Notes:

- helix: Re-ordered `helix_normal || helix_select` keybindings to follow the
same order as the keymap on the helix-editor
[documentation](https://docs.helix-editor.com/keymap.html).
- helix: Added `alt-o` & `alt-i` to Select larger and smaller syntax node
respectively
- helix: Added `alt-p` & `alt-n` to Select Next Syntax Node and Previous Syntax
Node respectively



--- 

The new main helix normal & select context looks like follows

```jsonc
{
    "context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
    "bindings": {
      // Movement
      "h": "vim::WrappingLeft",
      "left": "vim::WrappingLeft",
      "l": "vim::WrappingRight",
      "right": "vim::WrappingRight",
      "t": ["vim::PushFindForward", { "before": true, "multiline": true }],
      "f": ["vim::PushFindForward", { "before": false, "multiline": true }],
      "shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
      "shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
      "alt-.": "vim::RepeatFind",
      
      // Changes
      "shift-r": "editor::Paste",
      "`": "vim::ConvertToLowerCase",
      "alt-`": "vim::ConvertToUpperCase",
      "insert": "vim::InsertBefore",
      "shift-u": "editor::Redo",
      "ctrl-r": "vim::Redo",
      "y": "vim::HelixYank",
      "p": "vim::HelixPaste",
      "shift-p": ["vim::HelixPaste", { "before": true }],            
      ">": "vim::Indent",
      "<": "vim::Outdent",
      "=": "vim::AutoIndent",
      "d": "vim::HelixDelete",
      "c": "vim::HelixSubstitute",
      "alt-c": "vim::HelixSubstituteNoYank",
      
      // Selection manipulation
      "s": "vim::HelixSelectRegex",
      "alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
      ";": "vim::HelixCollapseSelection",
      "alt-;": "vim::OtherEnd",
      ",": "vim::HelixKeepNewestSelection",
      "shift-c": "vim::HelixDuplicateBelow",
      "alt-shift-c": "vim::HelixDuplicateAbove",
      "%": "editor::SelectAll",
      "x": "vim::HelixSelectLine",
      "shift-x": "editor::SelectLine",
      "ctrl-c": "editor::ToggleComments",
      "alt-o": "editor::SelectLargerSyntaxNode",
      "alt-i": "editor::SelectSmallerSyntaxNode",
      "alt-p": "editor::SelectPreviousSyntaxNode",
      "alt-n": "editor::SelectNextSyntaxNode",

      // Goto mode
      "g e": "vim::EndOfDocument",
      "g h": "vim::StartOfLine",
      "g l": "vim::EndOfLine",
      "g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
      "g t": "vim::WindowTop",
      "g c": "vim::WindowMiddle",
      "g b": "vim::WindowBottom",
      "g r": "editor::FindAllReferences", // zed specific
      "g n": "pane::ActivateNextItem",
      "shift-l": "pane::ActivateNextItem",      
      "g p": "pane::ActivatePreviousItem",
      "shift-h": "pane::ActivatePreviousItem",
      "g .": "vim::HelixGotoLastModification", // go to last modification
      
      // Window mode
      "space w h": "workspace::ActivatePaneLeft",
      "space w l": "workspace::ActivatePaneRight",
      "space w k": "workspace::ActivatePaneUp",
      "space w j": "workspace::ActivatePaneDown",
      "space w q": "pane::CloseActiveItem",
      "space w s": "pane::SplitRight",
      "space w r": "pane::SplitRight",
      "space w v": "pane::SplitDown",
      "space w d": "pane::SplitDown",

      // Space mode
      "space f": "file_finder::Toggle",
      "space k": "editor::Hover",
      "space s": "outline::Toggle",
      "space shift-s": "project_symbols::Toggle",
      "space d": "editor::GoToDiagnostic",
      "space r": "editor::Rename",
      "space a": "editor::ToggleCodeActions",
      "space h": "editor::SelectAllMatches",
      "space c": "editor::ToggleComments",
      "space p": "editor::Paste",
      "space y": "editor::Copy",

      // Other
      ":": "command_palette::Toggle",
      "m": "vim::PushHelixMatch",
      "]": ["vim::PushHelixNext", { "around": true }],
      "[": ["vim::PushHelixPrevious", { "around": true }],
      "g q": "vim::PushRewrap",
      "g w": "vim::PushRewrap",
      // "tab": "pane::ActivateNextItem",
      // "shift-tab": "pane::ActivatePrevItem",
    }
  }
  ```
2025-10-23 12:55:26 +02:00
Kirill Bulatov
3bb4c94ed4 Revert "Round the scroll offset in editor to fix jumping text (#40401)" (#40982)
This reverts commit 3da4cddce2.

The scrolling is ~30% less for the same gesture, and I'm not using
anything lodpi:


https://github.com/user-attachments/assets/b19521fc-9e29-4bfd-9660-dc1e4c8ae846


Release Notes:

- N/A
2025-10-23 09:25:36 +00:00
Lukas Wirth
16f7bd0a2e editor: Translate utf16 to utf8 offsets in copy_highlight_json (#40981)
Fixes ZED-2FM

Release Notes:

- Fixed panic in copy highlight json action
2025-10-23 08:46:50 +00:00
Lukas Wirth
c529a066bf gpui: Arc GlobalElementId (#40979)
This shrinks it from roughly a ~kilobyte to 8 byte, removing a bunch of
memmoves emitted by the compiler. Also `Arc`'s it instead of boxing as
we do clone it a couple times here and there, making that also a fair
bit cheaper

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-23 07:58:33 +00:00
Lukas Wirth
278032c6b8 extension_host: Run extensions on the tokio threadpool (#40936)
Fixes ZED-12D

`wasmtime_wasi` might call into tokio futures (to sleep for example)
which requires access to the tokio runtime. So we are required to run
these extensions in the tokio thread pool

Release Notes:

- Fixed extensions causing zed to occasionally panic
2025-10-23 09:41:05 +02:00
Anthony Eid
5a05986479 debugger: Fix debug scenario picker not showing language subtitles (#40977)
### Before 
<img width="544" height="403" alt="Screenshot 2025-10-23 at 2 58 44 AM"
src="https://github.com/user-attachments/assets/f5a69d27-e54a-4c1e-80f7-5cfff5b0bd47"
/>

### After
<img width="550" height="372" alt="Screenshot 2025-10-23 at 3 08 59 AM"
src="https://github.com/user-attachments/assets/33dd9c5e-054e-4ed1-ba1e-16746a5a697a"
/>

I also changed the debug picker to use a material list to cover the edge
case where there isn't a subtitle for an entry

Release Notes:

- debugger: Fix debug scenario picker not showing language subtitles
2025-10-23 07:37:37 +00:00
Anthony Eid
05c2cc0254 settings_ui: Enable editing project settings for worktrees without setting file (#40971)
I made three significant changes in this PR. 

1. `SettingsWindow::fetch_files` now creates
`SettingsUiFile::Project(..)` for any worktree that contains no project
settings.
2. `update_settings_file` now creates an empty settings file if a
worktree doesn't contain one.
3. `open_current_settings_file` also creates a settings file if the
current one doesn't exist.

Release Notes:

- settings ui: Enable editing project settings for worktrees that don't
have a project setting file.
2025-10-23 02:23:12 -04:00
Anthony Eid
1edb1b3896 settings ui: Update file headers when adding or removing projects (#40968)
This PR gets the `SettingsWindow` struct to subscribe to all
`Entity<Project>` events and any future project entities that are
created. When a project emits an event that signals a worktree has been
added or removed, the settings window refetches all settings files it
can find.

This fixes a bug where the settings ui would notice some project
settings that were created or opened after the `SettingsWindow` has been
initialized.

I also renamed `LOCAL` file mask to `PROJECT` to be inline with the
`SettingsFile` naming convention.

Release Notes:

- settings ui: Fix bug where project setting files wouldn't be detected
if they were created or opened after while an active settings window is
open
2025-10-23 00:49:36 -04:00
Jakub Konka
8f4646d6c3 Use ShellKind::try_quote whenever we need to quote shell args (#40912)
Using `shlex` unconditionally is dangerous as it assumes the underlying
shell is POSIX which is not the case for PowerShell, CMD, or Nushell.
Therefore, whenever we want to quote the args we should utilise our
helper `util::shell::ShellKind::try_quote` which takes into account
which shell is being used to actually exec/spawn the invocation.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-23 06:44:42 +02:00
Cole Miller
18daa9a839 Simplify environment loading code (#40531)
This is a refactoring PR to simplify our environment loading code by:

- Getting rid of `EnvironmentErrorMessage` in favor of using
`anyhow::Result` everywhere, with a separate `mpsc` channel to
communicate statuses that will be shown in the activity indicator
- Inlining some functions that were only called once to reduce
indirection
- Removing the separate `direnv` module

Release Notes:

- N/A
2025-10-23 03:57:33 +00:00
Cole Miller
bf63ff2b91 Fix path for vscode-html-language-server when found on PATH (#40832)
Don't prepend the worktree root when using an absolute path from
`Worktree::which`, since that does the wrong thing when running in
wasmtime given two Windows absolute paths. Also don't pass this path to
`node`, since when npm installed it's a sh/cmd wrapper not a JS file.

Part of #39153, also needs a fix on the vscode-langservers-extracted
side (missing shebang for the vscode-html-language-server script).

Release Notes:

- Fixed Zed failing to run the HTML language server in some cases.
2025-10-22 22:44:25 -04:00
Danilo Leal
f9e0642a72 settings_ui: Adjust warning banner design (#40952)
Just tidying this up a bit. Really have to fix this Banner component at
some point 😅 Having to add some spacing hacks to make it perfect here
that are not ideal and should be baked into the component.

Release Notes:

- N/A
2025-10-23 00:23:51 +00:00
Danilo Leal
bada88c5b3 Make the rules library window more consistent with the settings UI (#40948)
Now that we have two surface areas that open as separate windows, it's
important they're consistent with one another. This PR make the settings
UI and rules library windows more similar by having them use the same
minimum window size and similar styles for their navbar given they have
fundamentally the same design (nav on the left and content on the
right).

Release Notes:

- N/A
2025-10-22 20:57:01 -03:00
Moo, Kachon
6b8f8592ea agent_ui: Remove ellipses from some menu entries (#40858)
<img width="335" height="236" alt="image"
src="https://github.com/user-attachments/assets/5e09e9ed-f0f3-48df-ac22-032edfc6c114"
/>

Release Notes:
This PR fixes inconsistent use of trailing ellipsis (…) in the MCP
Server menu.
Previously, some menu items (like Add Custom Server…) used hardcoded
ellipses even though they didn’t trigger additional dialogs or steps.
This change removes unnecessary ellipses to align with standard UI/UX
conventions used across Zed’s menus.

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
2025-10-22 23:54:24 +00:00
Mikayla Maki
4fd4cbbfb7 gpui: Add focus-visible selector support (#40940)
Release Notes:

- N/A

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-22 23:36:18 +00:00
Lukas Wirth
044701e3a5 rope: Implement Rope::is_char_boundary via chars bitmap (#40945)
Slightly more efficient. No new tests as we already have tests
verifiying this.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 23:05:43 +00:00
Smit Barmase
a96bf504e0 theme: Fix entry could appear transparent on hover with certain themes (#40944)
Follow-up: https://github.com/zed-industries/zed/pull/34655  

We should use an opaque fallback color for `panel.overlay_hover`. This
helps when a custom theme doesn’t provide it, nor `element.hover`. For
example, VSCode’s default modern dark theme doesn’t include an
`element.hover` color after import.

Release Notes:

- Fixed an issue where the project panel’s sticky entry could appear
transparent on hover with certain themes.
2025-10-23 04:23:25 +05:30
Lukas Wirth
c16f2a1a29 project: Normalize Path env var to PATH in shell env on windows (#40720)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 22:47:16 +00:00
Anthony Eid
ca4103246f settings_ui: Fix file header from showing duplicate display names (#40943)
After clicking on the file drop-down and selecting a file, both the
selected file and the first drop-down entry would be the same file name
instead of overwriting a file name.

Release Notes:

- settings ui: Fix bug where duplicate file names showed in the header
files
2025-10-22 22:33:45 +00:00
Danilo Leal
731237222e agent_ui: Focus the message editor after regenerating a user message (#40938)
Release Notes:

- agent: Improved the editing previous messages UX by focusing in the
agent panel's message editor after regenerating a prompt, instead of
moving focus to the nearest regular buffer.
2025-10-22 19:29:35 -03:00
Smit Barmase
2096f256f2 editor: Reduce selection opacity when editor is not focused (#40925)
Focus:
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/97b0c7ed-8ad4-400c-9f36-01d8bd9b362d"
/>

Unfocus:
<img width="420" alt="image"
src="https://github.com/user-attachments/assets/19805293-b419-4669-8a93-e9cf41900403"
/>

Release Notes:

- Reduced selection opacity when the editor is out of focus to make
inactive states clearer.
2025-10-23 03:57:10 +05:30
Ted Robertson
7880e2b961 docs: Clarify providers in edit-prediction.md (#39655)
Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-10-22 21:09:34 +00:00
Joseph T. Lyons
ab22478ed4 Update extension docs to mention BSD 3-Clause is a valid license (#40934)
Release Notes:

- N/A
2025-10-22 20:34:38 +00:00
Ben Kunkle
6ed9c0271d Don't migrate empty formatter array (#40932)
Follow up for #40409
Fix for
https://github.com/zed-industries/zed/issues/40874#issuecomment-3433759849

Release Notes:

- Fixed an issue where having an empty formatter array in your settings
`"formatter": []` would result in an erroneous prompt to migrate
settings
2025-10-22 20:01:45 +00:00
Joseph T. Lyons
93136a9aaa Update extension docs to mention GNU GPLv3 is a valid license (#40933)
Merge after: 

- https://github.com/zed-industries/extensions/pull/3641

Release Notes:

- N/A
2025-10-22 15:59:39 -04:00
Finn Evers
f393138711 Fix keybind hints flickering in certain scenarios (#40927)
Closes #39172

This refactors when we resolve UI keybindings in an effort to reduce
flickering whilst painting these: Previously, we would always resolve
these upon creating the binding. This could lead to cases where the
corresponding context was not yet available and no binding could be
resolved, even if the binding was then available on the next presented
frame. Following that, on the next rerender of whatever requested this
keybinding, the keybind for that context would then be found, we would
render that and then also win a layout shift in that process, as we went
from nothing rendered to something rendered between these frames.

With these changes, this now happens less often, because we only look
for the keybinding once the context can actually be resolved in the
window.

| Before | After | 
| --- | --- |
|
https://github.com/user-attachments/assets/adebf8ac-217d-4c7f-ae5a-bab3aa0b0ee8
|
https://github.com/user-attachments/assets/70a82b4b-488f-4a9f-94d7-b6d0a49aada9
|

Also reduced cloning in the keymap editor in this process, since that
requiered changing due to this anyway.

Release Notes:

- Fixed some cases where keybinds would appear with a slight delay,
causing a flicker in the process
2025-10-22 19:52:38 +00:00
Kirill Bulatov
ed5b9a4705 Rework inlay hints system (#40183)
Closes https://github.com/zed-industries/zed/issues/40047
Closes https://github.com/zed-industries/zed/issues/24798
Closes https://github.com/zed-industries/zed/issues/24788

Before, each editor, even if it's the same buffer split in 2, was
querying for inlay hints separately, and storing the whole inlay hint
twice, in `Editor`'s `display_map` and its `inlay_hint_cache` fields.

Now, instead of `inlay_hint_cache`, each editor maintains a minimal set
of metadata (which area was queried by what task) instead, and all LSP
inlay hint data had been moved into `LspStore`, both local and remote
flavors store the data.
This allows Zed, as long as a buffer is open, to reuse the inlay hint
data similar to how document colors and code lens are now stored and
reused.

Unlike other reused LSP data, inlay hints data is the first one that's
possible to query by document ranges and previous version had issue with
caching and invalidating such ranges already queried for.
The new version re-approaches this by chunking the file into row ranges,
which are queried based on the editors' visible area.

Among the corresponding refactoring, one notable difference in inlays
display are multi buffers: buffers in them are not
[registered](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen)
in the language server until a caret/selection is placed inside their
excerpts inside the multi buffer.

New inlays code does not query language servers for unregistered
buffers, as servers usually respond with empty responses or errors in
such cases.

Release Notes:

- Reworked inlay hints to be less error-prone

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
2025-10-22 22:34:15 +03:00
Finn Evers
5738bde3ce gpui: Make Empty request layout with Display::None by default (#40900)
Release Notes:

- N/A
2025-10-22 20:54:29 +02:00
Bennet Fenner
6622902964 agent: Only show compatible tools in profile selector (#40917)
In practice this just hides the web search tool when not using the Zed
provider

Release Notes:

- Fixed an issue where the web search tool would show up in the profile
selector even when not using a model via Zed Pro
2025-10-22 18:17:33 +00:00
Ben Kunkle
cb7881ec0b Fix migration from #40409 for users who haven't been migrated yet (#40916)
Closes #40874

Release Notes:

- Fixed an issue where migrating settings after v0.208.5+ would
spuriously enable prettier. This fix only affects those who have not
updated or migrated yet. For those who have already updated to version
v0.208.5 or later, placing `"formatter": []` in your settings in the
affected languages will fix the issue.

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2025-10-22 18:17:01 +00:00
Bennet Fenner
c60343af71 eval: Port to agent2 (#40704)
Release Notes:

- N/A
2025-10-22 17:55:26 +00:00
Marshall Bowers
4a93719b6b Upgrade async-tar to v0.5.1 (#40911)
This PR switches us back to the upstream version of `async-tar` and
upgrades to v0.5.1.

This version has the patch we need:
0c18195639.

Release Notes:

- N/A
2025-10-22 17:13:28 +00:00
Remco Smits
d558005058 markdown: Add support for colspan and rowspan for HTML tables (#39898)
Closes https://github.com/zed-industries/zed/issues/39837

This PR adds support for `colspan` feature that is only supported for
HTML tables. I also fixed an edge case where the right side border was
not applied because it didn't match the total column count.

**Before**
<img width="725" height="179"
alt="499166907-385cc787-fc89-4e6d-bf06-c72c3c0bd775"
src="https://github.com/user-attachments/assets/69586053-9893-4c92-aa89-7830d2bc7a6d"
/>

**After**
<img width="1165" height="180" alt="Screenshot 2025-10-21 at 22 51 55"
src="https://github.com/user-attachments/assets/f40686e7-d95b-45a6-be42-e226e2f77483"
/>

```html
<table>
    <tr>
        <th rowspan="2">Region</th>
        <th colspan="2">Revenue</th>
        <th rowspan="2">Growth</th>
    </tr>
    <tr>
        <th>Q2 2024</th>
        <th>Q3 2024</th>
    </tr>
    <tr>
        <td>North America</td>
        <td>$2.8M</td>
        <td>$2.4B</td>
        <td>+85,614%</td>
    </tr>
    <tr>
        <td>Europe</td>
        <td>$1.2M</td>
        <td>$1.9B</td>
        <td>+158,233%</td>
    </tr>
    <tr>
        <td>Asia-Pacific</td>
        <td>$0.5M</td>
        <td>$1.4B</td>
        <td>+279,900%</td>
    </tr>
</table>
```

**TODO**:
- [x] Add tests for rending logic
- [x] Test all the tables again

cc @bennetbo

Release Notes:

- Markdown: Added support for `colspan` and `rowspan` for HTML tables

---------

Co-authored-by: Zed AI <ai@zed.nl>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
2025-10-22 13:10:37 -04:00
Donnie Adams
96a0db24d9 Add comment injections for gowork and gomod (#40842)
Release Notes:

- Add comment injections for go.mod and go.work

Signed-off-by: Donnie Adams <donnie@thedadams.com>
2025-10-22 11:07:20 -06:00
Affonso, Guilherme
23e9e32d65 emacs: Improve default keymap to better match the emacs behavior (#40631)
Hello,
I am having a great time setting up the editor, but with a few problems
related to the Emacs keymap.

In this PR I have compiled changes in the default `emacs.json` that I
believe make the onboarding smoother for incoming emacs users.
This includes points that may need further discussion and some breaking
changes, although nothing that cannot be reverted with a quick
`keymap.json` overwrite.

(Please let me know if it is better to split up the PR)

### 1. Avoid fallbacks to the default keymap
all platforms:
- `ctrl-g` activating `go_to_line::Toggle` when there is nothing to
cancel

linux / windows:
- `ctrl-x` activating `editor::Cut` on the 1 second timeout
- `ctrl-p` activating `file_finder::Toggle` when the cursor is on the
first character of the buffer
- `ctrl-n` activating `workspace::NewFile` when the cursor is on the
last character of the buffer

### 2. Make all move commands operate on full words
In the current Zed implementation some commands run on full words and
others on subwords.
Although ultimately a matter of user preference, I think it is sensible
to use full words as the default, since that is what is shipped with
emacs.

### ~~3. Cancel selections after copy/cut commands~~ Moved to #40904
Canceling the selection is the default emacs behavior, but the way to
achieve it might need some brushing.
Currently I am using `workspace::SendKeystrokes` to copy ->
cancel(`ctrl-g`), but this has the following problems:
- can only be used in the main buffer (since `editor::Cancel` would
typically close secondary buffers)
- may cause problems downstream if the user overwrites the `ctrl-g`
binding

### ~~4. Replace killring with normal cut/paste commands~~ Moved to
#40905
Ideally Zed would support emacs-like killrings (#25270 and #22490).
However, I understand that making an emacs emulator is not a project
goal, and the Zed team should have a bunch of tasks with higher
priority.

By using a unified clipboard and standard cut/paste commands, we can
provide an experience that is closer to the out-of-the-box emacs
behavior (#33351) while also avoiding some pitfalls of the current
killring implementation (#28715).

### 5. Promote some bindings to workspace commands
- `alt-x` as `command_palette::Toggle`
- `ctrl-x b` and `ctrl-x ctrl-b` as `tab_switcher::Toggle`

---

Release Notes:

- emacs: Fixed a problem where keys would fallback to their default
keymap binding on certain conditions
- emacs: Changed `alt-f` and `alt-b` to operate on full words, as in the
emacs default
- emacs: `alt-x`, `ctrl-x b`, and `ctrl-x ctrl-b` are now Workspace
bindings
2025-10-22 10:37:00 -06:00
Finn Evers
b207da5a71 gpui: Re-land uniform list scroll fixes (#40899)
Re-lands https://github.com/zed-industries/zed/pull/40719, fixes the
bugs that were discovered with it and improves some more stuff in that
area

Release Notes:

- Fixed a rare issue where the extension page would stutter while
scrolling.

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-10-22 16:33:16 +00:00
Danilo Leal
a24601903a agent: Improve discoverability of the quote selection action (#40897)
This PR renames the `agent::QuoteSelection` to
`agent::AddSelectionToThread` _and_ adds it as a menu item in both the
right-click context menu within regular buffers as well as the
"Selection" app menu.

We've received feedback in the past about how hard to discover this
feature is, and after watching [the Syntax podcast
crew](https://www.youtube.com/watch?v=bRK3PeVFfVE) recently struggle
with doing so—and then naturally looking for it in the context menu and
not finding it—it felt like time to push a change. I think the rename +
the availability in these places could help bringing it to surface more.

The same action can be done in Cursor through the `cmd-l` keybinding,
but in Zed, that triggers `editor::SelectLine`, which I don't want to
override by default. However, if you're using Cursor's keymap, then
`cmd-l` does trigger this action, as expected.

<img width="500" height="1812" alt="Screenshot 2025-10-22 at 12  01@2x"
src="https://github.com/user-attachments/assets/dfc2c41c-8d0a-4a1a-8ea1-1bd5d1aa1171"
/>


Release Notes:

- agent: Improves discoverability of the previously called "quote
selection" action—which allows to add a text selection in a buffer as
context within the agent panel—by renaming it to "add selection to
thread" and making it available from the right-click editor context menu
as well as the "Selection" app menu.
2025-10-22 12:56:11 -03:00
David Kleingeld
3a12122d1b Revert "keymaps: Update defaults for inline assist and signature help" (#40903)
Reverts zed-industries/zed#39587
2025-10-22 11:27:37 -04:00
Lukas Wirth
d0398da099 editor: Fix singleton multibuffer titles not being replicated (#40896)
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 14:59:30 +00:00
Joseph T. Lyons
69b2ee7bf0 Bump Zed to v0.211 (#40895)
Release Notes:

- N/A
2025-10-22 14:50:29 +00:00
Ajani Bilby
88bac4d5fc docs: Change tab character representation in docs (#40667)
The suggested `→` appears tiny, and almost looks like just a dot on my
monitor, and I got quite confused for a while thinking the
`whitespace_map.tab` setting wasn't working properly.
<img width="630" height="105" alt="Image"
src="https://github.com/user-attachments/assets/98feced2-39b3-4734-83e4-b4573b4e52c2"
/>

I think it would be really helpful if `⟶` was suggested instead since
that displays properly.
<img width="625" height="104" alt="Image"
src="https://github.com/user-attachments/assets/176886ab-cf88-4079-90a8-91a8e8182092"
/>

---

I am using `Fira Code` as my font on windows, however when I remove that
config to get the default font, it also still appears the same size. So
I don't believe this is just a font issue on my machine.
Thought I am using Windows, so I would be willing to believe this a
render issue specific to windows

Release Notes:

- N/A
2025-10-22 14:04:29 +00:00
Finn Evers
d53efe4e91 Revert "gpui: Fix uniform list scrolling with vertical padding present" (#40891)
Reverts zed-industries/zed#40719

This unveiled some bigger issues with the UniformList size computations,
which are more crucial than what was fixed here.

Release Notes:

- NOTE: BUGFIX "Fixed a rare issue where the extension page would
stutter while scrolling." was reverted due to some other issues
2025-10-22 13:35:25 +00:00
localcc
14b41b122f Fix JumpHost on Windows (#40713)
Closes #39382 

Release Notes:

- Fixed Windows specific ssh jumphost connection issues
2025-10-22 12:04:30 +00:00
Cameron Mcloughlin
98c7e018ae Add new action and handler for opening a specific setting (#40739)
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-22 11:25:50 +00:00
Lukas Wirth
c81ffaffb6 editor: Use unbounded shifts for chunk bitmaps (#40879)
This simplifies some code and is also more correct in some others (I
believe some of these might've overflowed causing panics in sentry)

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-22 11:05:32 +00:00
Nia
bd69124f2b Add option to disable crash handler (#40799)
Extra info needed for #39289. To be tested out next nightly build...

Release Notes:

- N/A

Co-authored-by: Cole Miller <m@cole-miller.net>
2025-10-22 11:04:58 +00:00
Samuel Oldham
77dbe08d9d project_panel: Fix buffer focus when canceling filename edit (#40747)
Closes #37726

Release Notes:

- Fixed an issue where the buffer would not regain focus when clicked
while a filename edit was in progress in the project panel.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-10-22 15:50:09 +05:30
Owen Law
252b75e493 Disable slang-server for verilog extension (#40442)
Should be merged with
https://github.com/zed-industries/extensions/pull/3584, which adds
`slang-server` as a new language server, but should be disabled by
default due to an issue with it not initializing on Windows and being a
relatively new language server in general.

Release Notes:

- N/A
2025-10-22 11:54:54 +02:00
Kirill Bulatov
bc0bace81f Add basic ico support (#40822)
Closes https://github.com/zed-industries/zed/discussions/40763

<img width="867" height="1088" alt="Screenshot 2025-10-21 at 23 14 47"
src="https://github.com/user-attachments/assets/d691fb2a-afc6-4445-a335-054ef164e0d3"
/>

Also improves error handling on image open failure:

<img width="864" height="1083" alt="Screenshot 2025-10-21 at 23 14 30"
src="https://github.com/user-attachments/assets/d5388b61-995f-441b-b375-ad5136d1533b"
/>


Release Notes:

- Added basic ico support, improved unsupported image handling
2025-10-22 12:07:32 +03:00
Joseph T. Lyons
8ceb2f2c61 Add more tweaks to the troubleshooting docs (#40870)
Release Notes:

- N/A
2025-10-22 09:04:05 +00:00
Joseph T. Lyons
25de2acfdb Correct workspace db directory paths (#40868)
Release Notes:

- N/A
2025-10-22 08:27:59 +00:00
Joseph T. Lyons
8bad2cbd83 Add another informational blog post to docs (#40865)
Release Notes:

- N/A
2025-10-22 07:58:03 +00:00
Joseph T. Lyons
762af0982c Add more troubleshooting information (#40864)
Release Notes:

- N/A
2025-10-22 07:55:49 +00:00
Be
3d3e9130a8 collab: Pin sea-orm-macro crate version together with sea-orm (#40846)
Currently running `cargo update` on Zed will break the collab crate
because the versions of sea-orm and sea-orm-macros will not match. This
results in a bunch of noisy warnings from rust-analyzer.

Release Notes:

- N/A
2025-10-22 07:37:20 +00:00
Joseph T. Lyons
fd9c2e32f3 Organize release docs (#40860)1
Release Notes:

- N/A
2025-10-22 00:53:44 -04:00
Mikayla Maki
08d95ad9d3 chore: Bump gpui to 0.2.2 (#40856)
Release Notes:

- N/A
2025-10-22 03:43:32 +00:00
Alvaro Parker
d096132888 docs: Add git stash to git.md (#40834)
Closes #ISSUE

Release Notes:

- Added git stash documentation
2025-10-21 23:22:20 -04:00
Mikayla Maki
d7c855550c Update async-tar dependency for GPUI (#40850)
Release Notes:

- N/A
2025-10-22 03:07:02 +00:00
Mikayla Maki
9c71a7f43c Revert arm64 runners (#40852)
Release Notes:

- N/A
2025-10-21 20:03:02 -07:00
John Tur
221637ea82 Fix code signing for Windows installer (#40847)
Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-10-21 20:21:43 -06:00
Max Brunsfeld
e468edd389 Fix extraction of font runs from text runs (#40840)
Fixes a bug in https://github.com/zed-industries/zed/pull/39928

The bug caused all completions to appear in bold-face

Release Notes:

- Fixed a bug where bold-face font was applied to the wrong characters
in items in the autocomplete menu

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-10-22 01:55:54 +00:00
John Tur
2903a06e5c Fix nightly upload on Windows (#40843)
Release Notes:

- N/A
2025-10-22 01:55:02 +00:00
Be
2338164c10 Revert "title_bar: Add configurable window controls position (#38834)" (#40839)
This reverts commit b479d1ef49.

This PR was accidentally merged prematurely.
https://github.com/zed-industries/zed/pull/38834#issuecomment-3424186051

cc @danilo-leal

Release Notes:

- N/A
2025-10-22 00:54:46 +00:00
Nia
2deafd8706 project_panel: Don't show trash dialog on remote connections (#40838)
In remote connections, we don't detect whether trashing is possible and
generally shouldn't assume it is. This also fixes up some incomplete
logic that was attempting to do that.

Closes #39212

Release Notes:

- No longer show trash option in remote projects
2025-10-22 00:24:38 +00:00
Ben Kunkle
0c13403fa5 settings_ui: Add broken file warning banner (#40823)
Closes #ISSUE

Release Notes:

- settings_ui: Added a warning banner when the settings file you are
actively editing is in a broken or invalid state.
2025-10-21 19:09:49 -04:00
Danilo Leal
8f3da5c5cd settings_ui: Add pickers for theme and icon themes (#40829)
In the process of adding pickers for the theme and icon themes fields in
the settings UI, I felt like there was an improvement opportunity in
regards to where some of these components are stored. The `ui_input`
crate originally was meant only for the text field-like component, which
couldn't be in the regular `ui` crate due to the dependency with
`editor`. Given we had also added the number field there—which is
similar in also having the same dependency—it made sense to think of
this crate more like a home for form-like components rather than for
only one component.

However, we were also storing some settings UI-specific stuff in that
crate, which didn't feel right. So I ended up creating a new directory
within the `settings_ui` for components and moved all the pickers and
the custom input field there. I think this makes it for a cleaner
structure.

Release Notes:

- settings_ui: Added the ability to search for theme and icon themes in
their respective fields.
2025-10-21 19:58:43 -03:00
Martin Pool
b519f53b3e Rope benchmarks: Generate random strings measured in bytes, not chars (#39951)
Follows on from https://github.com/zed-industries/zed/pull/39949.

Again I'm not 100% sure of the intent but I think this is a fix:

`generate_random_string(rng, 4096)` would previously give you a string
of 4096 *chars* which could be anywhere between 4kB and 16kB in bytes.
This seems probably not what was intended, because Ropes generally work
in bytes not chars, including for the offsets used to index into them.

This seems to possibly cause a _regression_ in benchmark performance,
which is surprising because it should generally cause smaller test data.
But, possibly it's doing better at exercising different paths?

cc @mrnugget 

Release Notes:

- N/A
2025-10-22 00:42:05 +02:00
Piotr Osiewicz
50d184b6a6 Revert "search: New old search implementation (#39956)" (#40831)
This reverts commit 7c4fb5a899.

Closes #40792

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-21 22:15:20 +00:00
John Tur
2bba3358b8 Add Windows Arm64 builds to CI (#40821)
Closes https://github.com/zed-industries/zed/issues/40378

Release Notes:

- N/A

---------

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
2025-10-21 14:51:54 -07:00
Marshall Bowers
fcecf379dc Switch to fork of async-tar (#40828)
This PR switches to our own fork of `async-tar`.

Release Notes:

- N/A
2025-10-21 21:49:29 +00:00
Kirill Bulatov
256fe6e45c Fix task terminal split (#40824)
Before:


https://github.com/user-attachments/assets/efe2aeb6-94bb-46b6-944f-5a6345c072b4


After:


https://github.com/user-attachments/assets/61a7f699-6b4d-465f-add1-07774068420c


Release Notes:

- Fixed task terminal split not working correctly
2025-10-21 21:08:56 +00:00
Jakub Konka
e49edfac74 python: Init venv/virtualenv activation scripts during list/resolve (#40816)
This means that existence of activation scripts for venv/virtualenv will
be checked locally either on the host if editing locally, or the remote
by the remote proxy if editing a remote project.

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

Release Notes:

- N/A
2025-10-21 22:57:31 +02:00
Marshall Bowers
fd10017837 docs: Add model prices for Claude Haiku 4.5 (#40820)
This PR adds the model prices for Claude Haiku 4.5 to the docs.

Release Notes:

- N/A
2025-10-21 19:44:01 +00:00
Delvin
4b429033e7 settings_ui: Correct stepper increment and enforce max value for Centered Layout Padding (#40751)
Closes #40748

This PR improves the Centered Layout Padding in settings ui by limiting the numeric stepper to be within valid values and adding a custom schema generated to improve the JSON LSP completions and warnings when editing the setting field manually.

Release Notes:

- settings ui: limit stepper increment for centered padding between 0 and 0.4 and increment by 0.05 now

---------

Co-authored by: Anthony Eid <anthony@zed.dev>
2025-10-21 19:20:42 +00:00
Joseph T. Lyons
a398f80ba6 Add an action to reveal log file in system file manager (#40815)
We document the location of the log file in many places, we should just
make it easy to open directly within your file browser. The one thing
here is naming. We use dynamic naming for "reveal" actions in the
project panel, to reflect the right file manager name per OS, but for a
command palette action, I dont think we want to have dynamic code for
the action name, just going with finder at the moment.

Release Notes:

- Added a `zed: reveal log in file manager` action to the command
palette.
2025-10-21 14:38:11 -04:00
Danilo Leal
a71cc6a1e7 settings_ui: Add some design tweaks (#40818)
- Improves the UI for subfields of dynamic items
- Makes description writing more consistent (add period at the end of
every sentence, fix capitalization of proper names, ensure description
is always in sentence case)
- Other small details, mostly around spacing/padding

Release Notes:

- N/A
2025-10-21 15:27:44 -03:00
Danilo Leal
2764c51af1 extensions_ui: Increase affordance of download button in cards (#40795)
Hopefully, this will make the install/configure/uninstall buttons in the
right stand out a bit more and make their presence a bit more obvious
for newcomers.

| Before | After |
|--------|--------|
| <img width="1938" height="1276" alt="Screenshot 2025-10-21 at 10 
58@2x"
src="https://github.com/user-attachments/assets/b76115e1-0be2-4d5b-a677-525663d86c7c"
/> | <img width="1938" height="1276" alt="Screenshot 2025-10-21 at 10 
53@2x"
src="https://github.com/user-attachments/assets/9e563b71-b11a-4b69-b687-c0b469ca4eec"
/> |

Release Notes:

- Increased affordance of the download button in each extension card in
the extensions page.
2025-10-21 13:55:42 -03:00
Dong
641ae90cd6 settings_ui: Fix IEEE 754 floating point error when serializing value to JSON (#40677)
Closes #40556

Release Notes:

- settings-ui: Fixed an issue where modifying floating point number fields could result in the values written to the settings file containing IEEE 754 floating point error

> [!Note]
> Seems like there's another pull request fixing the centered layout in
#40661 by normalizing the whole `settings.json` file when updating it.
Not sure which is the better way to handle this issue.

## Description

This pull request solves the IEEE 754 floating point error when
serializing values from settings-ui into `settings.json` by creating a
two `serde_helper` to convert the problematic f32 into a formatted two
decimal placed f32.

Fields currently exists the IEEE 754 error:
- Appearance → Unnecessary Code Fade
- Editor → Drop Size Target
- Window & Layout → Centered Layout Left/Right Padding
- Window & Layout → Inactive Opacity

## How to verify

### Unnecessary Code Fade

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/1bf4bad2-63c5-4b03-ac29-8b6b59569e16"
/> | <video
src="https://github.com/user-attachments/assets/dadcd4a1-651b-43dd-913f-edae073ceb68"
/>

### Drop Size Target

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/9d5b4173-fcac-44d0-b7fc-772a2e426ef1"
/> | <video
src="https://github.com/user-attachments/assets/4b5adeaf-e678-494d-bd1b-6c1d55824c43"
/>

### Centered Layout Left/Right Padding

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/33b4e1ff-7ab2-44f7-9e9b-8abad1565d9a"
/> | <video
src="https://github.com/user-attachments/assets/63d8de9e-28d1-4bd7-a6c9-02452e105486"
/>

### Inactive Opacity

As Is | To Be
--- | ---
<video
src="https://github.com/user-attachments/assets/a7fe2e72-deb6-41dc-82f3-e2649503b8a4"
/> | <video
src="https://github.com/user-attachments/assets/993c314f-b6f6-4dcd-8f74-fa357ab063e9"
/>

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-21 16:45:40 +00:00
Ruangyot Nanchiang
fa550de922 Fix Git UI truncation for long branch names (#40598)
Closes #40524

Release Notes:
- Fixed branch names not truncating properly in git branch picker.

Please review if you have time.

PS. I’m not fully sure if this completely fixes the issue, but I’ve
tested it on my local build and it seems to work fine.

Before Fix:
<img width="773" height="799"
alt="502782621-91ac0578-9f55-4fb3-b0da-49a49e862a33"
src="https://github.com/user-attachments/assets/a9597949-c46a-47d0-a9ef-eddd637a9dc7"
/>

After Fix:
<img width="545" height="766" alt="FixedRound2"
src="https://github.com/user-attachments/assets/0d9770dc-a9da-46cd-a69a-4c8de2ca1abd"
/>
2025-10-21 16:42:43 +00:00
Joseph T. Lyons
ce5d597efa Centralize Zed.log documentation (#40808)
Just wanted a single location to point people to for telling them where
to find their log file. I left duplicate text in GitHub Issue templates,
as it seems annoying to have to follow a link when making an issue.

Release Notes:

- N/A
2025-10-21 16:39:10 +00:00
Ben Kunkle
cf8422f7fd settings_ui: Fix focus bugs (#40806)
Closes #40608


Release Notes:

- settings_ui: Fixed an issue where tabbing to the nav bar from the
search bar while the nav bar was scrolled would result in the first
_visible_ nav entry being selected, instead of the literal first nav
entry
- settings_ui: Fixed an issue where scrolling the selected nav entry off
screen would cause the keyboard shortcut hint for the focus nav / focus
content binding to dissapear
- settings_ui: Fixed an issue where text input controls could not be
focused via the keyboard
2025-10-21 12:25:07 -04:00
Jason Lee
d7ffc37b14 editor: Improve text color in document color highlight (#39372)
Release Notes:

- Improved text color in LSP document color highlight.

----

Because highlight ranges are implemented using a paint background,
there's no way to control the text color.

I've been thinking about this problem for a long time, want to solve it.

~~Today, I come up with a new idea. Re-rendering the document color text
at the top should solve this problem.~~

#### Update 10/6: 

> The previous version is not good, when we have soft wrap text, that
version will not work correct.

Now use exists `bg_segments_per_row` feature to fix text color.

## Before

<img width="563" height="540" alt="image"
src="https://github.com/user-attachments/assets/99722253-0cab-4d2a-a5d1-7f28393bcaed"
/>


## After

<img width="544" height="527" alt="image"
src="https://github.com/user-attachments/assets/a1bf6cdb-0e9c-435d-b14a-6ee9159a63d9"
/>
2025-10-21 21:35:40 +05:30
Lukas Wirth
b79837695e fs: Implement FileHandle::current_path for windows (#40804)
Release Notes:

- Fixed worktree renames not working on windows
2025-10-21 15:48:40 +00:00
Lukas Wirth
69025f3bf4 Add linux_repo_snapshot got .gitignore (#40802)
It keeps popping up in my project searches ... This prevents that. Not
like we are planning on editing that file again.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-21 15:31:40 +00:00
Tim Vermeulen
981fa288eb editor: Hide the git blame popover on escape (#40549)
Release Notes:

- Added way to hide git blame popover by pressing the escape key.
2025-10-21 20:34:13 +05:30
Lukas Wirth
854d1ec4dc acp_thread: Fix panic when following acp agents across buffers (#40798)
Fixes ZED-2D7

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-21 14:37:16 +00:00
Smit Barmase
10b9ae5e44 multi_buffer: Assert char boundary for panic due to point_to_buffer_offset (#40777)
In an attempt to figure out what's wrong with `point_to_buffer_offset`
for crash https://github.com/zed-industries/zed/issues/40453. We want to
know which branch among these two is the bad one.

Release Notes:

- N/A

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-10-21 19:01:35 +05:30
Smit Barmase
cad06011c5 language: Fix hang when editing certain tailwind class names (#40791)
Closes #36223

Upsteam issue to track:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1479

Release Notes:

- Fixed an issue where Zed hanged when editing certain Tailwind class
names.
2025-10-21 19:00:02 +05:30
Lukas Wirth
0eccdfe61f project: Spawn terminal process on background executor (#40774)
We were spawning the process on the foreground thread before which can
block an arbitrary amount of time. Likewise we no longer block
deserialization on the terminal loading.

Release Notes:

- Improved startup time on systems with slow process spawning
capabilities
2025-10-21 13:10:21 +00:00
Lukas Wirth
0be70e24d6 persistence: More error contexts (#40787)
Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: David Kleingeld <davidsk@zed.dev>
2025-10-21 11:29:43 +00:00
Dino
3d4abde55a vim: Fix hang in visual block motion (#40723)
The `vim::visual::Vim.visual_block_motion` method was recently updated
(https://github.com/zed-industries/zed/pull/39355) in order to jump
between buffer rows instead of display rows. However, with this now
being the case, the `break` condition was never met when the motion was
horizontal rather than vertical and soft wrapped lines were used. As
such, this commit udpates the condition to ensure it's always reached,
preventing the hanging from happening.

Release Notes:

- Fixed hang in Vim's visual block motions when updating selections

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-10-21 12:23:00 +01:00
Piotr Osiewicz
2bfbe031c6 python: Bump version & get rid of explicit deps specifications for PET (#40785)
Release Notes:

- N/A
2025-10-21 12:50:56 +02:00
Agus Zubiaga
12d912114f ci: Update typos versions and fix new occurrences (#40784)
I noticed we had some typos that were getting through CI, but it looks
like the new version of `typos` catches them. So I updated it and fixed
them.

Release Notes:

- N/A
2025-10-21 10:43:22 +00:00
Agus Zubiaga
b487d2cfe0 zeta2 inspector: Feedback box (#40732)
Adds a way to submit feedback about a zeta2 prediction from the
inspector. The telemetry event includes:
- project snapshot (git + unsaved buffer state)
- the full request and response
- user feedback kind and text 

Release Notes:

- N/A
2025-10-21 10:30:21 +00:00
Piotr Osiewicz
977887b65f ci: Bump max target directory size on Mac to 300GB (#40778)
I did not bump it for Linux as some machines have smaller disks (~300GB
or so); with Mac, we have at least 1TB on all of our boxes

Release Notes:

- N/A
2025-10-21 11:24:22 +02:00
Finn Evers
0721c7873a Make kotlin-lsp the default language server (#40776)
Following a conversation with the maintainer/owner of
kotlin-language-server, he recommended switching to the official
language server, which is better in many aspects and also more actively
maintained.

Release Notes:

- Made the official Kotlin Language Server the default language server
for Kotlin.
2025-10-21 09:03:12 +00:00
Finn Evers
0aa7b7c773 editor: Toggle diff hunk based on current mouse position (#40773)
This fixes an issue where we would search for the hovered diff hunk
based on the mouse hit test computed during (or prior) editor paint
instead of the mouse hit test computed prior to the mouse event
invocation.

That in turn could lead to cases where moving the mouse from the editor
to the project panel and then clicking a file shortly after would expand
a diff hunk when actually nothing should happen in that case.

Release Notes:

- Fixed an issue where diff hunks would sometimes erroneously toggle
upon mouse clicks.
2025-10-21 08:38:40 +00:00
Piotr Osiewicz
1b544b9e19 ci: Run slow tests first (#40769)
Tests are hand-picked based on yours truly's preference

Release Notes:

- N/A
2025-10-21 09:58:26 +02:00
Julia Ryan
ea6e6dbda1 Add log message on first render (#40749)
Having this in our logs with a timestamp should help when users submit
issues with logs about slow startup time.

Release Notes:

- N/A
2025-10-21 00:17:26 -07:00
Piotr Osiewicz
a56122e144 ci: Do not use full debug info in CI builds (#40764)
For good backtraces in tests 'limited' is all we need.

Closes #ISSUE

Release Notes:

- N/A
2025-10-21 06:53:45 +00:00
Dario Griffo
04a45e3501 Add debian community repository (#40698)
I maintain this repository that contains several developer tools like
- ghostty
- zig
- yazi

all of them are updated usually the same day as upstream.

Release Notes: 

- N/A
2025-10-21 09:50:57 +03:00
341 changed files with 14218 additions and 10719 deletions

View File

@@ -5,12 +5,16 @@
# Arrays are merged together though. See: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
# The intent for this file is to configure CI build process with a divergance from Zed developers experience; for example, in this config file
# we use `-D warnings` for rustflags (which makes compilation fail in presence of warnings during build process). Placing that in developers `config.toml`
# would be incovenient.
# would be inconvenient.
# The reason for not using the RUSTFLAGS environment variable is that doing so would override all the settings in the config.toml file, even if the contents of the latter are completely nonsensical. See: https://github.com/rust-lang/cargo/issues/5376
# Here, we opted to use `[target.'cfg(all())']` instead of `[build]` because `[target.'**']` is guaranteed to be cumulative.
[target.'cfg(all())']
rustflags = ["-D", "warnings"]
# We don't need fullest debug information for dev stuff (tests etc.) in CI.
[profile.dev]
debug = "limited"
# Use Mold on Linux, because it's faster than GNU ld and LLD.
#
# We no longer set this in the default `config.toml` so that developers can opt in to Wild, which

View File

@@ -4,3 +4,17 @@ sequential-db-tests = { max-threads = 1 }
[[profile.default.overrides]]
filter = 'package(db)'
test-group = 'sequential-db-tests'
# Run slowest tests first.
#
[[profile.default.overrides]]
filter = 'package(worktree) and test(test_random_worktree_changes)'
priority = 100
[[profile.default.overrides]]
filter = 'package(collab) and (test(random_project_collaboration_tests) or test(random_channel_buffer_tests) or test(test_contact_requests) or test(test_basic_following))'
priority = 99
[[profile.default.overrides]]
filter = 'package(extension_host) and test(test_extension_store_with_test_extension)'
priority = 99

35
.github/ISSUE_TEMPLATE/06_bug_git.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Bug Report (Git)
description: Zed Git Related Bugs
type: "Bug"
labels: ["git"]
title: "Git: <a short description of the Git bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one-line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one-line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
Steps to trigger the problem:
1.
2.
3.
**Expected Behavior**:
**Actual Behavior**:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
validations:
required: true

View File

@@ -33,9 +33,10 @@ body:
required: true
- type: textarea
attributes:
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
label: If applicable, attach your `Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Windows: `C:\Users\YOU\AppData\Local\Zed\logs\Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |

View File

@@ -15,8 +15,11 @@ runs:
node-version: "18"
- name: Limit target directory size
env:
MAX_SIZE: ${{ runner.os == 'macOS' && 300 || 100 }}
shell: bash -euxo pipefail {0}
run: script/clear-target-dir-if-larger-than 100
# Use the variable in the run command
run: script/clear-target-dir-if-larger-than ${{ env.MAX_SIZE }}
- name: Run tests
shell: bash -euxo pipefail {0}

View File

@@ -177,7 +177,7 @@ jobs:
uses: ./.github/actions/check_style
- name: Check for typos
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6
uses: crate-ci/typos@80c8a4945eec0f6d464eaf9e65ed98ef085283d1 # v1.38.1
with:
config: ./typos.toml
@@ -296,49 +296,53 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- namespace-profile-16x32-ubuntu-2204
- namespace-profile-ubuntu22-x86-16x32-custom
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: |
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
echo "$HOME/.cargo-nextest/bin" >> "$GITHUB_PATH"
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
- name: Configure Go and Rust cache
uses: namespacelabs/nscloud-cache-action@v1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
path: |
/home/runner/.cargo-nextest
/home/runner/.rustup
./target
- name: Configure CI
run: |
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: cargo clippy
run: ./script/clippy
- name: Install cargo nextest
shell: bash -euxo pipefail {0}
run: |
cargo install cargo-nextest --locked --root ~/.cargo-nextest
- name: Limit target directory size
env:
MAX_SIZE: ${{ runner.os == 'macOS' && 300 || 100 }}
shell: bash -euxo pipefail {0}
# Use the variable in the run command
run: script/clear-target-dir-if-larger-than ${{ env.MAX_SIZE }}
- name: Run tests
uses: ./.github/actions/run_tests
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
- name: Build other binaries and features
run: |
cargo build -p zed
cargo check -p workspace
cargo check -p gpui --examples
# Even the Linux runner is not stateful, in theory there is no need to do this cleanup.
# But, to avoid potential issues in the future if we choose to use a stateful Linux runner and forget to add code
# to clean up the config file, Ive included the cleanup code here as a precaution.
# While its not strictly necessary at this moment, I believe its better to err on the side of caution.
- name: Clean CI config file
if: always()
run: rm -rf ./../.cargo
- name: cargo clippy
run: ./script/clippy
doctests:
# Nextest currently doesn't support doctests, so run them separately and in parallel.

1
.gitignore vendored
View File

@@ -25,6 +25,7 @@
/crates/collab/seed.json
/crates/theme/schemas/theme.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/crates/project_panel/benches/linux_repo_snapshot.txt
/dev.zed.Zed*.json
/node_modules/
/plugins/bin

359
Cargo.lock generated
View File

@@ -142,7 +142,7 @@ dependencies = [
"agent_servers",
"agent_settings",
"anyhow",
"assistant_context",
"assistant_text_thread",
"chrono",
"client",
"clock",
@@ -315,9 +315,9 @@ dependencies = [
"ai_onboarding",
"anyhow",
"arrayvec",
"assistant_context",
"assistant_slash_command",
"assistant_slash_commands",
"assistant_text_thread",
"audio",
"buffer_diff",
"chrono",
@@ -802,53 +802,6 @@ dependencies = [
"rust-embed",
]
[[package]]
name = "assistant_context"
version = "0.1.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_slash_command",
"assistant_slash_commands",
"chrono",
"client",
"clock",
"cloud_llm_client",
"collections",
"context_server",
"fs",
"futures 0.3.31",
"fuzzy",
"gpui",
"indoc",
"language",
"language_model",
"log",
"open_ai",
"parking_lot",
"paths",
"pretty_assertions",
"project",
"prompt_store",
"proto",
"rand 0.9.2",
"regex",
"rpc",
"serde",
"serde_json",
"settings",
"smallvec",
"smol",
"telemetry_events",
"text",
"ui",
"unindent",
"util",
"uuid",
"workspace",
"zed_env_vars",
]
[[package]]
name = "assistant_slash_command"
version = "0.1.0"
@@ -906,6 +859,53 @@ dependencies = [
"zlog",
]
[[package]]
name = "assistant_text_thread"
version = "0.1.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_slash_command",
"assistant_slash_commands",
"chrono",
"client",
"clock",
"cloud_llm_client",
"collections",
"context_server",
"fs",
"futures 0.3.31",
"fuzzy",
"gpui",
"indoc",
"language",
"language_model",
"log",
"open_ai",
"parking_lot",
"paths",
"pretty_assertions",
"project",
"prompt_store",
"proto",
"rand 0.9.2",
"regex",
"rpc",
"serde",
"serde_json",
"settings",
"smallvec",
"smol",
"telemetry_events",
"text",
"ui",
"unindent",
"util",
"uuid",
"workspace",
"zed_env_vars",
]
[[package]]
name = "async-attributes"
version = "1.1.2"
@@ -1184,9 +1184,9 @@ dependencies = [
[[package]]
name = "async-tar"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a42f905d4f623faf634bbd1e001e84e0efc24694afa64be9ad239bf6ca49e1f8"
checksum = "d1937db2d56578aa3919b9bdb0e5100693fd7d1c0f145c53eb81fbb03e217550"
dependencies = [
"async-std",
"filetime",
@@ -3324,8 +3324,8 @@ version = "0.44.0"
dependencies = [
"agent_settings",
"anyhow",
"assistant_context",
"assistant_slash_command",
"assistant_text_thread",
"async-trait",
"async-tungstenite",
"audio",
@@ -3393,6 +3393,7 @@ dependencies = [
"rpc",
"scrypt",
"sea-orm",
"sea-orm-macros",
"semantic_version",
"semver",
"serde",
@@ -4131,6 +4132,7 @@ dependencies = [
"bincode 1.3.3",
"cfg-if",
"crash-handler",
"extension_host",
"log",
"mach2 0.5.0",
"minidumper",
@@ -4900,6 +4902,18 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "derive_setters"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9"
dependencies = [
"darling 0.20.11",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "deunicode"
version = "1.6.2"
@@ -5367,6 +5381,7 @@ dependencies = [
"rand 0.9.2",
"regex",
"release_channel",
"rope",
"rpc",
"schemars 1.0.4",
"serde",
@@ -5690,6 +5705,61 @@ dependencies = [
"num-traits",
]
[[package]]
name = "eval"
version = "0.1.0"
dependencies = [
"acp_thread",
"agent",
"agent-client-protocol",
"agent_settings",
"agent_ui",
"anyhow",
"async-trait",
"buffer_diff",
"chrono",
"clap",
"client",
"collections",
"debug_adapter_extension",
"dirs 4.0.0",
"dotenvy",
"env_logger 0.11.8",
"extension",
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"handlebars 4.5.0",
"language",
"language_extension",
"language_model",
"language_models",
"languages",
"markdown",
"node_runtime",
"pathdiff",
"paths",
"pretty_assertions",
"project",
"prompt_store",
"rand 0.9.2",
"regex",
"release_channel",
"reqwest_client",
"serde",
"serde_json",
"settings",
"shellexpand 2.1.2",
"telemetry",
"terminal_view",
"toml 0.8.23",
"unindent",
"util",
"uuid",
"watch",
]
[[package]]
name = "event-listener"
version = "2.5.3"
@@ -5828,6 +5898,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"gpui_tokio",
"http_client",
"language",
"language_extension",
@@ -6883,6 +6954,35 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gh-workflow"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fffeec7bd9dfa73ffe1db38979cca5716ec6ffd944f03fae65fee81f16082ae"
dependencies = [
"async-trait",
"derive_more 2.0.1",
"derive_setters",
"gh-workflow-macros",
"indexmap 2.11.4",
"merge",
"serde",
"serde_json",
"serde_yaml",
"strum_macros 0.27.2",
]
[[package]]
name = "gh-workflow-macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eafb4d2a1005d4ac6d041ce929af10be1de1e1eae478795d9d634b84ccf8191"
dependencies = [
"heck 0.5.0",
"quote",
"syn 2.0.106",
]
[[package]]
name = "gif"
version = "0.13.3"
@@ -7158,7 +7258,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@@ -9753,6 +9853,28 @@ dependencies = [
"gpui",
]
[[package]]
name = "merge"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
dependencies = [
"merge_derive",
"num-traits",
]
[[package]]
name = "merge_derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "metal"
version = "0.29.0"
@@ -10789,7 +10911,6 @@ dependencies = [
"telemetry",
"theme",
"ui",
"ui_input",
"util",
"vim_mode_setting",
"workspace",
@@ -11387,7 +11508,7 @@ dependencies = [
[[package]]
name = "pet"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"clap",
"env_logger 0.10.2",
@@ -11424,7 +11545,7 @@ dependencies = [
[[package]]
name = "pet-conda"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11443,7 +11564,7 @@ dependencies = [
[[package]]
name = "pet-core"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"clap",
"lazy_static",
@@ -11458,7 +11579,7 @@ dependencies = [
[[package]]
name = "pet-env-var-path"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11474,7 +11595,7 @@ dependencies = [
[[package]]
name = "pet-fs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11483,7 +11604,7 @@ dependencies = [
[[package]]
name = "pet-global-virtualenvs"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11496,7 +11617,7 @@ dependencies = [
[[package]]
name = "pet-homebrew"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11514,7 +11635,7 @@ dependencies = [
[[package]]
name = "pet-jsonrpc"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -11527,7 +11648,7 @@ dependencies = [
[[package]]
name = "pet-linux-global-python"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11540,7 +11661,7 @@ dependencies = [
[[package]]
name = "pet-mac-commandlinetools"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11553,7 +11674,7 @@ dependencies = [
[[package]]
name = "pet-mac-python-org"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11566,7 +11687,7 @@ dependencies = [
[[package]]
name = "pet-mac-xcode"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11579,7 +11700,7 @@ dependencies = [
[[package]]
name = "pet-pipenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11592,7 +11713,7 @@ dependencies = [
[[package]]
name = "pet-pixi"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11604,7 +11725,7 @@ dependencies = [
[[package]]
name = "pet-poetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"base64 0.22.1",
"lazy_static",
@@ -11625,7 +11746,7 @@ dependencies = [
[[package]]
name = "pet-pyenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11643,7 +11764,7 @@ dependencies = [
[[package]]
name = "pet-python-utils"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11660,7 +11781,7 @@ dependencies = [
[[package]]
name = "pet-reporter"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"log",
@@ -11674,7 +11795,7 @@ dependencies = [
[[package]]
name = "pet-telemetry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"env_logger 0.10.2",
"lazy_static",
@@ -11689,7 +11810,7 @@ dependencies = [
[[package]]
name = "pet-venv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11701,7 +11822,7 @@ dependencies = [
[[package]]
name = "pet-virtualenv"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11713,7 +11834,7 @@ dependencies = [
[[package]]
name = "pet-virtualenvwrapper"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"log",
"msvc_spectre_libs",
@@ -11726,7 +11847,7 @@ dependencies = [
[[package]]
name = "pet-windows-registry"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -11744,7 +11865,7 @@ dependencies = [
[[package]]
name = "pet-windows-store"
version = "0.1.0"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=845945b830297a50de0e24020b980a65e4820559#845945b830297a50de0e24020b980a65e4820559"
source = "git+https://github.com/microsoft/python-environment-tools.git?rev=e97b9508befa0062929da65a01054d25c4be861c#e97b9508befa0062929da65a01054d25c4be861c"
dependencies = [
"lazy_static",
"log",
@@ -12743,6 +12864,30 @@ dependencies = [
"toml_edit 0.23.7",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
@@ -12890,23 +13035,6 @@ dependencies = [
"zlog",
]
[[package]]
name = "project_benchmarks"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"client",
"futures 0.3.31",
"gpui",
"http_client",
"language",
"node_runtime",
"project",
"settings",
"watch",
]
[[package]]
name = "project_panel"
version = "0.1.0"
@@ -14186,6 +14314,7 @@ dependencies = [
"log",
"rand 0.9.2",
"rayon",
"regex",
"smallvec",
"sum_tree",
"unicode-segmentation",
@@ -14916,6 +15045,7 @@ dependencies = [
"futures 0.3.31",
"gpui",
"language",
"lsp",
"menu",
"project",
"schemars 1.0.4",
@@ -15183,6 +15313,19 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.11.4",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "serial2"
version = "0.2.33"
@@ -15289,8 +15432,10 @@ dependencies = [
"menu",
"node_runtime",
"paths",
"picker",
"pretty_assertions",
"project",
"release_channel",
"schemars 1.0.4",
"search",
"serde",
@@ -16990,7 +17135,6 @@ dependencies = [
"parking_lot",
"postage",
"rand 0.9.2",
"regex",
"rope",
"smallvec",
"sum_tree",
@@ -18209,10 +18353,8 @@ version = "0.1.0"
dependencies = [
"component",
"editor",
"fuzzy",
"gpui",
"menu",
"picker",
"settings",
"theme",
"ui",
@@ -18355,6 +18497,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -20607,16 +20755,6 @@ dependencies = [
"zlog",
]
[[package]]
name = "worktree_benchmarks"
version = "0.1.0"
dependencies = [
"fs",
"gpui",
"settings",
"worktree",
]
[[package]]
name = "writeable"
version = "0.6.1"
@@ -20774,6 +20912,7 @@ dependencies = [
"cargo_metadata",
"cargo_toml",
"clap",
"gh-workflow",
"indoc",
"toml 0.8.23",
"toml_edit 0.22.27",
@@ -20959,7 +21098,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.210.0"
version = "0.211.0"
dependencies = [
"acp_tools",
"activity_indicator",
@@ -21005,6 +21144,7 @@ dependencies = [
"file_finder",
"fs",
"futures 0.3.31",
"gh-workflow",
"git",
"git_hosting_providers",
"git_ui",
@@ -21533,6 +21673,7 @@ dependencies = [
"serde",
"serde_json",
"settings",
"telemetry",
"text",
"ui",
"ui_input",

View File

@@ -13,7 +13,7 @@ members = [
"crates/anthropic",
"crates/askpass",
"crates/assets",
"crates/assistant_context",
"crates/assistant_text_thread",
"crates/assistant_slash_command",
"crates/assistant_slash_commands",
"crates/audio",
@@ -58,7 +58,7 @@ members = [
"crates/edit_prediction_context",
"crates/zeta2_tools",
"crates/editor",
# "crates/eval",
"crates/eval",
"crates/explorer_command_injector",
"crates/extension",
"crates/extension_api",
@@ -126,7 +126,6 @@ members = [
"crates/picker",
"crates/prettier",
"crates/project",
"crates/project_benchmarks",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_store",
@@ -195,7 +194,6 @@ members = [
"crates/web_search_providers",
"crates/workspace",
"crates/worktree",
"crates/worktree_benchmarks",
"crates/x_ai",
"crates/zed",
"crates/zed_actions",
@@ -248,7 +246,7 @@ ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
assistant_context = { path = "crates/assistant_context" }
assistant_text_thread = { path = "crates/assistant_text_thread" }
assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
audio = { path = "crates/audio" }
@@ -454,7 +452,7 @@ async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.0"
async-tar = "0.5.1"
async-task = "4.7"
async-trait = "0.1"
async-tungstenite = "0.31.0"
@@ -508,6 +506,7 @@ fork = "0.2.0"
futures = "0.3"
futures-batch = "0.6.1"
futures-lite = "1.13"
gh-workflow = "0.8.0"
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
handlebars = "4.3"
@@ -582,14 +581,14 @@ partial-json-fixer = "0.5.3"
parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "e97b9508befa0062929da65a01054d25c4be861c" }
portable-pty = "0.9.0"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
@@ -902,4 +901,5 @@ ignored = [
"serde",
"component",
"documented",
"sea-orm-macros",
]

View File

@@ -139,7 +139,7 @@
"find": "buffer_search::Deploy",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl->": "agent::QuoteSelection",
"ctrl->": "agent::AddSelectionToThread",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -243,7 +243,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
"ctrl-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl->": "agent::QuoteSelection",
"ctrl->": "agent::AddSelectionToThread",
"ctrl-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -366,7 +366,7 @@
}
},
{
"context": "PromptLibrary",
"context": "RulesLibrary",
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
@@ -539,7 +539,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -799,7 +799,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1094,7 +1094,7 @@
"paste": "terminal::Paste",
"shift-insert": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
@@ -1290,5 +1290,13 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
}
}
]

View File

@@ -142,7 +142,7 @@
"cmd-\"": "editor::ExpandAllDiffHunks",
"cmd-alt-g b": "git::Blame",
"cmd-alt-g m": "git::OpenModifiedFiles",
"cmd-shift-space": "editor::ShowSignatureHelp",
"cmd-i": "editor::ShowSignatureHelp",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
"ctrl-f12": "editor::GoToDeclaration",
@@ -163,7 +163,7 @@
"cmd-alt-f": "buffer_search::DeployReplace",
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd->": "agent::QuoteSelection",
"cmd->": "agent::AddSelectionToThread",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol",
"alt-enter": "editor::OpenSelectionsInMultibuffer"
@@ -282,7 +282,7 @@
"cmd-shift-i": "agent::ToggleOptionsMenu",
"cmd-alt-shift-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"cmd->": "agent::QuoteSelection",
"cmd->": "agent::AddSelectionToThread",
"cmd-alt-e": "agent::RemoveAllContext",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-ctrl-b": "agent::ToggleBurnMode",
@@ -423,7 +423,7 @@
}
},
{
"context": "PromptLibrary",
"context": "RulesLibrary",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "rules_library::NewRule",
@@ -864,7 +864,7 @@
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPreviousHunk",
"cmd-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
@@ -1168,7 +1168,7 @@
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"cmd-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-_": null, // emacs undo
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
@@ -1396,5 +1396,13 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"cmd-enter up": "dev::Zeta2RatePredictionPositive",
"cmd-enter down": "dev::Zeta2RatePredictionNegative"
}
}
]

View File

@@ -134,7 +134,7 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-.": "agent::AddSelectionToThread",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -244,7 +244,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-.": "agent::AddSelectionToThread",
"shift-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -375,7 +375,7 @@
}
},
{
"context": "PromptLibrary",
"context": "RulesLibrary",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "rules_library::NewRule",
@@ -548,7 +548,7 @@
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
"ctrl-shift-space": "editor::ShowSignatureHelp",
"ctrl-shift-space": "editor::ShowWordCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
@@ -812,7 +812,7 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPreviousHunk",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-shift-;": "editor::ToggleInlayHints"
}
},
@@ -1120,7 +1120,7 @@
"shift-insert": "terminal::Paste",
"ctrl-v": "terminal::Paste",
"ctrl-shift-v": "terminal::Paste",
"ctrl-i": "assistant::InlineAssist",
"ctrl-enter": "assistant::InlineAssist",
"alt-b": ["terminal::SendText", "\u001bb"],
"alt-f": ["terminal::SendText", "\u001bf"],
"alt-.": ["terminal::SendText", "\u001b."],
@@ -1319,5 +1319,13 @@
"home": "settings_editor::FocusFirstNavEntry",
"end": "settings_editor::FocusLastNavEntry"
}
},
{
"context": "Zeta2Feedback > Editor",
"bindings": {
"enter": "editor::Newline",
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
}
}
]

View File

@@ -17,8 +17,8 @@
"bindings": {
"ctrl-i": "agent::ToggleFocus",
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"ctrl-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"ctrl-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"ctrl-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"ctrl-k": "assistant::InlineAssist",
"ctrl-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -8,13 +8,23 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
// NOTE: in macos the 'ctrl-x' 'ctrl-p' and 'ctrl-n' rebindings are not needed, since they default to 'cmd'.
"context": "Editor",
"bindings": {
"ctrl-g": null, // currently activates `go_to_line::Toggle` when there is nothing to cancel
"ctrl-x": null, // currently activates `editor::Cut` if no following key is pressed for 1 second
"ctrl-p": null, // currently activates `file_finder::Toggle` when the cursor is on the first character of the buffer
"ctrl-n": null // currently activates `workspace::NewFile` when the cursor is on the last character of the buffer
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -33,8 +43,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -98,7 +108,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -126,15 +136,28 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -145,10 +168,19 @@
}
},
{
// Workaround to enable using emacs in the Zed terminal.
// Workaround to enable using native emacs from the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -17,8 +17,8 @@
"bindings": {
"cmd-i": "agent::ToggleFocus",
"cmd-shift-i": "agent::ToggleFocus",
"cmd-shift-l": "agent::QuoteSelection", // In cursor uses "Ask" mode
"cmd-l": "agent::QuoteSelection", // In cursor uses "Agent" mode
"cmd-shift-l": "agent::AddSelectionToThread", // In cursor uses "Ask" mode
"cmd-l": "agent::AddSelectionToThread", // In cursor uses "Agent" mode
"cmd-k": "assistant::InlineAssist",
"cmd-shift-k": "assistant::InsertIntoEditor"
}

View File

@@ -9,13 +9,19 @@
"ctrl-g": "menu::Cancel"
}
},
{
// Workaround to avoid falling back to default bindings.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE: must be declared before the `Editor` override.
"context": "Editor",
"bindings": {
"ctrl-g": null // currently activates `go_to_line::Toggle` when there is nothing to cancel
}
},
{
"context": "Editor",
"bindings": {
"alt-x": "command_palette::Toggle",
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
"ctrl-space": "editor::SetMark", // set-mark
@@ -34,8 +40,8 @@
"alt-m": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }], // back-to-indentation
"alt-left": "editor::MoveToPreviousWordStart", // left-word
"alt-right": "editor::MoveToNextWordEnd", // right-word
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-f": "editor::MoveToNextWordEnd", // forward-word
"alt-b": "editor::MoveToPreviousWordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
@@ -99,7 +105,7 @@
"ctrl-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-m": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": false, "stop_at_indent": true }],
"alt-f": "editor::SelectToNextWordEnd",
"alt-b": "editor::SelectToPreviousSubwordStart",
"alt-b": "editor::SelectToPreviousWordStart",
"alt-{": "editor::SelectToStartOfParagraph",
"alt-}": "editor::SelectToEndOfParagraph",
"ctrl-up": "editor::SelectToStartOfParagraph",
@@ -127,15 +133,28 @@
"ctrl-n": "editor::SignatureHelpNext"
}
},
// Example setting for using emacs-style tab
// (i.e. indent the current line / selection or perform symbol completion depending on context)
// {
// "context": "Editor && !showing_code_actions && !showing_completions",
// "bindings": {
// "tab": "editor::AutoIndent" // indent-for-tab-command
// }
// },
{
"context": "Workspace",
"bindings": {
"alt-x": "command_palette::Toggle", // execute-extended-command
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"ctrl-x ctrl-b": "tab_switcher::Toggle", // list-buffers
// "ctrl-x ctrl-c": "workspace::CloseWindow" // in case you only want to exit the current Zed instance
"ctrl-x ctrl-c": "zed::Quit", // save-buffers-kill-terminal
"ctrl-x 5 0": "workspace::CloseWindow", // delete-frame
"ctrl-x 5 2": "workspace::NewWindow", // make-frame-command
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
// "ctrl-x 1": "pane::JoinAll", // in case you prefer to delete the splits but keep the buffers open
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
@@ -146,10 +165,19 @@
}
},
{
// Workaround to enable using emacs in the Zed terminal.
// Workaround to enable using native emacs from the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
// NOTE:
// "terminal::SendKeystroke" only works for a single key stroke (e.g. ctrl-x),
// so override with null for compound sequences (e.g. ctrl-x ctrl-c).
"context": "Terminal",
"bindings": {
// If you want to perfect your emacs-in-zed setup, also consider the following.
// You may need to enable "option_as_meta" from the Zed settings for "alt-x" to work.
// "alt-x": ["terminal::SendKeystroke", "alt-x"],
// "ctrl-x": ["terminal::SendKeystroke", "ctrl-x"],
// "ctrl-n": ["terminal::SendKeystroke", "ctrl-n"],
// ...
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer

View File

@@ -422,56 +422,66 @@
{
"context": "(vim_mode == helix_normal || vim_mode == helix_select) && !menu",
"bindings": {
";": "vim::HelixCollapseSelection",
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"s": "vim::HelixSelectRegex",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"left": "vim::WrappingLeft",
"right": "vim::WrappingRight",
// Movement
"h": "vim::WrappingLeft",
"left": "vim::WrappingLeft",
"l": "vim::WrappingRight",
"right": "vim::WrappingRight",
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"alt-.": "vim::RepeatFind",
// Changes
"shift-r": "editor::Paste",
"`": "vim::ConvertToLowerCase",
"alt-`": "vim::ConvertToUpperCase",
"insert": "vim::InsertBefore",
"shift-u": "editor::Redo",
"ctrl-r": "vim::Redo",
"y": "vim::HelixYank",
"p": "vim::HelixPaste",
"shift-p": ["vim::HelixPaste", { "before": true }],
"alt-;": "vim::OtherEnd",
"ctrl-r": "vim::Redo",
"f": ["vim::PushFindForward", { "before": false, "multiline": true }],
"t": ["vim::PushFindForward", { "before": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
"shift-p": ["vim::HelixPaste", { "before": true }],
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"`": "vim::ConvertToLowerCase",
"alt-`": "vim::ConvertToUpperCase",
"g q": "vim::PushRewrap",
"g w": "vim::PushRewrap",
"insert": "vim::InsertBefore",
"alt-.": "vim::RepeatFind",
"d": "vim::HelixDelete",
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",
// Selection manipulation
"s": "vim::HelixSelectRegex",
"alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
";": "vim::HelixCollapseSelection",
"alt-;": "vim::OtherEnd",
",": "vim::HelixKeepNewestSelection",
"shift-c": "vim::HelixDuplicateBelow",
"alt-shift-c": "vim::HelixDuplicateAbove",
"%": "editor::SelectAll",
"x": "vim::HelixSelectLine",
"shift-x": "editor::SelectLine",
"ctrl-c": "editor::ToggleComments",
"alt-o": "editor::SelectLargerSyntaxNode",
"alt-i": "editor::SelectSmallerSyntaxNode",
"alt-p": "editor::SelectPreviousSyntaxNode",
"alt-n": "editor::SelectNextSyntaxNode",
// Goto mode
"g n": "pane::ActivateNextItem",
"g p": "pane::ActivatePreviousItem",
// "tab": "pane::ActivateNextItem",
// "shift-tab": "pane::ActivatePrevItem",
"shift-h": "pane::ActivatePreviousItem",
"shift-l": "pane::ActivateNextItem",
"g l": "vim::EndOfLine",
"g h": "vim::StartOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g e": "vim::EndOfDocument",
"g .": "vim::HelixGotoLastModification", // go to last modification
"g r": "editor::FindAllReferences", // zed specific
"g h": "vim::StartOfLine",
"g l": "vim::EndOfLine",
"g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
"g t": "vim::WindowTop",
"g c": "vim::WindowMiddle",
"g b": "vim::WindowBottom",
"shift-r": "editor::Paste",
"x": "vim::HelixSelectLine",
"shift-x": "editor::SelectLine",
"%": "editor::SelectAll",
"g r": "editor::FindAllReferences", // zed specific
"g n": "pane::ActivateNextItem",
"shift-l": "pane::ActivateNextItem",
"g p": "pane::ActivatePreviousItem",
"shift-h": "pane::ActivatePreviousItem",
"g .": "vim::HelixGotoLastModification", // go to last modification
// Window mode
"space w h": "workspace::ActivatePaneLeft",
"space w l": "workspace::ActivatePaneRight",
@@ -482,6 +492,7 @@
"space w r": "pane::SplitRight",
"space w v": "pane::SplitDown",
"space w d": "pane::SplitDown",
// Space mode
"space f": "file_finder::Toggle",
"space k": "editor::Hover",
@@ -492,16 +503,18 @@
"space a": "editor::ToggleCodeActions",
"space h": "editor::SelectAllMatches",
"space c": "editor::ToggleComments",
"space y": "editor::Copy",
"space p": "editor::Paste",
"shift-u": "editor::Redo",
"ctrl-c": "editor::ToggleComments",
"d": "vim::HelixDelete",
"c": "vim::HelixSubstitute",
"alt-c": "vim::HelixSubstituteNoYank",
"shift-c": "vim::HelixDuplicateBelow",
"alt-shift-c": "vim::HelixDuplicateAbove",
",": "vim::HelixKeepNewestSelection"
"space y": "editor::Copy",
// Other
":": "command_palette::Toggle",
"m": "vim::PushHelixMatch",
"]": ["vim::PushHelixNext", { "around": true }],
"[": ["vim::PushHelixPrevious", { "around": true }],
"g q": "vim::PushRewrap",
"g w": "vim::PushRewrap",
// "tab": "pane::ActivateNextItem",
// "shift-tab": "pane::ActivatePrevItem",
}
},
{
@@ -970,7 +983,9 @@
"bindings": {
"ctrl-h": "editor::Backspace",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-w": "editor::DeleteToPreviousWordStart"
"ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-p": "menu::SelectPrevious",
"ctrl-n": "menu::SelectNext"
}
},
{

View File

@@ -1741,7 +1741,7 @@
}
},
"Kotlin": {
"language_servers": ["kotlin-language-server", "!kotlin-lsp", "..."]
"language_servers": ["!kotlin-language-server", "kotlin-lsp", "..."]
},
"LaTeX": {
"formatter": "language_server",
@@ -1820,6 +1820,7 @@
},
"SystemVerilog": {
"format_on_save": "off",
"language_servers": ["!slang", "..."],
"use_on_type_format": false
},
"Vue.js": {

View File

@@ -1421,15 +1421,18 @@ impl AcpThread {
if let Some(Some(location)) = resolved_locations.last() {
project.update(cx, |project, cx| {
let should_ignore = if let Some(agent_location) = project.agent_location() {
let should_ignore = if let Some(agent_location) = project
.agent_location()
.filter(|agent_location| agent_location.buffer == location.buffer)
{
let snapshot = location.buffer.read(cx).snapshot();
let old_position = agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
agent_location.buffer == location.buffer
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
&& (old_position.row == new_position.row
&& old_position.column > new_position.column)
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
old_position.row == new_position.row
&& old_position.column > new_position.column
} else {
false
};

View File

@@ -1,10 +1,15 @@
use agent_client_protocol as acp;
use anyhow::Result;
use futures::{FutureExt as _, future::Shared};
use gpui::{App, AppContext, Context, Entity, Task};
use gpui::{App, AppContext, AsyncApp, Context, Entity, Task};
use language::LanguageRegistry;
use markdown::Markdown;
use project::Project;
use settings::{Settings as _, SettingsLocation};
use std::{path::PathBuf, process::ExitStatus, sync::Arc, time::Instant};
use task::Shell;
use terminal::terminal_settings::TerminalSettings;
use util::get_default_system_shell_preferring_bash;
pub struct Terminal {
id: acp::TerminalId,
@@ -170,3 +175,68 @@ impl Terminal {
)
}
}
pub async fn create_terminal_entity(
command: String,
args: &[String],
env_vars: Vec<(String, String)>,
cwd: Option<PathBuf>,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<Entity<terminal::Terminal>> {
let mut env = if let Some(dir) = &cwd {
project
.update(cx, |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
env.extend(env_vars);
// Use remote shell or default system shell, as appropriate
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
project
.update(cx, |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd,
env,
..Default::default()
},
cx,
)
})?
.await
}

View File

@@ -11,8 +11,7 @@ use language::{
LanguageServerStatusUpdate, ServerHealth,
};
use project::{
EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,
ProjectEnvironmentEvent,
LanguageServerProgress, LspStoreEvent, Project, ProjectEnvironmentEvent,
git_store::{GitStoreEvent, Repository},
};
use smallvec::SmallVec;
@@ -327,20 +326,20 @@ impl ActivityIndicator {
.flatten()
}
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {
self.project.read(cx).peek_environment_error(cx)
}
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some(error) = self.pending_environment_error(cx) {
if let Some(message) = self.pending_environment_error(cx) {
return Some(Content {
icon: Some(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.into_any_element(),
),
message: error.0.clone(),
message: message.clone(),
on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| {
project.pop_environment_error(cx);

View File

@@ -10,6 +10,8 @@ path = "src/agent.rs"
[features]
test-support = ["db/test-support"]
eval = []
edit-agent-eval = []
e2e = []
[lints]
@@ -22,7 +24,7 @@ agent-client-protocol.workspace = true
agent_servers.workspace = true
agent_settings.workspace = true
anyhow.workspace = true
assistant_context.workspace = true
assistant_text_thread.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
@@ -74,7 +76,7 @@ zstd.workspace = true
[dev-dependencies]
agent_servers = { workspace = true, "features" = ["test-support"] }
assistant_context = { workspace = true, "features" = ["test-support"] }
assistant_text_thread = { workspace = true, "features" = ["test-support"] }
client = { workspace = true, "features" = ["test-support"] }
clock = { workspace = true, "features" = ["test-support"] }
context_server = { workspace = true, "features" = ["test-support"] }

View File

@@ -48,24 +48,10 @@ use util::rel_path::RelPath;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectSnapshot {
pub worktree_snapshots: Vec<WorktreeSnapshot>,
pub worktree_snapshots: Vec<project::telemetry_snapshot::TelemetryWorktreeSnapshot>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorktreeSnapshot {
pub worktree_path: String,
pub git_state: Option<GitState>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GitState {
pub remote_url: Option<String>,
pub head_sha: Option<String>,
pub current_branch: Option<String>,
pub diff: Option<String>,
}
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
".cursorrules",
@@ -1280,8 +1266,9 @@ mod internal_tests {
)
.await;
let project = Project::test(fs.clone(), [], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let agent = NativeAgent::new(
project.clone(),
history_store,
@@ -1341,8 +1328,9 @@ mod internal_tests {
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/", json!({ "a": {} })).await;
let project = Project::test(fs.clone(), [], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let connection = NativeAgentConnection(
NativeAgent::new(
project.clone(),
@@ -1416,8 +1404,9 @@ mod internal_tests {
.await;
let project = Project::test(fs.clone(), [], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
// Create the agent and connection
let agent = NativeAgent::new(
@@ -1488,8 +1477,9 @@ mod internal_tests {
)
.await;
let project = Project::test(fs.clone(), [path!("/a").as_ref()], cx).await;
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let agent = NativeAgent::new(
project.clone(),
history_store.clone(),

View File

@@ -31,7 +31,7 @@ use std::{
use util::path;
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_extract_handle_command_output() {
// Test how well agent generates multiple edit hunks.
//
@@ -108,7 +108,7 @@ fn eval_extract_handle_command_output() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_delete_run_git_blame() {
// Model | Pass rate
// ----------------------------|----------
@@ -171,7 +171,7 @@ fn eval_delete_run_git_blame() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_translate_doc_comments() {
// Model | Pass rate
// ============================================
@@ -234,7 +234,7 @@ fn eval_translate_doc_comments() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
// Model | Pass rate
// ============================================
@@ -360,7 +360,7 @@ fn eval_use_wasi_sdk_in_compile_parser_to_wasm() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_disable_cursor_blinking() {
// Model | Pass rate
// ============================================
@@ -446,7 +446,7 @@ fn eval_disable_cursor_blinking() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_from_pixels_constructor() {
// Results for 2025-06-13
//
@@ -656,7 +656,7 @@ fn eval_from_pixels_constructor() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_zode() {
// Model | Pass rate
// ============================================
@@ -763,7 +763,7 @@ fn eval_zode() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_add_overwrite_test() {
// Model | Pass rate
// ============================================
@@ -995,7 +995,7 @@ fn eval_add_overwrite_test() {
}
#[test]
#[cfg_attr(not(feature = "eval"), ignore)]
#[cfg_attr(not(feature = "edit-agent-eval"), ignore)]
fn eval_create_empty_file() {
// Check that Edit Agent can create a file without writing its
// thoughts into it. This issue is not specific to empty files, but
@@ -1490,9 +1490,20 @@ impl EditAgentTest {
&std::env::var("ZED_JUDGE_MODEL").unwrap_or("anthropic/claude-4-sonnet-latest".into()),
)
.unwrap();
let authenticate_provider_tasks = cx.update(|cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry
.providers()
.iter()
.map(|p| p.authenticate(cx))
.collect::<Vec<_>>()
})
});
let (agent_model, judge_model) = cx
.update(|cx| {
cx.spawn(async move |cx| {
futures::future::join_all(authenticate_provider_tasks).await;
let agent_model = Self::load_model(&agent_model, cx).await;
let judge_model = Self::load_model(&judge_model, cx).await;
(agent_model.unwrap(), judge_model.unwrap())

View File

@@ -2,12 +2,12 @@ use crate::{DbThread, DbThreadMetadata, ThreadsDatabase};
use acp_thread::MentionUri;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_context::{AssistantContext, SavedContextMetadata};
use assistant_text_thread::{SavedTextThreadMetadata, TextThread};
use chrono::{DateTime, Utc};
use db::kvp::KEY_VALUE_STORE;
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
use itertools::Itertools;
use paths::contexts_dir;
use paths::text_threads_dir;
use project::Project;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, path::Path, rc::Rc, sync::Arc, time::Duration};
@@ -50,21 +50,23 @@ pub fn load_agent_thread(
#[derive(Clone, Debug)]
pub enum HistoryEntry {
AcpThread(DbThreadMetadata),
TextThread(SavedContextMetadata),
TextThread(SavedTextThreadMetadata),
}
impl HistoryEntry {
pub fn updated_at(&self) -> DateTime<Utc> {
match self {
HistoryEntry::AcpThread(thread) => thread.updated_at,
HistoryEntry::TextThread(context) => context.mtime.to_utc(),
HistoryEntry::TextThread(text_thread) => text_thread.mtime.to_utc(),
}
}
pub fn id(&self) -> HistoryEntryId {
match self {
HistoryEntry::AcpThread(thread) => HistoryEntryId::AcpThread(thread.id.clone()),
HistoryEntry::TextThread(context) => HistoryEntryId::TextThread(context.path.clone()),
HistoryEntry::TextThread(text_thread) => {
HistoryEntryId::TextThread(text_thread.path.clone())
}
}
}
@@ -74,9 +76,9 @@ impl HistoryEntry {
id: thread.id.clone(),
name: thread.title.to_string(),
},
HistoryEntry::TextThread(context) => MentionUri::TextThread {
path: context.path.as_ref().to_owned(),
name: context.title.to_string(),
HistoryEntry::TextThread(text_thread) => MentionUri::TextThread {
path: text_thread.path.as_ref().to_owned(),
name: text_thread.title.to_string(),
},
}
}
@@ -90,7 +92,7 @@ impl HistoryEntry {
&thread.title
}
}
HistoryEntry::TextThread(context) => &context.title,
HistoryEntry::TextThread(text_thread) => &text_thread.title,
}
}
}
@@ -120,7 +122,7 @@ enum SerializedRecentOpen {
pub struct HistoryStore {
threads: Vec<DbThreadMetadata>,
entries: Vec<HistoryEntry>,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
recently_opened_entries: VecDeque<HistoryEntryId>,
_subscriptions: Vec<gpui::Subscription>,
_save_recently_opened_entries_task: Task<()>,
@@ -128,7 +130,7 @@ pub struct HistoryStore {
impl HistoryStore {
pub fn new(
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
cx: &mut Context<Self>,
) -> Self {
let subscriptions =
@@ -192,16 +194,16 @@ impl HistoryStore {
cx: &mut Context<Self>,
) -> Task<Result<()>> {
self.text_thread_store
.update(cx, |store, cx| store.delete_local_context(path, cx))
.update(cx, |store, cx| store.delete_local(path, cx))
}
pub fn load_text_thread(
&self,
path: Arc<Path>,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
) -> Task<Result<Entity<TextThread>>> {
self.text_thread_store
.update(cx, |store, cx| store.open_local_context(path, cx))
.update(cx, |store, cx| store.open_local(path, cx))
}
pub fn reload(&self, cx: &mut Context<Self>) {
@@ -243,7 +245,7 @@ impl HistoryStore {
history_entries.extend(
self.text_thread_store
.read(cx)
.unordered_contexts()
.unordered_text_threads()
.cloned()
.map(HistoryEntry::TextThread),
);
@@ -278,14 +280,14 @@ impl HistoryStore {
let context_entries = self
.text_thread_store
.read(cx)
.unordered_contexts()
.flat_map(|context| {
.unordered_text_threads()
.flat_map(|text_thread| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::TextThread(path) if &context.path == path => {
Some((index, HistoryEntry::TextThread(context.clone())))
HistoryEntryId::TextThread(path) if &text_thread.path == path => {
Some((index, HistoryEntry::TextThread(text_thread.clone())))
}
_ => None,
})
@@ -347,7 +349,7 @@ impl HistoryStore {
acp::SessionId(id.as_str().into()),
)),
SerializedRecentOpen::TextThread(file_name) => Some(
HistoryEntryId::TextThread(contexts_dir().join(file_name).into()),
HistoryEntryId::TextThread(text_threads_dir().join(file_name).into()),
),
})
.collect();

View File

@@ -81,7 +81,7 @@ impl AgentServer for NativeAgentServer {
mod tests {
use super::*;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use gpui::AppContext;
agent_servers::e2e_tests::common_e2e_tests!(
@@ -116,8 +116,9 @@ mod tests {
});
let history = cx.update(|cx| {
let context_store = cx.new(move |cx| ContextStore::fake(project.clone(), cx));
cx.new(move |cx| HistoryStore::new(context_store, cx))
let text_thread_store =
cx.new(move |cx| TextThreadStore::fake(project.clone(), cx));
cx.new(move |cx| HistoryStore::new(text_thread_store, cx))
});
NativeAgentServer::new(fs.clone(), history)

View File

@@ -1834,8 +1834,9 @@ async fn test_agent_connection(cx: &mut TestAppContext) {
fake_fs.insert_tree(path!("/test"), json!({})).await;
let project = Project::test(fake_fs.clone(), [Path::new("/test")], cx).await;
let cwd = Path::new("/test");
let context_store = cx.new(|cx| assistant_context::ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store =
cx.new(|cx| assistant_text_thread::TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
// Create agent and connection
let agent = NativeAgent::new(
@@ -1995,7 +1996,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
locations: vec![],
raw_input: Some(json!({})),
raw_output: None,
meta: None,
meta: Some(json!({ "tool_name": "thinking" })),
}
);
let update = expect_tool_call_update_fields(&mut events).await;

View File

@@ -1,9 +1,8 @@
use crate::{
ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GitState, GrepTool,
DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,
ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,
SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,
WorktreeSnapshot,
};
use acp_thread::{MentionUri, UserMessageId};
use action_log::ActionLog;
@@ -26,7 +25,6 @@ use futures::{
future::Shared,
stream::FuturesUnordered,
};
use git::repository::DiffType;
use gpui::{
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
};
@@ -37,10 +35,7 @@ use language_model::{
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
};
use project::{
Project,
git_store::{GitStore, RepositoryState},
};
use project::Project;
use prompt_store::ProjectContext;
use schemars::{JsonSchema, Schema};
use serde::{Deserialize, Serialize};
@@ -750,7 +745,13 @@ impl Thread {
let title = tool.initial_title(tool_use.input.clone(), cx);
let kind = tool.kind();
stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
let output = tool_result
.as_ref()
@@ -880,101 +881,17 @@ impl Thread {
project: Entity<Project>,
cx: &mut Context<Self>,
) -> Task<Arc<ProjectSnapshot>> {
let git_store = project.read(cx).git_store().clone();
let worktree_snapshots: Vec<_> = project
.read(cx)
.visible_worktrees(cx)
.map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
.collect();
let task = project::telemetry_snapshot::TelemetrySnapshot::new(&project, cx);
cx.spawn(async move |_, _| {
let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
let snapshot = task.await;
Arc::new(ProjectSnapshot {
worktree_snapshots,
worktree_snapshots: snapshot.worktree_snapshots,
timestamp: Utc::now(),
})
})
}
fn worktree_snapshot(
worktree: Entity<project::Worktree>,
git_store: Entity<GitStore>,
cx: &App,
) -> Task<WorktreeSnapshot> {
cx.spawn(async move |cx| {
// Get worktree path and snapshot
let worktree_info = cx.update(|app_cx| {
let worktree = worktree.read(app_cx);
let path = worktree.abs_path().to_string_lossy().into_owned();
let snapshot = worktree.snapshot();
(path, snapshot)
});
let Ok((worktree_path, _snapshot)) = worktree_info else {
return WorktreeSnapshot {
worktree_path: String::new(),
git_state: None,
};
};
let git_state = git_store
.update(cx, |git_store, cx| {
git_store
.repositories()
.values()
.find(|repo| {
repo.read(cx)
.abs_path_to_repo_path(&worktree.read(cx).abs_path())
.is_some()
})
.cloned()
})
.ok()
.flatten()
.map(|repo| {
repo.update(cx, |repo, _| {
let current_branch =
repo.branch.as_ref().map(|branch| branch.name().to_owned());
repo.send_job(None, |state, _| async move {
let RepositoryState::Local { backend, .. } = state else {
return GitState {
remote_url: None,
head_sha: None,
current_branch,
diff: None,
};
};
let remote_url = backend.remote_url("origin");
let head_sha = backend.head_sha().await;
let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
GitState {
remote_url,
head_sha,
current_branch,
diff,
}
})
})
});
let git_state = match git_state {
Some(git_state) => match git_state.ok() {
Some(git_state) => git_state.await.ok(),
None => None,
},
None => None,
};
WorktreeSnapshot {
worktree_path,
git_state,
}
})
}
pub fn project_context(&self) -> &Entity<ProjectContext> {
&self.project_context
}
@@ -1133,14 +1050,18 @@ impl Thread {
Ok(())
}
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
pub fn latest_request_token_usage(&self) -> Option<language_model::TokenUsage> {
let last_user_message = self.last_user_message()?;
let tokens = self.request_token_usage.get(&last_user_message.id)?;
let model = self.model.clone()?;
Some(*tokens)
}
pub fn latest_token_usage(&self) -> Option<acp_thread::TokenUsage> {
let usage = self.latest_request_token_usage()?;
let model = self.model.clone()?;
Some(acp_thread::TokenUsage {
max_tokens: model.max_token_count_for_mode(self.completion_mode.into()),
used_tokens: tokens.total_tokens(),
used_tokens: usage.total_tokens(),
})
}
@@ -1183,6 +1104,14 @@ impl Thread {
self.run_turn(cx)
}
#[cfg(feature = "eval")]
pub fn proceed(
&mut self,
cx: &mut Context<Self>,
) -> Result<mpsc::UnboundedReceiver<Result<ThreadEvent>>> {
self.run_turn(cx)
}
fn run_turn(
&mut self,
cx: &mut Context<Self>,
@@ -1550,7 +1479,13 @@ impl Thread {
});
if push_new_tool_use {
event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
event_stream.send_tool_call(
&tool_use.id,
&tool_use.name,
title,
kind,
tool_use.input.clone(),
);
last_message
.content
.push(AgentMessageContent::ToolUse(tool_use.clone()));
@@ -1922,7 +1857,7 @@ impl Thread {
.tools
.iter()
.filter_map(|(tool_name, tool)| {
if tool.supported_provider(&model.provider_id())
if tool.supports_provider(&model.provider_id())
&& profile.is_tool_enabled(tool_name)
{
Some((truncate(tool_name), tool.clone()))
@@ -2198,7 +2133,7 @@ where
/// Some tools rely on a provider for the underlying billing or other reasons.
/// Allow the tool to check if they are compatible, or should be filtered out.
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
fn supports_provider(_provider: &LanguageModelProviderId) -> bool {
true
}
@@ -2239,7 +2174,7 @@ pub trait AnyAgentTool {
fn kind(&self) -> acp::ToolKind;
fn initial_title(&self, input: serde_json::Value, _cx: &mut App) -> SharedString;
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
fn supports_provider(&self, _provider: &LanguageModelProviderId) -> bool {
true
}
fn run(
@@ -2284,8 +2219,8 @@ where
Ok(json)
}
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
self.0.supported_provider(provider)
fn supports_provider(&self, provider: &LanguageModelProviderId) -> bool {
T::supports_provider(provider)
}
fn run(
@@ -2345,6 +2280,7 @@ impl ThreadEventStream {
fn send_tool_call(
&self,
id: &LanguageModelToolUseId,
tool_name: &str,
title: SharedString,
kind: acp::ToolKind,
input: serde_json::Value,
@@ -2352,6 +2288,7 @@ impl ThreadEventStream {
self.0
.unbounded_send(Ok(ThreadEvent::ToolCall(Self::initial_tool_call(
id,
tool_name,
title.to_string(),
kind,
input,
@@ -2361,12 +2298,15 @@ impl ThreadEventStream {
fn initial_tool_call(
id: &LanguageModelToolUseId,
tool_name: &str,
title: String,
kind: acp::ToolKind,
input: serde_json::Value,
) -> acp::ToolCall {
acp::ToolCall {
meta: None,
meta: Some(serde_json::json!({
"tool_name": tool_name
})),
id: acp::ToolCallId(id.to_string().into()),
title,
kind,

View File

@@ -40,13 +40,19 @@ pub use web_search_tool::*;
macro_rules! tools {
($($tool:ty),* $(,)?) => {
/// A list of all built-in tool names
pub fn built_in_tool_names() -> impl Iterator<Item = String> {
pub fn supported_built_in_tool_names(provider: Option<language_model::LanguageModelProviderId>) -> impl Iterator<Item = String> {
[
$(
<$tool>::name().to_string(),
(if let Some(provider) = provider.as_ref() {
<$tool>::supports_provider(provider)
} else {
true
})
.then(|| <$tool>::name().to_string()),
)*
]
.into_iter()
.flatten()
}
/// A list of all built-in tools

View File

@@ -57,7 +57,7 @@ impl AgentTool for WebSearchTool {
}
/// We currently only support Zed Cloud as a provider.
fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
fn supports_provider(provider: &LanguageModelProviderId) -> bool {
provider == &ZED_CLOUD_PROVIDER_ID
}

View File

@@ -9,9 +9,7 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use settings::{Settings as _, SettingsLocation};
use task::Shell;
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
use util::ResultExt as _;
use std::path::PathBuf;
use std::{any::Any, cell::RefCell};
@@ -23,7 +21,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
use terminal::terminal_settings::{AlternateScroll, CursorShape};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -816,62 +814,18 @@ impl acp::Client for ClientDelegate {
let thread = self.session_thread(&args.session_id)?;
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
for var in args.env {
env.insert(var.name, var.value);
}
// Use remote shell or default system shell, as appropriate
let shell = project
.update(&mut self.cx.clone(), |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);
let terminal_entity = project
.update(&mut self.cx.clone(), |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd: args.cwd.clone(),
env,
..Default::default()
},
cx,
)
})?
.await?;
let terminal_entity = acp_thread::create_terminal_entity(
args.command.clone(),
&args.args,
args.env
.into_iter()
.map(|env| (env.name, env.value))
.collect(),
args.cwd.clone(),
&project,
&mut self.cx.clone(),
)
.await?;
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {

View File

@@ -25,7 +25,7 @@ agent_settings.workspace = true
ai_onboarding.workspace = true
anyhow.workspace = true
arrayvec.workspace = true
assistant_context.workspace = true
assistant_text_thread.workspace = true
assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true
audio.workspace = true
@@ -102,7 +102,7 @@ zed_actions.workspace = true
[dev-dependencies]
acp_thread = { workspace = true, features = ["test-support"] }
agent = { workspace = true, features = ["test-support"] }
assistant_context = { workspace = true, features = ["test-support"] }
assistant_text_thread = { workspace = true, features = ["test-support"] }
buffer_diff = { workspace = true, features = ["test-support"] }
db = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }

View File

@@ -402,7 +402,7 @@ mod tests {
use agent::HistoryStore;
use agent_client_protocol as acp;
use agent_settings::AgentSettings;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
use editor::{EditorSettings, RowInfo};
use fs::FakeFs;
@@ -466,8 +466,8 @@ mod tests {
connection.send_update(session_id, acp::SessionUpdate::ToolCall(tool_call), cx)
});
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let view_state = cx.new(|_cx| {
EntryViewState::new(

View File

@@ -11,10 +11,10 @@ use assistant_slash_commands::codeblock_fence_for_path;
use collections::{HashMap, HashSet};
use editor::{
Addon, Anchor, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, InlayId,
EditorEvent, EditorMode, EditorSnapshot, EditorStyle, ExcerptId, FoldPlaceholder, Inlay,
MultiBuffer, ToOffset,
actions::Paste,
display_map::{Crease, CreaseId, FoldId, Inlay},
display_map::{Crease, CreaseId, FoldId},
};
use futures::{
FutureExt as _,
@@ -29,7 +29,8 @@ use language::{Buffer, Language, language_settings::InlayHintKind};
use language_model::LanguageModelImage;
use postage::stream::Stream as _;
use project::{
CompletionIntent, InlayHint, InlayHintLabel, Project, ProjectItem, ProjectPath, Worktree,
CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, ProjectItem, ProjectPath,
Worktree,
};
use prompt_store::{PromptId, PromptStore};
use rope::Point;
@@ -75,7 +76,7 @@ pub enum MessageEditorEvent {
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
const COMMAND_HINT_INLAY_ID: u32 = 0;
const COMMAND_HINT_INLAY_ID: InlayId = InlayId::Hint(0);
impl MessageEditor {
pub fn new(
@@ -151,7 +152,7 @@ impl MessageEditor {
let has_new_hint = !new_hints.is_empty();
editor.splice_inlays(
if has_hint {
&[InlayId::Hint(COMMAND_HINT_INLAY_ID)]
&[COMMAND_HINT_INLAY_ID]
} else {
&[]
},
@@ -628,12 +629,12 @@ impl MessageEditor {
path: PathBuf,
cx: &mut Context<Self>,
) -> Task<Result<Mention>> {
let context = self.history_store.update(cx, |store, cx| {
let text_thread_task = self.history_store.update(cx, |store, cx| {
store.load_text_thread(path.as_path().into(), cx)
});
cx.spawn(async move |_, cx| {
let context = context.await?;
let xml = context.update(cx, |context, cx| context.to_xml(cx))?;
let text_thread = text_thread_task.await?;
let xml = text_thread.update(cx, |text_thread, cx| text_thread.to_xml(cx))?;
Ok(Mention::Text {
content: xml,
tracked_buffers: Vec::new(),
@@ -1590,7 +1591,7 @@ mod tests {
use acp_thread::MentionUri;
use agent::{HistoryStore, outline};
use agent_client_protocol as acp;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use editor::{AnchorRangeExt as _, Editor, EditorMode};
use fs::FakeFs;
use futures::StreamExt as _;
@@ -1621,8 +1622,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {
@@ -1726,8 +1727,8 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
// Start with no available commands - simulating Claude which doesn't support slash commands
let available_commands = Rc::new(RefCell::new(vec![]));
@@ -1890,8 +1891,8 @@ mod tests {
let mut cx = VisualTestContext::from_window(*window, cx);
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let available_commands = Rc::new(RefCell::new(vec![
acp::AvailableCommand {
@@ -2130,8 +2131,8 @@ mod tests {
opened_editors.push(buffer);
}
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default()));
let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| {
@@ -2657,8 +2658,8 @@ mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store, cx));
let text_thread_store = cx.new(|cx| TextThreadStore::fake(project.clone(), cx));
let history_store = cx.new(|cx| HistoryStore::new(text_thread_store, cx));
let message_editor = cx.update(|window, cx| {
cx.new(|cx| {

View File

@@ -194,7 +194,7 @@ impl Render for ModeSelector {
trigger_button,
Tooltip::element({
let focus_handle = self.focus_handle.clone();
move |window, cx| {
move |_window, cx| {
v_flex()
.gap_1()
.child(
@@ -205,10 +205,9 @@ impl Render for ModeSelector {
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.child(Label::new("Cycle Through Modes"))
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&CycleModeSelector,
&focus_handle,
window,
cx,
)),
)
@@ -217,10 +216,9 @@ impl Render for ModeSelector {
.gap_2()
.justify_between()
.child(Label::new("Toggle Mode Menu"))
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)),
)

View File

@@ -77,14 +77,8 @@ impl Render for AcpModelSelectorPopover {
.ml_0p5(),
)
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
},
gpui::Corner::BottomRight,
cx,

View File

@@ -324,8 +324,8 @@ impl AcpThreadHistory {
HistoryEntry::AcpThread(thread) => self
.history_store
.update(cx, |this, cx| this.delete_thread(thread.id.clone(), cx)),
HistoryEntry::TextThread(context) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(context.path.clone(), cx)
HistoryEntry::TextThread(text_thread) => self.history_store.update(cx, |this, cx| {
this.delete_text_thread(text_thread.path.clone(), cx)
}),
};
task.detach_and_log_err(cx);
@@ -423,8 +423,8 @@ impl AcpThreadHistory {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
})
.on_click(
cx.listener(move |this, _, _, cx| this.remove_thread(ix, cx)),
@@ -595,8 +595,8 @@ impl RenderOnce for AcpHistoryEntryElement {
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Delete", &RemoveSelectedThread, cx)
})
.on_click({
let thread_view = self.thread_view.clone();
@@ -635,12 +635,12 @@ impl RenderOnce for AcpHistoryEntryElement {
});
}
}
HistoryEntry::TextThread(context) => {
HistoryEntry::TextThread(text_thread) => {
if let Some(panel) = workspace.read(cx).panel::<AgentPanel>(cx) {
panel.update(cx, |panel, cx| {
panel
.open_saved_text_thread(
context.path.clone(),
text_thread.path.clone(),
window,
cx,
)

View File

@@ -1259,6 +1259,7 @@ impl AcpThreadView {
.await?;
this.update_in(cx, |this, window, cx| {
this.send_impl(message_editor, window, cx);
this.focus_handle(cx).focus(window);
})?;
anyhow::Ok(())
})
@@ -2157,7 +2158,6 @@ impl AcpThreadView {
options,
entry_ix,
tool_call.id.clone(),
window,
cx,
))
.into_any(),
@@ -2558,7 +2558,6 @@ impl AcpThreadView {
options: &[acp::PermissionOption],
entry_ix: usize,
tool_call_id: acp::ToolCallId,
window: &Window,
cx: &Context<Self>,
) -> Div {
let is_first = self.thread().is_some_and(|thread| {
@@ -2615,7 +2614,7 @@ impl AcpThreadView {
seen_kinds.push(option.kind);
this.key_binding(
KeyBinding::for_action_in(action, &self.focus_handle, window, cx)
KeyBinding::for_action_in(action, &self.focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
})
@@ -2796,12 +2795,11 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Error)
.label_size(LabelSize::Small)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta(
"Stop This Command",
None,
"Also possible by placing your cursor inside the terminal and using regular terminal bindings.",
window,
cx,
)
})
@@ -3102,7 +3100,7 @@ impl AcpThreadView {
)
}
fn render_recent_history(&self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
fn render_recent_history(&self, cx: &mut Context<Self>) -> AnyElement {
let render_history = self
.agent
.clone()
@@ -3131,7 +3129,6 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&OpenHistory,
&self.focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -3459,7 +3456,6 @@ impl AcpThreadView {
&changed_buffers,
self.edits_expanded,
pending_edits,
window,
cx,
))
.when(self.edits_expanded, |parent| {
@@ -3619,7 +3615,6 @@ impl AcpThreadView {
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
expanded: bool,
pending_edits: bool,
window: &mut Window,
cx: &Context<Self>,
) -> Div {
const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
@@ -3695,12 +3690,11 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Review Changes",
&OpenAgentDiff,
&focus_handle,
window,
cx,
)
}
@@ -3718,13 +3712,8 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(
&RejectAll,
&focus_handle.clone(),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(&RejectAll, &focus_handle.clone(), cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
this.reject_all(&RejectAll, window, cx);
@@ -3738,7 +3727,7 @@ impl AcpThreadView {
this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
})
.key_binding(
KeyBinding::for_action_in(&KeepAll, &focus_handle, window, cx)
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(move |this, _, window, cx| {
@@ -3968,12 +3957,11 @@ impl AcpThreadView {
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip({
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
expand_tooltip,
&ExpandMessageEditor,
&focus_handle,
window,
cx,
)
}
@@ -4198,8 +4186,8 @@ impl AcpThreadView {
IconButton::new("stop-generation", IconName::Stop)
.icon_color(Color::Error)
.style(ButtonStyle::Tinted(ui::TintColor::Error))
.tooltip(move |window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Stop Generation", &editor::actions::Cancel, cx)
})
.on_click(cx.listener(|this, _event, _, cx| this.cancel_generation(cx)))
.into_any_element()
@@ -4221,7 +4209,7 @@ impl AcpThreadView {
this.icon_color(Color::Accent)
}
})
.tooltip(move |window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, window, cx))
.tooltip(move |_window, cx| Tooltip::for_action(send_btn_tooltip, &Chat, cx))
.on_click(cx.listener(|this, _, window, cx| {
this.send(window, cx);
}))
@@ -4282,15 +4270,14 @@ impl AcpThreadView {
.icon_color(Color::Muted)
.toggle_state(following)
.selected_icon_color(Some(Color::Custom(cx.theme().players().agent().cursor)))
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
if following {
Tooltip::for_action(tooltip_label.clone(), &Follow, window, cx)
Tooltip::for_action(tooltip_label.clone(), &Follow, cx)
} else {
Tooltip::with_meta(
tooltip_label.clone(),
Some(&Follow),
"Track the agent's location as it reads and edits files.",
window,
cx,
)
}
@@ -5079,7 +5066,7 @@ impl AcpThreadView {
}
}
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
fn render_thread_error(&self, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
ThreadError::Refusal => self.render_refusal_error(cx),
@@ -5090,9 +5077,7 @@ impl AcpThreadView {
ThreadError::ModelRequestLimitReached(plan) => {
self.render_model_request_limit_reached_error(*plan, cx)
}
ThreadError::ToolUseLimitReached => {
self.render_tool_use_limit_reached_error(window, cx)?
}
ThreadError::ToolUseLimitReached => self.render_tool_use_limit_reached_error(cx)?,
};
Some(div().child(content))
@@ -5283,11 +5268,7 @@ impl AcpThreadView {
.dismiss_action(self.dismiss_error_button(cx))
}
fn render_tool_use_limit_reached_error(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Callout> {
fn render_tool_use_limit_reached_error(&self, cx: &mut Context<Self>) -> Option<Callout> {
let thread = self.as_native_thread(cx)?;
let supports_burn_mode = thread
.read(cx)
@@ -5314,7 +5295,6 @@ impl AcpThreadView {
KeyBinding::for_action_in(
&ContinueWithBurnMode,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
@@ -5338,13 +5318,8 @@ impl AcpThreadView {
.layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
&ContinueThread,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(10.))),
KeyBinding::for_action_in(&ContinueThread, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(10.))),
)
.on_click(cx.listener(|this, _, _window, cx| {
this.resume_chat(cx);
@@ -5439,9 +5414,11 @@ impl AcpThreadView {
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
history.delete_thread(thread.id.clone(), cx)
}),
HistoryEntry::TextThread(context) => self.history_store.update(cx, |history, cx| {
history.delete_text_thread(context.path.clone(), cx)
}),
HistoryEntry::TextThread(text_thread) => {
self.history_store.update(cx, |history, cx| {
history.delete_text_thread(text_thread.path.clone(), cx)
})
}
};
task.detach_and_log_err(cx);
}
@@ -5520,7 +5497,7 @@ impl Render for AcpThreadView {
.into_any(),
ThreadState::Loading { .. } => v_flex()
.flex_1()
.child(self.render_recent_history(window, cx))
.child(self.render_recent_history(cx))
.into_any(),
ThreadState::LoadError(e) => v_flex()
.flex_1()
@@ -5551,8 +5528,7 @@ impl Render for AcpThreadView {
.vertical_scrollbar_for(self.list_state.clone(), window, cx)
.into_any()
} else {
this.child(self.render_recent_history(window, cx))
.into_any()
this.child(self.render_recent_history(cx)).into_any()
}
}),
})
@@ -5576,7 +5552,7 @@ impl Render for AcpThreadView {
Vec::<Empty>::new()
}
})
.children(self.render_thread_error(window, cx))
.children(self.render_thread_error(cx))
.when_some(
self.new_server_version_available.as_ref().filter(|_| {
!has_messages || !matches!(self.thread_state, ThreadState::Ready { .. })
@@ -5761,7 +5737,7 @@ fn terminal_command_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
pub(crate) mod tests {
use acp_thread::StubAgentConnection;
use agent_client_protocol::SessionId;
use assistant_context::ContextStore;
use assistant_text_thread::TextThreadStore;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
@@ -5924,10 +5900,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
let thread_view = cx.update(|window, cx| {
cx.new(|cx| {
@@ -6196,10 +6172,10 @@ pub(crate) mod tests {
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let context_store =
cx.update(|_window, cx| cx.new(|cx| ContextStore::fake(project.clone(), cx)));
let text_thread_store =
cx.update(|_window, cx| cx.new(|cx| TextThreadStore::fake(project.clone(), cx)));
let history_store =
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(context_store, cx)));
cx.update(|_window, cx| cx.new(|cx| HistoryStore::new(text_thread_store, cx)));
let connection = Rc::new(StubAgentConnection::new());
let thread_view = cx.update(|window, cx| {

View File

@@ -10,7 +10,7 @@ use settings::{OpenAiCompatibleSettingsContent, update_settings_file};
use ui::{
Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*,
};
use ui_input::SingleLineInput;
use ui_input::InputField;
use workspace::{ModalView, Workspace};
#[derive(Clone, Copy)]
@@ -33,9 +33,9 @@ impl LlmCompatibleProvider {
}
struct AddLlmProviderInput {
provider_name: Entity<SingleLineInput>,
api_url: Entity<SingleLineInput>,
api_key: Entity<SingleLineInput>,
provider_name: Entity<InputField>,
api_url: Entity<InputField>,
api_key: Entity<InputField>,
models: Vec<ModelInput>,
}
@@ -76,10 +76,10 @@ struct ModelCapabilityToggles {
}
struct ModelInput {
name: Entity<SingleLineInput>,
max_completion_tokens: Entity<SingleLineInput>,
max_output_tokens: Entity<SingleLineInput>,
max_tokens: Entity<SingleLineInput>,
name: Entity<InputField>,
max_completion_tokens: Entity<InputField>,
max_output_tokens: Entity<InputField>,
max_tokens: Entity<InputField>,
capabilities: ModelCapabilityToggles,
}
@@ -171,9 +171,9 @@ fn single_line_input(
text: Option<&str>,
window: &mut Window,
cx: &mut App,
) -> Entity<SingleLineInput> {
) -> Entity<InputField> {
cx.new(|cx| {
let input = SingleLineInput::new(window, cx, placeholder).label(label);
let input = InputField::new(window, cx, placeholder).label(label);
if let Some(text) = text {
input
.editor()
@@ -431,7 +431,7 @@ impl Focusable for AddLlmProviderModal {
impl ModalView for AddLlmProviderModal {}
impl Render for AddLlmProviderModal {
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
div()
@@ -484,7 +484,6 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -499,7 +498,6 @@ impl Render for AddLlmProviderModal {
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -757,12 +755,7 @@ mod tests {
models: Vec<(&str, &str, &str, &str)>,
cx: &mut VisualTestContext,
) -> Option<SharedString> {
fn set_text(
input: &Entity<SingleLineInput>,
text: &str,
window: &mut Window,
cx: &mut App,
) {
fn set_text(input: &Entity<InputField>, text: &str, window: &mut Window, cx: &mut App) {
input.update(cx, |input, cx| {
input.editor().update(cx, |editor, cx| {
editor.set_text(text, window, cx);

View File

@@ -566,7 +566,7 @@ impl ConfigureContextServerModal {
.into_any_element()
}
fn render_modal_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> ModalFooter {
fn render_modal_footer(&self, cx: &mut Context<Self>) -> ModalFooter {
let focus_handle = self.focus_handle(cx);
let is_connecting = matches!(self.state, State::Waiting);
@@ -584,12 +584,11 @@ impl ConfigureContextServerModal {
.icon_size(IconSize::Small)
.tooltip({
let repository_url = repository_url.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::with_meta(
"Open Repository",
None,
repository_url.clone(),
window,
cx,
)
}
@@ -616,7 +615,7 @@ impl ConfigureContextServerModal {
},
)
.key_binding(
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -634,7 +633,7 @@ impl ConfigureContextServerModal {
)
.disabled(is_connecting)
.key_binding(
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, window, cx)
KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(
@@ -709,7 +708,7 @@ impl Render for ConfigureContextServerModal {
State::Error(error) => Self::render_modal_error(error.clone()),
}),
)
.footer(self.render_modal_footer(window, cx)),
.footer(self.render_modal_footer(cx)),
)
}
}

View File

@@ -7,6 +7,7 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil
use editor::Editor;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
use language_model::LanguageModel;
use settings::Settings as _;
use ui::{
KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
@@ -96,6 +97,7 @@ pub struct NewProfileMode {
pub struct ManageProfilesModal {
fs: Arc<dyn Fs>,
context_server_registry: Entity<ContextServerRegistry>,
active_model: Option<Arc<dyn LanguageModel>>,
focus_handle: FocusHandle,
mode: Mode,
}
@@ -109,9 +111,14 @@ impl ManageProfilesModal {
workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let fs = workspace.app_state().fs.clone();
let active_model = panel
.read(cx)
.active_native_agent_thread(cx)
.and_then(|thread| thread.read(cx).model().cloned());
let context_server_registry = panel.read(cx).context_server_registry().clone();
workspace.toggle_modal(window, cx, |window, cx| {
let mut this = Self::new(fs, context_server_registry, window, cx);
let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
if let Some(profile_id) = action.customize_tools.clone() {
this.configure_builtin_tools(profile_id, window, cx);
@@ -125,6 +132,7 @@ impl ManageProfilesModal {
pub fn new(
fs: Arc<dyn Fs>,
active_model: Option<Arc<dyn LanguageModel>>,
context_server_registry: Entity<ContextServerRegistry>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -133,6 +141,7 @@ impl ManageProfilesModal {
Self {
fs,
active_model,
context_server_registry,
focus_handle,
mode: Mode::choose_profile(window, cx),
@@ -228,9 +237,11 @@ impl ManageProfilesModal {
let tool_picker = cx.new(|cx| {
let delegate = ToolPickerDelegate::builtin_tools(
//todo: This causes the web search tool to show up even it only works when using zed hosted models
agent::built_in_tool_names()
.map(|s| s.into())
.collect::<Vec<_>>(),
agent::supported_built_in_tool_names(
self.active_model.as_ref().map(|model| model.provider_id()),
)
.map(|s| s.into())
.collect::<Vec<_>>(),
self.fs.clone(),
profile_id.clone(),
profile,
@@ -341,10 +352,9 @@ impl ManageProfilesModal {
.size(LabelSize::Small)
.color(Color::Muted),
)
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&menu::Confirm,
&self.focus_handle,
window,
cx,
)),
)
@@ -638,14 +648,13 @@ impl ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().children(
div().child(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
.size(rems_from_px(12.)),
),
)
.on_click({
@@ -689,14 +698,9 @@ impl Render for ManageProfilesModal {
)
.child(Label::new("Go Back"))
.end_slot(
div().children(
KeyBinding::for_action_in(
&menu::Cancel,
&self.focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
div().child(
KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
.size(rems_from_px(12.)),
),
)
.on_click({

View File

@@ -669,7 +669,7 @@ impl Item for AgentDiffPane {
}
impl Render for AgentDiffPane {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_empty = self.multibuffer.read(cx).is_empty();
let focus_handle = &self.focus_handle;
@@ -702,7 +702,6 @@ impl Render for AgentDiffPane {
.key_binding(KeyBinding::for_action_in(
&ToggleFocus,
&focus_handle.clone(),
window,
cx,
))
.on_click(|_event, window, cx| {
@@ -719,14 +718,7 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
let thread = thread.clone();
Arc::new(
move |row,
status: &DiffHunkStatus,
hunk_range,
is_created_file,
line_height,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App| {
move |row, status, hunk_range, is_created_file, line_height, editor, _, cx| {
{
render_diff_hunk_controls(
row,
@@ -736,7 +728,6 @@ fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControl
line_height,
&thread,
editor,
window,
cx,
)
}
@@ -752,7 +743,6 @@ fn render_diff_hunk_controls(
line_height: Pixels,
thread: &AgentDiffThread,
editor: &Entity<Editor>,
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let editor = editor.clone();
@@ -775,13 +765,8 @@ fn render_diff_hunk_controls(
Button::new(("reject", row as u64), "Reject")
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(
&Reject,
&editor.read(cx).focus_handle(cx),
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
KeyBinding::for_action_in(&Reject, &editor.read(cx).focus_handle(cx), cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
let editor = editor.clone();
@@ -802,7 +787,7 @@ fn render_diff_hunk_controls(
}),
Button::new(("keep", row as u64), "Keep")
.key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click({
@@ -833,14 +818,8 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
}
})
.on_click({
@@ -869,12 +848,11 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}
@@ -1039,7 +1017,7 @@ impl ToolbarItemView for AgentDiffToolbar {
}
impl Render for AgentDiffToolbar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let spinner_icon = div()
.px_0p5()
.id("generating")
@@ -1114,7 +1092,6 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&RejectAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1129,7 +1106,6 @@ impl Render for AgentDiffToolbar {
KeyBinding::for_action_in(
&KeepAll,
&editor_focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
@@ -1206,13 +1182,8 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("reject-all", "Reject All")
.key_binding({
KeyBinding::for_action_in(
&RejectAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(&RejectAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&RejectAll, window, cx)
@@ -1221,13 +1192,8 @@ impl Render for AgentDiffToolbar {
.child(
Button::new("keep-all", "Keep All")
.key_binding({
KeyBinding::for_action_in(
&KeepAll,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.)))
KeyBinding::for_action_in(&KeepAll, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.)))
})
.on_click(cx.listener(|this, _, window, cx| {
this.dispatch_action(&KeepAll, window, cx)

View File

@@ -96,14 +96,8 @@ impl Render for AgentModelSelector {
.color(color)
.size(IconSize::XSmall),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
},
gpui::Corner::TopRight,
cx,

View File

@@ -36,8 +36,8 @@ use crate::{
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
use client::{UserStore, zed_urls};
use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
@@ -199,7 +199,7 @@ enum ActiveView {
thread_view: Entity<AcpThreadView>,
},
TextThread {
context_editor: Entity<TextThreadEditor>,
text_thread_editor: Entity<TextThreadEditor>,
title_editor: Entity<Editor>,
buffer_search_bar: Entity<BufferSearchBar>,
_subscriptions: Vec<gpui::Subscription>,
@@ -301,13 +301,13 @@ impl ActiveView {
}
pub fn text_thread(
context_editor: Entity<TextThreadEditor>,
text_thread_editor: Entity<TextThreadEditor>,
acp_history_store: Entity<agent::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
cx: &mut App,
) -> Self {
let title = context_editor.read(cx).title(cx).to_string();
let title = text_thread_editor.read(cx).title(cx).to_string();
let editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
@@ -323,7 +323,7 @@ impl ActiveView {
let subscriptions = vec![
window.subscribe(&editor, cx, {
{
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
move |editor, event, window, cx| match event {
EditorEvent::BufferEdited => {
if suppress_first_edit {
@@ -332,19 +332,19 @@ impl ActiveView {
}
let new_summary = editor.read(cx).text(cx);
context_editor.update(cx, |context_editor, cx| {
context_editor
.context()
.update(cx, |assistant_context, cx| {
assistant_context.set_custom_summary(new_summary, cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor
.text_thread()
.update(cx, |text_thread, cx| {
text_thread.set_custom_summary(new_summary, cx);
})
})
}
EditorEvent::Blurred => {
if editor.read(cx).text(cx).is_empty() {
let summary = context_editor
let summary = text_thread_editor
.read(cx)
.context()
.text_thread()
.read(cx)
.summary()
.or_default();
@@ -358,17 +358,17 @@ impl ActiveView {
}
}
}),
window.subscribe(&context_editor.read(cx).context().clone(), cx, {
window.subscribe(&text_thread_editor.read(cx).text_thread().clone(), cx, {
let editor = editor.clone();
move |assistant_context, event, window, cx| match event {
ContextEvent::SummaryGenerated => {
let summary = assistant_context.read(cx).summary().or_default();
move |text_thread, event, window, cx| match event {
TextThreadEvent::SummaryGenerated => {
let summary = text_thread.read(cx).summary().or_default();
editor.update(cx, |editor, cx| {
editor.set_text(summary, window, cx);
})
}
ContextEvent::PathChanged { old_path, new_path } => {
TextThreadEvent::PathChanged { old_path, new_path } => {
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -389,11 +389,11 @@ impl ActiveView {
let buffer_search_bar =
cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
buffer_search_bar.update(cx, |buffer_search_bar, cx| {
buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
buffer_search_bar.set_active_pane_item(Some(&text_thread_editor), window, cx)
});
Self::TextThread {
context_editor,
text_thread_editor,
title_editor: editor,
buffer_search_bar,
_subscriptions: subscriptions,
@@ -410,7 +410,7 @@ pub struct AgentPanel {
language_registry: Arc<LanguageRegistry>,
acp_history: Entity<AcpThreadHistory>,
history_store: Entity<agent::HistoryStore>,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
context_server_registry: Entity<ContextServerRegistry>,
inline_assist_context_store: Entity<ContextStore>,
@@ -474,7 +474,7 @@ impl AgentPanel {
let text_thread_store = workspace
.update(cx, |workspace, cx| {
let project = workspace.project().clone();
assistant_context::ContextStore::new(
assistant_text_thread::TextThreadStore::new(
project,
prompt_builder,
slash_commands,
@@ -512,7 +512,7 @@ impl AgentPanel {
fn new(
workspace: &Workspace,
text_thread_store: Entity<assistant_context::ContextStore>,
text_thread_store: Entity<assistant_text_thread::TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -565,8 +565,8 @@ impl AgentPanel {
DefaultView::TextThread => {
let context = text_thread_store.update(cx, |store, cx| store.create(cx));
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
context,
fs.clone(),
workspace.clone(),
@@ -579,7 +579,7 @@ impl AgentPanel {
editor
});
ActiveView::text_thread(
context_editor,
text_thread_editor,
history_store.clone(),
language_registry.clone(),
window,
@@ -736,8 +736,8 @@ impl AgentPanel {
.log_err()
.flatten();
let context_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_context(
let text_thread_editor = cx.new(|cx| {
let mut editor = TextThreadEditor::for_text_thread(
context,
self.fs.clone(),
self.workspace.clone(),
@@ -757,7 +757,7 @@ impl AgentPanel {
self.set_active_view(
ActiveView::text_thread(
context_editor.clone(),
text_thread_editor.clone(),
self.history_store.clone(),
self.language_registry.clone(),
window,
@@ -766,7 +766,7 @@ impl AgentPanel {
window,
cx,
);
context_editor.focus_handle(cx).focus(window);
text_thread_editor.focus_handle(cx).focus(window);
}
fn external_thread(
@@ -905,20 +905,20 @@ impl AgentPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
let context = self
let text_thread_task = self
.history_store
.update(cx, |store, cx| store.load_text_thread(path, cx));
cx.spawn_in(window, async move |this, cx| {
let context = context.await?;
let text_thread = text_thread_task.await?;
this.update_in(cx, |this, window, cx| {
this.open_text_thread(context, window, cx);
this.open_text_thread(text_thread, window, cx);
})
})
}
pub(crate) fn open_text_thread(
&mut self,
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -926,8 +926,8 @@ impl AgentPanel {
.log_err()
.flatten();
let editor = cx.new(|cx| {
TextThreadEditor::for_context(
context,
TextThreadEditor::for_text_thread(
text_thread,
self.fs.clone(),
self.workspace.clone(),
self.project.clone(),
@@ -965,8 +965,10 @@ impl AgentPanel {
ActiveView::ExternalAgentThread { thread_view } => {
thread_view.focus_handle(cx).focus(window);
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window);
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.focus_handle(cx).focus(window);
}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1183,9 +1185,11 @@ impl AgentPanel {
}
}
pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
pub(crate) fn active_text_thread_editor(&self) -> Option<Entity<TextThreadEditor>> {
match &self.active_view {
ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
ActiveView::TextThread {
text_thread_editor, ..
} => Some(text_thread_editor.clone()),
_ => None,
}
}
@@ -1206,16 +1210,16 @@ impl AgentPanel {
let new_is_special = new_is_history || new_is_config;
match &new_view {
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
})
}
ActiveView::TextThread {
text_thread_editor, ..
} => self.history_store.update(cx, |store, cx| {
if let Some(path) = text_thread_editor.read(cx).text_thread().read(cx).path() {
store.push_recently_opened_entry(
agent::HistoryEntryId::TextThread(path.clone()),
cx,
)
}
}),
ActiveView::ExternalAgentThread { .. } => {}
ActiveView::History | ActiveView::Configuration => {}
}
@@ -1372,7 +1376,9 @@ impl Focusable for AgentPanel {
match &self.active_view {
ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History => self.acp_history.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::TextThread {
text_thread_editor, ..
} => text_thread_editor.focus_handle(cx),
ActiveView::Configuration => {
if let Some(configuration) = self.configuration.as_ref() {
configuration.focus_handle(cx)
@@ -1507,17 +1513,17 @@ impl AgentPanel {
}
ActiveView::TextThread {
title_editor,
context_editor,
text_thread_editor,
..
} => {
let summary = context_editor.read(cx).context().read(cx).summary();
let summary = text_thread_editor.read(cx).text_thread().read(cx).summary();
match summary {
ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
TextThreadSummary::Pending => Label::new(TextThreadSummary::DEFAULT)
.color(Color::Muted)
.truncate()
.into_any_element(),
ContextSummary::Content(summary) => {
TextThreadSummary::Content(summary) => {
if summary.done {
div()
.w_full()
@@ -1530,17 +1536,17 @@ impl AgentPanel {
.into_any_element()
}
}
ContextSummary::Error => h_flex()
TextThreadSummary::Error => h_flex()
.w_full()
.child(title_editor.clone())
.child(
IconButton::new("retry-summary-generation", IconName::RotateCcw)
.icon_size(IconSize::Small)
.on_click({
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
move |_, _window, cx| {
context_editor.update(cx, |context_editor, cx| {
context_editor.regenerate_summary(cx);
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.regenerate_summary(cx);
});
}
})
@@ -1595,12 +1601,11 @@ impl AgentPanel {
.icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Agent Menu",
&ToggleOptionsMenu,
&focus_handle,
window,
cx,
)
}
@@ -1664,7 +1669,7 @@ impl AgentPanel {
.separator();
menu = menu
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Rules", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenSettings))
.separator()
.action(full_screen_label, Box::new(ToggleZoom));
@@ -1691,12 +1696,11 @@ impl AgentPanel {
.trigger_with_tooltip(
IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
{
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Recent Threads",
&ToggleNavigationMenu,
&focus_handle,
window,
cx,
)
}
@@ -1730,8 +1734,8 @@ impl AgentPanel {
this.go_back(&workspace::GoBack, window, cx);
}))
.tooltip({
move |window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
move |_window, cx| {
Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, cx)
}
})
}
@@ -1752,12 +1756,11 @@ impl AgentPanel {
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"New…",
&ToggleNewThreadMenu,
&focus_handle,
window,
cx,
)
}
@@ -2003,14 +2006,8 @@ impl AgentPanel {
.when_some(self.selected_agent.icon(), |this, icon| {
this.px(DynamicSpacing::Base02.rems(cx))
.child(Icon::new(icon).color(Color::Muted))
.tooltip(move |window, cx| {
Tooltip::with_meta(
selected_agent_label.clone(),
None,
"Selected Agent",
window,
cx,
)
.tooltip(move |_window, cx| {
Tooltip::with_meta(selected_agent_label.clone(), None, "Selected Agent", cx)
})
})
.into_any_element();
@@ -2186,7 +2183,6 @@ impl AgentPanel {
border_bottom: bool,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let zed_provider_configured = AgentSettings::get_global(cx)
@@ -2235,7 +2231,7 @@ impl AgentPanel {
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
KeyBinding::for_action_in(&OpenSettings, focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(|_event, window, cx| {
@@ -2253,7 +2249,7 @@ impl AgentPanel {
fn render_text_thread(
&self,
context_editor: &Entity<TextThreadEditor>,
text_thread_editor: &Entity<TextThreadEditor>,
buffer_search_bar: &Entity<BufferSearchBar>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -2287,7 +2283,7 @@ impl AgentPanel {
)
})
})
.child(context_editor.clone())
.child(text_thread_editor.clone())
.child(self.render_drag_target(cx))
}
@@ -2363,10 +2359,12 @@ impl AgentPanel {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
}
ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| {
ActiveView::TextThread {
text_thread_editor, ..
} => {
text_thread_editor.update(cx, |text_thread_editor, cx| {
TextThreadEditor::insert_dragged_files(
context_editor,
text_thread_editor,
paths,
added_worktrees,
window,
@@ -2437,7 +2435,7 @@ impl Render for AgentPanel {
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.acp_history.clone()),
ActiveView::TextThread {
context_editor,
text_thread_editor,
buffer_search_bar,
..
} => {
@@ -2453,7 +2451,6 @@ impl Render for AgentPanel {
true,
err,
&self.focus_handle(cx),
window,
cx,
))
} else {
@@ -2461,7 +2458,7 @@ impl Render for AgentPanel {
}
})
.child(self.render_text_thread(
context_editor,
text_thread_editor,
buffer_search_bar,
window,
cx,
@@ -2539,17 +2536,17 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
pub struct ConcreteAssistantPanelDelegate;
impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_context_editor(
fn active_text_thread_editor(
&self,
workspace: &mut Workspace,
_window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>> {
let panel = workspace.panel::<AgentPanel>(cx)?;
panel.read(cx).active_context_editor()
panel.read(cx).active_text_thread_editor()
}
fn open_saved_context(
fn open_local_text_thread(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -2565,10 +2562,10 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
})
}
fn open_remote_context(
fn open_remote_text_thread(
&self,
_workspace: &mut Workspace,
_context_id: assistant_context::ContextId,
_text_thread_id: assistant_text_thread::TextThreadId,
_window: &mut Window,
_cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>> {
@@ -2599,15 +2596,15 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
thread_view.update(cx, |thread_view, cx| {
thread_view.insert_selections(window, cx);
});
} else if let Some(context_editor) = panel.active_context_editor() {
} else if let Some(text_thread_editor) = panel.active_text_thread_editor() {
let snapshot = buffer.read(cx).snapshot(cx);
let selection_ranges = selection_ranges
.into_iter()
.map(|range| range.to_point(&snapshot))
.collect::<Vec<_>>();
context_editor.update(cx, |context_editor, cx| {
context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
text_thread_editor.update(cx, |text_thread_editor, cx| {
text_thread_editor.quote_ranges(selection_ranges, snapshot, window, cx)
});
}
});

View File

@@ -130,12 +130,6 @@ actions!(
]
);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Action)]
#[action(namespace = agent)]
#[action(deprecated_aliases = ["assistant::QuoteSelection"])]
/// Quotes the current selection in the agent panel's message editor.
pub struct QuoteSelection;
/// Creates a new conversation thread, optionally based on an existing thread.
#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
#[action(namespace = agent)]
@@ -256,7 +250,7 @@ pub fn init(
) {
AgentSettings::register(cx);
assistant_context::init(client.clone(), cx);
assistant_text_thread::init(client.clone(), cx);
rules_library::init(cx);
if !is_eval {
// Initializing the language model from the user settings messes with the eval, so we only initialize them when

View File

@@ -1,5 +1,5 @@
use agent::outline;
use assistant_context::AssistantContext;
use assistant_text_thread::TextThread;
use futures::future;
use futures::{FutureExt, future::Shared};
use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
@@ -581,7 +581,7 @@ impl Display for ThreadContext {
#[derive(Debug, Clone)]
pub struct TextThreadContextHandle {
pub context: Entity<AssistantContext>,
pub text_thread: Entity<TextThread>,
pub context_id: ContextId,
}
@@ -595,20 +595,20 @@ pub struct TextThreadContext {
impl TextThreadContextHandle {
// pub fn lookup_key() ->
pub fn eq_for_key(&self, other: &Self) -> bool {
self.context == other.context
self.text_thread == other.text_thread
}
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
self.context.hash(state)
self.text_thread.hash(state)
}
pub fn title(&self, cx: &App) -> SharedString {
self.context.read(cx).summary().or_default()
self.text_thread.read(cx).summary().or_default()
}
fn load(self, cx: &App) -> Task<Option<AgentContext>> {
let title = self.title(cx);
let text = self.context.read(cx).to_xml(cx);
let text = self.text_thread.read(cx).to_xml(cx);
let context = AgentContext::TextThread(TextThreadContext {
title,
text: text.into(),

View File

@@ -5,7 +5,7 @@ use crate::context::{
};
use agent_client_protocol as acp;
use anyhow::{Context as _, Result, anyhow};
use assistant_context::AssistantContext;
use assistant_text_thread::TextThread;
use collections::{HashSet, IndexSet};
use futures::{self, FutureExt};
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
@@ -200,13 +200,13 @@ impl ContextStore {
pub fn add_text_thread(
&mut self,
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
remove_if_exists: bool,
cx: &mut Context<Self>,
) -> Option<AgentContextHandle> {
let context_id = self.next_context_id.post_inc();
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
context,
text_thread,
context_id,
});
@@ -353,21 +353,15 @@ impl ContextStore {
);
};
}
// SuggestedContext::Thread { thread, name: _ } => {
// if let Some(thread) = thread.upgrade() {
// let context_id = self.next_context_id.post_inc();
// self.insert_context(
// AgentContextHandle::Thread(ThreadContextHandle { thread, context_id }),
// cx,
// );
// }
// }
SuggestedContext::TextThread { context, name: _ } => {
if let Some(context) = context.upgrade() {
SuggestedContext::TextThread {
text_thread,
name: _,
} => {
if let Some(text_thread) = text_thread.upgrade() {
let context_id = self.next_context_id.post_inc();
self.insert_context(
AgentContextHandle::TextThread(TextThreadContextHandle {
context,
text_thread,
context_id,
}),
cx,
@@ -392,7 +386,7 @@ impl ContextStore {
// }
AgentContextHandle::TextThread(text_thread_context) => {
self.context_text_thread_paths
.extend(text_thread_context.context.read(cx).path().cloned());
.extend(text_thread_context.text_thread.read(cx).path().cloned());
}
_ => {}
}
@@ -414,7 +408,7 @@ impl ContextStore {
.remove(thread_context.thread.read(cx).id());
}
AgentContextHandle::TextThread(text_thread_context) => {
if let Some(path) = text_thread_context.context.read(cx).path() {
if let Some(path) = text_thread_context.text_thread.read(cx).path() {
self.context_text_thread_paths.remove(path);
}
}
@@ -538,13 +532,9 @@ pub enum SuggestedContext {
icon_path: Option<SharedString>,
buffer: WeakEntity<Buffer>,
},
// Thread {
// name: SharedString,
// thread: WeakEntity<Thread>,
// },
TextThread {
name: SharedString,
context: WeakEntity<AssistantContext>,
text_thread: WeakEntity<TextThread>,
},
}
@@ -552,7 +542,6 @@ impl SuggestedContext {
pub fn name(&self) -> &SharedString {
match self {
Self::File { name, .. } => name,
// Self::Thread { name, .. } => name,
Self::TextThread { name, .. } => name,
}
}
@@ -560,7 +549,6 @@ impl SuggestedContext {
pub fn icon_path(&self) -> Option<SharedString> {
match self {
Self::File { icon_path, .. } => icon_path.clone(),
// Self::Thread { .. } => None,
Self::TextThread { .. } => None,
}
}
@@ -568,7 +556,6 @@ impl SuggestedContext {
pub fn kind(&self) -> ContextKind {
match self {
Self::File { .. } => ContextKind::File,
// Self::Thread { .. } => ContextKind::Thread,
Self::TextThread { .. } => ContextKind::TextThread,
}
}

View File

@@ -132,19 +132,19 @@ impl ContextStrip {
let workspace = self.workspace.upgrade()?;
let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
if let Some(active_context_editor) = panel.active_context_editor() {
let context = active_context_editor.read(cx).context();
let weak_context = context.downgrade();
let context = context.read(cx);
let path = context.path()?;
if let Some(active_text_thread_editor) = panel.active_text_thread_editor() {
let text_thread = active_text_thread_editor.read(cx).text_thread();
let weak_text_thread = text_thread.downgrade();
let text_thread = text_thread.read(cx);
let path = text_thread.path()?;
if self.context_store.read(cx).includes_text_thread(path) {
return None;
}
Some(SuggestedContext::TextThread {
name: context.summary().or_default(),
context: weak_context,
name: text_thread.summary().or_default(),
text_thread: weak_text_thread,
})
} else {
None
@@ -332,7 +332,7 @@ impl ContextStrip {
AgentContextHandle::TextThread(text_thread_context) => {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let context = text_thread_context.context.clone();
let context = text_thread_context.text_thread.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_text_thread(context, window, cx)
@@ -483,12 +483,11 @@ impl Render for ContextStrip {
.style(ui::ButtonStyle::Filled),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
@@ -558,12 +557,11 @@ impl Render for ContextStrip {
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Remove All Context",
&RemoveAllContext,
&focus_handle,
window,
cx,
)
}

View File

@@ -1508,8 +1508,8 @@ impl InlineAssistant {
return Some(InlineAssistTarget::Terminal(terminal_view));
}
let context_editor = agent_panel
.and_then(|panel| panel.read(cx).active_context_editor())
let text_thread_editor = agent_panel
.and_then(|panel| panel.read(cx).active_text_thread_editor())
.and_then(|editor| {
let editor = &editor.read(cx).editor().clone();
if editor.read(cx).is_focused(window) {
@@ -1519,8 +1519,8 @@ impl InlineAssistant {
}
});
if let Some(context_editor) = context_editor {
Some(InlineAssistTarget::Editor(context_editor))
if let Some(text_thread_editor) = text_thread_editor {
Some(InlineAssistTarget::Editor(text_thread_editor))
} else if let Some(workspace_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))

View File

@@ -468,12 +468,11 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta(
mode.tooltip_interrupt(),
Some(&menu::Cancel),
"Changes won't be discarded",
window,
cx,
)
})
@@ -487,12 +486,11 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta(
mode.tooltip_restart(),
Some(&menu::Confirm),
"Changes will be discarded",
window,
cx,
)
})
@@ -505,8 +503,8 @@ impl<T: 'static> PromptEditor<T> {
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action(mode.tooltip_accept(), &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
@@ -519,11 +517,10 @@ impl<T: 'static> PromptEditor<T> {
IconButton::new("confirm", IconName::PlayFilled)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
window,
cx,
)
})
@@ -615,13 +612,12 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
KeyBinding::for_action_in(
&CyclePreviousInlineAssist,
&focus_handle,
window,
cx,
),
);
@@ -657,13 +653,12 @@ impl<T: 'static> PromptEditor<T> {
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = self.editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
cx.new(|cx| {
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
KeyBinding::for_action_in(
&CycleNextInlineAssist,
&focus_handle,
window,
cx,
),
);

View File

@@ -162,12 +162,11 @@ impl Render for ProfileSelector {
PickerPopoverMenu::new(
picker,
trigger_button,
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Profile Menu",
&ToggleProfileSelector,
&focus_handle,
window,
cx,
)
},

View File

@@ -155,8 +155,8 @@ impl PickerDelegate for SlashCommandDelegate {
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, window, cx)
.update(cx, |text_thread_editor, cx| {
text_thread_editor.insert_command(&info.name, window, cx)
})
.ok();
}

View File

@@ -1,5 +1,4 @@
use crate::{
QuoteSelection,
language_model_selector::{LanguageModelSelector, language_model_selector},
ui::BurnModeTooltip,
};
@@ -72,13 +71,13 @@ use workspace::{
pane,
searchable::{SearchEvent, SearchableItem},
};
use zed_actions::agent::ToggleModelSelector;
use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
use assistant_context::{
AssistantContext, CacheStatus, Content, ContextEvent, ContextId, InvokedSlashCommandId,
InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus,
PendingSlashCommandStatus, ThoughtProcessOutputSection,
use assistant_text_thread::{
CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
TextThreadId, ThoughtProcessOutputSection,
};
actions!(
@@ -127,14 +126,14 @@ pub enum ThoughtProcessStatus {
}
pub trait AgentPanelDelegate {
fn active_context_editor(
fn active_text_thread_editor(
&self,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<Entity<TextThreadEditor>>;
fn open_saved_context(
fn open_local_text_thread(
&self,
workspace: &mut Workspace,
path: Arc<Path>,
@@ -142,10 +141,10 @@ pub trait AgentPanelDelegate {
cx: &mut Context<Workspace>,
) -> Task<Result<()>>;
fn open_remote_context(
fn open_remote_text_thread(
&self,
workspace: &mut Workspace,
context_id: ContextId,
text_thread_id: TextThreadId,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Task<Result<Entity<TextThreadEditor>>>;
@@ -178,7 +177,7 @@ struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
impl Global for GlobalAssistantPanelDelegate {}
pub struct TextThreadEditor {
context: Entity<AssistantContext>,
text_thread: Entity<TextThread>,
fs: Arc<dyn Fs>,
slash_commands: Arc<SlashCommandWorkingSet>,
workspace: WeakEntity<Workspace>,
@@ -224,8 +223,8 @@ impl TextThreadEditor {
.detach();
}
pub fn for_context(
context: Entity<AssistantContext>,
pub fn for_text_thread(
text_thread: Entity<TextThread>,
fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
@@ -234,14 +233,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) -> Self {
let completion_provider = SlashCommandCompletionProvider::new(
context.read(cx).slash_commands().clone(),
text_thread.read(cx).slash_commands().clone(),
Some(cx.entity().downgrade()),
Some(workspace.clone()),
);
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(context.read(cx).buffer().clone(), None, window, cx);
Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
editor.disable_scrollbars_and_minimap(window, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
@@ -265,18 +264,24 @@ impl TextThreadEditor {
});
let _subscriptions = vec![
cx.observe(&context, |_, _, cx| cx.notify()),
cx.subscribe_in(&context, window, Self::handle_context_event),
cx.observe(&text_thread, |_, _, cx| cx.notify()),
cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
cx.subscribe_in(&editor, window, Self::handle_editor_event),
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
];
let slash_command_sections = context.read(cx).slash_command_output_sections().to_vec();
let thought_process_sections = context.read(cx).thought_process_output_sections().to_vec();
let slash_commands = context.read(cx).slash_commands().clone();
let slash_command_sections = text_thread
.read(cx)
.slash_command_output_sections()
.to_vec();
let thought_process_sections = text_thread
.read(cx)
.thought_process_output_sections()
.to_vec();
let slash_commands = text_thread.read(cx).slash_commands().clone();
let mut this = Self {
context,
text_thread,
slash_commands,
editor,
lsp_adapter_delegate,
@@ -338,8 +343,8 @@ impl TextThreadEditor {
});
}
pub fn context(&self) -> &Entity<AssistantContext> {
&self.context
pub fn text_thread(&self) -> &Entity<TextThread> {
&self.text_thread
}
pub fn editor(&self) -> &Entity<Editor> {
@@ -351,9 +356,9 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
editor.insert(&format!("/{command_name}\n\n"), window, cx)
});
let command = self.context.update(cx, |context, cx| {
context.reparse(cx);
context.parsed_slash_commands()[0].clone()
let command = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread.parsed_slash_commands()[0].clone()
});
self.run_command(
command.source_range,
@@ -376,11 +381,14 @@ impl TextThreadEditor {
fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.last_error = None;
if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
if let Some(user_message) = self
.text_thread
.update(cx, |text_thread, cx| text_thread.assist(cx))
{
let new_selection = {
let cursor = user_message
.start
.to_offset(self.context.read(cx).buffer().read(cx));
.to_offset(self.text_thread.read(cx).buffer().read(cx));
cursor..cursor
};
self.editor.update(cx, |editor, cx| {
@@ -404,8 +412,8 @@ impl TextThreadEditor {
self.last_error = None;
if self
.context
.update(cx, |context, cx| context.cancel_last_assist(cx))
.text_thread
.update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
{
return;
}
@@ -420,13 +428,13 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
let cursors = self.cursors(cx);
self.context.update(cx, |context, cx| {
let messages = context
self.text_thread.update(cx, |text_thread, cx| {
let messages = text_thread
.messages_for_offsets(cursors, cx)
.into_iter()
.map(|message| message.id)
.collect();
context.cycle_message_roles(messages, cx)
text_thread.cycle_message_roles(messages, cx)
});
}
@@ -492,11 +500,11 @@ impl TextThreadEditor {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
let mut commands_by_range = HashMap::default();
let workspace = self.workspace.clone();
self.context.update(cx, |context, cx| {
context.reparse(cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
for selection in selections.iter() {
if let Some(command) =
context.pending_command_for_position(selection.head().text_anchor, cx)
text_thread.pending_command_for_position(selection.head().text_anchor, cx)
{
commands_by_range
.entry(command.source_range.clone())
@@ -534,14 +542,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(command) = self.slash_commands.command(name, cx) {
let context = self.context.read(cx);
let sections = context
let text_thread = self.text_thread.read(cx);
let sections = text_thread
.slash_command_output_sections()
.iter()
.filter(|section| section.is_valid(context.buffer().read(cx)))
.filter(|section| section.is_valid(text_thread.buffer().read(cx)))
.cloned()
.collect::<Vec<_>>();
let snapshot = context.buffer().read(cx).snapshot();
let snapshot = text_thread.buffer().read(cx).snapshot();
let output = command.run(
arguments,
&sections,
@@ -551,8 +559,8 @@ impl TextThreadEditor {
window,
cx,
);
self.context.update(cx, |context, cx| {
context.insert_command_output(
self.text_thread.update(cx, |text_thread, cx| {
text_thread.insert_command_output(
command_range,
name,
output,
@@ -563,32 +571,32 @@ impl TextThreadEditor {
}
}
fn handle_context_event(
fn handle_text_thread_event(
&mut self,
_: &Entity<AssistantContext>,
event: &ContextEvent,
_: &Entity<TextThread>,
event: &TextThreadEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
let context_editor = cx.entity().downgrade();
let text_thread_editor = cx.entity().downgrade();
match event {
ContextEvent::MessagesEdited => {
TextThreadEvent::MessagesEdited => {
self.update_message_headers(cx);
self.update_image_blocks(cx);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
ContextEvent::SummaryChanged => {
TextThreadEvent::SummaryChanged => {
cx.emit(EditorEvent::TitleChanged);
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
});
}
ContextEvent::SummaryGenerated => {}
ContextEvent::PathChanged { .. } => {}
ContextEvent::StartedThoughtProcess(range) => {
TextThreadEvent::SummaryGenerated => {}
TextThreadEvent::PathChanged { .. } => {}
TextThreadEvent::StartedThoughtProcess(range) => {
let creases = self.insert_thought_process_output_sections(
[(
ThoughtProcessOutputSection {
@@ -601,7 +609,7 @@ impl TextThreadEditor {
);
self.pending_thought_process = Some((creases[0], range.start));
}
ContextEvent::EndedThoughtProcess(end) => {
TextThreadEvent::EndedThoughtProcess(end) => {
if let Some((crease_id, start)) = self.pending_thought_process.take() {
self.editor.update(cx, |editor, cx| {
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
@@ -627,7 +635,7 @@ impl TextThreadEditor {
);
}
}
ContextEvent::StreamedCompletion => {
TextThreadEvent::StreamedCompletion => {
self.editor.update(cx, |editor, cx| {
if let Some(scroll_position) = self.scroll_position {
let snapshot = editor.snapshot(window, cx);
@@ -642,7 +650,7 @@ impl TextThreadEditor {
}
});
}
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
self.editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
@@ -658,12 +666,12 @@ impl TextThreadEditor {
updated.iter().map(|command| {
let workspace = self.workspace.clone();
let confirm_command = Arc::new({
let context_editor = context_editor.clone();
let text_thread_editor = text_thread_editor.clone();
let command = command.clone();
move |window: &mut Window, cx: &mut App| {
context_editor
.update(cx, |context_editor, cx| {
context_editor.run_command(
text_thread_editor
.update(cx, |text_thread_editor, cx| {
text_thread_editor.run_command(
command.source_range.clone(),
&command.name,
&command.arguments,
@@ -713,17 +721,17 @@ impl TextThreadEditor {
);
})
}
ContextEvent::InvokedSlashCommandChanged { command_id } => {
TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
self.update_invoked_slash_command(*command_id, window, cx);
}
ContextEvent::SlashCommandOutputSectionAdded { section } => {
TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
self.insert_slash_command_output_sections([section.clone()], false, window, cx);
}
ContextEvent::Operation(_) => {}
ContextEvent::ShowAssistError(error_message) => {
TextThreadEvent::Operation(_) => {}
TextThreadEvent::ShowAssistError(error_message) => {
self.last_error = Some(AssistError::Message(error_message.clone()));
}
ContextEvent::ShowPaymentRequiredError => {
TextThreadEvent::ShowPaymentRequiredError => {
self.last_error = Some(AssistError::PaymentRequired);
}
}
@@ -736,14 +744,14 @@ impl TextThreadEditor {
cx: &mut Context<Self>,
) {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
self.text_thread.read(cx).invoked_slash_command(&command_id)
&& let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
{
let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
for range in run_commands_in_ranges {
let commands = self.context.update(cx, |context, cx| {
context.reparse(cx);
context
let commands = self.text_thread.update(cx, |text_thread, cx| {
text_thread.reparse(cx);
text_thread
.pending_commands_for_range(range.clone(), cx)
.to_vec()
});
@@ -764,7 +772,7 @@ impl TextThreadEditor {
self.editor.update(cx, |editor, cx| {
if let Some(invoked_slash_command) =
self.context.read(cx).invoked_slash_command(&command_id)
self.text_thread.read(cx).invoked_slash_command(&command_id)
{
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
let buffer = editor.buffer().read(cx).snapshot(cx);
@@ -791,7 +799,7 @@ impl TextThreadEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
buffer.as_singleton().unwrap();
let context = self.context.downgrade();
let context = self.text_thread.downgrade();
let range = buffer
.anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
.unwrap();
@@ -1021,7 +1029,7 @@ impl TextThreadEditor {
let render_block = |message: MessageMetadata| -> RenderBlock {
Arc::new({
let context = self.context.clone();
let text_thread = self.text_thread.clone();
move |cx| {
let message_id = MessageId(message.timestamp);
@@ -1085,20 +1093,19 @@ impl TextThreadEditor {
.child(label)
.children(spinner),
)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Toggle message role",
None,
"Available roles: You (User), Agent, System",
window,
cx,
)
})
.on_click({
let context = context.clone();
let text_thread = text_thread.clone();
move |_, _window, cx| {
context.update(cx, |context, cx| {
context.cycle_message_roles(
text_thread.update(cx, |text_thread, cx| {
text_thread.cycle_message_roles(
HashSet::from_iter(Some(message_id)),
cx,
)
@@ -1126,12 +1133,11 @@ impl TextThreadEditor {
.size(IconSize::XSmall)
.color(Color::Hint),
)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Context Cached",
None,
"Large messages cached to optimize performance",
window,
cx,
)
})
@@ -1161,11 +1167,11 @@ impl TextThreadEditor {
.icon_position(IconPosition::Start)
.tooltip(Tooltip::text("View Details"))
.on_click({
let context = context.clone();
let text_thread = text_thread.clone();
let error = error.clone();
move |_, _window, cx| {
context.update(cx, |_, cx| {
cx.emit(ContextEvent::ShowAssistError(
text_thread.update(cx, |_, cx| {
cx.emit(TextThreadEvent::ShowAssistError(
error.clone(),
));
});
@@ -1208,7 +1214,7 @@ impl TextThreadEditor {
};
let mut new_blocks = vec![];
let mut block_index_to_message = vec![];
for message in self.context.read(cx).messages(cx) {
for message in self.text_thread.read(cx).messages(cx) {
if blocks_to_remove.remove(&message.id).is_some() {
// This is an old message that we might modify.
let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
@@ -1249,18 +1255,18 @@ impl TextThreadEditor {
) -> Option<(String, bool)> {
const CODE_FENCE_DELIMITER: &str = "```";
let context_editor = context_editor_view.read(cx).editor.clone();
context_editor.update(cx, |context_editor, cx| {
let display_map = context_editor.display_snapshot(cx);
if context_editor
let text_thread_editor = context_editor_view.read(cx).editor.clone();
text_thread_editor.update(cx, |text_thread_editor, cx| {
let display_map = text_thread_editor.display_snapshot(cx);
if text_thread_editor
.selections
.newest::<Point>(&display_map)
.is_empty()
{
let snapshot = context_editor.buffer().read(cx).snapshot(cx);
let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
let (_, _, snapshot) = snapshot.as_singleton()?;
let head = context_editor
let head = text_thread_editor
.selections
.newest::<Point>(&display_map)
.head();
@@ -1280,8 +1286,8 @@ impl TextThreadEditor {
(!text.is_empty()).then_some((text, true))
} else {
let selection = context_editor.selections.newest_adjusted(&display_map);
let buffer = context_editor.buffer().read(cx).snapshot(cx);
let selection = text_thread_editor.selections.newest_adjusted(&display_map);
let buffer = text_thread_editor.buffer().read(cx).snapshot(cx);
let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
(!selected_text.is_empty()).then_some((selected_text, false))
@@ -1299,7 +1305,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_context_editor(workspace, window, cx)
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
else {
return;
};
@@ -1327,7 +1333,7 @@ impl TextThreadEditor {
let result = maybe!({
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_view =
agent_panel_delegate.active_context_editor(workspace, window, cx)?;
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
Self::get_selection_or_code_block(&context_editor_view, cx)
});
let Some((text, is_code_block)) = result else {
@@ -1364,7 +1370,7 @@ impl TextThreadEditor {
return;
};
let Some(context_editor_view) =
agent_panel_delegate.active_context_editor(workspace, window, cx)
agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
else {
return;
};
@@ -1450,7 +1456,7 @@ impl TextThreadEditor {
pub fn quote_selection(
workspace: &mut Workspace,
_: &QuoteSelection,
_: &AddSelectionToThread,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
@@ -1625,29 +1631,33 @@ impl TextThreadEditor {
)
});
let context = self.context.read(cx);
let text_thread = self.text_thread.read(cx);
let mut text = String::new();
// If selection is empty, we want to copy the entire line
if selection.range().is_empty() {
let snapshot = context.buffer().read(cx).snapshot();
let snapshot = text_thread.buffer().read(cx).snapshot();
let point = snapshot.offset_to_point(selection.range().start);
selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
selection.end = snapshot
.point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
for chunk in context.buffer().read(cx).text_for_range(selection.range()) {
for chunk in text_thread
.buffer()
.read(cx)
.text_for_range(selection.range())
{
text.push_str(chunk);
}
} else {
for message in context.messages(cx) {
for message in text_thread.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
for chunk in context.buffer().read(cx).text_for_range(range) {
for chunk in text_thread.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
if message.offset_range.end < selection.range().end {
@@ -1758,7 +1768,7 @@ impl TextThreadEditor {
});
});
self.context.update(cx, |context, cx| {
self.text_thread.update(cx, |text_thread, cx| {
for image in images {
let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
else {
@@ -1768,7 +1778,7 @@ impl TextThreadEditor {
let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
for image_position in image_positions.iter() {
context.insert_content(
text_thread.insert_content(
Content::Image {
anchor: image_position.text_anchor,
image_id,
@@ -1789,7 +1799,7 @@ impl TextThreadEditor {
let excerpt_id = *buffer.as_singleton().unwrap().0;
let old_blocks = std::mem::take(&mut self.image_blocks);
let new_blocks = self
.context
.text_thread
.read(cx)
.contents(cx)
.map(
@@ -1837,36 +1847,36 @@ impl TextThreadEditor {
}
fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| {
self.text_thread.update(cx, |text_thread, cx| {
let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
for selection in selections.as_ref() {
let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
let range = selection
.map(|endpoint| endpoint.to_offset(&buffer))
.range();
context.split_message(range, cx);
text_thread.split_message(range, cx);
}
});
}
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
self.context.update(cx, |context, cx| {
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
self.text_thread.update(cx, |text_thread, cx| {
text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
});
}
pub fn title(&self, cx: &App) -> SharedString {
self.context.read(cx).summary().or_default()
self.text_thread.read(cx).summary().or_default()
}
pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
self.context
.update(cx, |context, cx| context.summarize(true, cx));
self.text_thread
.update(cx, |text_thread, cx| text_thread.summarize(true, cx));
}
fn render_remaining_tokens(&self, cx: &App) -> Option<impl IntoElement + use<>> {
let (token_count_color, token_count, max_token_count, tooltip) =
match token_state(&self.context, cx)? {
match token_state(&self.text_thread, cx)? {
TokenState::NoTokensLeft {
max_token_count,
token_count,
@@ -1914,7 +1924,7 @@ impl TextThreadEditor {
fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let (style, tooltip) = match token_state(&self.context, cx) {
let (style, tooltip) = match token_state(&self.text_thread, cx) {
Some(TokenState::NoTokensLeft { .. }) => (
ButtonStyle::Tinted(TintColor::Error),
Some(Tooltip::text("Token limit reached")(window, cx)),
@@ -1947,7 +1957,7 @@ impl TextThreadEditor {
})
.layer(ElevationIndex::ModalSurface)
.key_binding(
KeyBinding::for_action_in(&Assist, &focus_handle, window, cx)
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(move |_event, window, cx| {
@@ -1982,20 +1992,14 @@ impl TextThreadEditor {
.icon_color(Color::Muted)
.selected_icon_color(Color::Accent)
.selected_style(ButtonStyle::Filled),
move |window, cx| {
Tooltip::with_meta(
"Add Context",
None,
"Type / to insert via keyboard",
window,
cx,
)
move |_window, cx| {
Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
},
)
}
fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let context = self.context().read(cx);
let text_thread = self.text_thread().read(cx);
let active_model = LanguageModelRegistry::read_global(cx)
.default_model()
.map(|default| default.model)?;
@@ -2003,7 +2007,7 @@ impl TextThreadEditor {
return None;
}
let active_completion_mode = context.completion_mode();
let active_completion_mode = text_thread.completion_mode();
let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
let icon = if burn_mode_enabled {
IconName::ZedBurnModeOn
@@ -2018,8 +2022,8 @@ impl TextThreadEditor {
.toggle_state(burn_mode_enabled)
.selected_icon_color(Color::Error)
.on_click(cx.listener(move |this, _event, _window, cx| {
this.context().update(cx, |context, _cx| {
context.set_completion_mode(match active_completion_mode {
this.text_thread().update(cx, |text_thread, _cx| {
text_thread.set_completion_mode(match active_completion_mode {
CompletionMode::Burn => CompletionMode::Normal,
CompletionMode::Normal => CompletionMode::Burn,
});
@@ -2078,14 +2082,8 @@ impl TextThreadEditor {
)
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
},
gpui::Corner::BottomRight,
cx,
@@ -2652,10 +2650,10 @@ impl FollowableItem for TextThreadEditor {
}
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
let context = self.context.read(cx);
let text_thread = self.text_thread.read(cx);
Some(proto::view::Variant::ContextEditor(
proto::view::ContextEditor {
context_id: context.id().to_proto(),
context_id: text_thread.id().to_proto(),
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(window, cx)
{
@@ -2681,22 +2679,22 @@ impl FollowableItem for TextThreadEditor {
unreachable!()
};
let context_id = ContextId::from_proto(state.context_id);
let text_thread_id = TextThreadId::from_proto(state.context_id);
let editor_state = state.editor?;
let project = workspace.read(cx).project().clone();
let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
let context_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_context(workspace, context_id, window, cx)
let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
});
Some(window.spawn(cx, async move |cx| {
let context_editor = context_editor_task.await?;
context_editor
.update_in(cx, |context_editor, window, cx| {
context_editor.remote_id = Some(id);
context_editor.editor.update(cx, |editor, cx| {
let text_thread_editor = text_thread_editor_task.await?;
text_thread_editor
.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.remote_id = Some(id);
text_thread_editor.editor.update(cx, |editor, cx| {
editor.apply_update_proto(
&project,
proto::update_view::Variant::Editor(proto::update_view::Editor {
@@ -2713,7 +2711,7 @@ impl FollowableItem for TextThreadEditor {
})
})?
.await?;
Ok(context_editor)
Ok(text_thread_editor)
}))
}
@@ -2760,7 +2758,7 @@ impl FollowableItem for TextThreadEditor {
}
fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
if existing.context.read(cx).id() == self.context.read(cx).id() {
if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
Some(item::Dedup::KeepExisting)
} else {
None
@@ -2772,17 +2770,17 @@ enum PendingSlashCommand {}
fn invoked_slash_command_fold_placeholder(
command_id: InvokedSlashCommandId,
context: WeakEntity<AssistantContext>,
text_thread: WeakEntity<TextThread>,
) -> FoldPlaceholder {
FoldPlaceholder {
constrain_width: false,
merge_adjacent: false,
render: Arc::new(move |fold_id, _, cx| {
let Some(context) = context.upgrade() else {
let Some(text_thread) = text_thread.upgrade() else {
return Empty.into_any();
};
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
return Empty.into_any();
};
@@ -2823,14 +2821,15 @@ enum TokenState {
},
}
fn token_state(context: &Entity<AssistantContext>, cx: &App) -> Option<TokenState> {
fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
let model = LanguageModelRegistry::read_global(cx)
.default_model()?
.model;
let token_count = context.read(cx).token_count()?;
let max_token_count = model.max_token_count_for_mode(context.read(cx).completion_mode().into());
let token_count = text_thread.read(cx).token_count()?;
let max_token_count =
model.max_token_count_for_mode(text_thread.read(cx).completion_mode().into());
let token_state = if max_token_count.saturating_sub(token_count) == 0 {
TokenState::NoTokensLeft {
max_token_count,
@@ -2942,7 +2941,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(vec![
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
(Role::User, "What is the Zed editor?"),
(
Role::Assistant,
@@ -2952,8 +2951,8 @@ mod tests {
],cx).await;
// Select & Copy whole user message
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_range(&context, 0, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2964,8 +2963,8 @@ mod tests {
);
// Select & Copy whole assistant message
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_range(&context, 1, &mut cx),
indoc! {"
What is the Zed editor?
@@ -2979,7 +2978,7 @@ mod tests {
#[gpui::test]
async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
let (context, context_editor, mut cx) = setup_context_editor_text(
let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
vec![
(Role::User, "user1"),
(Role::Assistant, "assistant1"),
@@ -2992,8 +2991,8 @@ mod tests {
// Copy and paste first assistant message
let message_2_range = message_range(&context, 1, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_2_range.start..message_2_range.start,
indoc! {"
user1
@@ -3006,8 +3005,8 @@ mod tests {
// Copy and cut second assistant message
let message_3_range = message_range(&context, 2, &mut cx);
assert_copy_paste_context_editor(
&context_editor,
assert_copy_paste_text_thread_editor(
&text_thread_editor,
message_3_range.start..message_3_range.start,
indoc! {"
user1
@@ -3094,29 +3093,29 @@ mod tests {
}
}
async fn setup_context_editor_text(
async fn setup_text_thread_editor_text(
messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> (
Entity<AssistantContext>,
Entity<TextThread>,
Entity<TextThreadEditor>,
VisualTestContext,
) {
cx.update(init_test);
let fs = FakeFs::new(cx.executor());
let context = create_context_with_messages(messages, cx);
let text_thread = create_text_thread_with_messages(messages, cx);
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(*window, cx);
let context_editor = window
let text_thread_editor = window
.update(&mut cx, |_, window, cx| {
cx.new(|cx| {
TextThreadEditor::for_context(
context.clone(),
TextThreadEditor::for_text_thread(
text_thread.clone(),
fs,
workspace.downgrade(),
project,
@@ -3128,59 +3127,59 @@ mod tests {
})
.unwrap();
(context, context_editor, cx)
(text_thread, text_thread_editor, cx)
}
fn message_range(
context: &Entity<AssistantContext>,
text_thread: &Entity<TextThread>,
message_ix: usize,
cx: &mut TestAppContext,
) -> Range<usize> {
context.update(cx, |context, cx| {
context
text_thread.update(cx, |text_thread, cx| {
text_thread
.messages(cx)
.nth(message_ix)
.unwrap()
.anchor_range
.to_offset(&context.buffer().read(cx).snapshot())
.to_offset(&text_thread.buffer().read(cx).snapshot())
})
}
fn assert_copy_paste_context_editor<T: editor::ToOffset>(
context_editor: &Entity<TextThreadEditor>,
fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
text_thread_editor: &Entity<TextThreadEditor>,
range: Range<T>,
expected_text: &str,
cx: &mut VisualTestContext,
) {
context_editor.update_in(cx, |context_editor, window, cx| {
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges([range])
});
});
context_editor.copy(&Default::default(), window, cx);
text_thread_editor.copy(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
editor.move_to_end(&Default::default(), window, cx);
});
context_editor.paste(&Default::default(), window, cx);
text_thread_editor.paste(&Default::default(), window, cx);
context_editor.editor.update(cx, |editor, cx| {
text_thread_editor.editor.update(cx, |editor, cx| {
assert_eq!(editor.text(cx), expected_text);
});
});
}
fn create_context_with_messages(
fn create_text_thread_with_messages(
mut messages: Vec<(Role, &str)>,
cx: &mut TestAppContext,
) -> Entity<AssistantContext> {
) -> Entity<TextThread> {
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
cx.new(|cx| {
let mut context = AssistantContext::local(
let mut text_thread = TextThread::local(
registry,
None,
None,
@@ -3188,33 +3187,33 @@ mod tests {
Arc::new(SlashCommandWorkingSet::default()),
cx,
);
let mut message_1 = context.messages(cx).next().unwrap();
let mut message_1 = text_thread.messages(cx).next().unwrap();
let (role, text) = messages.remove(0);
loop {
if role == message_1.role {
context.buffer().update(cx, |buffer, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(message_1.offset_range, text)], None, cx);
});
break;
}
let mut ids = HashSet::default();
ids.insert(message_1.id);
context.cycle_message_roles(ids, cx);
message_1 = context.messages(cx).next().unwrap();
text_thread.cycle_message_roles(ids, cx);
message_1 = text_thread.messages(cx).next().unwrap();
}
let mut last_message_id = message_1.id;
for (role, text) in messages {
context.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = context.messages(cx).last().unwrap();
text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
let message = text_thread.messages(cx).last().unwrap();
last_message_id = message.id;
context.buffer().update(cx, |buffer, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(message.offset_range, text)], None, cx);
})
}
context
text_thread
})
}

View File

@@ -18,7 +18,7 @@ impl BurnModeTooltip {
}
impl Render for BurnModeTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (icon, color) = if self.selected {
(IconName::ZedBurnModeOn, Color::Error)
} else {
@@ -45,8 +45,7 @@ impl Render for BurnModeTooltip {
.child(Label::new("Burn Mode"))
.when(self.selected, |title| title.child(turned_on));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, window, cx)
.map(|kb| kb.size(rems_from_px(12.)));
let keybinding = KeyBinding::for_action(&ToggleBurnMode, cx).size(rems_from_px(12.));
tooltip_container(cx, |this, _| {
this
@@ -54,7 +53,7 @@ impl Render for BurnModeTooltip {
h_flex()
.justify_between()
.child(title)
.children(keybinding)
.child(keybinding)
)
.child(
div()

View File

@@ -244,8 +244,8 @@ impl RenderOnce for ContextPill {
.truncate(),
),
)
.tooltip(|window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", window, cx)
.tooltip(|_window, cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
})
.when_some(on_click.as_ref(), |element, on_click| {
let on_click = on_click.clone();
@@ -497,9 +497,9 @@ impl AddedContext {
icon_path: None,
status: ContextStatus::Ready,
render_hover: {
let context = handle.context.clone();
let text_thread = handle.text_thread.clone();
Some(Rc::new(move |_, cx| {
let text = context.read(cx).to_xml(cx);
let text = text_thread.read(cx).to_xml(cx);
ContextPillHover::new_text(text.into(), cx).into()
}))
},

View File

@@ -1,5 +1,5 @@
[package]
name = "assistant_context"
name = "assistant_text_thread"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/assistant_context.rs"
path = "src/assistant_text_thread.rs"
[features]
test-support = []

View File

@@ -0,0 +1,15 @@
#[cfg(test)]
mod assistant_text_thread_tests;
mod text_thread;
mod text_thread_store;
pub use crate::text_thread::*;
pub use crate::text_thread_store::*;
use client::Client;
use gpui::App;
use std::sync::Arc;
pub fn init(client: Arc<Client>, _: &mut App) {
text_thread_store::init(&client.into());
}

View File

@@ -1,7 +1,3 @@
#[cfg(test)]
mod assistant_context_tests;
mod context_store;
use agent_settings::{AgentSettings, SUMMARIZE_THREAD_PROMPT};
use anyhow::{Context as _, Result, bail};
use assistant_slash_command::{
@@ -9,7 +5,7 @@ use assistant_slash_command::{
SlashCommandResult, SlashCommandWorkingSet,
};
use assistant_slash_commands::FileCommandMetadata;
use client::{self, Client, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
use client::{self, ModelRequestUsage, RequestUsage, proto, telemetry::Telemetry};
use clock::ReplicaId;
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use collections::{HashMap, HashSet};
@@ -27,7 +23,7 @@ use language_model::{
report_assistant_event,
};
use open_ai::Model as OpenAiModel;
use paths::contexts_dir;
use paths::text_threads_dir;
use project::Project;
use prompt_store::PromptBuilder;
use serde::{Deserialize, Serialize};
@@ -48,16 +44,10 @@ use ui::IconName;
use util::{ResultExt, TryFutureExt, post_inc};
use uuid::Uuid;
pub use crate::context_store::*;
pub fn init(client: Arc<Client>, _: &mut App) {
context_store::init(&client.into());
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
pub struct TextThreadId(String);
impl ContextId {
impl TextThreadId {
pub fn new() -> Self {
Self(Uuid::new_v4().to_string())
}
@@ -130,7 +120,7 @@ impl MessageStatus {
}
#[derive(Clone, Debug)]
pub enum ContextOperation {
pub enum TextThreadOperation {
InsertMessage {
anchor: MessageAnchor,
metadata: MessageMetadata,
@@ -142,7 +132,7 @@ pub enum ContextOperation {
version: clock::Global,
},
UpdateSummary {
summary: ContextSummaryContent,
summary: TextThreadSummaryContent,
version: clock::Global,
},
SlashCommandStarted {
@@ -170,7 +160,7 @@ pub enum ContextOperation {
BufferOperation(language::Operation),
}
impl ContextOperation {
impl TextThreadOperation {
pub fn from_proto(op: proto::ContextOperation) -> Result<Self> {
match op.variant.context("invalid variant")? {
proto::context_operation::Variant::InsertMessage(insert) => {
@@ -212,7 +202,7 @@ impl ContextOperation {
version: language::proto::deserialize_version(&update.version),
}),
proto::context_operation::Variant::UpdateSummary(update) => Ok(Self::UpdateSummary {
summary: ContextSummaryContent {
summary: TextThreadSummaryContent {
text: update.summary,
done: update.done,
timestamp: language::proto::deserialize_timestamp(
@@ -453,7 +443,7 @@ impl ContextOperation {
}
#[derive(Debug, Clone)]
pub enum ContextEvent {
pub enum TextThreadEvent {
ShowAssistError(SharedString),
ShowPaymentRequiredError,
MessagesEdited,
@@ -476,24 +466,24 @@ pub enum ContextEvent {
SlashCommandOutputSectionAdded {
section: SlashCommandOutputSection<language::Anchor>,
},
Operation(ContextOperation),
Operation(TextThreadOperation),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ContextSummary {
pub enum TextThreadSummary {
Pending,
Content(ContextSummaryContent),
Content(TextThreadSummaryContent),
Error,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ContextSummaryContent {
pub struct TextThreadSummaryContent {
pub text: String,
pub done: bool,
pub timestamp: clock::Lamport,
}
impl ContextSummary {
impl TextThreadSummary {
pub const DEFAULT: &str = "New Text Thread";
pub fn or_default(&self) -> SharedString {
@@ -505,48 +495,48 @@ impl ContextSummary {
.map_or_else(|| message.into(), |content| content.text.clone().into())
}
pub fn content(&self) -> Option<&ContextSummaryContent> {
pub fn content(&self) -> Option<&TextThreadSummaryContent> {
match self {
ContextSummary::Content(content) => Some(content),
ContextSummary::Pending | ContextSummary::Error => None,
TextThreadSummary::Content(content) => Some(content),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
}
}
fn content_as_mut(&mut self) -> Option<&mut ContextSummaryContent> {
fn content_as_mut(&mut self) -> Option<&mut TextThreadSummaryContent> {
match self {
ContextSummary::Content(content) => Some(content),
ContextSummary::Pending | ContextSummary::Error => None,
TextThreadSummary::Content(content) => Some(content),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
}
}
fn content_or_set_empty(&mut self) -> &mut ContextSummaryContent {
fn content_or_set_empty(&mut self) -> &mut TextThreadSummaryContent {
match self {
ContextSummary::Content(content) => content,
ContextSummary::Pending | ContextSummary::Error => {
let content = ContextSummaryContent {
TextThreadSummary::Content(content) => content,
TextThreadSummary::Pending | TextThreadSummary::Error => {
let content = TextThreadSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
};
*self = ContextSummary::Content(content);
*self = TextThreadSummary::Content(content);
self.content_as_mut().unwrap()
}
}
}
pub fn is_pending(&self) -> bool {
matches!(self, ContextSummary::Pending)
matches!(self, TextThreadSummary::Pending)
}
fn timestamp(&self) -> Option<clock::Lamport> {
match self {
ContextSummary::Content(content) => Some(content.timestamp),
ContextSummary::Pending | ContextSummary::Error => None,
TextThreadSummary::Content(content) => Some(content.timestamp),
TextThreadSummary::Pending | TextThreadSummary::Error => None,
}
}
}
impl PartialOrd for ContextSummary {
impl PartialOrd for TextThreadSummary {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.timestamp().partial_cmp(&other.timestamp())
}
@@ -668,27 +658,27 @@ struct PendingCompletion {
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct InvokedSlashCommandId(clock::Lamport);
pub struct AssistantContext {
id: ContextId,
pub struct TextThread {
id: TextThreadId,
timestamp: clock::Lamport,
version: clock::Global,
pending_ops: Vec<ContextOperation>,
operations: Vec<ContextOperation>,
pub(crate) pending_ops: Vec<TextThreadOperation>,
operations: Vec<TextThreadOperation>,
buffer: Entity<Buffer>,
parsed_slash_commands: Vec<ParsedSlashCommand>,
pub(crate) parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription,
slash_commands: Arc<SlashCommandWorkingSet>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pub(crate) slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
thought_process_output_sections: Vec<ThoughtProcessOutputSection<language::Anchor>>,
message_anchors: Vec<MessageAnchor>,
pub(crate) message_anchors: Vec<MessageAnchor>,
contents: Vec<Content>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: ContextSummary,
pub(crate) messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: TextThreadSummary,
summary_task: Task<Option<()>>,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
token_count: Option<u64>,
pub(crate) token_count: Option<u64>,
pending_token_count: Task<Option<()>>,
pending_save: Task<Result<()>>,
pending_cache_warming_task: Task<Option<()>>,
@@ -711,9 +701,9 @@ impl ContextAnnotation for ParsedSlashCommand {
}
}
impl EventEmitter<ContextEvent> for AssistantContext {}
impl EventEmitter<TextThreadEvent> for TextThread {}
impl AssistantContext {
impl TextThread {
pub fn local(
language_registry: Arc<LanguageRegistry>,
project: Option<Entity<Project>>,
@@ -723,7 +713,7 @@ impl AssistantContext {
cx: &mut Context<Self>,
) -> Self {
Self::new(
ContextId::new(),
TextThreadId::new(),
ReplicaId::default(),
language::Capability::ReadWrite,
language_registry,
@@ -744,7 +734,7 @@ impl AssistantContext {
}
pub fn new(
id: ContextId,
id: TextThreadId,
replica_id: ReplicaId,
capability: language::Capability,
language_registry: Arc<LanguageRegistry>,
@@ -780,7 +770,7 @@ impl AssistantContext {
slash_command_output_sections: Vec::new(),
thought_process_output_sections: Vec::new(),
edits_since_last_parse: edits_since_last_slash_command_parse,
summary: ContextSummary::Pending,
summary: TextThreadSummary::Pending,
summary_task: Task::ready(None),
completion_count: Default::default(),
pending_completions: Default::default(),
@@ -823,12 +813,12 @@ impl AssistantContext {
this
}
pub(crate) fn serialize(&self, cx: &App) -> SavedContext {
pub(crate) fn serialize(&self, cx: &App) -> SavedTextThread {
let buffer = self.buffer.read(cx);
SavedContext {
SavedTextThread {
id: Some(self.id.clone()),
zed: "context".into(),
version: SavedContext::VERSION.into(),
version: SavedTextThread::VERSION.into(),
text: buffer.text(),
messages: self
.messages(cx)
@@ -876,7 +866,7 @@ impl AssistantContext {
}
pub fn deserialize(
saved_context: SavedContext,
saved_context: SavedTextThread,
path: Arc<Path>,
language_registry: Arc<LanguageRegistry>,
prompt_builder: Arc<PromptBuilder>,
@@ -885,7 +875,7 @@ impl AssistantContext {
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
) -> Self {
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let id = saved_context.id.clone().unwrap_or_else(TextThreadId::new);
let mut this = Self::new(
id,
ReplicaId::default(),
@@ -906,7 +896,7 @@ impl AssistantContext {
this
}
pub fn id(&self) -> &ContextId {
pub fn id(&self) -> &TextThreadId {
&self.id
}
@@ -914,9 +904,9 @@ impl AssistantContext {
self.timestamp.replica_id
}
pub fn version(&self, cx: &App) -> ContextVersion {
ContextVersion {
context: self.version.clone(),
pub fn version(&self, cx: &App) -> TextThreadVersion {
TextThreadVersion {
text_thread: self.version.clone(),
buffer: self.buffer.read(cx).version(),
}
}
@@ -938,7 +928,7 @@ impl AssistantContext {
pub fn serialize_ops(
&self,
since: &ContextVersion,
since: &TextThreadVersion,
cx: &App,
) -> Task<Vec<proto::ContextOperation>> {
let buffer_ops = self
@@ -949,7 +939,7 @@ impl AssistantContext {
let mut context_ops = self
.operations
.iter()
.filter(|op| !since.context.observed(op.timestamp()))
.filter(|op| !since.text_thread.observed(op.timestamp()))
.cloned()
.collect::<Vec<_>>();
context_ops.extend(self.pending_ops.iter().cloned());
@@ -973,13 +963,13 @@ impl AssistantContext {
pub fn apply_ops(
&mut self,
ops: impl IntoIterator<Item = ContextOperation>,
ops: impl IntoIterator<Item = TextThreadOperation>,
cx: &mut Context<Self>,
) {
let mut buffer_ops = Vec::new();
for op in ops {
match op {
ContextOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
TextThreadOperation::BufferOperation(buffer_op) => buffer_ops.push(buffer_op),
op @ _ => self.pending_ops.push(op),
}
}
@@ -988,7 +978,7 @@ impl AssistantContext {
self.flush_ops(cx);
}
fn flush_ops(&mut self, cx: &mut Context<AssistantContext>) {
fn flush_ops(&mut self, cx: &mut Context<TextThread>) {
let mut changed_messages = HashSet::default();
let mut summary_generated = false;
@@ -1001,7 +991,7 @@ impl AssistantContext {
let timestamp = op.timestamp();
match op.clone() {
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor, metadata, ..
} => {
if self.messages_metadata.contains_key(&anchor.id) {
@@ -1011,7 +1001,7 @@ impl AssistantContext {
self.insert_message(anchor, metadata, cx);
}
}
ContextOperation::UpdateMessage {
TextThreadOperation::UpdateMessage {
message_id,
metadata: new_metadata,
..
@@ -1022,7 +1012,7 @@ impl AssistantContext {
changed_messages.insert(message_id);
}
}
ContextOperation::UpdateSummary {
TextThreadOperation::UpdateSummary {
summary: new_summary,
..
} => {
@@ -1031,11 +1021,11 @@ impl AssistantContext {
.timestamp()
.is_none_or(|current_timestamp| new_summary.timestamp > current_timestamp)
{
self.summary = ContextSummary::Content(new_summary);
self.summary = TextThreadSummary::Content(new_summary);
summary_generated = true;
}
}
ContextOperation::SlashCommandStarted {
TextThreadOperation::SlashCommandStarted {
id,
output_range,
name,
@@ -1052,9 +1042,9 @@ impl AssistantContext {
timestamp: id.0,
},
);
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
}
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
let buffer = self.buffer.read(cx);
if let Err(ix) = self
.slash_command_output_sections
@@ -1062,10 +1052,10 @@ impl AssistantContext {
{
self.slash_command_output_sections
.insert(ix, section.clone());
cx.emit(ContextEvent::SlashCommandOutputSectionAdded { section });
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded { section });
}
}
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
let buffer = self.buffer.read(cx);
if let Err(ix) = self
.thought_process_output_sections
@@ -1075,7 +1065,7 @@ impl AssistantContext {
.insert(ix, section.clone());
}
}
ContextOperation::SlashCommandFinished {
TextThreadOperation::SlashCommandFinished {
id,
error_message,
timestamp,
@@ -1094,10 +1084,10 @@ impl AssistantContext {
slash_command.status = InvokedSlashCommandStatus::Finished;
}
}
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id: id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id: id });
}
}
ContextOperation::BufferOperation(_) => unreachable!(),
TextThreadOperation::BufferOperation(_) => unreachable!(),
}
self.version.observe(timestamp);
@@ -1107,43 +1097,43 @@ impl AssistantContext {
if !changed_messages.is_empty() {
self.message_roles_updated(changed_messages, cx);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
cx.notify();
}
if summary_generated {
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
cx.notify();
}
}
fn can_apply_op(&self, op: &ContextOperation, cx: &App) -> bool {
fn can_apply_op(&self, op: &TextThreadOperation, cx: &App) -> bool {
if !self.version.observed_all(op.version()) {
return false;
}
match op {
ContextOperation::InsertMessage { anchor, .. } => self
TextThreadOperation::InsertMessage { anchor, .. } => self
.buffer
.read(cx)
.version
.observed(anchor.start.timestamp),
ContextOperation::UpdateMessage { message_id, .. } => {
TextThreadOperation::UpdateMessage { message_id, .. } => {
self.messages_metadata.contains_key(message_id)
}
ContextOperation::UpdateSummary { .. } => true,
ContextOperation::SlashCommandStarted { output_range, .. } => {
TextThreadOperation::UpdateSummary { .. } => true,
TextThreadOperation::SlashCommandStarted { output_range, .. } => {
self.has_received_operations_for_anchor_range(output_range.clone(), cx)
}
ContextOperation::SlashCommandOutputSectionAdded { section, .. } => {
TextThreadOperation::SlashCommandOutputSectionAdded { section, .. } => {
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
}
ContextOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
TextThreadOperation::ThoughtProcessOutputSectionAdded { section, .. } => {
self.has_received_operations_for_anchor_range(section.range.clone(), cx)
}
ContextOperation::SlashCommandFinished { .. } => true,
ContextOperation::BufferOperation(_) => {
TextThreadOperation::SlashCommandFinished { .. } => true,
TextThreadOperation::BufferOperation(_) => {
panic!("buffer operations should always be applied")
}
}
@@ -1164,9 +1154,9 @@ impl AssistantContext {
observed_start && observed_end
}
fn push_op(&mut self, op: ContextOperation, cx: &mut Context<Self>) {
fn push_op(&mut self, op: TextThreadOperation, cx: &mut Context<Self>) {
self.operations.push(op.clone());
cx.emit(ContextEvent::Operation(op));
cx.emit(TextThreadEvent::Operation(op));
}
pub fn buffer(&self) -> &Entity<Buffer> {
@@ -1189,7 +1179,7 @@ impl AssistantContext {
self.path.as_ref()
}
pub fn summary(&self) -> &ContextSummary {
pub fn summary(&self) -> &TextThreadSummary {
&self.summary
}
@@ -1250,13 +1240,13 @@ impl AssistantContext {
language::BufferEvent::Operation {
operation,
is_local: true,
} => cx.emit(ContextEvent::Operation(ContextOperation::BufferOperation(
operation.clone(),
))),
} => cx.emit(TextThreadEvent::Operation(
TextThreadOperation::BufferOperation(operation.clone()),
)),
language::BufferEvent::Edited => {
self.count_remaining_tokens(cx);
self.reparse(cx);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
}
_ => {}
}
@@ -1522,7 +1512,7 @@ impl AssistantContext {
if !updated_parsed_slash_commands.is_empty()
|| !removed_parsed_slash_command_ranges.is_empty()
{
cx.emit(ContextEvent::ParsedSlashCommandsUpdated {
cx.emit(TextThreadEvent::ParsedSlashCommandsUpdated {
removed: removed_parsed_slash_command_ranges,
updated: updated_parsed_slash_commands,
});
@@ -1596,7 +1586,7 @@ impl AssistantContext {
&& (!command.range.start.is_valid(buffer) || !command.range.end.is_valid(buffer))
{
command.status = InvokedSlashCommandStatus::Finished;
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
invalidated_command_ids.push(command_id);
}
}
@@ -1605,7 +1595,7 @@ impl AssistantContext {
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
ContextOperation::SlashCommandFinished {
TextThreadOperation::SlashCommandFinished {
id: command_id,
timestamp,
error_message: None,
@@ -1910,9 +1900,9 @@ impl AssistantContext {
}
}
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
this.push_op(
ContextOperation::SlashCommandFinished {
TextThreadOperation::SlashCommandFinished {
id: command_id,
timestamp,
error_message,
@@ -1935,9 +1925,9 @@ impl AssistantContext {
timestamp: command_id.0,
},
);
cx.emit(ContextEvent::InvokedSlashCommandChanged { command_id });
cx.emit(TextThreadEvent::InvokedSlashCommandChanged { command_id });
self.push_op(
ContextOperation::SlashCommandStarted {
TextThreadOperation::SlashCommandStarted {
id: command_id,
output_range: command_range,
name: name.to_string(),
@@ -1961,13 +1951,13 @@ impl AssistantContext {
};
self.slash_command_output_sections
.insert(insertion_ix, section.clone());
cx.emit(ContextEvent::SlashCommandOutputSectionAdded {
cx.emit(TextThreadEvent::SlashCommandOutputSectionAdded {
section: section.clone(),
});
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
ContextOperation::SlashCommandOutputSectionAdded {
TextThreadOperation::SlashCommandOutputSectionAdded {
timestamp,
section,
version,
@@ -1996,7 +1986,7 @@ impl AssistantContext {
let version = self.version.clone();
let timestamp = self.next_timestamp();
self.push_op(
ContextOperation::ThoughtProcessOutputSectionAdded {
TextThreadOperation::ThoughtProcessOutputSectionAdded {
timestamp,
section,
version,
@@ -2115,7 +2105,7 @@ impl AssistantContext {
let end = buffer
.anchor_before(message_old_end_offset + chunk_len);
context_event = Some(
ContextEvent::StartedThoughtProcess(start..end),
TextThreadEvent::StartedThoughtProcess(start..end),
);
} else {
// This ensures that all the thinking chunks are inserted inside the thinking tag
@@ -2133,7 +2123,7 @@ impl AssistantContext {
if let Some(start) = thought_process_stack.pop() {
let end = buffer.anchor_before(message_old_end_offset);
context_event =
Some(ContextEvent::EndedThoughtProcess(end));
Some(TextThreadEvent::EndedThoughtProcess(end));
thought_process_output_section =
Some(ThoughtProcessOutputSection {
range: start..end,
@@ -2163,7 +2153,7 @@ impl AssistantContext {
cx.emit(context_event);
}
cx.emit(ContextEvent::StreamedCompletion);
cx.emit(TextThreadEvent::StreamedCompletion);
Some(())
})?;
@@ -2184,7 +2174,7 @@ impl AssistantContext {
this.update(cx, |this, cx| {
let error_message = if let Some(error) = result.as_ref().err() {
if error.is::<PaymentRequiredError>() {
cx.emit(ContextEvent::ShowPaymentRequiredError);
cx.emit(TextThreadEvent::ShowPaymentRequiredError);
this.update_metadata(assistant_message_id, cx, |metadata| {
metadata.status = MessageStatus::Canceled;
});
@@ -2195,7 +2185,7 @@ impl AssistantContext {
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(ContextEvent::ShowAssistError(SharedString::from(
cx.emit(TextThreadEvent::ShowAssistError(SharedString::from(
error_message.clone(),
)));
this.update_metadata(assistant_message_id, cx, |metadata| {
@@ -2412,13 +2402,13 @@ impl AssistantContext {
if let Some(metadata) = self.messages_metadata.get_mut(&id) {
f(metadata);
metadata.timestamp = timestamp;
let operation = ContextOperation::UpdateMessage {
let operation = TextThreadOperation::UpdateMessage {
message_id: id,
metadata: metadata.clone(),
version,
};
self.push_op(operation, cx);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
cx.notify();
}
}
@@ -2482,7 +2472,7 @@ impl AssistantContext {
};
self.insert_message(anchor.clone(), metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor: anchor.clone(),
metadata,
version,
@@ -2505,7 +2495,7 @@ impl AssistantContext {
Err(ix) => ix,
};
self.contents.insert(insertion_ix, content);
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
}
pub fn contents<'a>(&'a self, cx: &'a App) -> impl 'a + Iterator<Item = Content> {
@@ -2580,7 +2570,7 @@ impl AssistantContext {
};
self.insert_message(suffix.clone(), suffix_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor: suffix.clone(),
metadata: suffix_metadata,
version,
@@ -2630,7 +2620,7 @@ impl AssistantContext {
};
self.insert_message(selection.clone(), selection_metadata.clone(), cx);
self.push_op(
ContextOperation::InsertMessage {
TextThreadOperation::InsertMessage {
anchor: selection.clone(),
metadata: selection_metadata,
version,
@@ -2642,7 +2632,7 @@ impl AssistantContext {
};
if !edited_buffer {
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
}
new_messages
} else {
@@ -2656,7 +2646,7 @@ impl AssistantContext {
new_metadata: MessageMetadata,
cx: &mut Context<Self>,
) {
cx.emit(ContextEvent::MessagesEdited);
cx.emit(TextThreadEvent::MessagesEdited);
self.messages_metadata.insert(new_anchor.id, new_metadata);
@@ -2692,15 +2682,15 @@ impl AssistantContext {
// If there is no summary, it is set with `done: false` so that "Loading Summary…" can
// be displayed.
match self.summary {
ContextSummary::Pending | ContextSummary::Error => {
self.summary = ContextSummary::Content(ContextSummaryContent {
TextThreadSummary::Pending | TextThreadSummary::Error => {
self.summary = TextThreadSummary::Content(TextThreadSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
});
replace_old = true;
}
ContextSummary::Content(_) => {}
TextThreadSummary::Content(_) => {}
}
self.summary_task = cx.spawn(async move |this, cx| {
@@ -2722,13 +2712,13 @@ impl AssistantContext {
}
summary.text.extend(lines.next());
summary.timestamp = timestamp;
let operation = ContextOperation::UpdateSummary {
let operation = TextThreadOperation::UpdateSummary {
summary: summary.clone(),
version,
};
this.push_op(operation, cx);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
})?;
// Stop if the LLM generated multiple lines.
@@ -2752,13 +2742,13 @@ impl AssistantContext {
if let Some(summary) = this.summary.content_as_mut() {
summary.done = true;
summary.timestamp = timestamp;
let operation = ContextOperation::UpdateSummary {
let operation = TextThreadOperation::UpdateSummary {
summary: summary.clone(),
version,
};
this.push_op(operation, cx);
cx.emit(ContextEvent::SummaryChanged);
cx.emit(ContextEvent::SummaryGenerated);
cx.emit(TextThreadEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryGenerated);
}
})?;
@@ -2768,8 +2758,8 @@ impl AssistantContext {
if let Err(err) = result {
this.update(cx, |this, cx| {
this.summary = ContextSummary::Error;
cx.emit(ContextEvent::SummaryChanged);
this.summary = TextThreadSummary::Error;
cx.emit(TextThreadEvent::SummaryChanged);
})
.log_err();
log::error!("Error generating context summary: {}", err);
@@ -2875,7 +2865,7 @@ impl AssistantContext {
&mut self,
debounce: Option<Duration>,
fs: Arc<dyn Fs>,
cx: &mut Context<AssistantContext>,
cx: &mut Context<TextThread>,
) {
if self.replica_id() != ReplicaId::default() {
// Prevent saving a remote context for now.
@@ -2906,7 +2896,7 @@ impl AssistantContext {
let mut discriminant = 1;
let mut new_path;
loop {
new_path = contexts_dir().join(&format!(
new_path = text_threads_dir().join(&format!(
"{} - {}.zed.json",
summary.trim(),
discriminant
@@ -2918,7 +2908,7 @@ impl AssistantContext {
}
}
fs.create_dir(contexts_dir().as_ref()).await?;
fs.create_dir(text_threads_dir().as_ref()).await?;
// rename before write ensures that only one file exists
if let Some(old_path) = old_path.as_ref()
@@ -2940,7 +2930,7 @@ impl AssistantContext {
let new_path: Arc<Path> = new_path.clone().into();
move |this, cx| {
this.path = Some(new_path.clone());
cx.emit(ContextEvent::PathChanged { old_path, new_path });
cx.emit(TextThreadEvent::PathChanged { old_path, new_path });
}
})
.ok();
@@ -2959,7 +2949,7 @@ impl AssistantContext {
summary.timestamp = timestamp;
summary.done = true;
summary.text = custom_summary;
cx.emit(ContextEvent::SummaryChanged);
cx.emit(TextThreadEvent::SummaryChanged);
}
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
@@ -2979,23 +2969,23 @@ impl AssistantContext {
}
#[derive(Debug, Default)]
pub struct ContextVersion {
context: clock::Global,
pub struct TextThreadVersion {
text_thread: clock::Global,
buffer: clock::Global,
}
impl ContextVersion {
impl TextThreadVersion {
pub fn from_proto(proto: &proto::ContextVersion) -> Self {
Self {
context: language::proto::deserialize_version(&proto.context_version),
text_thread: language::proto::deserialize_version(&proto.context_version),
buffer: language::proto::deserialize_version(&proto.buffer_version),
}
}
pub fn to_proto(&self, context_id: ContextId) -> proto::ContextVersion {
pub fn to_proto(&self, context_id: TextThreadId) -> proto::ContextVersion {
proto::ContextVersion {
context_id: context_id.to_proto(),
context_version: language::proto::serialize_version(&self.context),
context_version: language::proto::serialize_version(&self.text_thread),
buffer_version: language::proto::serialize_version(&self.buffer),
}
}
@@ -3063,8 +3053,8 @@ pub struct SavedMessage {
}
#[derive(Serialize, Deserialize)]
pub struct SavedContext {
pub id: Option<ContextId>,
pub struct SavedTextThread {
pub id: Option<TextThreadId>,
pub zed: String,
pub version: String,
pub text: String,
@@ -3076,7 +3066,7 @@ pub struct SavedContext {
pub thought_process_output_sections: Vec<ThoughtProcessOutputSection<usize>>,
}
impl SavedContext {
impl SavedTextThread {
pub const VERSION: &'static str = "0.4.0";
pub fn from_json(json: &str) -> Result<Self> {
@@ -3086,9 +3076,9 @@ impl SavedContext {
.context("version not found")?
{
serde_json::Value::String(version) => match version.as_str() {
SavedContext::VERSION => {
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
}
SavedTextThread::VERSION => Ok(serde_json::from_value::<SavedTextThread>(
saved_context_json,
)?),
SavedContextV0_3_0::VERSION => {
let saved_context =
serde_json::from_value::<SavedContextV0_3_0>(saved_context_json)?;
@@ -3113,8 +3103,8 @@ impl SavedContext {
fn into_ops(
self,
buffer: &Entity<Buffer>,
cx: &mut Context<AssistantContext>,
) -> Vec<ContextOperation> {
cx: &mut Context<TextThread>,
) -> Vec<TextThreadOperation> {
let mut operations = Vec::new();
let mut version = clock::Global::new();
let mut next_timestamp = clock::Lamport::new(ReplicaId::default());
@@ -3124,7 +3114,7 @@ impl SavedContext {
if message.id == MessageId(clock::Lamport::MIN) {
first_message_metadata = Some(message.metadata);
} else {
operations.push(ContextOperation::InsertMessage {
operations.push(TextThreadOperation::InsertMessage {
anchor: MessageAnchor {
id: message.id,
start: buffer.read(cx).anchor_before(message.start),
@@ -3144,7 +3134,7 @@ impl SavedContext {
if let Some(metadata) = first_message_metadata {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::UpdateMessage {
operations.push(TextThreadOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::MIN),
metadata: MessageMetadata {
role: metadata.role,
@@ -3160,7 +3150,7 @@ impl SavedContext {
let buffer = buffer.read(cx);
for section in self.slash_command_output_sections {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::SlashCommandOutputSectionAdded {
operations.push(TextThreadOperation::SlashCommandOutputSectionAdded {
timestamp,
section: SlashCommandOutputSection {
range: buffer.anchor_after(section.range.start)
@@ -3177,7 +3167,7 @@ impl SavedContext {
for section in self.thought_process_output_sections {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::ThoughtProcessOutputSectionAdded {
operations.push(TextThreadOperation::ThoughtProcessOutputSectionAdded {
timestamp,
section: ThoughtProcessOutputSection {
range: buffer.anchor_after(section.range.start)
@@ -3190,8 +3180,8 @@ impl SavedContext {
}
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::UpdateSummary {
summary: ContextSummaryContent {
operations.push(TextThreadOperation::UpdateSummary {
summary: TextThreadSummaryContent {
text: self.summary,
done: true,
timestamp,
@@ -3221,7 +3211,7 @@ struct SavedMessageMetadataPreV0_4_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_3_0 {
id: Option<ContextId>,
id: Option<TextThreadId>,
zed: String,
version: String,
text: String,
@@ -3234,11 +3224,11 @@ struct SavedContextV0_3_0 {
impl SavedContextV0_3_0 {
const VERSION: &'static str = "0.3.0";
fn upgrade(self) -> SavedContext {
SavedContext {
fn upgrade(self) -> SavedTextThread {
SavedTextThread {
id: self.id,
zed: self.zed,
version: SavedContext::VERSION.into(),
version: SavedTextThread::VERSION.into(),
text: self.text,
messages: self
.messages
@@ -3270,7 +3260,7 @@ impl SavedContextV0_3_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_2_0 {
id: Option<ContextId>,
id: Option<TextThreadId>,
zed: String,
version: String,
text: String,
@@ -3282,7 +3272,7 @@ struct SavedContextV0_2_0 {
impl SavedContextV0_2_0 {
const VERSION: &'static str = "0.2.0";
fn upgrade(self) -> SavedContext {
fn upgrade(self) -> SavedTextThread {
SavedContextV0_3_0 {
id: self.id,
zed: self.zed,
@@ -3299,7 +3289,7 @@ impl SavedContextV0_2_0 {
#[derive(Serialize, Deserialize)]
struct SavedContextV0_1_0 {
id: Option<ContextId>,
id: Option<TextThreadId>,
zed: String,
version: String,
text: String,
@@ -3313,7 +3303,7 @@ struct SavedContextV0_1_0 {
impl SavedContextV0_1_0 {
const VERSION: &'static str = "0.1.0";
fn upgrade(self) -> SavedContext {
fn upgrade(self) -> SavedTextThread {
SavedContextV0_2_0 {
id: self.id,
zed: self.zed,
@@ -3328,7 +3318,7 @@ impl SavedContextV0_1_0 {
}
#[derive(Debug, Clone)]
pub struct SavedContextMetadata {
pub struct SavedTextThreadMetadata {
pub title: SharedString,
pub path: Arc<Path>,
pub mtime: chrono::DateTime<chrono::Local>,

View File

@@ -1,6 +1,6 @@
use crate::{
AssistantContext, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
SavedContextMetadata,
SavedTextThread, SavedTextThreadMetadata, TextThread, TextThreadEvent, TextThreadId,
TextThreadOperation, TextThreadVersion,
};
use anyhow::{Context as _, Result};
use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
@@ -11,9 +11,9 @@ use context_server::ContextServerId;
use fs::{Fs, RemoveOptions};
use futures::StreamExt;
use fuzzy::StringMatchCandidate;
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
use language::LanguageRegistry;
use paths::contexts_dir;
use paths::text_threads_dir;
use project::{
Project,
context_server_store::{ContextServerStatus, ContextServerStore},
@@ -27,24 +27,24 @@ use util::{ResultExt, TryFutureExt};
use zed_env_vars::ZED_STATELESS;
pub(crate) fn init(client: &AnyProtoClient) {
client.add_entity_message_handler(ContextStore::handle_advertise_contexts);
client.add_entity_request_handler(ContextStore::handle_open_context);
client.add_entity_request_handler(ContextStore::handle_create_context);
client.add_entity_message_handler(ContextStore::handle_update_context);
client.add_entity_request_handler(ContextStore::handle_synchronize_contexts);
client.add_entity_message_handler(TextThreadStore::handle_advertise_contexts);
client.add_entity_request_handler(TextThreadStore::handle_open_context);
client.add_entity_request_handler(TextThreadStore::handle_create_context);
client.add_entity_message_handler(TextThreadStore::handle_update_context);
client.add_entity_request_handler(TextThreadStore::handle_synchronize_contexts);
}
#[derive(Clone)]
pub struct RemoteContextMetadata {
pub id: ContextId,
pub struct RemoteTextThreadMetadata {
pub id: TextThreadId,
pub summary: Option<String>,
}
pub struct ContextStore {
contexts: Vec<ContextHandle>,
contexts_metadata: Vec<SavedContextMetadata>,
pub struct TextThreadStore {
text_threads: Vec<TextThreadHandle>,
text_threads_metadata: Vec<SavedTextThreadMetadata>,
context_server_slash_command_ids: HashMap<ContextServerId, Vec<SlashCommandId>>,
host_contexts: Vec<RemoteContextMetadata>,
host_text_threads: Vec<RemoteTextThreadMetadata>,
fs: Arc<dyn Fs>,
languages: Arc<LanguageRegistry>,
slash_commands: Arc<SlashCommandWorkingSet>,
@@ -58,34 +58,28 @@ pub struct ContextStore {
prompt_builder: Arc<PromptBuilder>,
}
pub enum ContextStoreEvent {
ContextCreated(ContextId),
enum TextThreadHandle {
Weak(WeakEntity<TextThread>),
Strong(Entity<TextThread>),
}
impl EventEmitter<ContextStoreEvent> for ContextStore {}
enum ContextHandle {
Weak(WeakEntity<AssistantContext>),
Strong(Entity<AssistantContext>),
}
impl ContextHandle {
fn upgrade(&self) -> Option<Entity<AssistantContext>> {
impl TextThreadHandle {
fn upgrade(&self) -> Option<Entity<TextThread>> {
match self {
ContextHandle::Weak(weak) => weak.upgrade(),
ContextHandle::Strong(strong) => Some(strong.clone()),
TextThreadHandle::Weak(weak) => weak.upgrade(),
TextThreadHandle::Strong(strong) => Some(strong.clone()),
}
}
fn downgrade(&self) -> WeakEntity<AssistantContext> {
fn downgrade(&self) -> WeakEntity<TextThread> {
match self {
ContextHandle::Weak(weak) => weak.clone(),
ContextHandle::Strong(strong) => strong.downgrade(),
TextThreadHandle::Weak(weak) => weak.clone(),
TextThreadHandle::Strong(strong) => strong.downgrade(),
}
}
}
impl ContextStore {
impl TextThreadStore {
pub fn new(
project: Entity<Project>,
prompt_builder: Arc<PromptBuilder>,
@@ -97,14 +91,14 @@ impl ContextStore {
let telemetry = project.read(cx).client().telemetry().clone();
cx.spawn(async move |cx| {
const CONTEXT_WATCH_DURATION: Duration = Duration::from_millis(100);
let (mut events, _) = fs.watch(contexts_dir(), CONTEXT_WATCH_DURATION).await;
let (mut events, _) = fs.watch(text_threads_dir(), CONTEXT_WATCH_DURATION).await;
let this = cx.new(|cx: &mut Context<Self>| {
let mut this = Self {
contexts: Vec::new(),
contexts_metadata: Vec::new(),
text_threads: Vec::new(),
text_threads_metadata: Vec::new(),
context_server_slash_command_ids: HashMap::default(),
host_contexts: Vec::new(),
host_text_threads: Vec::new(),
fs,
languages,
slash_commands,
@@ -142,10 +136,10 @@ impl ContextStore {
#[cfg(any(test, feature = "test-support"))]
pub fn fake(project: Entity<Project>, cx: &mut Context<Self>) -> Self {
Self {
contexts: Default::default(),
contexts_metadata: Default::default(),
text_threads: Default::default(),
text_threads_metadata: Default::default(),
context_server_slash_command_ids: Default::default(),
host_contexts: Default::default(),
host_text_threads: Default::default(),
fs: project.read(cx).fs().clone(),
languages: project.read(cx).languages().clone(),
slash_commands: Arc::default(),
@@ -166,13 +160,13 @@ impl ContextStore {
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
this.host_contexts = envelope
this.host_text_threads = envelope
.payload
.contexts
.into_iter()
.map(|context| RemoteContextMetadata {
id: ContextId::from_proto(context.context_id),
summary: context.summary,
.map(|text_thread| RemoteTextThreadMetadata {
id: TextThreadId::from_proto(text_thread.context_id),
summary: text_thread.summary,
})
.collect();
cx.notify();
@@ -184,25 +178,25 @@ impl ContextStore {
envelope: TypedEnvelope<proto::OpenContext>,
mut cx: AsyncApp,
) -> Result<proto::OpenContextResponse> {
let context_id = ContextId::from_proto(envelope.payload.context_id);
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
let operations = this.update(&mut cx, |this, cx| {
anyhow::ensure!(
!this.project.read(cx).is_via_collab(),
"only the host contexts can be opened"
);
let context = this
.loaded_context_for_id(&context_id, cx)
let text_thread = this
.loaded_text_thread_for_id(&context_id, cx)
.context("context not found")?;
anyhow::ensure!(
context.read(cx).replica_id() == ReplicaId::default(),
text_thread.read(cx).replica_id() == ReplicaId::default(),
"context must be opened via the host"
);
anyhow::Ok(
context
text_thread
.read(cx)
.serialize_ops(&ContextVersion::default(), cx),
.serialize_ops(&TextThreadVersion::default(), cx),
)
})??;
let operations = operations.await;
@@ -222,15 +216,14 @@ impl ContextStore {
"can only create contexts as the host"
);
let context = this.create(cx);
let context_id = context.read(cx).id().clone();
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
let text_thread = this.create(cx);
let context_id = text_thread.read(cx).id().clone();
anyhow::Ok((
context_id,
context
text_thread
.read(cx)
.serialize_ops(&ContextVersion::default(), cx),
.serialize_ops(&TextThreadVersion::default(), cx),
))
})??;
let operations = operations.await;
@@ -246,11 +239,11 @@ impl ContextStore {
mut cx: AsyncApp,
) -> Result<()> {
this.update(&mut cx, |this, cx| {
let context_id = ContextId::from_proto(envelope.payload.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
let operation_proto = envelope.payload.operation.context("invalid operation")?;
let operation = ContextOperation::from_proto(operation_proto)?;
context.update(cx, |context, cx| context.apply_ops([operation], cx));
let operation = TextThreadOperation::from_proto(operation_proto)?;
text_thread.update(cx, |text_thread, cx| text_thread.apply_ops([operation], cx));
}
Ok(())
})?
@@ -269,12 +262,12 @@ impl ContextStore {
let mut local_versions = Vec::new();
for remote_version_proto in envelope.payload.contexts {
let remote_version = ContextVersion::from_proto(&remote_version_proto);
let context_id = ContextId::from_proto(remote_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
let context = context.read(cx);
let operations = context.serialize_ops(&remote_version, cx);
local_versions.push(context.version(cx).to_proto(context_id.clone()));
let remote_version = TextThreadVersion::from_proto(&remote_version_proto);
let context_id = TextThreadId::from_proto(remote_version_proto.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&context_id, cx) {
let text_thread = text_thread.read(cx);
let operations = text_thread.serialize_ops(&remote_version, cx);
local_versions.push(text_thread.version(cx).to_proto(context_id.clone()));
let client = this.client.clone();
let project_id = envelope.payload.project_id;
cx.background_spawn(async move {
@@ -308,9 +301,9 @@ impl ContextStore {
}
if is_shared {
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Strong(strong_context);
self.text_threads.retain_mut(|text_thread| {
if let Some(strong_context) = text_thread.upgrade() {
*text_thread = TextThreadHandle::Strong(strong_context);
true
} else {
false
@@ -345,12 +338,12 @@ impl ContextStore {
self.synchronize_contexts(cx);
}
project::Event::DisconnectedFromHost => {
self.contexts.retain_mut(|context| {
if let Some(strong_context) = context.upgrade() {
*context = ContextHandle::Weak(context.downgrade());
strong_context.update(cx, |context, cx| {
if context.replica_id() != ReplicaId::default() {
context.set_capability(language::Capability::ReadOnly, cx);
self.text_threads.retain_mut(|text_thread| {
if let Some(strong_context) = text_thread.upgrade() {
*text_thread = TextThreadHandle::Weak(text_thread.downgrade());
strong_context.update(cx, |text_thread, cx| {
if text_thread.replica_id() != ReplicaId::default() {
text_thread.set_capability(language::Capability::ReadOnly, cx);
}
});
true
@@ -358,20 +351,24 @@ impl ContextStore {
false
}
});
self.host_contexts.clear();
self.host_text_threads.clear();
cx.notify();
}
_ => {}
}
}
pub fn unordered_contexts(&self) -> impl Iterator<Item = &SavedContextMetadata> {
self.contexts_metadata.iter()
pub fn unordered_text_threads(&self) -> impl Iterator<Item = &SavedTextThreadMetadata> {
self.text_threads_metadata.iter()
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
pub fn host_text_threads(&self) -> impl Iterator<Item = &RemoteTextThreadMetadata> {
self.host_text_threads.iter()
}
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<TextThread> {
let context = cx.new(|cx| {
AssistantContext::local(
TextThread::local(
self.languages.clone(),
Some(self.project.clone()),
Some(self.telemetry.clone()),
@@ -380,14 +377,11 @@ impl ContextStore {
cx,
)
});
self.register_context(&context, cx);
self.register_text_thread(&context, cx);
context
}
pub fn create_remote_context(
&mut self,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
@@ -403,10 +397,10 @@ impl ContextStore {
let request = self.client.request(proto::CreateContext { project_id });
cx.spawn(async move |this, cx| {
let response = request.await?;
let context_id = ContextId::from_proto(response.context_id);
let context_id = TextThreadId::from_proto(response.context_id);
let context_proto = response.context.context("invalid context")?;
let context = cx.new(|cx| {
AssistantContext::new(
let text_thread = cx.new(|cx| {
TextThread::new(
context_id.clone(),
replica_id,
capability,
@@ -423,29 +417,29 @@ impl ContextStore {
context_proto
.operations
.into_iter()
.map(ContextOperation::from_proto)
.map(TextThreadOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
if let Some(existing_context) = this.loaded_text_thread_for_id(&context_id, cx) {
existing_context
} else {
this.register_context(&context, cx);
this.register_text_thread(&text_thread, cx);
this.synchronize_contexts(cx);
context
text_thread
}
})
})
}
pub fn open_local_context(
pub fn open_local(
&mut self,
path: Arc<Path>,
cx: &Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
if let Some(existing_context) = self.loaded_context_for_path(&path, cx) {
) -> Task<Result<Entity<TextThread>>> {
if let Some(existing_context) = self.loaded_text_thread_for_path(&path, cx) {
return Task::ready(Ok(existing_context));
}
@@ -457,7 +451,7 @@ impl ContextStore {
let path = path.clone();
async move {
let saved_context = fs.load(&path).await?;
SavedContext::from_json(&saved_context)
SavedTextThread::from_json(&saved_context)
}
});
let prompt_builder = self.prompt_builder.clone();
@@ -466,7 +460,7 @@ impl ContextStore {
cx.spawn(async move |this, cx| {
let saved_context = load.await?;
let context = cx.new(|cx| {
AssistantContext::deserialize(
TextThread::deserialize(
saved_context,
path.clone(),
languages,
@@ -478,21 +472,17 @@ impl ContextStore {
)
})?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_path(&path, cx) {
if let Some(existing_context) = this.loaded_text_thread_for_path(&path, cx) {
existing_context
} else {
this.register_context(&context, cx);
this.register_text_thread(&context, cx);
context
}
})
})
}
pub fn delete_local_context(
&mut self,
path: Arc<Path>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
pub fn delete_local(&mut self, path: Arc<Path>, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(async move |this, cx| {
@@ -506,57 +496,57 @@ impl ContextStore {
.await?;
this.update(cx, |this, cx| {
this.contexts.retain(|context| {
context
this.text_threads.retain(|text_thread| {
text_thread
.upgrade()
.and_then(|context| context.read(cx).path())
.and_then(|text_thread| text_thread.read(cx).path())
!= Some(&path)
});
this.contexts_metadata
.retain(|context| context.path.as_ref() != path.as_ref());
this.text_threads_metadata
.retain(|text_thread| text_thread.path.as_ref() != path.as_ref());
})?;
Ok(())
})
}
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).path().map(Arc::as_ref) == Some(path) {
Some(context)
fn loaded_text_thread_for_path(&self, path: &Path, cx: &App) -> Option<Entity<TextThread>> {
self.text_threads.iter().find_map(|text_thread| {
let text_thread = text_thread.upgrade()?;
if text_thread.read(cx).path().map(Arc::as_ref) == Some(path) {
Some(text_thread)
} else {
None
}
})
}
pub fn loaded_context_for_id(
pub fn loaded_text_thread_for_id(
&self,
id: &ContextId,
id: &TextThreadId,
cx: &App,
) -> Option<Entity<AssistantContext>> {
self.contexts.iter().find_map(|context| {
let context = context.upgrade()?;
if context.read(cx).id() == id {
Some(context)
) -> Option<Entity<TextThread>> {
self.text_threads.iter().find_map(|text_thread| {
let text_thread = text_thread.upgrade()?;
if text_thread.read(cx).id() == id {
Some(text_thread)
} else {
None
}
})
}
pub fn open_remote_context(
pub fn open_remote(
&mut self,
context_id: ContextId,
text_thread_id: TextThreadId,
cx: &mut Context<Self>,
) -> Task<Result<Entity<AssistantContext>>> {
) -> Task<Result<Entity<TextThread>>> {
let project = self.project.read(cx);
let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
};
if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
if let Some(context) = self.loaded_text_thread_for_id(&text_thread_id, cx) {
return Task::ready(Ok(context));
}
@@ -567,16 +557,16 @@ impl ContextStore {
let telemetry = self.telemetry.clone();
let request = self.client.request(proto::OpenContext {
project_id,
context_id: context_id.to_proto(),
context_id: text_thread_id.to_proto(),
});
let prompt_builder = self.prompt_builder.clone();
let slash_commands = self.slash_commands.clone();
cx.spawn(async move |this, cx| {
let response = request.await?;
let context_proto = response.context.context("invalid context")?;
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
let text_thread = cx.new(|cx| {
TextThread::new(
text_thread_id.clone(),
replica_id,
capability,
language_registry,
@@ -592,38 +582,40 @@ impl ContextStore {
context_proto
.operations
.into_iter()
.map(ContextOperation::from_proto)
.map(TextThreadOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
context.update(cx, |context, cx| context.apply_ops(operations, cx))?;
text_thread.update(cx, |context, cx| context.apply_ops(operations, cx))?;
this.update(cx, |this, cx| {
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
if let Some(existing_context) = this.loaded_text_thread_for_id(&text_thread_id, cx)
{
existing_context
} else {
this.register_context(&context, cx);
this.register_text_thread(&text_thread, cx);
this.synchronize_contexts(cx);
context
text_thread
}
})
})
}
fn register_context(&mut self, context: &Entity<AssistantContext>, cx: &mut Context<Self>) {
fn register_text_thread(&mut self, text_thread: &Entity<TextThread>, cx: &mut Context<Self>) {
let handle = if self.project_is_shared {
ContextHandle::Strong(context.clone())
TextThreadHandle::Strong(text_thread.clone())
} else {
ContextHandle::Weak(context.downgrade())
TextThreadHandle::Weak(text_thread.downgrade())
};
self.contexts.push(handle);
self.text_threads.push(handle);
self.advertise_contexts(cx);
cx.subscribe(context, Self::handle_context_event).detach();
cx.subscribe(text_thread, Self::handle_context_event)
.detach();
}
fn handle_context_event(
&mut self,
context: Entity<AssistantContext>,
event: &ContextEvent,
text_thread: Entity<TextThread>,
event: &TextThreadEvent,
cx: &mut Context<Self>,
) {
let Some(project_id) = self.project.read(cx).remote_id() else {
@@ -631,12 +623,12 @@ impl ContextStore {
};
match event {
ContextEvent::SummaryChanged => {
TextThreadEvent::SummaryChanged => {
self.advertise_contexts(cx);
}
ContextEvent::PathChanged { old_path, new_path } => {
TextThreadEvent::PathChanged { old_path, new_path } => {
if let Some(old_path) = old_path.as_ref() {
for metadata in &mut self.contexts_metadata {
for metadata in &mut self.text_threads_metadata {
if &metadata.path == old_path {
metadata.path = new_path.clone();
break;
@@ -644,8 +636,8 @@ impl ContextStore {
}
}
}
ContextEvent::Operation(operation) => {
let context_id = context.read(cx).id().to_proto();
TextThreadEvent::Operation(operation) => {
let context_id = text_thread.read(cx).id().to_proto();
let operation = operation.to_proto();
self.client
.send(proto::UpdateContext {
@@ -670,15 +662,15 @@ impl ContextStore {
}
let contexts = self
.contexts
.text_threads
.iter()
.rev()
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() == ReplicaId::default() {
.filter_map(|text_thread| {
let text_thread = text_thread.upgrade()?.read(cx);
if text_thread.replica_id() == ReplicaId::default() {
Some(proto::ContextMetadata {
context_id: context.id().to_proto(),
summary: context
context_id: text_thread.id().to_proto(),
summary: text_thread
.summary()
.content()
.map(|summary| summary.text.clone()),
@@ -701,13 +693,13 @@ impl ContextStore {
return;
};
let contexts = self
.contexts
let text_threads = self
.text_threads
.iter()
.filter_map(|context| {
let context = context.upgrade()?.read(cx);
if context.replica_id() != ReplicaId::default() {
Some(context.version(cx).to_proto(context.id().clone()))
.filter_map(|text_thread| {
let text_thread = text_thread.upgrade()?.read(cx);
if text_thread.replica_id() != ReplicaId::default() {
Some(text_thread.version(cx).to_proto(text_thread.id().clone()))
} else {
None
}
@@ -717,26 +709,27 @@ impl ContextStore {
let client = self.client.clone();
let request = self.client.request(proto::SynchronizeContexts {
project_id,
contexts,
contexts: text_threads,
});
cx.spawn(async move |this, cx| {
let response = request.await?;
let mut context_ids = Vec::new();
let mut text_thread_ids = Vec::new();
let mut operations = Vec::new();
this.read_with(cx, |this, cx| {
for context_version_proto in response.contexts {
let context_version = ContextVersion::from_proto(&context_version_proto);
let context_id = ContextId::from_proto(context_version_proto.context_id);
if let Some(context) = this.loaded_context_for_id(&context_id, cx) {
context_ids.push(context_id);
operations.push(context.read(cx).serialize_ops(&context_version, cx));
let text_thread_version = TextThreadVersion::from_proto(&context_version_proto);
let text_thread_id = TextThreadId::from_proto(context_version_proto.context_id);
if let Some(text_thread) = this.loaded_text_thread_for_id(&text_thread_id, cx) {
text_thread_ids.push(text_thread_id);
operations
.push(text_thread.read(cx).serialize_ops(&text_thread_version, cx));
}
}
})?;
let operations = futures::future::join_all(operations).await;
for (context_id, operations) in context_ids.into_iter().zip(operations) {
for (context_id, operations) in text_thread_ids.into_iter().zip(operations) {
for operation in operations {
client.send(proto::UpdateContext {
project_id,
@@ -751,8 +744,8 @@ impl ContextStore {
.detach_and_log_err(cx);
}
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedContextMetadata>> {
let metadata = self.contexts_metadata.clone();
pub fn search(&self, query: String, cx: &App) -> Task<Vec<SavedTextThreadMetadata>> {
let metadata = self.text_threads_metadata.clone();
let executor = cx.background_executor().clone();
cx.background_spawn(async move {
if query.is_empty() {
@@ -782,20 +775,16 @@ impl ContextStore {
})
}
pub fn host_contexts(&self) -> &[RemoteContextMetadata] {
&self.host_contexts
}
fn reload(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let fs = self.fs.clone();
cx.spawn(async move |this, cx| {
if *ZED_STATELESS {
return Ok(());
}
fs.create_dir(contexts_dir()).await?;
fs.create_dir(text_threads_dir()).await?;
let mut paths = fs.read_dir(contexts_dir()).await?;
let mut contexts = Vec::<SavedContextMetadata>::new();
let mut paths = fs.read_dir(text_threads_dir()).await?;
let mut contexts = Vec::<SavedTextThreadMetadata>::new();
while let Some(path) = paths.next().await {
let path = path?;
if path.extension() != Some(OsStr::new("json")) {
@@ -821,7 +810,7 @@ impl ContextStore {
.lines()
.next()
{
contexts.push(SavedContextMetadata {
contexts.push(SavedTextThreadMetadata {
title: title.to_string().into(),
path: path.into(),
mtime: metadata.mtime.timestamp_for_user().into(),
@@ -829,10 +818,10 @@ impl ContextStore {
}
}
}
contexts.sort_unstable_by_key(|context| Reverse(context.mtime));
contexts.sort_unstable_by_key(|text_thread| Reverse(text_thread.mtime));
this.update(cx, |this, cx| {
this.contexts_metadata = contexts;
this.text_threads_metadata = contexts;
cx.notify();
})
})

View File

@@ -433,7 +433,7 @@ where
/// Stores already emitted samples, once its full we call the callback.
buffer: [Sample; N],
/// Next free element in buffer. If this is equal to the buffer length
/// we have no more free lements.
/// we have no more free elements.
free: usize,
}

View File

@@ -119,21 +119,19 @@ impl Render for Breadcrumbs {
}
}
})
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
&focus_handle,
window,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
window,
cx,
)
}

View File

@@ -23,7 +23,7 @@ pub(super) struct Socks5Authorization<'a> {
/// Socks Proxy Protocol Version
///
/// V4 allows idenfication using a user_id
/// V4 allows identification using a user_id
/// V5 allows authorization using a username and password
pub(super) enum SocksVersion<'a> {
V4 {

View File

@@ -83,7 +83,7 @@ impl Global {
self.values.get(replica_id.0 as usize).copied().unwrap_or(0) as Seq
}
/// Observe the lamport timestampe.
/// Observe the lamport timestamp.
///
/// This sets the current sequence number of the observed replica ID to the maximum of this global's observed sequence and the observed timestamp.
pub fn observe(&mut self, timestamp: Lamport) {

View File

@@ -47,7 +47,9 @@ reqwest = { version = "0.11", features = ["json"] }
reqwest_client.workspace = true
rpc.workspace = true
scrypt = "0.11"
# sea-orm and sea-orm-macros versions must match exactly.
sea-orm = { version = "=1.1.10", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
sea-orm-macros = "=1.1.10"
semantic_version.workspace = true
semver.workspace = true
serde.workspace = true
@@ -71,7 +73,7 @@ uuid.workspace = true
[dev-dependencies]
agent_settings.workspace = true
assistant_context.workspace = true
assistant_text_thread.workspace = true
assistant_slash_command.workspace = true
async-trait.workspace = true
audio.workspace = true

View File

@@ -343,7 +343,6 @@ impl Server {
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferForSymbol>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferById>)
.add_request_handler(forward_read_only_project_request::<proto::SynchronizeBuffers>)
.add_request_handler(forward_read_only_project_request::<proto::InlayHints>)
.add_request_handler(forward_read_only_project_request::<proto::ResolveInlayHint>)
.add_request_handler(forward_read_only_project_request::<proto::GetColorPresentation>)
.add_request_handler(forward_read_only_project_request::<proto::OpenBufferByPath>)

View File

@@ -505,7 +505,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
label: "third_method(…)".into(),
detail: Some("fn(&mut self, B, C, D) -> E".into()),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
// no snippet placehodlers
// no snippet placeholders
new_text: "third_method".to_string(),
range: lsp::Range::new(
lsp::Position::new(1, 32),
@@ -1849,10 +1849,40 @@ async fn test_mutual_editor_inlay_hint_cache_update(
..lsp::ServerCapabilities::default()
};
client_a.language_registry().add(rust_lang());
// Set up the language server to return an additional inlay hint on each request.
let edits_made = Arc::new(AtomicUsize::new(0));
let closure_edits_made = Arc::clone(&edits_made);
let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: capabilities.clone(),
initializer: Some(Box::new(move |fake_language_server| {
let closure_edits_made = closure_edits_made.clone();
fake_language_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
move |params, _| {
let edits_made_2 = Arc::clone(&closure_edits_made);
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
);
let edits_made =
AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, edits_made as u32),
label: lsp::InlayHintLabel::String(edits_made.to_string()),
kind: None,
text_edits: None,
tooltip: None,
padding_left: None,
padding_right: None,
data: None,
}]))
}
},
);
})),
..FakeLspAdapter::default()
},
);
@@ -1894,61 +1924,20 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap();
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
executor.start_waiting();
// The host opens a rust file.
let _buffer_a = project_a
.update(cx_a, |project, cx| {
project.open_local_buffer(path!("/a/main.rs"), cx)
})
.await
.unwrap();
let editor_a = workspace_a
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let file_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
// Set up the language server to return an additional inlay hint on each request.
let edits_made = Arc::new(AtomicUsize::new(0));
let closure_edits_made = Arc::clone(&edits_made);
fake_language_server
.set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
let edits_made_2 = Arc::clone(&closure_edits_made);
async move {
assert_eq!(
params.text_document.uri,
lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
);
let edits_made = AtomicUsize::load(&edits_made_2, atomic::Ordering::Acquire);
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, edits_made as u32),
label: lsp::InlayHintLabel::String(edits_made.to_string()),
kind: None,
text_edits: None,
tooltip: None,
padding_left: None,
padding_right: None,
data: None,
}]))
}
})
.next()
.await
.unwrap();
let editor_a = file_a.await.unwrap().downcast::<Editor>().unwrap();
executor.run_until_parked();
let initial_edit = edits_made.load(atomic::Ordering::Acquire);
editor_a.update(cx_a, |editor, _| {
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
vec![initial_edit.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
"Host should get its first hints when opens an editor"
);
});
@@ -1963,10 +1952,10 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap();
executor.run_until_parked();
editor_b.update(cx_b, |editor, _| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![initial_edit.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
"Client should get its first hints when opens an editor"
);
});
@@ -1981,16 +1970,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
cx_b.focus(&editor_b);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
);
});
editor_b.update(cx_b, |editor, _| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![after_client_edit.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
);
});
@@ -2004,16 +1993,16 @@ async fn test_mutual_editor_inlay_hint_cache_update(
cx_a.focus(&editor_a);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
);
});
editor_b.update(cx_b, |editor, _| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![after_host_edit.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
);
});
@@ -2025,26 +2014,22 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.expect("inlay refresh request failed");
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
editor_a.update(cx_a, |editor, cx| {
assert_eq!(
vec![after_special_edit_for_refresh.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
"Host should react to /refresh LSP request"
);
});
editor_b.update(cx_b, |editor, _| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec![after_special_edit_for_refresh.to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
"Guest should get a /refresh LSP request propagated by host"
);
});
}
// This test started hanging on seed 2 after the theme settings
// PR. The hypothesis is that it's been buggy for a while, but got lucky
// on seeds.
#[ignore]
#[gpui::test(iterations = 10)]
async fn test_inlay_hint_refresh_is_forwarded(
cx_a: &mut TestAppContext,
@@ -2206,18 +2191,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
executor.finish_waiting();
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
editor_a.update(cx_a, |editor, cx| {
assert!(
extract_hint_labels(editor).is_empty(),
extract_hint_labels(editor, cx).is_empty(),
"Host should get no hints due to them turned off"
);
});
executor.run_until_parked();
editor_b.update(cx_b, |editor, _| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec!["initial hint".to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
"Client should get its first hints when opens an editor"
);
});
@@ -2229,18 +2214,18 @@ async fn test_inlay_hint_refresh_is_forwarded(
.into_response()
.expect("inlay refresh request failed");
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
editor_a.update(cx_a, |editor, cx| {
assert!(
extract_hint_labels(editor).is_empty(),
extract_hint_labels(editor, cx).is_empty(),
"Host should get no hints due to them turned off, even after the /refresh"
);
});
executor.run_until_parked();
editor_b.update(cx_b, |editor, _| {
editor_b.update(cx_b, |editor, cx| {
assert_eq!(
vec!["other hint".to_string()],
extract_hint_labels(editor),
extract_hint_labels(editor, cx),
"Guest should get a /refresh LSP request propagated by host despite host hints are off"
);
});
@@ -4217,15 +4202,35 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_initial);
}
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
for hint in editor.inlay_hint_cache().hints() {
match hint.label {
project::InlayHintLabel::String(s) => labels.push(s),
_ => unreachable!(),
}
fn extract_hint_labels(editor: &Editor, cx: &mut App) -> Vec<String> {
let lsp_store = editor.project().unwrap().read(cx).lsp_store();
let mut all_cached_labels = Vec::new();
let mut all_fetched_hints = Vec::new();
for buffer in editor.buffer().read(cx).all_buffers() {
lsp_store.update(cx, |lsp_store, cx| {
let hints = &lsp_store.latest_lsp_data(&buffer, cx).inlay_hints();
all_cached_labels.extend(hints.all_cached_hints().into_iter().map(|hint| {
let mut label = hint.text().to_string();
if hint.padding_left {
label.insert(0, ' ');
}
if hint.padding_right {
label.push_str(" ");
}
label
}));
all_fetched_hints.extend(hints.all_fetched_hints());
});
}
labels
assert!(
all_fetched_hints.is_empty(),
"Did not expect background hints fetch tasks, but got {} of them",
all_fetched_hints.len()
);
all_cached_labels
}
#[track_caller]

View File

@@ -6,8 +6,8 @@ use crate::{
},
};
use anyhow::{Result, anyhow};
use assistant_context::ContextStore;
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_text_thread::TextThreadStore;
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
use call::{ActiveCall, ParticipantLocation, Room, room};
use client::{RECEIVE_TIMEOUT, User};
@@ -6877,9 +6877,9 @@ async fn test_context_collaboration_with_reconnect(
});
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context_store_a = cx_a
let text_thread_store_a = cx_a
.update(|cx| {
ContextStore::new(
TextThreadStore::new(
project_a.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
@@ -6888,9 +6888,9 @@ async fn test_context_collaboration_with_reconnect(
})
.await
.unwrap();
let context_store_b = cx_b
let text_thread_store_b = cx_b
.update(|cx| {
ContextStore::new(
TextThreadStore::new(
project_b.clone(),
prompt_builder.clone(),
Arc::new(SlashCommandWorkingSet::default()),
@@ -6901,60 +6901,60 @@ async fn test_context_collaboration_with_reconnect(
.unwrap();
// Client A creates a new chats.
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
let text_thread_a = text_thread_store_a.update(cx_a, |store, cx| store.create(cx));
executor.run_until_parked();
// Client B retrieves host's contexts and joins one.
let context_b = context_store_b
let text_thread_b = text_thread_store_b
.update(cx_b, |store, cx| {
let host_contexts = store.host_contexts().to_vec();
assert_eq!(host_contexts.len(), 1);
store.open_remote_context(host_contexts[0].id.clone(), cx)
let host_text_threads = store.host_text_threads().collect::<Vec<_>>();
assert_eq!(host_text_threads.len(), 1);
store.open_remote(host_text_threads[0].id.clone(), cx)
})
.await
.unwrap();
// Host and guest make changes
context_a.update(cx_a, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
text_thread_a.update(cx_a, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Host change\n")], None, cx)
})
});
context_b.update(cx_b, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
text_thread_b.update(cx_b, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Guest change\n")], None, cx)
})
});
executor.run_until_parked();
assert_eq!(
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
"Guest change\nHost change\n"
);
assert_eq!(
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
"Guest change\nHost change\n"
);
// Disconnect client A and make some changes while disconnected.
server.disconnect_client(client_a.peer_id().unwrap());
server.forbid_connections();
context_a.update(cx_a, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
text_thread_a.update(cx_a, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Host offline change\n")], None, cx)
})
});
context_b.update(cx_b, |context, cx| {
context.buffer().update(cx, |buffer, cx| {
text_thread_b.update(cx_b, |text_thread, cx| {
text_thread.buffer().update(cx, |buffer, cx| {
buffer.edit([(0..0, "Guest offline change\n")], None, cx)
})
});
executor.run_until_parked();
assert_eq!(
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
"Host offline change\nGuest change\nHost change\n"
);
assert_eq!(
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
"Guest offline change\nGuest change\nHost change\n"
);
@@ -6962,11 +6962,11 @@ async fn test_context_collaboration_with_reconnect(
server.allow_connections();
executor.advance_clock(RECEIVE_TIMEOUT);
assert_eq!(
context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
text_thread_a.read_with(cx_a, |text_thread, cx| text_thread.buffer().read(cx).text()),
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
);
assert_eq!(
context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
text_thread_b.read_with(cx_b, |text_thread, cx| text_thread.buffer().read(cx).text()),
"Guest offline change\nHost offline change\nGuest change\nHost change\n"
);
@@ -6974,8 +6974,8 @@ async fn test_context_collaboration_with_reconnect(
server.forbid_connections();
server.disconnect_client(client_a.peer_id().unwrap());
executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
context_b.read_with(cx_b, |context, cx| {
assert!(context.buffer().read(cx).read_only());
text_thread_b.read_with(cx_b, |text_thread, cx| {
assert!(text_thread.buffer().read(cx).read_only());
});
}

View File

@@ -358,7 +358,7 @@ impl TestServer {
settings::KeymapFile::load_asset_allow_partial_failure(os_keymap, cx).unwrap(),
);
language_model::LanguageModelRegistry::test(cx);
assistant_context::init(client.clone(), cx);
assistant_text_thread::init(client.clone(), cx);
agent_settings::init(cx);
});

View File

@@ -738,19 +738,17 @@ impl Render for NotificationToast {
.on_modifiers_changed(cx.listener(|_, _, _, cx| cx.notify()))
.child(
IconButton::new(close_id, close_icon)
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
if suppress {
Tooltip::for_action(
"Suppress.\nClose with click.",
&workspace::SuppressNotification,
window,
cx,
)
} else {
Tooltip::for_action(
"Close.\nSuppress with shift-click",
&menu::Cancel,
window,
cx,
)
}

View File

@@ -443,7 +443,7 @@ impl PickerDelegate for CommandPaletteDelegate {
&self,
ix: usize,
selected: bool,
window: &mut Window,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let matching_command = self.matches.get(ix)?;
@@ -462,10 +462,9 @@ impl PickerDelegate for CommandPaletteDelegate {
command.name.clone(),
matching_command.positions.clone(),
))
.children(KeyBinding::for_action_in(
.child(KeyBinding::for_action_in(
&*command.action,
&self.previous_focus_handle,
window,
cx,
)),
),

View File

@@ -9,6 +9,7 @@ license = "GPL-3.0-or-later"
bincode.workspace = true
cfg-if.workspace = true
crash-handler.workspace = true
extension_host.workspace = true
log.workspace = true
minidumper.workspace = true
paths.workspace = true

View File

@@ -33,17 +33,31 @@ const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
static PANIC_THREAD_ID: AtomicU32 = AtomicU32::new(0);
pub async fn init(crash_init: InitCrashHandler) {
if *RELEASE_CHANNEL == ReleaseChannel::Dev && env::var("ZED_GENERATE_MINIDUMPS").is_err() {
let old_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
unsafe { env::set_var("RUST_BACKTRACE", "1") };
old_hook(info);
// prevent the macOS crash dialog from popping up
std::process::exit(1);
}));
return;
} else {
panic::set_hook(Box::new(panic_hook));
let gen_var = match env::var("ZED_GENERATE_MINIDUMPS") {
Ok(v) => {
if v == "false" || v == "0" {
Some(false)
} else {
Some(true)
}
}
Err(_) => None,
};
match (gen_var, *RELEASE_CHANNEL) {
(Some(false), _) | (None, ReleaseChannel::Dev) => {
let old_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
unsafe { env::set_var("RUST_BACKTRACE", "1") };
old_hook(info);
// prevent the macOS crash dialog from popping up
std::process::exit(1);
}));
return;
}
(Some(true), _) | (None, _) => {
panic::set_hook(Box::new(panic_hook));
}
}
let exe = env::current_exe().expect("unable to find ourselves");
@@ -272,6 +286,11 @@ impl minidumper::ServerHandler for CrashServer {
}
pub fn panic_hook(info: &PanicHookInfo) {
// Don't handle a panic on threads that are not relevant to the main execution.
if extension_host::wasm_host::IS_WASM_THREAD.with(|v| v.load(Ordering::Acquire)) {
return;
}
let message = info
.payload()
.downcast_ref::<&str>()

View File

@@ -616,12 +616,11 @@ impl DebugPanel {
})
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Start Debug Session",
&crate::Start,
&focus_handle,
window,
cx,
)
}
@@ -694,12 +693,11 @@ impl DebugPanel {
))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Pause Program",
&Pause,
&focus_handle,
window,
cx,
)
}
@@ -719,12 +717,11 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Continue Program",
&Continue,
&focus_handle,
window,
cx,
)
}
@@ -744,12 +741,11 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Step Over",
&StepOver,
&focus_handle,
window,
cx,
)
}
@@ -770,12 +766,11 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Step In",
&StepInto,
&focus_handle,
window,
cx,
)
}
@@ -793,12 +788,11 @@ impl DebugPanel {
.disabled(thread_status != ThreadStatus::Stopped)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Step Out",
&StepOut,
&focus_handle,
window,
cx,
)
}
@@ -816,12 +810,11 @@ impl DebugPanel {
))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Rerun Session",
&RerunSession,
&focus_handle,
window,
cx,
)
}
@@ -861,12 +854,11 @@ impl DebugPanel {
} else {
"Terminate All Threads"
};
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
label,
&Stop,
&focus_handle,
window,
cx,
)
}
@@ -893,12 +885,11 @@ impl DebugPanel {
))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Detach",
&Detach,
&focus_handle,
window,
cx,
)
}

View File

@@ -96,7 +96,9 @@ impl NewProcessModal {
let debug_picker = cx.new(|cx| {
let delegate =
DebugDelegate::new(debug_panel.downgrade(), task_store.clone());
Picker::uniform_list(delegate, window, cx).modal(false)
Picker::list(delegate, window, cx)
.modal(false)
.list_measure_all()
});
let configure_mode = ConfigureMode::new(window, cx);
@@ -745,22 +747,15 @@ impl Render for NewProcessModal {
== 0;
let secondary_action = menu::SecondaryConfirm.boxed_clone();
container
.child(div().children(
KeyBinding::for_action(&*secondary_action, window, cx).map(
|keybind| {
Button::new("edit-attach-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(
secondary_action.boxed_clone(),
cx,
)
})
.disabled(disabled)
},
),
))
.child(div().child({
Button::new("edit-attach-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action(&*secondary_action, cx))
.on_click(move |_, window, cx| {
window.dispatch_action(secondary_action.boxed_clone(), cx)
})
.disabled(disabled)
}))
.child(
h_flex()
.child(div().child(self.adapter_drop_down_menu(window, cx))),
@@ -1053,7 +1048,7 @@ impl DebugDelegate {
Some(TaskSourceKind::Lsp { language_name, .. }) => {
Some(format!("LSP: {language_name}"))
}
Some(TaskSourceKind::Language { .. }) => None,
Some(TaskSourceKind::Language { name }) => Some(format!("Lang: {name}")),
_ => context.clone().and_then(|ctx| {
ctx.task_context
.task_variables
@@ -1447,56 +1442,48 @@ impl PickerDelegate for DebugDelegate {
.justify_between()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.children({
.child({
let action = menu::SecondaryConfirm.boxed_clone();
if self.matches.is_empty() {
Some(
Button::new("edit-debug-json", "Edit debug.json")
.label_size(LabelSize::Small)
.on_click(cx.listener(|_picker, _, window, cx| {
window.dispatch_action(
zed_actions::OpenProjectDebugTasks.boxed_clone(),
cx,
);
cx.emit(DismissEvent);
})),
)
Button::new("edit-debug-json", "Edit debug.json")
.label_size(LabelSize::Small)
.on_click(cx.listener(|_picker, _, window, cx| {
window.dispatch_action(
zed_actions::OpenProjectDebugTasks.boxed_clone(),
cx,
);
cx.emit(DismissEvent);
}))
} else {
KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("edit-debug-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
})
Button::new("edit-debug-task", "Edit in debug.json")
.label_size(LabelSize::Small)
.key_binding(KeyBinding::for_action(&*action, cx))
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
}
})
.map(|this| {
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
let action = picker::ConfirmInput { secondary: false }.boxed_clone();
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
this.child({
Button::new("launch-custom", "Launch Custom")
.key_binding(keybind)
.key_binding(KeyBinding::for_action(&*action, cx))
.on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx)
})
}))
})
} else {
this.children(KeyBinding::for_action(&menu::Confirm, window, cx).map(
|keybind| {
let is_recent_selected =
self.divider_index >= Some(self.selected_index);
let run_entry_label =
if is_recent_selected { "Rerun" } else { "Spawn" };
this.child({
let is_recent_selected = self.divider_index >= Some(self.selected_index);
let run_entry_label = if is_recent_selected { "Rerun" } else { "Spawn" };
Button::new("spawn", run_entry_label)
.key_binding(keybind)
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
})
},
))
Button::new("spawn", run_entry_label)
.key_binding(KeyBinding::for_action(&menu::Confirm, cx))
.on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx);
})
})
}
});
Some(footer.into_any_element())

View File

@@ -566,14 +566,13 @@ pub(crate) fn new_debugger_pane(
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
let zoomed_text =
if zoomed { "Minimize" } else { "Expand" };
Tooltip::for_action_in(
zoomed_text,
&ToggleExpandItem,
&focus_handle,
window,
cx,
)
}

View File

@@ -607,13 +607,12 @@ impl BreakpointList {
.when_some(toggle_label, |this, (label, meta)| {
this.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::with_meta_in(
label,
Some(&ToggleEnableBreakpoint),
meta,
&focus_handle,
window,
cx,
)
}
@@ -634,13 +633,12 @@ impl BreakpointList {
.when_some(remove_breakpoint_tooltip, |this, tooltip| {
this.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::with_meta_in(
"Remove Breakpoint",
Some(&UnsetBreakpoint),
tooltip,
&focus_handle,
window,
cx,
)
}
@@ -819,7 +817,7 @@ impl LineBreakpoint {
)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
if is_enabled {
"Disable Breakpoint"
@@ -828,7 +826,6 @@ impl LineBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -980,7 +977,7 @@ impl DataBreakpoint {
)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
if is_enabled {
"Disable Data Breakpoint"
@@ -989,7 +986,6 @@ impl DataBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -1085,7 +1081,7 @@ impl ExceptionBreakpoint {
)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
if is_enabled {
"Disable Exception Breakpoint"
@@ -1094,7 +1090,6 @@ impl ExceptionBreakpoint {
},
&ToggleEnableBreakpoint,
&focus_handle,
window,
cx,
)
}
@@ -1402,12 +1397,11 @@ impl RenderOnce for BreakpointOptionsStrip {
.disabled(!supports_logs)
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log))
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Log))
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Set Log Message",
None,
"Set log message to display (instead of stopping) when a breakpoint is hit.",
window,
cx,
)
}),
@@ -1438,12 +1432,11 @@ impl RenderOnce for BreakpointOptionsStrip {
.disabled(!supports_condition)
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition))
.on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition))
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Set Condition",
None,
"Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met.",
window,
cx,
)
}),
@@ -1474,12 +1467,11 @@ impl RenderOnce for BreakpointOptionsStrip {
.disabled(!supports_hit_condition)
.toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition))
.on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition))
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::with_meta(
"Set Hit Condition",
None,
"Set expression that controls how many hits of the breakpoint are ignored.",
window,
cx,
)
}),

View File

@@ -484,12 +484,11 @@ impl Render for Console {
.tooltip({
let query_focus_handle = query_focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Evaluate",
&Confirm,
&query_focus_handle,
window,
cx,
)
}

View File

@@ -872,8 +872,8 @@ impl StackFrameList {
"filter-by-visible-worktree-stack-frame-list",
IconName::ListFilter,
)
.tooltip(move |window, cx| {
Tooltip::for_action(tooltip_title, &ToggleUserFrames, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action(tooltip_title, &ToggleUserFrames, cx)
})
.toggle_state(self.list_filter == StackFrameFilter::OnlyUserFrames)
.icon_size(IconSize::Small)

View File

@@ -1306,14 +1306,8 @@ impl VariableList {
.ok();
}
})
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Remove Watch",
&RemoveWatch,
&focus_handle,
window,
cx,
)
.tooltip(move |_window, cx| {
Tooltip::for_action_in("Remove Watch", &RemoveWatch, &focus_handle, cx)
})
.icon_size(ui::IconSize::Indicator),
),

View File

@@ -1,9 +1,9 @@
use super::*;
use collections::{HashMap, HashSet};
use editor::{
DisplayPoint, EditorSettings,
DisplayPoint, EditorSettings, Inlay,
actions::{GoToDiagnostic, GoToPreviousDiagnostic, Hover, MoveToBeginning},
display_map::{DisplayRow, Inlay},
display_map::DisplayRow,
test::{
editor_content_with_blocks, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext,

View File

@@ -67,11 +67,10 @@ impl Render for DiagnosticIndicator {
Some(
Button::new("diagnostic_message", SharedString::new(message))
.label_size(LabelSize::Small)
.tooltip(|window, cx| {
.tooltip(|_window, cx| {
Tooltip::for_action(
"Next Diagnostic",
&editor::actions::GoToDiagnostic::default(),
window,
cx,
)
})
@@ -87,8 +86,8 @@ impl Render for DiagnosticIndicator {
.child(
ButtonLike::new("diagnostic-indicator")
.child(diagnostic_indicator)
.tooltip(|window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, window, cx)
.tooltip(move |_window, cx| {
Tooltip::for_action("Project Diagnostics", &Deploy, cx)
})
.on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() {

View File

@@ -203,6 +203,10 @@ fn template_big_table_of_actions(book: &mut Book) {
});
}
fn format_binding(binding: String) -> String {
binding.replace("\\", "\\\\")
}
fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
let regex = Regex::new(r"\{#kb (.*?)\}").unwrap();
@@ -223,7 +227,10 @@ fn template_and_validate_keybindings(book: &mut Book, errors: &mut HashSet<Prepr
return "<div>No default binding</div>".to_string();
}
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
let formatted_macos_binding = format_binding(macos_binding);
let formatted_linux_binding = format_binding(linux_binding);
format!("<kbd class=\"keybinding\">{formatted_macos_binding}|{formatted_linux_binding}</kbd>")
})
.into_owned()
});

View File

@@ -123,8 +123,8 @@ impl Render for EditPredictionButton {
});
}
}))
.tooltip(|window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
.tooltip(|_window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx)
}),
);
}
@@ -146,9 +146,7 @@ impl Render for EditPredictionButton {
.anchor(Corner::BottomRight)
.trigger_with_tooltip(
IconButton::new("copilot-icon", icon),
|window, cx| {
Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
},
|_window, cx| Tooltip::for_action("GitHub Copilot", &ToggleMenu, cx),
)
.with_handle(self.popover_menu_handle.clone()),
)
@@ -220,12 +218,7 @@ impl Render for EditPredictionButton {
IconButton::new("supermaven-icon", icon),
move |window, cx| {
if has_menu {
Tooltip::for_action(
tooltip_text.clone(),
&ToggleMenu,
window,
cx,
)
Tooltip::for_action(tooltip_text.clone(), &ToggleMenu, cx)
} else {
Tooltip::text(tooltip_text.clone())(window, cx)
}
@@ -288,9 +281,7 @@ impl Render for EditPredictionButton {
cx.theme().colors().status_bar_background,
))
}),
move |window, cx| {
Tooltip::for_action("Codestral", &ToggleMenu, window, cx)
},
move |_window, cx| Tooltip::for_action("Codestral", &ToggleMenu, cx),
)
.with_handle(self.popover_menu_handle.clone()),
)
@@ -317,14 +308,8 @@ impl Render for EditPredictionButton {
.shape(IconButtonShape::Square)
.indicator(Indicator::dot().color(Color::Muted))
.indicator_border_color(Some(cx.theme().colors().status_bar_background))
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Edit Predictions",
None,
tooltip_meta,
window,
cx,
)
.tooltip(move |_window, cx| {
Tooltip::with_meta("Edit Predictions", None, tooltip_meta, cx)
})
.on_click(cx.listener(move |_, _, window, cx| {
telemetry::event!(
@@ -365,16 +350,15 @@ impl Render for EditPredictionButton {
},
)
.when(!self.popover_menu_handle.is_deployed(), |element| {
element.tooltip(move |window, cx| {
element.tooltip(move |_window, cx| {
if enabled {
if show_editor_predictions {
Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
Tooltip::for_action("Edit Prediction", &ToggleMenu, cx)
} else {
Tooltip::with_meta(
"Edit Prediction",
Some(&ToggleMenu),
"Hidden For This File",
window,
cx,
)
}
@@ -383,7 +367,6 @@ impl Render for EditPredictionButton {
"Edit Prediction",
Some(&ToggleMenu),
"Disabled For This File",
window,
cx,
)
}

View File

@@ -854,7 +854,7 @@ mod tests {
}
#[gpui::test]
async fn test_declarations_limt(cx: &mut TestAppContext) {
async fn test_declarations_limit(cx: &mut TestAppContext) {
let (_, index, rust_lang_id) = init_test(cx).await;
let index_state = index.read_with(cx, |index, _cx| index.state().clone());

View File

@@ -64,6 +64,7 @@ project.workspace = true
rand.workspace = true
regex.workspace = true
rpc.workspace = true
rope.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -27,7 +27,7 @@ mod tab_map;
mod wrap_map;
use crate::{
EditorStyle, InlayId, RowExt, hover_links::InlayHighlight, movement::TextLayoutDetails,
EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
};
pub use block_map::{
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
@@ -42,7 +42,6 @@ pub use fold_map::{
ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
};
use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
pub use inlay_map::Inlay;
use inlay_map::InlaySnapshot;
pub use inlay_map::{InlayOffset, InlayPoint};
pub use invisibles::{is_invisible, replacement};
@@ -50,9 +49,10 @@ use language::{
OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
};
use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferPoint, MultiBufferRow,
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
RowInfo, ToOffset, ToPoint,
};
use project::InlayId;
use project::project_settings::DiagnosticSeverity;
use serde::Deserialize;
@@ -594,25 +594,6 @@ impl DisplayMap {
self.block_map.read(snapshot, edits);
}
pub fn remove_inlays_for_excerpts(
&mut self,
excerpts_removed: &[ExcerptId],
cx: &mut Context<Self>,
) {
let to_remove = self
.inlay_map
.current_inlays()
.filter_map(|inlay| {
if excerpts_removed.contains(&inlay.position.excerpt_id) {
Some(inlay.id)
} else {
None
}
})
.collect::<Vec<_>>();
self.splice_inlays(&to_remove, Vec::new(), cx);
}
fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {
let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
let language = buffer

View File

@@ -26,8 +26,8 @@ use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
use text::{BufferId, Edit};
use ui::ElementId;
const NEWLINES: &[u8; u128::BITS as usize] = &[b'\n'; _];
const BULLETS: &[u8; u128::BITS as usize] = &[b'*'; _];
const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
///
@@ -1186,18 +1186,14 @@ impl BlockMapWriter<'_> {
self.0.sync(wrap_snapshot, edits);
}
pub fn remove_intersecting_replace_blocks<T>(
pub fn remove_intersecting_replace_blocks(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
ranges: impl IntoIterator<Item = Range<usize>>,
inclusive: bool,
) where
T: ToOffset,
{
) {
let wrap_snapshot = self.0.wrap_snapshot.borrow();
let mut blocks_to_remove = HashSet::default();
for range in ranges {
let range = range.start.to_offset(wrap_snapshot.buffer_snapshot())
..range.end.to_offset(wrap_snapshot.buffer_snapshot());
for block in self.blocks_intersecting_buffer_range(range, inclusive) {
if matches!(block.placement, BlockPlacement::Replace(_)) {
blocks_to_remove.insert(block.id);
@@ -1783,11 +1779,11 @@ impl<'a> Iterator for BlockChunks<'a> {
if self.masked {
// Not great for multibyte text because to keep cursor math correct we
// need to have the same number of bytes in the input as output.
// need to have the same number of chars in the input as output.
let chars_count = prefix.chars().count();
let bullet_len = chars_count;
prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
chars = 1u128.unbounded_shl(bullet_len as u32) - 1;
chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
tabs = 0;
}
@@ -3570,8 +3566,12 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
writer.remove_intersecting_replace_blocks(
[buffer_snapshot.anchor_after(Point::new(1, 0))
..buffer_snapshot.anchor_after(Point::new(1, 0))],
[buffer_snapshot
.anchor_after(Point::new(1, 0))
.to_offset(&buffer_snapshot)
..buffer_snapshot
.anchor_after(Point::new(1, 0))
.to_offset(&buffer_snapshot)],
false,
);
let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());

View File

@@ -132,37 +132,31 @@ impl<'a> Iterator for CustomHighlightsChunks<'a> {
}
}
let chunk = self
.buffer_chunk
.get_or_insert_with(|| self.buffer_chunks.next().unwrap_or_default());
if chunk.text.is_empty() {
let chunk = match &mut self.buffer_chunk {
Some(it) => it,
slot => slot.insert(self.buffer_chunks.next()?),
};
while chunk.text.is_empty() {
*chunk = self.buffer_chunks.next()?;
}
let split_idx = chunk.text.len().min(next_highlight_endpoint - self.offset);
let (prefix, suffix) = chunk.text.split_at(split_idx);
let (chars, tabs) = if split_idx == 128 {
let output = (chunk.chars, chunk.tabs);
chunk.chars = 0;
chunk.tabs = 0;
output
} else {
let mask = (1 << split_idx) - 1;
let output = (chunk.chars & mask, chunk.tabs & mask);
chunk.chars = chunk.chars >> split_idx;
chunk.tabs = chunk.tabs >> split_idx;
output
};
chunk.text = suffix;
self.offset += prefix.len();
let mask = 1u128.unbounded_shl(split_idx as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
let mut prefix = Chunk {
text: prefix,
chars,
tabs,
..chunk.clone()
};
chunk.chars = chunk.chars.unbounded_shr(split_idx as u32);
chunk.tabs = chunk.tabs.unbounded_shr(split_idx as u32);
chunk.text = suffix;
if !self.active_highlights.is_empty() {
prefix.highlight_style = self
.active_highlights

View File

@@ -1,4 +1,4 @@
use crate::{InlayId, display_map::inlay_map::InlayChunk};
use crate::display_map::inlay_map::InlayChunk;
use super::{
Highlights,
@@ -9,6 +9,7 @@ use language::{Edit, HighlightId, Point, TextSummary};
use multi_buffer::{
Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
};
use project::InlayId;
use std::{
any::TypeId,
cmp::{self, Ordering},
@@ -1436,14 +1437,15 @@ impl<'a> Iterator for FoldChunks<'a> {
let transform_end = self.transform_cursor.end().1;
let chunk_end = buffer_chunk_end.min(transform_end);
chunk.text = &chunk.text
[(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
let bit_start = (self.inlay_offset - buffer_chunk_start).0;
let bit_end = (chunk_end - buffer_chunk_start).0;
chunk.text = &chunk.text[bit_start..bit_end];
let bit_end = (chunk_end - buffer_chunk_start).0;
let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
chunk.tabs = (chunk.tabs >> (self.inlay_offset - buffer_chunk_start).0) & mask;
chunk.chars = (chunk.chars >> (self.inlay_offset - buffer_chunk_start).0) & mask;
chunk.tabs = (chunk.tabs >> bit_start) & mask;
chunk.chars = (chunk.chars >> bit_start) & mask;
if chunk_end == transform_end {
self.transform_cursor.next();

View File

@@ -1,17 +1,18 @@
use crate::{ChunkRenderer, HighlightStyles, InlayId};
use collections::BTreeSet;
use gpui::{Hsla, Rgba};
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{
Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
use crate::{
ChunkRenderer, HighlightStyles,
inlays::{Inlay, InlayContent},
};
use collections::BTreeSet;
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset};
use project::InlayId;
use std::{
cmp,
ops::{Add, AddAssign, Range, Sub, SubAssign},
sync::{Arc, OnceLock},
sync::Arc,
};
use sum_tree::{Bias, Cursor, Dimensions, SumTree};
use text::{ChunkBitmaps, Patch, Rope};
use text::{ChunkBitmaps, Patch};
use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div};
use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId};
@@ -37,85 +38,6 @@ enum Transform {
Inlay(Inlay),
}
#[derive(Debug, Clone)]
pub struct Inlay {
pub id: InlayId,
pub position: Anchor,
pub content: InlayContent,
}
#[derive(Debug, Clone)]
pub enum InlayContent {
Text(text::Rope),
Color(Hsla),
}
impl Inlay {
pub fn hint(id: u32, position: Anchor, hint: &project::InlayHint) -> Self {
let mut text = hint.text();
if hint.padding_right && text.reversed_chars_at(text.len()).next() != Some(' ') {
text.push(" ");
}
if hint.padding_left && text.chars_at(0).next() != Some(' ') {
text.push_front(" ");
}
Self {
id: InlayId::Hint(id),
position,
content: InlayContent::Text(text),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn mock_hint(id: u32, position: Anchor, text: impl Into<Rope>) -> Self {
Self {
id: InlayId::Hint(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn color(id: u32, position: Anchor, color: Rgba) -> Self {
Self {
id: InlayId::Color(id),
position,
content: InlayContent::Color(color.into()),
}
}
pub fn edit_prediction<T: Into<Rope>>(id: u32, position: Anchor, text: T) -> Self {
Self {
id: InlayId::EditPrediction(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn debugger<T: Into<Rope>>(id: u32, position: Anchor, text: T) -> Self {
Self {
id: InlayId::DebuggerValue(id),
position,
content: InlayContent::Text(text.into()),
}
}
pub fn text(&self) -> &Rope {
static COLOR_TEXT: OnceLock<Rope> = OnceLock::new();
match &self.content {
InlayContent::Text(text) => text,
InlayContent::Color(_) => COLOR_TEXT.get_or_init(|| Rope::from("")),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn get_color(&self) -> Option<Hsla> {
match self.content {
InlayContent::Color(color) => Some(color),
_ => None,
}
}
}
impl sum_tree::Item for Transform {
type Summary = TransformSummary;
@@ -325,21 +247,16 @@ impl<'a> Iterator for InlayChunks<'a> {
};
let (prefix, suffix) = chunk.text.split_at(split_index);
let (chars, tabs) = if split_index == 128 {
let output = (chunk.chars, chunk.tabs);
chunk.chars = 0;
chunk.tabs = 0;
output
} else {
let mask = (1 << split_index) - 1;
let output = (chunk.chars & mask, chunk.tabs & mask);
chunk.chars = chunk.chars >> split_index;
chunk.tabs = chunk.tabs >> split_index;
output
};
chunk.text = suffix;
self.output_offset.0 += prefix.len();
let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
let chars = chunk.chars & mask;
let tabs = chunk.tabs & mask;
chunk.chars = chunk.chars.unbounded_shr(split_index as u32);
chunk.tabs = chunk.tabs.unbounded_shr(split_index as u32);
chunk.text = suffix;
InlayChunk {
chunk: Chunk {
text: prefix,
@@ -457,18 +374,12 @@ impl<'a> Iterator for InlayChunks<'a> {
let (chunk, remainder) = inlay_chunk.split_at(split_index);
*inlay_chunk = remainder;
let (chars, tabs) = if split_index == 128 {
let output = (*chars, *tabs);
*chars = 0;
*tabs = 0;
output
} else {
let mask = (1 << split_index as u32) - 1;
let output = (*chars & mask, *tabs & mask);
*chars = *chars >> split_index;
*tabs = *tabs >> split_index;
output
};
let mask = 1u128.unbounded_shl(split_index as u32).wrapping_sub(1);
let new_chars = *chars & mask;
let new_tabs = *tabs & mask;
*chars = chars.unbounded_shr(split_index as u32);
*tabs = tabs.unbounded_shr(split_index as u32);
if inlay_chunk.is_empty() {
self.inlay_chunk = None;
@@ -479,8 +390,8 @@ impl<'a> Iterator for InlayChunks<'a> {
InlayChunk {
chunk: Chunk {
text: chunk,
chars,
tabs,
chars: new_chars,
tabs: new_tabs,
highlight_style,
is_inlay: true,
..Chunk::default()
@@ -761,7 +672,7 @@ impl InlayMap {
#[cfg(test)]
pub(crate) fn randomly_mutate(
&mut self,
next_inlay_id: &mut u32,
next_inlay_id: &mut usize,
rng: &mut rand::rngs::StdRng,
) -> (InlaySnapshot, Vec<InlayEdit>) {
use rand::prelude::*;
@@ -1256,17 +1167,18 @@ const fn is_utf8_char_boundary(byte: u8) -> bool {
mod tests {
use super::*;
use crate::{
InlayId, MultiBuffer,
MultiBuffer,
display_map::{HighlightKey, InlayHighlights, TextHighlights},
hover_links::InlayHighlight,
};
use gpui::{App, HighlightStyle};
use multi_buffer::Anchor;
use project::{InlayHint, InlayHintLabel, ResolveState};
use rand::prelude::*;
use settings::SettingsStore;
use std::{any::TypeId, cmp::Reverse, env, sync::Arc};
use sum_tree::TreeMap;
use text::Patch;
use text::{Patch, Rope};
use util::RandomCharIter;
use util::post_inc;
@@ -1274,7 +1186,7 @@ mod tests {
fn test_inlay_properties_label_padding() {
assert_eq!(
Inlay::hint(
0,
InlayId::Hint(0),
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
@@ -1294,7 +1206,7 @@ mod tests {
assert_eq!(
Inlay::hint(
0,
InlayId::Hint(0),
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
@@ -1314,7 +1226,7 @@ mod tests {
assert_eq!(
Inlay::hint(
0,
InlayId::Hint(0),
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
@@ -1334,7 +1246,7 @@ mod tests {
assert_eq!(
Inlay::hint(
0,
InlayId::Hint(0),
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
@@ -1357,7 +1269,7 @@ mod tests {
fn test_inlay_hint_padding_with_multibyte_chars() {
assert_eq!(
Inlay::hint(
0,
InlayId::Hint(0),
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("🎨".to_string()),

View File

@@ -11,7 +11,7 @@ use sum_tree::Bias;
const MAX_EXPANSION_COLUMN: u32 = 256;
// Handles a tab width <= 128
const SPACES: &[u8; u128::BITS as usize] = &[b' '; _];
const SPACES: &[u8; rope::Chunk::MASK_BITS] = &[b' '; _];
const MAX_TABS: NonZeroU32 = NonZeroU32::new(SPACES.len() as u32).unwrap();
/// Keeps track of hard tabs in a text buffer.
@@ -569,56 +569,47 @@ impl<'a> Iterator for TabChunks<'a> {
//todo(improve performance by using tab cursor)
for (ix, c) in self.chunk.text.char_indices() {
match c {
'\t' if ix > 0 => {
let (prefix, suffix) = self.chunk.text.split_at(ix);
let mask = 1u128.unbounded_shl(ix as u32).wrapping_sub(1);
let chars = self.chunk.chars & mask;
let tabs = self.chunk.tabs & mask;
self.chunk.tabs = self.chunk.tabs.unbounded_shr(ix as u32);
self.chunk.chars = self.chunk.chars.unbounded_shr(ix as u32);
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
chars,
tabs,
..self.chunk.clone()
});
}
'\t' => {
if ix > 0 {
let (prefix, suffix) = self.chunk.text.split_at(ix);
let (chars, tabs) = if ix == 128 {
let output = (self.chunk.chars, self.chunk.tabs);
self.chunk.chars = 0;
self.chunk.tabs = 0;
output
} else {
let mask = (1 << ix) - 1;
let output = (self.chunk.chars & mask, self.chunk.tabs & mask);
self.chunk.chars = self.chunk.chars >> ix;
self.chunk.tabs = self.chunk.tabs >> ix;
output
};
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
chars,
tabs,
..self.chunk.clone()
});
self.chunk.text = &self.chunk.text[1..];
self.chunk.tabs >>= 1;
self.chunk.chars >>= 1;
let tab_size = if self.input_column < self.max_expansion_column {
self.tab_size.get()
} else {
self.chunk.text = &self.chunk.text[1..];
self.chunk.tabs >>= 1;
self.chunk.chars >>= 1;
let tab_size = if self.input_column < self.max_expansion_column {
self.tab_size.get()
} else {
1
};
let mut len = tab_size - self.column % tab_size;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
len = next_output_position.column - self.output_position.column;
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
is_tab: true,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});
}
1
};
let mut len = tab_size - self.column % tab_size;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
len = next_output_position.column - self.output_position.column;
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
is_tab: true,
chars: 1u128.unbounded_shl(len) - 1,
tabs: 0,
..self.chunk.clone()
});
}
'\n' => {
self.column = 0;

View File

@@ -972,18 +972,11 @@ impl<'a> Iterator for WrapChunks<'a> {
let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
let (chars, tabs) = if input_len == 128 {
let output = (self.input_chunk.chars, self.input_chunk.tabs);
self.input_chunk.chars = 0;
self.input_chunk.tabs = 0;
output
} else {
let mask = (1 << input_len) - 1;
let output = (self.input_chunk.chars & mask, self.input_chunk.tabs & mask);
self.input_chunk.chars = self.input_chunk.chars >> input_len;
self.input_chunk.tabs = self.input_chunk.tabs >> input_len;
output
};
let mask = 1u128.unbounded_shl(input_len as u32).wrapping_sub(1);
let chars = self.input_chunk.chars & mask;
let tabs = self.input_chunk.tabs & mask;
self.input_chunk.tabs = self.input_chunk.tabs.unbounded_shr(input_len as u32);
self.input_chunk.chars = self.input_chunk.chars.unbounded_shr(input_len as u32);
self.input_chunk.text = suffix;
Some(Chunk {

View File

@@ -7,7 +7,6 @@
//! * [`element`] — the place where all rendering happens
//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
//!
//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
//!
@@ -24,7 +23,7 @@ mod highlight_matching_bracket;
mod hover_links;
pub mod hover_popover;
mod indent_guides;
mod inlay_hint_cache;
mod inlays;
pub mod items;
mod jsx_tag_auto_close;
mod linked_editing_ranges;
@@ -61,6 +60,7 @@ pub use element::{
};
pub use git::blame::BlameRenderer;
pub use hover_popover::hover_markdown_style;
pub use inlays::Inlay;
pub use items::MAX_TAB_TITLE_LEN;
pub use lsp::CompletionContext;
pub use lsp_ext::lsp_tasks;
@@ -112,10 +112,10 @@ use gpui::{
div, point, prelude::*, pulsating_between, px, relative, size,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
use hover_links::{HoverLink, HoveredLinkState, find_file};
use hover_popover::{HoverState, hide_hover};
use indent_guides::ActiveIndentGuidesState;
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
use itertools::{Either, Itertools};
use language::{
AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
@@ -124,8 +124,8 @@ use language::{
IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
TextObject, TransactionId, TreeSitterOptions, WordsQuery,
language_settings::{
self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
all_language_settings, language_settings,
self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
language_settings,
},
point_from_lsp, point_to_lsp, text_diff_with_options,
};
@@ -140,15 +140,14 @@ use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails;
use multi_buffer::{
ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
ToOffsetUtf16,
};
use parking_lot::Mutex;
use persistence::DB;
use project::{
BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
ProjectTransaction, TaskSourceKind,
CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
ProjectPath, ProjectTransaction, TaskSourceKind,
debugger::{
breakpoint_store::{
Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
@@ -156,8 +155,11 @@ use project::{
},
session::{Session, SessionEvent},
},
git_store::{GitStoreEvent, RepositoryEvent},
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
git_store::GitStoreEvent,
lsp_store::{
CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
OpenLspBufferHandle,
},
project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
};
use rand::seq::SliceRandom;
@@ -178,7 +180,7 @@ use std::{
iter::{self, Peekable},
mem,
num::NonZeroU32,
ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
ops::{Deref, DerefMut, Not, Range, RangeInclusive},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
@@ -208,6 +210,10 @@ use crate::{
code_context_menus::CompletionsMenuSource,
editor_settings::MultiCursorModifier,
hover_links::{find_url, find_url_from_range},
inlays::{
InlineValueCache,
inlay_hints::{LspInlayHintData, inlay_hint_settings},
},
scroll::{ScrollOffset, ScrollPixelOffset},
signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
};
@@ -261,42 +267,6 @@ impl ReportEditorEvent {
}
}
struct InlineValueCache {
enabled: bool,
inlays: Vec<InlayId>,
refresh_task: Task<Option<()>>,
}
impl InlineValueCache {
fn new(enabled: bool) -> Self {
Self {
enabled,
inlays: Vec::new(),
refresh_task: Task::ready(None),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum InlayId {
EditPrediction(u32),
DebuggerValue(u32),
// LSP
Hint(u32),
Color(u32),
}
impl InlayId {
fn id(&self) -> u32 {
match self {
Self::EditPrediction(id) => *id,
Self::DebuggerValue(id) => *id,
Self::Hint(id) => *id,
Self::Color(id) => *id,
}
}
}
pub enum ActiveDebugLine {}
pub enum DebugStackFrameLine {}
enum DocumentHighlightRead {}
@@ -1124,9 +1094,8 @@ pub struct Editor {
edit_prediction_preview: EditPredictionPreview,
edit_prediction_indent_conflict: bool,
edit_prediction_requires_modifier_in_indent_conflict: bool,
inlay_hint_cache: InlayHintCache,
next_inlay_id: u32,
next_color_inlay_id: u32,
next_inlay_id: usize,
next_color_inlay_id: usize,
_subscriptions: Vec<Subscription>,
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
gutter_dimensions: GutterDimensions,
@@ -1193,10 +1162,19 @@ pub struct Editor {
colors: Option<LspColorData>,
post_scroll_update: Task<()>,
refresh_colors_task: Task<()>,
inlay_hints: Option<LspInlayHintData>,
folding_newlines: Task<()>,
pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
}
fn debounce_value(debounce_ms: u64) -> Option<Duration> {
if debounce_ms > 0 {
Some(Duration::from_millis(debounce_ms))
} else {
None
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
enum NextScrollCursorCenterTopBottom {
#[default]
@@ -1621,31 +1599,6 @@ pub enum GotoDefinitionKind {
Implementation,
}
#[derive(Debug, Clone)]
enum InlayHintRefreshReason {
ModifiersChanged(bool),
Toggle(bool),
SettingsChange(InlayHintSettings),
NewLinesShown,
BufferEdited(HashSet<Arc<Language>>),
RefreshRequested,
ExcerptsRemoved(Vec<ExcerptId>),
}
impl InlayHintRefreshReason {
fn description(&self) -> &'static str {
match self {
Self::ModifiersChanged(_) => "modifiers changed",
Self::Toggle(_) => "toggle",
Self::SettingsChange(_) => "settings change",
Self::NewLinesShown => "new lines shown",
Self::BufferEdited(_) => "buffer edited",
Self::RefreshRequested => "refresh requested",
Self::ExcerptsRemoved(_) => "excerpts removed",
}
}
}
pub enum FormatTarget {
Buffers(HashSet<Entity<Buffer>>),
Ranges(Vec<Range<MultiBufferPoint>>),
@@ -1881,8 +1834,11 @@ impl Editor {
project::Event::RefreshCodeLens => {
// we always query lens with actions, without storing them, always refreshing them
}
project::Event::RefreshInlayHints => {
editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
project::Event::RefreshInlayHints(server_id) => {
editor.refresh_inlay_hints(
InlayHintRefreshReason::RefreshRequested(*server_id),
cx,
);
}
project::Event::LanguageServerRemoved(..) => {
if editor.tasks_update_task.is_none() {
@@ -1919,17 +1875,12 @@ impl Editor {
project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
let buffer_id = *buffer_id;
if editor.buffer().read(cx).buffer(buffer_id).is_some() {
let registered = editor.register_buffer(buffer_id, cx);
if registered {
editor.update_lsp_data(Some(buffer_id), window, cx);
editor.refresh_inlay_hints(
InlayHintRefreshReason::RefreshRequested,
cx,
);
refresh_linked_ranges(editor, window, cx);
editor.refresh_code_actions(window, cx);
editor.refresh_document_highlights(cx);
}
editor.register_buffer(buffer_id, cx);
editor.update_lsp_data(Some(buffer_id), window, cx);
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
refresh_linked_ranges(editor, window, cx);
editor.refresh_code_actions(window, cx);
editor.refresh_document_highlights(cx);
}
}
@@ -2026,14 +1977,7 @@ impl Editor {
let git_store = project.read(cx).git_store().clone();
let project = project.clone();
project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
if let GitStoreEvent::RepositoryUpdated(
_,
RepositoryEvent::Updated {
new_instance: true, ..
},
_,
) = event
{
if let GitStoreEvent::RepositoryAdded = event {
this.load_diff_task = Some(
update_uncommitted_diff_for_buffer(
cx.entity(),
@@ -2200,7 +2144,6 @@ impl Editor {
diagnostics_enabled: full_mode,
word_completions_enabled: full_mode,
inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
pixel_position_of_newest_cursor: None,
last_bounds: None,
@@ -2266,6 +2209,7 @@ impl Editor {
pull_diagnostics_task: Task::ready(()),
colors: None,
refresh_colors_task: Task::ready(()),
inlay_hints: None,
next_color_inlay_id: 0,
post_scroll_update: Task::ready(()),
linked_edit_ranges: Default::default(),
@@ -2403,13 +2347,15 @@ impl Editor {
editor.go_to_active_debug_line(window, cx);
if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
editor.register_buffer(buffer.read(cx).remote_id(), cx);
}
editor.minimap =
editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
editor.colors = Some(LspColorData::new(cx));
editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
editor.register_buffer(buffer.read(cx).remote_id(), cx);
}
editor.update_lsp_data(None, window, cx);
editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
}
@@ -3990,6 +3936,10 @@ impl Editor {
return true;
}
if self.hide_blame_popover(true, cx) {
return true;
}
if hide_hover(self, cx) {
return true;
}
@@ -5194,179 +5144,8 @@ impl Editor {
}
}
pub fn toggle_inline_values(
&mut self,
_: &ToggleInlineValues,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
self.refresh_inline_values(cx);
}
pub fn toggle_inlay_hints(
&mut self,
_: &ToggleInlayHints,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.refresh_inlay_hints(
InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
cx,
);
}
pub fn inlay_hints_enabled(&self) -> bool {
self.inlay_hint_cache.enabled
}
pub fn inline_values_enabled(&self) -> bool {
self.inline_value_cache.enabled
}
#[cfg(any(test, feature = "test-support"))]
pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
.cloned()
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
.cloned()
.collect()
}
fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
if self.semantics_provider.is_none() || !self.mode.is_full() {
return;
}
let reason_description = reason.description();
let ignore_debounce = matches!(
reason,
InlayHintRefreshReason::SettingsChange(_)
| InlayHintRefreshReason::Toggle(_)
| InlayHintRefreshReason::ExcerptsRemoved(_)
| InlayHintRefreshReason::ModifiersChanged(_)
);
let (invalidate_cache, required_languages) = match reason {
InlayHintRefreshReason::ModifiersChanged(enabled) => {
match self.inlay_hint_cache.modifiers_override(enabled) {
Some(enabled) => {
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
self.clear_inlay_hints(cx);
return;
}
}
None => return,
}
}
InlayHintRefreshReason::Toggle(enabled) => {
if self.inlay_hint_cache.toggle(enabled) {
if enabled {
(InvalidationStrategy::RefreshRequested, None)
} else {
self.clear_inlay_hints(cx);
return;
}
} else {
return;
}
}
InlayHintRefreshReason::SettingsChange(new_settings) => {
match self.inlay_hint_cache.update_settings(
&self.buffer,
new_settings,
self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
cx,
) {
ControlFlow::Break(Some(InlaySplice {
to_remove,
to_insert,
})) => {
self.splice_inlays(&to_remove, to_insert, cx);
return;
}
ControlFlow::Break(None) => return,
ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
}
}
InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
{
self.splice_inlays(&to_remove, to_insert, cx);
}
self.display_map.update(cx, |display_map, cx| {
display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
});
return;
}
InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
InlayHintRefreshReason::BufferEdited(buffer_languages) => {
(InvalidationStrategy::BufferEdited, Some(buffer_languages))
}
InlayHintRefreshReason::RefreshRequested => {
(InvalidationStrategy::RefreshRequested, None)
}
};
let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
visible_excerpts.retain(|_, (buffer, _, _)| {
self.registered_buffers
.contains_key(&buffer.read(cx).remote_id())
});
if let Some(InlaySplice {
to_remove,
to_insert,
}) = self.inlay_hint_cache.spawn_hint_refresh(
reason_description,
visible_excerpts,
invalidate_cache,
ignore_debounce,
cx,
) {
self.splice_inlays(&to_remove, to_insert, cx);
}
}
pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
self.splice_inlays(
&self
.visible_inlay_hints(cx)
.map(|inlay| inlay.id)
.collect::<Vec<_>>(),
Vec::new(),
cx,
);
}
fn visible_inlay_hints<'a>(
&'a self,
cx: &'a Context<Editor>,
) -> impl Iterator<Item = &'a Inlay> {
self.display_map
.read(cx)
.current_inlays()
.filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
}
pub fn visible_excerpts(
&self,
restrict_to_languages: Option<&HashSet<Arc<Language>>>,
cx: &mut Context<Editor>,
) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
let Some(project) = self.project() else {
@@ -5385,9 +5164,8 @@ impl Editor {
+ Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
Bias::Left,
);
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
multi_buffer_snapshot
.range_to_buffer_ranges(multi_buffer_visible_range)
.range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
.into_iter()
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
.filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
@@ -5397,23 +5175,17 @@ impl Editor {
.read(cx)
.entry_for_id(buffer_file.project_entry_id()?)?;
if worktree_entry.is_ignored {
return None;
None
} else {
Some((
excerpt_id,
(
multi_buffer.buffer(buffer.remote_id()).unwrap(),
buffer.version().clone(),
excerpt_visible_range,
),
))
}
let language = buffer.language()?;
if let Some(restrict_to_languages) = restrict_to_languages
&& !restrict_to_languages.contains(language)
{
return None;
}
Some((
excerpt_id,
(
multi_buffer.buffer(buffer.remote_id()).unwrap(),
buffer.version().clone(),
excerpt_visible_range,
),
))
})
.collect()
}
@@ -5429,18 +5201,6 @@ impl Editor {
}
}
pub fn splice_inlays(
&self,
to_remove: &[InlayId],
to_insert: Vec<Inlay>,
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, to_insert, cx)
});
cx.notify();
}
fn trigger_on_type_formatting(
&self,
input: String,
@@ -6621,7 +6381,7 @@ impl Editor {
.when(show_tooltip, |this| {
this.tooltip({
let focus_handle = self.focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Toggle Code Actions",
&ToggleCodeActions {
@@ -6629,7 +6389,6 @@ impl Editor {
quick_launch: false,
},
&focus_handle,
window,
cx,
)
}
@@ -6839,13 +6598,15 @@ impl Editor {
}
}
fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
self.inline_blame_popover_show_task.take();
if let Some(state) = &mut self.inline_blame_popover {
let hide_task = cx.spawn(async move |editor, cx| {
cx.background_executor()
.timer(std::time::Duration::from_millis(100))
.await;
if !ignore_timeout {
cx.background_executor()
.timer(std::time::Duration::from_millis(100))
.await;
}
editor
.update(cx, |editor, cx| {
editor.inline_blame_popover.take();
@@ -6854,6 +6615,9 @@ impl Editor {
.ok();
});
state.hide_task = Some(hide_task);
true
} else {
false
}
}
@@ -8489,13 +8253,12 @@ impl Editor {
cx,
);
}))
.tooltip(move |window, cx| {
.tooltip(move |_window, cx| {
Tooltip::with_meta_in(
primary_action_text,
Some(&ToggleBreakpoint),
meta.clone(),
&focus_handle,
window,
cx,
)
})
@@ -11788,7 +11551,7 @@ impl Editor {
end
} else {
text.push('\n');
Point::new(rows.end.0, 0)
Point::new(rows.start.0, 0)
}
} else {
text.push('\n');
@@ -11804,11 +11567,57 @@ impl Editor {
}
}
self.transact(window, cx, |this, _, cx| {
self.transact(window, cx, |this, window, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
// When duplicating upward with whole lines, move the cursor to the duplicated line
if upwards && whole_lines {
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
let mut new_ranges = Vec::new();
let selections = s.all::<Point>(&display_map);
let mut selections_iter = selections.iter().peekable();
while let Some(first_selection) = selections_iter.next() {
// Group contiguous selections together to find the total row span
let mut group_selections = vec![first_selection];
let mut rows = first_selection.spanned_rows(false, &display_map);
while let Some(next_selection) = selections_iter.peek() {
let next_rows = next_selection.spanned_rows(false, &display_map);
if next_rows.start < rows.end {
rows.end = next_rows.end;
group_selections.push(selections_iter.next().unwrap());
} else {
break;
}
}
let row_count = rows.end.0 - rows.start.0;
// Move all selections in this group up by the total number of duplicated rows
for selection in group_selections {
let new_start = Point::new(
selection.start.row.saturating_sub(row_count),
selection.start.column,
);
let new_end = Point::new(
selection.end.row.saturating_sub(row_count),
selection.end.column,
);
new_ranges.push(new_start..new_end);
}
}
s.select_ranges(new_ranges);
});
}
this.request_autoscroll(Autoscroll::fit(), cx);
});
}
@@ -17609,9 +17418,9 @@ impl Editor {
HashSet::default(),
cx,
);
cx.emit(project::Event::RefreshInlayHints);
});
});
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
}
}
@@ -20799,18 +20608,6 @@ impl Editor {
cx.notify();
}
pub(crate) fn highlight_inlays<T: 'static>(
&mut self,
highlights: Vec<InlayHighlight>,
style: HighlightStyle,
cx: &mut Context<Self>,
) {
self.display_map.update(cx, |map, _| {
map.highlight_inlays(TypeId::of::<T>(), highlights, style)
});
cx.notify();
}
pub fn text_highlights<'a, T: 'static>(
&'a self,
cx: &'a App,
@@ -20961,38 +20758,19 @@ impl Editor {
self.update_visible_edit_prediction(window, cx);
}
if let Some(edited_buffer) = edited_buffer {
if edited_buffer.read(cx).file().is_none() {
if let Some(buffer) = edited_buffer {
if buffer.read(cx).file().is_none() {
cx.emit(EditorEvent::TitleChanged);
}
let buffer_id = edited_buffer.read(cx).remote_id();
if let Some(project) = self.project.clone() {
if self.project.is_some() {
let buffer_id = buffer.read(cx).remote_id();
self.register_buffer(buffer_id, cx);
self.update_lsp_data(Some(buffer_id), window, cx);
#[allow(clippy::mutable_key_type)]
let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
multibuffer
.all_buffers()
.into_iter()
.filter_map(|buffer| {
buffer.update(cx, |buffer, cx| {
let language = buffer.language()?;
let should_discard = project.update(cx, |project, cx| {
project.is_local()
&& !project.has_language_servers_for(buffer, cx)
});
should_discard.not().then_some(language.clone())
})
})
.collect::<HashSet<_>>()
});
if !languages_affected.is_empty() {
self.refresh_inlay_hints(
InlayHintRefreshReason::BufferEdited(languages_affected),
cx,
);
}
self.refresh_inlay_hints(
InlayHintRefreshReason::BufferEdited(buffer_id),
cx,
);
}
}
@@ -21039,6 +20817,9 @@ impl Editor {
ids,
removed_buffer_ids,
} => {
if let Some(inlay_hints) = &mut self.inlay_hints {
inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
}
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
for buffer_id in removed_buffer_ids {
self.registered_buffers.remove(buffer_id);
@@ -21213,7 +20994,7 @@ impl Editor {
if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
}) {
if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
if !inlay_splice.is_empty() {
self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
}
self.refresh_colors_for_visible_range(None, window, cx);
@@ -21608,7 +21389,10 @@ impl Editor {
if selection.range.is_empty() {
None
} else {
Some(selection.range)
Some(
snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
)
}
})
.unwrap_or_else(|| 0..snapshot.len());
@@ -21675,10 +21459,6 @@ impl Editor {
mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
}
pub fn inlay_hint_cache(&self) -> &InlayHintCache {
&self.inlay_hint_cache
}
pub fn replay_insert_event(
&mut self,
text: &str,
@@ -21717,21 +21497,6 @@ impl Editor {
self.handle_input(text, window, cx);
}
pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
let Some(provider) = self.semantics_provider.as_ref() else {
return false;
};
let mut supports = false;
self.buffer().update(cx, |this, cx| {
this.for_each_buffer(|buffer| {
supports |= provider.supports_inlay_hints(buffer, cx);
});
});
supports
}
pub fn is_focused(&self, window: &Window) -> bool {
self.focus_handle.is_focused(window)
}
@@ -22147,12 +21912,12 @@ impl Editor {
if self.ignore_lsp_data() {
return;
}
for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
}
}
fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
if !self.registered_buffers.contains_key(&buffer_id)
&& let Some(project) = self.project.as_ref()
{
@@ -22163,13 +21928,10 @@ impl Editor {
project.register_buffer_with_language_servers(&buffer, cx),
);
});
return true;
} else {
self.registered_buffers.remove(&buffer_id);
}
}
false
}
fn ignore_lsp_data(&self) -> bool {
@@ -22877,20 +22639,23 @@ pub trait SemanticsProvider {
cx: &mut App,
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
fn applicable_inlay_chunks(
&self,
buffer: &Entity<Buffer>,
ranges: &[Range<text::Anchor>],
cx: &mut App,
) -> Vec<Range<BufferRow>>;
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
fn inlay_hints(
&self,
buffer_handle: Entity<Buffer>,
range: Range<text::Anchor>,
invalidate: InvalidationStrategy,
buffer: Entity<Buffer>,
ranges: Vec<Range<text::Anchor>>,
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
cx: &mut App,
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
fn resolve_inlay_hint(
&self,
hint: InlayHint,
buffer_handle: Entity<Buffer>,
server_id: LanguageServerId,
cx: &mut App,
) -> Option<Task<anyhow::Result<InlayHint>>>;
) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
@@ -23383,26 +23148,33 @@ impl SemanticsProvider for Entity<Project> {
})
}
fn inlay_hints(
fn applicable_inlay_chunks(
&self,
buffer_handle: Entity<Buffer>,
range: Range<text::Anchor>,
buffer: &Entity<Buffer>,
ranges: &[Range<text::Anchor>],
cx: &mut App,
) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
Some(self.update(cx, |project, cx| {
project.inlay_hints(buffer_handle, range, cx)
}))
) -> Vec<Range<BufferRow>> {
self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
})
}
fn resolve_inlay_hint(
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
self.read(cx).lsp_store().update(cx, |lsp_store, _| {
lsp_store.invalidate_inlay_hints(for_buffers)
});
}
fn inlay_hints(
&self,
hint: InlayHint,
buffer_handle: Entity<Buffer>,
server_id: LanguageServerId,
invalidate: InvalidationStrategy,
buffer: Entity<Buffer>,
ranges: Vec<Range<text::Anchor>>,
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
cx: &mut App,
) -> Option<Task<anyhow::Result<InlayHint>>> {
Some(self.update(cx, |project, cx| {
project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
}))
}
@@ -23451,16 +23223,6 @@ impl SemanticsProvider for Entity<Project> {
}
}
fn inlay_hint_settings(
location: Anchor,
snapshot: &MultiBufferSnapshot,
cx: &mut Context<Editor>,
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name());
language_settings(language, file, cx).inlay_hints
}
fn consume_contiguous_rows(
contiguous_row_selections: &mut Vec<Selection<Point>>,
selection: &Selection<Point>,
@@ -24864,12 +24626,11 @@ fn render_diff_hunk_controls(
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Stage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
@@ -24891,12 +24652,11 @@ fn render_diff_hunk_controls(
.alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Unstage Hunk",
&::git::ToggleStaged,
&focus_handle,
window,
cx,
)
}
@@ -24918,14 +24678,8 @@ fn render_diff_hunk_controls(
Button::new(("restore", row as u64), "Restore")
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Restore Hunk",
&::git::Restore,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
}
})
.on_click({
@@ -24950,14 +24704,8 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
Tooltip::for_action_in(
"Next Hunk",
&GoToHunk,
&focus_handle,
window,
cx,
)
move |_window, cx| {
Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
}
})
.on_click({
@@ -24986,12 +24734,11 @@ fn render_diff_hunk_controls(
// .disabled(!has_multiple_hunks)
.tooltip({
let focus_handle = editor.focus_handle(cx);
move |window, cx| {
move |_window, cx| {
Tooltip::for_action_in(
"Previous Hunk",
&GoToPreviousHunk,
&focus_handle,
window,
cx,
)
}

View File

@@ -31,6 +31,7 @@ use language::{
tree_sitter_python,
};
use language_settings::Formatter;
use languages::rust_lang;
use lsp::CompletionParams;
use multi_buffer::{IndentGuide, PathKey};
use parking_lot::Mutex;
@@ -50,7 +51,7 @@ use std::{
iter,
sync::atomic::{self, AtomicUsize},
};
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
use test::build_editor_with_project;
use text::ToPoint as _;
use unindent::Unindent;
use util::{
@@ -62,7 +63,7 @@ use util::{
use workspace::{
CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
OpenOptions, ViewId,
invalid_buffer_view::InvalidBufferView,
invalid_item_view::InvalidItemView,
item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
register_project_item,
};
@@ -5645,8 +5646,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
);
});
// With `move_upwards` the selections stay in place, except for
// the lines inserted above them
// With `duplicate_line_up` the selections move to the duplicated lines,
// which are inserted above the original lines
let editor = cx.add_window(|window, cx| {
let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
build_editor(buffer, window, cx)
@@ -5668,7 +5669,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
]
);
});
@@ -12614,18 +12615,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
)
.await;
cx.run_until_parked();
// Set up a buffer white some trailing whitespace and no trailing newline.
cx.set_state(
&[
"one ", //
"twoˇ", //
"three ", //
"four", //
]
.join("\n"),
);
// Record which buffer changes have been sent to the language server
let buffer_changes = Arc::new(Mutex::new(Vec::new()));
cx.lsp
@@ -12641,6 +12630,11 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
}
});
#[cfg(target_os = "windows")]
let line_ending = "\r\n";
#[cfg(not(target_os = "windows"))]
let line_ending = "\n";
// Handle formatting requests to the language server.
cx.lsp
.set_request_handler::<lsp::request::Formatting, _, _>({
@@ -12664,7 +12658,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
),
(
lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
"\n".into()
line_ending.into()
),
]
);
@@ -12675,20 +12669,32 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
lsp::Position::new(1, 0),
lsp::Position::new(1, 0),
),
new_text: "\n".into(),
new_text: line_ending.into(),
},
lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(2, 0),
lsp::Position::new(2, 0),
),
new_text: "\n".into(),
new_text: line_ending.into(),
},
]))
}
}
});
// Set up a buffer white some trailing whitespace and no trailing newline.
cx.set_state(
&[
"one ", //
"twoˇ", //
"three ", //
"four", //
]
.join("\n"),
);
cx.run_until_parked();
// Submit a format request.
let format = cx
.update_editor(|editor, window, cx| editor.format(&Format, window, cx))
@@ -26251,7 +26257,7 @@ async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
assert_eq!(
handle.to_any().entity_type(),
TypeId::of::<InvalidBufferView>()
TypeId::of::<InvalidItemView>()
);
}
@@ -26656,6 +26662,83 @@ async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_mult
));
}
#[gpui::test]
async fn test_non_linux_line_endings_registration(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let unix_newlines_file_text = "fn main() {
let a = 5;
}";
let clrf_file_text = unix_newlines_file_text.lines().join("\r\n");
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/a"),
json!({
"first.rs": &clrf_file_text,
}),
)
.await;
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let registered_text = Arc::new(Mutex::new(Vec::new()));
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
..lsp::ServerCapabilities::default()
},
name: "rust-analyzer",
initializer: Some({
let registered_text = registered_text.clone();
Box::new(move |fake_server| {
fake_server.handle_notification::<lsp::notification::DidOpenTextDocument, _>({
let registered_text = registered_text.clone();
move |params, _| {
registered_text.lock().push(params.text_document.text);
}
});
})
}),
..FakeLspAdapter::default()
},
);
let editor = workspace
.update(cx, |workspace, window, cx| {
workspace.open_abs_path(
PathBuf::from(path!("/a/first.rs")),
OpenOptions::default(),
window,
cx,
)
})
.unwrap()
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let _fake_language_server = fake_servers.next().await.unwrap();
cx.executor().run_until_parked();
assert_eq!(
editor.update(cx, |editor, cx| editor.text(cx)),
unix_newlines_file_text,
"Default text API returns \n-separated text",
);
assert_eq!(
vec![clrf_file_text],
registered_text.lock().drain(..).collect::<Vec<_>>(),
"Expected the language server to receive the exact same text from the FS",
);
}
#[gpui::test]
async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -26805,8 +26888,8 @@ fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext)
assert_eq!(
editor.selections.display_ranges(cx),
vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)],
"Selection should remain on the original line"
vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
"Selection should move to the duplicated line"
);
})
.unwrap();

View File

@@ -652,7 +652,6 @@ impl EditorElement {
fn mouse_left_down(
editor: &mut Editor,
event: &MouseDownEvent,
hovered_hunk: Option<Range<Anchor>>,
position_map: &PositionMap,
line_numbers: &HashMap<MultiBufferRow, LineNumberLayout>,
window: &mut Window,
@@ -668,7 +667,20 @@ impl EditorElement {
let mut click_count = event.click_count;
let mut modifiers = event.modifiers;
if let Some(hovered_hunk) = hovered_hunk {
if let Some(hovered_hunk) =
position_map
.display_hunks
.iter()
.find_map(|(hunk, hunk_hitbox)| match hunk {
DisplayDiffHunk::Folded { .. } => None,
DisplayDiffHunk::Unfolded {
multi_buffer_range, ..
} => hunk_hitbox
.as_ref()
.is_some_and(|hitbox| hitbox.is_hovered(window))
.then(|| multi_buffer_range.clone()),
})
{
editor.toggle_single_diff_hunk(hovered_hunk, cx);
cx.notify();
return;
@@ -1195,10 +1207,10 @@ impl EditorElement {
if mouse_over_inline_blame || mouse_over_popover {
editor.show_blame_popover(*buffer_id, blame_entry, event.position, false, cx);
} else if !keyboard_grace {
editor.hide_blame_popover(cx);
editor.hide_blame_popover(false, cx);
}
} else {
editor.hide_blame_popover(cx);
editor.hide_blame_popover(false, cx);
}
let breakpoint_indicator = if gutter_hovered {
@@ -1410,7 +1422,11 @@ impl EditorElement {
layouts.push(layout);
}
let player = editor.current_user_player_color(cx);
let mut player = editor.current_user_player_color(cx);
if !editor.is_focused(window) {
const UNFOCUS_EDITOR_SELECTION_OPACITY: f32 = 0.5;
player.selection = player.selection.opacity(UNFOCUS_EDITOR_SELECTION_OPACITY);
}
selections.push((player, layouts));
if let SelectionDragState::Dragging {
@@ -3898,7 +3914,7 @@ impl EditorElement {
.children(toggle_chevron_icon)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
move |_window, cx| {
Tooltip::with_meta_in(
"Toggle Excerpt Fold",
Some(&ToggleFold),
@@ -3911,7 +3927,6 @@ impl EditorElement {
)
),
&focus_handle,
window,
cx,
)
}
@@ -4012,15 +4027,11 @@ impl EditorElement {
.id("jump-to-file-button")
.gap_2p5()
.child(Label::new("Jump To File"))
.children(
KeyBinding::for_action_in(
&OpenExcerpts,
&focus_handle,
window,
cx,
)
.map(|binding| binding.into_any_element()),
),
.child(KeyBinding::for_action_in(
&OpenExcerpts,
&focus_handle,
cx,
)),
)
},
)
@@ -7221,16 +7232,9 @@ impl EditorElement {
* ScrollPixelOffset::from(max_glyph_advance)
- ScrollPixelOffset::from(delta.x * scroll_sensitivity))
/ ScrollPixelOffset::from(max_glyph_advance);
let scale_factor = window.scale_factor();
let y = (current_scroll_position.y
* ScrollPixelOffset::from(line_height)
* ScrollPixelOffset::from(scale_factor)
let y = (current_scroll_position.y * ScrollPixelOffset::from(line_height)
- ScrollPixelOffset::from(delta.y * scroll_sensitivity))
.round()
/ ScrollPixelOffset::from(line_height)
/ ScrollPixelOffset::from(scale_factor);
/ ScrollPixelOffset::from(line_height);
let mut scroll_position =
point(x, y).clamp(&point(0., 0.), &position_map.scroll_max);
let forbid_vertical_scroll = editor.scroll_manager.forbid_vertical_scroll();
@@ -7263,26 +7267,6 @@ impl EditorElement {
window.on_mouse_event({
let position_map = layout.position_map.clone();
let editor = self.editor.clone();
let diff_hunk_range =
layout
.display_hunks
.iter()
.find_map(|(hunk, hunk_hitbox)| match hunk {
DisplayDiffHunk::Folded { .. } => None,
DisplayDiffHunk::Unfolded {
multi_buffer_range, ..
} => {
if hunk_hitbox
.as_ref()
.map(|hitbox| hitbox.is_hovered(window))
.unwrap_or(false)
{
Some(multi_buffer_range.clone())
} else {
None
}
}
});
let line_numbers = layout.line_numbers.clone();
move |event: &MouseDownEvent, phase, window, cx| {
@@ -7299,7 +7283,6 @@ impl EditorElement {
Self::mouse_left_down(
editor,
event,
diff_hunk_range.clone(),
&position_map,
line_numbers.as_ref(),
window,
@@ -8940,10 +8923,20 @@ impl Element for EditorElement {
cx,
);
let merged_highlighted_ranges =
if let Some((_, colors)) = document_colors.as_ref() {
&highlighted_ranges
.clone()
.into_iter()
.chain(colors.clone())
.collect()
} else {
&highlighted_ranges
};
let bg_segments_per_row = Self::bg_segments_per_row(
start_row..end_row,
&selections,
&highlighted_ranges,
&merged_highlighted_ranges,
self.style.background,
);

View File

@@ -16,7 +16,7 @@ use markdown::Markdown;
use multi_buffer::{MultiBuffer, RowInfo};
use project::{
Project, ProjectItem as _,
git_store::{GitStoreEvent, Repository, RepositoryEvent},
git_store::{GitStoreEvent, Repository},
};
use smallvec::SmallVec;
use std::{sync::Arc, time::Duration};
@@ -235,8 +235,8 @@ impl GitBlame {
let git_store = project.read(cx).git_store().clone();
let git_store_subscription =
cx.subscribe(&git_store, move |this, _, event, cx| match event {
GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, _)
| GitStoreEvent::RepositoryAdded(_)
GitStoreEvent::RepositoryUpdated(_, _, _)
| GitStoreEvent::RepositoryAdded
| GitStoreEvent::RepositoryRemoved(_) => {
log::debug!("Status of git repositories updated. Regenerating blame data...",);
this.generate(cx);

View File

@@ -1,19 +1,14 @@
use crate::{
Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
GoToDefinitionSplit, GoToTypeDefinition, GoToTypeDefinitionSplit, GotoDefinitionKind, InlayId,
Navigated, PointForPosition, SelectPhase,
editor_settings::GoToDefinitionFallback,
hover_popover::{self, InlayHover},
GoToDefinitionSplit, GoToTypeDefinition, GoToTypeDefinitionSplit, GotoDefinitionKind,
Navigated, PointForPosition, SelectPhase, editor_settings::GoToDefinitionFallback,
scroll::ScrollAmount,
};
use gpui::{App, AsyncWindowContext, Context, Entity, Modifiers, Task, Window, px};
use language::{Bias, ToOffset};
use linkify::{LinkFinder, LinkKind};
use lsp::LanguageServerId;
use project::{
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink, Project,
ResolveState, ResolvedPath,
};
use project::{InlayId, LocationLink, Project, ResolvedPath};
use settings::Settings;
use std::ops::Range;
use theme::ActiveTheme as _;
@@ -138,10 +133,9 @@ impl Editor {
show_link_definition(modifiers.shift, self, trigger_point, snapshot, window, cx);
}
None => {
update_inlay_link_and_hover_points(
self.update_inlay_link_and_hover_points(
snapshot,
point_for_position,
self,
hovered_link_modifier,
modifiers.shift,
window,
@@ -283,182 +277,6 @@ impl Editor {
}
}
pub fn update_inlay_link_and_hover_points(
snapshot: &EditorSnapshot,
point_for_position: PointForPosition,
editor: &mut Editor,
secondary_held: bool,
shift_held: bool,
window: &mut Window,
cx: &mut Context<Editor>,
) {
let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
} else {
None
};
let mut go_to_definition_updated = false;
let mut hover_updated = false;
if let Some(hovered_offset) = hovered_offset {
let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let previous_valid_anchor =
buffer_snapshot.anchor_before(point_for_position.previous_valid.to_point(snapshot));
let next_valid_anchor =
buffer_snapshot.anchor_after(point_for_position.next_valid.to_point(snapshot));
if let Some(hovered_hint) = editor
.visible_inlay_hints(cx)
.skip_while(|hint| {
hint.position
.cmp(&previous_valid_anchor, &buffer_snapshot)
.is_lt()
})
.take_while(|hint| {
hint.position
.cmp(&next_valid_anchor, &buffer_snapshot)
.is_le()
})
.max_by_key(|hint| hint.id)
{
let inlay_hint_cache = editor.inlay_hint_cache();
let excerpt_id = previous_valid_anchor.excerpt_id;
if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = snapshot
.buffer_snapshot()
.buffer_id_for_anchor(previous_valid_anchor)
{
inlay_hint_cache.spawn_hint_resolve(
buffer_id,
excerpt_id,
hovered_hint.id,
window,
cx,
);
}
}
ResolveState::Resolved => {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
if cached_hint.padding_left {
extra_shift_left += 1;
extra_shift_right += 1;
}
if cached_hint.padding_right {
extra_shift_right += 1;
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: match tooltip {
InlayHintTooltip::String(text) => HoverBlock {
text,
kind: HoverBlockKind::PlainText,
},
InlayHintTooltip::MarkupContent(content) => {
HoverBlock {
text: content.value,
kind: content.kind,
}
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text().len() + extra_shift_right,
},
},
window,
cx,
);
hover_updated = true;
}
}
project::InlayHintLabel::LabelParts(label_parts) => {
let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts,
hint_start,
hovered_offset,
)
{
let highlight_start =
(part_range.start - hint_start).0 + extra_shift_left;
let highlight_end =
(part_range.end - hint_start).0 + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = hovered_hint_part.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: match tooltip {
InlayHintLabelPartTooltip::String(text) => {
HoverBlock {
text,
kind: HoverBlockKind::PlainText,
}
}
InlayHintLabelPartTooltip::MarkupContent(
content,
) => HoverBlock {
text: content.value,
kind: content.kind,
},
},
range: highlight.clone(),
},
window,
cx,
);
hover_updated = true;
}
if let Some((language_server_id, location)) =
hovered_hint_part.location
&& secondary_held
&& !editor.has_pending_nonempty_selection()
{
go_to_definition_updated = true;
show_link_definition(
shift_held,
editor,
TriggerPoint::InlayHint(
highlight,
location,
language_server_id,
),
snapshot,
window,
cx,
);
}
}
}
};
}
ResolveState::Resolving => {}
}
}
}
}
if !go_to_definition_updated {
editor.hide_hovered_link(cx)
}
if !hover_updated {
hover_popover::hover_at(editor, None, window, cx);
}
}
pub fn show_link_definition(
shift_held: bool,
editor: &mut Editor,
@@ -912,7 +730,7 @@ mod tests {
DisplayPoint,
display_map::ToDisplayPoint,
editor_tests::init_test,
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels},
test::editor_lsp_test_context::EditorLspTestContext,
};
use futures::StreamExt;
@@ -1343,7 +1161,7 @@ mod tests {
cx.background_executor.run_until_parked();
cx.update_editor(|editor, _window, cx| {
let expected_layers = vec![hint_label.to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, cached_hint_labels(editor, cx));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
});

View File

@@ -986,17 +986,17 @@ impl DiagnosticPopover {
mod tests {
use super::*;
use crate::{
InlayId, PointForPosition,
PointForPosition,
actions::ConfirmCompletion,
editor_tests::{handle_completion_request, init_test},
hover_links::update_inlay_link_and_hover_points,
inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels},
inlays::inlay_hints::tests::{cached_hint_labels, visible_hint_labels},
test::editor_lsp_test_context::EditorLspTestContext,
};
use collections::BTreeSet;
use gpui::App;
use indoc::indoc;
use markdown::parser::MarkdownEvent;
use project::InlayId;
use settings::InlayHintSettingsContent;
use smol::stream::StreamExt;
use std::sync::atomic;
@@ -1648,7 +1648,7 @@ mod tests {
cx.background_executor.run_until_parked();
cx.update_editor(|editor, _, cx| {
let expected_layers = vec![entire_hint_label.to_string()];
assert_eq!(expected_layers, cached_hint_labels(editor));
assert_eq!(expected_layers, cached_hint_labels(editor, cx));
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
});
@@ -1687,10 +1687,9 @@ mod tests {
}
});
cx.update_editor(|editor, window, cx| {
update_inlay_link_and_hover_points(
editor.update_inlay_link_and_hover_points(
&editor.snapshot(window, cx),
new_type_hint_part_hover_position,
editor,
true,
false,
window,
@@ -1758,10 +1757,9 @@ mod tests {
cx.background_executor.run_until_parked();
cx.update_editor(|editor, window, cx| {
update_inlay_link_and_hover_points(
editor.update_inlay_link_and_hover_points(
&editor.snapshot(window, cx),
new_type_hint_part_hover_position,
editor,
true,
false,
window,
@@ -1813,10 +1811,9 @@ mod tests {
}
});
cx.update_editor(|editor, window, cx| {
update_inlay_link_and_hover_points(
editor.update_inlay_link_and_hover_points(
&editor.snapshot(window, cx),
struct_hint_part_hover_position,
editor,
true,
false,
window,

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