Compare commits

..

77 Commits

Author SHA1 Message Date
Michael Sloan
2a55062dd1 Refactor multibuffer methods for multibuffer range -> buffer range
* Renames `excerpts_for_ranges` to `disjoint_ranges_to_buffer_ranges`,
and modifies it to return `MultiBufferExcerpt` since it provides more
information and was already being built.

* `range_to_buffer_ranges` now returns an iterator, should improve
efficiency in a few places.  Now implemented by just calling
`disjoint_ranges_to_buffer_ranges`.
2025-01-06 19:05:57 -07: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
186 changed files with 4942 additions and 3207 deletions

View File

@@ -35,7 +35,12 @@ jobs:
- 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 "docs_only=false" >> $GITHUB_OUTPUT
elif git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -qvE '^docs/'; then
echo "docs_only=false" >> $GITHUB_OUTPUT
else
echo "docs_only=true" >> $GITHUB_OUTPUT
@@ -175,7 +180,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 +221,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 +252,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"

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"

194
Cargo.lock generated
View File

@@ -245,7 +245,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.25.0",
"strum",
"thiserror 1.0.69",
"util",
]
@@ -435,7 +435,7 @@ dependencies = [
"similar",
"smallvec",
"smol",
"strum 0.25.0",
"strum",
"telemetry_events",
"terminal",
"terminal_view",
@@ -494,7 +494,6 @@ dependencies = [
"project",
"proto",
"rand 0.8.5",
"release_channel",
"rope",
"schemars",
"serde",
@@ -2152,7 +2151,7 @@ dependencies = [
"cap-primitives",
"cap-std",
"io-lifetimes 2.0.4",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -2180,7 +2179,7 @@ dependencies = [
"ipnet",
"maybe-owned",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
"winx",
]
@@ -2275,7 +2274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
dependencies = [
"heck 0.4.1",
"indexmap 2.7.0",
"indexmap",
"log",
"proc-macro2",
"quote",
@@ -2716,7 +2715,7 @@ dependencies = [
"settings",
"sha2",
"sqlx",
"strum 0.25.0",
"strum",
"subtle",
"supermaven_api",
"telemetry_events",
@@ -2785,7 +2784,8 @@ dependencies = [
name = "collections"
version = "0.1.0"
dependencies = [
"rustc-hash 1.1.0",
"indexmap",
"rustc-hash 2.1.0",
]
[[package]]
@@ -2994,7 +2994,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"strum 0.25.0",
"strum",
"task",
"theme",
"ui",
@@ -4145,7 +4145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -4812,7 +4812,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
dependencies = [
"io-lifetimes 2.0.4",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -5102,7 +5102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
dependencies = [
"fallible-iterator",
"indexmap 2.7.0",
"indexmap",
"stable_deref_trait",
]
@@ -5194,6 +5194,7 @@ dependencies = [
"util",
"windows 0.58.0",
"workspace",
"worktree",
]
[[package]]
@@ -5274,7 +5275,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.25.0",
"strum",
]
[[package]]
@@ -5374,7 +5375,7 @@ dependencies = [
"slotmap",
"smallvec",
"smol",
"strum 0.25.0",
"strum",
"sum_tree",
"taffy",
"thiserror 1.0.69",
@@ -5434,7 +5435,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.7.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
@@ -5453,7 +5454,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.2.0",
"indexmap 2.7.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
@@ -6232,27 +6233,16 @@ dependencies = [
"heed",
"html_to_markdown",
"http_client",
"indexmap 1.9.3",
"indexmap",
"indoc",
"parking_lot",
"paths",
"pretty_assertions",
"serde",
"strum 0.25.0",
"strum",
"util",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.7.0"
@@ -6388,7 +6378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
dependencies = [
"io-lifetimes 2.0.4",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -6775,7 +6765,7 @@ dependencies = [
"serde",
"serde_json",
"smol",
"strum 0.25.0",
"strum",
"ui",
"util",
]
@@ -6821,7 +6811,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"strum 0.25.0",
"strum",
"telemetry_events",
"theme",
"thiserror 1.0.69",
@@ -6999,7 +6989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -7741,7 +7731,7 @@ dependencies = [
"cfg_aliases 0.1.1",
"codespan-reporting",
"hexf-parse",
"indexmap 2.7.0",
"indexmap",
"log",
"rustc-hash 1.1.0",
"spirv",
@@ -8373,7 +8363,7 @@ checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"crc32fast",
"hashbrown 0.15.2",
"indexmap 2.7.0",
"indexmap",
"memchr",
]
@@ -8478,7 +8468,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.25.0",
"strum",
]
[[package]]
@@ -9328,7 +9318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.7.0",
"indexmap",
]
[[package]]
@@ -9511,7 +9501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [
"base64 0.22.1",
"indexmap 2.7.0",
"indexmap",
"quick-xml 0.32.0",
"serde",
"time",
@@ -9829,7 +9819,7 @@ dependencies = [
"file_icons",
"git",
"gpui",
"indexmap 1.9.3",
"indexmap",
"language",
"menu",
"pretty_assertions",
@@ -9933,7 +9923,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes 1.9.0",
"heck 0.5.0",
"itertools 0.12.1",
"itertools 0.10.5",
"log",
"multimap 0.10.0",
"once_cell",
@@ -9966,7 +9956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [
"anyhow",
"itertools 0.12.1",
"itertools 0.10.5",
"proc-macro2",
"quote",
"syn 2.0.90",
@@ -10148,14 +10138,14 @@ dependencies = [
"once_cell",
"socket2 0.5.8",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@@ -10892,7 +10882,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"strum 0.25.0",
"strum",
"tracing",
"util",
"zstd",
@@ -11048,7 +11038,7 @@ dependencies = [
"libc",
"linux-raw-sys 0.4.14",
"once_cell",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -11223,7 +11213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
dependencies = [
"dyn-clone",
"indexmap 1.9.3",
"indexmap",
"schemars_derive",
"serde",
"serde_json",
@@ -11296,9 +11286,9 @@ dependencies = [
[[package]]
name = "sea-orm"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b24d72a69e89762982c29af249542b06c59fa131f87cc9d5b94be1f692b427a"
checksum = "0dbcf83248860dc632c46c7e81a221e041b50d0006191756cb001d9e8afc60a9"
dependencies = [
"async-stream",
"async-trait",
@@ -11314,7 +11304,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
"strum 0.26.3",
"strum",
"thiserror 1.0.69",
"time",
"tracing",
@@ -11324,9 +11314,9 @@ dependencies = [
[[package]]
name = "sea-orm-macros"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0497f4fd82ecb2a222bea5319b9048f8ab58d4e734d095b062987acbcdeecdda"
checksum = "49ce6f08134f3681b1ca92185b96fac898f26d9b4f5538d13f7032ef243d14b2"
dependencies = [
"heck 0.4.1",
"proc-macro2",
@@ -11556,11 +11546,11 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.133"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"indexmap 2.7.0",
"indexmap",
"itoa",
"memchr",
"ryu",
@@ -11573,7 +11563,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540"
dependencies = [
"indexmap 2.7.0",
"indexmap",
"itoa",
"memchr",
"ryu",
@@ -12146,7 +12136,7 @@ dependencies = [
"hashbrown 0.14.5",
"hashlink 0.9.1",
"hex",
"indexmap 2.7.0",
"indexmap",
"log",
"memchr",
"once_cell",
@@ -12370,7 +12360,7 @@ dependencies = [
"settings",
"simplelog",
"story",
"strum 0.25.0",
"strum",
"theme",
"title_bar",
"ui",
@@ -12430,26 +12420,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.25.0"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
[[package]]
name = "strum_macros"
version = "0.25.3"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"proc-macro2",
"quote",
"rustversion",
@@ -12768,7 +12752,7 @@ dependencies = [
"fd-lock",
"io-lifetimes 2.0.4",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
"winx",
]
@@ -12900,7 +12884,7 @@ dependencies = [
"fastrand 2.3.0",
"once_cell",
"rustix 0.38.42",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -13025,7 +13009,7 @@ dependencies = [
"fs",
"futures 0.3.31",
"gpui",
"indexmap 1.9.3",
"indexmap",
"log",
"palette",
"parking_lot",
@@ -13037,7 +13021,7 @@ dependencies = [
"serde_json_lenient",
"serde_repr",
"settings",
"strum 0.25.0",
"strum",
"util",
"uuid",
]
@@ -13060,7 +13044,7 @@ dependencies = [
"anyhow",
"clap",
"gpui",
"indexmap 1.9.3",
"indexmap",
"log",
"palette",
"rust-embed",
@@ -13069,7 +13053,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"simplelog",
"strum 0.25.0",
"strum",
"theme",
"vscode_theme",
]
@@ -13497,7 +13481,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.7.0",
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
@@ -14000,7 +13984,7 @@ dependencies = [
"settings",
"smallvec",
"story",
"strum 0.25.0",
"strum",
"theme",
"ui_macros",
"windows 0.58.0",
@@ -14029,9 +14013,9 @@ dependencies = [
[[package]]
name = "unicase"
version = "2.8.0"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-bidi"
@@ -14579,7 +14563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2"
dependencies = [
"anyhow",
"indexmap 2.7.0",
"indexmap",
"serde",
"serde_derive",
"serde_json",
@@ -14608,7 +14592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
dependencies = [
"bitflags 2.6.0",
"indexmap 2.7.0",
"indexmap",
"semver",
]
@@ -14621,7 +14605,7 @@ dependencies = [
"ahash 0.8.11",
"bitflags 2.6.0",
"hashbrown 0.14.5",
"indexmap 2.7.0",
"indexmap",
"semver",
"serde",
]
@@ -14651,7 +14635,7 @@ dependencies = [
"cfg-if",
"encoding_rs",
"hashbrown 0.14.5",
"indexmap 2.7.0",
"indexmap",
"libc",
"libm",
"log",
@@ -14772,7 +14756,7 @@ dependencies = [
"cranelift-bitset",
"cranelift-entity",
"gimli 0.29.0",
"indexmap 2.7.0",
"indexmap",
"log",
"object",
"postcard",
@@ -14902,7 +14886,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190"
dependencies = [
"anyhow",
"heck 0.4.1",
"indexmap 2.7.0",
"indexmap",
"wit-parser 0.215.0",
]
@@ -15203,7 +15187,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -15652,7 +15636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
dependencies = [
"bitflags 2.6.0",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -15699,7 +15683,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3"
dependencies = [
"anyhow",
"heck 0.4.1",
"indexmap 2.7.0",
"indexmap",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -15727,7 +15711,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
dependencies = [
"anyhow",
"bitflags 2.6.0",
"indexmap 2.7.0",
"indexmap",
"log",
"serde",
"serde_derive",
@@ -15746,7 +15730,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6"
dependencies = [
"anyhow",
"id-arena",
"indexmap 2.7.0",
"indexmap",
"log",
"semver",
"serde",
@@ -15764,7 +15748,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f"
dependencies = [
"anyhow",
"id-arena",
"indexmap 2.7.0",
"indexmap",
"log",
"semver",
"serde",
@@ -15820,7 +15804,7 @@ dependencies = [
"settings",
"smallvec",
"sqlez",
"strum 0.25.0",
"strum",
"task",
"tempfile",
"theme",
@@ -16204,7 +16188,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.168.0"
version = "0.169.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -16336,24 +16320,9 @@ dependencies = [
"serde",
]
[[package]]
name = "zed_astro"
version = "0.1.2"
dependencies = [
"serde",
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_clojure"
version = "0.0.3"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_csharp"
version = "0.0.2"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -16372,13 +16341,6 @@ dependencies = [
"zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "zed_elm"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_emmet"
version = "0.0.3"

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",
@@ -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"
@@ -443,9 +440,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 +463,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"

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

@@ -377,6 +377,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",
@@ -434,6 +435,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",

View File

@@ -4,55 +4,70 @@
// 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": "workspace::CloseWindow", // kill-emacs
"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

@@ -4,55 +4,70 @@
// 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": "workspace::CloseWindow", // kill-emacs
"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

@@ -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.
@@ -741,7 +746,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

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

@@ -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();

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();
@@ -228,19 +228,20 @@ impl InlineAssistant {
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
for (excerpt_id, buffer, buffer_range) in
snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
for (excerpt, buffer_range) in
snapshot.disjoint_ranges_to_buffer_ranges(selections.iter().map(|selection| {
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
}))
{
let buffer = excerpt.buffer();
let start = Anchor {
buffer_id: Some(buffer.remote_id()),
excerpt_id,
excerpt_id: excerpt.id(),
text_anchor: buffer.anchor_before(buffer_range.start),
};
let end = Anchor {
buffer_id: Some(buffer.remote_id()),
excerpt_id,
excerpt_id: excerpt.id(),
text_anchor: buffer.anchor_after(buffer_range.end),
};
codegen_ranges.push(start..end);
@@ -797,10 +798,12 @@ 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 mut ranges =
multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.next()
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
});
report_assistant_event(
@@ -2615,26 +2618,30 @@ 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)
.pop()
// TODO: Could be made more efficient by using a reverse iterator.
let (old_excerpt, _) = snapshot
.range_to_buffer_ranges(range.clone())
.last()
.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 +2652,7 @@ impl CodegenAlternative {
});
Self {
buffer: buffer.clone(),
buffer: multi_buffer.clone(),
old_buffer,
edit_position: None,
message_id: None,
@@ -2656,7 +2663,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 +2874,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 mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.next()
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
};

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

@@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::{
list, AbsoluteLength, AnyElement, AppContext, DefiniteLength, EdgesRefinement, Empty, Length,
ListAlignment, ListOffset, ListState, Model, StyleRefinement, Subscription,
TextStyleRefinement, View, WeakView,
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>>,
@@ -140,6 +140,15 @@ impl ActiveThread {
background_color: Some(colors.editor_foreground.opacity(0.01)),
..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()
};
@@ -274,12 +283,11 @@ impl ActiveThread {
.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()))),
h_flex().flex_wrap().gap_1().px_1p5().pb_1p5().children(
context
.iter()
.map(|context| ContextPill::new(context.clone())),
),
)
} else {
parent

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));

View File

@@ -257,17 +257,21 @@ impl CodegenAlternative {
) -> Self {
let snapshot = buffer.read(cx).snapshot(cx);
let (old_buffer, _, _) = buffer
.read(cx)
.range_to_buffer_ranges(range.clone(), cx)
.pop()
// TODO: Could be more efficient by using a reverse iterator.
let (old_excerpt, _) = snapshot
.range_to_buffer_ranges(range.clone())
.last()
.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 +475,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 mut ranges = snapshot.range_to_buffer_ranges(self.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.next()
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
};

View File

@@ -1,8 +1,11 @@
use gpui::SharedString;
use language_model::{LanguageModelRequestMessage, MessageContent};
use project::ProjectEntryId;
use serde::{Deserialize, Serialize};
use util::post_inc;
use crate::thread::ThreadId;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct ContextId(pub(crate) usize);
@@ -21,12 +24,12 @@ pub struct Context {
pub text: SharedString,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContextKind {
File,
File(ProjectEntryId),
Directory,
FetchedUrl,
Thread,
Thread(ThreadId),
}
pub fn attach_context_to_message(
@@ -40,7 +43,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');
}
@@ -54,7 +57,7 @@ pub fn attach_context_to_message(
fetch_context.push_str(&context.text);
fetch_context.push('\n');
}
ContextKind::Thread => {
ContextKind::Thread(_) => {
thread_context.push_str(&context.name);
thread_context.push('\n');
thread_context.push_str(&context.text);

View File

@@ -10,12 +10,10 @@ use gpui::{
WeakModel, WeakView,
};
use picker::{Picker, PickerDelegate};
use release_channel::ReleaseChannel;
use ui::{prelude::*, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::directory_context_picker::DirectoryContextPicker;
use crate::context_picker::fetch_context_picker::FetchContextPicker;
use crate::context_picker::file_context_picker::FileContextPicker;
@@ -54,29 +52,24 @@ impl ContextPicker {
let mut entries = Vec::new();
entries.push(ContextPickerEntry {
name: "File".into(),
kind: ContextKind::File,
kind: ContextPickerEntryKind::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: ContextPickerEntryKind::Directory,
icon: IconName::Folder,
});
entries.push(ContextPickerEntry {
name: "Fetch".into(),
kind: ContextKind::FetchedUrl,
kind: ContextPickerEntryKind::FetchedUrl,
icon: IconName::Globe,
});
if thread_store.is_some() {
entries.push(ContextPickerEntry {
name: "Thread".into(),
kind: ContextKind::Thread,
kind: ContextPickerEntryKind::Thread,
icon: IconName::MessageCircle,
});
}
@@ -140,10 +133,18 @@ impl Render for ContextPicker {
#[derive(Clone)]
struct ContextPickerEntry {
name: SharedString,
kind: ContextKind,
kind: ContextPickerEntryKind,
icon: IconName,
}
#[derive(Debug, Clone)]
enum ContextPickerEntryKind {
File,
Directory,
FetchedUrl,
Thread,
}
pub(crate) struct ContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
@@ -183,7 +184,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker
.update(cx, |this, cx| {
match entry.kind {
ContextKind::File => {
ContextPickerEntryKind::File => {
this.mode = ContextPickerMode::File(cx.new_view(|cx| {
FileContextPicker::new(
self.context_picker.clone(),
@@ -194,7 +195,7 @@ impl PickerDelegate for ContextPickerDelegate {
)
}));
}
ContextKind::Directory => {
ContextPickerEntryKind::Directory => {
this.mode = ContextPickerMode::Directory(cx.new_view(|cx| {
DirectoryContextPicker::new(
self.context_picker.clone(),
@@ -205,7 +206,7 @@ impl PickerDelegate for ContextPickerDelegate {
)
}));
}
ContextKind::FetchedUrl => {
ContextPickerEntryKind::FetchedUrl => {
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
FetchContextPicker::new(
self.context_picker.clone(),
@@ -216,7 +217,7 @@ impl PickerDelegate for ContextPickerDelegate {
)
}));
}
ContextKind::Thread => {
ContextPickerEntryKind::Thread => {
if let Some(thread_store) = self.thread_store.as_ref() {
this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
ThreadContextPicker::new(

View File

@@ -1,19 +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::file_context_picker::codeblock_fence_for_path;
use crate::context_picker::{ConfirmBehavior, ContextPicker};
use crate::context_store::ContextStore;
@@ -193,14 +192,61 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
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 {
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");
}
this.delegate
.context_store
.update(cx, |context_store, cx| {
.update(cx, |context_store, _cx| {
context_store.insert_context(
ContextKind::Directory,
path.to_string_lossy().to_string(),
@@ -247,3 +293,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
)
}
}
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

@@ -7,7 +7,7 @@ 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 project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use ui::{prelude::*, ListItem};
use util::ResultExt as _;
use workspace::Workspace;
@@ -207,11 +207,20 @@ impl PickerDelegate for FileContextPickerDelegate {
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
let Some((entry_id, 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 entry_id = project.entry_for_path(&project_path, cx)?.id;
let task = project.open_buffer(project_path, cx);
Some((entry_id, task))
})
.ok()
.flatten()
else {
return anyhow::Ok(());
};
@@ -232,7 +241,7 @@ impl PickerDelegate for FileContextPickerDelegate {
text.push_str("```\n");
context_store.insert_context(
ContextKind::File,
ContextKind::File(entry_id),
path.to_string_lossy().to_string(),
text,
);
@@ -307,7 +316,10 @@ impl PickerDelegate for FileContextPickerDelegate {
}
}
fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<RangeInclusive<u32>>) -> String {
pub(crate) fn codeblock_fence_for_path(
path: Option<&Path>,
row_range: Option<RangeInclusive<u32>>,
) -> String {
let mut text = String::new();
write!(text, "```").unwrap();

View File

@@ -169,25 +169,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);
context_store.insert_context(
ContextKind::Thread(thread.read(cx).id().clone()),
entry.summary.clone(),
thread.read(cx).text(),
);
})
.ok();

View File

@@ -1,6 +1,10 @@
use gpui::SharedString;
use project::ProjectEntryId;
use crate::context::{Context, ContextId, ContextKind};
use crate::{
context::{Context, ContextId, ContextKind},
thread::ThreadId,
};
pub struct ContextStore {
context: Vec<Context>,
@@ -44,4 +48,18 @@ impl ContextStore {
pub fn remove_context(&mut self, id: &ContextId) {
self.context.retain(|context| context.id != *id);
}
pub fn contains_project_entry(&self, entry_id: ProjectEntryId) -> bool {
self.context.iter().any(|probe| match probe.kind {
ContextKind::File(probe_entry_id) => probe_entry_id == entry_id,
ContextKind::Directory | ContextKind::FetchedUrl | ContextKind::Thread(_) => false,
})
}
pub fn contains_thread(&self, thread_id: &ThreadId) -> bool {
self.context.iter().any(|probe| match probe.kind {
ContextKind::Thread(ref probe_thread_id) => probe_thread_id == thread_id,
ContextKind::File(_) | ContextKind::Directory | ContextKind::FetchedUrl => false,
})
}
}

View File

@@ -1,21 +1,27 @@
use std::rc::Rc;
use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
use editor::Editor;
use gpui::{AppContext, FocusHandle, Model, View, WeakModel, WeakView};
use language::Buffer;
use project::ProjectEntryId;
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, ThreadId};
use crate::thread_store::ThreadStore;
use crate::ui::ContextPill;
use crate::ToggleContextPicker;
use settings::Settings;
use crate::{AssistantPanel, 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>,
}
impl ContextStrip {
@@ -25,6 +31,7 @@ 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 {
Self {
@@ -40,16 +47,76 @@ impl ContextStrip {
}),
context_picker_menu_handle,
focus_handle,
suggest_context_kind,
workspace,
}
}
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 entry_id = *active_item.project_entry_ids(cx).first()?;
if self.context_store.read(cx).contains_project_entry(entry_id) {
return None;
}
let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
let active_buffer = editor.buffer().read(cx).as_singleton()?;
let file = active_buffer.read(cx).file()?;
let title = file.path().to_string_lossy().into_owned().into();
Some(SuggestedContext::File {
entry_id,
title,
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)
.contains_thread(active_thread.id())
{
return None;
}
Some(SuggestedContext::Thread {
id: active_thread.id().clone(),
title: active_thread.summary().unwrap_or("Active Thread".into()),
thread: weak_active_thread,
})
}
}
impl Render for ContextStrip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let context = self.context_store.read(cx).context().clone();
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);
h_flex()
.flex_wrap()
.gap_1()
@@ -60,13 +127,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)
@@ -77,26 +148,22 @@ impl Render for ContextStrip {
})
.with_handle(self.context_picker_menu_handle.clone()),
)
.when(context.is_empty(), {
.when(context.is_empty() && suggested_context.is_none(), {
|parent| {
parent.child(
h_flex()
.id("no-content-info")
.ml_1p5()
.gap_2()
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.text_size(TextSize::Small.rems(cx))
.text_color(cx.theme().colors().text_muted)
.child("Add Context")
.children(
ui::KeyBinding::for_action_in(
&ToggleContextPicker,
&self.focus_handle,
cx,
)
.map(|binding| binding.into_any_element()),
.child(
Label::new("Add Context")
.size(LabelSize::Small)
.color(Color::Muted),
)
.opacity(0.5),
.opacity(0.5)
.children(
KeyBinding::for_action_in(&ToggleContextPicker, &focus_handle, cx)
.map(|binding| binding.into_any_element()),
),
)
}
})
@@ -112,6 +179,30 @@ impl Render for ContextStrip {
}))
})
}))
.when_some(suggested_context, |el, suggested| {
el.child(
Button::new("add-suggested-context", suggested.title().clone())
.on_click({
let context_store = self.context_store.clone();
cx.listener(move |_this, _event, cx| {
context_store.update(cx, |context_store, cx| {
suggested.accept(context_store, cx);
});
cx.notify();
})
})
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.style(ButtonStyle::Filled)
.tooltip(|cx| {
Tooltip::with_meta("Suggested Context", None, "Click to add it", cx)
}),
)
})
.when(!context.is_empty(), {
move |parent| {
parent.child(
@@ -130,3 +221,63 @@ impl Render for ContextStrip {
})
}
}
pub enum SuggestContextKind {
File,
Thread,
}
#[derive(Clone)]
pub enum SuggestedContext {
File {
entry_id: ProjectEntryId,
title: SharedString,
buffer: WeakModel<Buffer>,
},
Thread {
id: ThreadId,
title: SharedString,
thread: WeakModel<Thread>,
},
}
impl SuggestedContext {
pub fn title(&self) -> &SharedString {
match self {
Self::File { title, .. } => title,
Self::Thread { title, .. } => title,
}
}
pub fn accept(&self, context_store: &mut ContextStore, cx: &mut AppContext) {
match self {
Self::File {
entry_id,
title,
buffer,
} => {
let Some(buffer) = buffer.upgrade() else {
return;
};
let text = buffer.read(cx).text();
context_store.insert_context(
ContextKind::File(*entry_id),
title.clone(),
text.clone(),
);
}
Self::Thread { id, title, thread } => {
let Some(thread) = thread.upgrade() else {
return;
};
context_store.insert_context(
ContextKind::Thread(id.clone()),
title.clone(),
thread.read(cx).text(),
);
}
}
}
}

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();
@@ -294,19 +294,20 @@ impl InlineAssistant {
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
for (excerpt_id, buffer, buffer_range) in
snapshot.excerpts_in_ranges(selections.iter().map(|selection| {
for (excerpt, buffer_range) in
snapshot.disjoint_ranges_to_buffer_ranges(selections.iter().map(|selection| {
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
}))
{
let buffer = excerpt.buffer();
let start = Anchor {
buffer_id: Some(buffer.remote_id()),
excerpt_id,
excerpt_id: excerpt.id(),
text_anchor: buffer.anchor_before(buffer_range.start),
};
let end = Anchor {
buffer_id: Some(buffer.remote_id()),
excerpt_id,
excerpt_id: excerpt.id(),
text_anchor: buffer.anchor_after(buffer_range.end),
};
codegen_ranges.push(start..end);
@@ -871,10 +872,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 mut ranges = snapshot.range_to_buffer_ranges(assist.range.clone());
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.next()
.and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name())
});
report_assistant_event(

View File

@@ -2,7 +2,7 @@ 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, SuggestContextKind};
use crate::terminal_codegen::TerminalCodegen;
use crate::thread_store::ThreadStore;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
@@ -793,6 +793,7 @@ impl PromptEditor<BufferCodegen> {
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
cx,
)
}),
@@ -932,6 +933,7 @@ impl PromptEditor<TerminalCodegen> {
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
SuggestContextKind::Thread,
cx,
)
}),

View File

@@ -20,7 +20,7 @@ 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, SuggestContextKind};
use crate::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore;
use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
@@ -87,6 +87,7 @@ impl MessageEditor {
Some(thread_store.clone()),
editor.focus_handle(cx),
context_picker_menu_handle.clone(),
SuggestContextKind::File,
cx,
)
}),

View File

@@ -164,6 +164,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,

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

@@ -33,10 +33,10 @@ impl RenderOnce for ContextPill {
px(4.)
};
let icon = match self.context.kind {
ContextKind::File => IconName::File,
ContextKind::File(_) => IconName::File,
ContextKind::Directory => IconName::Folder,
ContextKind::FetchedUrl => IconName::Globe,
ContextKind::Thread => IconName::MessageCircle,
ContextKind::Thread(_) => IconName::MessageCircle,
};
h_flex()

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) => {
@@ -257,7 +257,6 @@ fn main() -> Result<()> {
if args.foreground {
app.run_foreground(url)?;
} else {
eprintln!("Logs are written to {:?}", paths::log_file());
app.launch(url)?;
sender.join().unwrap()?;
pipe_handle.join().unwrap()?;

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);
@@ -4067,7 +4065,7 @@ async fn test_collaborating_with_diagnostics(
DiagnosticEntry {
range: Point::new(0, 4)..Point::new(0, 7),
diagnostic: Diagnostic {
group_id: 2,
group_id: 3,
message: "message 1".to_string(),
severity: lsp::DiagnosticSeverity::ERROR,
is_primary: true,
@@ -4077,7 +4075,7 @@ async fn test_collaborating_with_diagnostics(
DiagnosticEntry {
range: Point::new(0, 10)..Point::new(0, 13),
diagnostic: Diagnostic {
group_id: 3,
group_id: 4,
severity: lsp::DiagnosticSeverity::WARNING,
message: "message 2".to_string(),
is_primary: true,
@@ -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

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

@@ -1135,15 +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()
.filter(|room| room.read(cx).contains_guests())
.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> {

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

@@ -82,7 +82,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::INFORMATION,
is_primary: false,
is_disk_based: true,
group_id: 1,
group_id: 2,
..Default::default()
},
},
@@ -95,7 +95,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::INFORMATION,
is_primary: false,
is_disk_based: true,
group_id: 0,
group_id: 1,
..Default::default()
},
},
@@ -106,7 +106,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::INFORMATION,
is_primary: false,
is_disk_based: true,
group_id: 1,
group_id: 2,
..Default::default()
},
},
@@ -117,7 +117,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::INFORMATION,
is_primary: false,
is_disk_based: true,
group_id: 0,
group_id: 1,
..Default::default()
},
},
@@ -128,7 +128,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::ERROR,
is_primary: true,
is_disk_based: true,
group_id: 0,
group_id: 1,
..Default::default()
},
},
@@ -139,7 +139,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::ERROR,
is_primary: true,
is_disk_based: true,
group_id: 1,
group_id: 2,
..Default::default()
},
},
@@ -241,7 +241,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::ERROR,
is_primary: true,
is_disk_based: true,
group_id: 0,
group_id: 1,
..Default::default()
},
}],
@@ -348,7 +348,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::ERROR,
is_primary: true,
is_disk_based: true,
group_id: 0,
group_id: 1,
..Default::default()
},
},
@@ -359,7 +359,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
severity: DiagnosticSeverity::ERROR,
is_primary: true,
is_disk_based: true,
group_id: 1,
group_id: 2,
..Default::default()
},
},
@@ -775,7 +775,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
assert!(view.focus_handle.is_focused(cx));
});
let mut next_group_id = 0;
let mut next_group_id = 1;
let mut next_filename = 0;
let mut language_server_ids = vec![LanguageServerId(0)];
let mut updated_language_servers = HashSet::default();

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

@@ -2748,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(),
@@ -2994,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

@@ -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;
@@ -109,7 +109,7 @@ pub use proposed_changes_editor::{
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
};
use similar::{ChangeTag, TextDiff};
use std::iter::Peekable;
use std::iter::{self, Peekable};
use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
@@ -993,7 +993,10 @@ pub(crate) struct FocusedBlock {
#[derive(Clone)]
enum JumpData {
MultiBufferRow(MultiBufferRow),
MultiBufferRow {
row: MultiBufferRow,
line_offset_from_top: u32,
},
MultiBufferPoint {
excerpt_id: ExcerptId,
position: Point,
@@ -3546,13 +3549,11 @@ 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)
.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())?;
multi_buffer_snapshot
.disjoint_ranges_to_buffer_ranges(iter::once(multi_buffer_visible_range))
.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)
@@ -3561,17 +3562,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,
),
))
@@ -9146,11 +9147,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()]);
});
}
@@ -9176,10 +9178,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
@@ -9209,7 +9224,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,
@@ -10286,11 +10302,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
});
@@ -10310,7 +10327,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| {
@@ -10320,16 +10337,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());
@@ -10340,8 +10359,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(
@@ -10372,7 +10389,6 @@ impl Editor {
is_valid: true,
})
});
self.active_diagnostics.is_some()
}
fn dismiss_diagnostics(&mut self, cx: &mut ViewContext<Self>) {
@@ -10481,13 +10497,13 @@ impl Editor {
} else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let mut toggled_buffers = HashSet::default();
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
self.selections
.disjoint_anchors()
.into_iter()
.map(|selection| selection.range()),
) {
let buffer_id = buffer_snapshot.remote_id();
let buffer_id = excerpt.buffer().remote_id();
if toggled_buffers.insert(buffer_id) {
if self.buffer_folded(buffer_id, cx) {
self.unfold_buffer(buffer_id, cx);
@@ -10567,13 +10583,13 @@ impl Editor {
} else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let mut folded_buffers = HashSet::default();
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
self.selections
.disjoint_anchors()
.into_iter()
.map(|selection| selection.range()),
) {
let buffer_id = buffer_snapshot.remote_id();
let buffer_id = excerpt.buffer().remote_id();
if folded_buffers.insert(buffer_id) {
self.fold_buffer(buffer_id, cx);
}
@@ -10733,13 +10749,13 @@ impl Editor {
} else {
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let mut unfolded_buffers = HashSet::default();
for (_, buffer_snapshot, _) in multi_buffer_snapshot.excerpts_in_ranges(
for (excerpt, _) in multi_buffer_snapshot.disjoint_ranges_to_buffer_ranges(
self.selections
.disjoint_anchors()
.into_iter()
.map(|selection| selection.range()),
) {
let buffer_id = buffer_snapshot.remote_id();
let buffer_id = excerpt.buffer().remote_id();
if unfolded_buffers.insert(buffer_id) {
self.unfold_buffer(buffer_id, cx);
}
@@ -11483,34 +11499,36 @@ impl Editor {
}
fn get_permalink_to_line(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<url::Url>> {
let buffer_and_selection = maybe!({
let selection = self.selections.newest::<Point>(cx);
let buffer_and_selection_rows = maybe!({
let multi_buffer = self.buffer().read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let selection = self.selections.newest_anchor();
let selection_range = selection.range();
let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
(buffer, selection_range.start.row..selection_range.end.row)
let (buffer, selection_rows) = if let Some(buffer) = multi_buffer.as_singleton() {
(
buffer,
selection_range.start.to_point(&multi_buffer_snapshot).row
..selection_range.end.to_point(&multi_buffer_snapshot).row,
)
} else {
let buffer_ranges = self
.buffer()
.read(cx)
.range_to_buffer_ranges(selection_range, cx);
let (buffer, range, _) = if selection.reversed {
buffer_ranges.first()
} else {
buffer_ranges.last()
}?;
let snapshot = buffer.read(cx).snapshot();
let selection = text::ToPoint::to_point(&range.start, &snapshot).row
..text::ToPoint::to_point(&range.end, &snapshot).row;
(buffer.clone(), selection)
let selection_head = selection.head();
let excerpt =
multi_buffer_snapshot.excerpt_containing(selection_head..selection_head)?;
let range =
excerpt.map_range_to_buffer(selection_range.to_offset(&multi_buffer_snapshot));
let snapshot = excerpt.buffer();
(
multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(),
text::ToPoint::to_point(&range.start, &snapshot).row
..text::ToPoint::to_point(&range.end, &snapshot).row,
)
};
Some((buffer, selection))
Some((buffer, selection_rows))
});
let Some((buffer, selection)) = buffer_and_selection else {
let Some((buffer, selection_rows)) = buffer_and_selection_rows else {
return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
};
@@ -11519,7 +11537,7 @@ impl Editor {
};
project.update(cx, |project, cx| {
project.get_permalink_to_line(&buffer, selection, cx)
project.get_permalink_to_line(&buffer, selection_rows, cx)
})
}
@@ -11762,7 +11780,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,
@@ -12396,17 +12414,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)
}
@@ -12487,7 +12506,10 @@ impl Editor {
);
}
}
Some(JumpData::MultiBufferRow(row)) => {
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)
@@ -12495,20 +12517,22 @@ impl Editor {
let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
new_selections_by_buffer
.entry(buffer)
.or_insert((Vec::new(), None))
.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());
@@ -12549,7 +12573,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) =

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

@@ -6,7 +6,7 @@ use crate::{
},
editor_settings::{
CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ScrollBeyondLastLine,
ShowScrollbar,
ScrollbarDiagnostics, ShowScrollbar,
},
git::blame::{CommitDetails, GitBlame},
hover_popover::{
@@ -45,7 +45,7 @@ use language::{
IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings,
ShowWhitespaceSetting,
},
ChunkRendererContext,
ChunkRendererContext, DiagnosticEntry,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{
@@ -599,8 +599,15 @@ impl EditorElement {
.row;
if let Some((_, Some(hitbox))) = line_numbers.get(&MultiBufferRow(multi_buffer_row)) {
if hitbox.contains(&event.position) {
let scroll_position_row =
position_map.scroll_pixel_position.y / position_map.line_height;
let line_offset_from_top = display_row - scroll_position_row as u32;
editor.open_excerpts_common(
Some(JumpData::MultiBufferRow(MultiBufferRow(multi_buffer_row))),
Some(JumpData::MultiBufferRow {
row: MultiBufferRow(multi_buffer_row),
line_offset_from_top,
}),
modifiers.alt,
cx,
);
@@ -1228,7 +1235,7 @@ impl EditorElement {
(is_singleton && scrollbar_settings.selected_symbol && (editor.has_background_highlights::<DocumentHighlightRead>() || editor.has_background_highlights::<DocumentHighlightWrite>()))
||
// Diagnostics
(is_singleton && scrollbar_settings.diagnostics && snapshot.buffer_snapshot.has_diagnostics())
(is_singleton && scrollbar_settings.diagnostics != ScrollbarDiagnostics::None && snapshot.buffer_snapshot.has_diagnostics())
||
// Cursors out of sight
non_visible_cursors
@@ -2959,7 +2966,12 @@ impl EditorElement {
selected_buffer_ids: &Vec<BufferId>,
cx: &mut WindowContext,
) -> AnyElement {
let jump_data = header_jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt);
let jump_data = header_jump_data(
snapshot,
DisplayRow(scroll_position as u32),
FILE_HEADER_HEIGHT + MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
excerpt,
);
let editor_bg_color = cx.theme().colors().editor_background;
@@ -4726,13 +4738,39 @@ impl EditorElement {
}
}
if scrollbar_settings.diagnostics {
if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
let diagnostics = snapshot
.buffer_snapshot
.diagnostics_in_range::<_, Point>(
Point::zero()..max_point,
false,
)
.diagnostics_in_range(Point::zero()..max_point, false)
.map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: range.to_point(&snapshot.buffer_snapshot),
})
// Don't show diagnostics the user doesn't care about
.filter(|diagnostic| {
match (
scrollbar_settings.diagnostics,
diagnostic.diagnostic.severity,
) {
(ScrollbarDiagnostics::All, _) => true,
(
ScrollbarDiagnostics::Error,
DiagnosticSeverity::ERROR,
) => true,
(
ScrollbarDiagnostics::Warning,
DiagnosticSeverity::ERROR
| DiagnosticSeverity::WARNING,
) => true,
(
ScrollbarDiagnostics::Information,
DiagnosticSeverity::ERROR
| DiagnosticSeverity::WARNING
| DiagnosticSeverity::INFORMATION,
) => true,
(_, _) => false,
}
})
// We want to sort by severity, in order to paint the most severe diagnostics last.
.sorted_by_key(|diagnostic| {
std::cmp::Reverse(diagnostic.diagnostic.severity)
@@ -5096,13 +5134,12 @@ fn header_jump_data(
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
let excerpt_start_row = language::ToPoint::to_point(&excerpt_start, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top = block_row_start.0
+ height
+ offset_from_excerpt_start.saturating_sub(
let line_offset_from_top = (block_row_start.0 + height + offset_from_excerpt_start)
.saturating_sub(
snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)

View File

@@ -194,14 +194,24 @@ 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()
.flat_map(|entry| {
entry.status().map(|git_entry| {
(git_entry.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

@@ -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,
@@ -263,29 +263,7 @@ fn show_hover(
delay.await;
}
// If there's a diagnostic, assign it on the hover state and notify
let mut local_diagnostic = snapshot
.buffer_snapshot
.diagnostics_in_range::<_, usize>(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),
});
// 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
let local_diagnostic = if let Some(invisible) = snapshot
.buffer_snapshot
.chars_at(anchor)
.next()
@@ -294,7 +272,7 @@ fn show_hover(
let after = snapshot.buffer_snapshot.anchor_after(
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
);
local_diagnostic = Some(DiagnosticEntry {
Some(DiagnosticEntry {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: format!("Unicode character U+{:02X}", invisible as u32),
@@ -311,7 +289,7 @@ fn show_hover(
let before = snapshot.buffer_snapshot.anchor_before(
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
);
local_diagnostic = Some(DiagnosticEntry {
Some(DiagnosticEntry {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: format!("Unicode character U+{:02X}", invisible as u32),
@@ -319,7 +297,16 @@ fn show_hover(
},
range: before..anchor,
})
}
} else {
snapshot
.buffer_snapshot
.diagnostics_in_range(anchor..anchor, false)
// Find the entry with the most specific range
.min_by_key(|entry| {
let range = entry.range.to_offset(&snapshot.buffer_snapshot);
range.end - range.start
})
};
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
let text = match local_diagnostic.diagnostic.source {
@@ -388,7 +375,6 @@ fn show_hover(
Some(DiagnosticPopover {
local_diagnostic,
primary_diagnostic,
parsed_content,
border_color,
background_color,
@@ -783,7 +769,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 +822,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

@@ -456,16 +456,16 @@ impl Editor {
range: Range<Anchor>,
cx: &mut ViewContext<Editor>,
) -> Option<()> {
let (buffer, range, _) = self
.buffer
.read(cx)
.range_to_buffer_ranges(range, cx)
.into_iter()
.next()?;
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).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);

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(())
}

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 {
@@ -1457,10 +1468,11 @@ impl SearchableItem for Editor {
search_within_ranges
};
for (excerpt_id, search_buffer, search_range) in
buffer.excerpts_in_ranges(search_within_ranges)
for (excerpt, search_range) in
buffer.disjoint_ranges_to_buffer_ranges(search_within_ranges)
{
if !search_range.is_empty() {
let search_buffer = excerpt.buffer();
ranges.extend(
query
.search(search_buffer, Some(search_range.clone()))
@@ -1471,8 +1483,8 @@ impl SearchableItem for Editor {
.anchor_after(search_range.start + match_range.start);
let end = search_buffer
.anchor_before(search_range.start + match_range.end);
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
buffer.anchor_in_excerpt(excerpt.id(), start).unwrap()
..buffer.anchor_in_excerpt(excerpt.id(), end).unwrap()
}),
);
}
@@ -1559,10 +1571,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

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

@@ -10,8 +10,6 @@ use git::GitHostingProviderRegistry;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use ashpd::desktop::trash;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use collections::BTreeSet;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use smol::process::Command;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use std::fs::File;
@@ -122,17 +120,6 @@ pub trait Fs: Send + Sync {
path: &Path,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
async fn watch(
&self,
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = BTreeSet<PathEvent>>>>,
Arc<dyn Watcher>,
);
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
async fn watch(
&self,
path: &Path,
@@ -714,14 +701,13 @@ impl Fs for RealFs {
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = BTreeSet<PathEvent>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use collections::BTreeSet;
use parking_lot::Mutex;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<BTreeSet<PathEvent>>> = Default::default();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
if watcher.add(path).is_err() {
@@ -734,7 +720,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 = canonical;
}
}
}
watcher.add(&target).ok();
if let Some(parent) = target.parent() {
watcher.add(parent).log_err();
@@ -1952,7 +1947,6 @@ impl Fs for FakeFs {
Ok(Box::pin(futures::stream::iter(paths)))
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
async fn watch(
&self,
path: &Path,
@@ -1981,38 +1975,6 @@ impl Fs for FakeFs {
)
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
async fn watch(
&self,
path: &Path,
_: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = BTreeSet<PathEvent>>>>,
Arc<dyn Watcher>,
) {
self.simulate_random_delay().await;
let (tx, rx) = smol::channel::unbounded();
self.state.lock().event_txs.push(tx);
let path = path.to_path_buf();
let executor = self.executor.clone();
(
Box::pin(
futures::StreamExt::filter(rx, move |events| {
let result = events
.iter()
.any(|evt_path| evt_path.path.starts_with(&path));
let executor = executor.clone();
async move {
executor.simulate_random_delay().await;
result
}
})
.map(|events| BTreeSet::from_iter(events.into_iter())),
),
Arc::new(FakeWatcher {}),
)
}
fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<dyn GitRepository>> {
let state = self.state.lock();
let entry = state.read_path(abs_dot_git).unwrap();

View File

@@ -1,4 +1,3 @@
use collections::BTreeSet;
use notify::EventKind;
use parking_lot::Mutex;
use std::sync::{Arc, OnceLock};
@@ -8,13 +7,13 @@ use crate::{PathEvent, PathEventKind, Watcher};
pub struct LinuxWatcher {
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<BTreeSet<PathEvent>>>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
}
impl LinuxWatcher {
pub fn new(
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<BTreeSet<PathEvent>>>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
) -> Self {
Self {
tx,
@@ -41,7 +40,7 @@ impl Watcher for LinuxWatcher {
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let path_events = event
let mut path_events = event
.paths
.iter()
.filter_map(|event_path| {
@@ -53,12 +52,17 @@ impl Watcher for LinuxWatcher {
.collect::<Vec<_>>();
if !path_events.is_empty() {
path_events.sort();
let mut pending_paths = pending_paths.lock();
let was_empty = pending_paths.is_empty();
pending_paths.extend(path_events);
if was_empty {
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(
&mut *pending_paths,
path_events,
usize::MAX,
|a, b| a.path.cmp(&b.path),
);
}
})
}

View File

@@ -3,7 +3,10 @@ use std::{
borrow::Cow,
cmp::{self, Ordering},
path::Path,
sync::{atomic::AtomicBool, Arc},
sync::{
atomic::{self, AtomicBool},
Arc,
},
};
use crate::{
@@ -154,6 +157,10 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
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 {
@@ -202,6 +209,10 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
})
.await;
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

@@ -8,7 +8,7 @@ use std::{
cmp::{self, Ordering},
iter,
ops::Range,
sync::atomic::AtomicBool,
sync::atomic::{self, AtomicBool},
};
#[derive(Clone, Debug)]
@@ -178,6 +178,10 @@ pub async fn match_strings(
})
.await;
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

@@ -7,6 +7,8 @@ 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},
@@ -37,7 +39,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<()>;
@@ -132,7 +135,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()
@@ -289,8 +292,9 @@ 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()
@@ -306,6 +310,7 @@ impl GitRepository for FakeGitRepository {
})
.collect::<Vec<_>>();
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
Ok(GitStatus {
entries: entries.into(),
})
@@ -394,6 +399,8 @@ pub enum GitFileStatus {
Added,
Modified,
Conflict,
Deleted,
Untracked,
}
impl GitFileStatus {
@@ -421,20 +428,34 @@ impl GitFileStatus {
}
}
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 From<&Path> for RepoPath {
fn from(value: &Path) -> Self {
RepoPath::new(value.to_path_buf())
RepoPath::new(value.into())
}
}
@@ -444,9 +465,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 +484,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,10 +1,6 @@
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)]
pub struct GitStatus {
@@ -15,7 +11,7 @@ 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)
@@ -27,7 +23,7 @@ impl GitStatus {
"-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
@@ -55,10 +51,12 @@ impl GitStatus {
let (status, path) = entry.split_at(3);
let status = status.trim();
Some((
RepoPath(PathBuf::from(path)),
RepoPath(Path::new(path).into()),
match status {
"A" | "??" => GitFileStatus::Added,
"A" => GitFileStatus::Added,
"M" => GitFileStatus::Modified,
"D" => GitFileStatus::Deleted,
"??" => GitFileStatus::Untracked,
_ => return None,
},
))
@@ -75,7 +73,7 @@ impl GitStatus {
pub fn get(&self, path: &Path) -> Option<GitFileStatus> {
self.entries
.binary_search_by(|(repo_path, _)| repo_path.0.as_path().cmp(path))
.binary_search_by(|(repo_path, _)| repo_path.0.as_ref().cmp(path))
.ok()
.map(|index| self.entries[index].1)
}

View File

@@ -14,9 +14,11 @@ path = "src/git_ui.rs"
[dependencies]
anyhow.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
futures.workspace = true
git.workspace = true
gpui.workspace = true
language.workspace = true
menu.workspace = true
@@ -29,8 +31,7 @@ settings.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
git.workspace = true
collections.workspace = true
worktree.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@@ -1,11 +1,16 @@
use crate::{git_status_icon, settings::GitPanelSettings};
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
use anyhow::{Context as _, Result};
use collections::HashMap;
use db::kvp::KEY_VALUE_STORE;
use editor::{
scroll::{Autoscroll, AutoscrollStrategy},
Editor, MultiBuffer, DEFAULT_MULTIBUFFER_CONTEXT,
};
use git::{diff::DiffHunk, repository::GitFileStatus};
use git::{
diff::DiffHunk,
repository::{GitFileStatus, RepoPath},
};
use gpui::*;
use gpui::{
actions, prelude::*, uniform_list, Action, AppContext, AsyncWindowContext, ClickEvent,
CursorStyle, EventEmitter, FocusHandle, FocusableView, KeyContext,
@@ -14,7 +19,7 @@ use gpui::{
};
use language::{Buffer, BufferRow, OffsetRangeExt};
use menu::{SelectNext, SelectPrev};
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
use project::{EntryKind, Fs, Project, ProjectEntryId, ProjectPath, WorktreeId};
use serde::{Deserialize, Serialize};
use settings::Settings as _;
use std::{
@@ -22,7 +27,7 @@ use std::{
collections::HashSet,
ffi::OsStr,
ops::{Deref, Range},
path::{Path, PathBuf},
path::PathBuf,
rc::Rc,
sync::Arc,
time::Duration,
@@ -37,9 +42,7 @@ use workspace::{
dock::{DockPosition, Panel, PanelEvent},
ItemHandle, Workspace,
};
use crate::{git_status_icon, settings::GitPanelSettings};
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
use worktree::StatusEntry;
actions!(git_panel, [ToggleFocus]);
@@ -69,7 +72,7 @@ pub struct GitStatusEntry {}
struct EntryDetails {
filename: String,
display_name: String,
path: Arc<Path>,
path: RepoPath,
kind: EntryKind,
depth: usize,
is_expanded: bool,
@@ -101,7 +104,8 @@ pub struct GitPanel {
scrollbar_state: ScrollbarState,
selected_item: Option<usize>,
show_scrollbar: bool,
expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
// TODO Reintroduce expanded directories, once we're deriving directories from paths
// expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
// The entries that are currently shown in the panel, aka
// not hidden by folding or such
@@ -115,18 +119,20 @@ pub struct GitPanel {
#[derive(Debug, Clone)]
struct WorktreeEntries {
worktree_id: WorktreeId,
// TODO support multiple repositories per worktree
work_directory: worktree::WorkDirectory,
visible_entries: Vec<GitPanelEntry>,
paths: Rc<OnceCell<HashSet<Arc<Path>>>>,
paths: Rc<OnceCell<HashSet<RepoPath>>>,
}
#[derive(Debug, Clone)]
struct GitPanelEntry {
entry: Entry,
entry: worktree::StatusEntry,
hunks: Rc<OnceCell<Vec<DiffHunk>>>,
}
impl Deref for GitPanelEntry {
type Target = Entry;
type Target = worktree::StatusEntry;
fn deref(&self) -> &Self::Target {
&self.entry
@@ -134,11 +140,11 @@ impl Deref for GitPanelEntry {
}
impl WorktreeEntries {
fn paths(&self) -> &HashSet<Arc<Path>> {
fn paths(&self) -> &HashSet<RepoPath> {
self.paths.get_or_init(|| {
self.visible_entries
.iter()
.map(|e| (e.entry.path.clone()))
.map(|e| (e.entry.repo_path.clone()))
.collect()
})
}
@@ -165,8 +171,11 @@ impl GitPanel {
})
.detach();
cx.subscribe(&project, |this, _, event, cx| match event {
project::Event::WorktreeRemoved(id) => {
this.expanded_dir_ids.remove(id);
project::Event::GitRepositoryUpdated => {
this.update_visible_entries(None, None, cx);
}
project::Event::WorktreeRemoved(_id) => {
// this.expanded_dir_ids.remove(id);
this.update_visible_entries(None, None, cx);
cx.notify();
}
@@ -183,7 +192,7 @@ impl GitPanel {
project::Event::Closed => {
this.git_diff_editor_updates = Task::ready(());
this.reveal_in_editor = Task::ready(());
this.expanded_dir_ids.clear();
// this.expanded_dir_ids.clear();
this.visible_entries.clear();
this.git_diff_editor = None;
}
@@ -200,8 +209,7 @@ impl GitPanel {
pending_serialization: Task::ready(None),
visible_entries: Vec::new(),
current_modifiers: cx.modifiers(),
expanded_dir_ids: Default::default(),
// expanded_dir_ids: Default::default(),
width: Some(px(360.)),
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
scroll_handle,
@@ -288,16 +296,16 @@ impl GitPanel {
}
fn calculate_depth_and_difference(
entry: &Entry,
visible_worktree_entries: &HashSet<Arc<Path>>,
entry: &StatusEntry,
visible_worktree_entries: &HashSet<RepoPath>,
) -> (usize, usize) {
let (depth, difference) = entry
.path
.repo_path
.ancestors()
.skip(1) // Skip the entry itself
.find_map(|ancestor| {
if let Some(parent_entry) = visible_worktree_entries.get(ancestor) {
let entry_path_components_count = entry.path.components().count();
let entry_path_components_count = entry.repo_path.components().count();
let parent_path_components_count = parent_entry.components().count();
let difference = entry_path_components_count - parent_path_components_count;
let depth = parent_entry
@@ -432,13 +440,7 @@ impl GitPanel {
fn entry_count(&self) -> usize {
self.visible_entries
.iter()
.map(|worktree_entries| {
worktree_entries
.visible_entries
.iter()
.filter(|entry| entry.git_status.is_some())
.count()
})
.map(|worktree_entries| worktree_entries.visible_entries.len())
.sum()
}
@@ -446,7 +448,7 @@ impl GitPanel {
&self,
range: Range<usize>,
cx: &mut ViewContext<Self>,
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<Self>),
mut callback: impl FnMut(usize, EntryDetails, &mut ViewContext<Self>),
) {
let mut ix = 0;
for worktree_entries in &self.visible_entries {
@@ -468,11 +470,11 @@ impl GitPanel {
{
let snapshot = worktree.read(cx).snapshot();
let root_name = OsStr::new(snapshot.root_name());
let expanded_entry_ids = self
.expanded_dir_ids
.get(&snapshot.id())
.map(Vec::as_slice)
.unwrap_or(&[]);
// let expanded_entry_ids = self
// .expanded_dir_ids
// .get(&snapshot.id())
// .map(Vec::as_slice)
// .unwrap_or(&[]);
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
let entries = worktree_entries.paths();
@@ -483,22 +485,22 @@ impl GitPanel {
.enumerate()
{
let index = index_start + i;
let status = entry.git_status;
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
let status = entry.status;
let is_expanded = true; //expanded_entry_ids.binary_search(&entry.id).is_ok();
let (depth, difference) = Self::calculate_depth_and_difference(entry, entries);
let filename = match difference {
diff if diff > 1 => entry
.path
.repo_path
.iter()
.skip(entry.path.components().count() - diff)
.skip(entry.repo_path.components().count() - diff)
.collect::<PathBuf>()
.to_str()
.unwrap_or_default()
.to_string(),
_ => entry
.path
.repo_path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| root_name.to_string_lossy().to_string()),
@@ -506,16 +508,17 @@ impl GitPanel {
let details = EntryDetails {
filename,
display_name: entry.path.to_string_lossy().into_owned(),
kind: entry.kind,
display_name: entry.repo_path.to_string_lossy().into_owned(),
// TODO get it from StatusEntry?
kind: EntryKind::File,
is_expanded,
path: entry.path.clone(),
status,
path: entry.repo_path.clone(),
status: Some(status),
hunks: entry.hunks.clone(),
depth,
index,
};
callback(entry.id, details, cx);
callback(ix, details, cx);
}
}
ix = end_ix;
@@ -527,7 +530,7 @@ impl GitPanel {
fn update_visible_entries(
&mut self,
for_worktree: Option<WorktreeId>,
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
_new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
cx: &mut ViewContext<Self>,
) {
let project = self.project.read(cx);
@@ -549,24 +552,36 @@ impl GitPanel {
None => false,
});
for worktree in project.visible_worktrees(cx) {
let worktree_id = worktree.read(cx).id();
let snapshot = worktree.read(cx).snapshot();
let worktree_id = snapshot.id();
if for_worktree.is_some() && for_worktree != Some(worktree_id) {
continue;
}
let snapshot = worktree.read(cx).snapshot();
let mut visible_worktree_entries = snapshot
.entries(false, 0)
.filter(|entry| !entry.is_external)
.filter(|entry| entry.git_status.is_some())
.cloned()
.collect::<Vec<_>>();
snapshot.propagate_git_statuses(&mut visible_worktree_entries);
project::sort_worktree_entries(&mut visible_worktree_entries);
let mut visible_worktree_entries = Vec::new();
// Only use the first repository for now
let repositories = snapshot.repositories().take(1);
let mut work_directory = None;
for repository in repositories {
visible_worktree_entries.extend(repository.status());
work_directory = Some(worktree::WorkDirectory::clone(repository));
}
// TODO use the GitTraversal
// let mut visible_worktree_entries = snapshot
// .entries(false, 0)
// .filter(|entry| !entry.is_external)
// .filter(|entry| entry.git_status.is_some())
// .cloned()
// .collect::<Vec<_>>();
// snapshot.propagate_git_statuses(&mut visible_worktree_entries);
// project::sort_worktree_entries(&mut visible_worktree_entries);
if !visible_worktree_entries.is_empty() {
self.visible_entries.push(WorktreeEntries {
worktree_id,
work_directory: work_directory.unwrap(),
visible_entries: visible_worktree_entries
.into_iter()
.map(|entry| GitPanelEntry {
@@ -580,24 +595,25 @@ impl GitPanel {
}
self.visible_entries.extend(after_update);
if let Some((worktree_id, entry_id)) = new_selected_entry {
self.selected_item = self.visible_entries.iter().enumerate().find_map(
|(worktree_index, worktree_entries)| {
if worktree_entries.worktree_id == worktree_id {
worktree_entries
.visible_entries
.iter()
.position(|entry| entry.id == entry_id)
.map(|entry_index| {
worktree_index * worktree_entries.visible_entries.len()
+ entry_index
})
} else {
None
}
},
);
}
// TODO re-implement this
// if let Some((worktree_id, entry_id)) = new_selected_entry {
// self.selected_item = self.visible_entries.iter().enumerate().find_map(
// |(worktree_index, worktree_entries)| {
// if worktree_entries.worktree_id == worktree_id {
// worktree_entries
// .visible_entries
// .iter()
// .position(|entry| entry.id == entry_id)
// .map(|entry_index| {
// worktree_index * worktree_entries.visible_entries.len()
// + entry_index
// })
// } else {
// None
// }
// },
// );
// }
let project = self.project.downgrade();
self.git_diff_editor_updates = cx.spawn(|git_panel, mut cx| async move {
@@ -612,12 +628,14 @@ impl GitPanel {
.visible_entries
.iter()
.filter_map(|entry| {
let git_status = entry.git_status()?;
let git_status = entry.status;
let entry_hunks = entry.hunks.clone();
let (entry_path, unstaged_changes_task) =
project.update(cx, |project, cx| {
let entry_path =
project.path_for_entry(entry.id, cx)?;
let entry_path = ProjectPath {
worktree_id: worktree_entries.worktree_id,
path: worktree_entries.work_directory.unrelativize(&entry.repo_path)?,
};
let open_task =
project.open_path(entry_path.clone(), cx);
let unstaged_changes_task =
@@ -682,8 +700,8 @@ impl GitPanel {
)
.collect()
}
// TODO support conflicts display
GitFileStatus::Conflict => Vec::new(),
// TODO support these
GitFileStatus::Conflict | GitFileStatus::Deleted | GitFileStatus::Untracked => Vec::new(),
}
}).clone()
})?;
@@ -992,18 +1010,17 @@ impl GitPanel {
fn render_entry(
&self,
id: ProjectEntryId,
ix: usize,
selected: bool,
details: EntryDetails,
cx: &ViewContext<Self>,
) -> impl IntoElement {
let id = id.to_proto() as usize;
let checkbox_id = ElementId::Name(format!("checkbox_{}", id).into());
let checkbox_id = ElementId::Name(format!("checkbox_{}", ix).into());
let is_staged = ToggleState::Selected;
let handle = cx.view().downgrade();
h_flex()
.id(id)
.id(("git-panel-entry", ix))
.h(px(28.))
.w_full()
.pl(px(12. + 12. * details.depth as f32))
@@ -1019,7 +1036,7 @@ impl GitPanel {
this.child(git_status_icon(status))
})
.child(
ListItem::new(("label", id))
ListItem::new(details.path.0.clone())
.toggle_state(selected)
.child(h_flex().gap_1p5().child(details.display_name.clone()))
.on_click(move |e, cx| {

View File

@@ -44,10 +44,13 @@ const REMOVED_COLOR: Hsla = Hsla {
// TODO: Add updated status colors to theme
pub fn git_status_icon(status: GitFileStatus) -> impl IntoElement {
match status {
GitFileStatus::Added => Icon::new(IconName::SquarePlus).color(Color::Custom(ADDED_COLOR)),
GitFileStatus::Added | GitFileStatus::Untracked => {
Icon::new(IconName::SquarePlus).color(Color::Custom(ADDED_COLOR))
}
GitFileStatus::Modified => {
Icon::new(IconName::SquareDot).color(Color::Custom(MODIFIED_COLOR))
}
GitFileStatus::Conflict => Icon::new(IconName::Warning).color(Color::Custom(REMOVED_COLOR)),
GitFileStatus::Deleted => Icon::new(IconName::Warning).color(Color::Custom(REMOVED_COLOR)),
}
}

View File

@@ -185,8 +185,7 @@ macro_rules! actions {
#[doc = "The `"]
#[doc = stringify!($name)]
#[doc = "` action, see [`gpui::actions!`]"]
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)]
#[serde(crate = "gpui::private::serde")]
#[derive(::std::clone::Clone,::std::cmp::PartialEq, ::std::default::Default)]
pub struct $name;
gpui::__impl_action!($namespace, $name, $name,
@@ -211,14 +210,7 @@ macro_rules! action_as {
#[doc = "The `"]
#[doc = stringify!($name)]
#[doc = "` action, see [`gpui::actions!`]"]
#[derive(
::std::cmp::PartialEq,
::std::clone::Clone,
::std::default::Default,
::std::fmt::Debug,
gpui::private::serde_derive::Deserialize,
)]
#[serde(crate = "gpui::private::serde")]
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default)]
pub struct $name;
gpui::__impl_action!(

View File

@@ -33,9 +33,9 @@ use util::ResultExt;
use crate::{
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding, Keymap,
Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
Asset, AssetSource, BackgroundExecutor, Bounds, ClipboardItem, Context, DispatchPhase,
DisplayId, Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding,
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
@@ -1612,6 +1612,12 @@ pub struct AnyTooltip {
/// The absolute position of the mouse when the tooltip was deployed.
pub mouse_position: Point<Pixels>,
/// Whether the tooltitp can be hovered or not.
pub hoverable: bool,
/// Bounds of the element that triggered the tooltip appearance.
pub origin_bounds: Bounds<Pixels>,
}
/// A keystroke event, and potentially the associated action

View File

@@ -117,8 +117,9 @@ impl EntityMap {
pub fn read<T: 'static>(&self, model: &Model<T>) -> &T {
self.assert_valid_context(model);
self.entities[model.entity_id]
.downcast_ref()
self.entities
.get(model.entity_id)
.and_then(|entity| entity.downcast_ref())
.unwrap_or_else(|| double_lease_panic::<T>("read"))
}

View File

@@ -1417,19 +1417,6 @@ impl Interactivity {
None
};
let invalidate_tooltip = hitbox
.as_ref()
.map_or(true, |hitbox| !hitbox.bounds.contains(&cx.mouse_position()));
if invalidate_tooltip {
if let Some(active_tooltip) = element_state
.as_ref()
.and_then(|state| state.active_tooltip.as_ref())
{
*active_tooltip.borrow_mut() = None;
self.tooltip_id = None;
}
}
let scroll_offset = self.clamp_scroll_position(bounds, &style, cx);
let result = f(&style, scroll_offset, hitbox, cx);
(result, element_state)
@@ -1936,6 +1923,7 @@ impl Interactivity {
cx.on_mouse_event({
let active_tooltip = active_tooltip.clone();
let hitbox = hitbox.clone();
let source_bounds = hitbox.bounds;
let tooltip_id = self.tooltip_id;
move |_: &MouseMoveEvent, phase, cx| {
let is_hovered =
@@ -1965,6 +1953,8 @@ impl Interactivity {
tooltip: Some(AnyTooltip {
view: build_tooltip(cx),
mouse_position: cx.mouse_position(),
hoverable: tooltip_is_hoverable,
origin_bounds: source_bounds,
}),
_task: None,
});

View File

@@ -675,6 +675,7 @@ impl Element for InteractiveText {
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let hitbox = hitbox.clone();
let source_bounds = hitbox.bounds;
let active_tooltip = interactive_state.active_tooltip.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
let text_layout = text_layout.clone();
@@ -708,6 +709,8 @@ impl Element for InteractiveText {
tooltip: Some(AnyTooltip {
view: tooltip,
mouse_position: cx.mouse_position(),
hoverable: true,
origin_bounds: source_bounds,
}),
_task: None,
}

View File

@@ -1580,7 +1580,7 @@ impl LinuxClient for X11Client {
}
}
// Adatpted from:
// Adapted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {

View File

@@ -55,6 +55,7 @@ x11rb::atom_manager! {
WM_PROTOCOLS,
WM_DELETE_WINDOW,
WM_CHANGE_STATE,
_NET_WM_PID,
_NET_WM_NAME,
_NET_WM_STATE,
_NET_WM_STATE_MAXIMIZED_VERT,
@@ -436,6 +437,18 @@ impl X11WindowState {
// Collect errors during setup, so that window can be destroyed on failure.
let setup_result = maybe!({
let pid = std::process::id();
check_reply(
|| "X11 ChangeProperty for _NET_WM_PID failed.",
xcb.change_property32(
xproto::PropMode::REPLACE,
x_window,
atoms._NET_WM_PID,
xproto::AtomEnum::CARDINAL,
&[pid],
),
)?;
if let Some(size) = params.window_min_size {
let mut size_hints = WmSizeHints::new();
let min_size = (size.width.0 as i32, size.height.0 as i32);

View File

@@ -322,7 +322,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
let mut chars_with_shift = chars_for_modified_key(native_event.keyCode(), SHIFT_MOD);
let always_use_cmd_layout = always_use_command_layout();
// Handle Dvorak+QWERTY / Russian / Armeniam
// Handle Dvorak+QWERTY / Russian / Armenian
if command || always_use_cmd_layout {
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), CMD_MOD);
let chars_with_both =

View File

@@ -47,6 +47,7 @@ pub(crate) fn handle_msg(
WM_MOUSEMOVE => handle_mouse_move_msg(handle, lparam, wparam, state_ptr),
WM_MOUSELEAVE => handle_mouse_leave_msg(state_ptr),
WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
WM_NCMOUSELEAVE => handle_nc_mouse_leave_msg(state_ptr),
WM_NCLBUTTONDOWN => {
handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
}
@@ -314,6 +315,18 @@ fn handle_mouse_move_msg(
Some(1)
}
fn handle_nc_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
let mut lock = state_ptr.state.borrow_mut();
lock.hovered = false;
if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
drop(lock);
callback(false);
state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
}
Some(0)
}
fn handle_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
let mut lock = state_ptr.state.borrow_mut();
lock.hovered = false;
@@ -971,6 +984,27 @@ fn handle_nc_mouse_move_msg(
return None;
}
let mut lock = state_ptr.state.borrow_mut();
if !lock.hovered {
lock.hovered = true;
unsafe {
TrackMouseEvent(&mut TRACKMOUSEEVENT {
cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
dwFlags: TME_LEAVE | TME_NONCLIENT,
hwndTrack: handle,
dwHoverTime: HOVER_DEFAULT,
})
.log_err()
};
if let Some(mut callback) = lock.callbacks.hovered_status_change.take() {
drop(lock);
callback(true);
state_ptr.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
}
} else {
drop(lock);
}
let mut lock = state_ptr.state.borrow_mut();
if let Some(mut callback) = lock.callbacks.input.take() {
let scale_factor = lock.scale_factor;

View File

@@ -81,6 +81,12 @@ impl<'a> PartialEq<&'a str> for SharedString {
}
}
impl From<&SharedString> for SharedString {
fn from(value: &SharedString) -> Self {
value.clone()
}
}
impl From<SharedString> for Arc<str> {
fn from(val: SharedString) -> Self {
match val.0 {

View File

@@ -356,7 +356,7 @@ impl WindowTextSystem {
});
}
let layout = self.layout_line(text.as_ref(), font_size, runs)?;
let layout = self.layout_line(&text, font_size, runs)?;
Ok(ShapedLine {
layout,
@@ -483,12 +483,16 @@ impl WindowTextSystem {
/// Subsets of the line can be styled independently with the `runs` parameter.
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
/// can be painted directly.
pub fn layout_line(
pub fn layout_line<Text>(
&self,
text: &str,
text: Text,
font_size: Pixels,
runs: &[TextRun],
) -> Result<Arc<LineLayout>> {
) -> Result<Arc<LineLayout>>
where
Text: AsRef<str>,
SharedString: From<Text>,
{
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
for run in runs.iter() {
let font_id = self.resolve_font(&run.font);

View File

@@ -1,4 +1,4 @@
use crate::{point, px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
use crate::{point, px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString, Size};
use collections::FxHashMap;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
@@ -420,15 +420,19 @@ impl LineLayoutCache {
curr_frame.used_wrapped_lines.clear();
}
pub fn layout_wrapped_line(
pub fn layout_wrapped_line<Text>(
&self,
text: &str,
text: Text,
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
) -> Arc<WrappedLineLayout> {
) -> Arc<WrappedLineLayout>
where
Text: AsRef<str>,
SharedString: From<Text>,
{
let key = &CacheKeyRef {
text,
text: text.as_ref(),
font_size,
runs,
wrap_width,
@@ -449,8 +453,8 @@ impl LineLayoutCache {
layout
} else {
drop(current_frame);
let unwrapped_layout = self.layout_line(text, font_size, runs);
let text = SharedString::from(text);
let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs);
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
} else {
@@ -462,7 +466,7 @@ impl LineLayoutCache {
wrap_width,
});
let key = Arc::new(CacheKey {
text: text.into(),
text,
font_size,
runs: SmallVec::from(runs),
wrap_width,
@@ -478,9 +482,18 @@ impl LineLayoutCache {
}
}
pub fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> Arc<LineLayout> {
pub fn layout_line<Text>(
&self,
text: Text,
font_size: Pixels,
runs: &[FontRun],
) -> Arc<LineLayout>
where
Text: AsRef<str>,
SharedString: From<Text>,
{
let key = &CacheKeyRef {
text,
text: text.as_ref(),
font_size,
runs,
wrap_width: None,
@@ -497,9 +510,13 @@ impl LineLayoutCache {
current_frame.used_lines.push(key);
layout
} else {
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
let text = SharedString::from(text);
let layout = Arc::new(
self.platform_text_system
.layout_line(&text, font_size, runs),
);
let key = Arc::new(CacheKey {
text: text.into(),
text,
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
@@ -524,7 +541,7 @@ trait AsCacheKeyRef {
#[derive(Clone, Debug, Eq)]
struct CacheKey {
text: String,
text: SharedString,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,

View File

@@ -1586,6 +1586,19 @@ impl<'a> WindowContext<'a> {
}
}
// Element's parent can get hidden (e.g. via the `visible_on_hover` method),
// and element's `paint` won't be called (ergo, mouse listeners also won't be active) to detect that the tooltip has to be removed.
// Ensure it's not stuck around in such cases.
let invalidate_tooltip = !tooltip_request
.tooltip
.origin_bounds
.contains(&self.mouse_position())
&& (!tooltip_request.tooltip.hoverable
|| !tooltip_bounds.contains(&self.mouse_position()));
if invalidate_tooltip {
return None;
}
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx));
self.window.tooltip_bounds = Some(TooltipBounds {
@@ -4867,6 +4880,8 @@ pub enum ElementId {
FocusHandle(FocusId),
/// A combination of a name and an integer.
NamedInteger(SharedString, usize),
/// A path
Path(Arc<std::path::Path>),
}
impl Display for ElementId {
@@ -4878,6 +4893,7 @@ impl Display for ElementId {
ElementId::FocusHandle(_) => write!(f, "FocusHandle")?,
ElementId::NamedInteger(s, i) => write!(f, "{}-{}", s, i)?,
ElementId::Uuid(uuid) => write!(f, "{}", uuid)?,
ElementId::Path(path) => write!(f, "{}", path.display())?,
}
Ok(())
@@ -4914,6 +4930,12 @@ impl From<SharedString> for ElementId {
}
}
impl From<Arc<std::path::Path>> for ElementId {
fn from(path: Arc<std::path::Path>) -> Self {
ElementId::Path(path)
}
}
impl From<&'static str> for ElementId {
fn from(name: &'static str) -> Self {
ElementId::Name(name.into())

View File

@@ -96,12 +96,18 @@ impl Item for ImageView {
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
let project_path = self.image_item.read(cx).project_path(cx);
let label_color = if ItemSettings::get_global(cx).git_status {
let git_status = self
.project
.read(cx)
.project_path_git_status(&project_path, cx);
self.project
.read(cx)
.entry_for_path(&project_path, cx)
.map(|entry| {
entry_git_aware_label_color(entry.git_status, entry.is_ignored, params.selected)
entry_git_aware_label_color(git_status, entry.is_ignored, params.selected)
})
.unwrap_or_else(|| params.text_color())
} else {

View File

@@ -208,10 +208,10 @@ pub struct Diagnostic {
/// The human-readable message associated with this diagnostic.
pub message: String,
/// An id that identifies the group to which this diagnostic belongs.
/// 0 is used for diagnostics that do not come from a language server.
///
/// When a language server produces a diagnostic with
/// one or more associated diagnostics, those diagnostics are all
/// assigned a single group ID.
/// When a language server produces a diagnostic with one or more associated diagnostics, those
/// diagnostics are all assigned a single group ID.
pub group_id: usize,
/// Whether this diagnostic is the primary diagnostic for its group.
///
@@ -3943,14 +3943,14 @@ impl BufferSnapshot {
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
where
T: 'a + Clone + ToOffset,
O: 'a + FromAnchor + Ord,
O: 'a + FromAnchor,
{
let mut iterators: Vec<_> = self
.diagnostics
.iter()
.map(|(_, collection)| {
collection
.range::<T, O>(search_range.clone(), self, true, reversed)
.range::<T, text::Anchor>(search_range.clone(), self, true, reversed)
.peekable()
})
.collect();
@@ -3964,7 +3964,7 @@ impl BufferSnapshot {
let cmp = a
.range
.start
.cmp(&b.range.start)
.cmp(&b.range.start, self)
// when range is equal, sort by diagnostic severity
.then(a.diagnostic.severity.cmp(&b.diagnostic.severity))
// and stabilize order with group_id
@@ -3975,7 +3975,13 @@ impl BufferSnapshot {
cmp
}
})?;
iterators[next_ix].next()
iterators[next_ix]
.next()
.map(|DiagnosticEntry { range, diagnostic }| DiagnosticEntry {
diagnostic,
range: FromAnchor::from_anchor(&range.start, self)
..FromAnchor::from_anchor(&range.end, self),
})
})
}
@@ -4013,12 +4019,12 @@ impl BufferSnapshot {
}
/// Returns an iterator over the diagnostics for the given group.
pub fn diagnostic_group<'a, O>(
&'a self,
pub fn diagnostic_group<O>(
&self,
group_id: usize,
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
) -> impl Iterator<Item = DiagnosticEntry<O>> + '_
where
O: 'a + FromAnchor,
O: FromAnchor + 'static,
{
self.diagnostics
.iter()

View File

@@ -52,7 +52,7 @@ pub struct Summary {
}
impl DiagnosticEntry<PointUtf16> {
/// Returns a raw LSP diagnostic ssed to provide diagnostic context to LSP
/// Returns a raw LSP diagnostic used to provide diagnostic context to LSP
/// codeAction request
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
let code = self

View File

@@ -1850,16 +1850,20 @@ pub fn point_from_lsp(point: lsp::Position) -> Unclipped<PointUtf16> {
}
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
lsp::Range {
start: point_to_lsp(range.start),
end: point_to_lsp(range.end),
let mut start = point_to_lsp(range.start);
let mut end = point_to_lsp(range.end);
if start > end {
log::error!("range_to_lsp called with inverted range {start:?}-{end:?}");
mem::swap(&mut start, &mut end);
}
lsp::Range { start, end }
}
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
let mut start = point_from_lsp(range.start);
let mut end = point_from_lsp(range.end);
if start > end {
log::warn!("range_from_lsp called with inverted range {start:?}-{end:?}");
mem::swap(&mut start, &mut end);
}
start..end

View File

@@ -68,6 +68,7 @@ impl CloudModel {
anthropic::Model::Claude3Opus
| anthropic::Model::Claude3Sonnet
| anthropic::Model::Claude3Haiku
| anthropic::Model::Claude3_5Haiku
| anthropic::Model::Custom { .. } => {
LanguageModelAvailability::RequiresPlan(Plan::ZedPro)
}

View File

@@ -2,8 +2,8 @@ use std::sync::Arc;
use feature_flags::ZedPro;
use gpui::{
Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Task,
View, WeakView,
Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
Subscription, Task, View, WeakView,
};
use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
use picker::{Picker, PickerDelegate};
@@ -17,6 +17,10 @@ type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &AppContext) + 'static>
pub struct LanguageModelSelector {
picker: View<Picker<LanguageModelPickerDelegate>>,
/// The task used to update the picker's matches when there is a change to
/// the language model registry.
update_matches_task: Option<Task<()>>,
_subscriptions: Vec<Subscription>,
}
impl LanguageModelSelector {
@@ -26,7 +30,51 @@ impl LanguageModelSelector {
) -> Self {
let on_model_changed = Arc::new(on_model_changed);
let all_models = LanguageModelRegistry::global(cx)
let all_models = Self::all_models(cx);
let delegate = LanguageModelPickerDelegate {
language_model_selector: cx.view().downgrade(),
on_model_changed: on_model_changed.clone(),
all_models: all_models.clone(),
filtered_models: all_models,
selected_index: 0,
};
let picker =
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
LanguageModelSelector {
picker,
update_matches_task: None,
_subscriptions: vec![cx.subscribe(
&LanguageModelRegistry::global(cx),
Self::handle_language_model_registry_event,
)],
}
}
fn handle_language_model_registry_event(
&mut self,
_registry: Model<LanguageModelRegistry>,
event: &language_model::Event,
cx: &mut ViewContext<Self>,
) {
match event {
language_model::Event::ProviderStateChanged
| language_model::Event::AddedProvider(_)
| language_model::Event::RemovedProvider(_) => {
let task = self.picker.update(cx, |this, cx| {
let query = this.query(cx);
this.delegate.all_models = Self::all_models(cx);
this.delegate.update_matches(query, cx)
});
self.update_matches_task = Some(task);
}
_ => {}
}
}
fn all_models(cx: &AppContext) -> Vec<ModelInfo> {
LanguageModelRegistry::global(cx)
.read(cx)
.providers()
.iter()
@@ -44,20 +92,7 @@ impl LanguageModelSelector {
}
})
})
.collect::<Vec<_>>();
let delegate = LanguageModelPickerDelegate {
language_model_selector: cx.view().downgrade(),
on_model_changed: on_model_changed.clone(),
all_models: all_models.clone(),
filtered_models: all_models,
selected_index: 0,
};
let picker =
cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
LanguageModelSelector { picker }
.collect::<Vec<_>>()
}
}
@@ -152,25 +187,25 @@ impl PickerDelegate for LanguageModelPickerDelegate {
let llm_registry = LanguageModelRegistry::global(cx);
let configured_models: Vec<_> = llm_registry
let configured_providers = llm_registry
.read(cx)
.providers()
.iter()
.filter(|provider| provider.is_authenticated(cx))
.map(|provider| provider.id())
.collect();
.collect::<Vec<_>>();
cx.spawn(|this, mut cx| async move {
let filtered_models = cx
.background_executor()
.spawn(async move {
let displayed_models = if configured_models.is_empty() {
let displayed_models = if configured_providers.is_empty() {
all_models
} else {
all_models
.into_iter()
.filter(|model_info| {
configured_models.contains(&model_info.model.provider_id())
configured_providers.contains(&model_info.model.provider_id())
})
.collect::<Vec<_>>()
};

View File

@@ -52,6 +52,8 @@ pub struct AvailableModel {
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
pub max_output_tokens: Option<u32>,
pub default_temperature: Option<f32>,
#[serde(default)]
pub extra_beta_headers: Vec<String>,
}
pub struct AnthropicLanguageModelProvider {
@@ -202,6 +204,7 @@ impl LanguageModelProvider for AnthropicLanguageModelProvider {
}),
max_output_tokens: model.max_output_tokens,
default_temperature: model.default_temperature,
extra_beta_headers: model.extra_beta_headers.clone(),
},
);
}

View File

@@ -94,6 +94,9 @@ pub struct AvailableModel {
pub cache_configuration: Option<LanguageModelCacheConfiguration>,
/// The default temperature to use for this model.
pub default_temperature: Option<f32>,
/// Any extra beta headers to provide when using the model.
#[serde(default)]
pub extra_beta_headers: Vec<String>,
}
struct GlobalRefreshLlmTokenListener(Model<RefreshLlmTokenListener>);
@@ -323,6 +326,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
}),
default_temperature: model.default_temperature,
max_output_tokens: model.max_output_tokens,
extra_beta_headers: model.extra_beta_headers.clone(),
}),
AvailableProvider::OpenAi => CloudModel::OpenAi(open_ai::Model::Custom {
name: model.name.clone(),

View File

@@ -97,6 +97,7 @@ impl AnthropicSettingsContent {
cache_configuration,
max_output_tokens,
default_temperature,
extra_beta_headers,
} => Some(provider::anthropic::AvailableModel {
name,
display_name,
@@ -111,6 +112,7 @@ impl AnthropicSettingsContent {
),
max_output_tokens,
default_temperature,
extra_beta_headers,
}),
_ => None,
})

View File

@@ -128,13 +128,20 @@ impl SyntaxTreeView {
fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
// Find which excerpt the cursor is in, and the position within that excerpted buffer.
let editor_state = self.editor.as_mut()?;
let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
let snapshot = editor_state
.editor
.update(cx, |editor, cx| editor.snapshot(cx));
let (excerpt, buffer, range) = editor_state.editor.update(cx, |editor, cx| {
let selection = editor.selections.last::<usize>(cx);
let selection_range = editor.selections.last::<usize>(cx).range();
editor
.buffer()
.read(cx)
.range_to_buffer_ranges(selection_range, cx)
.pop()
let selection_head = selection.head();
let multi_buffer = editor.buffer().read(cx);
let excerpt = snapshot
.buffer_snapshot
.excerpt_containing(selection_head..selection_head)?;
let range = excerpt.map_range_to_buffer(selection_range);
let buffer = multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone();
Some((excerpt, buffer, range))
})?;
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
@@ -143,16 +150,16 @@ impl SyntaxTreeView {
.active_buffer
.get_or_insert_with(|| BufferState {
buffer: buffer.clone(),
excerpt_id,
excerpt_id: excerpt.id(),
active_layer: None,
});
let mut prev_layer = None;
if did_reparse {
prev_layer = buffer_state.active_layer.take();
}
if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt.id() {
buffer_state.buffer = buffer.clone();
buffer_state.excerpt_id = excerpt_id;
buffer_state.excerpt_id = excerpt.id();
buffer_state.active_layer = None;
}

View File

@@ -2,6 +2,7 @@ name = "Markdown"
grammar = "markdown"
path_suffixes = ["md", "mdx", "mdwn", "markdown", "MD"]
word_characters = ["-"]
block_comment = ["<!-- ", " -->"]
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },

View File

@@ -770,6 +770,12 @@ impl PyLspAdapter {
}
}
const BINARY_DIR: &str = if cfg!(target_os = "windows") {
"Scripts"
} else {
"bin"
};
#[async_trait(?Send)]
impl LspAdapter for PyLspAdapter {
fn name(&self) -> LanguageServerName {
@@ -811,7 +817,7 @@ impl LspAdapter for PyLspAdapter {
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
let pip_path = venv.join("bin").join("pip3");
let pip_path = venv.join(BINARY_DIR).join("pip3");
ensure!(
util::command::new_smol_command(pip_path.as_path())
.arg("install")
@@ -842,7 +848,7 @@ impl LspAdapter for PyLspAdapter {
.success(),
"pylsp-mypy installation failed"
);
let pylsp = venv.join("bin").join("pylsp");
let pylsp = venv.join(BINARY_DIR).join("pylsp");
Ok(LanguageServerBinary {
path: pylsp,
env: None,
@@ -856,7 +862,7 @@ impl LspAdapter for PyLspAdapter {
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let venv = self.base_venv(delegate).await.ok()?;
let pylsp = venv.join("bin").join("pylsp");
let pylsp = venv.join(BINARY_DIR).join("pylsp");
Some(LanguageServerBinary {
path: pylsp,
env: None,

View File

@@ -253,49 +253,51 @@ impl LspAdapter for RustLspAdapter {
.as_ref()
.and_then(|detail| detail.detail.as_ref())
.or(completion.detail.as_ref())
.map(ToOwned::to_owned);
.map(|detail| detail.trim());
let function_signature = completion
.label_details
.as_ref()
.and_then(|detail| detail.description.as_ref())
.or(completion.detail.as_ref())
.map(ToOwned::to_owned);
match completion.kind {
Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => {
.and_then(|detail| detail.description.as_deref())
.or(completion.detail.as_deref());
match (detail, completion.kind) {
(Some(detail), Some(lsp::CompletionItemKind::FIELD)) => {
let name = &completion.label;
let text = format!("{}: {}", name, detail.unwrap());
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
let runs = language.highlight_text(&source, 11..11 + text.len());
let text = format!("{name}: {detail}");
let prefix = "struct S { ";
let source = Rope::from(format!("{prefix}{text} }}"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..name.len(),
});
}
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
if detail.is_some()
&& completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) =>
{
(
Some(detail),
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
let name = &completion.label;
let text = format!(
"{}: {}",
name,
completion.detail.as_ref().or(detail.as_ref()).unwrap()
completion.detail.as_deref().unwrap_or(detail)
);
let source = Rope::from(format!("let {} = ();", text).as_str());
let runs = language.highlight_text(&source, 4..4 + text.len());
let prefix = "let ";
let source = Rope::from(format!("{prefix}{text} = ();"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..name.len(),
});
}
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
if detail.is_some() =>
{
(
Some(detail),
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
) => {
static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap());
let detail = detail.unwrap();
const FUNCTION_PREFIXES: [&str; 6] = [
"async fn",
"async unsafe fn",
@@ -315,10 +317,11 @@ impl LspAdapter for RustLspAdapter {
// fn keyword should be followed by opening parenthesis.
if let Some((prefix, suffix)) = fn_keyword {
let mut text = REGEX.replace(&completion.label, suffix).to_string();
let source = Rope::from(format!("{prefix} {} {{}}", text).as_str());
let source = Rope::from(format!("{prefix} {text} {{}}"));
let run_start = prefix.len() + 1;
let runs = language.highlight_text(&source, run_start..run_start + text.len());
if detail.starts_with(" (") {
if detail.starts_with("(") {
text.push(' ');
text.push_str(&detail);
}
@@ -342,7 +345,7 @@ impl LspAdapter for RustLspAdapter {
});
}
}
Some(kind) => {
(_, Some(kind)) => {
let highlight_name = match kind {
lsp::CompletionItemKind::STRUCT
| lsp::CompletionItemKind::INTERFACE
@@ -356,9 +359,9 @@ impl LspAdapter for RustLspAdapter {
};
let mut label = completion.label.clone();
if let Some(detail) = detail.filter(|detail| detail.starts_with(" (")) {
use std::fmt::Write;
write!(label, "{detail}").ok()?;
if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) {
label.push(' ');
label.push_str(detail);
}
let mut label = CodeLabel::plain(label, None);
if let Some(highlight_name) = highlight_name {
@@ -883,7 +886,7 @@ mod tests {
kind: Some(lsp::CompletionItemKind::FUNCTION),
label: "hello(…)".to_string(),
label_details: Some(CompletionItemLabelDetails {
detail: Some(" (use crate::foo)".into()),
detail: Some("(use crate::foo)".into()),
description: Some("fn(&mut Option<T>) -> Vec<T>".to_string())
}),
..Default::default()

View File

@@ -6,8 +6,8 @@ use gpui::{
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
TextStyle, TextStyleRefinement, View,
Point, Render, Stateful, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout,
TextRun, TextStyle, TextStyleRefinement, View,
};
use language::{Language, LanguageRegistry, Rope};
use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
@@ -785,8 +785,52 @@ impl IntoElement for MarkdownElement {
}
}
enum AnyDiv {
Div(Div),
Stateful(Stateful<Div>),
}
impl AnyDiv {
fn into_any_element(self) -> AnyElement {
match self {
Self::Div(div) => div.into_any_element(),
Self::Stateful(div) => div.into_any_element(),
}
}
}
impl From<Div> for AnyDiv {
fn from(value: Div) -> Self {
Self::Div(value)
}
}
impl From<Stateful<Div>> for AnyDiv {
fn from(value: Stateful<Div>) -> Self {
Self::Stateful(value)
}
}
impl Styled for AnyDiv {
fn style(&mut self) -> &mut StyleRefinement {
match self {
Self::Div(div) => div.style(),
Self::Stateful(div) => div.style(),
}
}
}
impl ParentElement for AnyDiv {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
match self {
Self::Div(div) => div.extend(elements),
Self::Stateful(div) => div.extend(elements),
}
}
}
struct MarkdownElementBuilder {
div_stack: Vec<Div>,
div_stack: Vec<AnyDiv>,
rendered_lines: Vec<RenderedLine>,
pending_line: PendingLine,
rendered_links: Vec<RenderedLink>,
@@ -812,7 +856,7 @@ struct ListStackEntry {
impl MarkdownElementBuilder {
fn new(base_text_style: TextStyle, syntax_theme: Arc<SyntaxTheme>) -> Self {
Self {
div_stack: vec![div().debug_selector(|| "inner".into())],
div_stack: vec![div().debug_selector(|| "inner".into()).into()],
rendered_lines: Vec::new(),
pending_line: PendingLine::default(),
rendered_links: Vec::new(),
@@ -841,11 +885,12 @@ impl MarkdownElementBuilder {
self.text_style_stack.pop();
}
fn push_div(&mut self, mut div: Div, range: &Range<usize>, markdown_end: usize) {
fn push_div(&mut self, div: impl Into<AnyDiv>, range: &Range<usize>, markdown_end: usize) {
let mut div = div.into();
self.flush_text();
if range.start == 0 {
//first element, remove top margin
// Remove the top margin on the first element.
div.style().refine(&StyleRefinement {
margin: gpui::EdgesRefinement {
top: Some(Length::Definite(px(0.).into())),
@@ -856,6 +901,7 @@ impl MarkdownElementBuilder {
..Default::default()
});
}
if range.end == markdown_end {
div.style().refine(&StyleRefinement {
margin: gpui::EdgesRefinement {
@@ -867,12 +913,13 @@ impl MarkdownElementBuilder {
..Default::default()
});
}
self.div_stack.push(div);
}
fn pop_div(&mut self) {
self.flush_text();
let div = self.div_stack.pop().unwrap().into_any();
let div = self.div_stack.pop().unwrap().into_any_element();
self.div_stack.last_mut().unwrap().extend(iter::once(div));
}
@@ -973,7 +1020,7 @@ impl MarkdownElementBuilder {
debug_assert_eq!(self.div_stack.len(), 1);
self.flush_text();
RenderedMarkdown {
element: self.div_stack.pop().unwrap().into_any(),
element: self.div_stack.pop().unwrap().into_any_element(),
text: RenderedText {
lines: self.rendered_lines.into(),
links: self.rendered_links.into(),

View File

@@ -1667,42 +1667,6 @@ impl MultiBuffer {
})
}
pub fn range_to_buffer_ranges<T: ToOffset>(
&self,
range: Range<T>,
cx: &AppContext,
) -> Vec<(Model<Buffer>, Range<usize>, ExcerptId)> {
let snapshot = self.read(cx);
let start = range.start.to_offset(&snapshot);
let end = range.end.to_offset(&snapshot);
let mut result = Vec::new();
let mut cursor = snapshot.excerpts.cursor::<usize>(&());
cursor.seek(&start, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
}
while let Some(excerpt) = cursor.item() {
if *cursor.start() > end {
break;
}
let mut end_before_newline = cursor.end(&());
if excerpt.has_trailing_newline {
end_before_newline -= 1;
}
let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start());
let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start());
let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone();
result.push((buffer, start..end, excerpt.id));
cursor.next(&());
}
result
}
pub fn remove_excerpts(
&mut self,
excerpt_ids: impl IntoIterator<Item = ExcerptId>,
@@ -3907,35 +3871,43 @@ impl MultiBufferSnapshot {
.any(|excerpt| excerpt.buffer.has_diagnostics())
}
pub fn diagnostic_group<'a, O>(
&'a self,
pub fn diagnostic_group(
&self,
group_id: usize,
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
where
O: text::FromAnchor + 'a,
{
self.as_singleton()
.into_iter()
.flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id))
) -> impl Iterator<Item = DiagnosticEntry<Anchor>> + '_ {
self.all_excerpts().flat_map(move |excerpt| {
excerpt.buffer().diagnostic_group(group_id).map(
move |DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: self.anchor_in_excerpt(excerpt.id(), range.start).unwrap()
..self.anchor_in_excerpt(excerpt.id(), range.end).unwrap(),
},
)
})
}
pub fn diagnostics_in_range<'a, T, O>(
pub fn diagnostics_in_range<'a, T>(
&'a self,
range: Range<T>,
reversed: bool,
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
) -> impl Iterator<Item = DiagnosticEntry<Anchor>> + 'a
where
T: 'a + ToOffset,
O: 'a + text::FromAnchor + Ord,
{
self.as_singleton()
.into_iter()
.flat_map(move |(_, _, buffer)| {
buffer.diagnostics_in_range(
range.start.to_offset(self)..range.end.to_offset(self),
reversed,
)
})
let mut ranges = self.range_to_buffer_ranges(range).collect::<Vec<_>>();
if reversed {
ranges.reverse();
}
ranges.into_iter().flat_map(move |(excerpt, range)| {
let excerpt_id = excerpt.id();
excerpt.buffer().diagnostics_in_range(range, reversed).map(
move |DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
diagnostic,
range: self.anchor_in_excerpt(excerpt_id, range.start).unwrap()
..self.anchor_in_excerpt(excerpt_id, range.end).unwrap(),
},
)
})
}
pub fn syntax_ancestor<T: ToOffset>(
@@ -4185,44 +4157,60 @@ impl MultiBufferSnapshot {
})
}
/// Returns excerpts overlapping the given ranges. If range spans multiple excerpts returns one range for each excerpt
/// Returns excerpts overlapping the given range. If range spans multiple excerpts returns one
/// range for each excerpt.
///
/// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers.
/// Each returned excerpt's range is in the coordinate space of its source buffer.
pub fn excerpts_in_ranges(
pub fn range_to_buffer_ranges<T: ToOffset>(
&self,
ranges: impl IntoIterator<Item = Range<Anchor>>,
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, Range<usize>)> {
let mut ranges = ranges.into_iter().map(|range| range.to_offset(self));
range: Range<T>,
) -> impl Iterator<Item = (MultiBufferExcerpt<'_>, Range<usize>)> {
self.disjoint_ranges_to_buffer_ranges(iter::once(range))
}
/// Returns excerpts overlapping the given ranges, which must be disjoint and sorted by start position.
/// If range spans multiple excerpts returns one range for each excerpt
///
/// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers.
/// Each returned excerpt's range is in the coordinate space of its source buffer.
pub fn disjoint_ranges_to_buffer_ranges<T: ToOffset>(
&self,
ranges: impl IntoIterator<Item = Range<T>>,
) -> impl Iterator<Item = (MultiBufferExcerpt<'_>, Range<usize>)> {
let mut ranges = ranges.into_iter();
let mut cursor = self.excerpts.cursor::<(usize, Point)>(&());
cursor.next(&());
let mut current_range = ranges.next();
iter::from_fn(move || {
let range = current_range.clone()?;
if range.start >= cursor.end(&()).0 {
cursor.seek_forward(&range.start, Bias::Right, &());
if range.start == self.len() {
let range = current_range.as_ref()?;
let start = range.start.to_offset(self);
let end = range.end.to_offset(self);
if start >= cursor.end(&()).0 {
cursor.seek_forward(&start, Bias::Right, &());
if start == self.len() {
cursor.prev(&());
}
}
let excerpt = cursor.item()?;
let range_start_in_excerpt = cmp::max(range.start, cursor.start().0);
let range_start_in_excerpt = cmp::max(start, cursor.start().0);
let range_end_in_excerpt = if excerpt.has_trailing_newline {
cmp::min(range.end, cursor.end(&()).0 - 1)
cmp::min(end, cursor.end(&()).0 - 1)
} else {
cmp::min(range.end, cursor.end(&()).0)
cmp::min(end, cursor.end(&()).0)
};
let buffer_range = MultiBufferExcerpt::new(excerpt, *cursor.start())
let multi_buffer_excerpt = MultiBufferExcerpt::new(excerpt, *cursor.start());
let buffer_range = multi_buffer_excerpt
.map_range_to_buffer(range_start_in_excerpt..range_end_in_excerpt);
if range.end > cursor.end(&()).0 {
if end > cursor.end(&()).0 {
cursor.next(&());
} else {
current_range = ranges.next();
}
Some((excerpt.id, &excerpt.buffer, buffer_range))
Some((multi_buffer_excerpt, buffer_range))
})
}
@@ -4664,6 +4652,10 @@ impl<'a> MultiBufferExcerpt<'a> {
self.excerpt.id
}
pub fn buffer_id(&self) -> BufferId {
self.excerpt.buffer_id
}
pub fn start_anchor(&self) -> Anchor {
Anchor {
buffer_id: Some(self.excerpt.buffer_id),

View File

@@ -1234,14 +1234,15 @@ fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
start_ix..end_ix
);
let excerpted_buffer_ranges = multibuffer
.read(cx)
.range_to_buffer_ranges(start_ix..end_ix, cx);
let snapshot = multibuffer.read(cx).snapshot(cx);
let excerpted_buffer_ranges = snapshot
.range_to_buffer_ranges(start_ix..end_ix)
.collect::<Vec<_>>();
let excerpted_buffers_text = excerpted_buffer_ranges
.iter()
.map(|(buffer, buffer_range, _)| {
buffer
.read(cx)
.map(|(excerpt, buffer_range)| {
excerpt
.buffer()
.text_for_range(buffer_range.clone())
.collect::<String>()
})
@@ -1489,7 +1490,7 @@ fn test_excerpts_in_ranges_no_ranges(cx: &mut AppContext) {
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
let mut excerpts = snapshot.excerpts_in_ranges(iter::from_fn(|| None));
let mut excerpts = snapshot.disjoint_ranges_to_buffer_ranges::<usize>(iter::from_fn(|| None));
assert!(excerpts.next().is_none());
}
@@ -1588,12 +1589,12 @@ fn test_excerpts_in_ranges_range_inside_the_excerpt(cx: &mut AppContext) {
)];
let excerpts = snapshot
.excerpts_in_ranges(vec![range.clone()].into_iter())
.map(|(excerpt_id, buffer, actual_range)| {
.disjoint_ranges_to_buffer_ranges(vec![range.clone()].into_iter())
.map(|(excerpt, actual_range)| {
(
excerpt_id,
buffer.remote_id(),
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
excerpt.id(),
excerpt.buffer().remote_id(),
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
)
})
.collect_vec();
@@ -1653,12 +1654,12 @@ fn test_excerpts_in_ranges_range_crosses_excerpts_boundary(cx: &mut AppContext)
];
let excerpts = snapshot
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
.map(|(excerpt_id, buffer, actual_range)| {
.disjoint_ranges_to_buffer_ranges(vec![expected_range.clone()].into_iter())
.map(|(excerpt, actual_range)| {
(
excerpt_id,
buffer.remote_id(),
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
excerpt.id(),
excerpt.buffer().remote_id(),
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
)
})
.collect_vec();
@@ -1729,12 +1730,12 @@ fn test_excerpts_in_ranges_range_encloses_excerpt(cx: &mut AppContext) {
];
let excerpts = snapshot
.excerpts_in_ranges(vec![expected_range.clone()].into_iter())
.map(|(excerpt_id, buffer, actual_range)| {
.disjoint_ranges_to_buffer_ranges(vec![expected_range.clone()].into_iter())
.map(|(excerpt, actual_range)| {
(
excerpt_id,
buffer.remote_id(),
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
excerpt.id(),
excerpt.buffer().remote_id(),
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
)
})
.collect_vec();
@@ -1795,12 +1796,12 @@ fn test_excerpts_in_ranges_multiple_ranges(cx: &mut AppContext) {
});
let excerpts = snapshot
.excerpts_in_ranges(ranges)
.map(|(excerpt_id, buffer, actual_range)| {
.disjoint_ranges_to_buffer_ranges(ranges)
.map(|(excerpt, actual_range)| {
(
excerpt_id,
buffer.remote_id(),
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
excerpt.id(),
excerpt.buffer().remote_id(),
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
)
})
.collect_vec();
@@ -1861,12 +1862,12 @@ fn test_excerpts_in_ranges_range_ends_at_excerpt_end(cx: &mut AppContext) {
];
let excerpts = snapshot
.excerpts_in_ranges(ranges.into_iter())
.map(|(excerpt_id, buffer, actual_range)| {
.disjoint_ranges_to_buffer_ranges(ranges.into_iter())
.map(|(excerpt, actual_range)| {
(
excerpt_id,
buffer.remote_id(),
map_range_from_excerpt(&snapshot, excerpt_id, buffer, actual_range),
excerpt.id(),
excerpt.buffer().remote_id(),
map_range_from_excerpt(&snapshot, excerpt.id(), excerpt.buffer(), actual_range),
)
})
.collect_vec();

View File

@@ -19,8 +19,8 @@ db.workspace = true
editor.workspace = true
file_icons.workspace = true
fuzzy.workspace = true
itertools.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
menu.workspace = true
@@ -36,8 +36,8 @@ smol.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
worktree.workspace = true
workspace.workspace = true
worktree.workspace = true
[dev-dependencies]
search = { workspace = true, features = ["test-support"] }

File diff suppressed because it is too large Load Diff

View File

@@ -569,9 +569,9 @@ impl LocalBufferStore {
buffer_change_sets
.into_iter()
.filter_map(|(change_set, buffer_snapshot, path)| {
let (repo_entry, local_repo_entry) = snapshot.repo_for_path(&path)?;
let relative_path = repo_entry.relativize(&snapshot, &path).ok()?;
let base_text = local_repo_entry.repo().load_index_text(&relative_path);
let local_repo = snapshot.local_repo_for_path(&path)?;
let relative_path = local_repo.relativize(&path).ok()?;
let base_text = local_repo.repo().load_index_text(&relative_path);
Some((change_set, buffer_snapshot, base_text))
})
.collect::<Vec<_>>()
@@ -1161,16 +1161,16 @@ impl BufferStore {
Worktree::Local(worktree) => {
let worktree = worktree.snapshot();
let blame_params = maybe!({
let (repo_entry, local_repo_entry) = match worktree.repo_for_path(&file.path) {
let local_repo = match worktree.local_repo_for_path(&file.path) {
Some(repo_for_path) => repo_for_path,
None => return Ok(None),
};
let relative_path = repo_entry
.relativize(&worktree, &file.path)
let relative_path = local_repo
.relativize(&file.path)
.context("failed to relativize buffer path")?;
let repo = local_repo_entry.repo().clone();
let repo = local_repo.repo().clone();
let content = match version {
Some(version) => buffer.rope_for_version(&version).clone(),
@@ -1247,7 +1247,7 @@ impl BufferStore {
});
};
let path = match repo_entry.relativize(worktree, file.path()) {
let path = match repo_entry.relativize(file.path()) {
Ok(RepoPath(path)) => path,
Err(e) => return Task::ready(Err(e)),
};

View File

@@ -87,9 +87,8 @@ pub use language::Location;
#[cfg(any(test, feature = "test-support"))]
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
pub use worktree::{
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, RepositoryEntry,
UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
FS_WATCH_LATENCY,
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
};
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
@@ -2961,7 +2960,7 @@ impl LspStore {
http_client,
fs,
yarn,
next_diagnostic_group_id: Default::default(),
next_diagnostic_group_id: 1,
diagnostics: Default::default(),
_subscription: cx.on_app_quit(|this, cx| {
this.as_local_mut().unwrap().shutdown_language_servers(cx)
@@ -3109,6 +3108,7 @@ impl LspStore {
WorktreeStoreEvent::WorktreeUpdateSent(worktree) => {
worktree.update(cx, |worktree, _cx| self.send_diagnostic_summaries(worktree));
}
WorktreeStoreEvent::GitRepositoryUpdated => {}
}
}

View File

@@ -39,7 +39,10 @@ use futures::{
pub use image_store::{ImageItem, ImageStore};
use image_store::{ImageItemEvent, ImageStoreEvent};
use git::{blame::Blame, repository::GitRepository};
use git::{
blame::Blame,
repository::{GitFileStatus, GitRepository},
};
use gpui::{
AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla,
Model, ModelContext, SharedString, Task, WeakModel, WindowContext,
@@ -95,9 +98,8 @@ pub use task_inventory::{
BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
};
pub use worktree::{
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, RepositoryEntry,
UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings,
FS_WATCH_LATENCY,
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
UpdatedGitRepositoriesSet, Worktree, WorktreeId, WorktreeSettings, FS_WATCH_LATENCY,
};
pub use buffer_store::ProjectTransaction;
@@ -242,6 +244,7 @@ pub enum Event {
ActivateProjectPanel,
WorktreeAdded(WorktreeId),
WorktreeOrderChanged,
GitRepositoryUpdated,
WorktreeRemoved(WorktreeId),
WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet),
WorktreeUpdatedGitRepositories(WorktreeId),
@@ -1433,6 +1436,15 @@ impl Project {
.unwrap_or(false)
}
pub fn project_path_git_status(
&self,
project_path: &ProjectPath,
cx: &AppContext,
) -> Option<GitFileStatus> {
self.worktree_for_id(project_path.worktree_id, cx)
.and_then(|worktree| worktree.read(cx).status_for_file(&project_path.path))
}
pub fn visibility_for_paths(&self, paths: &[PathBuf], cx: &AppContext) -> Option<bool> {
paths
.iter()
@@ -2295,6 +2307,7 @@ impl Project {
}
WorktreeStoreEvent::WorktreeOrderChanged => cx.emit(Event::WorktreeOrderChanged),
WorktreeStoreEvent::WorktreeUpdateSent(_) => {}
WorktreeStoreEvent::GitRepositoryUpdated => cx.emit(Event::GitRepositoryUpdated),
}
}
@@ -3516,17 +3529,6 @@ impl Project {
)
}
pub fn get_repo(
&self,
project_path: &ProjectPath,
cx: &AppContext,
) -> Option<Arc<dyn GitRepository>> {
self.worktree_for_id(project_path.worktree_id, cx)?
.read(cx)
.as_local()?
.local_git_repo(&project_path.path)
}
pub fn get_first_worktree_root_repo(&self, cx: &AppContext) -> Option<Arc<dyn GitRepository>> {
let worktree = self.visible_worktrees(cx).next()?.read(cx).as_local()?;
let root_entry = worktree.root_git_entry()?;
@@ -4426,8 +4428,10 @@ impl Completion {
}
}
pub fn sort_worktree_entries(entries: &mut [Entry]) {
pub fn sort_worktree_entries(entries: &mut [impl AsRef<Entry>]) {
entries.sort_by(|entry_a, entry_b| {
let entry_a = entry_a.as_ref();
let entry_b = entry_b.as_ref();
compare_paths(
(&entry_a.path, entry_a.is_file()),
(&entry_b.path, entry_b.is_file()),

View File

@@ -1312,7 +1312,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: lsp::DiagnosticSeverity::ERROR,
message: "undefined variable 'A'".to_string(),
group_id: 0,
group_id: 1,
is_primary: true,
..Default::default()
}
@@ -1828,7 +1828,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
severity: DiagnosticSeverity::ERROR,
message: "undefined variable 'BB'".to_string(),
is_disk_based: true,
group_id: 1,
group_id: 2,
is_primary: true,
..Default::default()
},
@@ -1840,7 +1840,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
severity: DiagnosticSeverity::ERROR,
message: "undefined variable 'CCC'".to_string(),
is_disk_based: true,
group_id: 2,
group_id: 3,
is_primary: true,
..Default::default()
}
@@ -1906,7 +1906,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
severity: DiagnosticSeverity::WARNING,
message: "unreachable statement".to_string(),
is_disk_based: true,
group_id: 4,
group_id: 5,
is_primary: true,
..Default::default()
}
@@ -1918,7 +1918,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
severity: DiagnosticSeverity::ERROR,
message: "undefined variable 'A'".to_string(),
is_disk_based: true,
group_id: 3,
group_id: 4,
is_primary: true,
..Default::default()
},
@@ -1998,7 +1998,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
severity: DiagnosticSeverity::WARNING,
message: "undefined variable 'A'".to_string(),
is_disk_based: true,
group_id: 6,
group_id: 7,
is_primary: true,
..Default::default()
}
@@ -2010,7 +2010,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
severity: DiagnosticSeverity::ERROR,
message: "undefined variable 'BB'".to_string(),
is_disk_based: true,
group_id: 5,
group_id: 6,
is_primary: true,
..Default::default()
},
@@ -3838,7 +3838,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::WARNING,
message: "error 1".to_string(),
group_id: 1,
group_id: 2,
is_primary: true,
..Default::default()
}
@@ -3848,27 +3848,27 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 1 hint 1".to_string(),
group_id: 2,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 1,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 0,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 2".to_string(),
group_id: 0,
group_id: 1,
is_primary: false,
..Default::default()
}
@@ -3878,43 +3878,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::ERROR,
message: "error 2".to_string(),
group_id: 0,
is_primary: true,
..Default::default()
}
}
]
);
assert_eq!(
buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
&[
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 0,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 2".to_string(),
group_id: 0,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(2, 8)..Point::new(2, 17),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::ERROR,
message: "error 2".to_string(),
group_id: 0,
group_id: 1,
is_primary: true,
..Default::default()
}
@@ -3924,13 +3888,49 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
assert_eq!(
buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
&[
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 1".to_string(),
group_id: 1,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(1, 13)..Point::new(1, 15),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 2 hint 2".to_string(),
group_id: 1,
is_primary: false,
..Default::default()
}
},
DiagnosticEntry {
range: Point::new(2, 8)..Point::new(2, 17),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::ERROR,
message: "error 2".to_string(),
group_id: 1,
is_primary: true,
..Default::default()
}
}
]
);
assert_eq!(
buffer.diagnostic_group::<Point>(2).collect::<Vec<_>>(),
&[
DiagnosticEntry {
range: Point::new(1, 8)..Point::new(1, 9),
diagnostic: Diagnostic {
severity: DiagnosticSeverity::WARNING,
message: "error 1".to_string(),
group_id: 1,
group_id: 2,
is_primary: true,
..Default::default()
}
@@ -3940,7 +3940,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: "error 1 hint 1".to_string(),
group_id: 1,
group_id: 2,
is_primary: false,
..Default::default()
}

View File

@@ -109,7 +109,7 @@ impl Inventory {
/// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContext`] given.
/// Joins the new resolutions with the resolved tasks that were used (spawned) before,
/// orders them so that the most recently used come first, all equally used ones are ordered so that the most specific tasks come first.
/// Deduplicates the tasks by their labels and contenxt and splits the ordered list into two: used tasks and the rest, newly resolved tasks.
/// Deduplicates the tasks by their labels and context and splits the ordered list into two: used tasks and the rest, newly resolved tasks.
pub fn used_and_current_resolved_tasks(
&self,
worktree: Option<WorktreeId>,

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