Compare commits

..

210 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
340 changed files with 10904 additions and 5904 deletions

View File

@@ -29,15 +29,23 @@ jobs:
outputs:
docs_only: ${{ steps.check_changes.outputs.docs_only }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Check for non-docs changes
id: check_changes
run: |
if git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
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
@@ -175,7 +183,7 @@ jobs:
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -216,7 +224,7 @@ jobs:
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -247,7 +255,7 @@ jobs:
- name: Cache dependencies
if: needs.check_docs_only.outputs.docs_only == 'false'
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
@@ -314,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

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

576
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",
@@ -355,9 +352,9 @@ 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 = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
blade-util = { git = "https://github.com/kvark/blade", rev = "099555282605c7c4cca9e66a8f40148298347f80" }
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"
@@ -392,7 +389,7 @@ 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,7 +398,7 @@ 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"
@@ -412,12 +409,13 @@ 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"
@@ -443,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"] }
@@ -465,7 +464,7 @@ 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"
@@ -524,12 +523,7 @@ wasmtime-wasi = "24"
which = "6.0.0"
wit-component = "0.201"
zstd = "0.11"
# Custom metal-rs is only needed for "macos-blade" feature of GPUI
#TODO: switch to crates once these are published:
# - https://github.com/gfx-rs/metal-rs/pull/335
# - https://github.com/gfx-rs/metal-rs/pull/336
# - https://github.com/gfx-rs/metal-rs/pull/337
metal = { git = "https://github.com/gfx-rs/metal-rs", rev = "ef768ff9d742ae6a0f4e83ddc8031264e7d460c4" }
metal = "0.30"
[workspace.dependencies.async-stripe]
git = "https://github.com/zed-industries/async-stripe"

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

@@ -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"
}
},
{
@@ -473,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",
@@ -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,

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

@@ -260,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",
@@ -397,6 +397,7 @@
"'": "vim::Quotes",
"`": "vim::BackQuotes",
"\"": "vim::DoubleQuotes",
"q": "vim::AnyQuotes",
"|": "vim::VerticalBars",
"(": "vim::Parentheses",
")": "vim::Parentheses",

View File

@@ -256,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.
@@ -496,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.
@@ -741,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
@@ -1103,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 {}
@@ -4966,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

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

@@ -50,7 +50,6 @@ parking_lot.workspace = true
picker.workspace = true
project.workspace = true
proto.workspace = true
release_channel.workspace = true
rope.workspace = true
schemars.workspace = true
serde.workspace = true

View File

@@ -4,8 +4,8 @@ use assistant_tool::ToolWorkingSet;
use collections::HashMap;
use gpui::{
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListState, Model, StyleRefinement, Subscription, TextStyleRefinement, View,
WeakView,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, UnderlineStyle, View, WeakView,
};
use language::LanguageRegistry;
use language_model::Role;
@@ -22,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>>,
@@ -137,7 +137,16 @@ impl ActiveThread {
inline_code: TextStyleRefinement {
font_family: Some(theme_settings.buffer_font.family.clone()),
font_size: Some(buffer_font_size.into()),
background_color: Some(colors.editor_foreground.opacity(0.01)),
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()
@@ -153,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(
@@ -269,14 +282,11 @@ impl ActiveThread {
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
.when_some(context, |parent, context| {
if !context.is_empty() {
parent.child(
h_flex()
.flex_wrap()
.gap_1()
.px_1p5()
.pb_1p5()
.children(context.iter().map(|c| ContextPill::new(c.clone()))),
)
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
}

View File

@@ -41,6 +41,7 @@ actions!(
NewThread,
ToggleContextPicker,
ToggleModelSelector,
RemoveAllContext,
OpenHistory,
Chat,
CycleNextInlineAssist,

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};
@@ -206,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));
@@ -286,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))

View File

@@ -257,17 +257,20 @@ impl CodegenAlternative {
) -> Self {
let snapshot = 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 = 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);
@@ -471,10 +474,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

@@ -17,11 +17,13 @@ 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,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContextKind {
File,
Directory,
@@ -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

@@ -10,7 +10,6 @@ use gpui::{
WeakModel, WeakView,
};
use picker::{Picker, PickerDelegate};
use release_channel::ReleaseChannel;
use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::Workspace;
@@ -57,16 +56,11 @@ impl ContextPicker {
kind: ContextKind::File,
icon: IconName::File,
});
let release_channel = ReleaseChannel::global(cx);
// The directory context picker isn't fully implemented yet, so limit it
// to development builds.
if release_channel == ReleaseChannel::Dev {
entries.push(ContextPickerEntry {
name: "Folder".into(),
kind: ContextKind::Directory,
icon: IconName::Folder,
});
}
entries.push(ContextPickerEntry {
name: "Folder".into(),
kind: ContextKind::Directory,
icon: IconName::Folder,
});
entries.push(ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,

View File

@@ -1,21 +1,18 @@
// TODO: Remove this when we finish the implementation.
#![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::ContextKind;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::context_store::{push_fenced_codeblock, ContextStore};
pub struct DirectoryContextPicker {
picker: View<Picker<DirectoryContextPickerDelegate>>,
@@ -190,22 +187,77 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
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();
// TODO: Add the files from the selected directory.
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_context(
ContextKind::Directory,
path.to_string_lossy().to_string(),
text,
);
.update(cx, |context_store, _cx| {
context_store.insert_directory(&path, text);
})?;
match confirm_behavior {
@@ -234,16 +286,49 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
&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 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))),
.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,7 +11,6 @@ use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, ViewContext};
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
@@ -176,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()
}
@@ -201,7 +200,9 @@ 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 {
@@ -230,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,5 +1,3 @@
use std::fmt::Write as _;
use std::ops::RangeInclusive;
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
@@ -7,14 +5,13 @@ 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::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
use crate::context_store::{ContextStore, IncludedFile};
pub struct FileContextPicker {
picker: View<Picker<FileContextPickerDelegate>>,
@@ -204,14 +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(());
};
@@ -222,20 +245,7 @@ impl PickerDelegate for FileContextPickerDelegate {
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');
}
text.push_str("```\n");
context_store.insert_context(
ContextKind::File,
path.to_string_lossy().to_string(),
text,
);
context_store.insert_file(buffer.read(cx));
})?;
match confirm_behavior {
@@ -264,7 +274,7 @@ 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];
@@ -292,39 +302,52 @@ impl PickerDelegate for FileContextPickerDelegate {
(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,7 +5,6 @@ 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::{ConfirmBehavior, ContextPicker};
use crate::context_store;
use crate::thread::ThreadId;
@@ -169,25 +168,11 @@ 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();
@@ -210,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(Label::new(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::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,31 +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(),
ConfirmBehavior::KeepOpen,
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()
@@ -59,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)
@@ -76,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,
}
}
}

View File

