Compare commits

...

321 Commits

Author SHA1 Message Date
Nate Butler
c8566ed816 wip 2025-01-11 13:06:42 -05:00
Nate Butler
556fe6c14f 📎
Co-Authored-By: Cole Miller <53574922+cole-miller@users.noreply.github.com>
2025-01-10 11:54:14 -05:00
Nate Butler
72cb786e5b Handle more worktree update cases
Co-Authored-By: Cole Miller <53574922+cole-miller@users.noreply.github.com>
2025-01-10 11:50:57 -05:00
Cole Miller
73dceb5cf2 FIXME -> TODO 2025-01-10 10:46:31 -05:00
Cole Miller
9fad819749 FakeGitRepository::update_index is unimplemented for now 2025-01-10 10:46:31 -05:00
Cole Miller
312534ffe7 Separate toggle state and status in GitListEntry 2025-01-10 10:46:31 -05:00
Cole Miller
1d176f1a6b Special-case is_staged for untracked files 2025-01-10 10:46:31 -05:00
Nate Butler
e301b7bbf5 fix incorrect path color for deleted statuses 2025-01-10 10:45:18 -05:00
Nate Butler
a047c02bcc Ensure buttons use the ui font 2025-01-10 10:43:10 -05:00
Nate Butler
c82a27e4f9 Show complete path for entries 2025-01-10 08:45:19 -05:00
Nate Butler
d44a1a4474 Tidy 2025-01-10 08:30:40 -05:00
Cole Miller
6852bf5488 Get checkboxes hooked up to git 2025-01-09 21:35:10 -05:00
Cole Miller
ea678027e0 Start hooking up index update task 2025-01-09 21:29:42 -05:00
Nate Butler
232e0883f8 Prevent list movement when commit editor is focused 2025-01-09 16:50:29 -05:00
Nate Butler
8a6c6174f9 Update more actions, add focus actions 2025-01-09 16:10:30 -05:00
Nate Butler
5146520df8 Continue adding keyboard controls 2025-01-09 15:49:27 -05:00
Nate Butler
93007d42a6 Start on open_entry
Co-Authored-By: Cole Miller <53574922+cole-miller@users.noreply.github.com>
2025-01-09 13:05:14 -05:00
Nate Butler
4438c4c1c3 Start on git panel keybindings
Co-Authored-By: Cole Miller <53574922+cole-miller@users.noreply.github.com>
2025-01-09 12:27:15 -05:00
Nate Butler
2a152ddcdc Update incorrect index logic for entries 2025-01-09 11:00:20 -05:00
Cole Miller
c62b10792c Gather more info from git status and wire up UI
Co-authored-by: Nate <nate@zed.dev>
2025-01-08 17:55:15 -05:00
Nate Butler
ae50babfb4 Initialize the first repo when building the panel
Co-Authored-By: Cole Miller <53574922+cole-miller@users.noreply.github.com>
2025-01-08 14:15:45 -05:00
Nate Butler
c25079adb6 WIP - broken 2025-01-08 12:33:29 -05:00
Nate Butler
197591f557 Merge branch 'main' into git-panel-traversal 2025-01-08 08:28:10 -05:00
Nate Butler
1ced12fbaf WIP 2025-01-08 08:28:07 -05:00
Remco Smits
68e670bf54 Fix rust runnable is not detected if comment is after #[test] attribute (#22823)
Closes #22798

This fixes that we didn't detect the Rust runnable when there was a
comment after the `#[test]` attribute.

![Screenshot 2025-01-08 at 13 22
59](https://github.com/user-attachments/assets/bd6a7ae0-93d4-4f93-9d0d-11453acb2032)


Release Notes:

- Fixed Rust runnable not detected when comment is after `#[test]`
attribute.

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-01-08 12:51:23 +00:00
Yasin
8317c9215a python: Detect pixi environments automatically (#22635)
Goal: Allow zed to locate [`pixi`](https://github.com/prefix-dev/pixi)
environments

Changes:
- Uses a newer release of
[`python-environment-tools`](https://github.com/microsoft/python-environment-tools)
with the new `pet-pixi` create
- Adds `PythonEnvironmentKind::Pixi` as a possible environment kind, to
allow the rest of the code to detect the environment

I tested the changes locally. It found the correct pixi environment and
I was able to run `pytest` through the UI icon.


Release Notes:

- Added detection for pixi-environments

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2025-01-08 11:06:33 +00:00
Thorsten Ball
f9ee28db5e vim: Fix clipping when navigating over inlay hints (#22813)
This fixes the issue described in this comment:
https://github.com/zed-industries/zed/pull/22439#issuecomment-2563896422

Essentially, we'd clip in the wrong direction when there were multi-line
inlay hints.

It also fixes inline completions for non-Zeta-providers showing up in
normal mode.

Release Notes:

- N/A
2025-01-08 09:41:43 +00:00
Conrad Irwin
dffdf99228 Fix completion menu jumping (#22780)
Co-Authored-By: Thorsten <thorsten@zed.dev>

Release Notes:

- Fix selected suggestion updating too many times when Zeta triggers

Co-authored-by: Thorsten <thorsten@zed.dev>
2025-01-08 06:48:52 +00:00
Conrad Irwin
0b361e5b7c Fix panic in request_multiple_lsp_locally (#22806)
Release Notes:

- Fix a panic after disconnecting from a remote project
2025-01-08 03:34:24 +00:00
Osvaldo
222b04548d vim: Add AnyQuotes support for unified quote handling similar to mini.ai nvim (#22263)
### Edit 1:
I tested it locally and it works!

### IMPORTANT: 
**Feedback and suggestions for improvement are greatly appreciated!**

This commit introduces a new AnyQuotes text object to handle text
surrounded by single quotes ('), double quotes ("), or back quotes (`)
seamlessly. The following changes are included:

- Added AnyQuotes to the Object enum to represent the new feature.
- Registered AnyQuotes as an action in the actions! macro and register
function to ensure proper integration with Vim actions like ci, ca, di,
and da.
- Extended Object::range to check for surrounding single, double, or
back quotes sequentially.
- Updated methods like is_multiline and always_expands_both_ways to
ensure consistent behavior with other text objects.
- Added support in surrounding_markers to evaluate any of the quote
types when AnyQuotes is invoked.
- This enhancement provides users with a flexible and unified way to
interact with text objects enclosed by different types of quotes.

Release Notes:

- vim: Add `aq`/`iq` "any quote" text objects that are the smallest of
`a"`, `a'` or <code>a`</code>
2025-01-08 03:00:20 +00:00
tachyglossues
811b872f4e Fix no whitespace displaying after an "à " (#22403)
fixed a bug where with the "show_whitespaces": "boundary" option, when
there was an "à" followed by a space, a white space was displayed, and
when "à" was at the end of the line, a white space was added



Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-01-08 02:39:53 +00:00
Marshall Bowers
d67f2d3eab ui: Update doc comments (#22802)
This PR updates some doc comments in the `ui` crate:

- End doc comments with punctuation
- Place doc comments above attributes

Release Notes:

- N/A
2025-01-07 23:16:36 +00:00
Kirill Bulatov
eae88a5c47 Include generate-licenses into bundle-mac script (#22800)
Closes https://github.com/zed-industries/zed/issues/21613

Same as `bundle-linux`, to avoid panicking on missing licenses for
homegrown-built releases when `Help -> View dependency licenses` menu
action is triggered.

Release Notes:

- Altered bundle-mac script to generate licenses
2025-01-07 22:22:57 +00:00
Kirill Bulatov
a331497367 Display language server info in the server logs tab (#22797)
Follow-up of https://github.com/zed-industries/zed/pull/19448

When dealing with issues like
https://github.com/zed-industries/zed/issues/22749, it's quite tedious
to ask for logs and check them out.

This PR attempts to establish a single "diagnose my language server"
place in the server logs panel, where server capabilities were already
displayed after https://github.com/zed-industries/zed/pull/19448

The design is pretty brutal, but seems to be on par with the previous
version and it's a technical corner of Zed, so seems to be ok for now:


![image](https://github.com/user-attachments/assets/3471c83a-329e-475a-8cad-af95684da960)

Release Notes:

- Improved lsp logs view to display more language server data
2025-01-07 21:57:59 +00:00
Nate Butler
c2971819b4 move entry updates to a task 2025-01-07 16:42:53 -05:00
spotikhanov
a653e8adda Remove ENABLE_MATH option from pulldown_cmark to fix links which contain dollar sign (#22647)
This pr closes #21466 issue by disabling math in pulldown_cmark Parser.

The dollar sign symbol is used in pulldown_cmark Math extension, see
"Math in links" section for more details:
https://pulldown-cmark.github.io/pulldown-cmark/specs/math.html

I've tried another approach at first, without disabling math extension: 

```
let iterator = TextMergeWithOffset::new(Parser::new_ext(text, options));
```

instead of current implementation

```
Parser::new_ext(text, options).into_offset_iter()
```

This way pulldown_cmark merges consecutive text events and this helps to
correctly parse links from plain text:
https://svelte.dev/docs/svelte/$state

But in this case the dollar sign still breaks markdown links:
\[https://svelte.dev/docs/svelte/$state](https://svelte.dev/docs/svelte/$state)

So in the end I disabled the math extension, it fixes both link formats.
See markdown/examples/markdown.rs to reproduce.

Release Notes:
- N/A
2025-01-07 21:21:18 +00:00
Nate Butler
c4b470685d ui: Update Checkbox design (#22794)
This PR shifts the design of checkboxes and introduces ways to style
checkboxes based on Elevation, or tint them with a custom color.

This may have some impacts on existing uses of checkboxes.

When creating a checkbox you now need to call `.fill` if you want the
checkbox to have a filled style.

Before:

![CleanShot 2025-01-07 at 15 54
57@2x](https://github.com/user-attachments/assets/44463383-018e-4e7d-ac60-f3e7e643661d)

![CleanShot 2025-01-07 at 15 56
17@2x](https://github.com/user-attachments/assets/c72af034-4987-418e-b91b-5f50337fb212)


After:

![CleanShot 2025-01-07 at 15 55
47@2x](https://github.com/user-attachments/assets/711dff92-9ec3-485a-89de-e28f0b709833)

![CleanShot 2025-01-07 at 15 56
02@2x](https://github.com/user-attachments/assets/63797be4-22b2-464d-b4d3-fefc0d95537a)


Release Notes:

- N/A
2025-01-07 21:11:39 +00:00
Nate Butler
5a170ad359 Delete TODO.md 2025-01-07 16:07:26 -05:00
Piotr Osiewicz
7a66c764b4 python: Check for activate script existence before running it (#22792)
Closes #ISSUE

Release Notes:

- Python auto-venv activation in terminal now checks for path existence
before executing the activate script.
2025-01-07 20:57:58 +00:00
Danilo Leal
9d5ae516fd assistant2: Add keybinding for "Remove All Context" action (#22783)
Ensuring all of the assistant 2 actions have keybindings.

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-07 20:56:10 +00:00
Tim Vilgot Mikael Fredenberg
bb6e8053d3 windows: Don't load login shell environment (#22681)
I'm consistently getting the following error on startup:

```
2025-01-05T14:45:43.4602865+01:00 [ERROR] SHELL environment variable is not assigned so we can't source login environment variables

Caused by:
    environment variable not found
```

The source function, `load_login_shell_environment`, assumes a UNIX
environment and should therefore not be called on Windows. (Unless you
are using git bash?)

Release Notes:

* N/A
2025-01-07 19:52:47 +00:00
Marshall Bowers
c53615ff61 assistant2: Add intermediate bindings to improve conditional readability (#22790)
This PR adds some intermediate bindings to the checks for if a
file/directory is already included to make the conditional a bit
clearer.

It wasn't immediately obvious what the boolean values corresponded to
when looking at it.

Release Notes:

- N/A
2025-01-07 19:42:38 +00:00
Marshall Bowers
fffa40f973 assistant2: Make context persistent in the thread (#22789)
This PR makes it so the context is persistent in the thread, rather than
having to reattach it for each message.

This PR intentionally does not make an attempt to refresh the attached
context if it changes. That will come in a follow-up.

Release Notes:

- N/A
2025-01-07 19:16:30 +00:00
Danilo Leal
76a8b55f77 assistant2: Add little design improvements (#22784)
The most relevant change in this PR is ensuring that the path tooltip
doesn't overlap with the "Remove Context" tooltip. Now, the former
tooltip only shows if you hover over the context pill's label. This
avoids a little flicker that was happening as the path tooltip would
show first and then quickly followed by the icon button's one.

Release Notes:

- N/A
2025-01-07 19:10:54 +00:00
Agus Zubiaga
deeccd2c63 assistant2: Focus prompt editor after dismissing context picker (#22786)
https://github.com/user-attachments/assets/6d0ac75e-fbc2-4bc2-be13-2d109f61361b




Release Notes:

- N/A
2025-01-07 18:40:16 +00:00
tims
d3fc00d5a0 windows: Fix fs watch when file doesn't exist or is a symlink (#22660)
Closes #22659

More context can be found in attached issue.

This is specific to Windows:

1. Add parent directory watching for fs watch when the file doesn't
exist. For example, when Zed is first launched and `settings.json` isn't
there.
2. Add proper symlink handling for fs watch. For example, when
`settings.json` is a symlink.

This is exactly same as how we handle it on Linux.

Release Notes:

- Fixed an issue where items on the Welcome page could not be toggled on
Windows, either on first launch or when `settings.json` is a symlink.
2025-01-07 18:20:22 +00:00
uncenter
d58f006498 Use standard injection.language and injection.content captures (#22268)
Closes #9656. Continuation of #9654, but with the addition of backwards
compatibility for the existing captures.

Release Notes:

- Improved Tree-sitter support with added compatibility for standard
injections captures

---------

Co-authored-by: Finn Evers <finn.evers@outlook.de>
2025-01-07 18:17:49 +00:00
Nate Butler
f3e75d8ff6 git_ui: Update commit composer and git status entry UI (#22738)
Blocked on:

- No way to get # of lines changed (added/removed)
- Need methods for:
    - `commit`
    - `stage`
    - `unstage`
- `revert_all` - Similar to Editor::RevertFile, but for all changes in
the project

TODO:

- [ ] Update checkbox visual style to match
[figma](https://www.figma.com/design/sKk3aa7XPwBoE8fdlgp7E8/Git-integration?node-id=804-9255&t=wsHFxPgYHEX78Ky1-11)
- [ ] Update panel button style to filled

- [ ] Panel header
  - [x] Correct 1 change suffix (1 changes -> 1 change)
  - [ ] Add lines changed badge
  - [ ] Add context menu button (`...`)
  - [ ] Add context menu
  - [ ] Wire up Revert All
- [ ] Entry List
  - [x] Revert unwanted ListItem styling
  - [x] Add selected, hover states
  - [ ] Add `scrolled_to_top`, `scrolled_to_bottom`
  - [ ] Show gradient overflow indicator
- [ ] Add `JumpToTop`, `JumpToBottom` actions to the list, bind to shift
+ arrow keys
  - [ ] Remove wrapping from keyboard movement
- [ ] Entry
  - [x] Style deleted entries with a strikethrough
  - [x] `...` on hover or selected
  - [ ] Add context menu
- [ ] Composer
  - Todo...
  
Release Notes:

- N/A
2025-01-07 18:03:16 +00:00
Thorsten Ball
d2e44ab87d terminal: Set TERM to xterm-256color (#22777)
This is a follow-up to #22615 and fixes the issue of `alacritty`
resulting in broken shell/CLI apps if `alacritty` is not in the terminfo
database.

Closes #ISSUE

Release Notes:

- Set `TERM` to `xterm-256color` in Zed's built-in terminal
2025-01-07 17:25:48 +00:00
Sergei Shulepov
56017022c4 project_panel: Support multiple items in RemoveFromProject (#22455)
This makes the `RemoveFromProject` action to remove all marked entries
in the project panel instead of just the selected one.

Closes #22454

Release Notes:

- Improved the `RemoveFromProject` action to remove all selected items.
2025-01-07 17:25:33 +00:00
Marshall Bowers
3d8625f25c assistant2: Store deduped context on the Thread (#22781)
This PR is a small refactoring in advance of some other changes.

Previously we were storing the whole `Context` associated with each
message. However, it's likely that multiple messages may end up using
the same context.

We now store the deduped context in a separate collection and refer to
it from each message by its `ContextId`.

Release Notes:

- N/A
2025-01-07 17:21:39 +00:00
Piotr Osiewicz
f53a17b044 chore: Add missing test-support features to terminal_view and image_viewer (#22782)
Release Notes:

- N/A
2025-01-07 17:19:22 +00:00
Peter Tripp
57dfaa63ca emacs: Fix using emacs in embedded terminal (#22779)
- Follow-up to: https://github.com/zed-industries/zed/pull/22590

Release Notes:

- N/A
2025-01-07 17:04:54 +00:00
0x2CA
4deab8a0b9 vim: Add Separator and RemoveIndent in Join Lines, fix gJ use space join (#22496)
Closes #22492

Release Notes:

- Added Join Lines Separator And RemoveIndent

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-01-07 17:04:49 +00:00
Danilo Leal
677868ba1a Add toolbar spacing and alignment improvements (#22771)
Tackles some of the points here:
https://github.com/zed-industries/zed/issues/22673. However, this is not
doing anything yet to treat misalignment when with odd-number UI font
sizes. Here are some screenshots with a theme that makes easier to spot
them. It's subtle:

| Before | After |
|--------|--------|
| <img width="1313" alt="Screenshot 2025-01-07 at 10 23 31 AM"
src="https://github.com/user-attachments/assets/fdf125a7-ef1c-4368-aea8-579f916b9c34"
/> | <img width="1313" alt="Screenshot 2025-01-07 at 10 26 11 AM"
src="https://github.com/user-attachments/assets/9728fd47-3c17-4c42-9cf6-11083eb32980"
/> |
| <img width="1313" alt="Screenshot 2025-01-07 at 10 23 36 AM"
src="https://github.com/user-attachments/assets/dc2010e9-4ae4-451c-afd1-6bd13750dc66"
/> | <img width="1313" alt="Screenshot 2025-01-07 at 10 26 08 AM"
src="https://github.com/user-attachments/assets/a71ef2ef-3ac7-4b0a-8d50-1c3c4f17d5cb"
/> |

Release Notes:

- N/A
2025-01-07 16:07:25 +00:00
Danilo Leal
6af9e8ded8 assistant2: Fix toolbar layout shift (#22770)
Note how, previously, switching between the thread view and the history
caused a slightly reduction of the toolbar height. Super subtle stuff,
but doesn't happen anymore.

### Before


https://github.com/user-attachments/assets/712ff34e-a638-484d-8415-16011b10ae63

### After


https://github.com/user-attachments/assets/7ccff7a3-45a4-445c-9638-8445733e0ffc

Release Notes:

- N/A
2025-01-07 16:04:54 +00:00
Danilo Leal
f439ee0d55 assistant2: Add check icon for included context (#22774)
Quick follow-up to: https://github.com/zed-industries/zed/pull/22712 —
just to make it more visually easier to understand.

<img width="800" alt="Screenshot 2025-01-07 at 11 48 06 AM"
src="https://github.com/user-attachments/assets/92f0523b-eb85-4929-a825-2e1e524b3ad7"
/>

Release Notes:

- N/A
2025-01-07 16:03:05 +00:00
Conrad Irwin
44c492b3c0 Fix panic in vim text-objects (#22753)
Caused by messing up offsets between multi-buffers and excerpts :(

Fixes #22739

Release Notes:

- Fixed a panic in vim text objects in multibuffers
2025-01-07 15:55:25 +00:00
Cole Miller
0a8e9c0fe2 Use a temporary fork of oo7 (#22751)
Release Notes:

- N/A
2025-01-07 15:00:11 +00:00
Antonio Scandurra
aa0eaea4e9 Double max event count for zeta (#22772)
Release Notes:

- N/A

Co-authored-by: Thorsten <thorsten@zed.dev>
2025-01-07 14:42:19 +00:00
Thorsten Ball
fb272c0edc ssh remoting: Improve error message if netcat is missing (#22767)
Closes #22752

Release Notes:

- N/A
2025-01-07 13:16:52 +00:00
Agus Zubiaga
bcc6d95529 assistant2: Make context pill names work like editor tabs (#22741)
Context pills for files will now only display the basename of the file.
If two files have the same base name, we will also show their parent
directories, mimicking the behavior of editor tabs.


https://github.com/user-attachments/assets/ee88ee3b-80ff-4115-9ff9-8fe4845a67d8

Note: The double `/` in the file picker is a known separate issue.

Release Notes:

- N/A

---------

Co-authored-by: Danilo <danilo@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-01-07 12:18:46 +00:00
Kirill Bulatov
a827f54022 Reduce amount of workspace serialization happening (#22730)
Part of https://github.com/zed-industries/zed/issues/16472

Reduces amount of workspace serialization happening by:
* fixing the deserialization logic: now it does not set panels that are
hidden to active
* cleaning up `active_panel_index` for docks that are closed, to avoid
emitting extra events for such closed docks
* adjusting outline panel to drop active editor subscriptions on
deactivation — this way, `cx.observe` on the dock with outline panel is
not triggered (used to be triggered on every selection change before)
* adjusting workspace dock drag listener to remember previous
coordinates and only resize the dock if those had changed
* adjusting workspace pane event listener to ignore
`pane::Event::UserSavedItem` and `pane::Event::ChangeItemTitle` that
seem to happen relatively frequently but not influence values that are
serialized for the workspace
* not using `cx.observe` on docks, instead explicitly serializing on
panel zoom and size changes

Release Notes:

- Reduced amount of workspace serialization happening
2025-01-07 11:00:26 +00:00
Julius de Boer
4c47728d6f Set TERM env variable inside the terminal (#22615)
Closes #17991 

Release Notes:

- Set the `TERM` environment variable inside the terminal

Currently the terminal inherits the `TERM` variable from the parent
process. However this can cause issues with programs that rely on this
variable to make sure certain features are present. For example not
supporting backspaces making the terminal almost unusable.
2025-01-07 08:58:15 +00:00
Michael Sloan
e56b692036 Make expand excerpt apply to all excerpts in selections (#22748)
Closes #22720

Release Notes:

- `ExpandExcerpts` (`shift+enter` by default) now expands all excerpts
that have selected text, rather than just excerpts that contain the end
of a selection.
2025-01-07 04:20:32 +00:00
Cole Miller
810b37c129 Rename the OpenFile action to OpenSelectedFilename to better reflect its function (#22494)
Release Notes:

- Renamed the `OpenFile` action to `OpenSelectedFilename` for clarity
2025-01-07 04:18:04 +00:00
wuliuqii
2ba91609c9 Fix nix shell (#22091)
Add back necessary packages for linux user from
https://github.com/zed-industries/zed/pull/21075/files#diff-dd972f906c9914eb70fae1db9cf66baa653e6b643bbdedeaa0070939abc3fb56L20-L22

Release Notes: 

- N/A
2025-01-07 04:03:22 +00:00
spotikhanov
410b4bded1 Show error alert when there's an error opening file with native OS picker (#22671)
Closes #20814 by showing error alert if there's some error after OS
native File -> Open



https://github.com/user-attachments/assets/ce092831-4b55-4e20-8ffa-8e60eaf6364d



The implementation here is the same as in handle_external_paths_drop
function (when users uses drag and drop to open the file):
de08e47e5b/crates/workspace/src/pane.rs (L2810)

Release Notes:

- Added an error alert when there's an error opening file with native OS
picker.
2025-01-07 04:00:20 +00:00
Jason Lee
dc0075b8e6 Fix empty title in Recent Projects (#21952)
Close #13595 

Release Notes:

- Fixed empty title in Recent Projects.

---

| Before | After |
| --- | --- |
| <img width="695" alt="SCR-20241213-nzxr"
src="https://github.com/user-attachments/assets/f19a0bad-d542-44cd-85c1-89386d396f27"
/> | <img width="625" alt="image"
src="https://github.com/user-attachments/assets/0d2afef7-4cd2-43eb-9046-c169df2eb8a0"
/> |

This is because the `LocalPathsOrder` get empty list.

```
[crates/recent_projects/src/recent_projects.rs:385:9] &location = Local(
    LocalPaths(
        [
            "/Users/jason/Library/Application Support/Zed/prettier/node_modules",
        ],
    ),
    LocalPathsOrder(
        [],
    ),
)
[crates/recent_projects/src/recent_projects.rs:386:9] &paths = [
    "~/Library/Application Support/Zed/prettier/node_modules",
]
[crates/recent_projects/src/recent_projects.rs:385:9] &location = Local(
    LocalPaths(
        [
            "/Users/jason/github/tree-sitter-csv",
        ],
    ),
    LocalPathsOrder(
        [],
    ),
)
[crates/recent_projects/src/recent_projects.rs:386:9] &paths = [
    "~/github/tree-sitter-csv",
]
[crates/recent_projects/src/recent_projects.rs:385:9] &location = Local(
    LocalPaths(
        [
            "/Users/jason/work/autocorrect/autocorrect-website/dist",
        ],
    ),
    LocalPathsOrder(
        [],
    ),
)
```
2025-01-07 03:45:38 +00:00
renovate[bot]
e08eba8129 Update Rust crate tree-sitter-python to v0.23.6 (#22557)
This PR contains the following updates:

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

---

### Release Notes

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

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

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-python/compare/v0.23.5...v0.23.6)

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

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 03:24:57 +00:00
Conrad Irwin
92b9d80549 Silence unnecessary log (#22750)
It is expected that an unsaved buffer would have no
file.

Release Notes:

- N/A
2025-01-07 03:21:22 +00:00
tims
6fce53651b project_panel: Refine selection, copying, and deletion behavior (#22658)
Closes #22655

A more detailed write-up for this change can be found in the issue
itself. Here, I'm just providing previews after the change. The preview
before the change can be found in the attached issue.

1. While selecting multiple entries, the last clicked entry should be
selected.


[a.webm](https://github.com/user-attachments/assets/2add69c3-82a9-4e45-92e8-366aaf9b298a)

2. When holding `Ctrl`/`Cmd` on an entry, there should be clear visual
feedback to indicate whether the entry is selected or marked.


[b.webm](https://github.com/user-attachments/assets/2cefb8aa-e7d0-4929-9efa-89a4329f428b)


3. When only one entry is marked, but it’s different from the selection,
operations should prioritize the selected entry. This let's you do quick
one-off actions without disrupting the marked state.


[c.webm](https://github.com/user-attachments/assets/8e7ae0c0-4387-49b9-9761-5d02a1c21a84)


4. When more than one entries are marked, operations should prioritize
the marked entries. If the selection differs from the marked entries, it
should not interfere with operations on the marked entries. This let's
you do actions on multiple marked entries without needing to adjust the
selection.


[d.webm](https://github.com/user-attachments/assets/165a74be-cbe9-48ac-b558-2562485ea224)

Release Notes:

- Improved project panel selection, copying, and deletion behavior, to
be more predictable.
2025-01-07 00:46:50 +00:00
0x2CA
e7ca39dfe9 vim: Fix VisualYankLine (#22416)
Closes #22388

Release Notes:

- Fixed Visual Mode Use `Y` Yank Line

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-01-07 00:15:19 +00:00
tims
7d0c571a8f linux: Prevent target file from being trashed when trashing symlink (#22704)
Closes #22399

Currently, the target file is being trashed when trashing a symlink, and
the symlink remains intact. Symlinks are not handled separately yet, so
when `open` is used on a symlink, it gets resolved to the target file.

To fix this, we can get the file descriptor of the symlink by passing
`libc::O_PATH | libc::O_NOFOLLOW` flags to `open`, and then pass this
file descriptor to the existing `trash::trash_file` from `ashpd`.
However, this would result in an error because `ashpd` currently does
not support trashing symlink files. I have created an issue for it here:
[https://github.com/bilelmoussaoui/ashpd/issues/255](https://github.com/bilelmoussaoui/ashpd/issues/255).

For the time being, this PR partially fixes the issue by removing the
symlink without trashing so that the target file won't be affected. Once
the upstream bug is fixed, we can switch this remove action back to
trashing.

Release Notes:

- Fixed target file from being trashed when trashing symlink on Linux.
2025-01-07 00:13:16 +00:00
tims
d2d1779e0d linux: Add keyboard_layout and on_keyboard_layout_change support (#22736)
No issue, as the functionality is currently not being used in Zed. This
is more of a GPUI improvement.

Currently, `keyboard_layout` and `on_keyboard_layout_change` are already
handled on macOS. This PR implements the same for X11 and Wayland.

Linux supports up to 4 keyboard layout groups (e.g., Group 0: English
US, Group 1: Bulgarian, etc). On X11 and Wayland, `event` provides a new
active group, which maps to the `layout_index`. We already store keymap
state from where we can get the current `layout_index`. By comparing
them, we determine if the layout has changed.

X11:
<img
src="https://github.com/user-attachments/assets/b528db77-1ff2-4f17-aac5-7654837edeb9"
alt="x11" width="300px" />

Wayland:
<img
src="https://github.com/user-attachments/assets/2b4e2a30-b0f4-495c-96bb-7bca41365d56"
alt="wayland" width="300px" />

Release Notes:

- N/A
2025-01-07 00:10:00 +00:00
Arseny Kapoulkine
76d18f3cd2 Disable inline completions in Vim normal mode (#22439)
This is harmful for user experience and at best requires a user setting.
This was committed as part of
https://github.com/zed-industries/zed/pull/21739 however that change had
no relevant release notes and no relevant settings.

The issue #22343 shows how this can result in user experience
regression: deleting a text fragment can reinsert it back, and it's thus
unclear if the deletion has even worked. Maybe this can be reenabled in
some very restrictive setup, and put behind a setting, but it can't be
unconditional. Completions should activate when the user signals intent
of entering code - for example, if instead of `de` to delete a fragment,
I press `ce` to replace it, I would naturally expect inline completions
to show up.

Note: The linked PR added more code in vim crate to refresh inline
completions in normal mode. I'm keeping that code around in this commit,
so that this can be the minimal fix to the linked issue -- with the
assumption that maybe there's some way in the future to reenable this in
a subset of cases that don't result in confusing / broken UX. If that is
not true the code might need further cleanup. Let me know if you'd
rather see removal of those changes in this PR as well.

Closes #22343.

Release Notes:

- Fixes inline completions showing up in Vim normal mode.
2025-01-06 23:38:46 +00:00
uncenter
7fa30f411d html: Use @attribute highlight capture for HTML attributes (#20752)
`@attribute` is the very first query on the
https://zed.dev/docs/extensions/languages#syntax-highlighting captures
list, we should be using it! This PR changes the highlights queries for
HTML to use the `@attribute` capture instead of the `@property` capture
for `attribute_name` nodes.

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-06 23:17:42 +00:00
Conrad Irwin
7075f34b47 Fix uploading ssh binaries when ssh cds (#22744)
The code we have assumes that when you run commands over ssh they run
in your home directory. This was not true in some cases, and broke SSH
remoting if you had `upload_binary_over_ssh` set.

To reproduce this use Coder and set the `dir` parameter.

Release Notes:

- Fixed SSH remoting in the case that ssh defaults to a non-$HOME
directory.
2025-01-06 23:04:49 +00:00
Marshall Bowers
847596af6e assistant2: Expand some variable names (#22742)
This PR expands some variables names in the `ThreadStore` for better
readability.

Release Notes:

- N/A
2025-01-06 23:02:51 +00:00
David Baldwin
e36ae0465c elixir: Add textobjects queries (#22055)
Release Notes:

- N/A

Adds textobjects queries for Elixir.

These queries were originally pulled directly from the
[nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects)
repo with [this
textobjects.scm](https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter-textobjects/refs/heads/master/queries/elixir/textobjects.scm)
file, but have been heavily edited for Zed.
2025-01-06 23:01:19 +00:00
Marshall Bowers
5f7de2eb5d assistant2: Clear all collections when clearing the ThreadStore (#22743)
This PR adds some missing calls to clear the sub-collections in the
`ThreadStore` when we call `ThreadStore::drain` or `ThreadStore::clear`.

Release Notes:

- N/A
2025-01-06 23:00:13 +00:00
Mikayla Maki
3c430af31a Temporarily revert git panel diff editor feature (#22733)
The existing code was causing us to constantly re-scan files when
anything changed in the project. Temporarily revert this as we're about
to rework this entire UI with the new primitives.

follow up to https://github.com/zed-industries/zed/pull/22329

Release Notes:

- N/A

---------

Co-authored-by: Cole <cole@zed.dev>
2025-01-06 22:09:05 +00:00
Torrat
5ec924828e go: Adjust gopls path based on OS (#22727)
Based on the python https://github.com/zed-industries/zed/issues/21452
and PR https://github.com/zed-industries/zed/pull/22587

I found the same problem with go on windows.

Describe the bug / provide steps to reproduce it

Language server error: gopls

The system cannot find the file specified. (os error 2)
-- stderr--

[ERROR project::lsp_store] Failed to start language server "gopls": The
system cannot find the file specified. (os error 2)
[ERROR project::lsp_store] server stderr: ""

Environment

    Windows 11
    Go

Release Notes:

- Windows: Fixed `gopls` path construction on Windows 11.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-06 22:04:10 +00:00
Ross Timson
c968225b61 Add Emacs keybindings to open new/close windows and quit Zed (#22629)
These are more closely like default Emacs bindings.

I hope such small and minor changes didn't warrant a discussion in
advance.

Release Notes:

- Added Emacs bindings for creating a new window, closing a window, and
quitting zed entirely.
2025-01-06 22:02:54 +00:00
Michael Sloan
141393232e Add validation in LspCommand::to_lsp + check for inverted ranges (#22731)
#22690 logged errors and flipped the range in this case. Instead it
brings more visibility to the issue to return errors.

Release Notes:

- N/A
2025-01-06 22:00:36 +00:00
Peter Tripp
1c223d8940 Ensure project search keyboard shortcut tooltip is displayed (#22717)
Part of: https://github.com/zed-industries/zed/issues/22673

Before/After:
<img width="212" alt="Screenshot 2025-01-06 at 12 17 52"
src="https://github.com/user-attachments/assets/8eef7c5e-ccc7-4946-be19-f10dcd5f957d"
/><img width="211" alt="Screenshot 2025-01-06 at 12 17 42"
src="https://github.com/user-attachments/assets/8612b1b5-139d-422f-9457-ce399814d641"
/>


Release Notes:

- N/A
2025-01-06 21:42:47 +00:00
Peter Tripp
80acecc91a Improve R install docs (#22737)
Release Notes:

- N/A
2025-01-06 21:42:16 +00:00
Justin Simon
bbce1c19d1 Add compile_commands.json documentation for C/C++ (#22639)
Added documentation explaining that clangd requires
`compile_commands.json` for proper functionality in both C and C++
projects. Includes instructions for generating the file using CMake.

This is related to
https://github.com/zed-industries/zed/discussions/6480

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-01-06 21:42:00 +00:00
Richard Feldman
2856d0a661 Fix inline assist layout issues related to screen size (#22732)
## Before


https://github.com/user-attachments/assets/c84f15d2-5643-46f2-9eb6-f0234c563c01

## After


https://github.com/user-attachments/assets/d4eab08a-1bd5-442c-9663-34bb512dba4b


Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
2025-01-06 21:40:36 +00:00
volt
799e81ffe5 google_ai: Add Gemini 2.0 Flash support (#22665)
Release Notes:

- Added support for Google's Gemini 2.0 Flash experimental model.

Note:

Weirdly enough the model is slow on small talk responses like 'hi' (in
my tests) but very fast on things that need more tokens like 'write me a
snake game in python'. Likely an API problem.

TESTED ONLY ON WINDOWS! Would test further but don't have Linux
installed and don't have an Mac. Will likely work everywhere.

Why?:

I think Gemini 2.0 Flash is incredibly good model at coding and
following instructions. I think it would be nice to have it in the
editor. I did as minimal changes as possible while adding the model and
streaming validation. I think it's worth merging the commits as they
bring good improvements.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-06 21:28:31 +00:00
Peter Tripp
0d30bda740 Rename livekit_client_macos test_app to suppress warnings (#22719)
Current copy/paste conflict between crates triggers warnings during test
runs.
 
<img width="1492" alt="Screenshot 2025-01-06 at 12 41 11"
src="https://github.com/user-attachments/assets/ea7f90ec-bef8-482a-a954-6d5c41b9fd7e"
/>

Release Notes:

- N/A
2025-01-06 19:17:53 +00:00
Mikayla Maki
ec2506b2e7 Fix a bug where repositories were always being marked as changed (#22725)
Release Notes:

- N/A

Co-authored-by: cole <cole@zed.dev>
2025-01-06 19:03:15 +00:00
Agus Zubiaga
3a061a91e7 assistant2: Do not allow a context entry to be added multiple times (#22712)
https://github.com/user-attachments/assets/81674c88-031b-4d55-b362-43819492b93d


Release Notes:

- N/A
2025-01-06 18:55:20 +00:00
Danilo Leal
c74e5f5de2 assistant2: Render placeholder thread title until summary is generated (#22723)
This PR ensures we render a "New Thread" placeholder title until a
message has been sent, and thus, a summary is generated.


https://github.com/user-attachments/assets/1c30e0ff-baaa-44ad-a1a2-42f1ce9fe0b0

Release Notes:

- N/A
2025-01-06 18:53:38 +00:00
Marshall Bowers
2023c43681 ci: Add logging to docs-only change detection (#22724)
This PR adds some logging to the docs-only change detection, for better
auditability.

Release Notes:

- N/A
2025-01-06 18:43:07 +00:00
spotikhanov
84fdcbbe7d Document git.gutter_debounce setting (#22663)
Closes #22588 by providing documentation to git.gutter_debounce setting

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-06 18:33:04 +00:00
Michael Sloan
2dec4c2c91 Use info popovers instead of diagnostic for invisible char hover (#22701)
This will allow the diagnostic popover to be displayed even if hovering
an invisible char.

Beyond that, it solves a rare `DiagnosticPopover` corner case:

* Supports moving the selection to a diagnostic when `GoToDiagnostic` is
done while hovering, based on `group_id`

* Provides Diagnostic values with `group_id: 0` providing information on
hover about invisible characters.

So, `GoToDiagnostic` would navigate to the very first error produced by
a language server. Really not a big deal of course.

Release Notes:

- N/A
2025-01-06 17:44:26 +00:00
Nate Butler
d02bfe1e95 Add a case for shadows when blur_radius = 0 (#22441)
Closes #22433

Before/After (macOS):

![CleanShot 2024-12-26 at 22 41
11@2x](https://github.com/user-attachments/assets/1701da2e-3db7-4dd1-a680-0f63824cbdf5)

For some reason the non-blurred one seems much lower quality, so we may
need to tinker with the samples, or something else.

![CleanShot 2024-12-26 at 22 42
12@2x](https://github.com/user-attachments/assets/5a43330d-137b-4d45-a67a-fd10ef6a8ff8)

I'm unsure if this is a problem on Linux/in the Blade renderer, but
since no changes were made outside of the medal shaders we can probably
take this macOS-specific win for now.

Release Notes:

- gpui: Fixed an issue where shadows with a `blur_radius` of 0 would not
render.
2025-01-06 17:27:20 +00:00
Marshall Bowers
154a3915a6 zed: Add timeouts for feature flag resolution in workspace panel initialization (#22715)
This PR adds timeouts when resolving feature flags during workspace
panel initialization so that we don't block indefinitely if Zed is not
connected to the internet.

Right now we wait for 5 seconds, but this value was chosen arbitrarily.

Release Notes:

- N/A
2025-01-06 17:01:16 +00:00
Peter Tripp
0f548c6add Make show project panel keyboard shortcut work in more places (#22713)
- Closes: https://github.com/zed-industries/zed/issues/22699
- Refine the key binding for `cmd-shift-e` (macOS) / `ctrl-shift-e`
(linux)
- Now Works after closing the final buffer
- Now Works from other panels (Terminal/Assistant/Collab/Chat/etc)

Follow-up to:
- https://github.com/zed-industries/zed/pull/21228

Release Notes:

- Fixed Project Panel toggle (`cmd-shift-e` / `ctrl-shift-e`) so it
works in more contexts.
2025-01-06 16:34:44 +00:00
Danilo Leal
033726cf87 Improve diagnostics multibuffer design (#22705)
Namely, just removing the unnecessary extra line dividers and adding a
super subtle background color to the diagnostic message to create a bit
of separation/hierarchy.

<img width="800" alt="Screenshot 2025-01-04 at 9 46 03 PM"
src="https://github.com/user-attachments/assets/d62883b9-ed76-4fbb-b9c1-b55146eaeed4"
/>

Release Notes:

- N/A
2025-01-06 14:58:12 +00:00
Michael Sloan
d83f1e8f8f Revert "Start diagnostic group_id at 1 to handle non LS diagnostics (#22694) (#22700)
This reverts commit 3ae6aa0e4d.

If "group_id = 0" really did mean a diagnostic not from a language
server then various methods related to diagnostic set would need to be
updated. Something like [this
diff](https://gist.github.com/mgsloan/e902153bcaec207b39260a8f40d3134d).

Plan instead is to use InfoPopover instead of DiagnosticPopover for
these.

Release Notes:

- N/A
2025-01-06 07:03:01 +00:00
Michael Sloan
1ef638d802 Remove unnecessary lifetimes on Buffer::diagnostic_group (#22698)
Release Notes:

- N/A
2025-01-06 06:12:35 +00:00
Michael Sloan
fcb03989d2 Remove unnecessary finding of primary diagnostic for diagnostic hover (#22697)
No need to find or store the primary range ahead of time as it's found
by `activate_diagnostics`.

Not entirely sure we should still even have the special case when the
popover is visible. It does support the keyboard interaction of opening
hover followed by jumping to the primary position, but that seems pretty
undiscoverable.

Support for clicking the hover to navigate to the primary diagnostic was
removed in https://github.com/zed-industries/zed/pull/3408

Release Notes:

- N/A
2025-01-06 06:08:17 +00:00
Burak Varlı
2a9fa0e2dc Ensure end >= start in lsp::Range (#22690)
Should resolve https://github.com/zed-industries/zed/issues/21714.

In some conditions that I'm not sure of, Zed sends LSP requests with
`start > end` position, and zls has an [assertion for end >=
start](f253553b82/src/offsets.zig (L492)),
and that causes zls to crash, like:

```bash
# first `textDocument/inlayHint` request with `end >= start`
[2025-01-05T19:33:09+00:00 TRACE lsp] outgoing message:{"jsonrpc":"2.0","id":1043,"method":"textDocument/inlayHint","params":{"textDocument":{"uri":"file:///Users/burak/Code/parzig/src/parquet/decoding.zig"},"range":{"start":{"line":0,"character":0},"end":{"line":24,"character":0}}}}
# successful response 
[2025-01-05T19:33:09+00:00 TRACE lsp::input_handler] incoming message: {"jsonrpc":"2.0","id":1043,"result":[{"position":{"line":0,"character":9},"label":": type","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":1,"character":22},"label":": type","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":4,"character":13},"label":": [](unknown type)","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":4,"character":30},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":4,"character":33},"label":"n:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nusize\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":5,"character":23},"label":": bool","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":6,"character":19},"label":": usize","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":9,"character":26},"label":": [](unknown type)","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":9,"character":43},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":9,"character":47},"label":"n:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nusize\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":21,"character":13},"label":": [](unknown type)","kind":1,"paddingLeft":false,"paddingRight":false},{"position":{"line":21,"character":30},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":21,"character":33},"label":"n:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nusize\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":33},"label":"T:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\ncomptime type\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":36},"label":"buf:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\n[]T\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":41},"label":"bit_width:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nu8\n```"},"paddingLeft":false,"paddingRight":true},{"position":{"line":22,"character":52},"label":"reader:","kind":2,"tooltip":{"kind":"markdown","value":"```zig\nanytype\n```"},"paddingLeft":false,"paddingRight":true}]}
[2025-01-05T19:33:09+00:00 TRACE lsp] Took 14.855ms to receive response to "textDocument/inlayHint" id 1043
# problematic `textDocument/inlayHint` request with `start > end`
[2025-01-05T19:33:09+00:00 TRACE lsp] outgoing message:{"jsonrpc":"2.0","id":1044,"method":"textDocument/inlayHint","params":{"textDocument":{"uri":"file:///Users/burak/Code/parzig/src/parquet/decoding.zig"},"range":{"start":{"line":50,"character":25},"end":{"line":25,"character":0}}}}
# zls crashes here, and after this point, all LSP requests fail
[2025-01-05T19:33:09+00:00 TRACE lsp] incoming stderr message:thread 5391652 panic: reached unreachable code
[2025-01-05T19:33:09+00:00 ERROR lsp] cannot read LSP message headers
```

In LSP specification for
[`Range`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#range)
type, it says:
> ... If you want to specify a range that contains a line including the
line ending character(s) then use an end position denoting the start of
the next line.

I feel like zls's assertion is sensible, so I've updated the generic
`range_to_lsp` function rather than doing something specific to zls. But
let me know if this seems incorrect.

zls was crashing after 5-10 minutes of working with a Zig codebase
before, and after this change, I tested for an hour and didn't
experience any crashes.

Release Notes:

- Ensure `end >= start` in `lsp::Range`, which should fix Zig/zls
crashes.

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
2025-01-06 05:22:28 +00:00
Michael Sloan
3ae6aa0e4d Start diagnostic group_id at 1 to handle non LS diagnostics (#22694)
In particular, `DiagnosticPopover` both:

* Supports moving the selection to a diagnostic when clicked, based on
`group_id`

* Provides Diagnostic values with `group_id: 0` providing informztion on
hover about invisible characters.

So, clicking such a popover would navigate to the very first error
produced by a language server. Really not a big deal of course, but
seems good to fix as it might result in surprising behavior in other
future circumstances

Release Notes:

- N/A
2025-01-06 05:18:56 +00:00
Michael Sloan
7506c0385b Fix a doc comment typo on DiagnosticEntry::to_lsp_diagnostic_stub (#22695)
Release Notes:

- N/A
2025-01-06 04:32:30 +00:00
Michael Sloan
570e6c80a8 Fix panic on diagnostic hover (#22693)
In #22620 `diagnostic_group` was modified to return results for
multibuffers, but was returning singleton buffer points. `hover_popover`
uses it to find the jump target for clicking the popup - which doesn't
seem to be working right now but that's a separate issue. Now that
`diagnostic_group` is returning values in multibuffers converting these
to anchors was crashing.

Also resolves a potential bug - if folding in multibuffers was supported
then "Go To Diagnostics" would not properly skip diagnostics from folded
regions.

Release Notes:

- N/A
2025-01-06 02:31:02 +00:00
tims
94ee2e1811 Fix ghost files appearing in the project panel when clicking relative paths in the terminal (#22688)
Closes #15705

When opening a file from the terminal, if the file path is relative, we
attempt to guess all possible paths where the file could be. This
involves generating paths for each worktree, the current terminal
directory, etc. For example, if we have two worktrees, `dotfiles` and
`example`, and `foo.txt` in `example/a`, the generated paths might look
like this:

- `/home/tims/dotfiles/../example/a/foo.txt` from the `dotfiles`
worktree
- `/home/tims/example/../example/a/foo.txt` from the `example` worktree
- `/home/tims/example/a/foo.txt` from the current terminal directory
(This is already canonicalized)

Note that there should only be a single path, but multiple paths are
created due to missing canonicalization.

Later, when opening these paths, the worktree prefix is stripped, and
the remaining path is used to open the file in its respective worktree.

As a result, the above three paths would resolve like this:

- `../example/a/foo.txt` as the filename in the `dotfiles` worktree
(Ghost file)
- `../example/a/foo.txt` as the filename in the `example` worktree
(Ghost file)
- `foo.txt` as the filename in the `a` directory of the `example`
worktree (This opens the file)

This PR fixes the issue by canonicalizing these paths before adding them
to the HashSet.

Before:

![before](https://github.com/user-attachments/assets/7cb98b86-1adf-462f-bcc6-9bff6a8425cd)

After:

![after](https://github.com/user-attachments/assets/44568167-2a5a-4022-ba98-b359d2c6e56b)


Release Notes:

- Fixed ghost files appearing in the project panel when clicking
relative paths in the terminal.
2025-01-05 21:49:32 +00:00
Piotr Osiewicz
299ae92ffb gpui: Do not derive serde::Deserialize for automatically generated Actions (#22687)
Closes #ISSUE

Release Notes:

- N/A
2025-01-05 17:25:00 +00:00
Cole Miller
de08e47e5b Improve panic report with reentrant SlotMap use (#22667)
`double_lease_panic` already does what we want, just extend it to the
indexing operation as well.

Release Notes:

- N/A
2025-01-04 20:37:40 +00:00
Kirill Bulatov
8151dc7696 Return back Rust completion details (#22648)
Closes https://github.com/zed-industries/zed/issues/22642

In Zed, Rust's label generators expected the details to come in ` (use
std.foo.Bar)` form, but recently, r-a started to send these details
without the leading whitespace which broke the code generation.

The PR makes LSP results parsing more lenient to work with both details'
forms.

Release Notes:

- Fixed Rust completion labels not showing the imports
2025-01-04 11:15:09 +00:00
Michael Sloan
5f1eee3c66 Fix inlay hints display reverting to settings value on theme change (#22605)
Closes #4276

Release Notes:

- Fixed inlay hints that have been manually enabled disappearing when
theme selector is used.
2025-01-04 08:26:08 +00:00
Mikayla Maki
9613084f59 Move git status out of Entry (#22224)
- [x] Rewrite worktree git handling
- [x] Fix tests
- [x] Fix `test_propagate_statuses_for_repos_under_project`
- [x] Replace `WorkDirectoryEntry` with `WorkDirectory` in
`RepositoryEntry`
- [x] Add a worktree event for capturing git status changes
- [x] Confirm that the local repositories are correctly updating the new
WorkDirectory field
- [x] Implement the git statuses query as a join when pulling entries
out of worktree
- [x] Use this new join to implement the project panel and outline
panel.
- [x] Synchronize git statuses over the wire for collab and remote dev
(use the existing `worktree_repository_statuses` table, adjust as
needed)
- [x] Only send changed statuses to collab

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.com>
Co-authored-by: Nathan <nathan@zed.dev>
2025-01-04 01:00:16 +00:00
Jason Lee
72057e5716 cli: Fix support for older macOS versions (#22515)
Close #22419

Release Notes:

- Fixed `zed` cli crash by `ScreenCaptureKit` library not loaded error
on macOS lower versions.

<img width="843" alt="image"
src="https://github.com/user-attachments/assets/9e0b615e-933f-4808-bf20-3e37e9e8bc6d"
/>

--- 

The main reason is the `cli` depends on `release_channel`, and it
depends on `gpui`.

```
$ cargo tree -p cli
├── release_channel v0.1.0 (/Users/jason/github/zed/crates/release_channel)
│   └── gpui v0.1.0 (/Users/jason/github/zed/crates/gpui)
│       ├── anyhow v1.0.95
│       ├── async-task v4.7.1
│       ├── block v0.1.6
│       ├── cocoa v0.26.0
```
2025-01-04 00:42:37 +00:00
tims
e25789893d linux: Fix issue where relative symlinks were not being watched using fs watch (#22608)
Closes #22607

Symlinks can be absolute or relative. When using
[stow](https://www.gnu.org/software/stow/) to manage dotfiles, it
creates relative symlinks to the target files.

For example:  

- Original file:  `/home/tims/dotfiles/zed/setting.json`  
- Symlink path: `/home/tims/.config/zed/setting.json`  
- Target path (relative to symlink): `../../dotfiles/zed/setting.json`  

The issue is that you can’t watch the symlink path because it’s relative
and doesn't include the base path it is relative to. This PR fixes that
by converting relative symlink paths to absolute paths.

- Absolute path (after parent join):
`/home/tims/.config/zed/../../dotfiles/zed/setting.json` (This works)
- Canonicalized path (from absolute path):
`/home/tims/dotfiles/zed/setting.json` (This works too, just more
cleaner)

Release Notes:

- Fix issue where items on the Welcome page could not be toggled on
Linux when using Stow to manage dotfiles
2025-01-04 00:12:20 +00:00
tims
b46b261f11 linux: Fix process PID to window mapping for X11 (#22348)
Closes #22326

This PR adds process PID information to window created by X11, so that
window manager can identify which process this window belongs to.
Without this property, the window manager would have no reliable way to
know which process created this window.

In original issue, `robotgo` throws error on `x, y, w, h :=
robotgo.GetBounds(pid)` this method. If we go deeper into the source
code of `robotgo`, it calls `GetXidFromPid` which goes through all
windows, and tries to check for provided pid. Hence, when it tries to do
that for Zed, it fails and returns `0, err` to caller.

```go
// Robotgo source code trying to look through all windows and query pid

// GetXidFromPid get the xid from pid
func GetXidFromPid(xu *xgbutil.XUtil, pid int) (xproto.Window, error) {
	windows, err := ewmh.ClientListGet(xu)
	if err != nil {
		return 0, err
	}

	for _, window := range windows {
		wmPid, err := ewmh.WmPidGet(xu, window)
		if err != nil {
			return 0, err
		}

		if uint(pid) == wmPid {
			return window, nil
		}
	}

	return 0, errors.New("failed to find a window with a matching pid.")
}
```

Querying for pid for active Zed window:

Before:
```sh
tims@lemon ~/w/go-repro [127]> xprop -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4e00002
tims@lemon ~/w/go-repro> xprop -id 0x4e00002 _NET_WM_PID
_NET_WM_PID:  not found.
```

After:
```sh
tims@lemon ~/w/go-repro> xprop -root _NET_ACTIVE_WINDOW
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4e00002
tims@lemon ~/w/go-repro> xprop -id 0x4e00002 _NET_WM_PID
_NET_WM_PID(CARDINAL) = 103548
tims@lemon ~/w/go-repro>
```

Correct zed process PID (below) assosiated with zed window (shown
above):

![image](https://github.com/user-attachments/assets/8b40128b-addb-4c88-944e-b1d26b908bf5)

Release Notes:

- Fix `robotgo` failing when Zed window is open on Linux
2025-01-04 00:10:36 +00:00
tims
71a0eb3b13 windows: Fix cursor style not changing when hovering over items in the title bar (#22580)
Closes #22578

Currently, the `hovered` boolean in the window state is only updated by
the `WM_MOUSELEAVE` event, which fires when the mouse cursor leaves the
window's working area. This means that when the user moves the cursor
from the window to the title bar, `hovered` is set to `false`. Later in
the code, this flag is used to determine the cursor style and check if
the cursor is over the correct window.

The `hovered` boolean should remain active even when the mouse is over
non-client items, such as the title bar or window borders. This PR fixes
that by using `WM_NCMOUSELEAVE` event, which is triggered when the mouse
leaves non-client items. This event is used to update the `hovered`
boolean accordingly.

Now, `hovered` is `true` when the mouse is over the window's working
area, as well as non-client areas like the title bar.

More context:

- Existing: `dwFlags: TME_LEAVE` tracks window area mouse leaves, which
is used in `handle_mouse_move_msg` func.
- New: `dwFlags: TME_LEAVE | TME_NONCLIENT` tracks non-client mouse
leaves, which is used in `handle_nc_mouse_move_msg` func.

Preview:


https://github.com/user-attachments/assets/b319303f-81b9-45cb-bf0c-535a59b96561

Release Notes:

- Fix cursor style not changing on hover over items in the title bar on
Windows
2025-01-04 00:01:29 +00:00
Marshall Bowers
dd75f85ecf elm: Extract to zed-extensions/elm repository (#22637)
This PR extracts the Elm extension to the
[zed-extensions/elm](https://github.com/zed-extensions/elm) repository.

Release Notes:

- N/A
2025-01-04 00:00:45 +00:00
Roy Williams
b1a6e2427f anthropic: Allow specifying additional beta headers for custom models (#20551)
Release Notes:

- Added the ability to specify additional beta headers for custom
Anthropic models.

---------

Co-authored-by: David Soria Parra <167242713+dsp-ant@users.noreply.github.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 23:46:32 +00:00
Marshall Bowers
bbe6bf9caf assistant: Remove unused AssistantSettings::update_file (#22636)
As a follow-up to #21672, this PR removes the
`AssistantSettings::update_file` method, as it was no longer used
anywhere.

Release Notes:

- N/A
2025-01-03 23:34:15 +00:00
Torrat
53cfb578e8 python: Adjust binary path based on OS (#22587)
Closes #ISSUE

- #21452 

Describe the bug / provide steps to reproduce it

Language server error: pylsp

failed to spawn command. path:
"C:\Users\AppData\Local\Zed\languages\pylsp\pylsp-venv\bin\pylsp",
working directory: "D:\Coding\Python", args: []
-- stderr--

Environment
- Windows 11
- python

Release Notes:

- Windows: Fixed the path building used to run `pip` commands in the
venv generated on Windows 11.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 22:56:31 +00:00
Drew Ridley
e6fe12d0e1 assistant: Remove outdated settings update path (#21672)
Removed a settings update that should have been removed in the 0.148.0
release.

I am not sure if there is a tracking issue, but I identified this check
for outdated settings that should not be needed anymore. I investigated
a bit and did not find any conflicts or UB as a result of removing this
code.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 22:52:08 +00:00
Marshall Bowers
04cf19d49a Upgrade strum to v0.26 (#22633)
This PR upgrades `strum` to v0.26.

Supersedes #21896.

Release Notes:

- N/A
2025-01-03 22:23:06 +00:00
Marshall Bowers
4bd5f0d355 racket: Extract to zed-extensions/racket repository (#22630)
This PR extracts the Racket extension to the
[zed-extensions/racket](https://github.com/zed-extensions/racket)
repository.

Release Notes:

- N/A
2025-01-03 22:04:50 +00:00
Marshall Bowers
fdbf3d0f25 clojure: Extract to zed-extensions/clojure repository (#22628)
This PR extracts the Clojure extension to the
[zed-extensions/clojure](https://github.com/zed-extensions/clojure)
repository.

Release Notes:

- N/A
2025-01-03 21:14:53 +00:00
Nils Koch
a1ef1d3f76 Add syntax highlighting for character literals in Haskell, PureScript, and Zig (#22609)
Closes #22480

Release Notes:

- N/A

| Before | After |
|----------|----------|
| <img width="344" alt="before"
src="https://github.com/user-attachments/assets/37f8daf7-c9a0-4259-8c03-bd1a4479abca"
/> | <img width="344" alt="after"
src="https://github.com/user-attachments/assets/0f7e4429-e48b-4b32-9797-a0da8487e23e"
/> |

Zig, Haskel, and PureScript define a character caputure name in
`highlights.scm`, but we did not define a color for that capture name in
the themes. The new character color is the same as the string color in
all themes.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 20:51:43 +00:00
Marshall Bowers
04518b11bc language_model_selector: Refresh the models when the providers change (#22624)
This PR fixes an issue introduced in #21939 where the list of models in
the language model selector could be outdated.

Since we're no longer recreating the picker each render, we now need to
make sure we are updating the list of models accordingly when there are
changes to the language model providers.

I noticed it specifically in Assistant1.

Release Notes:

- Fixed a staleness issue with the language model selector.
2025-01-03 19:38:08 +00:00
saahityaedams
e4eef725de Add support for Claude 3.5 Haiku model (#22323)
Partly Closes #22185

Release Notes:

- Added support for the Claude 3.5 Haiku model.

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2025-01-03 18:49:29 +00:00
Marshall Bowers
7c7eb98e64 astro: Extract to zed-extensions/astro repository (#22623)
This PR extracts the Astro extension to the
[zed-extensions/astro](https://github.com/zed-extensions/astro)
repository.

Release Notes:

- N/A
2025-01-03 18:34:33 +00:00
Marshall Bowers
7826d79a40 markdown: Make push_div work with Stateful<Div>s (#22622)
This PR updates the `push_div` method in the `MarkdownElementBuilder` to
support taking in a `Stateful<Div>`.

This is some groundwork for supporting horizontal scrolling in Markdown
code blocks.

Release Notes:

- N/A
2025-01-03 18:24:04 +00:00
tims
bb24c085be linux: Add keyboard shortcuts for menus (#22074)
Closes #19837

This PR is a continuation of [linux: Implement
Menus](https://github.com/zed-industries/zed/pull/21873) and should only
be reviewed once the existing PR is merged.

I created this as a separate PR as the existing PR was already reviewed
but is yet to merge, and also it was my initial plan to do it in
separate parts because of the scope of it. This will also help reviewing
code faster.

This PR adds two new types of keyboard shortcuts to make menu navigation
easier:

1. `Alt + Z` for Zed, `Alt + F` for File, `Alt + S` for Selection, and
so on to open a specific menu with this combination. This mimics VSCode
and IntelliJ.

2. `Arrow Left/Right` when any menu is open. This will trigger the
current menu to close, and the previous/next to open respectively. First
and last element cycling is handled.

`Arrow Up/Down` to navigate menu entries is already there in existing
work.



https://github.com/user-attachments/assets/976aea48-4e20-4c19-850d-4d205a4bead2


Release Notes:

- Added keyboard navigation for menus on Linux (left/right). If you wish
to open menus with keyboard shortcuts add the following to your user
keymap:
    ```json
      {
        "context": "Workspace",
        "bindings": {
          "alt-z": ["app_menu::OpenApplicationMenu", "Zed"],
          "alt-f": ["app_menu::OpenApplicationMenu", "File"],
          "alt-e": ["app_menu::OpenApplicationMenu", "Edit"],
          "alt-s": ["app_menu::OpenApplicationMenu", "Selection"],
          "alt-v": ["app_menu::OpenApplicationMenu", "View"],
          "alt-g": ["app_menu::OpenApplicationMenu", "Go"],
          "alt-w": ["app_menu::OpenApplicationMenu", "Window"],
          "alt-h": ["app_menu::OpenApplicationMenu", "Help"]
        }
      }
    ```

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-01-03 18:19:24 +00:00
Cole Miller
11ec25aedb Support diagnostic navigation in multibuffers (#22620)
cc @nathansobo 

Release Notes:

- Support diagnostic navigation in multibuffers
2025-01-03 18:07:56 +00:00
Marshall Bowers
39af06085a assistant2: Add an example thread to showcase long lines of code (#22621)
This PR adds another example thread to showcase a response with long
lines of code.

This example will be helpful when working to make the code blocks scroll
horizontally instead of wrapping.

Release Notes:

- N/A
2025-01-03 17:48:04 +00:00
Marshall Bowers
a49e394e51 assistant2: Remove single-letter variable name (#22618)
This PR removes a single-letter variable name in place of a full one,
for readability.

Release Notes:

- N/A
2025-01-03 17:04:07 +00:00
Peter Tripp
e5c3d5d626 Emacs keybinding improvements (2025-01-02) (#22590)
Various improvements to the emacs compatibility keybindings.

- See also: https://github.com/zed-industries/zed/issues/4856

Release Notes:

- Improvements to emacs keybindings:
- Better support for running emacs inside Zed terminal (e.g. `ctrl-x
ctrl-c` will quit emacs in terminal not zed)
  - `alt-^` Join Lines
  - `ctrl-/` Undo
  - `alt-.` GotoDefinition and `alt-,` GoBack
  - `ctrl-x h` SelectAll
  - `alt-<` / `alt->` Goto End/Beginning of Buffer
  - `ctrl-g` as Menu::cancel
2025-01-03 16:48:41 +00:00
Marshall Bowers
1aba459a0a csharp: Bump to v0.1.0 (#22617)
This PR bumps the C# extension to v0.1.0.

Changes:

- https://github.com/zed-industries/zed/pull/15175
- https://github.com/zed-industries/zed/pull/15885
- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/18869
- https://github.com/zed-industries/zed/pull/22599

Release Notes:

- N/A
2025-01-03 16:47:19 +00:00
Hossein Khosravi
8c253af451 linux: Fix regex patterns for detecting Fedora in script/linux (#22611)
In `script/linux` file, in order to install build dependencies we check
ID and VERSION_ID fields of `/etc/os-release` file for installing
os-specific packages. The regex patterns for those fields are wrong
because there's no `"` character after `ID=` or `VERSION_ID=`. This
causes `grep` to fail.
So I extended the pattern by adding `"?` after each `"` character to
bypass the cause of failure.

Release Notes:

- N/A

Signed-off-by: thehxdev <hossein.khosravi.ce@gmail.com>
2025-01-03 16:28:07 +00:00
renovate[bot]
3c207209cb Update Rust crate sea-orm to v1.1.3 (#22554)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [sea-orm](https://www.sea-ql.org/SeaORM)
([source](https://redirect.github.com/SeaQL/sea-orm)) | dev-dependencies
| patch | `1.1.2` -> `1.1.3` |
| [sea-orm](https://www.sea-ql.org/SeaORM)
([source](https://redirect.github.com/SeaQL/sea-orm)) | dependencies |
patch | `1.1.2` -> `1.1.3` |

---

### Release Notes

<details>
<summary>SeaQL/sea-orm (sea-orm)</summary>

###
[`v1.1.3`](https://redirect.github.com/SeaQL/sea-orm/blob/HEAD/CHANGELOG.md#113---2024-12-24)

[Compare
Source](https://redirect.github.com/SeaQL/sea-orm/compare/1.1.2...1.1.3)

##### New Features

- \[sea-orm-codegen] register seaography entity modules & active
enums[https://github.com/SeaQL/sea-orm/pull/2403](https://redirect.github.com/SeaQL/sea-orm/pull/2403)3

```rust
pub mod prelude;

pub mod sea_orm_active_enums;

pub mod baker;
pub mod bakery;
pub mod cake;
pub mod cakes_bakers;
pub mod customer;
pub mod lineitem;
pub mod order;

seaography::register_entity_modules!([
    baker,
    bakery,
    cake,
    cakes_bakers,
    customer,
    lineitem,
    order,
]);

seaography::register_active_enums!([
    sea_orm_active_enums::Tea,
    sea_orm_active_enums::Color,
]);
```

##### Enhancements

- Insert many allow active models to have different column set
[https://github.com/SeaQL/sea-orm/pull/2433](https://redirect.github.com/SeaQL/sea-orm/pull/2433)

```rust
// this previously panics
let apple = cake_filling::ActiveModel {
    cake_id: ActiveValue::set(2),
    filling_id: ActiveValue::NotSet,
};
let orange = cake_filling::ActiveModel {
    cake_id: ActiveValue::NotSet,
    filling_id: ActiveValue::set(3),
};
assert_eq!(
    Insert::<cake_filling::ActiveModel>::new()
        .add_many([apple, orange])
        .build(DbBackend::Postgres)
        .to_string(),
    r#"INSERT INTO "cake_filling" ("cake_id", "filling_id") VALUES (2, NULL), (NULL, 3)"#,
);
```

- \[sea-orm-cli] Added `MIGRATION_DIR` environment variable
[https://github.com/SeaQL/sea-orm/pull/2419](https://redirect.github.com/SeaQL/sea-orm/pull/2419)
- Added `ColumnDef::is_unique`
[https://github.com/SeaQL/sea-orm/pull/2401](https://redirect.github.com/SeaQL/sea-orm/pull/2401)
- Postgres: quote schema in `search_path`
[https://github.com/SeaQL/sea-orm/pull/2436](https://redirect.github.com/SeaQL/sea-orm/pull/2436)

##### Bug Fixes

- MySQL: fix transaction isolation level not respected when used with
access mode
[https://github.com/SeaQL/sea-orm/pull/2450](https://redirect.github.com/SeaQL/sea-orm/pull/2450)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 16:22:21 +00:00
Peter Tripp
663f5244ca Deploy script: Specify remote for new Preview branch (#22614)
Set the git remote tracking branch when the new Preview branch is
created by `script/bump-zed-minor-versions`. This only impacts the local
git branch configuration of the user who runs this script.

Release Notes:

- N/A
2025-01-03 15:25:15 +00:00
Agus Zubiaga
0599f0fcb6 Fix vertical alignment when jumping from multibuffers (#22613)
Clicking buffer headers and line numbers would sometimes take you to a
disorienting scroll position. This PR improves that so the destination
line is roughly at the same Y position as it appeared in the
multibuffer.



https://github.com/user-attachments/assets/3ad71537-cf26-4136-948f-c5a96df57178


**Note**: The alignment won't always be perfect because the multibuffer
and target buffer might start at a different absolute Y position
(because of open search, breadcrumbs, etc). I wanted to compensate for
that, but that requires a fundamental change that I'd prefer to make
separately.

Release Notes:

- Fix vertical alignment when jumping from multibuffers
2025-01-03 14:37:00 +00:00
Finn Evers
6e2b6258b1 csharp: Add bracket indents (#22599)
This PR adds an initial `indents.scm` to the C#-extension in order to
support auto-indentation with brackets.

Release Notes:

- N/A
2025-01-03 13:55:25 +00:00
Marshall Bowers
82492d74a8 assistant2: Tweak "Add Context" placeholder (#22596)
This PR tweaks the "Add Context" placeholder, as the text appeared to be
vertically misaligned.

#### Before

<img width="215" alt="Screenshot 2025-01-02 at 6 03 06 PM"
src="https://github.com/user-attachments/assets/1bac0deb-bd90-4ff3-b681-ee884cbe831d"
/>

#### After

<img width="189" alt="Screenshot 2025-01-02 at 6 03 20 PM"
src="https://github.com/user-attachments/assets/c9673fb0-11d6-42ac-8fec-9af269dfc73c"
/>


Release Notes:

- N/A
2025-01-02 23:18:32 +00:00
renovate[bot]
16ead69052 Update Rust crate serde_json to v1.0.134 (#22555)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

- Add `RawValue` associated constants for literal `null`, `true`,
`false`
([#&#8203;1221](https://redirect.github.com/serde-rs/json/issues/1221),
thanks [@&#8203;bheylin](https://redirect.github.com/bheylin))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 23:08:47 +00:00
Marshall Bowers
a53be7b4be collab_ui: Show the chat panel icon when the chat panel is active (#22593)
This PR is a follow-up to #22200 that makes it so the chat panel icon is
visible when the chat panel is active, even if not in a call (when using
the `when_in_call` setting).

Release Notes:

- N/A
2025-01-02 22:53:34 +00:00
renovate[bot]
a79def005d Update Rust crate quote to v1.0.38 (#22553)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [quote](https://redirect.github.com/dtolnay/quote) | dependencies |
patch | `1.0.37` -> `1.0.38` |

---

### Release Notes

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

###
[`v1.0.38`](https://redirect.github.com/dtolnay/quote/releases/tag/1.0.38)

[Compare
Source](https://redirect.github.com/dtolnay/quote/compare/1.0.37...1.0.38)

- Support interpolating arrays inside of arrays using a repetition
([#&#8203;286](https://redirect.github.com/dtolnay/quote/issues/286))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 22:45:55 +00:00
Michael Sloan
2d431e9b51 Improve truncate efficiency and fix OBOE in truncate_and_remove_front (#22591)
* Skip walking string for truncate when byte len is <= char limit

* Fix `truncate_and_remove_front` returning string that is `max_chars +
1` in length. Now more consistent with `truncate_and_trailoff` behavior.

* Fix `truncate_and_remove_front` adding ellipsis when max_chars == char
length

Release Notes:

- N/A
2025-01-02 22:35:36 +00:00
Michael Sloan
f9df8c1729 Use the same label for both string and bag in tasks modal fuzzy match (#22022)
#22592 tracks properly doing fuzzy match within the full label

Release Notes:

- N/A
2025-01-02 22:11:14 +00:00
Justin Su
898064e6b4 Fix a typo in default.json (#22589)
Release Notes:

- N/A
2025-01-02 21:44:51 +00:00
Josef Zoller
8cb397cf6c project_panel: Open rename file editor if pasted file was disambiguated (#19975)
Closes #19974.

When a file is pasted in the project panel at a location where a file
with that name already exists, the new file's name is disambiguated by
appending " copy" at the end. This happens on the paste and the
duplicate actions, as well as when Alt-dragging files.
With this PR, this will now open the file rename editor with the
disambiguator pre-selected.

Open question:
With this PR's current implementation, this won't always work when
pasting multiple files at once. In this case, the file rename editor
only opens for the last pasted file, if that file was disambiguated. If
only other files were disambiguated instead, it won't open.
This roughly mimics the previous paste behaviour, namely that only the
last pasted file was selected.

I see two options here: If multiple files were pasted and some of them
were disambiguated, we could select and open the rename editor for the
last file that was actually disambiguated (easy), or we could open a
kind of multi-editor for all files (hard, but maybe a multi-rename
editor could actually be interesting in general...).

Release Notes:

- Open rename file editor if pasted file was disambiguated
2025-01-02 21:33:51 +00:00
Agus Zubiaga
374c298bd5 assistant2: Suggest current thread in inline assistant (#22586)
Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.com>
2025-01-02 20:36:57 +00:00
Kirill Bulatov
0e75ca8603 Fix tooltips too eager to disappear when there's a gap between the tooltip source and the tooltip itself (#22583)
Follow-up of https://github.com/zed-industries/zed/pull/22548

Release Notes:

- N/A

Co-authored-by: Peter Tripp <peter@zed.dev>
2025-01-02 19:45:47 +00:00
Marshall Bowers
2c2ca9e370 assistant2: Wire up the directory context picker (#22582)
This PR wires up the functionality of the directory context picker.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
2025-01-02 19:42:59 +00:00
Peter Tripp
3cf5ab16a9 racket: Bump Extension to v0.0.2 (#22584)
Includes:
- https://github.com/zed-industries/zed/pull/18728
2025-01-02 14:39:18 -05:00
Peter Tripp
53e1ab3c64 elixir: Bump to v0.1.3 (#22585)
Includes:
- https://github.com/zed-industries/zed/pull/22579
2025-01-02 14:38:51 -05:00
Peter Tripp
d0c4c0c240 elixir: Capture identifiers as @variable (#22579)
- Closes: https://github.com/zed-industries/zed/issues/19382

Before/After
<img width="329" alt="Screenshot 2025-01-02 at 13 08 46"
src="https://github.com/user-attachments/assets/ede36fd3-ed55-4436-912c-bb8b7ad9b0cd"
/><img width="329" alt="Screenshot 2025-01-02 at 13 08 18"
src="https://github.com/user-attachments/assets/eb784bdc-fd13-487d-b6ed-c960d8020d9b"
/>


Release Notes:

- N/A
2025-01-02 18:27:12 +00:00
Marshall Bowers
20c0d72fe4 ci: Make docs-only check a no-op in the merge queue (#22576)
This PR makes the docs-only check a no-op that defaults to `false` when
running in the merge queue.

I noticed that the current check did not work properly in the merge
queue, resulting in it always assuming a change was docs-only and not
running the requisite CI jobs.

Release Notes:

- N/A
2025-01-02 18:14:17 +00:00
renovate[bot]
d5f058d6e2 Update swatinem/rust-cache digest to f0deed1 (#22552)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [swatinem/rust-cache](https://redirect.github.com/swatinem/rust-cache)
| action | digest | `82a92a6` -> `f0deed1` |

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 17:41:35 +00:00
Bennet Bo Fenner
fbef8c2b3b docs: Update scrollbar > diagnostics setting section (#22574)
This updates the docs after the setting was changed in #22364 

Release Notes:

- N/A
2025-01-02 17:27:17 +00:00
Bennet Bo Fenner
b009e72121 terminal: Support clicking on "file://" URLs with line numbers (#22559)
Closes #10325

Release Notes:

- Fixed an issue inside the integrated terminal where clicking on URLs
that started with `file://` would sometimes not work when the path
included a line number (e.g. `file:///Users/someuser/lorem.txt:221:22`)
2025-01-02 17:24:55 +00:00
Aaron Feickert
f55a3629b0 Add fine-grained control for scrollbar diagnostics (#22364)
This PR updates the scrollbar diagnostic setting to provide fine-grained
control over which indicators to show, based on severity level. This
allows the user to hide lower-severity diagnostics that can otherwise
clutter the scrollbar (for example, unused or disabled code).

The options are set such that the existing boolean setting has the same
effect: when `true` all diagnostics are shown, and when `false` no
diagnostics are shown.

Closes #22296.

Release Notes:

- Added fine-grained control of scrollbar diagnostic indicators.
2025-01-02 17:03:00 +00:00
renovate[bot]
3ac0aef211 Update Rust crate unicase to v2.8.1 (#22558)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [unicase](https://redirect.github.com/seanmonstar/unicase) |
dependencies | patch | `2.8.0` -> `2.8.1` |
| [unicase](https://redirect.github.com/seanmonstar/unicase) |
workspace.dependencies | patch | `2.8.0` -> `2.8.1` |

---

### Release Notes

<details>
<summary>seanmonstar/unicase (unicase)</summary>

###
[`v2.8.1`](https://redirect.github.com/seanmonstar/unicase/releases/tag/v2.8.1)

[Compare
Source](https://redirect.github.com/seanmonstar/unicase/compare/v2.8.0...v2.8.1)

##### What's Changed

- fix: hash for Unicode will call write_u8 like Ascii does by
[@&#8203;seanmonstar](https://redirect.github.com/seanmonstar) in
[https://github.com/seanmonstar/unicase/pull/73](https://redirect.github.com/seanmonstar/unicase/pull/73)
- fix: provide prefix-freedom in Hash impls by
[@&#8203;seanmonstar](https://redirect.github.com/seanmonstar) in
[https://github.com/seanmonstar/unicase/pull/74](https://redirect.github.com/seanmonstar/unicase/pull/74)

**Full Changelog**:
https://github.com/seanmonstar/unicase/compare/v2.8.0...v2.8.1

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 14:55:59 +00:00
Agus Zubiaga
59b5b9af90 assistant2: Suggest current file as context (#22526)
Suggest adding the current file as context in the new assistant panel.



https://github.com/user-attachments/assets/62bc267b-3dfe-4a3b-a6af-c89af2c779a8


Note: This doesn't include suggesting the current thread in the inline
assistant.

Release Notes:

- N/A
2025-01-02 13:33:47 +00:00
Piotr Osiewicz
b3e36c93b4 deps: Bump indexmap to 2.0 (#22567)
Closes #ISSUE

Release Notes:

- N/A
2025-01-02 12:07:46 +00:00
Piotr Osiewicz
5447821715 gpui/perf: Use SharedString on API boundary of line layout (#22566)
This commit is all about strings, not about line layout at all. When
laying out text, we use a line layout cache to avoid roundtrips to
system layout engine where possible. This makes it so that we might end
up not needing an owned version of text to insert into the cache, as we
might get a cached version.

The API boundary of line layout accepted text to be laid out as &str. It
then performed cache lookup (which didn't require having an owned
version) and only resorted to making an owned version when needed. As it
turned out though, exact cache hits are quite rare and we end up needing
owned version more often than not. The callers of line layout either
dealt with SharedStrings or owned Strings. Due to coercing them into
&str, we were ~always copying text into a new string (unless there was a
same-frame-hit). This is a bit wasteful, thus this PR generifies the API
a bit to make it easier to reuse existing string allocations if there
are any.

Benchmark scenario: scrolling down page-by-page through editor_tests (I
ran the same scenario twice):

![1](https://github.com/user-attachments/assets/8cd09692-2699-41d9-b211-83554d93902f)

![2](https://github.com/user-attachments/assets/d11f7c22-2315-4261-8189-2356baf5d2f7)


Release Notes:

- N/A
2025-01-02 11:06:01 +00:00
Michael Sloan
665717da9a Fuzzy match performance improvements redo (#22561)
Release Notes:

- N/A
2025-01-02 05:31:06 +00:00
Danilo Leal
28d1d2d939 assistant2: Add link styles for thread messages (#22560)
<img width="700" alt="Screenshot 2025-01-02 at 1 52 30 AM"
src="https://github.com/user-attachments/assets/8d2308c8-cdea-421f-b9ff-7893479dba3c"
/>


Release Notes:

- N/A
2025-01-02 05:09:33 +00:00
Hayashi Mikihiro
44af405fb0 pane: Turn off preview mode when pinning a tab (#22501)
I opened the tab in preview mode, pinned it, then I opened another file,
but its tab unexpectedly closed.


https://github.com/user-attachments/assets/b857382e-f0ad-4d5a-9036-19de01663c97

Pinning a tab now turns off preview mode.



https://github.com/user-attachments/assets/e34b7c7f-452b-4f36-99c1-e0c68429225c


Release Notes:

- Pinning a preview tab will now turn off preview mode

---------

Signed-off-by: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2025-01-02 04:09:35 +00:00
Kirill Bulatov
c11bde7bf4 Remove stuck tooltips (#22548)
Closes https://github.com/zed-industries/zed/issues/21657

Follow-up of https://github.com/zed-industries/zed/pull/22488
Previous PR broke git blame tooltips, which are expected to be open when
hovered, even if the mouse cursor is moved away from the actual blame
entry that caused the tooltip to appear.

Current version moves the invalidation logic into `prepaint_tooltip`,
where the new data about the tooltip origin is used to ensure we
invalidate only tooltips that have no mouse cursor in either origin
bounds or tooltip bounds (if it's hoverable).


Release Notes:

- Fixed tooltips getting stuck
2025-01-01 18:47:10 +00:00
Peter Tripp
0d423a7b37 Bump Zed to v0.169 (#22547)
Release Notes:

-N/A
2025-01-01 12:31:37 -05:00
Danilo Leal
a6f95a14b7 docs: Improve the Getting Started page (#22545)
- Little tweaks to wording and punctuation
- Remove redundancy on the Configuration and Key binding sections
- Ensure key bindings for the sections mentioned above appear

Release Notes:

- N/A
2025-01-01 17:19:10 +00:00
Peter Tripp
642dab82e5 Fix for extension search crash (revert #22524; revert #22525) (#22543)
Revert "Improve fuzzy match performance and fix corner case that omits
results (#22524)"
This reverts commit 6ef5d8f748.

Revert "Check cancel in multithreaded fuzzy matching (#22525)"
This reverts commit 51ac2d3667.

Fuzzy matching implemented in:
- https://github.com/zed-industries/zed/pull/22524
- https://github.com/zed-industries/zed/pull/22525

Caused a panic in the extension store search:
- Closes: https://github.com/zed-industries/zed/issues/22541

cc: @mgsloan 

Release Notes:

- N/A
2025-01-01 17:04:37 +00:00
Kirill Bulatov
38938ece49 Revert "Invalidate tooltips when mouse leaves element's hitbox (#22488)" (#22542)
This reverts commit 344284e013.

That change broke git blame tooltips, as Zed should also show tooltips
which are hovered, even though the mouse had left the origin element's
bounds.

Release Notes:

- N/A
2025-01-01 16:56:58 +00:00
Peter Tripp
8e12c679fc Revert "Have Zed cli output logs path to stderr" (#22540)
Removes noisy log location stderr output on every `zed` cli invocation.

Reverts zed-industries/zed#22509

Release Notes:

- N/A
2025-01-01 16:21:08 +00:00
Rusydy
4ce1ccfc40 Add support for block comments in Markdown configuration (#22352)
This pull request includes a small change to the
`crates/languages/src/markdown/config.toml` file. The change adds block
comment syntax for Markdown files.

*
[`crates/languages/src/markdown/config.toml`](diffhunk://#diff-4cf73d9af0f11f2ac8929bd8113ee76aa382dc96a731f18510c09fc3d0db1f9cR5):
Added block comment syntax `<!-- ` and ` -->` for Markdown files.

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

I have been testing it with the following:

1. create a simple markdown file containing following words

```markdown
good morning - English
Dzień dobry - polish
おはよう - japanese
صباح الخير - arabic
早安 - Chineses (Traditional)
```

2. comment using `cmd+/` and see if there are any errors



Release Notes:

- Added block comment syntax `<!-- ` and ` -->` for Markdown files.
2025-01-01 15:46:38 +00:00
Danilo Leal
eedbede939 docs: Ensure "Remote Projects" dialog keybinding is displayed (#22530)
Closes https://github.com/zed-industries/zed/issues/22429

Release Notes:

- N/A
2025-01-01 00:40:38 +00:00
Danilo Leal
30668589ed Improve project search design details (#22529)
Closes https://github.com/zed-industries/zed/issues/22469 — and, aside
from fixing that issue also took the opportunity to make the
`file_search` icon dimensions feel better in comparison the other icons
in the Project Search area.

Release Notes:

- N/A
2025-01-01 00:09:36 +00:00
Michael Sloan
51ac2d3667 Check cancel in multithreaded fuzzy matching (#22525)
For both the strings and paths multithreaded matching it would still
aggregate the response even though it is unneeded. It now checks cancel.

In the paths matcher, cancel is now checked within the loop, since it
was calling `match_candidates` even though no further results would be
computed.

Release Notes:

- N/A
2024-12-31 22:37:41 +00:00
Michael Sloan
6ef5d8f748 Improve fuzzy match performance and fix corner case that omits results (#22524)
* Removes `max_results` from the matcher interface as this is better
dealt with in consumers once all results are known. The current
implementation was quite inefficient as it was using binary search to
find insertion points and then doing an insert which copies the entire
suffix each time.

* There was a corner case where if the binary search found a match
candidate with the same score, it was dropped. Now fixed.

* Uses of `util::extend_sorted` when merging results from worker threads
also repeatedly uses binary search and insertion which copies the entire
suffix. A followup will remove that and its usage.

* Adds `util::truncate_to_bottom_n_sorted_by` which uses quickselect +
sort to efficiently get a sorted count limited result.

* Improves interface of Matcher::match_candidates by providing the match
positions to the build function. This allows for removal of the `Match`
trait. It also fixes a bug where the Match's own Ord wasn't being used,
which seems relevant to PathMatch for cases where scores are the same.

Release Notes:

- N/A
2024-12-31 20:56:23 +00:00
Kirill Bulatov
f912c545e7 Deduplicate edits from WorkspaceEdit LSP responses (#22512)
Closes https://github.com/zed-industries/zed/issues/21515

Release Notes:

- Fixed zls renames applying duplicate edits
2024-12-31 00:50:57 +00:00
Kirill Bulatov
ddc469ca3e Ensure zls is used for Zig as a primary language server (#22511)
Part of https://github.com/zed-industries/zed/issues/22415

I've noticed that I cannot work with any Zig projects, as there were no
"go to definition", formatting and inlay hints.

After debugging, I've discovered that `typos` was registered as a first
language server, becoming the "primary" one for Zig.
That one does not have any proper capabilities, hence all corresponding
LSP requests were no-op.

While this solution is not ideal (I wonder, how many other set-ups are
broken due to the same thing?), we'd better fix things for now this way
at least.

Release Notes:

- Fixed `zls` not working properly when `typos` extension is installed
2024-12-30 23:34:25 +00:00
Michael Sloan
e9bd4b2890 Have Zed cli output logs path to stderr (#22509)
When switching from just running the editor directly to using the CLI I
missed the logs in the terminal and started using `--foreground`. I
realized this was because there was a tiny amount of effort involved in
finding out where logs were being written to. Having the cli output it
to stderr helps make this more visible.

Seems like such a minor thing not listing in release notes.

Release Notes:

- N/A
2024-12-30 21:14:08 +00:00
Vitaly Slobodin
a119688a50 ruby: Document use_bundler configuration option (#22498)
Hello,

This PR documents the `use_bundler` configuration option that was added
in https://github.com/zed-extensions/ruby/releases/tag/v0.3.3

**What it does?**

This option allows users to run `solargraph` and `rubocop` within the
context of Bundler, which is the most commonly used scenario in the Ruby
ecosystem. Ruby LSP is not configured to run in the context of Bundler
because it is recommended not to add it to the project dependencies
(https://shopify.github.io/ruby-lsp/composed-bundle.html). However, if
this is the case, the Ruby LSP can still be configured to run in the
context of Bundler via the same configuration option.

This configuration option has the following default values:

- `rubocop` - `use_bundler` is `true`.
- `solargraph` - `use_bundler` is `true`.
- `ruby-lsp` - `use_bundler` is `false`.

Release Notes:

- N/A
2024-12-30 16:56:25 +00:00
Cole Miller
dcbff982ad Decide which panel should be active for a dock based on ordering panels (#22346)
This means that `workspace::ToggleRightDock` will open the assistant if
no right-dock panel has been manually activated, instead of the chat as
before. Also cleans up the `active_panel_index` logic a bit.

cc @nathansobo 

Release Notes:

- Make `workspace::ToggleRightDock` open the assistant panel if no
right-dock panel has yet been activated
2024-12-30 14:43:17 +00:00
Danilo Leal
ad51df7644 Improve multibuffer excerpt affordances (#22167)
Changes:
- [x] Increase expand affordance surface area
- [x] Ensure expand buttons have tooltips with keybindings
- [x] Make line numbers clickable to jump you to location (only in
multibuffers)
- [x] Hide the "Jump To File" element in not-focused excerpts

Before merging it:

- [x] Fix off-by-one header focus styles glitch

Improvements to consider for follow-up PRs:

1. Experiment with increasing the width of the clickable surface area
for line numbers
2. Don't show (or disable) the "expand excerpt" button when at the top
or bottom edge of the file
3. Once you jump to location, centralize the cursor scroll position

Release Notes:

- Improved multibuffer's "expand excerpt" affordance
- Fixed "jump to file/location" and "expand excerpt" keybinding display
- Made clicking on line numbers in multibuffers jump you to cursor
location in file

---------

Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
2024-12-30 12:23:11 +00:00
Piotr Osiewicz
3f33ca01a8 chore: Remove outline dependency from breadcrumbs (#22504)
This slashes our incremental dev times (touch editor) by 0.6s
(8.1->7.6s) due to unblocking terminal_view build sooner.

Closes #ISSUE

Release Notes:

- N/A
2024-12-30 12:08:26 +00:00
Jason Lee
5f4f3a9c87 chore: Remove duplicate lines in crates/zed/build.rs (#22500)
Release Notes:

- N/A

This may a mistake by merge in PR #21550


344284e013/crates/zed/build.rs (L8-L14)
2024-12-30 11:52:40 +00:00
Kirill Bulatov
344284e013 Invalidate tooltips when mouse leaves element's hitbox (#22488)
Closes https://github.com/zed-industries/zed/issues/21657

In case of the task rerun button tooltip from


f6dabadaf7/crates/terminal_view/src/terminal_view.rs (L1051-L1070)

, the actual button element is not styled as invisible, only its parent.
Zed won't render such element since it's parent is hidden, but will
consider it "visible" all the time its `paint` is called, spawning a
task with the delay, that will create the tooltip:


f6dabadaf7/crates/gpui/src/elements/div.rs (L1949-L1959)

When the parent is hidden, the child won't be painted anymore, and no
mouse listeners will be able to detect this fact and hide the tooltip.

Hence, check such cases separately, during `prepaint`, and invalidate
the tooltips that are not valid anymore.
We cannot use `hitbox.is_hovered(cx)` as it's not really hovered during
prepaint, so a mouse position check is used instead.

Release Notes:

- Fixed tooltips getting stuck
2024-12-29 19:37:04 +00:00
Jayden Seric
f6dabadaf7 Syntax highlight the JavaScript keyword using (#22479)
The ECMAScript Explicit Resource Management proposal (stage 3) that
specifies the keyword `using`:

https://github.com/tc39/proposal-explicit-resource-management

This has already been done for the TypeScript and TSX languages:

- https://github.com/zed-industries/zed/issues/14762
- https://github.com/zed-industries/zed/pull/14772

Release Notes:

- Syntax highlight the JavaScript keyword `using`
([#22479](https://github.com/zed-industries/zed/pull/22479); thanks
[jaydenseric](https://github.com/jaydenseric)).
2024-12-29 08:17:51 +00:00
Michael Sloan
ca6825066f Fix SHA for zed-patches branch of livekit-rust-sdks (#22478)
Also renamed the repo to `livekit-rustk-sdks` on GitHub for clarity. The
old name will still work for old references.

See [discussion
here](https://github.com/zed-industries/zed/pull/21292#issuecomment-2529573128).
The only changes in the target repo are immaterial:

```
$ git diff 799f10133d93ba2a88642cd480d01ec4da53408c 060964da10574cd9bf06463a53bf6e0769c5c45e
diff --git a/livekit-protocol/src/livekit.rs b/livekit-protocol/src/livekit.rs
index 43f5496..19181c2 100644
--- a/livekit-protocol/src/livekit.rs
+++ b/livekit-protocol/src/livekit.rs
@@ -3837,7 +3837,7 @@ pub struct SendDataRequest {
     #[prost(string, optional, tag="5")]
     pub topic: ::core::option::Option<::prost::alloc::string::String>,
 }
-
+///
 #[allow(clippy::derive_partial_eq_without_eq)]
 #[derive(Clone, PartialEq, ::prost::Message)]
 pub struct SendDataResponse {
diff --git a/livekit-runtime/Cargo.toml b/livekit-runtime/Cargo.toml
index 4d83cdf..7051f30 100644
--- a/livekit-runtime/Cargo.toml
+++ b/livekit-runtime/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
 repository = "https://github.com/livekit/rust-sdks"

 [features]
+tokio = ["dep:tokio", "dep:tokio-stream"]
 async = ["dep:async-std", "dep:futures", "dep:async-io"]
 dispatcher = ["dep:futures", "dep:async-io", "dep:async-std", "dep:async-task"]
```

Release Notes:

- N/A
2024-12-29 00:18:58 +00:00
Michael Sloan
a15360bcc8 Dequalify WindowContext and ViewContext references (#22477)
Release Notes:

- N/A
2024-12-28 23:09:55 +00:00
Michael Sloan
016b5d60e1 Cleanups preparing for WindowContext refactor (#22475)
* Remove unnecessary WindowContext and ViewContext '_ lifetimes

* Removed some cases where WindowContext has a different name than `cx`.

Release Notes:

- N/A
2024-12-28 21:36:14 +00:00
Michael Sloan
9815358bdd Dequalify WindowContext and AsyncWindowContext identifiers (#22444)
This makes a WIP automated refactoring a bit easier to write

Release Notes:

- N/A
2024-12-28 21:21:32 +00:00
Kirill Bulatov
ac60dcd67a Fix Project strong reference leaks (#22470)
Closes https://github.com/zed-industries/zed/issues/21906

* After https://github.com/zed-industries/zed/pull/21238,
`TerminalPanel` and `Project` strong references were moved into
`Pane`-related closures, creating a cycle, that did not allow
registering project release and shutting down corresponding language
servers

* After https://github.com/zed-industries/zed/pull/22329, a special
`Editor` was created with a strong reference to the `Project` which
seemed to do nothing bad in general, but when a working Zed was running
a Zed Dev build, had the same issue with preventing language servers
from shutting down.

The latter is very odd, and seems quite dangerous, as any arbitrary
`Editor` with `Project` in it may do the same, yet it seems that we did
not store them before the way git panel does.

I have tried creating a test, yet seems that we need to initialize a lot
of Zed for it which I failed — all my attempts resulted in a single
language server being present in the `Project`'s statuses.

Release Notes:

- Fixed language servers not being released between project reopens
2024-12-28 17:29:15 +00:00
Kirill Bulatov
3e3a5f04a2 Small fixes after terminal split (#22468)
* removes cache access workarounds as
https://github.com/zed-industries/zed/pull/21165 is supposed to fix this
* use `WeakModel<Project>` inside `Pane` just in case 

Release Notes:

- N/A
2024-12-28 17:29:13 +00:00
Cole Miller
7903f4ea58 Fix the install-linux fix (#22466)
Follow-up fix to #22465, forgot my shell fundamentals!

Release Notes:

- N/A
2024-12-28 16:50:29 +00:00
Cole Miller
a3dec643a1 Fix script/install-linux in non-CI settings (#22465)
Closes #22456 

Release Notes:

- N/A
2024-12-28 15:59:54 +00:00
Dzmitry Malyshau
02cc0b9afa Update Blade with acquire fixes, multisampling (#22409)
Fixes #22406

Picks up:
- https://github.com/kvark/blade/pull/234 and
https://github.com/kvark/blade/pull/237 (`OutOfDate` workaround)
- https://github.com/kvark/blade/pull/229 ("metal-rs" to "objc2"
migration)

Release Notes:

- Improved handling of resizing and multiple monitors in Linux/Windows
2024-12-27 21:29:03 +00:00
Marcel Kersten
787466383e copilot: Update Copilot Chat to o1 GA model version (#22376)
Closes #22375

Release Notes:

- Fixed model version of o1 in GitHub Copilot Chat

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-27 18:09:50 +00:00
Danilo Leal
34f8bb246a assistant2: Scroll to the bottom when you submit a new message (#22451)
Up until now, in the assistant 2, if you scrolled up either while a
message was being generated or after it's been generated, then submitted
a new message, you'd keep your scroll position. Now, with this PR, if
your scroll position is somewhere else that's not the bottom, as you
submit a new message, you'll be back at the bottom.


https://github.com/user-attachments/assets/8b111c10-27ff-4d7b-9b10-4c31093c6457

Release Notes:

- N/A

Co-authored-by: Agus Zubiaga <hi@aguz.me>
2024-12-27 16:54:35 +00:00
Danilo Leal
56b425fced assistant2: Show the popover keybinding when context is empty (#22452)
This PR makes it so we always show the "Add Context {keybinding}" text
when there's no context pills attached. Also, while we haven't fully
implemented the mention system (triggered by typing `@`), we removed the
instruction on the message editor placeholder. Once that's fully in
place, we should return with it!

<img width="800" alt="Screenshot 2024-12-27 at 1 35 56 PM"
src="https://github.com/user-attachments/assets/201cf784-e7ac-420a-adf2-51b6e075c2b6"
/>

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <hi@aguz.me>
2024-12-27 16:54:31 +00:00
Kirill Bulatov
ed61abb8b8 Resolve completion items once exactly (#22448)
Closes https://github.com/zed-industries/zed/issues/19214
Closes https://github.com/zed-industries/zed/pull/22443

Adds `resolved` property into Zed completion item data, to ensure we
resolve every completion item exactly once.

There are 2 paths for singplayer Zed, and corresponding 2 analogues for
multi player code, where resolve may happen:
* completions menu display & selection, that ends up using
`resolve_completions` in `lsp_store.rs`
* applying a completion menu entry, that ends up using
`apply_additional_edits_for_completion` in `lsp_store.rs`

Now, all local counterparts check `enabled` field before resolving and
set it to true afterwards, and reuse the same `resolve_completion_local`
method for resolving the items.

A logic for re-generating docs and item labels was moved out from the
`resolve_completion_local` method into a separate method, as
`apply_additional_edits_for_completion` does not need that, but needs
the rest of the logic for resolving.
During the extraction, I've noted that multiplayer clients are not
getting the item labels, regenerated after the resolve — as the Zed
protocol-based flow is not the exact copy of the local resolving.
To improve that, `resolve_completion_remote` needs to be adjusted, but
this change is omitted to avoid bloating the PR.

Release Notes:

- Fixed autocomplete inserting multiple imports
2024-12-27 16:43:01 +00:00
renovate[bot]
d71180abc2 Update Rust crate async-broadcast to v0.7.2 (#22424)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [async-broadcast](https://redirect.github.com/smol-rs/async-broadcast)
| dependencies | patch | `0.7.1` -> `0.7.2` |

---

### Release Notes

<details>
<summary>smol-rs/async-broadcast (async-broadcast)</summary>

###
[`v0.7.2`](https://redirect.github.com/smol-rs/async-broadcast/blob/HEAD/CHANGELOG.md#Version-072)

[Compare
Source](https://redirect.github.com/smol-rs/async-broadcast/compare/0.7.1...0.7.2)

- Add `Sender::broadcast_blocking` and `Receiver::recv_blocking`.
[#&#8203;41](https://redirect.github.com/smol-rs/async-broadcast/issues/41)
- Use `Mutex` instead of `RwLock` for securing the inner data.
[#&#8203;42](https://redirect.github.com/smol-rs/async-broadcast/issues/42)
-   Many non-user-facing internal improvements and fixes.

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-27 00:11:37 +00:00
renovate[bot]
af2ac1a4a0 Update Rust crate env_logger to v0.11.6 (#22425)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [env_logger](https://redirect.github.com/rust-cli/env_logger) |
workspace.dependencies | patch | `0.11.5` -> `0.11.6` |

---

### Release Notes

<details>
<summary>rust-cli/env_logger (env_logger)</summary>

###
[`v0.11.6`](https://redirect.github.com/rust-cli/env_logger/blob/HEAD/CHANGELOG.md#0116---2024-12-20)

[Compare
Source](https://redirect.github.com/rust-cli/env_logger/compare/v0.11.5...v0.11.6)

##### Features

-   Opt-in file and line rendering

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-26 23:55:28 +00:00
renovate[bot]
c742cda8e8 Update Rust crate libc to v0.2.169 (#22427)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

##### Added

- FreeBSD: add more socket TCP stack constants
[#&#8203;4193](https://redirect.github.com/rust-lang/libc/pull/4193)
- Fuchsia: add a `sockaddr_vm` definition
[#&#8203;4194](https://redirect.github.com/rust-lang/libc/pull/4194)

##### Fixed

**Breaking**:
[rust-lang/rust#132975](https://redirect.github.com/rust-lang/rust/pull/132975)
corrected the signedness of `core::ffi::c_char` on various Tier 2 and
Tier 3 platforms (mostly Arm and RISC-V) to match Clang. This release
contains the corresponding changes to `libc`, including the following
specific pull requests:

- ESP-IDF: Replace arch-conditional `c_char` with a reexport
[#&#8203;4195](https://redirect.github.com/rust-lang/libc/pull/4195)
- Fix `c_char` on various targets
[#&#8203;4199](https://redirect.github.com/rust-lang/libc/pull/4199)
- Mirror `c_char` configuration from `rust-lang/rust`
[#&#8203;4198](https://redirect.github.com/rust-lang/libc/pull/4198)

##### Cleanup

- Do not re-export `c_void` in target-specific code
[#&#8203;4200](https://redirect.github.com/rust-lang/libc/pull/4200)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-26 23:55:11 +00:00
sofaspawn
52614482bf docs: Add "bounded" mode to soft_wrap documentation (#22430)
Closes #22272

Release Notes:

- Added documentation for the new "bounded" mode in the soft_wrap
configuration

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-12-26 19:40:27 +00:00
Nate Butler
8b0a0dfb11 gpui: Update Shadow Example (#22434)
This PR adds a more comprehensive shadow example to gpui:

![CleanShot 2024-12-26 at 13 54
05@2x](https://github.com/user-attachments/assets/452fb8a1-d294-4b56-b0e0-f4e4ca6c29d4)

This is prep to work on the following issues:

- Shadows with a blur of 0 do not render. Expected: A shadow with sharp
edges (#22433)
- Spread does not correctly conform to the shape the shadow is cast from

Release Notes:

- N/A
2024-12-26 19:23:10 +00:00
renovate[bot]
f5935d0cec Pin actions/checkout action to 11bd719 (#22422)
This PR contains the following updates:

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

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-26 13:30:18 +00:00
renovate[bot]
d32fe0216f Update Rust crate anyhow to v1.0.95 (#22423)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

- Add
[`Error::from_boxed`](https://docs.rs/anyhow/1/anyhow/struct.Error.html#method.from_boxed)
([#&#8203;401](https://redirect.github.com/dtolnay/anyhow/issues/401),
[#&#8203;402](https://redirect.github.com/dtolnay/anyhow/issues/402))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-26 13:29:27 +00:00
Danilo Leal
2957263b23 pane: Add keybinding to "Close Left" and "Close Right" actions (#22402)
Was super missing these as I use these actions all the time 😄 

<img width="680" alt="Screenshot 2024-12-24 at 3 18 04 PM"
src="https://github.com/user-attachments/assets/5dbf9ebd-afc5-438c-aad7-c17ca59d9f9b"
/>

Release Notes:

- Add keybinding to "Close Left" and "Close Right" actions
2024-12-26 13:22:08 +00:00
张小白
95911aaa14 windows: Fix crashing when minimizing a window on Windows 11 (#22414)
Closes #22366

This PR fixes the issue of crashing when minimizing a window on Windows
11. And this PR supersedes #22366.

The main change in this PR is to stop rendering the window when it is
minimized. Additionally, I’ve made some modifications to the code in
#21756 to improve clarity (I think...).

cc @mgsloan 

Release Notes:

- N/A
2024-12-25 09:52:19 +00:00
Piotr Osiewicz
1a9f0a647a chore: Remove explicit usages of once_cell in favor of std (#22407)
Closes #ISSUE

Release Notes:

- N/A
2024-12-25 00:33:26 +00:00
Agus Zubiaga
45c714110e Sticky multibuffer headers (#22391)
https://github.com/user-attachments/assets/92cc5ff7-d8be-4e4b-ac6e-68eb310fffce

Release Notes:

- Multibuffer headers will now stick to the top of the viewport as you
scroll
- Added support for expanding diagnostic excerpts

---------

Co-authored-by: Michael <michael@zed.dev>
2024-12-24 17:55:25 +00:00
Marshall Bowers
4c84600630 assistant2: Derive the Context icon at render time instead of storing (#22397)
This PR is a follow up to #22385 that makes it so we treat the icon as
derived state.

Release Notes:

- N/A
2024-12-24 16:13:35 +00:00
Danilo Leal
564936e1fe assistant2: Add stray visual adjusments (#22386)
This PR adds just some tiny visual clean ups to the assistant2 panel.
Nothing major, honestly.

<img width="800" alt="Screenshot 2024-12-24 at 12 19 46 AM"
src="https://github.com/user-attachments/assets/da22aa7f-8a42-4ff0-9e4c-5e8b60b28802"
/>

Release Notes:

- N/A
2024-12-24 04:26:13 +00:00
Danilo Leal
3d4e0780c4 assistant2: Add icons to the context pill (#22385)
This PR adds an icon field to the `ContextKind` enum, which means that
icons will now display on context pills, both on the message editor and
on the active thread.

<img width="800" alt="Screenshot 2024-12-24 at 12 23 17 AM"
src="https://github.com/user-attachments/assets/f00e540b-30fe-49ac-b3df-7c7a5dc86d65"
/>

Release Notes:

- N/A
2024-12-24 04:26:09 +00:00
Danilo Leal
44a46e3713 assistant2: Don't render the context space if there's none (#22383)
Note the extra bottom space on the before version. That was because,
previously, the container that holds the context pills in an active
thread was being rendered even if there was no attached context.

| Before | After |
|--------|--------|
| <img width="1577" alt="Screenshot 2024-12-23 at 8 42 00 PM"
src="https://github.com/user-attachments/assets/b74bdc67-0a08-4d59-b1ec-43a00a59a373"
/> | <img width="1577" alt="Screenshot 2024-12-23 at 8 39 00 PM"
src="https://github.com/user-attachments/assets/1cbc340d-19df-4cce-8c0b-b671703a0ff5"
/> |

Release Notes:

- N/A
2024-12-24 04:26:04 +00:00
Danilo Leal
8a724acc4d Add new CSS logo (#22382)
CSS has a brand new logo :) For context, see:

- https://github.com/CSS-Next/logo.css
- https://github.com/CSS-Next/logo.css/issues/54
- https://github.com/CSS-Next/logo.css/pull/53
- https://github.com/CSS-Next/logo.css/pull/55

<img width="1412" alt="Screenshot 2024-12-23 at 6 17 07 PM"
src="https://github.com/user-attachments/assets/7352a921-06fa-4e50-8aad-44c974c57ed4"
/>

Release Notes:

- Add new CSS logo
2024-12-23 21:42:39 +00:00
Danilo Leal
7595d36943 assistant2: Refine buffer inline assistant styles (#22377)
A lot of spacing tweaks. But, most notably, using the buffer font for
the inline assistant.

<img width="800" alt="Screenshot 2024-12-23 at 12 46 16 PM"
src="https://github.com/user-attachments/assets/ee2908a7-7515-4244-83fc-791172b29364"
/>

Release Notes:

- N/A
2024-12-23 21:29:39 +00:00
Danilo Leal
d25c2ff866 assistant2: Improve markdown rendering design (#22321)
This PR visually balances code blocks within thread messages a bit more.

<img width="800" alt="Screenshot 2024-12-23 at 11 26 14 AM"
src="https://github.com/user-attachments/assets/6d459aac-5d94-4021-8289-0125bc82e77c"
/>

Release Notes:

- N/A
2024-12-23 18:18:20 +00:00
plyght
7f33f31ebe docs: Use higher-quality image in the header (#22195)
Improved image quality of docs site logo at top.

Prior Change: 
<img width="159" alt="SCR-20241218-konx"
src="https://github.com/user-attachments/assets/18c936e8-958d-4970-98e2-8dd1ad3a0a89"
/>
Post Change: 
<img width="177" alt="SCR-20241218-kojm"
src="https://github.com/user-attachments/assets/0b874ac2-7193-44ec-9634-b91a71c53864"
/>

Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2024-12-23 13:39:28 +00:00
Kirill Bulatov
5df409971c Use more elaborate messages for permission denied errors on startup (#22368)
Also show all paths with issues, grouping them by the issue kind.

Inspired by https://github.com/zed-industries/zed/issues/22365 ,
https://github.com/zed-industries/zed/issues/20162,
https://github.com/zed-industries/zed/issues/13426, ...

Attempts to provide example `chown` and `chmod` commands on this error,
instead of encouraging to submit another issue.
For now, uses a hardcoded command examples, but we might provide a
better workflow later, e.g. try to automatically run those commands.

I seem to be unable to select the size of the modal, but for something
that's supposed to appear only once, it seems to be ok.


![regular](https://github.com/user-attachments/assets/484feb95-b9c5-445c-87f8-cd1db9337529)


![also_regular](https://github.com/user-attachments/assets/680ea95d-91fe-4b16-b3bc-e241ecd841d4)


![many_kinds](https://github.com/user-attachments/assets/7103161f-754d-413a-94df-62a44e353674)


![exaggerated](https://github.com/user-attachments/assets/b6e70be7-9ab4-4861-afae-5285a554fdba)



Release Notes:

- N/A
2024-12-23 12:34:29 +00:00
Cole Miller
8ccc7b81c8 Restore crates/zed dependency on libc (#22354)
This somehow got lost during or after merge of #22202.

Release Notes:

- N/A
2024-12-22 15:53:16 +00:00
teapo
a0e4464a33 Update Luau docs (#22351)
The domain has changed and the old StyLua arguments work again.

Release Notes:

- N/A
2024-12-22 13:54:50 +00:00
tims
204af9cac5 linux: Fix "Failed to start language server" errors when starting Zed from .desktop file (#22335)
Closes #21406

Context:

A few weeks ago on Linux, we resolved an
[issue](https://github.com/zed-industries/zed/issues/20070) where users
could not open more than one file from the file explorer. This was fixed
by replacing `zed-editor` (zed binary in the code) with `zed` (cli
binary in the code) in the `.desktop` file. The reason for this change
was that using the cli to open files is more convenient - it determines
weather to spawn a new Zed instance or use an existing one, if we use
main binary instead it would throw error `Zed is already running`.

You can read the complete PR here: [linux: Fix file not opening from
file explorer](https://github.com/zed-industries/zed/pull/21137).

While this fix resolved the original issue, it introduced a new one.

Problem:

When the cli binary is used, it assumes it is always being invoked from
a terminal and relies on `std::env::vars()` to retrieve the environment
variables needed to spawn Zed. These env vars are then passed to the
worktree, and eventually, languages use the `PATH` from this env to find
binaries. This leads to the "Failed to start language server" error when
the `.desktop` entry is used on Linux.

Solution:

When the `zed-editor` binary is used, it uses some clever Unix-specific
logic to retrieve the default shell (`load_shell_from_passwd`) and then
fetch the env vars from that shell (`load_login_shell_environment`).
This same logic should be used in the cli binary when it is invoked via
a `.desktop` entry rather than from a terminal.

Approach:

I moved these two functions mentioned above to a utils file and reused
them in cli binary to fetch env vars only on Linux when it is not run
from a terminal. This provides missing paths, and fix the issue.

It is also possible to handle this in the `zed-editor` binary by
modifying the logic in `handle_cli_connection`, where `CliRequest::Open`
is processed. There we can discard incoming env, and use our logic. But
discarding incoming envs felt weird, and I thought it's better to handle
this at source.

Release Notes:

- Fixed `Failed to start language server` errors when starting from
dekstop entry on Linux
2024-12-22 10:05:52 +00:00
Cole Miller
a2022d7da3 Improve Linux panic reporting (#22202)
- [x] Upload separate debug symbols for Linux binaries to DigitalOcean
- [x] Send raw offsets with panic report JSON on Linux
- [x] Update `symbolicate` script to handle Linux crashes
- [x] Demangle backtraces 🎉 
- [x] Check that it works
- [x] Improve deduplication (?)
 
Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
2024-12-22 08:20:17 +00:00
Kirill Bulatov
b51a28b75f Return back Windows menu (#22339)
Follow-up of https://github.com/zed-industries/zed/pull/21873

Release Notes:

- N/A
2024-12-21 22:03:17 +00:00
Michael Sloan
dbb76100e5 Improve layout of completions doc popover (#22337)
* Now more often displayed to the right. Resizes docs width if more
space than min width is available.

* If constrained in horizontal space and so displayed above/below resize
docs height to fit.

* Makes space for scrollbar and gap.

Layout is imperfect for viewport sizes smaller than the context menu,
left TODOs in the code for handling this. Wanted to get this change out
for feedback first.

Release Notes:

- N/A
2024-12-21 21:22:15 +00:00
Cole Miller
6b92e0b5da In terminal context, open new terminals with cmd-n instead of new files (#22253)
Release Notes:

- In terminal context, `cmd-n` and `ctrl-n` now open a new terminal
instead of a new file
2024-12-21 19:03:58 +00:00
Kirill Bulatov
2930211af9 Allow disabling editor scrollbars programmatically (#22333)
Disable them in the diff editors

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

Release Notes:

- N/A
2024-12-21 16:58:26 +00:00
Marshall Bowers
1449377278 assistant2: Fix panics when confirming nonexistent entries in the context picker (#22332)
This PR fixes a panic that could occur when confirming when the context
picker had no matching entries.

Release Notes:

- N/A
2024-12-21 16:11:29 +00:00
Kirill Bulatov
831930aad0 Make git panel entries clickable (#22329)
Makes a first pass over git panel UI, making it more interactive.


![image](https://github.com/user-attachments/assets/4d43b086-4ef2-4913-9783-2b9467d99c9a)


* every item can be selected, the selection is shown in the panel
* every item can be clicked, which changes the selection and
creates/focuses the editor with a project changes multi buffer
* the editor is scrolled so that the clicked item is in the center
* it's possible to nagivate up and down the panel, selecting
next/previous items in it, triggering the editor scroll

Known issues:

* entries are updated oddly sometimes (should become better after
DiffMap improvements land?)
* only unstaged diffs are shown currently (entry status storage should
help with this)
* no deleted files are displayed (the underlying work is done by others
now)
* added files have no diff hunks shown (DiffMap will have it?)
* performance story has not improved (again, DiffMap and status storage
should help with this)

Release Notes:

- N/A
2024-12-21 14:20:08 +00:00
Piotr Osiewicz
bc32b4d016 zeta: Compute diff on background thread (#22328)
@iamnbutler noticed slowness in assistant panel which I've pinned down
to the fact that we're calculating buffer diff on foreground thread.
This PR moves this computation into the background; I don't know much
about Zeta but it seems fine to do, as the call-site is asynchronous
anyways.

Closes #ISSUE

Release Notes:

- N/A
2024-12-21 13:44:18 +00:00
Piotr Osiewicz
fac5118f10 python: Fix decorated test detection (#22327)
Follow-up to #22325, where I missed a couple points in the review that
were actually quite relevant.

Closes #ISSUE

Release Notes:

- N/A
2024-12-21 13:11:52 +00:00
Thomas
7184b15f48 Add decorated function to pytest runnables (#22325)
Fixes: #22324

Closes #ISSUE

Release Notes:

- Fixed pytest decoracted function discovery
2024-12-21 11:31:35 +00:00
Danilo Leal
e82af55d64 assistant2: Adjust role info colors on the thread header (#22318)
<img width="800" alt="Screenshot 2024-12-20 at 9 01 29 PM"
src="https://github.com/user-attachments/assets/5e9e9bf2-c84e-4215-b658-9b668dd151b2"
/>

Release Notes:

- N/A
2024-12-21 00:19:50 +00:00
Danilo Leal
dcd21e6f23 assistant2: Use SwitchWithLabel for the tool toggle (#22317)
<img width="800" alt="Screenshot 2024-12-20 at 8 52 27 PM"
src="https://github.com/user-attachments/assets/4b4f62a3-eb23-489b-a459-2117baaa37b4"
/>

Release Notes:

- N/A
2024-12-21 00:15:47 +00:00
Danilo Leal
9efa13116d assistant2: Use a label to render the thread menu item (#22316)
The thread menu items were rendering with a larger font that the file &
directory counterparts. Now, they're consistent!

| Before | After | Reference |
|--------|--------|--------|
| <img width="800" alt="Screenshot 2024-12-20 at 8 39 47 PM"
src="https://github.com/user-attachments/assets/d196adcb-aea6-4399-8fb1-f5b771e76c1f"
/> | <img width="800" alt="Screenshot 2024-12-20 at 8 35 43 PM"
src="https://github.com/user-attachments/assets/a1e22c13-fd7e-4665-b09b-f3b583e7ce24"
/> | <img width="800" alt="Screenshot 2024-12-20 at 8 40 38 PM"
src="https://github.com/user-attachments/assets/d509ac6f-1251-4240-a369-906faebed8c0"
/> |


Release Notes:

- N/A
2024-12-20 23:53:51 +00:00
Danilo Leal
6dbc12f6af Add the SwitchWithLabel component (#22314)
<img width="800" alt="Screenshot 2024-12-20 at 8 31 14 PM"
src="https://github.com/user-attachments/assets/1d7bd10a-0db3-41e4-9f59-977cc2ab137c"
/>

Release Notes:

- N/A
2024-12-20 23:44:47 +00:00
Marshall Bowers
a8afc63a91 gpui: Remove use of use gpui::* in examples (#22311)
This PR removes the use of `use gpui::*` in the GPUI examples, as this
is not how consumers should be importing GPUI.

Release Notes:

- N/A
2024-12-20 23:09:30 +00:00
Marshall Bowers
7c6feeb3a8 Remove usage of use gpui::* (#22310)
This PR removes usages of `use gpui::*;` in the codebase (excluding
examples).

We should only use glob imports for `gpui::prelude`, and then import
everything else individually.

Release Notes:

- N/A
2024-12-20 22:52:11 +00:00
Danilo Leal
fadf9ff4f4 Make breadcrumb content scrollable (#22309)
Part of https://github.com/zed-industries/zed/issues/5363


https://github.com/user-attachments/assets/66c90cb7-1b36-4436-ad1e-344fbcd4befc

Release Notes:

- N/A
2024-12-20 22:50:41 +00:00
Michael Sloan
9b2bc458e3 Expand diagnostic excerpts using heuristics on syntactic information from TreeSitter (#21942)
This is quite experimental and untested in languages other than Rust.
It's written to attempt to do something sensible in many languages. Due
to its experimental nature, just releasing to staff, and so not
including it in release notes. Future release note might be "Improved
diagnostic excerpts by using syntactic info to determine the context
lines to show."

Release Notes:

- N/A
2024-12-20 22:42:18 +00:00
tims
ca9cee85e1 linux: Fix non-maximized Zed windows growing larger across sessions (#22301)
Closes #17870

Context:

On Linux, when creating a new window, bounds are either pulled from
existing workspace data (serialized in an SQLite DB) or fall back to
some default constants if no data exists.

These bounds include the full dimensions of the window (width and
height), which already account for insets. However, properties like
`inset` (Wayland) or `last_insets` (X11) exist only at the platform
level and are not part of the window bounds themselves.

During rendering, we call `set_client_inset`, which updates the inset
values and also adjusts the window bounds, increasing their dimensions.
In Zed's case, the inset is 10px, which adds 20px to both the width and
height (10px from each side).

Problem:

When quitting, the full window bounds (which already account for inset)
are saved to the DB. On reopening, these saved bounds are used to create
the window. `set_client_inset` runs again and inflates the dimensions
even more.

Solution:

Store window bounds *without* the inset-inflated dimensions. On the next
session, `set_client_inset` will take care of applying the inset,
resulting window dimensions matching the previous session. This fix is
in the PR.

Alternative Solution:

Another option is to save the inset explicitly in the DB and apply it
during window creation. But this means storing more data, and the inset
would need to be platform-agnostic, which adds complexity. Doesn’t seem
worth it for no real gain.

X11 Before:
```sh
saving window bounds with width: 1136, height: 784
tims@lemon ~/w/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.37s
     Running `target/debug/zed`
saving window bounds with width: 1156, height: 804   <---- +20px
tims@lemon ~/w/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/zed`
saving window bounds with width: 1176, height: 824  <---- +20px
tims@lemon ~/w/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.36s
     Running `target/debug/zed`
saving window bounds with width: 1196, height: 844  <---- +20px
```

X11 After:
```sh
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/zed`
saving window bounds with width: 1116, height: 764
tims@lemon ~/w/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/zed`
saving window bounds with width: 1116, height: 764     <---- same
tims@lemon ~/w/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/zed`
saving window bounds with width: 1116, height: 764   <---- same
```

On Wayland, saving occurs only when you actually resize the window (on
X11, saving happens both on init and while dragging the window). To
trigger saving, I manually resized the window by ~1px to make it print.

Wayland Before:
```sh
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 36s
     Running `target/debug/zed`
saving window bounds with width: 945, height: 577
tims@orange ~/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.77s
     Running `target/debug/zed`
saving window bounds with width: 966, height: 597    <--- +20px on both  (1px increase in width is me resizing)
tims@orange ~/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.87s
     Running `target/debug/zed`
saving window bounds with width: 987, height: 618    <--- +20px on both  (1px increase in width and height is me resizing)
tims@orange ~/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.89s
     Running `target/debug/zed`
saving window bounds with width: 1006, height: 638   <--- +20px on both  (1px decrease in width is me resizing)
```

Wayland After:
```sh
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.82s
     Running `target/debug/zed`
saving window bounds with width: 925, height: 558
tims@orange ~/zed (fix-window-growing-larger)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.84s
     Running `target/debug/zed`
saving window bounds with width: 925, height: 557     <--- same (1px decrease in height is me resizing)
saving window bounds with width: 925, height: 558
```

Release Notes:

- Fix non-maximized zed windows growing larger across sessions on Linux

---------

Co-authored-by: mgsloan@gmail.com <michael@zed.dev>
2024-12-20 22:23:25 +00:00
Michael Sloan
c01403b4b1 Reapply completion docs prefetch (#22306)
Leaving release notes as N/A because it had release notes in the past in
#21705

In #21286, documentation resolution was made more efficient by only
resolving the current completion. However, this meant that single line
documentation shown inline in the menu was missing until scrolled
to. This also meant that it would wait for navigation to resolve
completion docs, leading to lag for displaying documentation.

This change resolves this by attempting to fetch all the completions
that will be shown. It also mostly avoids re-resolving completions. It
intentionally re-resolves the current selection on navigation, as some
language servers will respond with more information later on.

Release Notes:

- N/A
2024-12-20 22:16:07 +00:00
Danilo Leal
8ee04bf04a Fix remote dev project name label (#22307)
Closes https://github.com/zed-industries/zed/issues/21877

<img width="800" alt="Screenshot 2024-12-20 at 6 45 50 PM"
src="https://github.com/user-attachments/assets/2069465f-1a00-47de-b691-585bb2603279"
/>

Release Notes:

- N/A
2024-12-20 22:02:12 +00:00
Richard Feldman
4ed0e5160f Inline assistant v2 layout (#22305)
Makes the inline assistant look like @danilo-leal's prototype.

Also fixes the bug with indent guides that @maxdeviant found!

<img width="1059" alt="Screenshot 2024-12-20 at 4 24 56 PM"
src="https://github.com/user-attachments/assets/5e55a3c2-768a-4e9d-bad5-d4ebbe9db9ce"
/>

Release Notes:

- N/A
2024-12-20 22:00:52 +00:00
Kirill Bulatov
72e56eee7a Use stable Alacritty crate version (#22304)
Release Notes:

- N/A
2024-12-20 21:40:22 +00:00
tims
306fc19739 Fix knockout icon background to match hovered entry background in project panel (#22258)
No issue attached, as this is something trivial and should have been
part of my merged PR
[here](https://github.com/zed-industries/zed/pull/22073).

For context, in the above-mentioned PR, I removed the hover color from
selected/marked entries in the Project Panel. The reasoning behind this
change is already explained in that PR, but to reiterate:

> This change was inspired by the behavior in VSCode, the Firefox
sidebar, and Dolphin (KDE File Manager). When an item is selected, it
doesn’t display a separate hover state to avoid confusing users about
whether the item is selected or merely hovered over.

@nilskch mentioned in the comments of the above PR that I should have
also changed the background of the knockout icon, which appears on
entries, to match the entry background color on hover. This PR addresses
that.

Before:

![Screenshot_20241219_230005](https://github.com/user-attachments/assets/fd517e01-1a1e-4e63-9320-ba24bf3d4c64)

After:

![Screenshot_20241219_225853](https://github.com/user-attachments/assets/35fb146f-83e4-409b-8061-0efbdbba5cf9)

Release Notes:

- N/A
2024-12-20 21:24:38 +00:00
Peter Tripp
4fbb568f42 Improve script/bump-zed-minor-version (#22199)
- Follow up to: https://github.com/zed-industries/zed/pull/22170

CC: @mgsloan 

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
2024-12-20 21:02:57 +00:00
Marshall Bowers
7913b6a5a2 assistant2: Restrict directory context picker to development builds (#22302)
This PR temporarily restricts the directory context picker to
development builds until the implementation is finished.

Release Notes:

- N/A
2024-12-20 19:08:29 +00:00
Marshall Bowers
d566792ae1 assistant2: List directories in directory context picker (#22300)
This PR updates the directory context picker in Assistant2 to show the
available directories.

Release Notes:

- N/A
2024-12-20 18:42:36 +00:00
Piotr Osiewicz
62f5ca562e editor: Improve performance of lsp_ext validation (#22299)
We've received a complaint on Discord about bad multicursor performance.
I too run 15k cursor simultaneously. The gist of the issue was in the
lsp_ext; whenever we gather up actions to be registered on a buffer, we
need to know whether a buffer has any of the languages for which we have
LSP extensions. The problem stemed from the fact that we did a two-phase
filtering. For each selection we'd first check whether this selection
lies in a part of a file that is part of a language for which we have
LSP extensions. Then, we'd check whether we're running a language server
of interest for this buffer.

This is not optimal, because it would often do the redundant work:
1. We resolve selections for buffer that are known to not contain a
given language server.
2. We look up language server in the LspStore once per each matching
selection.

In case where the file is not related at all, we end up resolving all of
the selections which is pretty bad. This PR makes us skip buffers which
are known to not match the criteria. It also caches the result of
language server lookup for the buffers.

Closes #ISSUE

Release Notes:

- Improved performance with large quantity of cursors
2024-12-20 18:41:23 +00:00
Piotr Osiewicz
4eb8492308 chore: Remove stray println (#22297)
Follow-up to #22292

Release Notes:

- N/A
2024-12-20 18:18:44 +00:00
Thorsten Ball
d824baeece Fix lang servers status set to Downloading when checking version (#22292)
This message has confused me many times too: we printed the status as
"Downloading" when we were only checking whether we need to install a
given version of a language server.

This fixes the issue for Node-based language servers where we had the
same check in all implementations.

Closes  #22241

Release Notes:

- Fixed some language servers reporting status as "Downloading..." when
only a version check was being done.
2024-12-20 16:59:10 +00:00
Marshall Bowers
8a858fee7c Enable Assistant2 outside of development builds (#22294)
This PR removes the gate that limited Assistant2 to development builds,
so that we can start testing it out in Nightly.

Note that currently this still requires explicit opt-in to the
`assistant2` feature flag.

Release Notes:

- N/A
2024-12-20 16:43:24 +00:00
Alejandro Gómez-Londoño
cbd2e81a7e Add arrow key movements to terminal vi mode (#22103)
Expands #18715

Release Notes:

- Added arrow keys movement to the built-in terminal's [vi
mode](https://github.com/alacritty/alacritty/blob/master/docs/features.md#vi-mode)
(which is using Alacritty under the hood).

Details
--
A minuscule improvement on #18715 to allow user with alternative
keyboard layouts to use the terminal's vi mode with the arrow keys.
2024-12-20 13:49:12 +00:00
Thorsten Ball
b25d8ecb75 vim: Fix deletion with sentence-motion (#22289)
Fixes #22151.

Turns out Vim also has some weird behavior with sentence deletion in
case it's on the first character of a line.

Release Notes:

- vim: Fixed deleting sentence-wise (i.e. `d(` and `d)`), which would
previously delete the whole line instead of just a sentence.
2024-12-20 12:48:38 +00:00
Aaron Feickert
7c03e11cfc Fix blank line cursor width (#22275)
This PR ensures that, for fixed-width fonts, the cursor width is the
same on blank lines as on non-blank lines, as well as at the end of a
line. It does so by using the em advance to define the cursor width
instead of the em width in these cases.

Note that this can look... bizarre on non-fixed-width fonts:
<img width="93" alt="Screenshot 2024-12-19 at 21 43 11"
src="https://github.com/user-attachments/assets/a4c9b26c-98ea-4a1d-947b-51f1acd3c2f8"
/>
However, this is arguably reasonably consistent with how (terminal) Vim
handles it:
<img width="45" alt="Screenshot 2024-12-19 at 21 46 42"
src="https://github.com/user-attachments/assets/ec3ff614-7a15-4cc3-8d14-3d15ce62f2b8"
/>

Closes #22260.

Release Notes:

- N/A
2024-12-20 10:26:27 +00:00
Michael Sloan
f3fc4d6279 Add a CI check for todo! and FIXME comments (#21950)
Motivation for this is to support writing comments that will certainly
be revisited before merge.

Release Notes:

- N/A
2024-12-20 08:38:50 +00:00
tims
e4493d60dc linux: Fix wrong cursor theme for arrow cursor style (#22276)
Closes #22264

On Linux, the arrow cursor style currently used by Zed is `arrow`.
However, this style might not be available in most themes, causing the
cursor to fall back to system default theme. Note cursor style are
platform (X11 and Wayland) agnostic.

Most themes use `left_ptr` as their arrow cursor style instead of
`arrow`. In some cases, `left_ptr` and `arrow` are symlinks pointing to
the `default` style, but the `default` style is not guaranteed to be
available across all themes.

After inspecting the available cursor themes on popular desktop
environments, changing the default from `arrow` to `left_ptr` seems to
be available in all of them. `left_ptr` as default cursor style is also
mentioned in [Arch Wiki: Cursor
themes](https://wiki.archlinux.org/title/Cursor_themes#Change_X_shaped_default_cursor).

KDE:
```sh
tims@lemon /u/s/icons> find . -name "arrow"
./Breeze_Snow/cursors/arrow
./breeze_cursors/cursors/arrow
./Adwaita/cursors/arrow

tims@lemon /u/s/icons> find . -name "default"
./default
./Breeze_Snow/cursors/default
./breeze_cursors/cursors/default
./Adwaita/cursors/default

tims@lemon /u/s/icons> find . -name "left_ptr"
./Oxygen_White/cursors/left_ptr
./KDE_Classic/cursors/left_ptr
./Oxygen_Yellow/cursors/left_ptr
./Oxygen_Blue/cursors/left_ptr
./Oxygen_Black/cursors/left_ptr
./Breeze_Snow/cursors/left_ptr
./breeze_cursors/cursors/left_ptr
./Adwaita/cursors/left_ptr
./Oxygen_Zion/cursors/left_ptr

```

Gnome:
```sh
tims@orange:/usr/share/icons$ find . -name "arrow"
./DMZ-Black/cursors/arrow
./Adwaita/cursors/arrow
./redglass/cursors/arrow
./whiteglass/cursors/arrow
./handhelds/cursors/arrow
./Yaru/cursors/arrow
./DMZ-White/cursors/arrow

tims@orange:/usr/share/icons$ find . -name "default"
./Adwaita/cursors/default
./default
./Yaru/cursors/default

tims@orange:/usr/share/icons$ find . -name "left_ptr"
./DMZ-Black/cursors/left_ptr
./Adwaita/cursors/left_ptr
./redglass/cursors/left_ptr
./whiteglass/cursors/left_ptr
./handhelds/cursors/left_ptr
./Yaru/cursors/left_ptr
./DMZ-White/cursors/left_ptr
```

My theme is set to Oxygen Yellow here.

Before:
<img
src="https://github.com/user-attachments/assets/7485f1e7-5936-45b4-96bd-399525bad95d"
alt="before" width="450px" />

After:
<img
src="https://github.com/user-attachments/assets/56090735-6a1f-4652-ad3e-075ff4c3f9ab"
alt="after" width="450px" />


Release Notes:

- Fixed wrong cursor theme for arrow cursor style on Linux.
2024-12-20 05:57:47 +00:00
Max Brunsfeld
3632b36fde Move multibuffer tests to their own source file (#22270)
Release Notes:

- N/A
2024-12-20 02:02:32 +00:00
renovate[bot]
97e11fd5d2 Update aws-sdk-rust monorepo (#22215)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aws-config](https://redirect.github.com/smithy-lang/smithy-rs) |
dependencies | patch | `1.5.10` -> `1.5.11` |
| [aws-sdk-kinesis](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.52.0` -> `1.53.0` |
| [aws-sdk-s3](https://redirect.github.com/awslabs/aws-sdk-rust) |
dependencies | minor | `1.65.0` -> `1.66.0` |

---

### Configuration

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

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

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

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:15:25 +00:00
renovate[bot]
150aa03c5f Update Rust crate hyper to v0.14.32 (#22207)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [hyper](https://hyper.rs)
([source](https://redirect.github.com/hyperium/hyper)) |
workspace.dependencies | patch | `0.14.31` -> `0.14.32` |

---

### Release Notes

<details>
<summary>hyperium/hyper (hyper)</summary>

###
[`v0.14.32`](https://redirect.github.com/hyperium/hyper/releases/tag/v0.14.32)

[Compare
Source](https://redirect.github.com/hyperium/hyper/compare/v0.14.31...v0.14.32)

#### Features

- **server:** add `Builder::max_pending_accept_reset_streams(num)`
option
([a24f0c0](a24f0c0af8))

#### Bug Fixes

- **http1:** fix intermittent panic parsing partial headers
([0f274ae](0f274ae653))

#### New Contributors

- [@&#8203;cratelyn](https://redirect.github.com/cratelyn) made their
first contribution in
[https://github.com/hyperium/hyper/pull/3796](https://redirect.github.com/hyperium/hyper/pull/3796)

**Full Changelog**:
https://github.com/hyperium/hyper/compare/v0.14.31...v0.14.32

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:14:22 +00:00
renovate[bot]
ebf6115c3c Update Rust crate semver to v1.0.24 (#22211)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [semver](https://redirect.github.com/dtolnay/semver) |
workspace.dependencies | patch | `1.0.23` -> `1.0.24` |

---

### Release Notes

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

###
[`v1.0.24`](https://redirect.github.com/dtolnay/semver/releases/tag/1.0.24)

[Compare
Source](https://redirect.github.com/dtolnay/semver/compare/1.0.23...1.0.24)

- Optimize Ord impls for semver::Prerelease and semver::BuildMetadata
([#&#8203;328](https://redirect.github.com/dtolnay/semver/issues/328),
thanks [@&#8203;Eh2406](https://redirect.github.com/Eh2406))

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:14:10 +00:00
renovate[bot]
394af7481d Update Rust crate tree-sitter-c to v0.23.4 (#22212)
This PR contains the following updates:

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

---

### Release Notes

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

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

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

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

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:13:51 +00:00
renovate[bot]
7b0d63fffb Update Rust crate tree-sitter-json to 0.24 (#22226)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[tree-sitter-json](https://redirect.github.com/tree-sitter/tree-sitter-json)
| workspace.dependencies | minor | `0.23` -> `0.24` |

---

### Release Notes

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

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

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.7...v0.24.8)

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

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

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.5...v0.24.7)

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

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

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.4...v0.24.5)

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

###
[`v0.24.4`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.3...v0.24.4)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.3...v0.24.4)

###
[`v0.24.3`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.2...v0.24.3)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.2...v0.24.3)

###
[`v0.24.2`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.1...v0.24.2)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.1...v0.24.2)

###
[`v0.24.1`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.0...v0.24.1)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.24.0...v0.24.1)

###
[`v0.24.0`](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.23.0...v0.24.0)

[Compare
Source](https://redirect.github.com/tree-sitter/tree-sitter-json/compare/v0.23.0...v0.24.0)

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 01:13:17 +00:00
tims
f64bfe8c1d linux: Fix saving file with root ownership (#22045)
Closes #13585

Currently, saving files with `root` ownership or `root` as the group
throws a `Permission denied (os error 13). Please try again.` error.
This PR fixes the issue on Linux by prompting the user for a password
and saving the file with elevated privileges.

It uses `pkexec` (Polkit), which is by default available on GNOME, KDE,
and most Linux systems. I haven't implemented this for macOS as I don't
have a device to test it on.

This implementation is similar to how Vscode handles it. Except, they
don't show custom message.

**Working**:

When file saving fails due to a `PermissionDenied` error, we create a
temporary file in the same directory as the target file and writes the
data to this temporary file. After, the contents of this file are copied
to the original file using the `tee` command instead of `cp` or `mv`.
This ensures that the ownership and permissions of the original file are
preserved. This command is executed using `pkexec` which will prompt
user for their password.

**Custom Message**:

The message displayed to the user in the prompt is automatically
retrieved from the `org.zed.app.policy` file, which is located at
`/usr/share/polkit-1/actions/`. This file should be installed during the
setup process. While the policy file is optional, omitting it will cause
the user to see the underlying command being executed rather than a
user-friendly message. Currently, VSCode does not display the
user-friendly message.

The policy file must specify a unique binary, ensuring that only that
binary can use the policy file. It cannot be as generic as a
`/bin/bash`, as any software using bash to prompt will end up showing
Zed’s custom message. To address this, we will create a custom bash
script, as simple as the following, placed in `/usr/bin/zed/elevate.sh`.
The script should have root ownership and should not reside in the home
directory, since the policy file cannot resolve `$HOME`.

```sh
#!/bin/bash
eval "$@"
```

*IMPORTANT NOTE*

Since copying the policy file and our script requires sudo privileges,
the installation script will now prompt for the password at very end.
Only on Linux, if `pexec` is installed.

Screenshots:

KDE with policy file:
![Screenshot from 2024-12-15
22-13-06](https://github.com/user-attachments/assets/b8bb7565-85df-4c95-bb10-82e50acf9b56)

Gnome with policy file:
![Screenshot from 2024-12-15
22-21-48](https://github.com/user-attachments/assets/83d15056-a2bd-41d9-a01d-9b8954260381)

Gnome without policy file:

![image](https://github.com/user-attachments/assets/66c39d02-eed4-4f09-886f-621b6d37ff43)

VSCode:

![image](https://github.com/user-attachments/assets/949dc470-c3df-4e2f-8cc6-31babaee1d18)

User declines the permission request:

![image](https://github.com/user-attachments/assets/c5cbf056-f6f9-43a8-8d88-f2b0597e14d6)

Release Notes:

- Fixed file saving with root ownership on Linux.
2024-12-19 22:16:01 +00:00
Michael Sloan
5b86845605 Fix docs for Bounds::from_corner_and_size (#22265)
(left in a todo!, oops)

Release Notes:

- N/A
2024-12-19 21:50:52 +00:00
Marshall Bowers
9782abf3c5 ci: Put docs-only conditionals on each step (#22261)
This PR is a follow up to #22254 with a different approach.

We need to put the conditional on each step in order to skip them, as I
couldn't see any other way of bailing out of the pipeline early based on
a condition.

Release Notes:

- N/A
2024-12-19 19:46:10 +00:00
Cole Miller
6231072d85 Hide chat panel button when not in a call (#22200)
cc @nathansobo 

Release Notes:

- Hide chat panel button by default when not in a call
2024-12-19 19:32:45 +00:00
Cole Miller
2094d50514 Fix permalink-to-line when Git repo root and worktree dir don't coincide (#22003)
Closes #21505. This should work if the git dir is an ancestor of the
worktree dir or vice versa.

Release Notes:

- Fixed GitHub permalink-to-line actions when worktree dir and Git dir
aren't the same
2024-12-19 19:23:50 +00:00
Ringo De Smet
1e2fa3b022 Update macOS system requirements in docs (#22248)
Update macOS system requirements to include Sequoia (15.x), the latest
major version.

Release Notes:

- N/A

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-12-19 17:55:46 +00:00
Piotr Osiewicz
8e81070091 terminal: Clear output after venv is activated (#22256)
The command used to activate the venv can still be accessed/scrolled to
if needed.

Release Notes:

- The Python virtual environment activation command is no longer shown
in the terminal output by default.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-19 17:43:19 +00:00
Thorsten Ball
1071814d41 zeta: Always include current file's outline in telemetry (#22257)
This doesn't include the outline in the prompt yet, but it does send it
up via telemetry so we can use it to see whether it would have improved
generated output.

Release Notes:

- N/A

Co-authored-by: Agus <agus@zed.dev>
2024-12-19 17:17:10 +00:00
Marshall Bowers
3d3d8f20eb ci: Run required status checks for docs-only PRs, but exit early (#22254)
I noticed a problem with the addition of required status checks where
PRs that only touched the docs wouldn't pass the status checks due to
all of the checks being skipped via `paths-ignore`:

<img width="950" alt="Screenshot 2024-12-19 at 11 16 38 AM"
src="https://github.com/user-attachments/assets/a6fa43ee-de63-40a6-a15a-f2f3519e9db8"
/>

This PR aims to address this by making it so required status checks run
for docs-only PRs, but exit early (before doing all of the work).

Release Notes:

- N/A
2024-12-19 17:05:11 +00:00
Aaron Feickert
536a958c58 Fix cursor overlap (#21999)
When the cursor has a block shape and is not on the first line, its name
popup exhibits an overlap:
<img width="171" alt="Screenshot 2024-12-13 at 18 00 54"
src="https://github.com/user-attachments/assets/1dc2ef93-020b-45c4-9fc6-db7d97f65c62"
/>

This occurs because the popup's horizontal alignment is set differently
when the cursor
[is](fff12ec1e5/crates/editor/src/element.rs (L6383))
or
[isn't](fff12ec1e5/crates/editor/src/element.rs (L6385))
on the first line.

This PR makes the horizontal alignment the same in both cases, which
removes the overlap:
<img width="176" alt="Screenshot 2024-12-13 at 17 57 20"
src="https://github.com/user-attachments/assets/a3c10ed5-6a1b-4040-9408-92290e9da30b"
/>

Closes #21887.

Release Notes:

- Fixed an overlap that cuts off user names when a cursor has a block
shape.
2024-12-19 16:24:30 +00:00
Thorsten Ball
96ad022cd7 Fix project environment not working correctly with multiple worktrees (#22246)
Fixes https://github.com/zed-industries/zed/issues/21972

This fixes two bugs:

**Bug 1**: this bug caused us to only ever load a single environment in
a multi-worktree project, thanks to this line:

```rust
if let Some(task) = self.get_environment_task.as_ref()
```

We'd only ever run a single task per project, which is wrong.

What does code does is to cache the tasks per `worktree_id`, which means
we don't even need to cache the environments again, since we can just
cache the `Shared<Task<...>>`.

**Bug 2**: we assumed that every `worktree_abs_path` is a directory,
which lead to `Failed to run direnv` log messages when opening a project
that had a worktree with a single file open (easy to reproduce: open a
normal project, open your settings, close Zed, reopen it — the settings
faile caused environments to not load)

It's fixed by checking whether the `worktree_abs_path` is an absolute
directory. Since this is always running locally, it's fine to use
`smol::fs` here instead of using our `Fs`.

Release Notes:

- Fixed shell environments not being loaded properly to be used by
language servers and terminals in case a project had multiple worktrees.
- Fixed `Failed to run direnv` messages showing up in case Zed restored
a window that contained a worktree with a single file.
https://github.com/zed-industries/zed/issues/21972
2024-12-19 14:50:14 +00:00
Agus Zubiaga
11260e6d37 Match keymap-style action names in command palette (#22149)
For example, `editor::TabPrev` matches "editor: tab prev".

Release Notes:

- Added support for searching command palette using keymap-style action
names.

---------

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-19 12:48:54 +00:00
Thorsten Ball
d54662e683 zeta: Do not change user's menu selection if completion is late (#22238)
Release Notes:

- N/A
2024-12-19 09:42:29 +00:00
Thorsten Ball
2a17274ec2 zeta: Always show ghost text, even if showing completion in menu (#22194)
See:
![screenshot-2024-12-18-17 26
22@2x](https://github.com/user-attachments/assets/82bdea76-6c96-4c4d-a08a-1601381a378f)


https://github.com/user-attachments/assets/165d9f0d-8339-4fd9-a796-a67121689af6



Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2024-12-19 09:00:58 +00:00
Michael Sloan
b93cee8d27 Use static LazyLocks for all constant regexes (#22225)
Release Notes:

- N/A
2024-12-19 02:20:35 +00:00
Peter Tripp
837bbc851f astro: Bump to v0.1.2 (#22220)
Includes:
- https://github.com/zed-industries/zed/pull/20206

Release Notes:

- N/A
2024-12-18 23:22:24 +00:00
Peter Tripp
0fe88a88b1 elixir: Bump to v0.1.2 (#22219)
Includes:
- https://github.com/zed-industries/zed/pull/21666
- https://github.com/zed-industries/zed/pull/20206

Release Notes:

- N/A
2024-12-18 23:22:19 +00:00
renovate[bot]
2b4f0deff5 Update Rust crate async-tungstenite to v0.28.2 (#22206)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[async-tungstenite](https://redirect.github.com/sdroege/async-tungstenite)
| workspace.dependencies | patch | `0.28.1` -> `0.28.2` |

---

### Release Notes

<details>
<summary>sdroege/async-tungstenite (async-tungstenite)</summary>

###
[`v0.28.2`](https://redirect.github.com/sdroege/async-tungstenite/blob/HEAD/CHANGELOG.md#0282---2024-12-15)

[Compare
Source](https://redirect.github.com/sdroege/async-tungstenite/compare/0.28.1...0.28.2)

##### Fixed

- Add `alloc` feature to `futures_task` dependency to make sure
`futures-task::ArcWake` is available.

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 23:05:46 +00:00
Marshall Bowers
4f2ab812fb ci: Enable merge_group for workflows (#22214)
Enable the `merge_group` for CI workflows so that they work with the
merge queue.

Release Notes:

- N/A
2024-12-18 22:59:20 +00:00
Michael Sloan
3f40d76be4 Enable search within diagnostics pane (#22213)
Closes #16033
Addresses one part of #4475

Release Notes:

- Added support for find and replace in diagnostics. Also causes
keybindings that use search to now work (such as `*` and `#` vim
bindings).
2024-12-18 22:52:16 +00:00
Michael Sloan
6bb21b1e5e Fix vim repeat (.) and macro playback insertions in diagnostics and assistant (#22210)
Release Notes:

- Fixed vim repeat (`.`) and macro playback of insertions in diagnostics
and assistant.

Co-authored-by: Conrad <conrad@zed.dev>
2024-12-18 15:30:52 -07:00
tims
f7a7866d4a linux: Implement Menus (#21873)
Closes #19837

This PR implements menus for Linux and Windows, inspired by JetBrains
IDEs. Thanks to @notpeter for the inspiration.


https://github.com/user-attachments/assets/7267fcdf-fec5-442e-a53b-281f89471095

I plan to complete this in multiple parts. While this PR delivers a
fully functional menus, there are many UX improvements that can be done.
So, this is part 1 of 3.

**This PR**:
- [x] Clicking the application menu opens the first menu popup. This
also shows other available menus.
- [x] While a menu is open, hovering over other menus opens them without
needing a click.
- [x] Up/down arrow keys works out of the box. Thanks GPUI. 

**Future - Part 2**:
- Add keybinding support to open specific menus using `Option + first
character of menu item`.
- Add support for left/right arrow keys to move between menus.

**Future - Part 3**:
- Implement nested context menus in GPUI for submenus. (I haven't
checked if this already exists).

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-12-18 17:27:25 -05:00
Dzmitry Malyshau
298b9df589 Switch to a single GPU context in Blade (#20853)
Closes #17005

Release Notes:

- Improved GPU context management: share a single context with multiple
surfaces.

### High Level

Blade got a proper support for Surface objects in
https://github.com/kvark/blade/pull/203.
That was mainly motivated by Zed needing to draw multiple windows. With
the Surface API, Zed is now able to have the GPU context tied to the
"Platform" instead of "Window". Practically speaking, this means:
  - architecture more sound
  - faster to open/close windows
  - less surprises, more robust

### Concerns

1. Zed has been using a temporary workaround for the platform bug on
some Intel+Nvidia machines that makes us unable to present -
https://github.com/kvark/blade/pull/144 . This workaround is no longer
available with the new architecture. I'm looking for ideas on how to
approach this better.
- we are now picking up the change in
https://github.com/kvark/blade/pull/210, which allows forcing a specific
Device ID. This should allow Zed users to work around the issue. We
could help them to automate it, too.
2. ~~Metal-rs dependency is switched to
https://github.com/kvark/metal-rs/tree/blade, since upstream isn't
responsive in merging changes that are required for Blade. Hopefully,
temporary.~~
- ~~we can also hack around it by just transmuting the texture
references, since we know those are unchanged in the branch. That would
allow Blade to use it's own version of Metal, temporarily, if switching
metal-rs in the workspace is a concern.~~
- merged my metal-rs changes and updated Zed to use the upstream github
reference

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-12-18 14:47:09 -07:00
Agus Zubiaga
56d20fc0a3 Reuse prompt editor across buffer and terminal assist (#22188)
Builds on https://github.com/zed-industries/zed/pull/22160 and extracts
the rest of `PromptEditor` so it can be shared across terminal and
inline assistants. This will help avoid the UI drifting as we have
already observed.

Note: This is mostly a mechanical refactor. I imagine some things could
be factored in a better way by someone with more context, but I think
this is a good start.

Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
2024-12-18 16:12:54 -05:00
Marshall Bowers
fc00eaa161 assistant2: Reduce message editor's maximum height (#22205)
This PR reduces the message editor's maximum height to 10 lines, after
which point it will scroll.

Release Notes:

- N/A
2024-12-18 16:11:31 -05:00
renovate[bot]
7414e91a85 Update actions/upload-artifact digest to 6f51ac0 (#22203)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | digest | `b4b15b8` -> `6f51ac0` |

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:59:38 +02:00
renovate[bot]
b51a162d22 Update Rust crate tiktoken-rs to 0.6.0 (#21900)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tiktoken-rs](https://redirect.github.com/zurawiki/tiktoken-rs) |
workspace.dependencies | minor | `0.5.9` -> `0.6.0` |

---

### Release Notes

<details>
<summary>zurawiki/tiktoken-rs (tiktoken-rs)</summary>

###
[`v0.6.0`](https://redirect.github.com/zurawiki/tiktoken-rs/releases/tag/v0.6.0)

[Compare
Source](https://redirect.github.com/zurawiki/tiktoken-rs/compare/v0.5.9...v0.6.0)

**Minor version release signifies a breaking change in 0.x**

#### What's Changed

- Add support for chatgpt-4o-latest by
[@&#8203;Congyuwang](https://redirect.github.com/Congyuwang) in
[https://github.com/zurawiki/tiktoken-rs/pull/85](https://redirect.github.com/zurawiki/tiktoken-rs/pull/85)
- Refactor internals to make future updates to tiktoken easier to merge
-   Do not expose tiktoken internal modules and functions
-   Update dependencies

#### New Contributors

- [@&#8203;Congyuwang](https://redirect.github.com/Congyuwang) made
their first contribution in
[https://github.com/zurawiki/tiktoken-rs/pull/85](https://redirect.github.com/zurawiki/tiktoken-rs/pull/85)

**Full Changelog**:
https://github.com/zurawiki/tiktoken-rs/compare/v0.5.9...v0.6.0

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:56 +02:00
renovate[bot]
78dde63337 Update Rust crate heed to 0.21.0 (#21892)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [heed](https://redirect.github.com/Kerollmops/heed) |
workspace.dependencies | minor | `0.20.1` -> `0.21.0` |

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:48 +02:00
renovate[bot]
c0b40d0bd0 Update Rust crate cargo_toml to 0.21 (#21880)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [cargo_toml](https://lib.rs/cargo_toml)
([source](https://gitlab.com/lib.rs/cargo_toml)) |
workspace.dependencies | minor | `0.20` -> `0.21` |

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:30 +02:00
renovate[bot]
0acb743dac Update 2428392/gh-truncate-string-action action to v1.4.1 (#22204)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[2428392/gh-truncate-string-action](https://redirect.github.com/2428392/gh-truncate-string-action)
| action | patch | `v1.4.0` -> `v1.4.1` |

---

### Release Notes

<details>
<summary>2428392/gh-truncate-string-action
(2428392/gh-truncate-string-action)</summary>

###
[`v1.4.1`](https://redirect.github.com/2428392/gh-truncate-string-action/releases/tag/v1.4.1)

[Compare
Source](https://redirect.github.com/2428392/gh-truncate-string-action/compare/v1.4.0...v1.4.1)

#### What's Changed

- update action.yml inputs to include truncationSymbol by
[@&#8203;aballman](https://redirect.github.com/aballman) in
[https://github.com/2428392/gh-truncate-string-action/pull/14](https://redirect.github.com/2428392/gh-truncate-string-action/pull/14)

#### New Contributors

- [@&#8203;aballman](https://redirect.github.com/aballman) made their
first contribution in
[https://github.com/2428392/gh-truncate-string-action/pull/14](https://redirect.github.com/2428392/gh-truncate-string-action/pull/14)

**Full Changelog**:
https://github.com/2428392/gh-truncate-string-action/compare/v1...v1.4.1

</details>

---

### Configuration

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

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

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

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

---

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

---

Release Notes:

- N/A

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:24:02 +02:00
Marshall Bowers
8b2afab0d3 assistant2: Wire up basic @-mention interaction for context (#22197)
This PR adds an initial version of using `@` in the message editor to
add context to the thread.

We don't yet insert any sort of reference to it in the message body
itself.

Release Notes:

- N/A
2024-12-18 13:29:39 -05:00
Peter Tripp
b79117c882 Bump Zed to v0.168.x (#22198) 2024-12-18 12:03:40 -05:00
Conrad Irwin
77abf13f42 Allow telemetry::event! with no properties (#22190)
CC @josephTLyons

Release Notes:

- N/A
2024-12-18 09:41:47 -07:00
Michael Sloan
433cb99170 Remove main push from bump-zed-minor-versions script (#22170)
Motivation for this is to allow the `main` branch to be protected. This will enable use of auto-merge and merge queue.

Co-authored-by: Peter Tripp <peter@zed.dev>
2024-12-18 11:32:58 -05:00
Marshall Bowers
670ade9546 extensions_ui: Don't block scrolling on disabled extension cards (#22189)
This PR fixes an issue where disabled extension cards would block scroll
events when the mouse was over them.

Instead of using `.occlude`—which captures _all_ mouse events—we use
`.block_mouse_down` to just block mouse down events (to prevent clicking
the buttons on the disabled card). We also set the cursor style to the
default, as it was still switching to the pointer when hovered over top
of one of the buttons.

Release Notes:

- Fixed disabled extensions blocking scroll events in the extensions
list.
2024-12-18 11:20:58 -05:00
Conrad Irwin
4a6f071fde vim: Add support for :g/ and :v/ (#22177)
Closes #ISSUE

Still TODO to make this feature good is better command history

Release Notes:

- vim: Add support for `:g/<pattern>/<cmd>` and `:v/<pattern>/<cmd>`
2024-12-18 08:28:42 -07:00
Conrad Irwin
2ecbd97fe8 vim: Support count with [x and ]x (#22176)
Fixes: #21577
Fixes: #17245

Release Notes:

- vim: Add <count> support for [x/]x
2024-12-18 08:28:22 -07:00
Thorsten Ball
a0a095c6a3 zeta: Show deletions when inline completion is shown in menu (#22186)
If an inline completion isn't shown in a menu, we highlight text in the
editor as deleted.

But if it's shown in the menu, we didn't even show deleted text, which
makes it hard to understand what's going on.

This fixes it.
![screenshot-2024-12-18-14 34
55@2x](https://github.com/user-attachments/assets/579639e4-5ed9-4fe6-8e21-65166d192432)


Release Notes:

- N/A
2024-12-18 14:46:51 +01:00
Richard Feldman
4bfc107e3a Fix inconsistencies in "Transform" vs "Generate" tooltips for assistant v2 (#22160)
Also makes the inline assistant and inline terminal assistant share a
bunch more code.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
2024-12-18 07:10:55 -05:00
Thorsten Ball
6192c83f23 Show inline completions in menu only for supported providers (#22181)
This changes #22093 by making the change only have an effect for the
supported provider: Zeta.

Made the change because the UX is still experimental and I don't want to
break existing workflows for Copilot/Supermaven users.

Even Zeta users can opt-out of it by setting
`"show_inline_completions_in_menu": false` in their settings, in case
they want the old show-inline-completion-or-show-lsp-completion behavior
back.

Release Notes:

- N/A
2024-12-18 12:55:09 +01:00
Hai Zhu
e8c9283700 Fix the illegal JSON in the documentation (#22175)
Release Notes:

- N/A
2024-12-18 11:10:13 +02:00
Thorsten Ball
2469122784 Fix panic when calculating inline completion (#22180)
Possible panic here in case we can't find the excerpt in the
multibuffer.

I thought this could happen when the inline completion references an
excerpt that disappeared from the multibuffer, but looking at the code
I'm not sure anymore - we use the same multibuffer snapshot in this
whole function and the `anchor_in_excerpt` method clips the anchors.

Still, let's be safe here.

Release Notes:

- N/A
2024-12-18 09:53:34 +01:00
Conrad Irwin
6898a31f06 Fix panic in linked editing ranges (#21905)
We were committing the cardinal sin of subtracting one from a column
without clipping

Closes #ISSUE

Release Notes:

- Fixed a panic in linked editing ranges
2024-12-17 22:17:08 -07:00
Conrad Irwin
94bfb93d35 More telemetry events (#22171)
- **Convert more events to telemetry::event**
- **And call events**

Closes #ISSUE

Release Notes:

- N/A
2024-12-17 22:16:59 -07:00
Enrique Kessler Martínez
1b83020dc8 Update collab local development instructions (#22018)
When running the collab server locally on a new machine, I found there
were some instructions missing for setting up the Postgres database.
Namely, the user mentioned on the `.env.toml` is not created by default.

Ref: https://github.com/zed-industries/zed/issues/8260

Release Notes:

- N/A
2024-12-17 20:43:48 -07:00
Cole Miller
613deb6421 Remove panics from X11Window::set_title (#22173)
Release Notes:

- N/A
2024-12-17 22:15:35 -05:00
Cole Miller
672fc76832 Fix panic in activate_pane_in_direction (#21948)
Ah, invalid SecondaryMap key, we meet again...

Release Notes:

- Fixed panic when jumping between panes
2024-12-17 22:10:30 -05:00
Michael Sloan
5d7b6141fd Improve context menu aside layout via custom logic (#22154)
* Presence of the aside no longer affects position or size of the
context menu.

* Prefers to fit to the right, then on same side of line, then other
side of line, within the following preference order:
  - Max possible size within text area.
  - Max possible size within window.
- Actual size within window. This is the only case that could cause it
to jump around with less stability.

A further enhancement atop this might be to dynamically resize aside
height to fit.

Release notes are N/A as they are covered by the notes for #22102.

Closes #8523

Release Notes:

* N/A
2024-12-17 17:01:15 -07:00
Marshall Bowers
6aad616165 assistant2: Fix focus issues with opening history (#22169)
This PR fixes some focus issues with opening the thread history, which
required that the Assistant2 panel be focused in order for the history
to open.

Release Notes:

- N/A
2024-12-17 18:07:28 -05:00
Marshall Bowers
918866b7de assistant2: Allow creating a new thread via the command palette (#22168)
This PR allows a new thread to be created via the command palette when
the Assistant2 panel is not focused.

Release Notes:

- N/A
2024-12-17 17:51:47 -05:00
Peter Tripp
5b2653a1d1 Improve GitHub Issue template for Environment (#22147)
Add support for pre-filling feature request template from zed::RequestFeature action.

Co-authored-by: Agus <agus@zed.dev>
2024-12-17 17:28:47 -05:00
Marshall Bowers
ba44db7f49 gpui: Rename GPUSpecs to GpuSpecs (#22166)
This PR renames the `GPUSpecs` type to `GpuSpecs` to match Rust naming
conventions.

Release Notes:

- N/A
2024-12-17 17:22:53 -05:00
Danilo Leal
ce97e4ddc1 assistant2: Add temporary icon for the status bar item (#22156)
This is temporary and should be removed whenever we fully transition to
Assistant 2.

<img width="800" alt="Screenshot 2024-12-17 at 16 50 48"
src="https://github.com/user-attachments/assets/61137993-ec2c-4754-9f9a-7559d1b21d89"
/>

Release Notes:

- N/A
2024-12-17 16:57:47 -05:00
Marshall Bowers
63d8a43f9d assistant2: Fix file context picker (#22163)
This PR fixes an issue with the Assistant2 file context picker where
files weren't being attached properly.

We needed to retrieve the files from the worktree without the worktree
root name in the file path.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2024-12-17 16:53:56 -05:00
451 changed files with 20646 additions and 12558 deletions

View File

@@ -18,8 +18,11 @@ body:
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
label: Zed Version and System Specs
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
placeholder: |
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `request feature` and this field will be autopopulated -->
validations:
required: true
- type: textarea

View File

@@ -20,8 +20,13 @@ body:
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below. If you are unable to run the command, please include your Zed version and release channel, operating system and version, RAM amount, and architecture.
label: Zed Version and System Specs
description: Zed version, release channel, architecture (x86_64 or aarch64), OS (macOS version / Linux distro and version) and RAM amount.
placeholder: |
<!-- In Zed run `copy system specs into clipboard` from the Zed command palette and paste here. -->
<!-- Alternatively spawn `file bug report` and this field will be autopopulated -->
<!-- If Zed won't launch, include the equivalent with other relevant details (e.g. video card driver version for display bugs, etc) -->
<!-- Zed Version: 0.xxx.x; Channel: Stable, OS: macOS xx.xx, RAM: XXGB, Architecture: x86_64"
validations:
required: true
- type: textarea

View File

@@ -7,14 +7,10 @@ on:
- "v[0-9]+.[0-9]+.x"
tags:
- "v*"
paths-ignore:
- "docs/**"
pull_request:
branches:
- "**"
paths-ignore:
- "docs/**/*"
- ".github/workflows/community_*"
merge_group:
concurrency:
# Allow only one workflow per any non-`main` branch.
@@ -28,6 +24,31 @@ env:
RUSTFLAGS: "-D warnings"
jobs:
check_docs_only:
runs-on: ubuntu-latest
outputs:
docs_only: ${{ steps.check_changes.outputs.docs_only }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Check for non-docs changes
id: check_changes
run: |
if [ "${{ github.event_name }}" == "merge_group" ]; then
# When we're running in a merge queue, never assume that the changes
# are docs-only, as there could be other PRs in the group that
# contain non-docs changes.
echo "Running in the merge queue"
echo "docs_only=false" >> $GITHUB_OUTPUT
elif git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
echo "Detected non-docs changes"
echo "docs_only=false" >> $GITHUB_OUTPUT
else
echo "Docs-only change"
echo "docs_only=true" >> $GITHUB_OUTPUT
fi
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
if: github.repository_owner == 'zed-industries'
@@ -81,6 +102,10 @@ jobs:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
# To support writing comments that they will certainly be revisited.
- name: Check for todo! and FIXME comments
run: script/check-todos
- name: Run style checks
uses: ./.github/actions/check_style
@@ -96,6 +121,7 @@ jobs:
runs-on:
- self-hosted
- test
needs: check_docs_only
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -103,29 +129,35 @@ jobs:
clean: false
- name: cargo clippy
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/clippy
- name: Check unused dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: bnjbvr/cargo-machete@main
- name: Check licenses
if: needs.check_docs_only.outputs.docs_only == 'false'
run: |
script/check-licenses
script/generate-licenses /tmp/zed_licenses_output
- name: Check for new vulnerable dependencies
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && needs.check_docs_only.outputs.docs_only == 'false'
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
with:
license-check: false
- name: Run tests
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: ./.github/actions/run_tests
- name: Build collab
if: needs.check_docs_only.outputs.docs_only == 'false'
run: cargo build -p collab
- name: Build other binaries and features
if: needs.check_docs_only.outputs.docs_only == 'false'
run: |
cargo build --workspace --bins --all-features
cargo check -p gpui --features "macos-blade"
@@ -139,6 +171,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
needs: check_docs_only
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -149,21 +182,26 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Linux dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/linux
- name: cargo clippy
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/clippy
- name: Run tests
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: ./.github/actions/run_tests
- name: Build other binaries and features
if: needs.check_docs_only.outputs.docs_only == 'false'
run: |
cargo build -p zed
cargo check -p workspace
@@ -174,6 +212,7 @@ jobs:
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
needs: check_docs_only
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
@@ -184,15 +223,18 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
- name: Install Clang & Mold
if: needs.check_docs_only.outputs.docs_only == 'false'
run: ./script/remote-server && ./script/install-mold 2.34.0
- name: Build Remote Server
if: needs.check_docs_only.outputs.docs_only == 'false'
run: cargo build -p remote_server
# todo(windows): Actually run the tests
@@ -201,6 +243,7 @@ jobs:
name: (Windows) Run Clippy and tests
if: github.repository_owner == 'zed-industries'
runs-on: hosted-windows-1
needs: check_docs_only
steps:
# more info here:- https://github.com/rust-lang/cargo/issues/13020
- name: Enable longer pathnames for git
@@ -211,16 +254,19 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
- name: cargo clippy
if: needs.check_docs_only.outputs.docs_only == 'false'
# Windows can't run shell scripts, so we need to use `cargo xtask`.
run: cargo xtask clippy
- name: Build Zed
if: needs.check_docs_only.outputs.docs_only == 'false'
run: cargo build
bundle-mac:
@@ -276,9 +322,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generate license file
run: script/generate-licenses
- name: Create macOS app bundle
run: script/bundle-mac
@@ -289,14 +332,14 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -326,6 +369,8 @@ jobs:
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -345,7 +390,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -372,6 +417,8 @@ jobs:
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -391,7 +438,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz

View File

@@ -18,7 +18,7 @@ jobs:
fi
echo "::set-output name=URL::$URL"
- name: Get content
uses: 2428392/gh-truncate-string-action@e6b5885fb83c81ca9a700a91b079baec2133be3e # v1.4.0
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
id: get-content
with:
stringToTruncate: |

View File

@@ -7,6 +7,7 @@ on:
push:
branches:
- main
merge_group:
jobs:
check_formatting:

View File

@@ -21,7 +21,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"

View File

@@ -86,9 +86,6 @@ jobs:
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Generate license file
run: script/generate-licenses
- name: Create macOS app bundle
run: script/bundle-mac

759
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -149,12 +149,9 @@ members = [
# Extensions
#
"extensions/astro",
"extensions/clojure",
"extensions/csharp",
"extensions/deno",
"extensions/elixir",
"extensions/elm",
"extensions/emmet",
"extensions/erlang",
"extensions/glsl",
@@ -337,7 +334,7 @@ zeta = { path = "crates/zeta" }
#
aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91d034ff8b53867143c005acfaa14609147c9a2c" }
alacritty_terminal = "0.24"
any_vec = "0.14"
anyhow = "1.0.86"
arrayvec = { version = "0.7.4", features = ["serde"] }
@@ -355,13 +352,13 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
cargo_toml = "0.20"
cargo_toml = "0.21"
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
cocoa = "0.26"
@@ -385,14 +382,14 @@ futures-lite = "1.13"
git2 = { version = "0.19", default-features = false }
globset = "0.4"
handlebars = "4.3"
heed = { version = "0.20.1", features = ["read-txn-no-tls"] }
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
hyper = "0.14"
http = "1.1"
ignore = "0.4.22"
image = "0.25.1"
indexmap = { version = "1.6.2", features = ["serde"] }
indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
itertools = "0.13.0"
jsonwebtoken = "9.3"
@@ -401,24 +398,24 @@ jupyter-websocket-client = { version = "0.8.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="799f10133d93ba2a88642cd480d01ec4da53408c", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = { version = "0.9.0" }
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0" }
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
profiling = "1"
@@ -444,9 +441,10 @@ runtimelib = { version = "0.24.0", default-features = false, features = [
] }
rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = "0.21.12"
rustls-native-certs = "0.8.0"
schemars = { version = "0.8", features = ["impl_json_schema"] }
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
@@ -466,13 +464,13 @@ smallvec = { version = "1.6", features = ["union"] }
smol = "1.2"
sqlformat = "0.2"
strsim = "0.11"
strum = { version = "0.25.0", features = ["derive"] }
strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0"
sys-locale = "0.3.1"
sysinfo = "0.31.0"
tempfile = "3.9.0"
thiserror = "1.0.29"
tiktoken-rs = "0.5.9"
tiktoken-rs = "0.6.0"
time = { version = "0.3", features = [
"macros",
"parsing",
@@ -498,7 +496,7 @@ tree-sitter-heex = { git = "https://github.com/zed-industries/tree-sitter-heex",
tree-sitter-diff = "0.1.0"
tree-sitter-html = "0.20"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.23"
tree-sitter-json = "0.24"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
@@ -525,6 +523,7 @@ wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zstd = "0.11"
metal = "0.30"
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"

View File

@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.7633 4.2078H4.23674L4.3551 5.5189H10.1429L9.99592 6.87645H6.20408L6.33877 8.16255H9.86939L9.66122 9.92379L8 10.3275L6.3102 9.92021L6.20408 8.86633H4.7102L4.87755 10.7955L8 11.6457L11.0694 10.8812L11.7633 4.2078ZM2 2H14L12.9061 12.7818L7.98775 14L3.09388 12.7818L2 2Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.58 2H2.5V12.08C2.5 12.5892 2.70229 13.0776 3.06235 13.4376C3.42242 13.7977 3.91078 14 4.42 14H12.58C13.0892 14 13.5776 13.7977 13.9376 13.4376C14.2977 13.0776 14.5 12.5892 14.5 12.08V3.92C14.5 3.41078 14.2977 2.92242 13.9376 2.56235C13.5776 2.20229 13.0892 2 12.58 2ZM3.358 11.6285C3.34615 12.6668 3.96437 13.2311 4.96636 13.232H4.96621C6.06429 13.2456 6.70951 12.4798 6.63088 11.3867H5.48085C5.4899 11.601 5.47243 11.8974 5.36026 12.0313C5.27992 12.1441 5.16183 12.2005 5.00645 12.2005C4.67402 12.1952 4.50788 11.9534 4.50788 11.4753V9.19488C4.50788 8.94247 4.54407 8.75168 4.61645 8.62283C4.73423 8.38524 5.17961 8.3584 5.34825 8.58663C5.47804 8.71252 5.48974 9.04683 5.48101 9.26757H6.63104C6.66099 8.70582 6.53494 8.10381 6.20079 7.80913C5.65853 7.23521 4.37403 7.26765 3.82039 7.80102C3.51213 8.07495 3.358 8.47525 3.358 9.00159V11.6285ZM7.04116 11.3867C7.01043 12.4573 7.50713 13.2473 8.61739 13.232L8.61723 13.2317C10.1571 13.2967 10.5874 11.592 9.96023 10.4759C9.74995 10.1097 9.16994 9.80702 8.71379 9.62981C8.36155 9.46772 8.21038 9.3086 8.20711 8.92079C8.20711 8.55559 8.35983 8.37291 8.66543 8.37291C8.83688 8.37291 8.95357 8.42939 9.01519 8.54217C9.10317 8.6754 9.12454 9.0409 9.11565 9.26742H10.1612C10.1866 8.71627 10.0554 8.11739 9.75509 7.81303C9.26822 7.22257 7.99791 7.24909 7.5115 7.82504C7.0109 8.29179 6.97783 9.4437 7.3346 9.96848C7.49278 10.205 7.75143 10.409 8.1107 10.5809C8.15897 10.6051 8.21552 10.6314 8.27633 10.6598C8.53247 10.7792 8.86416 10.9338 8.97119 11.1046C9.16073 11.3241 9.13593 11.8913 9.00333 12.0877C8.9336 12.1952 8.81285 12.2489 8.64141 12.2489C8.25703 12.2785 8.09666 11.8534 8.12677 11.3867H7.04116ZM10.5474 11.3867C10.5167 12.4573 11.0134 13.2473 12.1236 13.232L12.1235 13.2317C13.6634 13.2967 14.0936 11.592 13.4665 10.4759C13.2562 10.1097 12.6762 9.80702 12.2201 9.62981C11.8678 9.46772 11.7166 9.3086 11.7134 8.92079C11.7134 8.55559 11.8661 8.37291 12.1717 8.37291C12.3431 8.37291 12.4598 8.42939 12.5214 8.54217C12.6094 8.6754 12.6308 9.0409 12.6219 9.26742H13.6674C13.6928 8.71627 13.5617 8.11739 13.2614 7.81303C12.7745 7.22257 11.5042 7.24909 11.0178 7.82504C10.5172 8.29179 10.4841 9.4437 10.8409 9.96848C10.999 10.205 11.2577 10.409 11.617 10.5809C11.6652 10.6051 11.7218 10.6314 11.7826 10.6598C12.0387 10.7792 12.3704 10.9338 12.4775 11.1046C12.667 11.3241 12.6422 11.8913 12.5096 12.0877C12.4399 12.1952 12.3191 12.2489 12.1477 12.2489C11.7633 12.2785 11.6029 11.8534 11.633 11.3867H10.5474Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-search"><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3"/><path d="m9 18-1.5-1.5"/><circle cx="5" cy="14" r="3"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.2345 20.1C5.38772 20.373 5.60794 20.5998 5.87313 20.7577C6.13832 20.9157 6.43919 20.9992 6.74562 21H17.25C17.7141 21 18.1592 20.8104 18.4874 20.4728C18.8156 20.1352 19 19.6774 19 19.2V7.5L14.625 3H6.75C6.28587 3 5.84075 3.18964 5.51256 3.52721C5.18437 3.86477 5 4.32261 5 4.8V6.5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 16.8182L8.5 15.3182" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 15.8182C7.65685 15.8182 9 14.475 9 12.8182C9 11.1613 7.65685 9.81818 6 9.81818C4.34315 9.81818 3 11.1613 3 12.8182C3 14.475 4.34315 15.8182 6 15.8182Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 837 B

View File

@@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6749 2.40608C11.8058 2.24239 11.6893 1.99991 11.4796 1.99991H2.51996C2.31033 1.99991 2.19379 2.24239 2.32474 2.40608L5.14583 5.93246C5.34148 6.17701 5.44808 6.48087 5.44808 6.79412C5.44808 7.46881 5.44808 10.334 5.44808 11.5016C5.44808 11.7778 5.67194 11.9999 5.94808 11.9999H8.05153C8.32767 11.9999 8.55153 11.7778 8.55153 11.5016C8.55153 10.334 8.55153 7.46881 8.55153 6.79412C8.55153 6.48087 8.65815 6.17701 8.8538 5.93246L11.6749 2.40608Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9416 2.99643C13.08 2.79636 12.9568 2.5 12.7352 2.5H3.26475C3.04317 2.5 2.91999 2.79636 3.0584 2.99643L6.04033 7.30646C6.24713 7.60535 6.35981 7.97674 6.35981 8.3596C6.35981 9.18422 6.35981 11.4639 6.35981 12.891C6.35981 13.2285 6.59643 13.5 6.88831 13.5H9.11168C9.40357 13.5 9.64019 13.2285 9.64019 12.891C9.64019 11.4639 9.64019 9.18422 9.64019 8.3596C9.64019 7.97674 9.75289 7.60535 9.95969 7.30646L12.9416 2.99643Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 618 B

View File

@@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12L9.41379 9.41379M2 6.31034C2 3.92981 3.92981 2 6.31034 2C8.6909 2 10.6207 3.92981 10.6207 6.31034C10.6207 8.6909 8.6909 10.6207 6.31034 10.6207C3.92981 10.6207 2 8.6909 2 6.31034Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 13L10.4138 10.4138M3 7.31034C3 4.92981 4.92981 3 7.31034 3C9.6909 3 11.6207 4.92981 11.6207 7.31034C11.6207 9.6909 9.6909 11.6207 7.31034 11.6207C4.92981 11.6207 3 9.6909 3 7.31034Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 382 B

View File

@@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.27772 1.38585L4.39187 4.07909C4.34653 4.21692 4.26946 4.34219 4.16685 4.44479C4.06425 4.5474 3.93898 4.62447 3.80115 4.66981L1.10791 5.55566L3.80115 6.44151C3.93898 6.48685 4.06425 6.56392 4.16685 6.66653C4.26946 6.76913 4.34653 6.8944 4.39187 7.03223L5.27772 9.72547L6.16357 7.03223C6.20891 6.8944 6.28598 6.76913 6.38859 6.66653C6.49119 6.56392 6.61646 6.48685 6.7543 6.44151L9.44753 5.55566L6.7543 4.66981C6.61646 4.62447 6.49119 4.5474 6.38859 4.44479C6.28598 4.34219 6.20891 4.21692 6.16357 4.07909L5.27772 1.38585Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.35938 12.3555C8.35938 12.0664 8.52734 11.8086 9.00781 11.3594L10.2031 10.2344C10.6094 9.85156 10.7891 9.60156 10.7891 9.34375C10.7891 9.05469 10.5781 8.85938 10.2734 8.85938C10.0391 8.85938 9.87109 8.95312 9.66406 9.21094C9.42578 9.50781 9.25391 9.60938 8.99219 9.60938C8.61719 9.60938 8.35156 9.35938 8.35156 9.01172C8.35156 8.25 9.26953 7.57812 10.3594 7.57812C11.4961 7.57812 12.3438 8.26172 12.3438 9.17969C12.3438 9.75391 12.0391 10.3008 11.418 10.8516L10.4961 11.6719V11.7344H11.8047C12.2578 11.7344 12.5391 11.9766 12.5391 12.3711C12.5391 12.7656 12.2656 13 11.8047 13H9.08203C8.65234 13 8.35938 12.7383 8.35938 12.3555Z" fill="black"/>
<path d="M11.0834 1.38585V3.71918M9.91675 2.55248H12.2501" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -172,6 +172,7 @@
"context": "AssistantPanel",
"bindings": {
"ctrl-k c": "assistant::CopyCode",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-g": "search::SelectNextMatch",
"ctrl-shift-g": "search::SelectPrevMatch",
"ctrl-shift-m": "assistant::ToggleModelSelector",
@@ -260,9 +261,11 @@
"ctrl-f4": "pane::CloseActiveItem",
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
"ctrl-shift-f": "project_search::ToggleFocus",
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-alt-g": "search::SelectNextMatch",
"ctrl-alt-shift-g": "search::SelectPrevMatch",
"ctrl-alt-shift-h": "search::ToggleReplace",
@@ -375,6 +378,7 @@
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
"alt-ctrl-o": "projects::OpenRecent",
"alt-ctrl-shift-o": "projects::OpenRemote",
"alt-ctrl-shift-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
"ctrl-s": "workspace::Save",
@@ -408,7 +412,7 @@
"ctrl-shift-p": "command_palette::Toggle",
"f1": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
"ctrl-?": "assistant::ToggleFocus",
"ctrl-alt-s": "workspace::SaveAll",
@@ -432,6 +436,13 @@
// "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }]
}
},
{
"context": "ApplicationMenu",
"bindings": {
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
}
},
// Bindings from Sublime Text
{
"context": "Editor",
@@ -522,6 +533,7 @@
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit",
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"
@@ -602,7 +614,6 @@
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",

View File

@@ -196,6 +196,7 @@
"use_key_equivalents": true,
"bindings": {
"cmd-k c": "assistant::CopyCode",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-g": "search::SelectNextMatch",
"cmd-shift-g": "search::SelectPrevMatch",
"cmd-shift-m": "assistant::ToggleModelSelector",
@@ -226,7 +227,8 @@
"cmd-n": "assistant2::NewThread",
"cmd-shift-h": "assistant2::OpenHistory",
"cmd-shift-m": "assistant2::ToggleModelSelector",
"cmd-shift-a": "assistant2::ToggleContextPicker"
"cmd-shift-a": "assistant2::ToggleContextPicker",
"cmd-alt-e": "assistant2::RemoveAllContext"
}
},
{
@@ -327,6 +329,8 @@
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
"cmd-f": "project_search::ToggleFocus",
@@ -432,7 +436,7 @@
"ctrl--": "pane::GoBack",
"ctrl-shift--": "pane::GoForward",
"cmd-shift-t": "pane::ReopenClosedItem",
"cmd-shift-f": "project_search::ToggleFocus"
"cmd-shift-f": "pane::DeploySearch"
}
},
{
@@ -448,7 +452,6 @@
"cmd-s": "workspace::Save",
"cmd-k s": "workspace::SaveWithoutFormat",
"cmd-shift-s": "workspace::SaveAs",
"cmd-n": "workspace::NewFile",
"cmd-shift-n": "workspace::NewWindow",
"ctrl-`": "terminal_panel::ToggleFocus",
"cmd-1": ["workspace::ActivatePane", 0],
@@ -474,7 +477,7 @@
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-shift-b": "outline_panel::ToggleFocus",
"cmd-?": "assistant::ToggleFocus",
"cmd-alt-s": "workspace::SaveAll",
@@ -495,6 +498,7 @@
"context": "Workspace && !Terminal",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "workspace::NewFile",
"cmd-shift-r": "task::Spawn",
"cmd-alt-r": "task::Rerun",
"ctrl-alt-shift-r": ["task::Spawn", { "reveal_target": "center" }]
@@ -593,6 +597,7 @@
"alt-enter": "editor::OpenExcerpts",
"shift-enter": "editor::ExpandExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit",
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"
@@ -611,6 +616,7 @@
"use_key_equivalents": true,
"bindings": {
"cmd-shift-a": "assistant2::ToggleContextPicker",
"cmd-alt-e": "assistant2::RemoveAllContext",
"ctrl-[": "assistant::CyclePreviousInlineAssist",
"ctrl-]": "assistant::CycleNextInlineAssist"
}
@@ -661,7 +667,6 @@
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"cmd-shift-e": "project_panel::ToggleFocus",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
@@ -676,6 +681,38 @@
"space": "project_panel::Open"
}
},
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrev",
"down": "menu::SelectNext",
"cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast",
"enter": "menu::Confirm",
"space": "git::ToggleStaged",
"cmd-shift-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll",
"alt-down": "git_panel::FocusEditor"
}
},
{
"context": "GitPanel && CommitEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"alt-up": "git_panel::FocusChanges",
"escape": "git_panel::FocusChanges",
"cmd-enter": "git::CommitChanges",
"cmd-alt-enter": "git::CommitAllChanges"
}
},
{
"context": "CollabPanel && not_editing",
"use_key_equivalents": true,
@@ -761,6 +798,7 @@
"cmd-v": "terminal::Paste",
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist",
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"],

View File

@@ -4,55 +4,72 @@
// from the command palette.
[
{
"context": "Editor",
"bindings": {
"ctrl-g": "editor::Cancel",
"ctrl-shift-g": "go_to_line::Toggle",
//"ctrl-space": "editor::SetMark",
"ctrl-x u": "editor::Undo",
"ctrl-x ctrl-u": "editor::Redo",
"ctrl-f": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown",
"ctrl-p": "editor::MoveUp",
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-f": "editor::MoveToNextSubwordEnd",
"alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-d": "editor::Delete",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-w": "editor::Cut",
"alt-w": "editor::Copy",
"ctrl-y": "editor::Paste",
"ctrl-_": "editor::Undo",
"ctrl-v": "editor::MovePageDown",
"alt-v": "editor::MovePageUp",
"ctrl-x ]": "editor::MoveToEnd",
"ctrl-x [": "editor::MoveToBeginning",
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
"ctrl-s": "buffer_search::Deploy",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-shift-r": "editor::Rename"
"ctrl-g": "menu::Cancel"
}
},
{
"context": "Workspace",
"context": "Editor",
"bindings": {
"ctrl-x k": "pane::CloseActiveItem",
"ctrl-x ctrl-c": "workspace::CloseWindow",
"ctrl-x o": "workspace::ActivateNextPane",
"ctrl-x b": "tab_switcher::Toggle",
"ctrl-x 0": "pane::CloseActiveItem",
"ctrl-x 1": "pane::CloseInactiveItems",
"ctrl-x 2": "pane::SplitVertical",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-x ctrl-s": "workspace::Save",
"ctrl-x ctrl-w": "workspace::SaveAs",
"ctrl-x s": "workspace::SaveAll",
"shift shift": "file_finder::Toggle"
"ctrl-g": "editor::Cancel",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
//"ctrl-space": "editor::SetMark",
"ctrl-f": "editor::MoveRight", // forward-char
"ctrl-b": "editor::MoveLeft", // backward-char
"ctrl-n": "editor::MoveDown", // next-line
"ctrl-p": "editor::MoveUp", // previous-line
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-x ctrl-;": "editor::ToggleComments",
"alt-.": "editor::GoToDefinition", // xref-find-definitions
"alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
"ctrl-k": "editor::KillRingCut", // kill-line
"ctrl-w": "editor::Cut", // kill-region
"alt-w": "editor::Copy", // kill-ring-save
"ctrl-y": "editor::KillRingYank", // yank
"ctrl-_": "editor::Undo", // undo
"ctrl-/": "editor::Undo", // undo
"ctrl-x u": "editor::Undo", // undo
"ctrl-v": "editor::MovePageDown", // scroll-up
"alt-v": "editor::MovePageUp", // scroll-down
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
"ctrl-x ]": "editor::MoveToEnd", // end-of-buffer
"alt-<": "editor::MoveToBeginning", // beginning-of-buffer
"alt->": "editor::MoveToEnd", // end-of-buffer
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
"ctrl-s": "buffer_search::Deploy", // isearch-forward
"alt-^": "editor::JoinLines" // join-line
}
},
{
"context": "Workspace && !Terminal",
"bindings": {
"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::CloseInactiveItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
}
},
{

View File

@@ -3,56 +3,85 @@
// To see the default key bindings run `zed: open default keymap`
// from the command palette.
[
{
"bindings": {
"ctrl-g": "menu::Cancel"
}
},
{
"context": "Editor",
"bindings": {
"ctrl-g": "editor::Cancel",
"ctrl-shift-g": "go_to_line::Toggle",
"ctrl-x b": "tab_switcher::Toggle", // switch-to-buffer
"alt-g g": "go_to_line::Toggle", // goto-line
"alt-g alt-g": "go_to_line::Toggle", // goto-line
//"ctrl-space": "editor::SetMark",
"ctrl-x u": "editor::Undo",
"ctrl-x ctrl-u": "editor::Redo",
"ctrl-f": "editor::MoveRight",
"ctrl-b": "editor::MoveLeft",
"ctrl-n": "editor::MoveDown",
"ctrl-p": "editor::MoveUp",
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
"alt-f": "editor::MoveToNextSubwordEnd",
"alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-d": "editor::Delete",
"alt-d": "editor::DeleteToNextWordEnd",
"ctrl-k": "editor::CutToEndOfLine",
"ctrl-w": "editor::Cut",
"alt-w": "editor::Copy",
"ctrl-y": "editor::Paste",
"ctrl-_": "editor::Undo",
"ctrl-v": "editor::MovePageDown",
"alt-v": "editor::MovePageUp",
"ctrl-x ]": "editor::MoveToEnd",
"ctrl-x [": "editor::MoveToBeginning",
"ctrl-l": "editor::ScrollCursorCenterTopBottom",
"ctrl-s": "buffer_search::Deploy",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-shift-r": "editor::Rename"
"ctrl-f": "editor::MoveRight", // forward-char
"ctrl-b": "editor::MoveLeft", // backward-char
"ctrl-n": "editor::MoveDown", // next-line
"ctrl-p": "editor::MoveUp", // previous-line
"home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }], // move-beginning-of-line
"ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }], // move-end-of-line
"alt-f": "editor::MoveToNextSubwordEnd", // forward-word
"alt-b": "editor::MoveToPreviousSubwordStart", // backward-word
"alt-u": "editor::ConvertToUpperCase", // upcase-word
"alt-l": "editor::ConvertToLowerCase", // downcase-word
"alt-c": "editor::ConvertToUpperCamelCase", // capitalize-word
"alt-;": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-x ctrl-;": "editor::ToggleComments",
"alt-.": "editor::GoToDefinition", // xref-find-definitions
"alt-,": "pane::GoBack", // xref-pop-marker-stack
"ctrl-x h": "editor::SelectAll", // mark-whole-buffer
"ctrl-d": "editor::Delete", // delete-char
"alt-d": "editor::DeleteToNextWordEnd", // kill-word
"ctrl-k": "editor::KillRingCut", // kill-line
"ctrl-w": "editor::Cut", // kill-region
"alt-w": "editor::Copy", // kill-ring-save
"ctrl-y": "editor::KillRingYank", // yank
"ctrl-_": "editor::Undo", // undo
"ctrl-/": "editor::Undo", // undo
"ctrl-x u": "editor::Undo", // undo
"ctrl-v": "editor::MovePageDown", // scroll-up
"alt-v": "editor::MovePageUp", // scroll-down
"ctrl-x [": "editor::MoveToBeginning", // beginning-of-buffer
"ctrl-x ]": "editor::MoveToEnd", // end-of-buffer
"alt-<": "editor::MoveToBeginning", // beginning-of-buffer
"alt->": "editor::MoveToEnd", // end-of-buffer
"ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom
"ctrl-s": "buffer_search::Deploy", // isearch-forward
"alt-^": "editor::JoinLines" // join-line
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-x k": "pane::CloseActiveItem",
"ctrl-x ctrl-c": "workspace::CloseWindow",
"ctrl-x o": "workspace::ActivateNextPane",
"ctrl-x b": "tab_switcher::Toggle",
"ctrl-x 0": "pane::CloseActiveItem",
"ctrl-x 1": "pane::CloseInactiveItems",
"ctrl-x 2": "pane::SplitVertical",
"ctrl-x ctrl-f": "file_finder::Toggle",
"ctrl-x ctrl-s": "workspace::Save",
"ctrl-x ctrl-w": "workspace::SaveAs",
"ctrl-x s": "workspace::SaveAll",
"shift shift": "file_finder::Toggle"
"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::CloseInactiveItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file
"ctrl-x ctrl-s": "workspace::Save", // save-buffer
"ctrl-x ctrl-w": "workspace::SaveAs", // write-file
"ctrl-x s": "workspace::SaveAll" // save-some-buffers
}
},
{
// Workaround to enable using emacs in the Zed terminal.
// Unbind so Zed ignores these keys and lets emacs handle them.
"context": "Terminal",
"bindings": {
"ctrl-x ctrl-c": null, // save-buffers-kill-terminal
"ctrl-x ctrl-f": null, // find-file
"ctrl-x ctrl-s": null, // save-buffer
"ctrl-x ctrl-w": null, // write-file
"ctrl-x s": null // save-some-buffers
}
},
{

View File

@@ -197,6 +197,7 @@
"d": ["vim::PushOperator", "Delete"],
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"g shift-j": "vim::JoinLinesNoWhitespace",
"y": ["vim::PushOperator", "Yank"],
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
@@ -230,8 +231,8 @@
"ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore",
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode",
"[ x": "vim::SelectLargerSyntaxNode",
"] x": "vim::SelectSmallerSyntaxNode",
"] d": "editor::GoToDiagnostic",
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",
@@ -259,7 +260,7 @@
"shift-d": "vim::VisualDeleteLine",
"shift-x": "vim::VisualDeleteLine",
"y": "vim::VisualYank",
"shift-y": "vim::VisualYank",
"shift-y": "vim::VisualYankLine",
"p": "vim::Paste",
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
"s": "vim::Substitute",
@@ -278,6 +279,7 @@
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
"g shift-a": "vim::VisualInsertEndOfLine",
"shift-j": "vim::JoinLines",
"g shift-j": "vim::JoinLinesNoWhitespace",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
@@ -395,6 +397,7 @@
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
"q": "vim::AnyQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",

View File

@@ -160,6 +160,9 @@
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
/// Whether to show the inline completions next to the completions provided by a language server.
/// Only has an effect if inline completion provider supports it.
"show_inline_completions_in_menu": true,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -253,8 +256,13 @@
"search_results": true,
// Whether to show selected symbol occurrences in the scrollbar.
"selected_symbol": true,
// Whether to show diagnostic indicators in the scrollbar.
"diagnostics": true,
// Which diagnostic indicators to show in the scrollbar:
// - "none" or false: do not show diagnostics
// - "error": show only errors
// - "warning": show only errors and warnings
// - "information": show only errors, warnings, and information
// - "all" or true: show all diagnostics
"diagnostics": "all",
/// Forcefully enable or disable the scrollbar for each axis
"axes": {
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
@@ -478,8 +486,10 @@
"default_width": 240
},
"chat_panel": {
// Whether to show the chat panel button in the status bar.
"button": true,
// When to show the chat panel button in the status bar.
// Can be 'never', 'always', or 'when_in_call',
// or a boolean (interpreted as 'never'/'always').
"button": "when_in_call",
// Where to the chat panel. Can be 'left' or 'right'.
"dock": "right",
// Default width of the chat panel.
@@ -491,7 +501,11 @@
// Where to the git panel. Can be 'left' or 'right'.
"dock": "left",
// Default width of the git panel.
"default_width": 360
"default_width": 360,
"status_style": "icon",
"scrollbar": {
"show": "auto"
}
},
"message_editor": {
// Whether to automatically replace emoji shortcodes with emoji characters.
@@ -736,7 +750,7 @@
// Delay is restarted with every cursor movement.
// "delay_ms": 600
//
// Whether or not do display the git commit summary on the same line.
// Whether or not to display the git commit summary on the same line.
// "show_commit_summary": false
//
// The minimum column number to show the inline blame information at
@@ -1098,6 +1112,9 @@
"prettier": {
"allowed": true
}
},
"Zig": {
"language_servers": ["zls", "..."]
}
},
// Different settings for specific language models.

View File

@@ -30,6 +30,8 @@ pub enum Model {
#[default]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
Claude3_5Haiku,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
@@ -48,6 +50,8 @@ pub enum Model {
cache_configuration: Option<AnthropicModelCacheConfiguration>,
max_output_tokens: Option<u32>,
default_temperature: Option<f32>,
#[serde(default)]
extra_beta_headers: Vec<String>,
},
}
@@ -55,6 +59,8 @@ impl Model {
pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-3-5-sonnet") {
Ok(Self::Claude3_5Sonnet)
} else if id.starts_with("claude-3-5-haiku") {
Ok(Self::Claude3_5Haiku)
} else if id.starts_with("claude-3-opus") {
Ok(Self::Claude3Opus)
} else if id.starts_with("claude-3-sonnet") {
@@ -69,6 +75,7 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3_5Haiku => "claude-3-5-haiku-latest",
Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-latest",
Model::Claude3Haiku => "claude-3-haiku-latest",
@@ -79,6 +86,7 @@ impl Model {
pub fn display_name(&self) -> &str {
match self {
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3_5Haiku => "Claude 3.5 Haiku",
Self::Claude3Opus => "Claude 3 Opus",
Self::Claude3Sonnet => "Claude 3 Sonnet",
Self::Claude3Haiku => "Claude 3 Haiku",
@@ -90,11 +98,13 @@ impl Model {
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
match self {
Self::Claude3_5Sonnet | Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048,
should_speculate: true,
max_cache_anchors: 4,
}),
Self::Claude3_5Sonnet | Self::Claude3_5Haiku | Self::Claude3Haiku => {
Some(AnthropicModelCacheConfiguration {
min_total_token: 2_048,
should_speculate: true,
max_cache_anchors: 4,
})
}
Self::Custom {
cache_configuration,
..
@@ -106,6 +116,7 @@ impl Model {
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 200_000,
@@ -116,7 +127,7 @@ impl Model {
pub fn max_output_tokens(&self) -> u32 {
match self {
Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
Self::Claude3_5Sonnet => 8_192,
Self::Claude3_5Sonnet | Self::Claude3_5Haiku => 8_192,
Self::Custom {
max_output_tokens, ..
} => max_output_tokens.unwrap_or(4_096),
@@ -126,6 +137,7 @@ impl Model {
pub fn default_temperature(&self) -> f32 {
match self {
Self::Claude3_5Sonnet
| Self::Claude3_5Haiku
| Self::Claude3Opus
| Self::Claude3Sonnet
| Self::Claude3Haiku => 1.0,
@@ -136,6 +148,24 @@ impl Model {
}
}
pub fn beta_headers(&self) -> String {
let mut headers = vec!["prompt-caching-2024-07-31".to_string()];
if let Self::Custom {
extra_beta_headers, ..
} = self
{
headers.extend(
extra_beta_headers
.iter()
.filter(|header| !header.trim().is_empty())
.cloned(),
);
}
headers.join(",")
}
pub fn tool_model_id(&self) -> &str {
if let Self::Custom {
tool_override: Some(tool_override),
@@ -156,11 +186,12 @@ pub async fn complete(
request: Request,
) -> Result<Response, AnthropicError> {
let uri = format!("{api_url}/v1/messages");
let model = Model::from_id(&request.model)?;
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header("Anthropic-Beta", "prompt-caching-2024-07-31")
.header("Anthropic-Beta", model.beta_headers())
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
@@ -271,14 +302,12 @@ pub async fn stream_completion_with_rate_limit_info(
stream: true,
};
let uri = format!("{api_url}/v1/messages");
let model = Model::from_id(&request.base.model)?;
let request_builder = HttpRequest::builder()
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
.header(
"Anthropic-Beta",
"tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
)
.header("Anthropic-Beta", model.beta_headers())
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
let serialized_request =

View File

@@ -103,6 +103,7 @@ pretty_assertions.workspace = true
project = { workspace = true, features = ["test-support"] }
rand.workspace = true
serde_json_lenient.workspace = true
terminal_view = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
tree-sitter-md.workspace = true
unindent.workspace = true

View File

@@ -37,7 +37,7 @@ pub use prompts::PromptBuilder;
use prompts::PromptLoadingParams;
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use settings::{Settings, SettingsStore};
use slash_command::search_command::SearchSlashCommandFeatureFlag;
use slash_command::{
auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
@@ -199,16 +199,6 @@ pub fn init(
AssistantSettings::register(cx);
SlashCommandSettings::register(cx);
// TODO: remove this when 0.148.0 is released.
if AssistantSettings::get_global(cx).using_outdated_settings_version {
update_settings_file::<AssistantSettings>(fs.clone(), cx, {
let fs = fs.clone();
|content, cx| {
content.update_file(fs, cx);
}
});
}
cx.spawn(|mut cx| {
let client = client.clone();
async move {

View File

@@ -122,7 +122,7 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
let settings = AssistantSettings::get_global(cx);
terminal_panel.asssistant_enabled(settings.enabled, cx);
terminal_panel.set_assistant_enabled(settings.enabled, cx);
},
)
.detach();
@@ -1458,6 +1458,10 @@ impl Panel for AssistantPanel {
fn toggle_action(&self) -> Box<dyn Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
4
}
}
impl EventEmitter<PanelEvent> for AssistantPanel {}
@@ -1556,6 +1560,7 @@ impl ContextEditor {
let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_line_numbers(false, cx);
editor.set_show_scrollbars(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);
@@ -4965,8 +4970,8 @@ fn fold_toggle(
) -> impl Fn(
MultiBufferRow,
bool,
Arc<dyn Fn(bool, &mut WindowContext<'_>) + Send + Sync>,
&mut WindowContext<'_>,
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
&mut WindowContext,
) -> AnyElement {
move |row, is_folded, fold, _cx| {
Disclosure::new((name, row.0 as u64), !is_folded)

View File

@@ -3,18 +3,12 @@ use std::sync::Arc;
use ::open_ai::Model as OpenAiModel;
use anthropic::Model as AnthropicModel;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
use gpui::{AppContext, Pixels};
use language_model::{CloudModel, LanguageModel};
use language_models::{
provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent,
AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent,
OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent,
};
use ollama::Model as OllamaModel;
use schemars::{schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsSources};
use settings::{Settings, SettingsSources};
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -106,96 +100,6 @@ impl AssistantSettingsContent {
}
}
pub fn update_file(&mut self, fs: Arc<dyn Fs>, cx: &AppContext) {
if let AssistantSettingsContent::Versioned(settings) = self {
if let VersionedAssistantSettingsContent::V1(settings) = settings {
if let Some(provider) = settings.provider.clone() {
match provider {
AssistantProviderContentV1::Anthropic { api_url, .. } => {
update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.anthropic.is_none() {
content.anthropic =
Some(AnthropicSettingsContent::Versioned(
VersionedAnthropicSettingsContent::V1(
AnthropicSettingsContentV1 {
api_url,
available_models: None,
},
),
));
}
},
)
}
AssistantProviderContentV1::Ollama { api_url, .. } => {
update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.ollama.is_none() {
content.ollama = Some(OllamaSettingsContent {
api_url,
available_models: None,
});
}
},
)
}
AssistantProviderContentV1::OpenAi {
api_url,
available_models,
..
} => update_settings_file::<AllLanguageModelSettings>(
fs,
cx,
move |content, _| {
if content.openai.is_none() {
let available_models = available_models.map(|models| {
models
.into_iter()
.filter_map(|model| match model {
OpenAiModel::Custom {
name,
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens: None,
} => Some(open_ai::AvailableModel {
name,
display_name,
max_tokens,
max_output_tokens,
max_completion_tokens: None,
}),
_ => None,
})
.collect::<Vec<_>>()
});
content.openai = Some(OpenAiSettingsContent::Versioned(
VersionedOpenAiSettingsContent::V1(
OpenAiSettingsContentV1 {
api_url,
available_models,
},
),
));
}
},
),
_ => {}
}
}
}
}
*self = AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(
self.upgrade(),
));
}
fn upgrade(&self) -> AssistantSettingsContentV2 {
match self {
AssistantSettingsContent::Versioned(settings) => match settings {
@@ -534,6 +438,7 @@ fn merge<T>(target: &mut T, value: Option<T>) {
#[cfg(test)]
mod tests {
use fs::Fs;
use gpui::{ReadGlobal, TestAppContext};
use super::*;

View File

@@ -22,6 +22,7 @@ use paths::contexts_dir;
use project::Project;
use regex::Regex;
use rpc::AnyProtoClient;
use std::sync::LazyLock;
use std::{
cmp::Reverse,
ffi::OsStr,
@@ -753,8 +754,8 @@ impl ContextStore {
continue;
}
let pattern = r" - \d+.zed.json$";
let re = Regex::new(pattern).unwrap();
static ASSISTANT_CONTEXT_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r" - \d+.zed.json$").unwrap());
let metadata = fs.metadata(&path).await?;
if let Some((file_name, metadata)) = path
@@ -763,11 +764,15 @@ impl ContextStore {
.zip(metadata)
{
// This is used to filter out contexts saved by the new assistant.
if !re.is_match(file_name) {
if !ASSISTANT_CONTEXT_REGEX.is_match(file_name) {
continue;
}
if let Some(title) = re.replace(file_name, "").lines().next() {
if let Some(title) = ASSISTANT_CONTEXT_REGEX
.replace(file_name, "")
.lines()
.next()
{
contexts.push(SavedContextMetadata {
title: title.to_string(),
path,

View File

@@ -133,7 +133,7 @@ impl InlineAssistant {
};
let enabled = AssistantSettings::get_global(cx).enabled;
terminal_panel.update(cx, |terminal_panel, cx| {
terminal_panel.asssistant_enabled(enabled, cx)
terminal_panel.set_assistant_enabled(enabled, cx)
});
})
.detach();
@@ -797,10 +797,11 @@ impl InlineAssistant {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let language_name = assist.editor.upgrade().and_then(|editor| {
let multibuffer = editor.read(cx).buffer().read(cx);
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
let multibuffer_snapshot = multibuffer.snapshot(cx);
let ranges = multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
});
report_assistant_event(
@@ -2615,26 +2616,29 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
impl CodegenAlternative {
pub fn new(
buffer: Model<MultiBuffer>,
multi_buffer: Model<MultiBuffer>,
range: Range<Anchor>,
active: bool,
telemetry: Option<Arc<Telemetry>>,
builder: Arc<PromptBuilder>,
cx: &mut ModelContext<Self>,
) -> Self {
let snapshot = buffer.read(cx).snapshot(cx);
let snapshot = multi_buffer.read(cx).snapshot(cx);
let (old_buffer, _, _) = buffer
.read(cx)
.range_to_buffer_ranges(range.clone(), cx)
let (old_excerpt, _) = snapshot
.range_to_buffer_ranges(range.clone())
.pop()
.unwrap();
let old_buffer = cx.new_model(|cx| {
let old_buffer = old_buffer.read(cx);
let text = old_buffer.as_rope().clone();
let line_ending = old_buffer.line_ending();
let language = old_buffer.language().cloned();
let language_registry = old_buffer.language_registry();
let text = old_excerpt.buffer().as_rope().clone();
let line_ending = old_excerpt.buffer().line_ending();
let language = old_excerpt.buffer().language().cloned();
let language_registry = multi_buffer
.read(cx)
.buffer(old_excerpt.buffer_id())
.unwrap()
.read(cx)
.language_registry();
let mut buffer = Buffer::local_normalized(text, line_ending, cx);
buffer.set_language(language, cx);
@@ -2645,7 +2649,7 @@ impl CodegenAlternative {
});
Self {
buffer: buffer.clone(),
buffer: multi_buffer.clone(),
old_buffer,
edit_position: None,
message_id: None,
@@ -2656,7 +2660,7 @@ impl CodegenAlternative {
generation: Task::ready(()),
diff: Diff::default(),
telemetry,
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
_subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event),
builder,
active,
edits: Vec::new(),
@@ -2867,10 +2871,11 @@ impl CodegenAlternative {
let telemetry = self.telemetry.clone();
let language_name = {
let multibuffer = self.buffer.read(cx);
let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx);
let snapshot = multibuffer.snapshot(cx);
let ranges = snapshot.range_to_buffer_ranges(self.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
};

View File

@@ -149,6 +149,7 @@ impl SlashCommandCompletionProvider {
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm,
resolved: true,
})
})
.collect()
@@ -242,6 +243,7 @@ impl SlashCommandCompletionProvider {
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
confirm,
resolved: true,
}
})
.collect())
@@ -330,16 +332,6 @@ impl CompletionProvider for SlashCommandCompletionProvider {
Task::ready(Ok(true))
}
fn apply_additional_edits_for_completion(
&self,
_: Model<Buffer>,
_: project::Completion,
_: bool,
_: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
buffer: &Model<Buffer>,

View File

@@ -5,7 +5,7 @@ use assistant_slash_command::{
};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
use gpui::{AppContext, AsyncAppContext, AsyncWindowContext, Task, WeakView, WindowContext};
use language::{CodeLabel, LspAdapterDelegate};
use language_model::{
LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
@@ -14,7 +14,7 @@ use language_model::{
use semantic_index::{FileSummary, SemanticDb};
use smol::channel;
use std::sync::{atomic::AtomicBool, Arc};
use ui::{prelude::*, BorrowAppContext, WindowContext};
use ui::{prelude::*, BorrowAppContext};
use util::ResultExt;
use workspace::Workspace;
@@ -115,7 +115,7 @@ impl SlashCommand for AutoCommand {
return Task::ready(Err(anyhow!("no project indexer")));
};
let task = cx.spawn(|cx: gpui::AsyncWindowContext| async move {
let task = cx.spawn(|cx: AsyncWindowContext| async move {
let summaries = project_index
.read_with(&cx, |project_index, cx| project_index.all_summaries(cx))?
.await?;

View File

@@ -281,7 +281,7 @@ fn tab_items_for_queries(
fn active_item_buffer(
workspace: &mut Workspace,
cx: &mut ui::ViewContext<Workspace>,
cx: &mut ViewContext<Workspace>,
) -> anyhow::Result<BufferSnapshot> {
let active_editor = workspace
.active_item(cx)

View File

@@ -27,8 +27,8 @@ enum SlashCommandEntry {
Info(SlashCommandInfo),
Advert {
name: SharedString,
renderer: fn(&mut WindowContext<'_>) -> AnyElement,
on_confirm: fn(&mut WindowContext<'_>),
renderer: fn(&mut WindowContext) -> AnyElement,
on_confirm: fn(&mut WindowContext),
},
}

View File

@@ -3,8 +3,9 @@ use std::sync::Arc;
use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use gpui::{
list, AnyElement, AppContext, Empty, ListAlignment, ListState, Model, StyleRefinement,
Subscription, TextStyleRefinement, View, WeakView,
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, UnderlineStyle, View, WeakView,
};
use language::LanguageRegistry;
use language_model::Role;
@@ -21,7 +22,7 @@ pub struct ActiveThread {
workspace: WeakView<Workspace>,
language_registry: Arc<LanguageRegistry>,
tools: Arc<ToolWorkingSet>,
thread: Model<Thread>,
pub(crate) thread: Model<Thread>,
messages: Vec<MessageId>,
list_state: ListState,
rendered_messages_by_id: HashMap<MessageId, View<Markdown>>,
@@ -89,10 +90,11 @@ impl ActiveThread {
self.list_state.splice(old_len..old_len, 1);
let theme_settings = ThemeSettings::get_global(cx);
let colors = cx.theme().colors();
let ui_font_size = TextSize::Default.rems(cx);
let buffer_font_size = theme_settings.buffer_font_size;
let buffer_font_size = TextSize::Small.rems(cx);
let mut text_style = cx.text_style();
text_style.refine(&TextStyleRefinement {
font_family: Some(theme_settings.ui_font.family.clone()),
font_size: Some(ui_font_size.into()),
@@ -105,6 +107,26 @@ impl ActiveThread {
syntax: cx.theme().syntax().clone(),
selection_background_color: cx.theme().players().local().selection,
code_block: StyleRefinement {
margin: EdgesRefinement {
top: Some(Length::Definite(rems(1.0).into())),
left: Some(Length::Definite(rems(0.).into())),
right: Some(Length::Definite(rems(0.).into())),
bottom: Some(Length::Definite(rems(1.).into())),
},
padding: EdgesRefinement {
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
},
background: Some(colors.editor_foreground.opacity(0.01).into()),
border_color: Some(colors.border_variant.opacity(0.3)),
border_widths: EdgesRefinement {
top: Some(AbsoluteLength::Pixels(Pixels(1.0))),
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
},
text: Some(TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(buffer_font_size.into()),
@@ -114,8 +136,17 @@ impl ActiveThread {
},
inline_code: TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(ui_font_size.into()),
background_color: Some(cx.theme().colors().editor_background),
font_size: Some(buffer_font_size.into()),
background_color: Some(colors.editor_foreground.opacity(0.1)),
..Default::default()
},
link: TextStyleRefinement {
background_color: Some(colors.editor_foreground.opacity(0.025)),
underline: Some(UnderlineStyle {
color: Some(colors.text_accent.opacity(0.5)),
thickness: px(1.),
..Default::default()
}),
..Default::default()
},
..Default::default()
@@ -131,6 +162,10 @@ impl ActiveThread {
)
});
self.rendered_messages_by_id.insert(*id, markdown);
self.list_state.scroll_to(ListOffset {
item_ix: old_len,
offset_in_item: Pixels(0.0),
});
}
fn handle_thread_event(
@@ -204,11 +239,12 @@ impl ActiveThread {
};
let context = self.thread.read(cx).context_for_message(message_id);
let colors = cx.theme().colors();
let (role_icon, role_name) = match message.role {
Role::User => (IconName::Person, "You"),
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
Role::System => (IconName::Settings, "System"),
let (role_icon, role_name, role_color) = match message.role {
Role::User => (IconName::Person, "You", Color::Muted),
Role::Assistant => (IconName::ZedAssistant, "Assistant", Color::Accent),
Role::System => (IconName::Settings, "System", Color::Default),
};
div()
@@ -218,36 +254,42 @@ impl ActiveThread {
.child(
v_flex()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.border_color(colors.border_variant)
.bg(colors.editor_background)
.rounded_md()
.child(
h_flex()
.justify_between()
.py_1()
.px_2()
.py_1p5()
.px_2p5()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.border_color(colors.border_variant)
.justify_between()
.child(
h_flex()
.gap_1p5()
.child(
Icon::new(role_icon)
.size(IconSize::XSmall)
.color(Color::Muted),
.color(role_color),
)
.child(Label::new(role_name).size(LabelSize::XSmall)),
.child(
Label::new(role_name)
.size(LabelSize::XSmall)
.color(role_color),
),
),
)
.child(v_flex().px_2().py_1().text_ui(cx).child(markdown.clone()))
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
.when_some(context, |parent, context| {
parent.child(
h_flex().flex_wrap().gap_2().p_1p5().children(
context
.iter()
.map(|context| ContextPill::new(context.clone())),
),
)
if !context.is_empty() {
parent.child(h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
context.iter().map(|context| {
ContextPill::new_added(context.clone(), false, None)
}),
))
} else {
parent
}
}),
)
.into_any()

View File

@@ -1,14 +1,18 @@
mod active_thread;
mod assistant_model_selector;
mod assistant_panel;
mod assistant_settings;
mod buffer_codegen;
mod context;
mod context_picker;
mod context_store;
mod context_strip;
mod inline_assistant;
mod inline_prompt_editor;
mod message_editor;
mod prompts;
mod streaming_diff;
mod terminal_codegen;
mod terminal_inline_assistant;
mod thread;
mod thread_history;
@@ -37,6 +41,7 @@ actions!(
NewThread,
ToggleContextPicker,
ToggleModelSelector,
RemoveAllContext,
OpenHistory,
Chat,
CycleNextInlineAssist,

View File

@@ -0,0 +1,85 @@
use fs::Fs;
use gpui::View;
use language_model::LanguageModelRegistry;
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::update_settings_file;
use std::sync::Arc;
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
use crate::{assistant_settings::AssistantSettings, ToggleModelSelector};
pub struct AssistantModelSelector {
selector: View<LanguageModelSelector>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
}
impl AssistantModelSelector {
pub(crate) fn new(
fs: Arc<dyn Fs>,
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
cx: &mut WindowContext,
) -> Self {
Self {
selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _cx| settings.set_model(model.clone()),
);
},
cx,
)
}),
menu_handle,
}
}
}
impl Render for AssistantModelSelector {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.selector.focus_handle(cx).clone();
LanguageModelSelectorPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match active_model {
Some(model) => h_flex()
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
}),
)
.with_handle(self.menu_handle.clone())
}
}

View File

@@ -19,7 +19,7 @@ use workspace::Workspace;
use crate::active_thread::ActiveThread;
use crate::assistant_settings::{AssistantDockPosition, AssistantSettings};
use crate::message_editor::MessageEditor;
use crate::thread::{ThreadError, ThreadId};
use crate::thread::{Thread, ThreadError, ThreadId};
use crate::thread_history::{PastThread, ThreadHistory};
use crate::thread_store::ThreadStore;
use crate::{NewThread, OpenHistory, ToggleFocus};
@@ -27,9 +27,22 @@ use crate::{NewThread, OpenHistory, ToggleFocus};
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
});
workspace
.register_action(|workspace, _: &ToggleFocus, cx| {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
})
.register_action(|workspace, _: &NewThread, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| panel.new_thread(cx));
workspace.focus_panel::<AssistantPanel>(cx);
}
})
.register_action(|workspace, _: &OpenHistory, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_history(cx));
}
});
},
)
.detach();
@@ -157,6 +170,12 @@ impl AssistantPanel {
self.message_editor.focus_handle(cx).focus(cx);
}
fn open_history(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::History;
self.history.focus_handle(cx).focus(cx);
cx.notify();
}
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
let Some(thread) = self
.thread_store
@@ -187,6 +206,10 @@ impl AssistantPanel {
self.message_editor.focus_handle(cx).focus(cx);
}
pub(crate) fn active_thread(&self, cx: &AppContext) -> Model<Thread> {
self.thread.read(cx).thread.clone()
}
pub(crate) fn delete_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
self.thread_store
.update(cx, |this, cx| this.delete_thread(thread_id, cx));
@@ -257,7 +280,7 @@ impl Panel for AssistantPanel {
}
fn icon(&self, _cx: &WindowContext) -> Option<IconName> {
Some(IconName::ZedAssistant)
Some(IconName::ZedAssistant2)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -267,26 +290,40 @@ impl Panel for AssistantPanel {
fn toggle_action(&self) -> Box<dyn Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
3
}
}
impl AssistantPanel {
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let title = if self.thread.read(cx).is_empty() {
SharedString::from("New Thread")
} else {
self.thread
.read(cx)
.summary(cx)
.unwrap_or_else(|| SharedString::from("Loading Summary…"))
};
h_flex()
.id("assistant-toolbar")
.px(DynamicSpacing::Base08.rems(cx))
.h(Tab::container_height(cx))
.flex_none()
.justify_between()
.gap(DynamicSpacing::Base08.rems(cx))
.h(Tab::container_height(cx))
.px(DynamicSpacing::Base08.rems(cx))
.bg(cx.theme().colors().tab_bar_background)
.border_b_1()
.border_color(cx.theme().colors().border)
.child(h_flex().children(self.thread.read(cx).summary(cx).map(Label::new)))
.child(h_flex().child(Label::new(title)))
.child(
h_flex()
.h_full()
.pl_1()
.pl_1p5()
.border_l_1()
.border_color(cx.theme().colors().border)
.gap(DynamicSpacing::Base02.rems(cx))
@@ -572,9 +609,7 @@ impl Render for AssistantPanel {
this.new_thread(cx);
}))
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
this.active_view = ActiveView::History;
this.history.focus_handle(cx).focus(cx);
cx.notify();
this.open_history(cx);
}))
.child(self.render_toolbar(cx))
.map(|parent| match self.active_view {

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,8 @@ impl ContextId {
pub struct Context {
pub id: ContextId,
pub name: SharedString,
pub parent: Option<SharedString>,
pub tooltip: Option<SharedString>,
pub kind: ContextKind,
pub text: SharedString,
}
@@ -40,7 +42,7 @@ pub fn attach_context_to_message(
for context in context.into_iter() {
match context.kind {
ContextKind::File => {
ContextKind::File { .. } => {
file_context.push_str(&context.text);
file_context.push('\n');
}

View File

@@ -22,6 +22,12 @@ use crate::context_picker::thread_context_picker::ThreadContextPicker;
use crate::context_store::ContextStore;
use crate::thread_store::ThreadStore;
#[derive(Debug, Clone, Copy)]
pub enum ConfirmBehavior {
KeepOpen,
Close,
}
#[derive(Debug, Clone)]
enum ContextPickerMode {
Default,
@@ -41,25 +47,25 @@ impl ContextPicker {
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let mut entries = vec![
ContextPickerEntry {
name: "File".into(),
kind: ContextKind::File,
icon: IconName::File,
},
ContextPickerEntry {
name: "Folder".into(),
kind: ContextKind::Directory,
icon: IconName::Folder,
},
ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,
icon: IconName::Globe,
},
];
let mut entries = Vec::new();
entries.push(ContextPickerEntry {
name: "File".into(),
kind: ContextKind::File,
icon: IconName::File,
});
entries.push(ContextPickerEntry {
name: "Folder".into(),
kind: ContextKind::Directory,
icon: IconName::Folder,
});
entries.push(ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,
icon: IconName::Globe,
});
if thread_store.is_some() {
entries.push(ContextPickerEntry {
@@ -74,6 +80,7 @@ impl ContextPicker {
workspace,
thread_store,
context_store,
confirm_behavior,
entries,
selected_ix: 0,
};
@@ -136,6 +143,7 @@ pub(crate) struct ContextPickerDelegate {
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
entries: Vec<ContextPickerEntry>,
selected_ix: usize,
}
@@ -175,6 +183,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));
@@ -185,6 +194,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));
@@ -195,6 +205,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker.clone(),
self.workspace.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));
@@ -206,6 +217,7 @@ impl PickerDelegate for ContextPickerDelegate {
thread_store.clone(),
self.context_picker.clone(),
self.context_store.clone(),
self.confirm_behavior,
cx,
)
}));

View File

@@ -1,18 +1,18 @@
// TODO: Remove this once we've implemented the functionality.
#![allow(unused)]
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::anyhow;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, WorktreeId};
use project::{PathMatchCandidateSet, ProjectPath, Worktree, WorktreeId};
use ui::{prelude::*, ListItem};
use util::ResultExt as _;
use workspace::Workspace;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::{push_fenced_codeblock, ContextStore};
pub struct DirectoryContextPicker {
picker: View<Picker<DirectoryContextPickerDelegate>>,
@@ -23,10 +23,15 @@ impl DirectoryContextPicker {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate =
DirectoryContextPickerDelegate::new(context_picker, workspace, context_store);
let delegate = DirectoryContextPickerDelegate::new(
context_picker,
workspace,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
@@ -49,6 +54,7 @@ pub struct DirectoryContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
}
@@ -58,15 +64,76 @@ impl DirectoryContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
context_picker,
workspace,
context_store,
confirm_behavior,
matches: Vec::new(),
selected_index: 0,
}
}
fn search(
&mut self,
query: String,
cancellation_flag: Arc<AtomicBool>,
workspace: &View<Workspace>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<Vec<PathMatch>> {
if query.is_empty() {
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let directory_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.directories(false, 0).map(move |entry| PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
path: entry.path.clone(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: true,
})
});
Task::ready(directory_matches.collect())
} else {
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
let candidate_sets = worktrees
.into_iter()
.map(|worktree| {
let worktree = worktree.read(cx);
PathMatchCandidateSet {
snapshot: worktree.snapshot(),
include_ignored: worktree
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name: true,
candidates: project::Candidates::Directories,
}
})
.collect::<Vec<_>>();
let executor = cx.background_executor().clone();
cx.foreground_executor().spawn(async move {
fuzzy::match_path_sets(
candidate_sets.as_slice(),
query.as_str(),
None,
false,
100,
&cancellation_flag,
executor,
)
.await
})
}
}
}
impl PickerDelegate for DirectoryContextPickerDelegate {
@@ -80,7 +147,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
self.selected_index
}
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix;
}
@@ -88,13 +155,122 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
"Search folders…".into()
}
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
// TODO: Implement this once we fix the issues with the file context picker.
Task::ready(())
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(());
};
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn(|this, mut cx| async move {
let mut paths = search_task.await;
let empty_path = Path::new("");
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
this.update(&mut cx, |this, _cx| {
this.delegate.matches = paths;
})
.log_err();
})
}
fn confirm(&mut self, _secondary: bool, _cx: &mut ViewContext<Picker<Self>>) {
// TODO: Implement this once we fix the issues with the file context picker.
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let workspace = self.workspace.clone();
let Some(project) = workspace
.upgrade()
.map(|workspace| workspace.read(cx).project().clone())
else {
return;
};
let path = mat.path.clone();
let already_included = self
.context_store
.update(cx, |context_store, _cx| {
if let Some(context_id) = context_store.included_directory(&path) {
context_store.remove_context(&context_id);
true
} else {
false
}
})
.unwrap_or(true);
if already_included {
return;
}
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
let worktree = project.update(&mut cx, |project, cx| {
project
.worktree_for_id(worktree_id, cx)
.ok_or_else(|| anyhow!("no worktree found for {worktree_id:?}"))
})??;
let files = worktree.update(&mut cx, |worktree, _cx| {
collect_files_in_path(worktree, &path)
})?;
let open_buffer_tasks = project.update(&mut cx, |project, cx| {
files
.into_iter()
.map(|file_path| {
project.open_buffer(
ProjectPath {
worktree_id,
path: file_path.clone(),
},
cx,
)
})
.collect::<Vec<_>>()
})?;
let open_all_buffers_tasks = cx.background_executor().spawn(async move {
let mut buffers = Vec::with_capacity(open_buffer_tasks.len());
for open_buffer_task in open_buffer_tasks {
let buffer = open_buffer_task.await?;
buffers.push(buffer);
}
anyhow::Ok(buffers)
});
let buffers = open_all_buffers_tasks.await?;
this.update(&mut cx, |this, cx| {
let mut text = String::new();
for buffer in buffers {
let buffer = buffer.read(cx);
let path = buffer.file().map_or(&path, |file| file.path());
push_fenced_codeblock(&path, buffer.text(), &mut text);
}
this.delegate
.context_store
.update(cx, |context_store, _cx| {
context_store.insert_directory(&path, text);
})?;
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
})??;
anyhow::Ok(())
})
.detach_and_log_err(cx)
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
@@ -108,10 +284,51 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
fn render_match(
&self,
_ix: usize,
_selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
None
let path_match = &self.matches[ix];
let directory_name = path_match.path.to_string_lossy().to_string();
let added = self.context_store.upgrade().map_or(false, |context_store| {
context_store
.read(cx)
.included_directory(&path_match.path)
.is_some()
});
Some(
ListItem::new(ix)
.inset(true)
.toggle_state(selected)
.child(h_flex().gap_2().child(Label::new(directory_name)))
.when(added, |el| {
el.end_slot(
h_flex()
.gap_1()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success),
)
.child(Label::new("Added").size(LabelSize::Small)),
)
}),
)
}
}
fn collect_files_in_path(worktree: &Worktree, path: &Path) -> Vec<Arc<Path>> {
let mut files = Vec::new();
for entry in worktree.child_entries(path) {
if entry.is_dir() {
files.extend(collect_files_in_path(worktree, &entry.path));
} else if entry.is_file() {
files.push(entry.path.clone());
}
}
files
}

View File

@@ -11,8 +11,7 @@ use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, ViewContext};
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
pub struct FetchContextPicker {
@@ -24,9 +23,15 @@ impl FetchContextPicker {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, context_store);
let delegate = FetchContextPickerDelegate::new(
context_picker,
workspace,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
@@ -56,6 +61,7 @@ pub struct FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
url: String,
}
@@ -64,11 +70,13 @@ impl FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
FetchContextPickerDelegate {
context_picker,
workspace,
context_store,
confirm_behavior,
url: String::new(),
}
}
@@ -167,7 +175,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
fn placeholder_text(&self, _cx: &mut ui::WindowContext) -> Arc<str> {
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
"Enter a URL…".into()
}
@@ -184,6 +192,7 @@ impl PickerDelegate for FetchContextPickerDelegate {
let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone();
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
let text = Self::build_message(http_client, &url).await?;
@@ -191,8 +200,17 @@ impl PickerDelegate for FetchContextPickerDelegate {
this.delegate
.context_store
.update(cx, |context_store, _cx| {
context_store.insert_context(ContextKind::FetchedUrl, url, text);
})
if context_store.included_url(&url).is_none() {
context_store.insert_fetched_url(url, text);
}
})?;
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
anyhow::Ok(())
})??;
anyhow::Ok(())
@@ -213,13 +231,29 @@ impl PickerDelegate for FetchContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let added = self.context_store.upgrade().map_or(false, |context_store| {
context_store.read(cx).included_url(&self.url).is_some()
});
Some(
ListItem::new(ix)
.inset(true)
.toggle_state(selected)
.child(Label::new(self.url.clone())),
.child(Label::new(self.url.clone()))
.when(added, |child| {
child.disabled(true).end_slot(
h_flex()
.gap_1()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success),
)
.child(Label::new("Added").size(LabelSize::Small)),
)
}),
)
}
}

View File

@@ -1,20 +1,17 @@
use std::fmt::Write as _;
use std::ops::RangeInclusive;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, WorktreeId};
use ui::{prelude::*, ListItem};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{prelude::*, ListItem, Tooltip};
use util::ResultExt as _;
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::{ContextStore, IncludedFile};
pub struct FileContextPicker {
picker: View<Picker<FileContextPickerDelegate>>,
@@ -25,9 +22,15 @@ impl FileContextPicker {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FileContextPickerDelegate::new(context_picker, workspace, context_store);
let delegate = FileContextPickerDelegate::new(
context_picker,
workspace,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
@@ -50,6 +53,7 @@ pub struct FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<PathMatch>,
selected_index: usize,
}
@@ -59,11 +63,13 @@ impl FileContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
context_store: WeakModel<ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
Self {
context_picker,
workspace,
context_store,
confirm_behavior,
matches: Vec::new(),
selected_index: 0,
}
@@ -79,44 +85,37 @@ impl FileContextPickerDelegate {
if query.is_empty() {
let workspace = workspace.read(cx);
let project = workspace.project().read(cx);
let entries = workspace.recent_navigation_history(Some(10), cx);
let entries = entries
let recent_matches = workspace
.recent_navigation_history(Some(10), cx)
.into_iter()
.map(|entries| entries.0)
.chain(project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let id = worktree.id();
worktree
.child_entries(Path::new(""))
.filter(|entry| entry.kind.is_file())
.map(move |entry| project::ProjectPath {
worktree_id: id,
path: entry.path.clone(),
})
}))
.collect::<Vec<_>>();
let path_prefix: Arc<str> = Arc::default();
Task::ready(
entries
.into_iter()
.filter_map(|entry| {
let worktree = project.worktree_for_id(entry.worktree_id, cx)?;
let mut full_path = PathBuf::from(worktree.read(cx).root_name());
full_path.push(&entry.path);
Some(PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: entry.worktree_id.to_usize(),
path: full_path.into(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: false,
})
.filter_map(|(project_path, _)| {
let worktree = project.worktree_for_id(project_path.worktree_id, cx)?;
Some(PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: project_path.worktree_id.to_usize(),
path: project_path.path,
path_prefix: worktree.read(cx).root_name().into(),
distance_to_relative_ancestor: 0,
is_dir: false,
})
.collect(),
)
});
let file_matches = project.worktrees(cx).flat_map(|worktree| {
let worktree = worktree.read(cx);
let path_prefix: Arc<str> = worktree.root_name().into();
worktree.files(true, 0).map(move |entry| PathMatch {
score: 0.,
positions: Vec::new(),
worktree_id: worktree.id().to_usize(),
path: entry.path.clone(),
path_prefix: path_prefix.clone(),
distance_to_relative_ancestor: 0,
is_dir: false,
})
});
Task::ready(recent_matches.chain(file_matches).collect())
} else {
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
let candidate_sets = worktrees
@@ -190,7 +189,9 @@ impl PickerDelegate for FileContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let mat = &self.matches[self.selected_index];
let Some(mat) = self.matches.get(self.selected_index) else {
return;
};
let workspace = self.workspace.clone();
let Some(project) = workspace
@@ -200,13 +201,40 @@ impl PickerDelegate for FileContextPickerDelegate {
return;
};
let path = mat.path.clone();
let already_included = self
.context_store
.update(cx, |context_store, _cx| {
match context_store.included_file(&path) {
Some(IncludedFile::Direct(context_id)) => {
context_store.remove_context(&context_id);
true
}
Some(IncludedFile::InDirectory(_)) => true,
None => false,
}
})
.unwrap_or(true);
if already_included {
return;
}
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
let confirm_behavior = self.confirm_behavior;
cx.spawn(|this, mut cx| async move {
let Some(open_buffer_task) = project
.update(&mut cx, |project, cx| {
project.open_buffer((worktree_id, path.clone()), cx)
let project_path = ProjectPath {
worktree_id,
path: path.clone(),
};
let task = project.open_buffer(project_path, cx);
Some(task)
})
.ok()
.flatten()
else {
return anyhow::Ok(());
};
@@ -214,22 +242,18 @@ impl PickerDelegate for FileContextPickerDelegate {
let buffer = open_buffer_task.await?;
this.update(&mut cx, |this, cx| {
this.delegate.context_store.update(cx, |context_store, cx| {
let mut text = String::new();
text.push_str(&codeblock_fence_for_path(Some(&path), None));
text.push_str(&buffer.read(cx).text());
if !text.ends_with('\n') {
text.push('\n');
}
this.delegate
.context_store
.update(cx, |context_store, cx| {
context_store.insert_file(buffer.read(cx));
})?;
text.push_str("```\n");
match confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => this.delegate.dismissed(cx),
}
context_store.insert_context(
ContextKind::File,
path.to_string_lossy().to_string(),
text,
);
})
anyhow::Ok(())
})??;
anyhow::Ok(())
@@ -250,53 +274,80 @@ impl PickerDelegate for FileContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let path_match = &self.matches[ix];
let file_name = path_match
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let directory = path_match
.path
.parent()
.map(|directory| format!("{}/", directory.to_string_lossy()));
let (file_name, directory) = if path_match.path.as_ref() == Path::new("") {
(SharedString::from(path_match.path_prefix.clone()), None)
} else {
let file_name = path_match
.path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string()
.into();
let mut directory = format!("{}/", path_match.path_prefix);
if let Some(parent) = path_match
.path
.parent()
.filter(|parent| parent != &Path::new(""))
{
directory.push_str(&parent.to_string_lossy());
directory.push('/');
}
(file_name, Some(directory))
};
let added = self
.context_store
.upgrade()
.and_then(|context_store| context_store.read(cx).included_file(&path_match.path));
Some(
ListItem::new(ix).inset(true).toggle_state(selected).child(
h_flex()
.gap_2()
.child(Label::new(file_name))
.children(directory.map(|directory| {
Label::new(directory)
.size(LabelSize::Small)
.color(Color::Muted)
})),
),
ListItem::new(ix)
.inset(true)
.toggle_state(selected)
.child(
h_flex()
.gap_2()
.child(Label::new(file_name))
.children(directory.map(|directory| {
Label::new(directory)
.size(LabelSize::Small)
.color(Color::Muted)
})),
)
.when_some(added, |el, added| match added {
IncludedFile::Direct(_) => el.end_slot(
h_flex()
.gap_1()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success),
)
.child(Label::new("Added").size(LabelSize::Small)),
),
IncludedFile::InDirectory(dir_name) => {
let dir_name = dir_name.to_string_lossy().into_owned();
el.end_slot(
h_flex()
.gap_1()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success),
)
.child(Label::new("Included").size(LabelSize::Small)),
)
.tooltip(move |cx| Tooltip::text(format!("in {dir_name}"), cx))
}
}),
)
}
}
fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<RangeInclusive<u32>>) -> String {
let mut text = String::new();
write!(text, "```").unwrap();
if let Some(path) = path {
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
write!(text, "{} ", extension).unwrap();
}
write!(text, "{}", path.display()).unwrap();
} else {
write!(text, "untitled").unwrap();
}
if let Some(row_range) = row_range {
write!(text, ":{}-{}", row_range.start() + 1, row_range.end() + 1).unwrap();
}
text.push('\n');
text
}

View File

@@ -5,8 +5,7 @@ use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, Wea
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem};
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store;
use crate::thread::ThreadId;
use crate::thread_store::ThreadStore;
@@ -20,10 +19,15 @@ impl ThreadContextPicker {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate =
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
let delegate = ThreadContextPickerDelegate::new(
thread_store,
context_picker,
context_store,
confirm_behavior,
);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
ThreadContextPicker { picker }
@@ -52,6 +56,7 @@ pub struct ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
matches: Vec<ThreadContextEntry>,
selected_index: usize,
}
@@ -61,11 +66,13 @@ impl ThreadContextPickerDelegate {
thread_store: WeakModel<ThreadStore>,
context_picker: WeakView<ContextPicker>,
context_store: WeakModel<context_store::ContextStore>,
confirm_behavior: ConfirmBehavior,
) -> Self {
ThreadContextPickerDelegate {
thread_store,
context_picker,
context_store,
confirm_behavior,
matches: Vec::new(),
selected_index: 0,
}
@@ -146,7 +153,9 @@ impl PickerDelegate for ThreadContextPickerDelegate {
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let entry = &self.matches[self.selected_index];
let Some(entry) = self.matches.get(self.selected_index) else {
return;
};
let Some(thread_store) = self.thread_store.upgrade() else {
return;
@@ -159,27 +168,18 @@ impl PickerDelegate for ThreadContextPickerDelegate {
self.context_store
.update(cx, |context_store, cx| {
let text = thread.update(cx, |thread, _cx| {
let mut text = String::new();
for message in thread.messages() {
text.push_str(match message.role {
language_model::Role::User => "User:",
language_model::Role::Assistant => "Assistant:",
language_model::Role::System => "System:",
});
text.push('\n');
text.push_str(&message.text);
text.push('\n');
}
text
});
context_store.insert_context(ContextKind::Thread, entry.summary.clone(), text);
if let Some(context_id) = context_store.included_thread(&entry.id) {
context_store.remove_context(&context_id);
} else {
context_store.insert_thread(thread.read(cx));
}
})
.ok();
match self.confirm_behavior {
ConfirmBehavior::KeepOpen => {}
ConfirmBehavior::Close => self.dismissed(cx),
}
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
@@ -195,15 +195,31 @@ impl PickerDelegate for ThreadContextPickerDelegate {
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
let thread = &self.matches[ix];
let added = self.context_store.upgrade().map_or(false, |ctx_store| {
ctx_store.read(cx).included_thread(&thread.id).is_some()
});
Some(
ListItem::new(ix)
.inset(true)
.toggle_state(selected)
.child(thread.summary.clone()),
.child(Label::new(thread.summary.clone()))
.when(added, |el| {
el.end_slot(
h_flex()
.gap_1()
.child(
Icon::new(IconName::Check)
.size(IconSize::Small)
.color(Color::Success),
)
.child(Label::new("Added").size(LabelSize::Small)),
)
}),
)
}
}

View File

@@ -1,10 +1,23 @@
use gpui::SharedString;
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use crate::context::{Context, ContextId, ContextKind};
use collections::{HashMap, HashSet};
use gpui::SharedString;
use language::Buffer;
use crate::thread::Thread;
use crate::{
context::{Context, ContextId, ContextKind},
thread::ThreadId,
};
pub struct ContextStore {
context: Vec<Context>,
next_context_id: ContextId,
files: HashMap<PathBuf, ContextId>,
directories: HashMap<PathBuf, ContextId>,
threads: HashMap<ThreadId, ContextId>,
fetched_urls: HashMap<String, ContextId>,
}
impl ContextStore {
@@ -12,6 +25,10 @@ impl ContextStore {
Self {
context: Vec::new(),
next_context_id: ContextId(0),
files: HashMap::default(),
directories: HashMap::default(),
threads: HashMap::default(),
fetched_urls: HashMap::default(),
}
}
@@ -19,29 +36,192 @@ impl ContextStore {
&self.context
}
pub fn drain(&mut self) -> Vec<Context> {
self.context.drain(..).collect()
}
pub fn clear(&mut self) {
self.context.clear();
self.files.clear();
self.directories.clear();
self.threads.clear();
self.fetched_urls.clear();
}
pub fn insert_context(
&mut self,
kind: ContextKind,
name: impl Into<SharedString>,
text: impl Into<SharedString>,
) {
pub fn insert_file(&mut self, buffer: &Buffer) {
let Some(file) = buffer.file() else {
return;
};
let path = file.path();
let id = self.next_context_id.post_inc();
self.files.insert(path.to_path_buf(), id);
let full_path: SharedString = path.to_string_lossy().into_owned().into();
let name = match path.file_name() {
Some(name) => name.to_string_lossy().into_owned().into(),
None => full_path.clone(),
};
let parent = path
.parent()
.and_then(|p| p.file_name())
.map(|p| p.to_string_lossy().into_owned().into());
let mut text = String::new();
push_fenced_codeblock(path, buffer.text(), &mut text);
self.context.push(Context {
id: self.next_context_id.post_inc(),
name: name.into(),
kind,
id,
name,
parent,
tooltip: Some(full_path),
kind: ContextKind::File,
text: text.into(),
});
}
pub fn insert_directory(&mut self, path: &Path, text: impl Into<SharedString>) {
let id = self.next_context_id.post_inc();
self.directories.insert(path.to_path_buf(), id);
let full_path: SharedString = path.to_string_lossy().into_owned().into();
let name = match path.file_name() {
Some(name) => name.to_string_lossy().into_owned().into(),
None => full_path.clone(),
};
let parent = path
.parent()
.and_then(|p| p.file_name())
.map(|p| p.to_string_lossy().into_owned().into());
self.context.push(Context {
id,
name,
parent,
tooltip: Some(full_path),
kind: ContextKind::Directory,
text: text.into(),
});
}
pub fn insert_thread(&mut self, thread: &Thread) {
let context_id = self.next_context_id.post_inc();
self.threads.insert(thread.id().clone(), context_id);
self.context.push(Context {
id: context_id,
name: thread.summary().unwrap_or("New thread".into()),
parent: None,
tooltip: None,
kind: ContextKind::Thread,
text: thread.text().into(),
});
}
pub fn insert_fetched_url(&mut self, url: String, text: impl Into<SharedString>) {
let context_id = self.next_context_id.post_inc();
self.fetched_urls.insert(url.clone(), context_id);
self.context.push(Context {
id: context_id,
name: url.into(),
parent: None,
tooltip: None,
kind: ContextKind::FetchedUrl,
text: text.into(),
});
}
pub fn remove_context(&mut self, id: &ContextId) {
self.context.retain(|context| context.id != *id);
let Some(ix) = self.context.iter().position(|context| context.id == *id) else {
return;
};
match self.context.remove(ix).kind {
ContextKind::File => {
self.files.retain(|_, context_id| context_id != id);
}
ContextKind::Directory => {
self.directories.retain(|_, context_id| context_id != id);
}
ContextKind::FetchedUrl => {
self.fetched_urls.retain(|_, context_id| context_id != id);
}
ContextKind::Thread => {
self.threads.retain(|_, context_id| context_id != id);
}
}
}
pub fn included_file(&self, path: &Path) -> Option<IncludedFile> {
if let Some(id) = self.files.get(path) {
return Some(IncludedFile::Direct(*id));
}
if self.directories.is_empty() {
return None;
}
let mut buf = path.to_path_buf();
while buf.pop() {
if let Some(_) = self.directories.get(&buf) {
return Some(IncludedFile::InDirectory(buf));
}
}
None
}
pub fn included_directory(&self, path: &Path) -> Option<ContextId> {
self.directories.get(path).copied()
}
pub fn included_thread(&self, thread_id: &ThreadId) -> Option<ContextId> {
self.threads.get(thread_id).copied()
}
pub fn included_url(&self, url: &str) -> Option<ContextId> {
self.fetched_urls.get(url).copied()
}
pub fn duplicated_names(&self) -> HashSet<SharedString> {
let mut seen = HashSet::default();
let mut dupes = HashSet::default();
for context in self.context().iter() {
if !seen.insert(&context.name) {
dupes.insert(context.name.clone());
}
}
dupes
}
}
pub enum IncludedFile {
Direct(ContextId),
InDirectory(PathBuf),
}
pub(crate) fn push_fenced_codeblock(path: &Path, content: String, buffer: &mut String) {
buffer.reserve(content.len() + 64);
write!(buffer, "```").unwrap();
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
write!(buffer, "{} ", extension).unwrap();
}
write!(buffer, "{}", path.display()).unwrap();
buffer.push('\n');
buffer.push_str(&content);
if !buffer.ends_with('\n') {
buffer.push('\n');
}
buffer.push_str("```\n");
}

View File

@@ -1,20 +1,30 @@
use std::rc::Rc;
use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
use editor::Editor;
use gpui::{
AppContext, DismissEvent, EventEmitter, FocusHandle, Model, Subscription, View, WeakModel,
WeakView,
};
use language::Buffer;
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
use workspace::Workspace;
use crate::context_picker::ContextPicker;
use crate::context::ContextKind;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::thread::Thread;
use crate::thread_store::ThreadStore;
use crate::ui::ContextPill;
use crate::ToggleContextPicker;
use crate::{AssistantPanel, RemoveAllContext, ToggleContextPicker};
pub struct ContextStrip {
context_store: Model<ContextStore>,
context_picker: View<ContextPicker>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle,
suggest_context_kind: SuggestContextKind,
workspace: WeakView<Workspace>,
_context_picker_subscription: Subscription,
}
impl ContextStrip {
@@ -24,30 +34,111 @@ impl ContextStrip {
thread_store: Option<WeakModel<ThreadStore>>,
focus_handle: FocusHandle,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
suggest_context_kind: SuggestContextKind,
cx: &mut ViewContext<Self>,
) -> Self {
let context_picker = cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
thread_store.clone(),
context_store.downgrade(),
ConfirmBehavior::KeepOpen,
cx,
)
});
let context_picker_subscription =
cx.subscribe(&context_picker, Self::handle_context_picker_event);
Self {
context_store: context_store.clone(),
context_picker: cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
thread_store.clone(),
context_store.downgrade(),
cx,
)
}),
context_picker,
context_picker_menu_handle,
focus_handle,
suggest_context_kind,
workspace,
_context_picker_subscription: context_picker_subscription,
}
}
fn suggested_context(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
match self.suggest_context_kind {
SuggestContextKind::File => self.suggested_file(cx),
SuggestContextKind::Thread => self.suggested_thread(cx),
}
}
fn suggested_file(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
let workspace = self.workspace.upgrade()?;
let active_item = workspace.read(cx).active_item(cx)?;
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
let active_buffer = editor.buffer().read(cx).as_singleton()?;
let path = active_buffer.read(cx).file()?.path();
if self.context_store.read(cx).included_file(path).is_some() {
return None;
}
let name = match path.file_name() {
Some(name) => name.to_string_lossy().into_owned().into(),
None => path.to_string_lossy().into_owned().into(),
};
Some(SuggestedContext::File {
name,
buffer: active_buffer.downgrade(),
})
}
fn suggested_thread(&self, cx: &ViewContext<Self>) -> Option<SuggestedContext> {
let workspace = self.workspace.upgrade()?;
let active_thread = workspace
.read(cx)
.panel::<AssistantPanel>(cx)?
.read(cx)
.active_thread(cx);
let weak_active_thread = active_thread.downgrade();
let active_thread = active_thread.read(cx);
if self
.context_store
.read(cx)
.included_thread(active_thread.id())
.is_some()
{
return None;
}
Some(SuggestedContext::Thread {
name: active_thread.summary().unwrap_or("New Thread".into()),
thread: weak_active_thread,
})
}
fn handle_context_picker_event(
&mut self,
_picker: View<ContextPicker>,
_event: &DismissEvent,
cx: &mut ViewContext<Self>,
) {
cx.emit(ContextStripEvent::PickerDismissed);
}
}
impl Render for ContextStrip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let context = self.context_store.read(cx).context();
let context_store = self.context_store.read(cx);
let context = context_store.context().clone();
let context_picker = self.context_picker.clone();
let focus_handle = self.focus_handle.clone();
let suggested_context = self.suggested_context(cx);
let dupe_names = context_store.duplicated_names();
h_flex()
.flex_wrap()
.gap_1()
@@ -58,13 +149,17 @@ impl Render for ContextStrip {
IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled)
.tooltip(move |cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
cx,
)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
cx,
)
}
}),
)
.attach(gpui::Corner::TopLeft)
@@ -75,31 +170,135 @@ impl Render for ContextStrip {
})
.with_handle(self.context_picker_menu_handle.clone()),
)
.when(context.is_empty() && suggested_context.is_none(), {
|parent| {
parent.child(
h_flex()
.ml_1p5()
.gap_2()
.child(
Label::new("Add Context")
.size(LabelSize::Small)
.color(Color::Muted),
)
.opacity(0.5)
.children(
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
),
)
}
})
.children(context.iter().map(|context| {
ContextPill::new(context.clone()).on_remove({
let context = context.clone();
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| {
this.remove_context(&context.id);
});
cx.notify();
}))
})
}))
.when(!context.is_empty(), |parent| {
parent.child(
IconButton::new("remove-all-context", IconName::Eraser)
.icon_size(IconSize::Small)
.tooltip(move |cx| Tooltip::text("Remove All Context", cx))
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| this.clear());
cx.notify();
})
}),
ContextPill::new_added(
context.clone(),
dupe_names.contains(&context.name),
Some({
let context = context.clone();
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, cx| {
context_store.update(cx, |this, _cx| {
this.remove_context(&context.id);
});
cx.notify();
}))
}),
)
}))
.when_some(suggested_context, |el, suggested| {
el.child(ContextPill::new_suggested(
suggested.name().clone(),
suggested.kind(),
{
let context_store = self.context_store.clone();
Rc::new(cx.listener(move |_this, _event, cx| {
context_store.update(cx, |context_store, cx| {
suggested.accept(context_store, cx);
});
cx.notify();
}))
},
))
})
.when(!context.is_empty(), {
move |parent| {
parent.child(
IconButton::new("remove-all-context", IconName::Eraser)
.icon_size(IconSize::Small)
.tooltip({
let focus_handle = focus_handle.clone();
move |cx| {
Tooltip::for_action_in(
"Remove All Context",
&RemoveAllContext,
&focus_handle,
cx,
)
}
})
.on_click(cx.listener({
let focus_handle = focus_handle.clone();
move |_this, _event, cx| {
focus_handle.dispatch_action(&RemoveAllContext, cx);
}
})),
)
}
})
}
}
pub enum ContextStripEvent {
PickerDismissed,
}
impl EventEmitter<ContextStripEvent> for ContextStrip {}
pub enum SuggestContextKind {
File,
Thread,
}
#[derive(Clone)]
pub enum SuggestedContext {
File {
name: SharedString,
buffer: WeakModel<Buffer>,
},
Thread {
name: SharedString,
thread: WeakModel<Thread>,
},
}
impl SuggestedContext {
pub fn name(&self) -> &SharedString {
match self {
Self::File { name, .. } => name,
Self::Thread { name, .. } => name,
}
}
pub fn accept(&self, context_store: &mut ContextStore, cx: &mut AppContext) {
match self {
Self::File { buffer, name: _ } => {
if let Some(buffer) = buffer.upgrade() {
context_store.insert_file(buffer.read(cx));
};
}
Self::Thread { thread, name: _ } => {
if let Some(thread) = thread.upgrade() {
context_store.insert_thread(thread.read(cx));
};
}
}
}
pub fn kind(&self) -> ContextKind {
match self {
Self::File { .. } => ContextKind::File,
Self::Thread { .. } => ContextKind::Thread,
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,29 @@
use std::sync::Arc;
use editor::{Editor, EditorElement, EditorStyle};
use editor::{Editor, EditorElement, EditorEvent, EditorStyle};
use fs::Fs;
use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakModel, WeakView};
use gpui::{
AppContext, DismissEvent, FocusableView, Model, Subscription, TextStyle, View, WeakModel,
WeakView,
};
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use settings::{update_settings_file, Settings};
use language_model_selector::LanguageModelSelector;
use rope::Point;
use settings::Settings;
use theme::ThemeSettings;
use ui::{
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenuHandle,
Tooltip,
prelude::*, ButtonLike, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle,
SwitchWithLabel,
};
use workspace::Workspace;
use crate::assistant_settings::AssistantSettings;
use crate::context_picker::ContextPicker;
use crate::assistant_model_selector::AssistantModelSelector;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore;
use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
use crate::{Chat, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor {
thread: Model<Thread>,
@@ -27,9 +31,12 @@ pub struct MessageEditor {
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
inline_context_picker: View<ContextPicker>,
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool,
_subscriptions: Vec<Subscription>,
}
impl MessageEditor {
@@ -42,56 +49,78 @@ impl MessageEditor {
) -> Self {
let context_store = cx.new_model(|_cx| ContextStore::new());
let context_picker_menu_handle = PopoverMenuHandle::default();
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
editor.set_placeholder_text("Ask anything, @ to add context", cx);
let mut editor = Editor::auto_height(10, cx);
editor.set_placeholder_text("Ask anything", cx);
editor.set_show_indent_guides(false, cx);
editor
});
let inline_context_picker = cx.new_view(|cx| {
ContextPicker::new(
workspace.clone(),
Some(thread_store.clone()),
context_store.downgrade(),
ConfirmBehavior::Close,
cx,
)
});
let context_strip = cx.new_view(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
Some(thread_store.clone()),
editor.focus_handle(cx),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
cx,
)
});
let subscriptions = vec![
cx.subscribe(&editor, Self::handle_editor_event),
cx.subscribe(
&inline_context_picker,
Self::handle_inline_context_picker_event,
),
cx.subscribe(&context_strip, Self::handle_context_strip_event),
];
Self {
thread,
editor: editor.clone(),
context_store: context_store.clone(),
context_strip: cx.new_view(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
Some(thread_store.clone()),
editor.focus_handle(cx),
context_picker_menu_handle.clone(),
cx,
)
}),
context_store,
context_strip,
context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _cx| settings.set_model(model.clone()),
);
},
cx,
)
inline_context_picker,
inline_context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
}),
language_model_selector_menu_handle: PopoverMenuHandle::default(),
model_selector_menu_handle,
use_tools: false,
_subscriptions: subscriptions,
}
}
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
self.language_model_selector_menu_handle.toggle(cx);
self.model_selector_menu_handle.toggle(cx)
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
pub fn remove_all_context(&mut self, _: &RemoveAllContext, cx: &mut ViewContext<Self>) {
self.context_store.update(cx, |store, _cx| store.clear());
cx.notify();
}
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx);
}
@@ -118,7 +147,9 @@ impl MessageEditor {
editor.clear(cx);
text
});
let context = self.context_store.update(cx, |this, _cx| this.drain());
let context = self
.context_store
.update(cx, |this, _cx| this.context().clone());
self.thread.update(cx, |thread, cx| {
thread.insert_user_message(user_message, context, cx);
@@ -143,48 +174,48 @@ impl MessageEditor {
None
}
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.language_model_selector.focus_handle(cx).clone();
fn handle_editor_event(
&mut self,
editor: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::SelectionsChanged { .. } => {
editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let newest_cursor = editor.selections.newest::<Point>(cx).head();
if newest_cursor.column > 0 {
let behind_cursor = Point::new(newest_cursor.row, newest_cursor.column - 1);
let char_behind_cursor = snapshot.chars_at(behind_cursor).next();
if char_behind_cursor == Some('@') {
self.inline_context_picker_menu_handle.show(cx);
}
}
});
}
_ => {}
}
}
LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match active_model {
Some(model) => h_flex()
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |cx| {
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
}),
)
.with_handle(self.language_model_selector_menu_handle.clone())
fn handle_inline_context_picker_event(
&mut self,
_inline_context_picker: View<ContextPicker>,
_event: &DismissEvent,
cx: &mut ViewContext<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
}
fn handle_context_strip_event(
&mut self,
_context_strip: View<ContextStrip>,
ContextStripEvent::PickerDismissed: &ContextStripEvent,
cx: &mut ViewContext<Self>,
) {
let editor_focus_handle = self.editor.focus_handle(cx);
cx.focus(&editor_focus_handle);
}
}
@@ -199,6 +230,7 @@ impl Render for MessageEditor {
let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.5;
let focus_handle = self.editor.focus_handle(cx);
let inline_context_picker = self.inline_context_picker.clone();
let bg_color = cx.theme().colors().editor_background;
v_flex()
@@ -206,63 +238,78 @@ impl Render for MessageEditor {
.on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::toggle_model_selector))
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::remove_all_context))
.size_full()
.gap_2()
.p_2()
.bg(bg_color)
.child(self.context_strip.clone())
.child(div().id("thread_editor").overflow_y_scroll().h_12().child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
EditorElement::new(
&self.editor,
EditorStyle {
background: bg_color,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
}))
.child(
h_flex()
.justify_between()
.child(CheckboxWithLabel::new(
"use-tools",
Label::new("Tools"),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
};
}),
))
v_flex()
.gap_4()
.child({
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: cx.theme().colors().text,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: font_size.into(),
font_weight: settings.ui_font.weight,
line_height: line_height.into(),
..Default::default()
};
EditorElement::new(
&self.editor,
EditorStyle {
background: bg_color,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
})
.child(
PopoverMenu::new("inline-context-picker")
.menu(move |_cx| Some(inline_context_picker.clone()))
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {
x: px(0.0),
y: px(-16.0),
})
.with_handle(self.inline_context_picker_menu_handle.clone()),
)
.child(
h_flex()
.gap_1()
.child(self.render_language_model_selector(cx))
.justify_between()
.child(SwitchWithLabel::new(
"use-tools",
Label::new("Tools").size(LabelSize::Small),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => {
false
}
};
}),
))
.child(
ButtonLike::new("chat")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit"))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
}),
h_flex().gap_1().child(self.model_selector.clone()).child(
ButtonLike::new("chat")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit").size(LabelSize::Small))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
)
.on_click(move |_event, cx| {
focus_handle.dispatch_action(&Chat, cx);
}),
),
),
),
)

View File

@@ -0,0 +1,192 @@
use crate::inline_prompt_editor::CodegenStatus;
use client::telemetry::Telemetry;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{AppContext, EventEmitter, Model, ModelContext, Task};
use language_model::{LanguageModelRegistry, LanguageModelRequest};
use language_models::report_assistant_event;
use std::{sync::Arc, time::Instant};
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
pub struct TerminalCodegen {
pub status: CodegenStatus,
pub telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>,
generation: Task<()>,
pub message_id: Option<String>,
transaction: Option<TerminalTransaction>,
}
impl EventEmitter<CodegenEvent> for TerminalCodegen {}
impl TerminalCodegen {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self {
terminal,
telemetry,
status: CodegenStatus::Idle,
generation: Task::ready(()),
message_id: None,
transaction: None,
}
}
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
let model_api_key = model.api_key(cx);
let http_client = cx.http_client();
let telemetry = self.telemetry.clone();
self.status = CodegenStatus::Pending;
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
self.generation = cx.spawn(|this, mut cx| async move {
let model_telemetry_id = model.telemetry_id();
let model_provider_id = model.provider_id();
let response = model.stream_completion_text(prompt, &cx).await;
let generate = async {
let message_id = response
.as_ref()
.ok()
.and_then(|response| response.message_id.clone());
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
let task = cx.background_executor().spawn({
let message_id = message_id.clone();
let executor = cx.background_executor().clone();
async move {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut chunks = response?.stream;
while let Some(chunk) = chunks.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());
}
let chunk = chunk?;
hunks_tx.send(chunk).await?;
}
anyhow::Ok(())
};
let result = task.await;
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
language_name: None,
},
telemetry,
http_client,
model_api_key,
&executor,
);
result?;
anyhow::Ok(())
}
});
this.update(&mut cx, |this, _| {
this.message_id = message_id;
})?;
while let Some(hunk) = hunks_rx.next().await {
this.update(&mut cx, |this, cx| {
if let Some(transaction) = &mut this.transaction {
transaction.push(hunk, cx);
cx.notify();
}
})?;
}
task.await?;
anyhow::Ok(())
};
let result = generate.await;
this.update(&mut cx, |this, cx| {
if let Err(error) = result {
this.status = CodegenStatus::Error(error);
} else {
this.status = CodegenStatus::Done;
}
cx.emit(CodegenEvent::Finished);
cx.notify();
})
.ok();
});
cx.notify();
}
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished);
cx.notify();
}
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.complete(cx);
}
}
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.undo(cx);
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum CodegenEvent {
Finished,
}
pub const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
terminal: Model<Terminal>,
}
impl TerminalTransaction {
pub fn start(terminal: Model<Terminal>) -> Self {
Self { terminal }
}
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
pub fn undo(&self, cx: &mut AppContext) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
}
pub fn complete(&self, cx: &mut AppContext) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});
}
fn sanitize_input(input: String) -> String {
input.replace(['\r', '\n'], "")
}
}

View File

@@ -1,37 +1,29 @@
use crate::assistant_settings::AssistantSettings;
use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::inline_prompt_editor::{
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
};
use crate::prompts::PromptBuilder;
use crate::terminal_codegen::{CodegenEvent, TerminalCodegen, CLEAR_INPUT};
use crate::thread_store::ThreadStore;
use crate::ToggleContextPicker;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque};
use editor::{
actions::{MoveDown, MoveUp, SelectAll},
Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
};
use editor::{actions::SelectAll, MultiBuffer};
use fs::Fs;
use futures::{channel::mpsc, SinkExt, StreamExt};
use gpui::{
AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext,
Subscription, Task, TextStyle, UpdateGlobal, View, WeakModel, WeakView,
AppContext, Context, FocusableView, Global, Model, Subscription, UpdateGlobal, View, WeakModel,
WeakView,
};
use language::Buffer;
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
use language_models::report_assistant_event;
use settings::{update_settings_file, Settings};
use std::{cmp, sync::Arc, time::Instant};
use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal;
use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{prelude::*, text_for_action, IconButtonShape, PopoverMenuHandle, Tooltip};
use ui::prelude::*;
use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
@@ -47,17 +39,6 @@ pub fn init(
const DEFAULT_CONTEXT_LINES: usize = 50;
const PROMPT_HISTORY_MAX_LEN: usize = 20;
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
struct TerminalInlineAssistId(usize);
impl TerminalInlineAssistId {
fn post_inc(&mut self) -> TerminalInlineAssistId {
let id = *self;
self.0 += 1;
id
}
}
pub struct TerminalInlineAssistant {
next_assist_id: TerminalInlineAssistId,
assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>,
@@ -98,10 +79,10 @@ impl TerminalInlineAssistant {
MultiBuffer::singleton(cx.new_model(|cx| Buffer::local(String::new(), cx)), cx)
});
let context_store = cx.new_model(|_cx| ContextStore::new());
let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone()));
let codegen = cx.new_model(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new_view(|cx| {
PromptEditor::new(
PromptEditor::new_terminal(
assist_id,
self.prompt_history.clone(),
prompt_buffer.clone(),
@@ -150,11 +131,11 @@ impl TerminalInlineAssistant {
fn handle_prompt_editor_event(
&mut self,
prompt_editor: View<PromptEditor>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
event: &PromptEditorEvent,
cx: &mut WindowContext,
) {
let assist_id = prompt_editor.read(cx).id;
let assist_id = prompt_editor.read(cx).id();
match event {
PromptEditorEvent::StartRequested => {
self.start_assist(assist_id, cx);
@@ -380,8 +361,8 @@ impl TerminalInlineAssistant {
struct TerminalInlineAssist {
terminal: WeakView<TerminalView>,
prompt_editor: Option<View<PromptEditor>>,
codegen: Model<Codegen>,
prompt_editor: Option<View<PromptEditor<TerminalCodegen>>>,
codegen: Model<TerminalCodegen>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
_subscriptions: Vec<Subscription>,
@@ -391,12 +372,12 @@ impl TerminalInlineAssist {
pub fn new(
assist_id: TerminalInlineAssistId,
terminal: &View<TerminalView>,
prompt_editor: View<PromptEditor>,
prompt_editor: View<PromptEditor<TerminalCodegen>>,
workspace: WeakView<Workspace>,
context_store: Model<ContextStore>,
cx: &mut WindowContext,
) -> Self {
let codegen = prompt_editor.read(cx).codegen.clone();
let codegen = prompt_editor.read(cx).codegen().clone();
Self {
terminal: terminal.downgrade(),
prompt_editor: Some(prompt_editor.clone()),
@@ -447,660 +428,3 @@ impl TerminalInlineAssist {
}
}
}
enum PromptEditorEvent {
StartRequested,
StopRequested,
ConfirmRequested { execute: bool },
CancelRequested,
DismissRequested,
Resized { height_in_lines: u8 },
}
struct PromptEditor {
id: TerminalInlineAssistId,
height_in_lines: u8,
editor: View<Editor>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
pending_prompt: String,
codegen: Model<Codegen>,
_codegen_subscription: Subscription,
editor_subscriptions: Vec<Subscription>,
}
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let status = &self.codegen.read(cx).status;
let mut buttons = Vec::new();
buttons.extend(match status {
CodegenStatus::Idle => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("start", IconName::SparkleAlt)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Generate", &menu::Confirm, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
.into_any_element(),
],
CodegenStatus::Pending => vec![
IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element(),
IconButton::new("stop", IconName::Stop)
.icon_color(Color::Error)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Interrupt Generation",
Some(&menu::Cancel),
"Changes won't be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
.into_any_element(),
],
CodegenStatus::Error(_) | CodegenStatus::Done => {
let cancel = IconButton::new("cancel", IconName::Close)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
.into_any_element();
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
vec![
cancel,
IconButton::new("restart", IconName::RotateCw)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::with_meta(
"Restart Generation",
Some(&menu::Confirm),
"Changes will be discarded",
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::StartRequested);
}))
.into_any_element(),
]
} else {
vec![
cancel,
IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action("Accept Generated Command", &menu::Confirm, cx)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}))
.into_any_element(),
IconButton::new("confirm", IconName::Play)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
.tooltip(|cx| {
Tooltip::for_action(
"Execute Generated Command",
&menu::SecondaryConfirm,
cx,
)
})
.on_click(cx.listener(|_, _, cx| {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}))
.into_any_element(),
]
}
}
});
v_flex()
.border_y_1()
.border_color(cx.theme().status().info_border)
.py_2()
.size_full()
.child(
h_flex()
.key_context("PromptEditor")
.bg(cx.theme().colors().editor_background)
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::secondary_confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.child(
h_flex()
.w_12()
.justify_center()
.gap_2()
.child(LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
cx,
)
}),
))
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
let error_message = SharedString::from(error.to_string());
Some(
div()
.id("error")
.tooltip(move |cx| {
Tooltip::text(error_message.clone(), cx)
})
.child(
Icon::new(IconName::XCircle)
.size(IconSize::Small)
.color(Color::Error),
),
)
} else {
None
},
),
)
.child(div().flex_1().child(self.render_prompt_editor(cx)))
.child(h_flex().gap_1().pr_4().children(buttons)),
)
.child(h_flex().child(self.context_strip.clone()))
}
}
impl FocusableView for PromptEditor {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.editor.focus_handle(cx)
}
}
impl PromptEditor {
const MAX_LINES: u8 = 8;
#[allow(clippy::too_many_arguments)]
fn new(
id: TerminalInlineAssistId,
prompt_history: VecDeque<String>,
prompt_buffer: Model<MultiBuffer>,
codegen: Model<Codegen>,
fs: Arc<dyn Fs>,
context_store: Model<ContextStore>,
workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>,
cx: &mut ViewContext<Self>,
) -> Self {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::new(
EditorMode::AutoHeight {
max_lines: Self::MAX_LINES as usize,
},
prompt_buffer,
None,
false,
cx,
);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor
});
let context_picker_menu_handle = PopoverMenuHandle::default();
let mut this = Self {
id,
height_in_lines: 1,
editor: prompt_editor.clone(),
context_strip: cx.new_view(|cx| {
ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
cx,
)
}),
context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| {
let fs = fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
cx,
)
}),
edited_since_done: false,
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
_codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
editor_subscriptions: Vec::new(),
codegen,
};
this.count_lines(cx);
this.subscribe_to_editor(cx);
this
}
fn placeholder_text(cx: &WindowContext) -> String {
let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
.map(|keybinding| format!("{keybinding} for context"))
.unwrap_or_default();
format!("Generate…{context_keybinding} ↓↑ for history")
}
fn subscribe_to_editor(&mut self, cx: &mut ViewContext<Self>) {
self.editor_subscriptions.clear();
self.editor_subscriptions
.push(cx.observe(&self.editor, Self::handle_prompt_editor_changed));
self.editor_subscriptions
.push(cx.subscribe(&self.editor, Self::handle_prompt_editor_events));
}
fn prompt(&self, cx: &AppContext) -> String {
self.editor.read(cx).text(cx)
}
fn count_lines(&mut self, cx: &mut ViewContext<Self>) {
let height_in_lines = cmp::max(
2, // Make the editor at least two lines tall, to account for padding and buttons.
cmp::min(
self.editor
.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1),
Self::MAX_LINES as u32,
),
) as u8;
if height_in_lines != self.height_in_lines {
self.height_in_lines = height_in_lines;
cx.emit(PromptEditorEvent::Resized { height_in_lines });
}
}
fn handle_prompt_editor_changed(&mut self, _: View<Editor>, cx: &mut ViewContext<Self>) {
self.count_lines(cx);
}
fn handle_prompt_editor_events(
&mut self,
_: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
match event {
EditorEvent::Edited { .. } => {
let prompt = self.editor.read(cx).text(cx);
if self
.prompt_history_ix
.map_or(true, |ix| self.prompt_history[ix] != prompt)
{
self.prompt_history_ix.take();
self.pending_prompt = prompt;
}
self.edited_since_done = true;
cx.notify();
}
_ => {}
}
}
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
CodegenStatus::Pending => {
self.editor
.update(cx, |editor, _| editor.set_read_only(true));
}
CodegenStatus::Done | CodegenStatus::Error(_) => {
self.edited_since_done = false;
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
}
}
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::CancelRequested);
}
CodegenStatus::Pending => {
cx.emit(PromptEditorEvent::StopRequested);
}
}
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status {
CodegenStatus::Idle => {
if !self.editor.read(cx).text(cx).trim().is_empty() {
cx.emit(PromptEditorEvent::StartRequested);
}
}
CodegenStatus::Pending => {
cx.emit(PromptEditorEvent::DismissRequested);
}
CodegenStatus::Done => {
if self.edited_since_done {
cx.emit(PromptEditorEvent::StartRequested);
} else {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: false });
}
}
CodegenStatus::Error(_) => {
cx.emit(PromptEditorEvent::StartRequested);
}
}
}
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
if matches!(self.codegen.read(cx).status, CodegenStatus::Done) {
cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
}
}
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
}
}
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else {
self.prompt_history_ix = None;
let prompt = self.pending_prompt.as_str();
self.editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
}
}
}
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
},
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size.into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
EditorElement::new(
&self.editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
}
}
#[derive(Debug)]
pub enum CodegenEvent {
Finished,
}
impl EventEmitter<CodegenEvent> for Codegen {}
const CLEAR_INPUT: &str = "\x15";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
terminal: Model<Terminal>,
}
impl TerminalTransaction {
pub fn start(terminal: Model<Terminal>) -> Self {
Self { terminal }
}
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
pub fn undo(&self, cx: &mut AppContext) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string()));
}
pub fn complete(&self, cx: &mut AppContext) {
self.terminal.update(cx, |terminal, _| {
terminal.input(CARRIAGE_RETURN.to_string())
});
}
fn sanitize_input(input: String) -> String {
input.replace(['\r', '\n'], "")
}
}
pub struct Codegen {
status: CodegenStatus,
telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>,
generation: Task<()>,
message_id: Option<String>,
transaction: Option<TerminalTransaction>,
}
impl Codegen {
pub fn new(terminal: Model<Terminal>, telemetry: Option<Arc<Telemetry>>) -> Self {
Self {
terminal,
telemetry,
status: CodegenStatus::Idle,
generation: Task::ready(()),
message_id: None,
transaction: None,
}
}
pub fn start(&mut self, prompt: LanguageModelRequest, cx: &mut ModelContext<Self>) {
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
let model_api_key = model.api_key(cx);
let http_client = cx.http_client();
let telemetry = self.telemetry.clone();
self.status = CodegenStatus::Pending;
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
self.generation = cx.spawn(|this, mut cx| async move {
let model_telemetry_id = model.telemetry_id();
let model_provider_id = model.provider_id();
let response = model.stream_completion_text(prompt, &cx).await;
let generate = async {
let message_id = response
.as_ref()
.ok()
.and_then(|response| response.message_id.clone());
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
let task = cx.background_executor().spawn({
let message_id = message_id.clone();
let executor = cx.background_executor().clone();
async move {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut chunks = response?.stream;
while let Some(chunk) = chunks.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());
}
let chunk = chunk?;
hunks_tx.send(chunk).await?;
}
anyhow::Ok(())
};
let result = task.await;
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
language_name: None,
},
telemetry,
http_client,
model_api_key,
&executor,
);
result?;
anyhow::Ok(())
}
});
this.update(&mut cx, |this, _| {
this.message_id = message_id;
})?;
while let Some(hunk) = hunks_rx.next().await {
this.update(&mut cx, |this, cx| {
if let Some(transaction) = &mut this.transaction {
transaction.push(hunk, cx);
cx.notify();
}
})?;
}
task.await?;
anyhow::Ok(())
};
let result = generate.await;
this.update(&mut cx, |this, cx| {
if let Err(error) = result {
this.status = CodegenStatus::Error(error);
} else {
this.status = CodegenStatus::Done;
}
cx.emit(CodegenEvent::Finished);
cx.notify();
})
.ok();
});
cx.notify();
}
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
cx.emit(CodegenEvent::Finished);
cx.notify();
}
pub fn complete(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.complete(cx);
}
}
pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
if let Some(transaction) = self.transaction.take() {
transaction.undo(cx);
}
}
}
enum CodegenStatus {
Idle,
Pending,
Done,
Error(anyhow::Error),
}

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_tool::ToolWorkingSet;
use chrono::{DateTime, Utc};
use collections::HashMap;
use collections::{HashMap, HashSet};
use futures::future::Shared;
use futures::{FutureExt as _, StreamExt as _};
use gpui::{AppContext, EventEmitter, ModelContext, SharedString, Task};
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
use util::{post_inc, TryFutureExt as _};
use uuid::Uuid;
use crate::context::{attach_context_to_message, Context};
use crate::context::{attach_context_to_message, Context, ContextId};
#[derive(Debug, Clone, Copy)]
pub enum RequestKind {
@@ -64,7 +64,8 @@ pub struct Thread {
pending_summary: Task<Option<()>>,
messages: Vec<Message>,
next_message_id: MessageId,
context_by_message: HashMap<MessageId, Vec<Context>>,
context: HashMap<ContextId, Context>,
context_by_message: HashMap<MessageId, Vec<ContextId>>,
completion_count: usize,
pending_completions: Vec<PendingCompletion>,
tools: Arc<ToolWorkingSet>,
@@ -82,6 +83,7 @@ impl Thread {
pending_summary: Task::ready(None),
messages: Vec::new(),
next_message_id: MessageId(0),
context: HashMap::default(),
context_by_message: HashMap::default(),
completion_count: 0,
pending_completions: Vec::new(),
@@ -129,8 +131,15 @@ impl Thread {
&self.tools
}
pub fn context_for_message(&self, id: MessageId) -> Option<&Vec<Context>> {
self.context_by_message.get(&id)
pub fn context_for_message(&self, id: MessageId) -> Option<Vec<Context>> {
let context = self.context_by_message.get(&id)?;
Some(
context
.into_iter()
.filter_map(|context_id| self.context.get(&context_id))
.cloned()
.collect::<Vec<_>>(),
)
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
@@ -144,7 +153,10 @@ impl Thread {
cx: &mut ModelContext<Self>,
) {
let message_id = self.insert_message(Role::User, text, cx);
self.context_by_message.insert(message_id, context);
let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
self.context
.extend(context.into_iter().map(|context| (context.id, context)));
self.context_by_message.insert(message_id, context_ids);
}
pub fn insert_message(
@@ -164,6 +176,27 @@ impl Thread {
id
}
/// Returns the representation of this [`Thread`] in a textual form.
///
/// This is the representation we use when attaching a thread as context to another thread.
pub fn text(&self) -> String {
let mut text = String::new();
for message in &self.messages {
text.push_str(match message.role {
language_model::Role::User => "User:",
language_model::Role::Assistant => "Assistant:",
language_model::Role::System => "System:",
});
text.push('\n');
text.push_str(&message.text);
text.push('\n');
}
text
}
pub fn to_completion_request(
&self,
_request_kind: RequestKind,
@@ -176,7 +209,13 @@ impl Thread {
temperature: None,
};
let mut referenced_context_ids = HashSet::default();
for message in &self.messages {
if let Some(context_ids) = self.context_by_message.get(&message.id) {
referenced_context_ids.extend(context_ids);
}
let mut request_message = LanguageModelRequestMessage {
role: message.role,
content: Vec::new(),
@@ -191,10 +230,6 @@ impl Thread {
}
}
if let Some(context) = self.context_for_message(message.id) {
attach_context_to_message(&mut request_message, context.clone());
}
if !message.text.is_empty() {
request_message
.content
@@ -212,6 +247,22 @@ impl Thread {
request.messages.push(request_message);
}
if !referenced_context_ids.is_empty() {
let mut context_message = LanguageModelRequestMessage {
role: Role::User,
content: Vec::new(),
cache: false,
};
let referenced_context = referenced_context_ids
.into_iter()
.filter_map(|context_id| self.context.get(context_id))
.cloned();
attach_context_to_message(&mut context_message, referenced_context);
request.messages.push(context_message);
}
request
}

View File

@@ -238,5 +238,46 @@ impl ThreadStore {
Async programming in Rust provides a powerful way to write concurrent code that's both safe and efficient. It's particularly useful for servers, network programming, and any application that deals with many concurrent operations.".unindent(), cx);
thread
}));
self.threads.push(cx.new_model(|cx| {
let mut thread = Thread::new(self.tools.clone(), cx);
thread.set_summary("Rust code with long lines", cx);
thread.insert_user_message("Could you write me some Rust code with long lines?", Vec::new(), cx);
thread.insert_message(Role::Assistant, r#"Here's some Rust code with some intentionally long lines:
```rust
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let very_long_vector = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
let complicated_hashmap: HashMap<String, Vec<(i32, f64, String)>> = [("key1".to_string(), vec![(1, 1.1, "value1".to_string()), (2, 2.2, "value2".to_string())]), ("key2".to_string(), vec![(3, 3.3, "value3".to_string()), (4, 4.4, "value4".to_string())])].iter().cloned().collect();
let nested_structure = Arc::new(Mutex::new(HashMap::new()));
let long_closure = |x: i32, y: i32, z: i32| -> i32 { let result = x * y + z; println!("The result of the long closure calculation is: {}", result); result };
let thread_handles: Vec<_> = (0..10).map(|i| {
let nested_structure_clone = Arc::clone(&nested_structure);
thread::spawn(move || {
let mut lock = nested_structure_clone.lock().unwrap();
lock.entry(format!("thread_{}", i)).or_insert_with(|| HashSet::new()).insert(i * i);
})
}).collect();
for handle in thread_handles {
handle.join().unwrap();
}
println!("The final state of the nested structure is: {:?}", nested_structure.lock().unwrap());
let complex_expression = very_long_vector.iter().filter(|&&x| x % 2 == 0).map(|&x| x * x).fold(0, |acc, x| acc + x) + long_closure(5, 10, 15);
println!("The result of the complex expression is: {}", complex_expression);
}
```"#.unindent(), cx);
thread
}));
}
}

View File

@@ -1,52 +1,153 @@
use std::rc::Rc;
use gpui::ClickEvent;
use ui::{prelude::*, IconButtonShape};
use ui::{prelude::*, IconButtonShape, Tooltip};
use crate::context::Context;
use crate::context::{Context, ContextKind};
#[derive(IntoElement)]
pub struct ContextPill {
context: Context,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
pub enum ContextPill {
Added {
context: Context,
dupe_name: bool,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
},
Suggested {
name: SharedString,
kind: ContextKind,
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
},
}
impl ContextPill {
pub fn new(context: Context) -> Self {
Self {
pub fn new_added(
context: Context,
dupe_name: bool,
on_remove: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext)>>,
) -> Self {
Self::Added {
context,
on_remove: None,
dupe_name,
on_remove,
}
}
pub fn on_remove(mut self, on_remove: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>) -> Self {
self.on_remove = Some(on_remove);
self
pub fn new_suggested(
name: SharedString,
kind: ContextKind,
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
) -> Self {
Self::Suggested { name, kind, on_add }
}
pub fn id(&self) -> ElementId {
match self {
Self::Added { context, .. } => {
ElementId::NamedInteger("context-pill".into(), context.id.0)
}
Self::Suggested { .. } => "suggested-context-pill".into(),
}
}
pub fn kind(&self) -> &ContextKind {
match self {
Self::Added { context, .. } => &context.kind,
Self::Suggested { kind, .. } => kind,
}
}
}
impl RenderOnce for ContextPill {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
h_flex()
.gap_1()
.pl_1p5()
.pr_0p5()
let icon = match &self.kind() {
ContextKind::File => IconName::File,
ContextKind::Directory => IconName::Folder,
ContextKind::FetchedUrl => IconName::Globe,
ContextKind::Thread => IconName::MessageCircle,
};
let color = cx.theme().colors();
let base_pill = h_flex()
.id(self.id())
.pl_1()
.pb(px(1.))
.border_1()
.border_color(cx.theme().colors().border.opacity(0.5))
.bg(cx.theme().colors().element_background)
.rounded_md()
.child(Label::new(self.context.name.clone()).size(LabelSize::Small))
.when_some(self.on_remove, |parent, on_remove| {
parent.child(
IconButton::new(("remove", self.context.id.0), IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.on_click({
let on_remove = on_remove.clone();
move |event, cx| on_remove(event, cx)
.gap_1()
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted));
match &self {
ContextPill::Added {
context,
dupe_name,
on_remove,
} => base_pill
.bg(color.element_background)
.border_color(color.border.opacity(0.5))
.pr(if on_remove.is_some() { px(2.) } else { px(4.) })
.child(
h_flex()
.id("context-data")
.gap_1()
.child(Label::new(context.name.clone()).size(LabelSize::Small))
.when_some(context.parent.as_ref(), |element, parent_name| {
if *dupe_name {
element.child(
Label::new(parent_name.clone())
.size(LabelSize::XSmall)
.color(Color::Muted),
)
} else {
element
}
})
.when_some(context.tooltip.clone(), |element, tooltip| {
element.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx))
}),
)
})
.when_some(on_remove.as_ref(), |element, on_remove| {
element.child(
IconButton::new(("remove", context.id.0), IconName::Close)
.shape(IconButtonShape::Square)
.icon_size(IconSize::XSmall)
.tooltip(|cx| Tooltip::text("Remove Context", cx))
.on_click({
let on_remove = on_remove.clone();
move |event, cx| on_remove(event, cx)
}),
)
}),
ContextPill::Suggested { name, kind, on_add } => base_pill
.cursor_pointer()
.pr_1()
.border_color(color.border_variant.opacity(0.5))
.hover(|style| style.bg(color.element_hover.opacity(0.5)))
.child(
Label::new(name.clone())
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
Label::new(match kind {
ContextKind::File => "Open File",
ContextKind::Thread | ContextKind::Directory | ContextKind::FetchedUrl => {
"Active"
}
})
.size(LabelSize::XSmall)
.color(Color::Muted),
)
.child(
Icon::new(IconName::Plus)
.size(IconSize::XSmall)
.into_any_element(),
)
.tooltip(|cx| Tooltip::with_meta("Suggested Context", None, "Click to add it", cx))
.on_click({
let on_add = on_add.clone();
move |event, cx| on_add(event, cx)
}),
}
}
}

View File

@@ -18,7 +18,7 @@ pub struct UpdateNotification {
impl EventEmitter<DismissEvent> for UpdateNotification {}
impl Render for UpdateNotification {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let app_name = ReleaseChannel::global(cx).display_name();
v_flex()

View File

@@ -16,10 +16,10 @@ doctest = false
editor.workspace = true
gpui.workspace = true
itertools.workspace = true
outline.workspace = true
theme.workspace = true
ui.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }

View File

@@ -39,10 +39,17 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
impl Render for Breadcrumbs {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const MAX_SEGMENTS: usize = 12;
let element = h_flex().text_ui(cx);
let element = h_flex()
.id("breadcrumb-container")
.flex_grow()
.overflow_x_scroll()
.text_ui(cx);
let Some(active_item) = self.active_item.as_ref() else {
return element;
};
let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
return element;
};
@@ -52,6 +59,7 @@ impl Render for Breadcrumbs {
prefix_end_ix,
segments.len().saturating_sub(MAX_SEGMENTS / 2),
);
if suffix_start_ix > prefix_end_ix {
segments.splice(
prefix_end_ix..suffix_start_ix,
@@ -82,6 +90,7 @@ impl Render for Breadcrumbs {
});
let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
match active_item
.downcast::<Editor>()
.map(|editor| editor.downgrade())
@@ -93,8 +102,11 @@ impl Render for Breadcrumbs {
.on_click({
let editor = editor.clone();
move |_, cx| {
if let Some(editor) = editor.upgrade() {
outline::toggle(editor, &editor::actions::ToggleOutline, cx)
if let Some((editor, callback)) = editor
.upgrade()
.zip(zed_actions::outline::TOGGLE_OUTLINE.get())
{
callback(editor.to_any(), cx);
}
}
})
@@ -102,15 +114,15 @@ impl Render for Breadcrumbs {
if let Some(editor) = editor.upgrade() {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show symbol outline",
&editor::actions::ToggleOutline,
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
&focus_handle,
cx,
)
} else {
Tooltip::for_action(
"Show symbol outline",
&editor::actions::ToggleOutline,
"Show Symbol Outline",
&zed_actions::outline::ToggleOutline,
cx,
)
}

View File

@@ -40,6 +40,7 @@ schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
settings.workspace = true
telemetry.workspace = true
util.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]

View File

@@ -250,7 +250,9 @@ impl ActiveCall {
cx.spawn(move |this, mut cx| async move {
let result = invite.await;
if result.is_ok() {
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
this.update(&mut cx, |this, cx| {
this.report_call_event("Participant Invited", cx)
})?;
} else {
//TODO: report collaboration error
log::error!("invite failed: {:?}", result);
@@ -318,7 +320,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("accept incoming", cx)
this.report_call_event("Incoming Call Accepted", cx)
})?;
Ok(())
})
@@ -331,7 +333,7 @@ impl ActiveCall {
.borrow_mut()
.take()
.ok_or_else(|| anyhow!("no incoming call"))?;
report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
telemetry::event!("Incoming Call Declined", room_id = call.room_id);
self.client.send(proto::DeclineCall {
room_id: call.room_id,
})?;
@@ -366,7 +368,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("join channel", cx)
this.report_call_event("Channel Joined", cx)
})?;
Ok(room)
})
@@ -374,7 +376,7 @@ impl ActiveCall {
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
self.report_call_event("hang up", cx);
self.report_call_event("Call Ended", cx);
Audio::end_call(cx);
@@ -393,7 +395,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("share project", cx);
self.report_call_event("Project Shared", cx);
room.update(cx, |room, cx| room.share_project(project, cx))
} else {
Task::ready(Err(anyhow!("no active call")))
@@ -406,7 +408,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("unshare project", cx);
self.report_call_event("Project Unshared", cx);
room.update(cx, |room, cx| room.unshare_project(project, cx))
} else {
Err(anyhow!("no active call"))
@@ -486,35 +488,15 @@ impl ActiveCall {
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
if let Some(room) = self.room() {
let room = room.read(cx);
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
telemetry::event!(
operation,
room_id = room.id(),
channel_id = room.channel_id()
)
}
}
}
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<ChannelId>,
client: &Arc<Client>,
) {
let telemetry = client.telemetry();
telemetry.report_call_event(operation, Some(room_id), channel_id)
}
pub fn report_call_event_for_channel(
operation: &'static str,
channel_id: ChannelId,
client: &Arc<Client>,
cx: &AppContext,
) {
let room = ActiveCall::global(cx).read(cx).room();
let telemetry = client.telemetry();
telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
}
#[cfg(test)]
mod test {
use gpui::TestAppContext;

View File

@@ -243,7 +243,9 @@ impl ActiveCall {
cx.spawn(move |this, mut cx| async move {
let result = invite.await;
if result.is_ok() {
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
this.update(&mut cx, |this, cx| {
this.report_call_event("Participant Invited", cx)
})?;
} else {
//TODO: report collaboration error
log::error!("invite failed: {:?}", result);
@@ -311,7 +313,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("accept incoming", cx)
this.report_call_event("Incoming Call Accepted", cx)
})?;
Ok(())
})
@@ -324,7 +326,7 @@ impl ActiveCall {
.borrow_mut()
.take()
.ok_or_else(|| anyhow!("no incoming call"))?;
report_call_event_for_room("decline incoming", call.room_id, None, &self.client);
telemetry::event!("Incoming Call Declined", room_id = call.room_id);
self.client.send(proto::DeclineCall {
room_id: call.room_id,
})?;
@@ -359,7 +361,7 @@ impl ActiveCall {
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
.await?;
this.update(&mut cx, |this, cx| {
this.report_call_event("join channel", cx)
this.report_call_event("Channel Joined", cx)
})?;
Ok(room)
})
@@ -367,7 +369,7 @@ impl ActiveCall {
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
cx.notify();
self.report_call_event("hang up", cx);
self.report_call_event("Call Ended", cx);
Audio::end_call(cx);
@@ -386,7 +388,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Task<Result<u64>> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("share project", cx);
self.report_call_event("Project Shared", cx);
room.update(cx, |room, cx| room.share_project(project, cx))
} else {
Task::ready(Err(anyhow!("no active call")))
@@ -399,7 +401,7 @@ impl ActiveCall {
cx: &mut ModelContext<Self>,
) -> Result<()> {
if let Some((room, _)) = self.room.as_ref() {
self.report_call_event("unshare project", cx);
self.report_call_event("Project Unshared", cx);
room.update(cx, |room, cx| room.unshare_project(project, cx))
} else {
Err(anyhow!("no active call"))
@@ -479,35 +481,15 @@ impl ActiveCall {
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
if let Some(room) = self.room() {
let room = room.read(cx);
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client);
telemetry::event!(
operation,
room_id = room.id(),
channel_id = room.channel_id()
);
}
}
}
pub fn report_call_event_for_room(
operation: &'static str,
room_id: u64,
channel_id: Option<ChannelId>,
client: &Arc<Client>,
) {
let telemetry = client.telemetry();
telemetry.report_call_event(operation, Some(room_id), channel_id)
}
pub fn report_call_event_for_channel(
operation: &'static str,
channel_id: ChannelId,
client: &Arc<Client>,
cx: &AppContext,
) {
let room = ActiveCall::global(cx).read(cx).room();
let telemetry = client.telemetry();
telemetry.report_call_event(operation, room.map(|r| r.read(cx).id()), Some(channel_id))
}
#[cfg(test)]
mod test {
use gpui::TestAppContext;

View File

@@ -25,7 +25,6 @@ anyhow.workspace = true
clap.workspace = true
collections.workspace = true
ipc-channel = "0.19"
once_cell.workspace = true
parking_lot.workspace = true
paths.workspace = true
release_channel.workspace = true

View File

@@ -2,4 +2,10 @@ fn main() {
if std::env::var("ZED_UPDATE_EXPLANATION").is_ok() {
println!(r#"cargo:rustc-cfg=feature="no-bundled-uninstall""#);
}
if cfg!(target_os = "macos") {
println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
// Weakly link ScreenCaptureKit to ensure can be used on macOS 10.15+.
println!("cargo:rustc-link-arg=-Wl,-weak_framework,ScreenCaptureKit");
}
}

View File

@@ -18,6 +18,12 @@ use std::{
use tempfile::NamedTempFile;
use util::paths::PathWithPosition;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use {
std::io::IsTerminal,
util::{load_login_shell_environment, load_shell_from_passwd, ResultExt},
};
struct Detect;
trait InstalledApp {
@@ -73,7 +79,7 @@ fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
Ok(existing_path) => PathWithPosition::from_path(existing_path),
Err(_) => {
let path = PathWithPosition::parse_str(argument_str);
let curdir = env::current_dir().context("reteiving current directory")?;
let curdir = env::current_dir().context("retrieving current directory")?;
path.map_path(|path| match fs::canonicalize(&path) {
Ok(path) => Ok(path),
Err(e) => {
@@ -161,7 +167,16 @@ fn main() -> Result<()> {
None
};
// On Linux, desktop entry uses `cli` to spawn `zed`, so we need to load env vars from the shell
// since it doesn't inherit env vars from the terminal.
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
if !std::io::stdout().is_terminal() {
load_shell_from_passwd().log_err();
load_login_shell_environment().log_err();
}
let env = Some(std::env::vars().collect::<HashMap<_, _>>());
let exit_status = Arc::new(Mutex::new(None));
let mut paths = vec![];
let mut urls = vec![];
@@ -262,6 +277,7 @@ mod linux {
os::unix::net::{SocketAddr, UnixDatagram},
path::{Path, PathBuf},
process::{self, ExitStatus},
sync::LazyLock,
thread,
time::Duration,
};
@@ -269,12 +285,11 @@ mod linux {
use anyhow::anyhow;
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use fork::Fork;
use once_cell::sync::Lazy;
use crate::{Detect, InstalledApp};
static RELEASE_CHANNEL: Lazy<String> =
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
static RELEASE_CHANNEL: LazyLock<String> =
LazyLock::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string());
struct App(PathBuf);

View File

@@ -27,7 +27,6 @@ futures.workspace = true
gpui.workspace = true
http_client.workspace = true
log.workspace = true
once_cell.workspace = true
paths.workspace = true
parking_lot.workspace = true
postage.workspace = true

View File

@@ -8,7 +8,6 @@ use futures::channel::mpsc;
use futures::{Future, StreamExt};
use gpui::{AppContext, BackgroundExecutor, Task};
use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use settings::{Settings, SettingsStore};
@@ -16,10 +15,15 @@ use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Write;
use std::time::Instant;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use std::{
env, mem,
path::PathBuf,
sync::{Arc, LazyLock},
time::Duration,
};
use telemetry_events::{
AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
InlineCompletionEvent, InlineCompletionRating, InlineCompletionRatingEvent, SettingEvent,
InlineCompletionEvent,
};
use util::{ResultExt, TryFutureExt};
use worktree::{UpdatedEntriesSet, WorktreeId};
@@ -84,7 +88,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
#[cfg(not(debug_assertions))]
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
static ZED_CLIENT_CHECKSUM_SEED: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
option_env!("ZED_CLIENT_CHECKSUM_SEED")
.map(|s| s.as_bytes().into())
.or_else(|| {
@@ -349,24 +353,6 @@ impl Telemetry {
self.report_event(event)
}
pub fn report_inline_completion_rating_event(
self: &Arc<Self>,
rating: InlineCompletionRating,
input_events: Arc<str>,
input_excerpt: Arc<str>,
output_excerpt: Arc<str>,
feedback: String,
) {
let event = Event::InlineCompletionRating(InlineCompletionRatingEvent {
rating,
input_events,
input_excerpt,
output_excerpt,
feedback,
});
self.report_event(event);
}
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
self.report_event(Event::Assistant(event));
}
@@ -394,15 +380,6 @@ impl Telemetry {
event
}
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
let event = Event::Setting(SettingEvent {
setting: setting.to_string(),
value,
});
self.report_event(event)
}
pub fn log_edit_event(self: &Arc<Self>, environment: &'static str, is_via_ssh: bool) {
let mut state = self.state.lock();
let period_data = state.event_coalescer.log_event(environment);

View File

@@ -16,7 +16,9 @@ use util::TryFutureExt as _;
pub type UserId = u64;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
)]
pub struct ChannelId(pub u64);
impl std::fmt::Display for ChannelId {

View File

@@ -8,7 +8,12 @@ It contains our back-end logic for collaboration, to which we connect from the Z
## Database setup
Before you can run the collab server locally, you'll need to set up a zed Postgres database.
Before you can run the collab server locally, you'll need to set up a zed Postgres database. Follow the steps sequentially:
1. Ensure you have postgres installed. If not, install with `brew install postgresql@15`.
2. Follow the steps on Brew's formula and verify your `$PATH` contains `/opt/homebrew/opt/postgresql@15/bin`.
3. If you hadn't done it before, create the `postgres` user with `createuser -s postgres`.
4. You are now ready to run the `bootstrap` script:
```sh
script/bootstrap

View File

@@ -252,7 +252,7 @@ spec:
value: "${AUTO_JOIN_CHANNEL_ID}"
securityContext:
capabilities:
# FIXME - Switch to the more restrictive `PERFMON` capability.
# TODO - Switch to the more restrictive `PERFMON` capability.
# This capability isn't yet available in a stable version of Debian.
add: ["SYS_ADMIN"]
terminationGracePeriodSeconds: 10

View File

@@ -106,6 +106,22 @@ CREATE TABLE "worktree_repositories" (
CREATE INDEX "index_worktree_repositories_on_project_id" ON "worktree_repositories" ("project_id");
CREATE INDEX "index_worktree_repositories_on_project_id_and_worktree_id" ON "worktree_repositories" ("project_id", "worktree_id");
CREATE TABLE "worktree_repository_statuses" (
"project_id" INTEGER NOT NULL,
"worktree_id" INT8 NOT NULL,
"work_directory_id" INT8 NOT NULL,
"repo_path" VARCHAR NOT NULL,
"status" INT8 NOT NULL,
"scan_id" INT8 NOT NULL,
"is_deleted" BOOL NOT NULL,
PRIMARY KEY(project_id, worktree_id, work_directory_id, repo_path),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE,
FOREIGN KEY(project_id, worktree_id, work_directory_id) REFERENCES worktree_entries (project_id, worktree_id, id) ON DELETE CASCADE
);
CREATE INDEX "index_wt_repos_statuses_on_project_id" ON "worktree_repository_statuses" ("project_id");
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
CREATE INDEX "index_wt_repos_statuses_on_project_id_and_wt_id_and_wd_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
CREATE TABLE "worktree_settings_files" (
"project_id" INTEGER NOT NULL,
"worktree_id" INTEGER NOT NULL,

View File

@@ -279,6 +279,7 @@ pub async fn post_panic(
let report: telemetry_events::PanicRequest = serde_json::from_slice(&body)
.map_err(|_| Error::http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
let incident_id = uuid::Uuid::new_v4().to_string();
let panic = report.panic;
if panic.os_name == "Linux" && panic.os_version == Some("1.0.0".to_string()) {
@@ -288,11 +289,37 @@ pub async fn post_panic(
))?;
}
if let Some(blob_store_client) = app.blob_store_client.as_ref() {
let response = blob_store_client
.head_object()
.bucket(CRASH_REPORTS_BUCKET)
.key(incident_id.clone() + ".json")
.send()
.await;
if response.is_ok() {
log::info!("We've already uploaded this crash");
return Ok(());
}
blob_store_client
.put_object()
.bucket(CRASH_REPORTS_BUCKET)
.key(incident_id.clone() + ".json")
.acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead)
.body(ByteStream::from(body.to_vec()))
.send()
.await
.map_err(|e| log::error!("Failed to upload crash: {}", e))
.ok();
}
tracing::error!(
service = "client",
version = %panic.app_version,
os_name = %panic.os_name,
os_version = %panic.os_version.clone().unwrap_or_default(),
incident_id = %incident_id,
installation_id = %panic.installation_id.clone().unwrap_or_default(),
description = %panic.payload,
backtrace = %panic.backtrace.join("\n"),
@@ -331,10 +358,19 @@ pub async fn post_panic(
panic.app_version
)))
.add_field({
let hostname = app.config.blob_store_url.clone().unwrap_or_default();
let hostname = hostname.strip_prefix("https://").unwrap_or_else(|| {
hostname.strip_prefix("http://").unwrap_or_default()
});
slack::Text::markdown(format!(
"*OS:*\n{} {}",
"*{} {}:*\n<https://{}.{}/{}.json|{}…>",
panic.os_name,
panic.os_version.unwrap_or_default()
panic.os_version.unwrap_or_default(),
CRASH_REPORTS_BUCKET,
hostname,
incident_id,
incident_id.chars().take(8).collect::<String>(),
))
})
})
@@ -361,6 +397,12 @@ pub async fn post_panic(
}
fn report_to_slack(panic: &Panic) -> bool {
// Panics on macOS should make their way to Slack as a crash report,
// so we don't need to send them a second time via this channel.
if panic.os_name == "macOS" {
return false;
}
if panic.payload.contains("ERROR_SURFACE_LOST_KHR") {
return false;
}

View File

@@ -1,4 +1,5 @@
use anyhow::Context as _;
use util::ResultExt;
use super::*;
@@ -274,8 +275,8 @@ impl Database {
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
canonical_path: ActiveValue::set(entry.canonical_path.clone()),
is_ignored: ActiveValue::set(entry.is_ignored),
git_status: ActiveValue::set(None),
is_external: ActiveValue::set(entry.is_external),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
is_deleted: ActiveValue::set(false),
scan_id: ActiveValue::set(update.scan_id as i64),
is_fifo: ActiveValue::set(entry.is_fifo),
@@ -295,7 +296,6 @@ impl Database {
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::CanonicalPath,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
])
.to_owned(),
@@ -349,6 +349,79 @@ impl Database {
)
.exec(&*tx)
.await?;
let has_any_statuses = update
.updated_repositories
.iter()
.any(|repository| !repository.updated_statuses.is_empty());
if has_any_statuses {
worktree_repository_statuses::Entity::insert_many(
update.updated_repositories.iter().flat_map(
|repository: &proto::RepositoryEntry| {
repository.updated_statuses.iter().map(|status_entry| {
worktree_repository_statuses::ActiveModel {
project_id: ActiveValue::set(project_id),
worktree_id: ActiveValue::set(worktree_id),
work_directory_id: ActiveValue::set(
repository.work_directory_id as i64,
),
scan_id: ActiveValue::set(update.scan_id as i64),
is_deleted: ActiveValue::set(false),
repo_path: ActiveValue::set(status_entry.repo_path.clone()),
status: ActiveValue::set(status_entry.status as i64),
}
})
},
),
)
.on_conflict(
OnConflict::columns([
worktree_repository_statuses::Column::ProjectId,
worktree_repository_statuses::Column::WorktreeId,
worktree_repository_statuses::Column::WorkDirectoryId,
worktree_repository_statuses::Column::RepoPath,
])
.update_columns([
worktree_repository_statuses::Column::ScanId,
worktree_repository_statuses::Column::Status,
])
.to_owned(),
)
.exec(&*tx)
.await?;
}
let has_any_removed_statuses = update
.updated_repositories
.iter()
.any(|repository| !repository.removed_statuses.is_empty());
if has_any_removed_statuses {
worktree_repository_statuses::Entity::update_many()
.filter(
worktree_repository_statuses::Column::ProjectId
.eq(project_id)
.and(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree_id),
)
.and(
worktree_repository_statuses::Column::RepoPath.is_in(
update.updated_repositories.iter().flat_map(|repository| {
repository.removed_statuses.iter()
}),
),
),
)
.set(worktree_repository_statuses::ActiveModel {
is_deleted: ActiveValue::Set(true),
scan_id: ActiveValue::Set(update.scan_id as i64),
..Default::default()
})
.exec(&*tx)
.await?;
}
}
if !update.removed_repositories.is_empty() {
@@ -643,7 +716,6 @@ impl Database {
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go
@@ -657,23 +729,49 @@ impl Database {
// Populate repository entries.
{
let mut db_repository_entries = worktree_repository::Entity::find()
let db_repository_entries = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::IsDeleted.eq(false)),
)
.stream(tx)
.all(tx)
.await?;
while let Some(db_repository_entry) = db_repository_entries.next().await {
let db_repository_entry = db_repository_entry?;
for db_repository_entry in db_repository_entries {
if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
{
let mut repository_statuses = worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(worktree_repository_statuses::Column::ProjectId.eq(project.id))
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),
)
.add(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(db_repository_entry.work_directory_id),
)
.add(worktree_repository_statuses::Column::IsDeleted.eq(false)),
)
.stream(tx)
.await?;
let mut updated_statuses = Vec::new();
while let Some(status_entry) = repository_statuses.next().await {
let status_entry: worktree_repository_statuses::Model = status_entry?;
updated_statuses.push(proto::StatusEntry {
repo_path: status_entry.repo_path,
status: status_entry.status as i32,
});
}
worktree.repository_entries.insert(
db_repository_entry.work_directory_id as u64,
proto::RepositoryEntry {
work_directory_id: db_repository_entry.work_directory_id as u64,
branch: db_repository_entry.branch,
updated_statuses,
removed_statuses: Vec::new(),
},
);
}

View File

@@ -662,7 +662,6 @@ impl Database {
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go
@@ -682,26 +681,69 @@ impl Database {
worktree_repository::Column::IsDeleted.eq(false)
};
let mut db_repositories = worktree_repository::Entity::find()
let db_repositories = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::WorktreeId.eq(worktree.id))
.add(repository_entry_filter),
)
.stream(tx)
.all(tx)
.await?;
while let Some(db_repository) = db_repositories.next().await {
let db_repository = db_repository?;
for db_repository in db_repositories.into_iter() {
if db_repository.is_deleted {
worktree
.removed_repositories
.push(db_repository.work_directory_id as u64);
} else {
let status_entry_filter = if let Some(rejoined_worktree) = rejoined_worktree
{
worktree_repository_statuses::Column::ScanId
.gt(rejoined_worktree.scan_id)
} else {
worktree_repository_statuses::Column::IsDeleted.eq(false)
};
let mut db_statuses = worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(
worktree_repository_statuses::Column::ProjectId
.eq(project.id),
)
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),
)
.add(
worktree_repository_statuses::Column::WorkDirectoryId
.eq(db_repository.work_directory_id),
)
.add(status_entry_filter),
)
.stream(tx)
.await?;
let mut removed_statuses = Vec::new();
let mut updated_statuses = Vec::new();
while let Some(db_status) = db_statuses.next().await {
let db_status: worktree_repository_statuses::Model = db_status?;
if db_status.is_deleted {
removed_statuses.push(db_status.repo_path);
} else {
updated_statuses.push(proto::StatusEntry {
repo_path: db_status.repo_path,
status: db_status.status as i32,
});
}
}
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
updated_statuses,
removed_statuses,
});
}
}

View File

@@ -2925,8 +2925,6 @@ async fn test_git_status_sync(
assert_eq!(snapshot.status_for_file(file), status);
}
// Smoke test status reading
project_local.read_with(cx_a, |project, cx| {
assert_status(&Path::new(A_TXT), Some(GitFileStatus::Added), project, cx);
assert_status(&Path::new(B_TXT), Some(GitFileStatus::Added), project, cx);
@@ -6669,6 +6667,10 @@ async fn test_remote_git_branches(
client_a
.fs()
.insert_branches(Path::new("/project/.git"), &branches);
let branches_set = branches
.into_iter()
.map(ToString::to_string)
.collect::<HashSet<_>>();
let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await;
let project_id = active_call_a
@@ -6690,10 +6692,10 @@ async fn test_remote_git_branches(
let branches_b = branches_b
.into_iter()
.map(|branch| branch.name)
.collect::<Vec<_>>();
.map(|branch| branch.name.to_string())
.collect::<HashSet<_>>();
assert_eq!(&branches_b, &branches);
assert_eq!(branches_b, branches_set);
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {

View File

@@ -1134,7 +1134,7 @@ impl RandomizedTest for ProjectCollaborationTest {
let end = PointUtf16::new(end_row, end_column);
let range = if start > end { end..start } else { start..end };
highlights.push(lsp::DocumentHighlight {
range: range_to_lsp(range.clone()),
range: range_to_lsp(range.clone()).unwrap(),
kind: Some(lsp::DocumentHighlightKind::READ),
});
}
@@ -1222,7 +1222,7 @@ impl RandomizedTest for ProjectCollaborationTest {
id,
guest_project.remote_id(),
);
assert_eq!(guest_snapshot.repositories().collect::<Vec<_>>(), host_snapshot.repositories().collect::<Vec<_>>(),
assert_eq!(guest_snapshot.repositories().iter().collect::<Vec<_>>(), host_snapshot.repositories().iter().collect::<Vec<_>>(),
"{} has different repositories than the host for worktree {:?} and project {:?}",
client.username,
host_snapshot.abs_path(),

View File

@@ -229,6 +229,10 @@ async fn test_ssh_collaboration_git_branches(
.await;
let branches = ["main", "dev", "feature-1"];
let branches_set = branches
.iter()
.map(ToString::to_string)
.collect::<HashSet<_>>();
remote_fs.insert_branches(Path::new("/project/.git"), &branches);
// User A connects to the remote project via SSH.
@@ -281,10 +285,10 @@ async fn test_ssh_collaboration_git_branches(
let branches_b = branches_b
.into_iter()
.map(|branch| branch.name)
.collect::<Vec<_>>();
.map(|branch| branch.name.to_string())
.collect::<HashSet<_>>();
assert_eq!(&branches_b, &branches);
assert_eq!(&branches_b, &branches_set);
cx_b.update(|cx| {
project_b.update(cx, |project, cx| {

View File

@@ -56,6 +56,7 @@ serde_json.workspace = true
settings.workspace = true
smallvec.workspace = true
story = { workspace = true, optional = true }
telemetry.workspace = true
theme.workspace = true
time.workspace = true
time_format.workspace = true

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use call::report_call_event_for_channel;
use call::ActiveCall;
use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore};
use client::{
proto::{self, PeerId},
@@ -66,11 +66,13 @@ impl ChannelView {
cx.spawn(|mut cx| async move {
let channel_view = channel_view.await?;
pane.update(&mut cx, |pane, cx| {
report_call_event_for_channel(
"open channel notes",
telemetry::event!(
"Channel Notes Opened",
channel_id,
&workspace.read(cx).app_state().client,
cx,
room_id = ActiveCall::global(cx)
.read(cx)
.room()
.map(|r| r.read(cx).id())
);
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
})?;

View File

@@ -1,4 +1,4 @@
use crate::{collab_panel, ChatPanelSettings};
use crate::{collab_panel, ChatPanelButton, ChatPanelSettings};
use anyhow::Result;
use call::{room, ActiveCall};
use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore};
@@ -1096,7 +1096,7 @@ impl FocusableView for ChatPanel {
}
impl Panel for ChatPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
fn position(&self, cx: &WindowContext) -> DockPosition {
ChatPanelSettings::get_global(cx).dock
}
@@ -1112,7 +1112,7 @@ impl Panel for ChatPanel {
);
}
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
}
@@ -1135,7 +1135,20 @@ impl Panel for ChatPanel {
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button)
let show_icon = match ChatPanelSettings::get_global(cx).button {
ChatPanelButton::Never => false,
ChatPanelButton::Always => true,
ChatPanelButton::WhenInCall => {
let is_in_call = ActiveCall::global(cx)
.read(cx)
.room()
.map_or(false, |room| room.read(cx).contains_guests());
self.active || is_in_call
}
};
show_icon.then(|| ui::IconName::MessageBubbles)
}
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -1152,6 +1165,10 @@ impl Panel for ChatPanel {
.room()
.is_some_and(|room| room.read(cx).contains_guests())
}
fn activation_priority(&self) -> u32 {
7
}
}
impl EventEmitter<PanelEvent> for ChatPanel {}

View File

@@ -79,16 +79,6 @@ impl CompletionProvider for MessageEditorCompletionProvider {
Task::ready(Ok(false))
}
fn apply_additional_edits_for_completion(
&self,
_buffer: Model<Buffer>,
_completion: Completion,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
_buffer: &Model<Buffer>,
@@ -319,6 +309,7 @@ impl MessageEditor {
server_id: LanguageServerId(0), // TODO: Make this optional or something?
lsp_completion: Default::default(), // TODO: Make this optional or something?
confirm: None,
resolved: true,
}
})
.collect()

View File

@@ -2719,7 +2719,7 @@ impl Render for CollabPanel {
impl EventEmitter<PanelEvent> for CollabPanel {}
impl Panel for CollabPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
fn position(&self, cx: &WindowContext) -> DockPosition {
CollaborationPanelSettings::get_global(cx).dock
}
@@ -2735,7 +2735,7 @@ impl Panel for CollabPanel {
);
}
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| CollaborationPanelSettings::get_global(cx).default_width)
}
@@ -2746,7 +2746,7 @@ impl Panel for CollabPanel {
cx.notify();
}
fn icon(&self, cx: &gpui::WindowContext) -> Option<ui::IconName> {
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
CollaborationPanelSettings::get_global(cx)
.button
.then_some(ui::IconName::UserGroup)
@@ -2763,6 +2763,10 @@ impl Panel for CollabPanel {
fn persistent_name() -> &'static str {
"CollabPanel"
}
fn activation_priority(&self) -> u32 {
6
}
}
impl FocusableView for CollabPanel {

View File

@@ -14,7 +14,7 @@ use gpui::{
};
use panel_settings::MessageEditorSettings;
pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
ChatPanelButton, ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
};
use release_channel::ReleaseChannel;
use settings::Settings;

View File

@@ -662,7 +662,7 @@ impl Panel for NotificationPanel {
"NotificationPanel"
}
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
fn position(&self, cx: &WindowContext) -> DockPosition {
NotificationPanelSettings::get_global(cx).dock
}
@@ -678,7 +678,7 @@ impl Panel for NotificationPanel {
);
}
fn size(&self, cx: &gpui::WindowContext) -> Pixels {
fn size(&self, cx: &WindowContext) -> Pixels {
self.width
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
}
@@ -702,7 +702,7 @@ impl Panel for NotificationPanel {
}
}
fn icon(&self, cx: &gpui::WindowContext) -> Option<IconName> {
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
let show_button = NotificationPanelSettings::get_global(cx).button;
if !show_button {
return None;
@@ -731,6 +731,10 @@ impl Panel for NotificationPanel {
fn toggle_action(&self) -> Box<dyn gpui::Action> {
Box::new(ToggleFocus)
}
fn activation_priority(&self) -> u32 {
8
}
}
pub struct NotificationToast {

View File

@@ -1,6 +1,6 @@
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use workspace::dock::DockPosition;
@@ -11,13 +11,82 @@ pub struct CollaborationPanelSettings {
pub default_width: Pixels,
}
#[derive(Clone, Copy, Default, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChatPanelButton {
Never,
Always,
#[default]
WhenInCall,
}
impl<'de> Deserialize<'de> for ChatPanelButton {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = ChatPanelButton;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
r#"a boolean or one of "never", "always", "when_in_call""#
)
}
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match b {
false => Ok(ChatPanelButton::Never),
true => Ok(ChatPanelButton::Always),
}
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match s {
"never" => Ok(ChatPanelButton::Never),
"always" => Ok(ChatPanelButton::Always),
"when_in_call" => Ok(ChatPanelButton::WhenInCall),
_ => Err(E::unknown_variant(s, &["never", "always", "when_in_call"])),
}
}
}
deserializer.deserialize_any(Visitor)
}
}
#[derive(Deserialize, Debug)]
pub struct ChatPanelSettings {
pub button: bool,
pub button: ChatPanelButton,
pub dock: DockPosition,
pub default_width: Pixels,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct ChatPanelSettingsContent {
/// When to show the panel button in the status bar.
///
/// Default: only when in a call
pub button: Option<ChatPanelButton>,
/// Where to dock the panel.
///
/// Default: right
pub dock: Option<DockPosition>,
/// Default width of the panel in pixels.
///
/// Default: 240
pub default_width: Option<f32>,
}
#[derive(Deserialize, Debug)]
pub struct NotificationPanelSettings {
pub button: bool,
@@ -66,7 +135,7 @@ impl Settings for CollaborationPanelSettings {
impl Settings for ChatPanelSettings {
const KEY: Option<&'static str> = Some("chat_panel");
type FileContent = PanelSettingsContent;
type FileContent = ChatPanelSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,

View File

@@ -16,4 +16,5 @@ doctest = false
test-support = []
[dependencies]
rustc-hash = "1.1"
indexmap.workspace = true
rustc-hash.workspace = true

View File

@@ -4,12 +4,24 @@ pub type HashMap<K, V> = FxHashMap<K, V>;
#[cfg(feature = "test-support")]
pub type HashSet<T> = FxHashSet<T>;
#[cfg(feature = "test-support")]
pub type IndexMap<K, V> = indexmap::IndexMap<K, V, rustc_hash::FxBuildHasher>;
#[cfg(feature = "test-support")]
pub type IndexSet<T> = indexmap::IndexSet<T, rustc_hash::FxBuildHasher>;
#[cfg(not(feature = "test-support"))]
pub type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "test-support"))]
pub type HashSet<T> = std::collections::HashSet<T>;
#[cfg(not(feature = "test-support"))]
pub type IndexMap<K, V> = indexmap::IndexMap<K, V>;
#[cfg(not(feature = "test-support"))]
pub type IndexSet<T> = indexmap::IndexSet<T>;
pub use rustc_hash::FxHasher;
pub use rustc_hash::{FxHashMap, FxHashSet};
pub use std::collections::*;

View File

@@ -36,21 +36,26 @@ pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>,
}
fn trim_consecutive_whitespaces(input: &str) -> String {
/// Removes subsequent whitespace characters and double colons from the query.
///
/// This improves the likelihood of a match by either humanized name or keymap-style name.
fn normalize_query(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut last_char_was_whitespace = false;
let mut last_char = None;
for char in input.trim().chars() {
if char.is_whitespace() {
if !last_char_was_whitespace {
result.push(char);
match (last_char, char) {
(Some(':'), ':') => continue,
(Some(last_char), char) if last_char.is_whitespace() && char.is_whitespace() => {
continue
}
_ => {
last_char = Some(char);
}
last_char_was_whitespace = true;
} else {
result.push(char);
last_char_was_whitespace = false;
}
result.push(char);
}
result
}
@@ -258,7 +263,7 @@ impl PickerDelegate for CommandPaletteDelegate {
let mut commands = self.all_commands.clone();
let hit_counts = cx.global::<HitCounts>().clone();
let executor = cx.background_executor().clone();
let query = trim_consecutive_whitespaces(query.as_str());
let query = normalize_query(query.as_str());
async move {
commands.sort_by_key(|action| {
(
@@ -463,6 +468,25 @@ mod tests {
);
}
#[test]
fn test_normalize_query() {
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
assert_eq!(normalize_query("editor: backspace"), "editor: backspace");
assert_eq!(
normalize_query("editor::GoToDefinition"),
"editor:GoToDefinition"
);
assert_eq!(
normalize_query("editor::::GoToDefinition"),
"editor:GoToDefinition"
);
assert_eq!(
normalize_query("editor: :GoToDefinition"),
"editor: :GoToDefinition"
);
}
#[gpui::test]
async fn test_command_palette(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@@ -533,6 +557,40 @@ mod tests {
assert!(palette.delegate.matches.is_empty())
});
}
#[gpui::test]
async fn test_normalized_matches(cx: &mut TestAppContext) {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_text("abc", cx);
editor
});
workspace.update(cx, |workspace, cx| {
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
editor.update(cx, |editor, cx| editor.focus(cx))
});
// Test normalize (trimming whitespace and double colons)
cx.simulate_keystrokes("cmd-shift-p");
let palette = workspace.update(cx, |workspace, cx| {
workspace
.active_modal::<CommandPalette>(cx)
.unwrap()
.read(cx)
.picker
.clone()
});
cx.simulate_input("Editor:: Backspace");
palette.update(cx, |palette, _| {
assert_eq!(palette.delegate.matches[0].string, "editor: backspace");
});
}
#[gpui::test]
async fn test_go_to_line(cx: &mut TestAppContext) {

View File

@@ -28,7 +28,6 @@ serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
ui.workspace = true
url = { workspace = true, features = ["serde"] }
util.workspace = true
workspace.workspace = true

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{anyhow, bail};
use assistant_tool::Tool;
use gpui::{Model, Task};
use gpui::{Model, Task, WindowContext};
use crate::manager::ContextServerManager;
use crate::types;
@@ -52,7 +52,7 @@ impl Tool for ContextServerTool {
self: std::sync::Arc<Self>,
input: serde_json::Value,
_workspace: gpui::WeakView<workspace::Workspace>,
cx: &mut ui::WindowContext,
cx: &mut WindowContext,
) -> gpui::Task<gpui::Result<String>> {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
cx.foreground_executor().spawn({

View File

@@ -34,9 +34,9 @@ pub enum Model {
Gpt4,
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
Gpt3_5Turbo,
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
#[serde(alias = "o1-preview", rename = "o1")]
O1Preview,
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
#[serde(alias = "o1-mini", rename = "o1-mini")]
O1Mini,
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet,

View File

@@ -59,6 +59,14 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
"Copilot"
}
fn show_completions_in_menu() -> bool {
false
}
fn show_completions_in_normal_mode() -> bool {
false
}
fn is_enabled(
&self,
buffer: &Model<Buffer>,
@@ -326,17 +334,15 @@ mod tests {
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
// We want to show both: the inline completion and the completion menu
assert!(editor.context_menu_visible());
assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
assert!(!editor.context_menu_contains_inline_completion());
assert!(!editor.has_active_inline_completion());
// Since we have both, the copilot suggestion is not shown inline
assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
assert_eq!(editor.display_text(cx), "one.\ntwo\nthree\n");
// Confirming a non-copilot completion inserts it and hides the context menu, without showing
// the copilot suggestion afterwards.
editor.context_menu_next(&Default::default(), cx);
editor
.confirm_completion(&Default::default(), cx)
.unwrap()
@@ -384,23 +390,12 @@ mod tests {
// Ensure existing inline completion is interpolated when inserting again.
cx.simulate_keystroke("c");
drop(handle_completion_request(
&mut cx,
indoc! {"
one.c|<>
two
three
"},
vec!["completion_a", "completion_b"],
));
executor.run_until_parked();
cx.update_editor(|editor, cx| {
// Since we have an LSP completion too, the inline completion is
// shown in the menu now
assert!(editor.context_menu_visible());
assert!(editor.context_menu_contains_inline_completion());
assert!(!editor.context_menu_visible());
assert!(!editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
});
@@ -416,16 +411,9 @@ mod tests {
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
assert!(editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert!(editor.context_menu_contains_inline_completion());
assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
// Canceling should first hide the menu and make Copilot suggestion visible.
editor.cancel(&Default::default(), cx);
assert!(!editor.context_menu_visible());
assert!(editor.has_active_inline_completion());
assert!(!editor.context_menu_contains_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
@@ -928,8 +916,8 @@ mod tests {
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
assert!(editor.context_menu_visible());
assert!(editor.context_menu_contains_inline_completion());
assert!(editor.has_active_inline_completion(),);
assert!(!editor.context_menu_contains_inline_completion());
assert!(!editor.has_active_inline_completion(),);
assert_eq!(editor.text(cx), "one\ntwo.\nthree\n");
});
}

View File

@@ -18,6 +18,7 @@ collections.workspace = true
ctor.workspace = true
editor.workspace = true
env_logger.workspace = true
feature_flags.workspace = true
gpui.workspace = true
language.workspace = true
log.workspace = true

View File

@@ -14,6 +14,7 @@ use editor::{
scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
};
use feature_flags::FeatureFlagAppExt;
use gpui::{
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
FocusableView, Global, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement,
@@ -21,7 +22,8 @@ use gpui::{
WeakView, WindowContext,
};
use language::{
Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection, SelectionGoal,
Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity,
Point, Selection, SelectionGoal, ToTreeSitterPoint,
};
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
@@ -29,9 +31,10 @@ use project_diagnostics_settings::ProjectDiagnosticsSettings;
use settings::Settings;
use std::{
any::{Any, TypeId},
cmp,
cmp::Ordering,
mem,
ops::Range,
ops::{Range, RangeInclusive},
sync::Arc,
time::Duration,
};
@@ -41,6 +44,7 @@ use ui::{h_flex, prelude::*, Icon, IconName, Label};
use util::ResultExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
searchable::SearchableItemHandle,
ItemNavHistory, ToolbarItemLocation, Workspace,
};
@@ -162,7 +166,7 @@ impl ProjectDiagnosticsEditor {
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
let editor = cx.new_view(|cx| {
let mut editor =
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), true, cx);
editor.set_vertical_scroll_margin(5, cx);
editor
});
@@ -421,31 +425,28 @@ impl ProjectDiagnosticsEditor {
blocks: Default::default(),
block_count: 0,
};
let mut pending_range: Option<(Range<Point>, usize)> = None;
let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None;
let mut is_first_excerpt_for_group = true;
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
if let Some((range, start_ix)) = &mut pending_range {
if let Some(entry) = resolved_entry.as_ref() {
if entry.range.start.row <= range.end.row + 1 + self.context * 2 {
range.end = range.end.max(entry.range.end);
let expanded_range = resolved_entry.as_ref().map(|entry| {
context_range_for_entry(entry, self.context, &snapshot, cx)
});
if let Some((range, context_range, start_ix)) = &mut pending_range {
if let Some(expanded_range) = expanded_range.clone() {
// If the entries are overlapping or next to each-other, merge them into one excerpt.
if context_range.end.row + 1 >= expanded_range.start.row {
context_range.end = context_range.end.max(expanded_range.end);
continue;
}
}
let excerpt_start =
Point::new(range.start.row.saturating_sub(self.context), 0);
let excerpt_end = snapshot.clip_point(
Point::new(range.end.row + self.context, u32::MAX),
Bias::Left,
);
let excerpt_id = excerpts
.insert_excerpts_after(
prev_excerpt_id,
buffer.clone(),
[ExcerptRange {
context: excerpt_start..excerpt_end,
context: context_range.clone(),
primary: Some(range.clone()),
}],
cx,
@@ -502,8 +503,9 @@ impl ProjectDiagnosticsEditor {
pending_range.take();
}
if let Some(entry) = resolved_entry {
pending_range = Some((entry.range.clone(), ix));
if let Some(entry) = resolved_entry.as_ref() {
let range = entry.range.clone();
pending_range = Some((range, expanded_range.unwrap(), ix));
}
}
@@ -810,6 +812,10 @@ impl Item for ProjectDiagnosticsEditor {
}
}
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.editor.clone()))
}
fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation {
ToolbarItemLocation::PrimaryLeft
}
@@ -830,19 +836,23 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
let message: SharedString = message;
Arc::new(move |cx| {
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
let color = cx.theme().colors();
let highlight_style: HighlightStyle = color.text_accent.into();
h_flex()
.id(DIAGNOSTIC_HEADER)
.block_mouse_down()
.h(2. * cx.line_height())
.pl_10()
.pr_5()
.w_full()
.px_9()
.justify_between()
.gap_2()
.child(
h_flex()
.gap_3()
.gap_2()
.px_1()
.rounded_md()
.bg(color.surface_background.opacity(0.5))
.map(|stack| {
stack.child(
svg()
@@ -874,22 +884,18 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
stack.child(
div()
.child(SharedString::from(format!("({code})")))
.text_color(cx.theme().colors().text_muted),
.text_color(color.text_muted),
)
}),
),
)
.child(
h_flex()
.gap_1()
.when_some(diagnostic.source.as_ref(), |stack, source| {
stack.child(
div()
.child(SharedString::from(source.clone()))
.text_color(cx.theme().colors().text_muted),
)
}),
)
.when_some(diagnostic.source.as_ref(), |stack, source| {
stack.child(
div()
.child(SharedString::from(source.clone()))
.text_color(color.text_muted),
)
})
.into_any_element()
})
}
@@ -918,3 +924,169 @@ fn compare_diagnostics(
})
.then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
}
const DIAGNOSTIC_EXPANSION_ROW_LIMIT: u32 = 32;
fn context_range_for_entry(
entry: &DiagnosticEntry<Point>,
context: u32,
snapshot: &BufferSnapshot,
cx: &AppContext,
) -> Range<Point> {
if cx.is_staff() {
if let Some(rows) = heuristic_syntactic_expand(
entry.range.clone(),
DIAGNOSTIC_EXPANSION_ROW_LIMIT,
snapshot,
cx,
) {
return Range {
start: Point::new(*rows.start(), 0),
end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left),
};
}
}
Range {
start: Point::new(entry.range.start.row.saturating_sub(context), 0),
end: snapshot.clip_point(
Point::new(entry.range.end.row + context, u32::MAX),
Bias::Left,
),
}
}
/// Expands the input range using syntax information from TreeSitter. This expansion will be limited
/// to the specified `max_row_count`.
///
/// If there is a containing outline item that is less than `max_row_count`, it will be returned.
/// Otherwise fairly arbitrary heuristics are applied to attempt to return a logical block of code.
fn heuristic_syntactic_expand<'a>(
input_range: Range<Point>,
max_row_count: u32,
snapshot: &'a BufferSnapshot,
cx: &'a AppContext,
) -> Option<RangeInclusive<BufferRow>> {
let input_row_count = input_range.end.row - input_range.start.row;
if input_row_count > max_row_count {
return None;
}
// If the outline node contains the diagnostic and is small enough, just use that.
let outline_range = snapshot.outline_range_containing(input_range.clone());
if let Some(outline_range) = outline_range.clone() {
// Remove blank lines from start and end
if let Some(start_row) = (outline_range.start.row..outline_range.end.row)
.find(|row| !snapshot.line_indent_for_row(*row).is_line_blank())
{
if let Some(end_row) = (outline_range.start.row..outline_range.end.row + 1)
.rev()
.find(|row| !snapshot.line_indent_for_row(*row).is_line_blank())
{
let row_count = end_row.saturating_sub(start_row);
if row_count <= max_row_count {
return Some(RangeInclusive::new(
outline_range.start.row,
outline_range.end.row,
));
}
}
}
}
let mut node = snapshot.syntax_ancestor(input_range.clone())?;
loop {
let node_start = Point::from_ts_point(node.start_position());
let node_end = Point::from_ts_point(node.end_position());
let node_range = node_start..node_end;
let row_count = node_end.row - node_start.row + 1;
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval
// of node children which contains the query range. For example, this allows just returning
// the header of a declaration rather than the entire declaration.
if row_count > max_row_count || outline_range == Some(node_range.clone()) {
let mut cursor = node.walk();
let mut included_child_start = None;
let mut included_child_end = None;
let mut previous_end = node_start;
if cursor.goto_first_child() {
loop {
let child_node = cursor.node();
let child_range = previous_end..Point::from_ts_point(child_node.end_position());
if included_child_start.is_none() && child_range.contains(&input_range.start) {
included_child_start = Some(child_range.start);
}
if child_range.contains(&input_range.end) {
included_child_end = Some(child_range.end);
}
previous_end = child_range.end;
if !cursor.goto_next_sibling() {
break;
}
}
}
let end = included_child_end.unwrap_or(node_range.end);
if let Some(start) = included_child_start {
let row_count = end.row - start.row;
if row_count < max_row_count {
return Some(RangeInclusive::new(start.row, end.row));
}
}
log::info!(
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
node.grammar_name()
);
return None;
}
let node_name = node.grammar_name();
let node_row_range = RangeInclusive::new(node_range.start.row, node_range.end.row);
if node_name.ends_with("block") {
return Some(node_row_range);
} else if node_name.ends_with("statement") || node_name.ends_with("declaration") {
// Expand to the nearest dedent or blank line for statements and declarations.
let tab_size = snapshot.settings_at(node_range.start, cx).tab_size.get();
let indent_level = snapshot
.line_indent_for_row(node_range.start.row)
.len(tab_size);
let rows_remaining = max_row_count.saturating_sub(row_count);
let Some(start_row) = (node_range.start.row.saturating_sub(rows_remaining)
..node_range.start.row)
.rev()
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
else {
return Some(node_row_range);
};
let rows_remaining = max_row_count.saturating_sub(node_range.end.row - start_row);
let Some(end_row) = (node_range.end.row + 1
..cmp::min(
node_range.end.row + rows_remaining + 1,
snapshot.row_count(),
))
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
else {
return Some(node_row_range);
};
return Some(RangeInclusive::new(start_row, end_row));
}
// TODO: doing this instead of walking a cursor as that doesn't work - why?
let Some(parent) = node.parent() else {
log::info!(
"Expanding to ancestor reached the top node, so using default context line count.",
);
return None;
};
node = parent;
}
}
fn is_line_blank_or_indented_less(
indent_level: u32,
row: u32,
tab_size: u32,
snapshot: &BufferSnapshot,
) -> bool {
let line_indent = snapshot.line_indent_for_row(row);
line_indent.is_line_blank() || line_indent.len(tab_size) < indent_level
}

View File

@@ -167,10 +167,10 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(15), EXCERPT_HEADER.into()),
(DisplayRow(16), DIAGNOSTIC_HEADER.into()),
(DisplayRow(25), EXCERPT_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(16), EXCERPT_HEADER.into()),
(DisplayRow(18), DIAGNOSTIC_HEADER.into()),
(DisplayRow(27), EXCERPT_HEADER.into()),
]
);
assert_eq!(
@@ -184,6 +184,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
@@ -195,6 +196,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" c(y);\n",
"\n", // supporting diagnostic
" d(x);\n",
"\n", // expand
"\n", // context ellipsis
// diagnostic group 2
"\n", // primary message
@@ -206,11 +208,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // expand
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
"}",
"\n", // expand
)
);
@@ -218,7 +222,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
[DisplayPoint::new(DisplayRow(13), 6)..DisplayPoint::new(DisplayRow(13), 6)]
);
});
@@ -253,12 +257,12 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), FILE_HEADER.into()),
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
(DisplayRow(22), EXCERPT_HEADER.into()),
(DisplayRow(23), DIAGNOSTIC_HEADER.into()),
(DisplayRow(32), EXCERPT_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), FILE_HEADER.into()),
(DisplayRow(12), DIAGNOSTIC_HEADER.into()),
(DisplayRow(25), EXCERPT_HEADER.into()),
(DisplayRow(27), DIAGNOSTIC_HEADER.into()),
(DisplayRow(36), EXCERPT_HEADER.into()),
]
);
@@ -273,6 +277,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
@@ -284,6 +289,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"\n", // expand
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
@@ -299,6 +306,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 2
"\n", // primary message
"\n", // filename
"\n", // expand
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
@@ -306,11 +314,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // expand
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
"}",
"\n", // expand
)
);
@@ -318,7 +328,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor.update(cx, |editor, cx| {
assert_eq!(
editor.selections.display_ranges(cx),
[DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
[DisplayPoint::new(DisplayRow(22), 6)..DisplayPoint::new(DisplayRow(22), 6)]
);
});
@@ -366,14 +376,14 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
(DisplayRow(13), FILE_HEADER.into()),
(DisplayRow(15), DIAGNOSTIC_HEADER.into()),
(DisplayRow(28), EXCERPT_HEADER.into()),
(DisplayRow(29), DIAGNOSTIC_HEADER.into()),
(DisplayRow(38), EXCERPT_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), EXCERPT_HEADER.into()),
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
(DisplayRow(15), FILE_HEADER.into()),
(DisplayRow(19), DIAGNOSTIC_HEADER.into()),
(DisplayRow(32), EXCERPT_HEADER.into()),
(DisplayRow(34), DIAGNOSTIC_HEADER.into()),
(DisplayRow(43), EXCERPT_HEADER.into()),
]
);
@@ -388,6 +398,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"const a: i32 = 'a';\n",
"\n", // supporting diagnostic
"const b: i32 = c;\n",
@@ -395,6 +406,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 2
"\n", // primary message
"\n", // padding
"\n", // expand
"const a: i32 = 'a';\n",
"const b: i32 = c;\n",
"\n", // supporting diagnostic
@@ -406,6 +418,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"\n", // expand
" let x = vec![];\n",
" let y = vec![];\n",
"\n", // supporting diagnostic
@@ -421,6 +435,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
// diagnostic group 2
"\n", // primary message
"\n", // filename
"\n", // expand
"fn main() {\n",
" let x = vec![];\n",
"\n", // supporting diagnostic
@@ -428,11 +443,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
" a(x);\n",
"\n", // supporting diagnostic
" b(y);\n",
"\n", // expand
"\n", // context ellipsis
" c(y);\n",
" d(x);\n",
"\n", // supporting diagnostic
"}"
"}",
"\n", // expand
)
);
}
@@ -513,7 +530,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -524,8 +541,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"a();\n", //
"b();",
"b();", "\n", // expand
)
);
@@ -561,9 +579,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(6), EXCERPT_HEADER.into()),
(DisplayRow(7), DIAGNOSTIC_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -574,8 +592,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"a();\n", // location
"b();\n", //
"\n", // expand
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
@@ -583,6 +603,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
"a();\n", // context
"b();\n", //
"c();", // context
"\n", // expand
)
);
@@ -629,9 +650,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), EXCERPT_HEADER.into()),
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -642,9 +663,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"a();\n", // location
"b();\n", //
"c();\n", // context
"\n", // expand
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
@@ -652,6 +675,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
"b();\n", // context
"c();\n", //
"d();", // context
"\n", // expand
)
);
@@ -687,9 +711,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
editor_blocks(&editor, cx),
[
(DisplayRow(0), FILE_HEADER.into()),
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
(DisplayRow(7), EXCERPT_HEADER.into()),
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
(DisplayRow(8), EXCERPT_HEADER.into()),
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
]
);
assert_eq!(
@@ -700,9 +724,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
// diagnostic group 1
"\n", // primary message
"\n", // padding
"\n", // expand
"b();\n", // location
"c();\n", //
"d();\n", // context
"\n", // expand
"\n", // collapsed context
// diagnostic group 2
"\n", // primary message
@@ -710,6 +736,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
"c();\n", // context
"d();\n", //
"e();", // context
"\n", // expand
)
);
}

View File

@@ -1,11 +1,11 @@
use std::time::Duration;
use editor::Editor;
use editor::{AnchorRangeExt, Editor};
use gpui::{
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
ViewContext, WeakView,
};
use language::Diagnostic;
use language::{Diagnostic, DiagnosticEntry};
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
@@ -148,7 +148,11 @@ impl DiagnosticIndicator {
(buffer, cursor_position)
});
let new_diagnostic = buffer
.diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false)
.diagnostics_in_range(cursor_position..cursor_position, false)
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: range.to_offset(&buffer),
})
.filter(|entry| !entry.range.is_empty())
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
.map(|entry| entry.diagnostic);

View File

@@ -1,6 +1,6 @@
//! This module contains all actions supported by [`Editor`].
use super::*;
use gpui::action_as;
use gpui::{action_aliases, action_as};
use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default)]
@@ -204,7 +204,7 @@ impl_actions!(
ToggleCodeActions,
ToggleComments,
UnfoldAt,
FoldAtLevel
FoldAtLevel,
]
);
@@ -311,7 +311,6 @@ gpui::actions!(
OpenExcerpts,
OpenExcerptsSplit,
OpenProposedChangesEditor,
OpenFile,
OpenDocs,
OpenPermalinkToLine,
OpenUrl,
@@ -388,6 +387,6 @@ gpui::actions!(
]
);
action_as!(outline, ToggleOutline as Toggle);
action_as!(go_to_line, ToggleGoToLine as Toggle);
action_aliases!(editor, OpenSelectedFilename, [OpenFile]);

View File

@@ -16,7 +16,7 @@ fn is_c_language(language: &Language) -> bool {
pub fn switch_source_header(
editor: &mut Editor,
_: &SwitchSourceHeader,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) {
let Some(project) = &editor.project else {
return;

View File

@@ -1,11 +1,8 @@
use std::cell::RefCell;
use std::{cell::Cell, cmp::Reverse, ops::Range, rc::Rc};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior,
Model, ScrollStrategy, SharedString, StrikethroughStyle, StyledText, UniformListScrollHandle,
ViewContext, WeakView,
Model, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText,
UniformListScrollHandle, ViewContext, WeakView,
};
use language::Buffer;
use language::{CodeLabel, Documentation};
@@ -13,9 +10,16 @@ use lsp::LanguageServerId;
use multi_buffer::{Anchor, ExcerptId};
use ordered_float::OrderedFloat;
use project::{CodeAction, Completion, TaskSourceKind};
use std::{
cell::RefCell,
cmp::{min, Reverse},
iter,
ops::Range,
rc::Rc,
};
use task::ResolvedTask;
use ui::{prelude::*, Color, IntoElement, ListItem, Popover, Styled};
use util::ResultExt as _;
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
use util::ResultExt;
use workspace::Workspace;
use crate::{
@@ -26,6 +30,11 @@ use crate::{
};
use crate::{AcceptInlineCompletion, InlineCompletionMenuHint, InlineCompletionText};
pub const MENU_GAP: Pixels = px(4.);
pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);
pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.);
pub enum CodeContextMenu {
Completions(CompletionsMenu),
CodeActions(CodeActionsMenu),
@@ -109,20 +118,31 @@ impl CodeContextMenu {
CodeContextMenu::CodeActions(menu) => menu.origin(cursor_position),
}
}
pub fn render(
&self,
style: &EditorStyle,
max_height_in_lines: u32,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
match self {
CodeContextMenu::Completions(menu) => {
menu.render(style, max_height_in_lines, workspace, cx)
}
CodeContextMenu::Completions(menu) => menu.render(style, max_height_in_lines, cx),
CodeContextMenu::CodeActions(menu) => menu.render(style, max_height_in_lines, cx),
}
}
pub fn render_aside(
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
match self {
CodeContextMenu::Completions(menu) => menu.render_aside(style, max_size, workspace, cx),
CodeContextMenu::CodeActions(_) => None,
}
}
}
pub enum ContextMenuOrigin {
@@ -142,8 +162,8 @@ pub struct CompletionsMenu {
pub selected_item: usize,
scroll_handle: UniformListScrollHandle,
resolve_completions: bool,
pub aside_was_displayed: Cell<bool>,
show_completion_documentation: bool,
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
}
#[derive(Clone, Debug)]
@@ -160,7 +180,6 @@ impl CompletionsMenu {
initial_position: Anchor,
buffer: Model<Buffer>,
completions: Box<[Completion]>,
aside_was_displayed: bool,
) -> Self {
let match_candidates = completions
.iter()
@@ -180,7 +199,7 @@ impl CompletionsMenu {
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: true,
aside_was_displayed: Cell::new(aside_was_displayed),
last_rendered_range: RefCell::new(None).into(),
}
}
@@ -205,6 +224,7 @@ impl CompletionsMenu {
documentation: None,
lsp_completion: Default::default(),
confirm: None,
resolved: true,
})
.collect();
@@ -236,8 +256,8 @@ impl CompletionsMenu {
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
resolve_completions: false,
aside_was_displayed: Cell::new(false),
show_completion_documentation: false,
last_rendered_range: RefCell::new(None).into(),
}
}
@@ -246,11 +266,7 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
self.selected_item = 0;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
self.update_selection_index(0, provider, cx);
}
fn select_prev(
@@ -258,15 +274,7 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
if self.selected_item > 0 {
self.selected_item -= 1;
} else {
self.selected_item = self.entries.len() - 1;
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
self.update_selection_index(self.prev_match_index(), provider, cx);
}
fn select_next(
@@ -274,15 +282,7 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
if self.selected_item + 1 < self.entries.len() {
self.selected_item += 1;
} else {
self.selected_item = 0;
}
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
self.update_selection_index(self.next_match_index(), provider, cx);
}
fn select_last(
@@ -290,11 +290,38 @@ impl CompletionsMenu {
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
self.selected_item = self.entries.len() - 1;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_selected_completion(provider, cx);
cx.notify();
self.update_selection_index(self.entries.len() - 1, provider, cx);
}
fn update_selection_index(
&mut self,
match_index: usize,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
) {
if self.selected_item != match_index {
self.selected_item = match_index;
self.scroll_handle
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
self.resolve_visible_completions(provider, cx);
cx.notify();
}
}
fn prev_match_index(&self) -> usize {
if self.selected_item > 0 {
self.selected_item - 1
} else {
self.entries.len() - 1
}
}
fn next_match_index(&self) -> usize {
if self.selected_item + 1 < self.entries.len() {
self.selected_item + 1
} else {
0
}
}
pub fn show_inline_completion_hint(&mut self, hint: InlineCompletionMenuHint) {
@@ -307,6 +334,9 @@ impl CompletionsMenu {
entries
}
_ => {
if self.selected_item != 0 {
self.selected_item += 1;
}
let mut entries = Vec::with_capacity(self.entries.len() + 1);
entries.push(hint);
entries.extend_from_slice(&self.entries);
@@ -314,10 +344,9 @@ impl CompletionsMenu {
}
}
.into();
self.selected_item = 0;
}
pub fn resolve_selected_completion(
pub fn resolve_visible_completions(
&mut self,
provider: Option<&dyn CompletionProvider>,
cx: &mut ViewContext<Editor>,
@@ -329,24 +358,76 @@ impl CompletionsMenu {
return;
};
match &self.entries[self.selected_item] {
CompletionEntry::Match(entry) => {
let completion_index = entry.candidate_id;
let resolve_task = provider.resolve_completions(
self.buffer.clone(),
vec![completion_index],
self.completions.clone(),
cx,
);
// Attempt to resolve completions for every item that will be displayed. This matters
// because single line documentation may be displayed inline with the completion.
//
// When navigating to the very beginning or end of completions, `last_rendered_range` may
// have no overlap with the completions that will be displayed, so instead use a range based
// on the last rendered count.
const APPROXIMATE_VISIBLE_COUNT: usize = 12;
let last_rendered_range = self.last_rendered_range.borrow().clone();
let visible_count = last_rendered_range
.clone()
.map_or(APPROXIMATE_VISIBLE_COUNT, |range| range.count());
let entry_range = if self.selected_item == 0 {
0..min(visible_count, self.entries.len())
} else if self.selected_item == self.entries.len() - 1 {
self.entries.len().saturating_sub(visible_count)..self.entries.len()
} else {
last_rendered_range.map_or(0..0, |range| {
min(range.start, self.entries.len())..min(range.end, self.entries.len())
})
};
cx.spawn(move |editor, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
editor.update(&mut cx, |_, cx| cx.notify()).ok();
}
})
.detach();
// Expand the range to resolve more completions than are predicted to be visible, to reduce
// jank on navigation.
const EXTRA_TO_RESOLVE: usize = 4;
let entry_indices = util::iterate_expanded_and_wrapped_usize_range(
entry_range.clone(),
EXTRA_TO_RESOLVE,
EXTRA_TO_RESOLVE,
self.entries.len(),
);
// Avoid work by sometimes filtering out completions that already have documentation.
// This filtering doesn't happen if the completions are currently being updated.
let completions = self.completions.borrow();
let candidate_ids = entry_indices
.flat_map(|i| Self::entry_candidate_id(&self.entries[i]))
.filter(|i| completions[*i].documentation.is_none());
// Current selection is always resolved even if it already has documentation, to handle
// out-of-spec language servers that return more results later.
let candidate_ids = match Self::entry_candidate_id(&self.entries[self.selected_item]) {
None => candidate_ids.collect::<Vec<usize>>(),
Some(selected_candidate_id) => iter::once(selected_candidate_id)
.chain(candidate_ids.filter(|id| *id != selected_candidate_id))
.collect::<Vec<usize>>(),
};
if candidate_ids.is_empty() {
return;
}
let resolve_task = provider.resolve_completions(
self.buffer.clone(),
candidate_ids,
self.completions.clone(),
cx,
);
cx.spawn(move |editor, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
editor.update(&mut cx, |_, cx| cx.notify()).ok();
}
CompletionEntry::InlineCompletionHint { .. } => {}
})
.detach();
}
fn entry_candidate_id(entry: &CompletionEntry) -> Option<usize> {
match entry {
CompletionEntry::Match(entry) => Some(entry.candidate_id),
CompletionEntry::InlineCompletionHint { .. } => None,
}
}
@@ -362,11 +443,8 @@ impl CompletionsMenu {
&self,
style: &EditorStyle,
max_height_in_lines: u32,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
let max_height = max_height_in_lines as f32 * cx.line_height();
let completions = self.completions.borrow_mut();
let show_completion_documentation = self.show_completion_documentation;
let widest_completion_ix = self
@@ -393,76 +471,19 @@ impl CompletionsMenu {
}) => provider_name.len(),
})
.map(|(ix, _)| ix);
drop(completions);
let selected_item = self.selected_item;
let style = style.clone();
let multiline_docs = match &self.entries[selected_item] {
CompletionEntry::Match(mat) if show_completion_documentation => {
match &completions[mat.candidate_id].documentation {
Some(Documentation::MultiLinePlainText(text)) => {
Some(div().child(SharedString::from(text.clone())))
}
Some(Documentation::MultiLineMarkdown(parsed)) if !parsed.text.is_empty() => {
Some(div().child(render_parsed_markdown(
"completions_markdown",
parsed,
&style,
workspace,
cx,
)))
}
Some(Documentation::Undocumented) if self.aside_was_displayed.get() => {
Some(div().child("No documentation"))
}
_ => None,
}
}
CompletionEntry::InlineCompletionHint(hint) => Some(match &hint.text {
InlineCompletionText::Edit { text, highlights } => div()
.my_1()
.rounded(px(6.))
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.child(
gpui::StyledText::new(text.clone())
.with_highlights(&style.text, highlights.clone()),
),
InlineCompletionText::Move(text) => div().child(text.clone()),
}),
_ => None,
};
let aside_contents = if let Some(multiline_docs) = multiline_docs {
Some(multiline_docs)
} else if show_completion_documentation && self.aside_was_displayed.get() {
Some(div().child("Fetching documentation..."))
} else {
None
};
self.aside_was_displayed.set(aside_contents.is_some());
let aside_contents = aside_contents.map(|div| {
div.id("multiline_docs")
.max_h(max_height)
.flex_1()
.px_1p5()
.py_1()
.max_w(px(640.))
.w(px(450.))
.overflow_y_scroll()
.occlude()
});
drop(completions);
let completions = self.completions.clone();
let matches = self.entries.clone();
let last_rendered_range = self.last_rendered_range.clone();
let style = style.clone();
let list = uniform_list(
cx.view().clone(),
"completions",
matches.len(),
move |_editor, range, cx| {
last_rendered_range.borrow_mut().replace(range.clone());
let start_ix = range.start;
let completions_guard = completions.borrow_mut();
@@ -588,12 +609,70 @@ impl CompletionsMenu {
.with_width_from_item(widest_completion_ix)
.with_sizing_behavior(ListSizingBehavior::Infer);
Popover::new()
.child(list)
.when_some(aside_contents, |popover, aside_contents| {
popover.aside(aside_contents)
})
.into_any_element()
Popover::new().child(list).into_any_element()
}
fn render_aside(
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
workspace: Option<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
if !self.show_completion_documentation {
return None;
}
let multiline_docs = match &self.entries[self.selected_item] {
CompletionEntry::Match(mat) => {
match self.completions.borrow_mut()[mat.candidate_id]
.documentation
.as_ref()?
{
Documentation::MultiLinePlainText(text) => {
div().child(SharedString::from(text.clone()))
}
Documentation::MultiLineMarkdown(parsed) if !parsed.text.is_empty() => div()
.child(render_parsed_markdown(
"completions_markdown",
parsed,
&style,
workspace,
cx,
)),
Documentation::MultiLineMarkdown(_) => return None,
Documentation::SingleLine(_) => return None,
Documentation::Undocumented => return None,
}
}
CompletionEntry::InlineCompletionHint(hint) => match &hint.text {
InlineCompletionText::Edit { text, highlights } => div()
.mx_1()
.rounded(px(6.))
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.child(
gpui::StyledText::new(text.clone())
.with_highlights(&style.text, highlights.clone()),
),
InlineCompletionText::Move(text) => div().child(text.clone()),
},
};
Some(
Popover::new()
.child(
multiline_docs
.id("multiline_docs")
.px(MENU_ASIDE_X_PADDING / 2.)
.max_w(max_size.width)
.max_h(max_size.height)
.overflow_y_scroll()
.occlude(),
)
.into_any_element(),
)
}
pub async fn filter(&mut self, query: Option<&str>, executor: BackgroundExecutor) {

View File

@@ -32,6 +32,7 @@ use crate::{
pub use block_map::{
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
StickyHeaderExcerpt,
};
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
@@ -41,7 +42,7 @@ use fold_map::{FoldMap, FoldSnapshot};
use gpui::{
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
};
pub(crate) use inlay_map::Inlay;
pub use inlay_map::Inlay;
use inlay_map::{InlayMap, InlaySnapshot};
pub use inlay_map::{InlayOffset, InlayPoint};
use invisibles::{is_invisible, replacement};
@@ -1105,6 +1106,10 @@ impl DisplaySnapshot {
.map(|(row, block)| (DisplayRow(row), block))
}
pub fn sticky_header_excerpt(&self, row: DisplayRow) -> Option<StickyHeaderExcerpt<'_>> {
self.block_snapshot.sticky_header_excerpt(row.0)
}
pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
self.block_snapshot.block_for_id(id)
}

View File

@@ -1411,6 +1411,66 @@ impl BlockSnapshot {
})
}
pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
let mut cursor = self.transforms.cursor::<BlockRow>(&());
cursor.seek(&BlockRow(top_row), Bias::Left, &());
while let Some(transform) = cursor.item() {
let start = cursor.start().0;
let end = cursor.end(&()).0;
match &transform.block {
Some(Block::ExcerptBoundary {
prev_excerpt,
next_excerpt,
starts_new_buffer,
show_excerpt_controls,
..
}) => {
let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
start < top_row
} else {
start <= top_row
};
if matches_start && top_row <= end {
return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
next_buffer_row: None,
next_excerpt_controls_present: *show_excerpt_controls,
excerpt,
});
}
let next_buffer_row = if *starts_new_buffer { Some(end) } else { None };
return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
excerpt,
next_buffer_row,
next_excerpt_controls_present: *show_excerpt_controls,
});
}
Some(Block::FoldedBuffer {
prev_excerpt: Some(excerpt),
..
}) if top_row <= start => {
return Some(StickyHeaderExcerpt {
next_buffer_row: Some(end),
next_excerpt_controls_present: false,
excerpt,
});
}
Some(Block::FoldedBuffer { .. }) | Some(Block::Custom(_)) | None => {}
}
// This is needed to iterate past None / FoldedBuffer / Custom blocks. For FoldedBuffer,
// if scrolled slightly past the header of a folded block, the next block is needed for
// the sticky header.
cursor.next(&());
}
None
}
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
let buffer = self.wrap_snapshot.buffer_snapshot();
let wrap_point = match block_id {
@@ -1694,6 +1754,13 @@ impl<'a> BlockChunks<'a> {
}
}
pub struct StickyHeaderExcerpt<'a> {
pub excerpt: &'a ExcerptInfo,
pub next_excerpt_controls_present: bool,
// TODO az remove option
pub next_buffer_row: Option<u32>,
}
impl<'a> Iterator for BlockChunks<'a> {
type Item = Chunk<'a>;
@@ -2681,7 +2748,7 @@ mod tests {
.iter()
.filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
.count(),
"Should have one folded block, prodicing a header of the second buffer"
"Should have one folded block, producing a header of the second buffer"
);
assert_eq!(
blocks_snapshot.text(),
@@ -2927,7 +2994,7 @@ mod tests {
}
})
.count(),
"Should have one folded block, prodicing a header of the second buffer"
"Should have one folded block, producing a header of the second buffer"
);
assert_eq!(blocks_snapshot.text(), "\n");
assert_eq!(

View File

@@ -33,7 +33,7 @@ enum Transform {
}
#[derive(Debug, Clone)]
pub(crate) struct Inlay {
pub struct Inlay {
pub(crate) id: InlayId,
pub position: Anchor,
pub text: text::Rope,

View File

@@ -99,8 +99,8 @@ use itertools::Itertools;
use language::{
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId,
CursorShape, Diagnostic, DiagnosticEntry, Documentation, IndentKind, IndentSize, Language,
OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
@@ -128,6 +128,7 @@ use multi_buffer::{
ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, ToOffsetUtf16,
};
use project::{
buffer_store::BufferChangeSet,
lsp_store::{FormatTarget, FormatTrigger, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink,
@@ -257,7 +258,7 @@ pub fn render_parsed_markdown(
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum InlayId {
pub enum InlayId {
InlineCompletion(usize),
Hint(usize),
}
@@ -605,6 +606,7 @@ pub struct Editor {
mode: EditorMode,
show_breadcrumbs: bool,
show_gutter: bool,
show_scrollbars: bool,
show_line_numbers: Option<bool>,
use_relative_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
@@ -990,12 +992,17 @@ pub(crate) struct FocusedBlock {
}
#[derive(Clone)]
struct JumpData {
excerpt_id: ExcerptId,
position: Point,
anchor: text::Anchor,
path: Option<project::ProjectPath>,
line_offset_from_top: u32,
enum JumpData {
MultiBufferRow {
row: MultiBufferRow,
line_offset_from_top: u32,
},
MultiBufferPoint {
excerpt_id: ExcerptId,
position: Point,
anchor: text::Anchor,
line_offset_from_top: u32,
},
}
impl Editor {
@@ -1234,6 +1241,7 @@ impl Editor {
project,
blink_manager: blink_manager.clone(),
show_local_selections: true,
show_scrollbars: true,
mode,
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
show_gutter: mode == EditorMode::Full,
@@ -2473,7 +2481,7 @@ impl Editor {
}
if self.hide_context_menu(cx).is_some() {
if self.has_active_inline_completion() {
if self.show_inline_completions_in_menu(cx) && self.has_active_inline_completion() {
self.update_visible_inline_completion(cx);
}
return true;
@@ -2856,6 +2864,7 @@ impl Editor {
);
}
let had_active_inline_completion = this.has_active_inline_completion();
this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
s.select(new_selections)
});
@@ -2876,7 +2885,9 @@ impl Editor {
this.show_signature_help(&ShowSignatureHelp, cx);
}
this.trigger_completion_on_input(&text, true, cx);
let trigger_in_words =
this.show_inline_completions_in_menu(cx) || !had_active_inline_completion;
this.trigger_completion_on_input(&text, trigger_in_words, cx);
linked_editing_ranges::refresh_linked_ranges(this, cx);
this.refresh_inline_completion(true, false, cx);
});
@@ -3507,7 +3518,7 @@ impl Editor {
}
}
fn visible_inlay_hints(&self, cx: &ViewContext<'_, Editor>) -> Vec<Inlay> {
fn visible_inlay_hints(&self, cx: &ViewContext<Editor>) -> Vec<Inlay> {
self.display_map
.read(cx)
.current_inlays()
@@ -3538,13 +3549,12 @@ impl Editor {
Bias::Left,
);
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
multi_buffer
.range_to_buffer_ranges(multi_buffer_visible_range, cx)
multi_buffer_snapshot
.range_to_buffer_ranges(multi_buffer_visible_range)
.into_iter()
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
.filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| {
let buffer = buffer_handle.read(cx);
let buffer_file = project::File::from_dyn(buffer.file())?;
.filter(|(_, excerpt_visible_range)| !excerpt_visible_range.is_empty())
.filter_map(|(excerpt, excerpt_visible_range)| {
let buffer_file = project::File::from_dyn(excerpt.buffer().file())?;
let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
let worktree_entry = buffer_worktree
.read(cx)
@@ -3553,17 +3563,17 @@ impl Editor {
return None;
}
let language = buffer.language()?;
let language = excerpt.buffer().language()?;
if let Some(restrict_to_languages) = restrict_to_languages {
if !restrict_to_languages.contains(language) {
return None;
}
}
Some((
excerpt_id,
excerpt.id(),
(
buffer_handle,
buffer.version().clone(),
multi_buffer.buffer(excerpt.buffer_id()).unwrap(),
excerpt.buffer().version().clone(),
excerpt_visible_range,
),
))
@@ -3582,7 +3592,7 @@ impl Editor {
}
}
fn splice_inlays(
pub fn splice_inlays(
&self,
to_remove: Vec<InlayId>,
to_insert: Vec<Inlay>,
@@ -3682,10 +3692,6 @@ impl Editor {
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
let aside_was_displayed = match self.context_menu.borrow().deref() {
Some(CodeContextMenu::Completions(menu)) => menu.aside_was_displayed.get(),
_ => false,
};
let trigger_kind = match &options.trigger {
Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
CompletionTriggerKind::TRIGGER_CHARACTER
@@ -3720,7 +3726,6 @@ impl Editor {
position,
buffer.clone(),
completions.into(),
aside_was_displayed,
);
menu.filter(query.as_deref(), cx.background_executor().clone())
@@ -3744,11 +3749,14 @@ impl Editor {
if editor.focus_handle.is_focused(cx) && menu.is_some() {
let mut menu = menu.unwrap();
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
editor.hide_active_inline_completion(cx);
menu.show_inline_completion_hint(hint);
if editor.show_inline_completions_in_menu(cx) {
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
menu.show_inline_completion_hint(hint);
}
} else {
editor.discard_inline_completion(false, cx);
}
*editor.context_menu.borrow_mut() =
@@ -3757,9 +3765,14 @@ impl Editor {
cx.notify();
} else if editor.completion_tasks.len() <= 1 {
// If there are no more completion tasks and the last menu was
// empty, we should hide it. If it was already hidden, we should
// also show the copilot completion when available.
editor.hide_context_menu(cx);
// empty, we should hide it.
let was_hidden = editor.hide_context_menu(cx).is_none();
// If it was already hidden and we don't show inline
// completions in the menu, we should also show the
// inline-completion when available.
if was_hidden && editor.show_inline_completions_in_menu(cx) {
editor.update_visible_inline_completion(cx);
}
}
})?;
@@ -3813,14 +3826,19 @@ impl Editor {
return Some(Task::ready(Ok(())));
}
CompletionEntry::Match(mat) => {
self.discard_inline_completion(true, cx);
if self.show_inline_completions_in_menu(cx) {
self.discard_inline_completion(true, cx);
}
mat
}
};
let buffer_handle = completions_menu.buffer;
let completions = completions_menu.completions.borrow_mut();
let completion = completions.get(mat.candidate_id)?;
let completion = completions_menu
.completions
.borrow()
.get(mat.candidate_id)?
.clone();
cx.stop_propagation();
let snippet;
@@ -3964,9 +3982,11 @@ impl Editor {
}
let provider = self.completion_provider.as_ref()?;
drop(completion);
let apply_edits = provider.apply_additional_edits_for_completion(
buffer_handle,
completion.clone(),
completions_menu.completions.clone(),
mat.candidate_id,
true,
cx,
);
@@ -4551,7 +4571,9 @@ impl Editor {
_: &AcceptInlineCompletion,
cx: &mut ViewContext<Self>,
) {
self.hide_context_menu(cx);
if self.show_inline_completions_in_menu(cx) {
self.hide_context_menu(cx);
}
let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
return;
@@ -4700,17 +4722,6 @@ impl Editor {
Some(active_inline_completion.completion)
}
fn hide_active_inline_completion(&mut self, cx: &mut ViewContext<Self>) {
if let Some(active_inline_completion) = self.active_inline_completion.as_ref() {
self.splice_inlays(
active_inline_completion.inlay_ids.clone(),
Default::default(),
cx,
);
self.clear_highlights::<InlineCompletionHighlight>(cx);
}
}
fn update_visible_inline_completion(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
let selection = self.selections.newest_anchor();
let cursor = selection.head();
@@ -4718,7 +4729,11 @@ impl Editor {
let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
let excerpt_id = cursor.excerpt_id;
if !offset_selection.is_empty()
let completions_menu_has_precedence = !self.show_inline_completions_in_menu(cx)
&& (self.context_menu.borrow().is_some()
|| (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
if completions_menu_has_precedence
|| !offset_selection.is_empty()
|| self
.active_inline_completion
.as_ref()
@@ -4742,16 +4757,10 @@ impl Editor {
let edits = completion
.edits
.into_iter()
.map(|(range, new_text)| {
(
multibuffer
.anchor_in_excerpt(excerpt_id, range.start)
.unwrap()
..multibuffer
.anchor_in_excerpt(excerpt_id, range.end)
.unwrap(),
new_text,
)
.flat_map(|(range, new_text)| {
let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
Some((start..end, new_text))
})
.collect::<Vec<_>>();
if edits.is_empty() {
@@ -4782,34 +4791,32 @@ impl Editor {
invalidation_row_range = edit_start_row..cursor_row;
completion = InlineCompletion::Move(first_edit_start);
} else {
if !self.has_active_completions_menu() {
if edits
.iter()
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
{
let mut inlays = Vec::new();
for (range, new_text) in &edits {
let inlay = Inlay::inline_completion(
post_inc(&mut self.next_inlay_id),
range.start,
new_text.as_str(),
);
inlay_ids.push(inlay.id);
inlays.push(inlay);
}
self.splice_inlays(vec![], inlays, cx);
} else {
let background_color = cx.theme().status().deleted_background;
self.highlight_text::<InlineCompletionHighlight>(
edits.iter().map(|(range, _)| range.clone()).collect(),
HighlightStyle {
background_color: Some(background_color),
..Default::default()
},
cx,
if edits
.iter()
.all(|(range, _)| range.to_offset(&multibuffer).is_empty())
{
let mut inlays = Vec::new();
for (range, new_text) in &edits {
let inlay = Inlay::inline_completion(
post_inc(&mut self.next_inlay_id),
range.start,
new_text.as_str(),
);
inlay_ids.push(inlay.id);
inlays.push(inlay);
}
self.splice_inlays(vec![], inlays, cx);
} else {
let background_color = cx.theme().status().deleted_background;
self.highlight_text::<InlineCompletionHighlight>(
edits.iter().map(|(range, _)| range.clone()).collect(),
HighlightStyle {
background_color: Some(background_color),
..Default::default()
},
cx,
);
}
invalidation_row_range = edit_start_row..edit_end_row;
@@ -4829,7 +4836,7 @@ impl Editor {
invalidation_range,
});
if self.has_active_completions_menu() {
if self.show_inline_completions_in_menu(cx) && self.has_active_completions_menu() {
if let Some(hint) = self.inline_completion_menu_hint(cx) {
match self.context_menu.borrow_mut().as_mut() {
Some(CodeContextMenu::Completions(menu)) => {
@@ -4855,7 +4862,7 @@ impl Editor {
let text = match &self.active_inline_completion.as_ref()?.completion {
InlineCompletion::Edit(edits) => {
inline_completion_edit_text(&editor_snapshot, edits, cx)
inline_completion_edit_text(&editor_snapshot, edits, true, cx)
}
InlineCompletion::Move(target) => {
let target_point =
@@ -4876,10 +4883,17 @@ impl Editor {
}
}
fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
pub fn inline_completion_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
Some(self.inline_completion_provider.as_ref()?.provider.clone())
}
fn show_inline_completions_in_menu(&self, cx: &AppContext) -> bool {
EditorSettings::get_global(cx).show_inline_completions_in_menu
&& self
.inline_completion_provider()
.map_or(false, |provider| provider.show_completions_in_menu())
}
fn render_code_actions_indicator(
&self,
_style: &EditorStyle,
@@ -5082,7 +5096,7 @@ impl Editor {
}))
}
#[cfg(feature = "test-support")]
#[cfg(any(feature = "test-support", test))]
pub fn context_menu_visible(&self) -> bool {
self.context_menu
.borrow()
@@ -5118,12 +5132,27 @@ impl Editor {
) -> Option<AnyElement> {
self.context_menu.borrow().as_ref().and_then(|menu| {
if menu.visible() {
Some(menu.render(
Some(menu.render(style, max_height_in_lines, cx))
} else {
None
}
})
}
fn render_context_menu_aside(
&self,
style: &EditorStyle,
max_size: Size<Pixels>,
cx: &mut ViewContext<Editor>,
) -> Option<AnyElement> {
self.context_menu.borrow().as_ref().and_then(|menu| {
if menu.visible() {
menu.render_aside(
style,
max_height_in_lines,
max_size,
self.workspace.as_ref().map(|(w, _)| w.clone()),
cx,
))
)
} else {
None
}
@@ -5133,7 +5162,11 @@ impl Editor {
fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<CodeContextMenu> {
cx.notify();
self.completion_tasks.clear();
self.context_menu.borrow_mut().take()
let context_menu = self.context_menu.borrow_mut().take();
if context_menu.is_some() && !self.show_inline_completions_in_menu(cx) {
self.update_visible_inline_completion(cx);
}
context_menu
}
fn show_snippet_choices(
@@ -5422,7 +5455,8 @@ impl Editor {
if end_point == start_point {
let offset = text::ToOffset::to_offset(&range.start, &snapshot)
.saturating_sub(1);
start_point = TP::to_point(&offset, &snapshot);
start_point =
snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
};
(start_point..end_point, empty_str.clone())
@@ -5817,7 +5851,7 @@ impl Editor {
});
}
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
pub fn join_lines_impl(&mut self, insert_whitespace: bool, cx: &mut ViewContext<Self>) {
if self.read_only(cx) {
return;
}
@@ -5859,11 +5893,12 @@ impl Editor {
let indent = snapshot.indent_size_for_line(next_line_row);
let start_of_next_line = Point::new(next_line_row.0, indent.len);
let replace = if snapshot.line_len(next_line_row) > indent.len {
" "
} else {
""
};
let replace =
if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
" "
} else {
""
};
this.buffer.update(cx, |buffer, cx| {
buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
@@ -5877,6 +5912,10 @@ impl Editor {
});
}
pub fn join_lines(&mut self, _: &JoinLines, cx: &mut ViewContext<Self>) {
self.join_lines_impl(true, cx);
}
pub fn sort_lines_case_sensitive(
&mut self,
_: &SortLinesCaseSensitive,
@@ -5979,7 +6018,7 @@ impl Editor {
fn gather_revert_changes(
&mut self,
selections: &[Selection<Point>],
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
let mut revert_changes = HashMap::default();
let snapshot = self.snapshot(cx);
@@ -8752,9 +8791,10 @@ impl Editor {
.map(|selection| {
let old_range = selection.start..selection.end;
let mut new_range = old_range.clone();
while let Some(containing_range) =
buffer.range_for_syntax_ancestor(new_range.clone())
let mut new_node = None;
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
{
new_node = Some(node);
new_range = containing_range;
if !display_map.intersects_fold(new_range.start)
&& !display_map.intersects_fold(new_range.end)
@@ -8763,6 +8803,17 @@ impl Editor {
}
}
if let Some(node) = new_node {
// Log the ancestor, to support using this action as a way to explore TreeSitter
// nodes. Parent and grandparent are also logged because this operation will not
// visit nodes that have the same range as their parent.
log::info!("Node: {node:?}");
let parent = node.parent();
log::info!("Parent: {parent:?}");
let grandparent = parent.and_then(|x| x.parent());
log::info!("Grandparent: {grandparent:?}");
}
selected_larger_node |= new_range != old_range;
Selection {
id: selection.id,
@@ -8894,7 +8945,7 @@ impl Editor {
fn templates_with_tags(
project: &Model<Project>,
runnable: &mut Runnable,
cx: &WindowContext<'_>,
cx: &WindowContext,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
let (worktree_id, file) = project
@@ -9063,15 +9114,18 @@ impl Editor {
};
self.buffer.update(cx, |buffer, cx| {
buffer.expand_excerpts(
selections
.iter()
.map(|selection| selection.head().excerpt_id)
.dedup(),
lines,
direction,
cx,
)
let snapshot = buffer.snapshot(cx);
let mut excerpt_ids = selections
.iter()
.flat_map(|selection| {
snapshot
.excerpts_for_range(selection.range())
.map(|excerpt| excerpt.id())
})
.collect::<Vec<_>>();
excerpt_ids.sort();
excerpt_ids.dedup();
buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
})
}
@@ -9102,11 +9156,12 @@ impl Editor {
// If there is an active Diagnostic Popover jump to its diagnostic instead.
if direction == Direction::Next {
if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
let (group_id, jump_to) = popover.activation_info();
if self.activate_diagnostics(group_id, cx) {
self.activate_diagnostics(popover.group_id(), cx);
if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
let primary_range_start = active_diagnostics.primary_range.start;
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
let mut new_selection = s.newest_anchor().clone();
new_selection.collapse_to(jump_to, SelectionGoal::None);
new_selection.collapse_to(primary_range_start, SelectionGoal::None);
s.select_anchors(vec![new_selection.clone()]);
});
}
@@ -9132,10 +9187,23 @@ impl Editor {
let snapshot = self.snapshot(cx);
loop {
let diagnostics = if direction == Direction::Prev {
buffer.diagnostics_in_range::<_, usize>(0..search_start, true)
buffer
.diagnostics_in_range(0..search_start, true)
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: range.to_offset(&buffer),
})
.collect::<Vec<_>>()
} else {
buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false)
buffer
.diagnostics_in_range(search_start..buffer.len(), false)
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: range.to_offset(&buffer),
})
.collect::<Vec<_>>()
}
.into_iter()
.filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
let group = diagnostics
// relies on diagnostics_in_range to return diagnostics with the same starting range to
@@ -9165,7 +9233,8 @@ impl Editor {
});
if let Some((primary_range, group_id)) = group {
if self.activate_diagnostics(group_id, cx) {
self.activate_diagnostics(group_id, cx);
if self.active_diagnostics.is_some() {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(vec![Selection {
id: selection.id,
@@ -9206,7 +9275,7 @@ impl Editor {
&mut self,
snapshot: &EditorSnapshot,
position: Point,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> Option<MultiBufferDiffHunk> {
for (ix, position) in [position, Point::zero()].into_iter().enumerate() {
if let Some(hunk) = self.go_to_next_hunk_in_direction(
@@ -9235,7 +9304,7 @@ impl Editor {
&mut self,
snapshot: &EditorSnapshot,
position: Point,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> Option<MultiBufferDiffHunk> {
for (ix, position) in [position, snapshot.buffer_snapshot.max_point()]
.into_iter()
@@ -9442,7 +9511,7 @@ impl Editor {
url_finder.detach();
}
pub fn open_file(&mut self, _: &OpenFile, cx: &mut ViewContext<Self>) {
pub fn open_selected_filename(&mut self, _: &OpenSelectedFilename, cx: &mut ViewContext<Self>) {
let Some(workspace) = self.workspace() else {
return;
};
@@ -10242,11 +10311,12 @@ impl Editor {
let buffer = self.buffer.read(cx).snapshot(cx);
let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
let is_valid = buffer
.diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false)
.diagnostics_in_range(active_diagnostics.primary_range.clone(), false)
.any(|entry| {
let range = entry.range.to_offset(&buffer);
entry.diagnostic.is_primary
&& !entry.range.is_empty()
&& entry.range.start == primary_range_start
&& !range.is_empty()
&& range.start == primary_range_start
&& entry.diagnostic.message == active_diagnostics.primary_message
});
@@ -10266,7 +10336,7 @@ impl Editor {
}
}
fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) -> bool {
fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext<Self>) {
self.dismiss_diagnostics(cx);
let snapshot = self.snapshot(cx);
self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
@@ -10276,16 +10346,18 @@ impl Editor {
let mut primary_message = None;
let mut group_end = Point::zero();
let diagnostic_group = buffer
.diagnostic_group::<MultiBufferPoint>(group_id)
.diagnostic_group(group_id)
.filter_map(|entry| {
if snapshot.is_line_folded(MultiBufferRow(entry.range.start.row))
&& (entry.range.start.row == entry.range.end.row
|| snapshot.is_line_folded(MultiBufferRow(entry.range.end.row)))
let start = entry.range.start.to_point(&buffer);
let end = entry.range.end.to_point(&buffer);
if snapshot.is_line_folded(MultiBufferRow(start.row))
&& (start.row == end.row
|| snapshot.is_line_folded(MultiBufferRow(end.row)))
{
return None;
}
if entry.range.end > group_end {
group_end = entry.range.end;
if end > group_end {
group_end = end;
}
if entry.diagnostic.is_primary {
primary_range = Some(entry.range.clone());
@@ -10296,8 +10368,6 @@ impl Editor {
.collect::<Vec<_>>();
let primary_range = primary_range?;
let primary_message = primary_message?;
let primary_range =
buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end);
let blocks = display_map
.insert_blocks(
@@ -10328,7 +10398,6 @@ impl Editor {
is_valid: true,
})
});
self.active_diagnostics.is_some()
}
fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
@@ -10377,7 +10446,7 @@ impl Editor {
self.end_transaction_at(Instant::now(), cx)
}
fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
pub fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
self.end_selection(cx);
if let Some(tx_id) = self
.buffer
@@ -10391,7 +10460,7 @@ impl Editor {
}
}
fn end_transaction_at(
pub fn end_transaction_at(
&mut self,
now: Instant,
cx: &mut ViewContext<Self>,
@@ -11225,6 +11294,11 @@ impl Editor {
cx.notify();
}
pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut ViewContext<Self>) {
self.show_scrollbars = show_scrollbars;
cx.notify();
}
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut ViewContext<Self>) {
self.show_line_numbers = Some(show_line_numbers);
cx.notify();
@@ -11441,21 +11515,23 @@ impl Editor {
let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
(buffer, selection_range.start.row..selection_range.end.row)
} else {
let buffer_ranges = self
.buffer()
.read(cx)
.range_to_buffer_ranges(selection_range, cx);
let multi_buffer = self.buffer().read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
let (buffer, range, _) = if selection.reversed {
let (excerpt, range) = if selection.reversed {
buffer_ranges.first()
} else {
buffer_ranges.last()
}?;
let snapshot = buffer.read(cx).snapshot();
let snapshot = excerpt.buffer();
let selection = text::ToPoint::to_point(&range.start, &snapshot).row
..text::ToPoint::to_point(&range.end, &snapshot).row;
(buffer.clone(), selection)
(
multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(),
selection,
)
};
Some((buffer, selection))
@@ -11713,7 +11789,7 @@ impl Editor {
}
/// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
/// Rerturns a map of display rows that are highlighted and their corresponding highlight color.
/// Returns a map of display rows that are highlighted and their corresponding highlight color.
/// Allows to ignore certain kinds of highlights.
pub fn highlighted_display_rows(
&mut self,
@@ -12347,17 +12423,18 @@ impl Editor {
};
let selections = self.selections.all::<usize>(cx);
let buffer = self.buffer.read(cx);
let multi_buffer = self.buffer.read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let mut new_selections_by_buffer = HashMap::default();
for selection in selections {
for (buffer, range, _) in
buffer.range_to_buffer_ranges(selection.start..selection.end, cx)
for (excerpt, range) in
multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
{
let mut range = range.to_point(buffer.read(cx));
let mut range = range.to_point(excerpt.buffer());
range.start.column = 0;
range.end.column = buffer.read(cx).line_len(range.end.row);
range.end.column = excerpt.buffer().line_len(range.end.row);
new_selections_by_buffer
.entry(buffer)
.entry(multi_buffer.buffer(excerpt.buffer_id()).unwrap())
.or_insert(Vec::new())
.push(range)
}
@@ -12411,37 +12488,60 @@ impl Editor {
let mut new_selections_by_buffer = HashMap::default();
match &jump_data {
Some(jump_data) => {
Some(JumpData::MultiBufferPoint {
excerpt_id,
position,
anchor,
line_offset_from_top,
}) => {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
if let Some(buffer) = multi_buffer_snapshot
.buffer_id_for_excerpt(jump_data.excerpt_id)
.buffer_id_for_excerpt(*excerpt_id)
.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
{
let buffer_snapshot = buffer.read(cx).snapshot();
let jump_to_point = if buffer_snapshot.can_resolve(&jump_data.anchor) {
language::ToPoint::to_point(&jump_data.anchor, &buffer_snapshot)
let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
language::ToPoint::to_point(anchor, &buffer_snapshot)
} else {
buffer_snapshot.clip_point(jump_data.position, Bias::Left)
buffer_snapshot.clip_point(*position, Bias::Left)
};
let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
new_selections_by_buffer.insert(
buffer,
(
vec![jump_to_offset..jump_to_offset],
Some(jump_data.line_offset_from_top),
Some(*line_offset_from_top),
),
);
}
}
Some(JumpData::MultiBufferRow {
row,
line_offset_from_top,
}) => {
let point = MultiBufferPoint::new(row.0, 0);
if let Some((buffer, buffer_point, _)) =
self.buffer.read(cx).point_to_buffer_point(point, cx)
{
let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
new_selections_by_buffer
.entry(buffer)
.or_insert((Vec::new(), Some(*line_offset_from_top)))
.0
.push(buffer_offset..buffer_offset)
}
}
None => {
let selections = self.selections.all::<usize>(cx);
let buffer = self.buffer.read(cx);
let multi_buffer = self.buffer.read(cx);
for selection in selections {
for (mut buffer_handle, mut range, _) in
buffer.range_to_buffer_ranges(selection.range(), cx)
for (excerpt, mut range) in multi_buffer
.snapshot(cx)
.range_to_buffer_ranges(selection.range())
{
// When editing branch buffers, jump to the corresponding location
// in their base buffer.
let mut buffer_handle = multi_buffer.buffer(excerpt.buffer_id()).unwrap();
let buffer = buffer_handle.read(cx);
if let Some(base_buffer) = buffer.base_buffer() {
range = buffer.range_to_version(range, &base_buffer.read(cx).version());
@@ -12482,7 +12582,7 @@ impl Editor {
.file()
.is_none()
.then(|| {
// Handle file-less buffers separately: those are not really the project items, so won't have a paroject path or entity id,
// Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
// so `workspace.open_project_item` will never find them, always opening a new editor.
// Instead, we try to activate the existing editor in the pane first.
let (editor, pane_item_index) =
@@ -12916,6 +13016,14 @@ impl Editor {
.and_then(|item| item.to_any().downcast_ref::<T>())
}
pub fn add_change_set(
&mut self,
change_set: Model<BufferChangeSet>,
cx: &mut ViewContext<Self>,
) {
self.diff_map.add_change_set(change_set, cx);
}
fn character_size(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> {
let text_layout_details = self.text_layout_details(cx);
let style = &text_layout_details.editor_style;
@@ -13397,11 +13505,14 @@ pub trait CompletionProvider {
fn apply_additional_edits_for_completion(
&self,
buffer: Model<Buffer>,
completion: Completion,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>>;
_buffer: Model<Buffer>,
_completions: Rc<RefCell<Box<[Completion]>>>,
_completion_index: usize,
_push_to_history: bool,
_cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
Task::ready(Ok(None))
}
fn is_completion_trigger(
&self,
@@ -13560,6 +13671,7 @@ fn snippet_completions(
Some(Completion {
old_range: range,
new_text: snippet.body.clone(),
resolved: false,
label: CodeLabel {
text: matching_prefix.clone(),
runs: vec![],
@@ -13625,19 +13737,30 @@ impl CompletionProvider for Model<Project> {
cx: &mut ViewContext<Editor>,
) -> Task<Result<bool>> {
self.update(cx, |project, cx| {
project.resolve_completions(buffer, completion_indices, completions, cx)
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
})
})
}
fn apply_additional_edits_for_completion(
&self,
buffer: Model<Buffer>,
completion: Completion,
completions: Rc<RefCell<Box<[Completion]>>>,
completion_index: usize,
push_to_history: bool,
cx: &mut ViewContext<Editor>,
) -> Task<Result<Option<language::Transaction>>> {
self.update(cx, |project, cx| {
project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.apply_additional_edits_for_completion(
buffer,
completions,
completion_index,
push_to_history,
cx,
)
})
})
}
@@ -13772,7 +13895,7 @@ impl SemanticsProvider for Model<Project> {
fn inlay_hint_settings(
location: Anchor,
snapshot: &MultiBufferSnapshot,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name());
@@ -14594,6 +14717,7 @@ pub fn diagnostic_block_renderer(
fn inline_completion_edit_text(
editor_snapshot: &EditorSnapshot,
edits: &Vec<(Range<Anchor>, String)>,
include_deletions: bool,
cx: &WindowContext,
) -> InlineCompletionText {
let edit_start = edits
@@ -14617,12 +14741,24 @@ fn inline_completion_edit_text(
offset = old_offset_range.end;
let start = text.len();
text.push_str(new_text);
let color = if include_deletions && new_text.is_empty() {
text.extend(
editor_snapshot
.buffer_snapshot
.chunks(old_offset_range.start..offset, false)
.map(|chunk| chunk.text),
);
cx.theme().status().deleted_background
} else {
text.push_str(new_text);
cx.theme().status().created_background
};
let end = text.len();
highlights.push((
start..end,
HighlightStyle {
background_color: Some(cx.theme().status().created_background),
background_color: Some(color),
..Default::default()
},
));

View File

@@ -35,6 +35,7 @@ pub struct EditorSettings {
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
pub jupyter: Jupyter,
pub show_inline_completions_in_menu: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -104,7 +105,7 @@ pub struct Scrollbar {
pub git_diff: bool,
pub selected_symbol: bool,
pub search_results: bool,
pub diagnostics: bool,
pub diagnostics: ScrollbarDiagnostics,
pub cursors: bool,
pub axes: ScrollbarAxes,
}
@@ -149,6 +150,73 @@ pub struct ScrollbarAxes {
pub vertical: bool,
}
/// Which diagnostic indicators to show in the scrollbar.
///
/// Default: all
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ScrollbarDiagnostics {
/// Show all diagnostic levels: hint, information, warnings, error.
All,
/// Show only the following diagnostic levels: information, warning, error.
Information,
/// Show only the following diagnostic levels: warning, error.
Warning,
/// Show only the following diagnostic level: error.
Error,
/// Do not show diagnostics.
None,
}
impl<'de> Deserialize<'de> for ScrollbarDiagnostics {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = ScrollbarDiagnostics;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
r#"a boolean or one of "all", "information", "warning", "error", "none""#
)
}
fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match b {
false => Ok(ScrollbarDiagnostics::None),
true => Ok(ScrollbarDiagnostics::All),
}
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match s {
"all" => Ok(ScrollbarDiagnostics::All),
"information" => Ok(ScrollbarDiagnostics::Information),
"warning" => Ok(ScrollbarDiagnostics::Warning),
"error" => Ok(ScrollbarDiagnostics::Error),
"none" => Ok(ScrollbarDiagnostics::None),
_ => Err(E::unknown_variant(
s,
&["all", "information", "warning", "error", "none"],
)),
}
}
}
deserializer.deserialize_any(Visitor)
}
}
/// The key to use for adding multiple cursors
///
/// Default: alt
@@ -300,6 +368,12 @@ pub struct EditorSettingsContent {
/// Default: false
pub show_signature_help_after_edits: Option<bool>,
/// Whether to show the inline completions next to the completions provided by a language server.
/// Only has an effect if inline completion provider supports it.
///
/// Default: true
pub show_inline_completions_in_menu: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<JupyterContent>,
}
@@ -341,10 +415,10 @@ pub struct ScrollbarContent {
///
/// Default: true
pub selected_symbol: Option<bool>,
/// Whether to show diagnostic indicators in the scrollbar.
/// Which diagnostic indicators to show in the scrollbar:
///
/// Default: true
pub diagnostics: Option<bool>,
/// Default: all
pub diagnostics: Option<ScrollbarDiagnostics>,
/// Whether to show cursor positions in the scrollbar.
///
/// Default: true

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