@@ -118,7 +118,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();
@@ -871,10 +871,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 snapshot = multibuffer.snapshot(cx);
let ranges = 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(

View File

@@ -2,11 +2,11 @@ use crate::assistant_model_selector::AssistantModelSelector;
use crate::buffer_codegen::BufferCodegen;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip;
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
use crate::{ToggleContextPicker, ToggleModelSelector};
use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
use client::ErrorExt;
use collections::VecDeque;
use editor::{
@@ -27,6 +27,7 @@ use settings::Settings;
use std::cmp;
use std::sync::Arc;
use theme::ThemeSettings;
use ui::utils::WithRemSize;
use ui::{
prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip,
};
@@ -36,6 +37,7 @@ use workspace::Workspace;
pub struct PromptEditor<T> {
pub editor: View<Editor>,
mode: PromptEditorMode,
context_store: Model<ContextStore>,
context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: View<AssistantModelSelector>,
@@ -46,6 +48,7 @@ pub struct PromptEditor<T> {
pending_prompt: String,
_codegen_subscription: Subscription,
editor_subscriptions: Vec<Subscription>,
_context_strip_subscription: Subscription,
show_rate_limit_notice: bool,
_phantom: std::marker::PhantomData<T>,
}
@@ -54,9 +57,10 @@ impl<T: 'static> EventEmitter<PromptEditorEvent> for PromptEditor<T> {}
impl<T: 'static> Render for PromptEditor<T> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
let mut buttons = Vec::new();
let left_gutter_spacing = match &self.mode {
let left_gutter_width = match &self.mode {
PromptEditorMode::Buffer {
id: _,
codegen,
@@ -106,12 +110,17 @@ impl<T: 'static> Render for PromptEditor<T> {
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.on_action(cx.listener(Self::remove_all_context))
.capture_action(cx.listener(Self::cycle_prev))
.capture_action(cx.listener(Self::cycle_next))
.child(
h_flex()
WithRemSize::new(ui_font_size)
.flex()
.flex_row()
.flex_shrink_0()
.items_center()
.h_full()
.w(left_gutter_spacing)
.w(left_gutter_width)
.justify_center()
.gap_2()
.child(self.render_close_button(cx))
@@ -171,19 +180,31 @@ impl<T: 'static> Render for PromptEditor<T> {
.w_full()
.justify_between()
.child(div().flex_1().child(self.render_editor(cx)))
.child(h_flex().gap_1().children(buttons)),
.child(
WithRemSize::new(ui_font_size)
.flex()
.flex_row()
.items_center()
.gap_1()
.children(buttons),
),
),
)
.child(
h_flex().child(div().w(left_gutter_spacing)).child(
h_flex()
.w_full()
.pl_1()
.items_start()
.justify_between()
.child(self.context_strip.clone())
.child(self.model_selector.clone()),
),
WithRemSize::new(ui_font_size)
.flex()
.flex_row()
.items_center()
.child(h_flex().flex_shrink_0().w(left_gutter_width))
.child(
h_flex()
.w_full()
.pl_1()
.items_start()
.justify_between()
.child(self.context_strip.clone())
.child(self.model_selector.clone()),
),
)
}
}
@@ -320,6 +341,11 @@ impl<T: 'static> PromptEditor<T> {
self.model_selector_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 cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match self.codegen_status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
@@ -680,9 +706,10 @@ impl<T: 'static> PromptEditor<T> {
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
div()
.key_context("MessageEditor")
.key_context("InlineAssistEditor")
.size_full()
.p_2()
.pl_1()
.bg(cx.theme().colors().editor_background)
.child({
let settings = ThemeSettings::get_global(cx);
@@ -707,6 +734,16 @@ impl<T: 'static> PromptEditor<T> {
})
.into_any_element()
}
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);
}
}
pub enum PromptEditorMode {
@@ -784,18 +821,25 @@ impl PromptEditor<BufferCodegen> {
let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new_view(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
cx,
)
});
let context_strip_subscription =
cx.subscribe(&context_strip, Self::handle_context_strip_event);
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
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_store,
context_strip,
context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
@@ -807,6 +851,7 @@ impl PromptEditor<BufferCodegen> {
pending_prompt: String::new(),
_codegen_subscription: codegen_subscription,
editor_subscriptions: Vec::new(),
_context_strip_subscription: context_strip_subscription,
show_rate_limit_notice: false,
mode,
_phantom: Default::default(),
@@ -923,18 +968,25 @@ impl PromptEditor<TerminalCodegen> {
let context_picker_menu_handle = PopoverMenuHandle::default();
let model_selector_menu_handle = PopoverMenuHandle::default();
let context_strip = cx.new_view(|cx| {
ContextStrip::new(
context_store.clone(),
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
cx,
)
});
let context_strip_subscription =
cx.subscribe(&context_strip, Self::handle_context_strip_event);
let mut this = Self {
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_store,
context_strip,
context_picker_menu_handle,
model_selector: cx.new_view(|cx| {
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
@@ -946,6 +998,7 @@ impl PromptEditor<TerminalCodegen> {
pending_prompt: String::new(),
_codegen_subscription: codegen_subscription,
editor_subscriptions: Vec::new(),
_context_strip_subscription: context_strip_subscription,
mode,
show_rate_limit_notice: false,
_phantom: Default::default(),

View File

@@ -20,10 +20,10 @@ use workspace::Workspace;
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>,
@@ -54,11 +54,12 @@ impl MessageEditor {
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(10, cx);
editor.set_placeholder_text("Ask anything, @ to add context", 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(),
@@ -68,28 +69,33 @@ impl MessageEditor {
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,
inline_context_picker,
inline_context_picker_menu_handle,
@@ -110,6 +116,11 @@ impl MessageEditor {
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);
}
@@ -136,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);
@@ -194,6 +207,16 @@ impl MessageEditor {
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);
}
}
impl FocusableView for MessageEditor {
@@ -215,6 +238,7 @@ 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()
@@ -261,7 +285,7 @@ impl Render for MessageEditor {
.justify_between()
.child(SwitchWithLabel::new(
"use-tools",
Label::new("Tools"),
Label::new("Tools").size(LabelSize::Small),
self.use_tools.into(),
cx.listener(|this, selection, _cx| {
this.use_tools = match selection {
@@ -277,7 +301,7 @@ impl Render for MessageEditor {
ButtonLike::new("chat")
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface)
.child(Label::new("Submit"))
.child(Label::new("Submit").size(LabelSize::Small))
.children(
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
.map(|binding| binding.into_any_element()),

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,65 +1,153 @@
use std::rc::Rc;
use gpui::ClickEvent;
use ui::{prelude::*, IconButtonShape};
use ui::{prelude::*, IconButtonShape, Tooltip};
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 {
let padding_right = if self.on_remove.is_some() {
px(2.)
} else {
px(4.)
};
let icon = match self.context.kind {
let icon = match &self.kind() {
ContextKind::File => IconName::File,
ContextKind::Directory => IconName::Folder,
ContextKind::FetchedUrl => IconName::Globe,
ContextKind::Thread => IconName::MessageCircle,
};
h_flex()
.gap_1()
let color = cx.theme().colors();
let base_pill = h_flex()
.id(self.id())
.pl_1()
.pr(padding_right)
.pb(px(1.))
.border_1()
.border_color(cx.theme().colors().border.opacity(0.5))
.bg(cx.theme().colors().element_background)
.rounded_md()
.child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
.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

@@ -102,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);
}
}
})
@@ -112,14 +115,14 @@ impl Render for Breadcrumbs {
let focus_handle = editor.read(cx).focus_handle(cx);
Tooltip::for_action_in(
"Show Symbol Outline",
&editor::actions::ToggleOutline,
&zed_actions::outline::ToggleOutline,
&focus_handle,
cx,
)
} else {
Tooltip::for_action(
"Show Symbol Outline",
&editor::actions::ToggleOutline,
&zed_actions::outline::ToggleOutline,
cx,
)
}

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

@@ -79,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) => {

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

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

@@ -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,14 +1135,20 @@ impl Panel for ChatPanel {
}
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
match ChatPanelSettings::get_global(cx).button {
ChatPanelButton::Never => None,
ChatPanelButton::Always => Some(ui::IconName::MessageBubbles),
ChatPanelButton::WhenInCall => ActiveCall::global(cx)
.read(cx)
.room()
.map(|_| ui::IconName::MessageBubbles),
}
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> {
@@ -1159,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

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

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

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

@@ -63,6 +63,10 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
false
}
fn show_completions_in_normal_mode() -> bool {
false
}
fn is_enabled(
&self,
buffer: &Model<Buffer>,

View File

@@ -836,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()
@@ -880,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()
})
}

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

@@ -224,6 +224,7 @@ impl CompletionsMenu {
documentation: None,
lsp_completion: Default::default(),
confirm: None,
resolved: true,
})
.collect();
@@ -333,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);
@@ -340,9 +344,6 @@ impl CompletionsMenu {
}
}
.into();
if self.selected_item != 0 && self.selected_item + 1 < self.entries.len() {
self.selected_item += 1;
}
}
pub fn resolve_visible_completions(

View File

@@ -42,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};

View File

@@ -1757,6 +1757,7 @@ 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>,
}
@@ -2747,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(),
@@ -2993,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;
@@ -258,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),
}
@@ -992,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 {
@@ -3513,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()
@@ -3544,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)
@@ -3559,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,
),
))
@@ -3588,7 +3592,7 @@ impl Editor {
}
}
fn splice_inlays(
pub fn splice_inlays(
&self,
to_remove: Vec<InlayId>,
to_insert: Vec<Inlay>,
@@ -3830,8 +3834,11 @@ impl Editor {
};
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;
@@ -3975,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,
);
@@ -4874,7 +4883,7 @@ 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())
}
@@ -5087,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()
@@ -6009,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);
@@ -8936,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
@@ -9105,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)
})
}
@@ -9144,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()]);
});
}
@@ -9174,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
@@ -9207,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,
@@ -9248,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(
@@ -9277,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()
@@ -9484,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;
};
@@ -10284,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
});
@@ -10308,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| {
@@ -10318,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());
@@ -10338,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(
@@ -10370,7 +10398,6 @@ impl Editor {
is_valid: true,
})
});
self.active_diagnostics.is_some()
}
fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
@@ -11488,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))
@@ -11760,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,
@@ -12394,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)
}
@@ -12458,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());
@@ -12529,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) =
@@ -13452,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,
@@ -13615,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![],
@@ -13680,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,
)
})
})
}
@@ -13827,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());

View File

@@ -105,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,
}
@@ -150,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
@@ -348,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

View File

@@ -5962,8 +5962,8 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
.with_injection_query(
r#"
(script_element
(raw_text) @content
(#set! "language" "javascript"))
(raw_text) @injection.content
(#set! injection.language "javascript"))
"#,
)
.unwrap(),
@@ -8402,7 +8402,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
additional edit
"});
handle_resolve_completion_request(&mut cx, None).await;
apply_additional_edits.await.unwrap();
update_test_language_settings(&mut cx, |settings| {
@@ -9069,8 +9068,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
.with_injection_query(
r#"
(script_element
(raw_text) @content
(#set! "language" "javascript"))
(raw_text) @injection.content
(#set! injection.language "javascript"))
"#,
)
.unwrap(),
@@ -10698,10 +10697,14 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
..lsp::CompletionItem::default()
};
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let item1 = item1.clone();
cx.handle_request::<lsp::request::Completion, _, _>({
let item1 = item1.clone();
let item2 = item2.clone();
async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
move |_, _, _| {
let item1 = item1.clone();
let item2 = item2.clone();
async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
}
})
.next()
.await;
@@ -10728,43 +10731,41 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
}
});
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
Ok(lsp::CompletionItem {
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
let item1 = item1.clone();
move |_, item_to_resolve, _| {
let item1 = item1.clone();
async move {
if item1 == item_to_resolve {
Ok(lsp::CompletionItem {
label: "method id()".to_string(),
filter_text: Some("id".to_string()),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(0, 22),
lsp::Position::new(0, 22),
),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
} else {
Ok(item_to_resolve)
}
}
}
})
.next()
.await;
.await
.unwrap();
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&Default::default(), cx);
});
cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
Ok(lsp::CompletionItem {
label: "invalid changed label".to_string(),
detail: Some("Now resolved!".to_string()),
documentation: Some(lsp::Documentation::String("Docs".to_string())),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
})
})
.next()
.await;
cx.run_until_parked();
cx.update_editor(|editor, _| {
let context_menu = editor.context_menu.borrow_mut();
let context_menu = context_menu
@@ -10787,6 +10788,172 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
});
}
#[gpui::test]
async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
resolve_provider: Some(true),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
cx.simulate_keystroke(".");
let unresolved_item_1 = lsp::CompletionItem {
label: "id".to_string(),
filter_text: Some("id".to_string()),
detail: None,
documentation: None,
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".id".to_string(),
})),
..lsp::CompletionItem::default()
};
let resolved_item_1 = lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
new_text: "!!".to_string(),
}]),
..unresolved_item_1.clone()
};
let unresolved_item_2 = lsp::CompletionItem {
label: "other".to_string(),
filter_text: Some("other".to_string()),
detail: None,
documentation: None,
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
new_text: ".other".to_string(),
})),
..lsp::CompletionItem::default()
};
let resolved_item_2 = lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
new_text: "??".to_string(),
}]),
..unresolved_item_2.clone()
};
let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
cx.lsp
.server
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
let unresolved_item_1 = unresolved_item_1.clone();
let resolved_item_1 = resolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
let resolved_item_2 = resolved_item_2.clone();
let resolve_requests_1 = resolve_requests_1.clone();
let resolve_requests_2 = resolve_requests_2.clone();
move |unresolved_request, _| {
let unresolved_item_1 = unresolved_item_1.clone();
let resolved_item_1 = resolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
let resolved_item_2 = resolved_item_2.clone();
let resolve_requests_1 = resolve_requests_1.clone();
let resolve_requests_2 = resolve_requests_2.clone();
async move {
if unresolved_request == unresolved_item_1 {
resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
Ok(resolved_item_1.clone())
} else if unresolved_request == unresolved_item_2 {
resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
Ok(resolved_item_2.clone())
} else {
panic!("Unexpected completion item {unresolved_request:?}")
}
}
}
})
.detach();
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
let unresolved_item_1 = unresolved_item_1.clone();
let unresolved_item_2 = unresolved_item_2.clone();
async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
unresolved_item_1,
unresolved_item_2,
])))
}
})
.next()
.await;
cx.condition(|editor, _| editor.context_menu_visible())
.await;
cx.update_editor(|editor, _| {
let context_menu = editor.context_menu.borrow_mut();
let context_menu = context_menu
.as_ref()
.expect("Should have the context menu deployed");
match context_menu {
CodeContextMenu::Completions(completions_menu) => {
let completions = completions_menu.completions.borrow_mut();
assert_eq!(
completions
.iter()
.map(|completion| &completion.label.text)
.collect::<Vec<_>>(),
vec!["id", "other"]
)
}
CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
}
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&ContextMenuNext, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_prev(&ContextMenuPrev, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor.context_menu_next(&ContextMenuNext, cx);
});
cx.run_until_parked();
cx.update_editor(|editor, cx| {
editor
.compose_completion(&ComposeCompletion::default(), cx)
.expect("No task returned")
})
.await
.expect("Completion failed");
cx.run_until_parked();
cx.update_editor(|editor, cx| {
assert_eq!(
resolve_requests_1.load(atomic::Ordering::Acquire),
1,
"Should always resolve once despite multiple selections"
);
assert_eq!(
resolve_requests_2.load(atomic::Ordering::Acquire),
1,
"Should always resolve once after multiple selections and applying the completion"
);
assert_eq!(
editor.text(cx),
"fn main() { let a = ??.other; }",
"Should use resolved data when applying the completion"
);
});
}
#[gpui::test]
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -10950,15 +11117,10 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
// Completions that have already been resolved are skipped.
assert_eq!(
*resolved_items.lock(),
[
// Selected item is always resolved even if it was resolved before.
&items_out[items_out.len() - 1..items_out.len()],
&items_out[items_out.len() - 16..items_out.len() - 4]
]
.concat()
.iter()
.cloned()
.collect::<Vec<lsp::CompletionItem>>()
items_out[items_out.len() - 16..items_out.len() - 4]
.iter()
.cloned()
.collect::<Vec<lsp::CompletionItem>>()
);
resolved_items.lock().clear();
}
@@ -11098,7 +11260,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
},
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
)));
update_test_language_settings(cx, |settings| {
settings.defaults.prettier = Some(PrettierSettings {
@@ -14570,6 +14732,62 @@ fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
}
}
#[gpui::test]
async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
cx.set_state(indoc! {"
struct Fˇoo {}
"});
cx.update_editor(|editor, cx| {
let highlight_range = Point::new(0, 7)..Point::new(0, 10);
let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
editor.highlight_background::<DocumentHighlightRead>(
&[highlight_range],
|c| c.editor_document_highlight_read_background,
cx,
);
});
cx.update_editor(|e, cx| e.rename(&Rename, cx))
.expect("Rename was not started")
.await
.expect("Rename failed");
let mut rename_handler =
cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
let edit = lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 7,
},
end: lsp::Position {
line: 0,
character: 10,
},
},
new_text: "FooRenamed".to_string(),
};
Ok(Some(lsp::WorkspaceEdit::new(
// Specify the same edit twice
std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
)))
});
cx.update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
.expect("Confirm rename was not started")
.await
.expect("Confirm rename failed");
rename_handler.next().await.unwrap();
cx.run_until_parked();
// Despite two edits, only one is actually applied as those are identical
cx.assert_editor_state(indoc! {"
struct FooRenamedˇ {}
"});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point

File diff suppressed because it is too large Load Diff

View File

@@ -194,14 +194,25 @@ impl ProjectDiffEditor {
let open_tasks = project
.update(&mut cx, |project, cx| {
let worktree = project.worktree_for_id(id, cx)?;
let applicable_entries = worktree
.read(cx)
.entries(false, 0)
.filter(|entry| !entry.is_external)
.filter(|entry| entry.is_file())
.filter_map(|entry| Some((entry.git_status?, entry)))
.filter_map(|(git_status, entry)| {
Some((git_status, entry.id, project.path_for_entry(entry.id, cx)?))
let snapshot = worktree.read(cx).snapshot();
let applicable_entries = snapshot
.repositories()
.iter()
.flat_map(|entry| {
entry.status().map(|git_entry| {
(git_entry.combined_status(), entry.join(git_entry.repo_path))
})
})
.filter_map(|(status, path)| {
let id = snapshot.entry_for_path(&path)?.id;
Some((
status,
id,
ProjectPath {
worktree_id: snapshot.id(),
path: path.into(),
},
))
})
.collect::<Vec<_>>();
Some(

View File

@@ -266,7 +266,7 @@ pub fn update_inlay_link_and_hover_points(
editor: &mut Editor,
secondary_held: bool,
shift_held: bool,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) {
let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))

View File

@@ -3,7 +3,7 @@ use crate::{
hover_links::{InlayHighlight, RangeInEditor},
scroll::ScrollAmount,
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
Hover, RangeToAnchorExt,
Hover,
};
use gpui::{
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
@@ -11,11 +11,11 @@ use gpui::{
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
};
use itertools::Itertools;
use language::{Diagnostic, DiagnosticEntry, Language, LanguageRegistry};
use language::{DiagnosticEntry, Language, LanguageRegistry};
use lsp::DiagnosticSeverity;
use markdown::{Markdown, MarkdownStyle};
use multi_buffer::ToOffset;
use project::{HoverBlock, InlayHintLabelPart};
use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
use settings::Settings;
use std::rc::Rc;
use std::{borrow::Cow, cell::RefCell};
@@ -263,64 +263,15 @@ fn show_hover(
delay.await;
}
// If there's a diagnostic, assign it on the hover state and notify
let mut local_diagnostic = snapshot
let local_diagnostic = snapshot
.buffer_snapshot
.diagnostics_in_range::<_, usize>(anchor..anchor, false)
.diagnostics_in_range(anchor..anchor, false)
// Find the entry with the most specific range
.min_by_key(|entry| entry.range.end - entry.range.start)
.map(|entry| DiagnosticEntry {
diagnostic: entry.diagnostic,
range: entry.range.to_anchors(&snapshot.buffer_snapshot),
.min_by_key(|entry| {
let range = entry.range.to_offset(&snapshot.buffer_snapshot);
range.end - range.start
});
// Pull the primary diagnostic out so we can jump to it if the popover is clicked
let primary_diagnostic = local_diagnostic.as_ref().and_then(|local_diagnostic| {
snapshot
.buffer_snapshot
.diagnostic_group::<usize>(local_diagnostic.diagnostic.group_id)
.find(|diagnostic| diagnostic.diagnostic.is_primary)
.map(|entry| DiagnosticEntry {
diagnostic: entry.diagnostic,
range: entry.range.to_anchors(&snapshot.buffer_snapshot),
})
});
if let Some(invisible) = snapshot
.buffer_snapshot
.chars_at(anchor)
.next()
.filter(|&c| is_invisible(c))
{
let after = snapshot.buffer_snapshot.anchor_after(
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
);
local_diagnostic = Some(DiagnosticEntry {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: format!("Unicode character U+{:02X}", invisible as u32),
..Default::default()
},
range: anchor..after,
})
} else if let Some(invisible) = snapshot
.buffer_snapshot
.reversed_chars_at(anchor)
.next()
.filter(|&c| is_invisible(c))
{
let before = snapshot.buffer_snapshot.anchor_before(
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
);
local_diagnostic = Some(DiagnosticEntry {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: format!("Unicode character U+{:02X}", invisible as u32),
..Default::default()
},
range: before..anchor,
})
}
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
let text = match local_diagnostic.diagnostic.source {
Some(ref source) => {
@@ -388,7 +339,6 @@ fn show_hover(
Some(DiagnosticPopover {
local_diagnostic,
primary_diagnostic,
parsed_content,
border_color,
background_color,
@@ -403,6 +353,31 @@ fn show_hover(
this.hover_state.diagnostic_popover = diagnostic_popover;
})?;
let invisible_char = if let Some(invisible) = snapshot
.buffer_snapshot
.chars_at(anchor)
.next()
.filter(|&c| is_invisible(c))
{
let after = snapshot.buffer_snapshot.anchor_after(
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
);
Some((invisible, anchor..after))
} else if let Some(invisible) = snapshot
.buffer_snapshot
.reversed_chars_at(anchor)
.next()
.filter(|&c| is_invisible(c))
{
let before = snapshot.buffer_snapshot.anchor_before(
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
);
Some((invisible, before..anchor))
} else {
None
};
let hovers_response = if let Some(hover_request) = hover_request {
hover_request.await
} else {
@@ -410,8 +385,26 @@ fn show_hover(
};
let snapshot = this.update(&mut cx, |this, cx| this.snapshot(cx))?;
let mut hover_highlights = Vec::with_capacity(hovers_response.len());
let mut info_popovers = Vec::with_capacity(hovers_response.len());
let mut info_popover_tasks = Vec::with_capacity(hovers_response.len());
let mut info_popovers = Vec::with_capacity(
hovers_response.len() + if invisible_char.is_some() { 1 } else { 0 },
);
if let Some((invisible, range)) = invisible_char {
let blocks = vec![HoverBlock {
text: format!("Unicode character U+{:02X}", invisible as u32),
kind: HoverBlockKind::PlainText,
}];
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
let scroll_handle = ScrollHandle::new();
info_popovers.push(InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
})
}
for hover_result in hovers_response {
// Create symbol range of anchors for highlighting and filtering of future requests.
@@ -424,7 +417,6 @@ fn show_hover(
let end = snapshot
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
Some(start..end)
})
.or_else(|| {
@@ -442,21 +434,15 @@ fn show_hover(
let parsed_content =
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
let scroll_handle = ScrollHandle::new();
info_popover_tasks.push((
range.clone(),
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
},
));
}
for (highlight_range, info_popover) in info_popover_tasks {
hover_highlights.push(highlight_range);
info_popovers.push(info_popover);
hover_highlights.push(range.clone());
info_popovers.push(InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
});
}
this.update(&mut cx, |editor, cx| {
@@ -746,6 +732,7 @@ impl InfoPopover {
cx.notify();
self.scroll_handle.set_offset(current);
}
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
div()
.occlude()
@@ -783,7 +770,6 @@ impl InfoPopover {
#[derive(Debug, Clone)]
pub struct DiagnosticPopover {
local_diagnostic: DiagnosticEntry<Anchor>,
primary_diagnostic: Option<DiagnosticEntry<Anchor>>,
parsed_content: Option<View<Markdown>>,
border_color: Option<Hsla>,
background_color: Option<Hsla>,
@@ -837,13 +823,8 @@ impl DiagnosticPopover {
diagnostic_div.into_any_element()
}
pub fn activation_info(&self) -> (usize, Anchor) {
let entry = self
.primary_diagnostic
.as_ref()
.unwrap_or(&self.local_diagnostic);
(entry.diagnostic.group_id, entry.range.start)
pub fn group_id(&self) -> usize {
self.local_diagnostic.diagnostic.group_id
}
}

View File

@@ -365,7 +365,7 @@ impl Editor {
&mut self,
diff_base_buffer: Option<Model<Buffer>>,
hunk: &HoveredHunk,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> Option<()> {
let buffer = self.buffer.clone();
let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -454,18 +454,21 @@ impl Editor {
fn apply_diff_hunks_in_range(
&mut self,
range: Range<Anchor>,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> Option<()> {
let (buffer, range, _) = self
.buffer
.read(cx)
.range_to_buffer_ranges(range, cx)
let multi_buffer = self.buffer.read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let (excerpt, range) = multi_buffer_snapshot
.range_to_buffer_ranges(range)
.into_iter()
.next()?;
buffer.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(vec![range], cx);
});
multi_buffer
.buffer(excerpt.buffer_id())
.unwrap()
.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(vec![range], cx);
});
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
@@ -530,7 +533,7 @@ impl Editor {
fn hunk_header_block(
&self,
hunk: &HoveredHunk,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> BlockProperties<Anchor> {
let is_branch_buffer = self
.buffer
@@ -801,7 +804,7 @@ impl Editor {
hunk: &HoveredHunk,
diff_base_buffer: Model<Buffer>,
deleted_text_height: u32,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> BlockProperties<Anchor> {
let gutter_color = match hunk.status {
DiffHunkStatus::Added => unreachable!(),
@@ -864,7 +867,7 @@ impl Editor {
}
}
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<Editor>) -> bool {
if self.diff_map.expand_all {
return false;
}
@@ -887,7 +890,7 @@ impl Editor {
pub(super) fn sync_expanded_diff_hunks(
diff_map: &mut DiffMap,
buffer_id: BufferId,
cx: &mut ViewContext<'_, Self>,
cx: &mut ViewContext<Self>,
) {
let diff_base_state = diff_map.diff_bases.get_mut(&buffer_id);
let mut diff_base_buffer = None;
@@ -1134,7 +1137,7 @@ fn editor_with_deleted_text(
diff_base_buffer: Model<Buffer>,
deleted_color: Hsla,
hunk: &HoveredHunk,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> (u32, View<Editor>) {
let parent_editor = cx.view().downgrade();
let editor = cx.new_view(|cx| {

View File

@@ -36,6 +36,7 @@ pub struct InlayHintCache {
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
version: usize,
pub(super) enabled: bool,
enabled_in_settings: bool,
update_tasks: HashMap<ExcerptId, TasksForRanges>,
refresh_task: Option<Task<()>>,
invalidate_debounce: Option<Duration>,
@@ -268,6 +269,7 @@ impl InlayHintCache {
Self {
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
enabled: inlay_hint_settings.enabled,
enabled_in_settings: inlay_hint_settings.enabled,
hints: HashMap::default(),
update_tasks: HashMap::default(),
refresh_task: None,
@@ -288,10 +290,21 @@ impl InlayHintCache {
visible_hints: Vec<Inlay>,
cx: &mut ViewContext<Editor>,
) -> ControlFlow<Option<InlaySplice>> {
let old_enabled = self.enabled;
// If the setting for inlay hints has changed, update `enabled`. This condition avoids inlay
// hint visibility changes when other settings change (such as theme).
//
// Another option might be to store whether the user has manually toggled inlay hint
// visibility, and prefer this. This could lead to confusion as it means inlay hint
// visibility would not change when updating the setting if they were ever toggled.
if new_hint_settings.enabled != self.enabled_in_settings {
self.enabled = new_hint_settings.enabled;
};
self.enabled_in_settings = new_hint_settings.enabled;
self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms);
self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
match (self.enabled, new_hint_settings.enabled) {
match (old_enabled, self.enabled) {
(false, false) => {
self.allowed_hint_kinds = new_allowed_hint_kinds;
ControlFlow::Break(None)
@@ -314,7 +327,6 @@ impl InlayHintCache {
}
}
(true, false) => {
self.enabled = new_hint_settings.enabled;
self.allowed_hint_kinds = new_allowed_hint_kinds;
if self.hints.is_empty() {
ControlFlow::Break(None)
@@ -327,7 +339,6 @@ impl InlayHintCache {
}
}
(false, true) => {
self.enabled = new_hint_settings.enabled;
self.allowed_hint_kinds = new_allowed_hint_kinds;
ControlFlow::Continue(())
}
@@ -579,7 +590,7 @@ impl InlayHintCache {
buffer_id: BufferId,
excerpt_id: ExcerptId,
id: InlayId,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) {
if let Some(excerpt_hints) = self.hints.get(&excerpt_id) {
let mut guard = excerpt_hints.write();
@@ -640,7 +651,7 @@ fn spawn_new_update_tasks(
excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
update_cache_version: usize,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) {
for (excerpt_id, (excerpt_buffer, new_task_buffer_version, excerpt_visible_range)) in
excerpts_to_query
@@ -797,7 +808,7 @@ fn new_update_task(
query: ExcerptQuery,
query_ranges: QueryRanges,
excerpt_buffer: Model<Buffer>,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) -> Task<()> {
cx.spawn(move |editor, mut cx| async move {
let visible_range_update_results = future::join_all(
@@ -1129,7 +1140,7 @@ fn apply_hint_update(
invalidate: bool,
buffer_snapshot: BufferSnapshot,
multi_buffer_snapshot: MultiBufferSnapshot,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) {
let cached_excerpt_hints = editor
.inlay_hint_cache
@@ -3434,7 +3445,7 @@ pub mod tests {
labels
}
pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, Editor>) -> Vec<String> {
pub fn visible_hint_labels(editor: &Editor, cx: &ViewContext<Editor>) -> Vec<String> {
let mut hints = editor
.visible_inlay_hints(cx)
.into_iter()

View File

@@ -325,6 +325,10 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
false
}
fn show_completions_in_normal_mode() -> bool {
false
}
fn is_enabled(
&self,
_buffer: &gpui::Model<language::Buffer>,

View File

@@ -615,9 +615,20 @@ impl Item for Editor {
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).project_path(cx))
.and_then(|path| self.project.as_ref()?.read(cx).entry_for_path(&path, cx))
.map(|entry| {
entry_git_aware_label_color(entry.git_status, entry.is_ignored, params.selected)
.and_then(|path| {
let project = self.project.as_ref()?.read(cx);
let entry = project.entry_for_path(&path, cx)?;
let git_status = project
.worktree_for_id(path.worktree_id, cx)?
.read(cx)
.snapshot()
.status_for_file(path.path);
Some(entry_git_aware_label_color(
git_status,
entry.is_ignored,
params.selected,
))
})
.unwrap_or_else(|| entry_label_color(params.selected))
} else {
@@ -1559,10 +1570,10 @@ pub fn entry_git_aware_label_color(
Color::Ignored
} else {
match git_status {
Some(GitFileStatus::Added) => Color::Created,
Some(GitFileStatus::Added) | Some(GitFileStatus::Untracked) => Color::Created,
Some(GitFileStatus::Modified) => Color::Modified,
Some(GitFileStatus::Conflict) => Color::Conflict,
None => entry_label_color(selected),
Some(GitFileStatus::Deleted) | None => entry_label_color(selected),
}
}
}

View File

@@ -288,7 +288,7 @@ impl EventEmitter<EditorEvent> for ProposedChangesEditor {}
impl Item for ProposedChangesEditor {
type Event = EditorEvent;
fn tab_icon(&self, _cx: &ui::WindowContext) -> Option<Icon> {
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
Some(Icon::new(IconName::Diff))
}

View File

@@ -33,7 +33,7 @@ pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
pub fn expand_macro_recursively(
editor: &mut Editor,
_: &ExpandMacroRecursively,
cx: &mut ViewContext<'_, Editor>,
cx: &mut ViewContext<Editor>,
) {
if editor.selections.count() == 0 {
return;
@@ -98,7 +98,7 @@ pub fn expand_macro_recursively(
.detach_and_log_err(cx);
}
pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<'_, Editor>) {
pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<Editor>) {
if editor.selections.count() == 0 {
return;
}

View File

@@ -4,7 +4,7 @@ use crate::{
ScrollAnchor, ScrollCursorBottom, ScrollCursorCenter, ScrollCursorCenterTopBottom,
ScrollCursorTop, SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT,
};
use gpui::{Point, ViewContext};
use gpui::{AsyncWindowContext, Point, ViewContext};
impl Editor {
pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
@@ -75,7 +75,7 @@ impl Editor {
self.next_scroll_position = self.next_scroll_position.next();
self._scroll_cursor_center_top_bottom_task =
cx.spawn(|editor, mut cx: gpui::AsyncWindowContext| async move {
cx.spawn(|editor, mut cx: AsyncWindowContext| async move {
cx.background_executor()
.timer(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT)
.await;

View File

@@ -8,7 +8,7 @@ use workspace::Workspace;
fn task_context_with_editor(
editor: &mut Editor,
cx: &mut WindowContext<'_>,
cx: &mut WindowContext,
) -> AsyncTask<Option<TaskContext>> {
let Some(project) = editor.project.clone() else {
return AsyncTask::ready(None);
@@ -74,7 +74,7 @@ fn task_context_with_editor(
})
}
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> AsyncTask<TaskContext> {
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext) -> AsyncTask<TaskContext> {
let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))

View File

@@ -257,7 +257,8 @@ impl EditorLspTestContext {
Self::new(language, Default::default(), cx).await
}
// Constructs lsp range using a marked string with '[', ']' range delimiters
/// Constructs lsp range using a marked string with '[', ']' range delimiters
#[track_caller]
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
let ranges = self.ranges(marked_text);
self.to_lsp_range(ranges[0].clone())

View File

@@ -230,6 +230,7 @@ impl EditorTestContext {
self.cx.background_executor.run_until_parked();
}
#[track_caller]
pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
assert_eq!(self.buffer_text(), unmarked_text);

View File

@@ -843,7 +843,7 @@ impl ExtensionsPage {
}
}
fn fetch_extensions_debounced(&mut self, cx: &mut ViewContext<'_, ExtensionsPage>) {
fn fetch_extensions_debounced(&mut self, cx: &mut ViewContext<ExtensionsPage>) {
self.extension_fetch_task = Some(cx.spawn(|this, mut cx| async move {
let search = this
.update(&mut cx, |this, cx| this.search_query(cx))

View File

@@ -1,11 +1,10 @@
use client::telemetry;
use gpui::Task;
use gpui::{Task, WindowContext};
use human_bytes::human_bytes;
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use serde::Serialize;
use std::{env, fmt::Display};
use sysinfo::{MemoryRefreshKind, RefreshKind, System};
use ui::WindowContext;
#[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs {

View File

@@ -884,7 +884,7 @@ impl FileFinderDelegate {
fn lookup_absolute_path(
&self,
query: FileSearchQuery,
cx: &mut ViewContext<'_, Picker<Self>>,
cx: &mut ViewContext<Picker<Self>>,
) -> Task<()> {
cx.spawn(|picker, mut cx| async move {
let Some(project) = picker

View File

@@ -1,8 +1,8 @@
#[cfg(target_os = "macos")]
mod mac_watcher;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub mod linux_watcher;
#[cfg(not(target_os = "macos"))]
pub mod fs_watcher;
use anyhow::{anyhow, Result};
use git::GitHostingProviderRegistry;
@@ -11,8 +11,7 @@ use git::GitHostingProviderRegistry;
use ashpd::desktop::trash;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use smol::process::Command;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use std::fs::File;
#[cfg(unix)]
use std::os::fd::AsFd;
#[cfg(unix)]
@@ -445,7 +444,13 @@ impl Fs for RealFs {
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
async fn trash_file(&self, path: &Path, _options: RemoveOptions) -> Result<()> {
let file = File::open(path)?;
if let Ok(Some(metadata)) = self.metadata(path).await {
if metadata.is_symlink {
// TODO: trash_file does not support trashing symlinks yet - https://github.com/bilelmoussaoui/ashpd/issues/255
return self.remove_file(path, RemoveOptions::default()).await;
}
}
let file = smol::fs::File::open(path).await?;
match trash::trash_file(&file.as_fd()).await {
Ok(_) => Ok(()),
Err(err) => Err(anyhow::Error::new(err)),
@@ -695,7 +700,7 @@ impl Fs for RealFs {
)
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
#[cfg(not(target_os = "macos"))]
async fn watch(
&self,
path: &Path,
@@ -705,10 +710,11 @@ impl Fs for RealFs {
Arc<dyn Watcher>,
) {
use parking_lot::Mutex;
use util::paths::SanitizedPath;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
if watcher.add(path).is_err() {
// If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
@@ -720,7 +726,16 @@ impl Fs for RealFs {
}
// Check if path is a symlink and follow the target parent
if let Some(target) = self.read_link(&path).await.ok() {
if let Some(mut target) = self.read_link(&path).await.ok() {
// Check if symlink target is relative path, if so make it absolute
if target.is_relative() {
if let Some(parent) = path.parent() {
target = parent.join(target);
if let Ok(canonical) = self.canonicalize(&target).await {
target = SanitizedPath::from(canonical).as_path().to_path_buf();
}
}
}
watcher.add(&target).ok();
if let Some(parent) = target.parent() {
watcher.add(parent).log_err();
@@ -744,56 +759,6 @@ impl Fs for RealFs {
)
}
#[cfg(target_os = "windows")]
async fn watch(
&self,
path: &Path,
_latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use notify::{EventKind, Watcher};
let (tx, rx) = smol::channel::unbounded();
let mut file_watcher = notify::recommended_watcher({
let tx = tx.clone();
move |event: Result<notify::Event, _>| {
if let Some(event) = event.log_err() {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
tx.try_send(
event
.paths
.into_iter()
.map(|path| PathEvent { path, kind })
.collect::<Vec<_>>(),
)
.ok();
}
}
})
.expect("Could not start file watcher");
file_watcher
.watch(path, notify::RecursiveMode::Recursive)
.log_err();
(
Box::pin(rx.chain(futures::stream::once(async move {
drop(file_watcher);
vec![]
}))),
Arc::new(RealWatcher {}),
)
}
fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<dyn GitRepository>> {
// with libgit2, we can open git repo from an existing work dir
// https://libgit2.org/docs/reference/main/repository/git_repository_open.html

View File

@@ -5,12 +5,12 @@ use util::ResultExt;
use crate::{PathEvent, PathEventKind, Watcher};
pub struct LinuxWatcher {
pub struct FsWatcher {
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
}
impl LinuxWatcher {
impl FsWatcher {
pub fn new(
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
@@ -22,7 +22,7 @@ impl LinuxWatcher {
}
}
impl Watcher for LinuxWatcher {
impl Watcher for FsWatcher {
fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
let root_path = path.to_path_buf();
@@ -69,7 +69,7 @@ impl Watcher for LinuxWatcher {
})?;
global(|g| {
g.inotify
g.watcher
.lock()
.watch(path, notify::RecursiveMode::NonRecursive)
})??;
@@ -79,16 +79,18 @@ impl Watcher for LinuxWatcher {
fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
use notify::Watcher;
Ok(global(|w| w.inotify.lock().unwatch(path))??)
Ok(global(|w| w.watcher.lock().unwatch(path))??)
}
}
pub struct GlobalWatcher {
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
// two mutexes because calling watcher.add triggers an watcher.event, which needs watchers.
#[cfg(target_os = "linux")]
pub(super) inotify: Mutex<notify::INotifyWatcher>,
pub(super) watcher: Mutex<notify::INotifyWatcher>,
#[cfg(target_os = "freebsd")]
pub(super) inotify: Mutex<notify::KqueueWatcher>,
pub(super) watcher: Mutex<notify::KqueueWatcher>,
#[cfg(target_os = "windows")]
pub(super) watcher: Mutex<notify::ReadDirectoryChangesWatcher>,
pub(super) watchers: Mutex<Vec<Box<dyn Fn(&notify::Event) + Send + Sync>>>,
}
@@ -98,7 +100,8 @@ impl GlobalWatcher {
}
}
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
static FS_WATCHER_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
OnceLock::new();
fn handle_event(event: Result<notify::Event, notify::Error>) {
let Some(event) = event.log_err() else { return };
@@ -111,9 +114,9 @@ fn handle_event(event: Result<notify::Event, notify::Error>) {
}
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
let result = INOTIFY_INSTANCE.get_or_init(|| {
let result = FS_WATCHER_INSTANCE.get_or_init(|| {
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
inotify: Mutex::new(file_watcher),
watcher: Mutex::new(file_watcher),
watchers: Default::default(),
})
});

View File

@@ -14,7 +14,6 @@ pub struct Matcher<'a> {
lowercase_query: &'a [char],
query_char_bag: CharBag,
smart_case: bool,
max_results: usize,
min_score: f64,
match_positions: Vec<usize>,
last_positions: Vec<usize>,
@@ -22,11 +21,6 @@ pub struct Matcher<'a> {
best_position_matrix: Vec<usize>,
}
pub trait Match: Ord {
fn score(&self) -> f64;
fn set_positions(&mut self, positions: Vec<usize>);
}
pub trait MatchCandidate {
fn has_chars(&self, bag: CharBag) -> bool;
fn to_string(&self) -> Cow<'_, str>;
@@ -38,7 +32,6 @@ impl<'a> Matcher<'a> {
lowercase_query: &'a [char],
query_char_bag: CharBag,
smart_case: bool,
max_results: usize,
) -> Self {
Self {
query,
@@ -50,10 +43,11 @@ impl<'a> Matcher<'a> {
score_matrix: Vec::new(),
best_position_matrix: Vec::new(),
smart_case,
max_results,
}
}
/// Filter and score fuzzy match candidates. Results are returned unsorted, in the same order as
/// the input candidates.
pub fn match_candidates<C: MatchCandidate, R, F>(
&mut self,
prefix: &[char],
@@ -63,8 +57,7 @@ impl<'a> Matcher<'a> {
cancel_flag: &AtomicBool,
build_match: F,
) where
R: Match,
F: Fn(&C, f64) -> R,
F: Fn(&C, f64, &Vec<usize>) -> R,
{
let mut candidate_chars = Vec::new();
let mut lowercase_candidate_chars = Vec::new();
@@ -103,20 +96,7 @@ impl<'a> Matcher<'a> {
);
if score > 0.0 {
let mut mat = build_match(&candidate, score);
if let Err(i) = results.binary_search_by(|m| mat.cmp(m)) {
if results.len() < self.max_results {
mat.set_positions(self.match_positions.clone());
results.insert(i, mat);
} else if i < results.len() {
results.pop();
mat.set_positions(self.match_positions.clone());
results.insert(i, mat);
}
if results.len() == self.max_results {
self.min_score = results.last().unwrap().score();
}
}
results.push(build_match(&candidate, score, &self.match_positions));
}
}
}
@@ -325,18 +305,18 @@ mod tests {
#[test]
fn test_get_last_positions() {
let mut query: &[char] = &['d', 'c'];
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let mut matcher = Matcher::new(query, query, query.into(), false);
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
assert!(!result);
query = &['c', 'd'];
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let mut matcher = Matcher::new(query, query, query.into(), false);
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
assert!(result);
assert_eq!(matcher.last_positions, vec![2, 4]);
query = &['z', '/', 'z', 'f'];
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let mut matcher = Matcher::new(query, query, query.into(), false);
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
assert!(result);
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
@@ -451,7 +431,7 @@ mod tests {
});
}
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, 100);
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case);
let cancel_flag = AtomicBool::new(false);
let mut results = Vec::new();
@@ -462,16 +442,17 @@ mod tests {
path_entries.into_iter(),
&mut results,
&cancel_flag,
|candidate, score| PathMatch {
|candidate, score, positions| PathMatch {
score,
worktree_id: 0,
positions: Vec::new(),
positions: positions.clone(),
path: Arc::from(candidate.path),
path_prefix: "".into(),
distance_to_relative_ancestor: usize::MAX,
is_dir: false,
},
);
results.sort_by(|a, b| b.cmp(a));
results
.into_iter()

View File

@@ -3,11 +3,14 @@ use std::{
borrow::Cow,
cmp::{self, Ordering},
path::Path,
sync::{atomic::AtomicBool, Arc},
sync::{
atomic::{self, AtomicBool},
Arc,
},
};
use crate::{
matcher::{Match, MatchCandidate, Matcher},
matcher::{MatchCandidate, Matcher},
CharBag,
};
@@ -42,16 +45,6 @@ pub trait PathMatchCandidateSet<'a>: Send + Sync {
fn candidates(&'a self, start: usize) -> Self::Candidates;
}
impl Match for PathMatch {
fn score(&self) -> f64 {
self.score
}
fn set_positions(&mut self, positions: Vec<usize>) {
self.positions = positions;
}
}
impl<'a> MatchCandidate for PathMatchCandidate<'a> {
fn has_chars(&self, bag: CharBag) -> bool {
self.char_bag.is_superset(bag)
@@ -102,13 +95,7 @@ pub fn match_fixed_path_set(
let query = query.chars().collect::<Vec<_>>();
let query_char_bag = CharBag::from(&lowercase_query[..]);
let mut matcher = Matcher::new(
&query,
&lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut matcher = Matcher::new(&query, &lowercase_query, query_char_bag, smart_case);
let mut results = Vec::new();
matcher.match_candidates(
@@ -117,16 +104,17 @@ pub fn match_fixed_path_set(
candidates.into_iter(),
&mut results,
&AtomicBool::new(false),
|candidate, score| PathMatch {
|candidate, score, positions| PathMatch {
score,
worktree_id,
positions: Vec::new(),
positions: positions.clone(),
is_dir: candidate.is_dir,
path: Arc::from(candidate.path),
path_prefix: Arc::default(),
distance_to_relative_ancestor: usize::MAX,
},
);
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
results
}
@@ -164,16 +152,15 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
scope.spawn(async move {
let segment_start = segment_idx * segment_size;
let segment_end = segment_start + segment_size;
let mut matcher = Matcher::new(
query,
lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut matcher =
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
let mut tree_start = 0;
for candidate_set in candidate_sets {
if cancel_flag.load(atomic::Ordering::Relaxed) {
break;
}
let tree_end = tree_start + candidate_set.len();
if tree_start < segment_end && segment_start < tree_end {
@@ -193,10 +180,10 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
candidates,
results,
cancel_flag,
|candidate, score| PathMatch {
|candidate, score, positions| PathMatch {
score,
worktree_id,
positions: Vec::new(),
positions: positions.clone(),
path: Arc::from(candidate.path),
is_dir: candidate.is_dir,
path_prefix: candidate_set.prefix(),
@@ -222,14 +209,12 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
})
.await;
let mut results = Vec::new();
for segment_result in segment_results {
if results.is_empty() {
results = segment_result;
} else {
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
}
if cancel_flag.load(atomic::Ordering::Relaxed) {
return Vec::new();
}
let mut results = segment_results.concat();
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
results
}

View File

@@ -1,5 +1,5 @@
use crate::{
matcher::{Match, MatchCandidate, Matcher},
matcher::{MatchCandidate, Matcher},
CharBag,
};
use gpui::BackgroundExecutor;
@@ -8,7 +8,7 @@ use std::{
cmp::{self, Ordering},
iter,
ops::Range,
sync::atomic::AtomicBool,
sync::atomic::{self, AtomicBool},
};
#[derive(Clone, Debug)]
@@ -46,16 +46,6 @@ pub struct StringMatch {
pub string: String,
}
impl Match for StringMatch {
fn score(&self) -> f64 {
self.score
}
fn set_positions(&mut self, positions: Vec<usize>) {
self.positions = positions;
}
}
impl StringMatch {
pub fn ranges(&self) -> impl '_ + Iterator<Item = Range<usize>> {
let mut positions = self.positions.iter().peekable();
@@ -167,13 +157,8 @@ pub async fn match_strings(
scope.spawn(async move {
let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
let segment_end = cmp::min(segment_start + segment_size, candidates.len());
let mut matcher = Matcher::new(
query,
lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut matcher =
Matcher::new(query, lowercase_query, query_char_bag, smart_case);
matcher.match_candidates(
&[],
@@ -181,10 +166,10 @@ pub async fn match_strings(
candidates[segment_start..segment_end].iter(),
results,
cancel_flag,
|candidate, score| StringMatch {
|candidate, score, positions| StringMatch {
candidate_id: candidate.id,
score,
positions: Vec::new(),
positions: positions.clone(),
string: candidate.string.to_string(),
},
);
@@ -193,13 +178,11 @@ pub async fn match_strings(
})
.await;
let mut results = Vec::new();
for segment_result in segment_results {
if results.is_empty() {
results = segment_result;
} else {
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
}
if cancel_flag.load(atomic::Ordering::Relaxed) {
return Vec::new();
}
let mut results = segment_results.concat();
util::truncate_to_bottom_n_sorted_by(&mut results, max_results, &|a, b| b.cmp(a));
results
}

View File

@@ -16,6 +16,7 @@ use std::sync::LazyLock;
pub use crate::hosting_provider::*;
pub use crate::remote::*;
pub use git2 as libgit;
pub use repository::WORK_DIRECTORY_REPO_PATH;
pub static DOT_GIT: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new(".git"));
pub static COOKIES: LazyLock<&'static OsStr> = LazyLock::new(|| OsStr::new("cookies"));

View File

@@ -1,18 +1,22 @@
use crate::status::GitStatusPair;
use crate::GitHostingProviderRegistry;
use crate::{blame::Blame, status::GitStatus};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet};
use git2::BranchType;
use gpui::SharedString;
use parking_lot::Mutex;
use rope::Rope;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::sync::LazyLock;
use std::{
cmp::Ordering,
path::{Component, Path, PathBuf},
sync::Arc,
};
use sum_tree::MapSeekTarget;
use util::command::new_std_command;
use util::ResultExt;
#[derive(Clone, Debug, Hash, PartialEq)]
@@ -37,7 +41,8 @@ pub trait GitRepository: Send + Sync {
/// Returns the SHA of the current HEAD.
fn head_sha(&self) -> Option<String>;
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus>;
/// Returns the list of git statuses, sorted by path
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
fn branches(&self) -> Result<Vec<Branch>>;
fn change_branch(&self, _: &str) -> Result<()>;
@@ -48,6 +53,8 @@ pub trait GitRepository: Send + Sync {
/// Returns the path to the repository, typically the `.git` folder.
fn dot_git_dir(&self) -> PathBuf;
fn update_index(&self, stage: &[RepoPath], unstage: &[RepoPath]) -> Result<()>;
}
impl std::fmt::Debug for dyn GitRepository {
@@ -132,7 +139,7 @@ impl GitRepository for RealGitRepository {
Some(self.repository.lock().head().ok()?.target()?.to_string())
}
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
let working_directory = self
.repository
.lock()
@@ -149,7 +156,7 @@ impl GitRepository for RealGitRepository {
Ok(_) => Ok(true),
Err(e) => match e.code() {
git2::ErrorCode::NotFound => Ok(false),
_ => Err(anyhow::anyhow!(e)),
_ => Err(anyhow!(e)),
},
}
}
@@ -193,7 +200,7 @@ impl GitRepository for RealGitRepository {
repo.set_head(
revision
.name()
.ok_or_else(|| anyhow::anyhow!("Branch name could not be retrieved"))?,
.ok_or_else(|| anyhow!("Branch name could not be retrieved"))?,
)?;
Ok(())
}
@@ -225,6 +232,36 @@ impl GitRepository for RealGitRepository {
self.hosting_provider_registry.clone(),
)
}
fn update_index(&self, stage: &[RepoPath], unstage: &[RepoPath]) -> Result<()> {
let working_directory = self
.repository
.lock()
.workdir()
.context("failed to read git work directory")?
.to_path_buf();
if !stage.is_empty() {
let add = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["add", "--"])
.args(stage.iter().map(|p| p.as_ref()))
.status()?;
if !add.success() {
return Err(anyhow!("Failed to stage files: {add}"));
}
}
if !unstage.is_empty() {
let rm = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["restore", "--staged", "--"])
.args(unstage.iter().map(|p| p.as_ref()))
.status()?;
if !rm.success() {
return Err(anyhow!("Failed to unstage files: {rm}"));
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
@@ -289,23 +326,31 @@ impl GitRepository for FakeGitRepository {
state.dot_git_dir.clone()
}
fn status(&self, path_prefixes: &[PathBuf]) -> Result<GitStatus> {
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
let state = self.state.lock();
let mut entries = state
.worktree_statuses
.iter()
.filter_map(|(repo_path, status)| {
.filter_map(|(repo_path, status_worktree)| {
if path_prefixes
.iter()
.any(|path_prefix| repo_path.0.starts_with(path_prefix))
{
Some((repo_path.to_owned(), *status))
Some((
repo_path.to_owned(),
GitStatusPair {
index_status: None,
worktree_status: Some(*status_worktree),
},
))
} else {
None
}
})
.collect::<Vec<_>>();
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
Ok(GitStatus {
entries: entries.into(),
})
@@ -358,6 +403,10 @@ impl GitRepository for FakeGitRepository {
.with_context(|| format!("failed to get blame for {:?}", path))
.cloned()
}
fn update_index(&self, _stage: &[RepoPath], _unstage: &[RepoPath]) -> Result<()> {
unimplemented!()
}
}
fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
@@ -393,7 +442,10 @@ fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> {
pub enum GitFileStatus {
Added,
Modified,
// TODO conflicts should be represented by the GitStatusPair
Conflict,
Deleted,
Untracked,
}
impl GitFileStatus {
@@ -419,22 +471,52 @@ impl GitFileStatus {
_ => None,
}
}
pub fn from_byte(byte: u8) -> Option<Self> {
match byte {
b'M' => Some(GitFileStatus::Modified),
b'A' => Some(GitFileStatus::Added),
b'D' => Some(GitFileStatus::Deleted),
b'?' => Some(GitFileStatus::Untracked),
_ => None,
}
}
}
pub static WORK_DIRECTORY_REPO_PATH: LazyLock<RepoPath> =
LazyLock::new(|| RepoPath(Path::new("").into()));
#[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct RepoPath(pub PathBuf);
pub struct RepoPath(pub Arc<Path>);
impl RepoPath {
pub fn new(path: PathBuf) -> Self {
debug_assert!(path.is_relative(), "Repo paths must be relative");
RepoPath(path)
RepoPath(path.into())
}
pub fn from_str(path: &str) -> Self {
let path = Path::new(path);
debug_assert!(path.is_relative(), "Repo paths must be relative");
RepoPath(path.into())
}
pub fn to_proto(&self) -> String {
self.0.to_string_lossy().to_string()
}
}
impl std::fmt::Display for RepoPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.to_string_lossy().fmt(f)
}
}
impl From<&Path> for RepoPath {
fn from(value: &Path) -> Self {
RepoPath::new(value.to_path_buf())
RepoPath::new(value.into())
}
}
@@ -444,9 +526,15 @@ impl From<PathBuf> for RepoPath {
}
}
impl From<&str> for RepoPath {
fn from(value: &str) -> Self {
Self::from_str(value)
}
}
impl Default for RepoPath {
fn default() -> Self {
RepoPath(PathBuf::new())
RepoPath(Path::new("").into())
}
}
@@ -457,13 +545,19 @@ impl AsRef<Path> for RepoPath {
}
impl std::ops::Deref for RepoPath {
type Target = PathBuf;
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Borrow<Path> for RepoPath {
fn borrow(&self) -> &Path {
self.0.as_ref()
}
}
#[derive(Debug)]
pub struct RepoPathDescendants<'a>(pub &'a Path);

View File

@@ -1,21 +1,41 @@
use crate::repository::{GitFileStatus, RepoPath};
use anyhow::{anyhow, Result};
use std::{
path::{Path, PathBuf},
process::Stdio,
sync::Arc,
};
use std::{path::Path, process::Stdio, sync::Arc};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GitStatusPair {
// Not both `None`.
pub index_status: Option<GitFileStatus>,
pub worktree_status: Option<GitFileStatus>,
}
impl GitStatusPair {
pub fn is_staged(&self) -> Option<bool> {
match (self.index_status, self.worktree_status) {
(Some(_), None) => Some(true),
(None, Some(_)) => Some(false),
(Some(GitFileStatus::Untracked), Some(GitFileStatus::Untracked)) => Some(false),
(Some(_), Some(_)) => None,
(None, None) => unreachable!(),
}
}
// TODO reconsider uses of this
pub fn combined(&self) -> GitFileStatus {
self.index_status.or(self.worktree_status).unwrap()
}
}
#[derive(Clone)]
pub struct GitStatus {
pub entries: Arc<[(RepoPath, GitFileStatus)]>,
pub entries: Arc<[(RepoPath, GitStatusPair)]>,
}
impl GitStatus {
pub(crate) fn new(
git_binary: &Path,
working_directory: &Path,
path_prefixes: &[PathBuf],
path_prefixes: &[RepoPath],
) -> Result<Self> {
let child = util::command::new_std_command(git_binary)
.current_dir(working_directory)
@@ -24,10 +44,11 @@ impl GitStatus {
"status",
"--porcelain=v1",
"--untracked-files=all",
"--no-renames",
"-z",
])
.args(path_prefixes.iter().map(|path_prefix| {
if *path_prefix == Path::new("") {
if path_prefix.0.as_ref() == Path::new("") {
Path::new(".")
} else {
path_prefix
@@ -51,34 +72,32 @@ impl GitStatus {
let mut entries = stdout
.split('\0')
.filter_map(|entry| {
if entry.is_char_boundary(3) {
let (status, path) = entry.split_at(3);
let status = status.trim();
Some((
RepoPath(PathBuf::from(path)),
match status {
"A" | "??" => GitFileStatus::Added,
"M" => GitFileStatus::Modified,
_ => return None,
},
))
} else {
None
let sep = entry.get(2..3)?;
if sep != " " {
return None;
};
let path = &entry[3..];
let status = entry[0..2].as_bytes();
let index_status = GitFileStatus::from_byte(status[0]);
let worktree_status = GitFileStatus::from_byte(status[1]);
if (index_status, worktree_status) == (None, None) {
return None;
}
let path = RepoPath(Path::new(path).into());
Some((
path,
GitStatusPair {
index_status,
worktree_status,
},
))
})
.collect::<Vec<_>>();
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
entries.sort_unstable_by(|(a, _), (b, _)| a.cmp(&b));
Ok(Self {
entries: entries.into(),
})
}
pub fn get(&self, path: &Path) -> Option<GitFileStatus> {
self.entries
.binary_search_by(|(repo_path, _)| repo_path.0.as_path().cmp(path))
.ok()
.map(|index| self.entries[index].1)
}
}
impl Default for GitStatus {

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