Compare commits

..

175 Commits

Author SHA1 Message Date
Michael Sloan
5d11fc5bc4 Update edit predictions documentation 2025-02-13 04:47:19 -07:00
Conrad Irwin
d57f5937d4 Git panel: Right click menu (#24787)
Release Notes:

- N/A
2025-02-12 22:26:34 -07:00
Joseph T. Lyons
fc7bf7bcb9 Bump Zed to v0.175 (#24785)
Release Notes:

-N/A
2025-02-12 23:14:45 -05:00
tidely
5d634245a2 remote_server: Remove unnecessary Box, prevent time-of-check time-of-use bug (#24730)
The MultiWrite struct is defined in the function scope and is allowed to
have a concrete type, which means we can throw away the extra Box.
PathBuf::exists is known to be prone to invalid usage. It doesn't take
into account permissions errors and just returns false, additionally it
introduces a time-of-check time-of-use bug. While extremely unlikely,
why not fix it anyway.

Release Notes:

- remove unnecessary Box
- prevent time-of-check time-of-use bug
2025-02-12 20:55:22 -07:00
Conrad Irwin
21a1541a70 Branch/co-authors in commit (#24768)
- **branch selector in commit box**
- **TEMP**
- **Add co-authors toggle button**

Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2025-02-12 20:53:52 -07:00
Max Brunsfeld
71867096c8 Migrate edit_prediction_provider setting before updating its value to 'zed' during onboarding (#24781)
This fixes a bug where we'd update your settings to an invalid state if
you were using the old `inline_completion_provider` setting, then
onboarded to Zeta, then migrated your settings.

Release Notes:

- N/A

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
2025-02-13 02:35:25 +00:00
Danilo Leal
3d68dba696 edit predictions: Iterate on onboarding modal copywriting (#24779)
Release Notes:

- N/A

---------

Co-authored-by: Nathan Sobo <1789+nathansobo@users.noreply.github.com>
2025-02-13 02:07:20 +00:00
renovate[bot]
f0cd71e43c Update cloudflare/wrangler-action digest to 392082e (#24753)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cloudflare/wrangler-action](https://redirect.github.com/cloudflare/wrangler-action)
| action | digest | `7a5f8bb` -> `392082e` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNjcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE2Ny4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 20:01:42 -05:00
Michael Sloan
a13c2baa7f Improve error message for AcceptEditPredictions - add docs link (#24772)
The docs have not been updated yet, this is anticipating their presence
soon.


![image](https://github.com/user-attachments/assets/bbcf56f2-6d5b-460b-8ed0-36bef3b4f12f)

Release Notes:

- N/A
2025-02-12 17:46:53 -07:00
Michal Vrbata
7ba1492f0a file_icons: Add separate icon key for Bicep files (#24757)
This PR adds support for bicep file icon:

Release Notes:

- Icon themes: Added the ability to change the file icon for Bicep
(`.bicep`) files.
2025-02-13 00:36:27 +00:00
Danilo Leal
0a681225b6 edit predictions: Enable blog post link behind a feature flag (#24720)
This PR updates the blog post link in the onboarding modal to be behind
the `predict-edits-launch` feature flag instead of a staff flag.

This will allow us to enable the blog post link once we're live.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-12 23:59:06 +00:00
Marshall Bowers
277fb54632 zeta: Respect x-zed-minimum-required-version header (#24771)
This PR makes it so Zeta respects the `x-zed-minimum-required-version`
header sent back from the server.

If the current Zed version is strictly less than the indicated minimum
required version, we show an error indicating that an update is required
in order to continue using Zeta:

<img width="472" alt="Screenshot 2025-02-12 at 6 15 44 PM"
src="https://github.com/user-attachments/assets/51b85dff-23a0-464c-ae4b-5b8f46b5915c"
/>

Release Notes:

- N/A
2025-02-12 23:58:38 +00:00
renovate[bot]
0e42a69490 Update dependency danger to v12.3.4 (#24770)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [danger](https://redirect.github.com/danger/danger-js) | [`12.3.3` ->
`12.3.4`](https://renovatebot.com/diffs/npm/danger/12.3.3/12.3.4) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/danger/12.3.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/danger/12.3.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/danger/12.3.3/12.3.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/danger/12.3.3/12.3.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>danger/danger-js (danger)</summary>

###
[`v12.3.4`](https://redirect.github.com/danger/danger-js/blob/HEAD/CHANGELOG.md#1234)

[Compare
Source](https://redirect.github.com/danger/danger-js/compare/12.3.3...12.3.4)

- Ensure that [babel ignores](https://babeljs.io/docs/options#ignore) do
not cause the transpiler to fall over, by supporting the
`null` return from `loadOptions` which occurs when a file is ignored.
-   Allow absolute paths for a Dangerfile

</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:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNjcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE2Ny4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 23:26:07 +00:00
Mikayla Maki
b014afa938 Add an undo button to the git panel (#24593)
Also prep infrastructure for pushing a commit

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
2025-02-12 15:57:08 -07:00
Ben Kunkle
df8adc8b11 Fix linux zeta modifiers display (#24764)
Improves rendering of Zeta keybind shortcuts on Linux

Before:

![image](https://github.com/user-attachments/assets/9b6a61f7-dade-480f-a864-acdcede05957)

After: (with muting modifier changes merged)

![image](https://github.com/user-attachments/assets/dd616d29-ac2e-4c8b-bf9b-5d74f8e4f1c4)


Release Notes:

- N/A

---------

Co-authored-by: Michael <michael@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
2025-02-12 14:46:42 -08:00
smit
522b8d662c editor: Fix autoscroll flickering regression (#24758)
This PR fixes autoscroll flickering issue caused by recent
[#24735](https://github.com/zed-industries/zed/pull/24735) which fixes
soft wrap scroll issues. No release notes, as this was few hours ago.

Adding vertical scrollbar width to viewport width, so that autoscroll
function don't try to that much pixels extra.

Release Notes:

- N/A
2025-02-13 03:42:22 +05:30
smit
5dc3c237eb workspace: Do not reuse window for sub directory (only for root directory and sub files) (#24560)
Closes #10232

Context:

We have three ways to open files or dirs in Zed: `zed`, `zed --new`, and
`zed --add`. `--new` forces the project to open in a new window, while
`--add` forces it to open in an existing window (even if the dir isn’t a
subdir of an existing project or the file isn’t part of it).

Using just `zed` tries to open it in an existing window based on similar
logic of `--add`, but if no related project is found the dir, opens in a
new window.

Problem:

Right now, subdirs that are part of an existing project open in the
existing window when using `zed`. By default, subdirs should open in a
new window instead. If someone wants to open it in the existing window,
they can explicitly use `--add`. After this PR, only root dir and files
will focus on existing window, when `zed ` is used.

Fix:

For the `zed` case, we’ve filtered out subdirs in the logic that assigns
them to an existing window.

Release Notes:

- Fixed an issue where subdirectories of an already opened project, when
opened via the terminal, would open in the existing project instead of a
new window.
2025-02-13 03:37:39 +05:30
Agus Zubiaga
c771ca49e1 Fix <1px gap between diff popover and accept keybindingg (#24756)
Release Notes:

- N/A

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: rtfeldman <oss@rtfeldman.com>
Co-authored-by: mgsloan <mgsloan@gmail.com>
2025-02-12 20:59:11 +00:00
Cole Miller
ab4a6f1c79 Open git panel when deploying project diff via action (#24751)
Release Notes:

- N/A
2025-02-12 15:37:17 -05:00
Max Brunsfeld
316b97d6e3 Position prediction popover adjacent to edit if possible (#24750)
Also, place accept key binding indicator on right edge of popover to
avoid obscuring content above.

Release Notes:

- N/A

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: rtfeldman <oss@rtfeldman.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
2025-02-12 12:11:49 -08:00
Cole Miller
eea6b526dc Implement staging and unstaging hunks (#24606)
- [x] Staging hunks
- [x] Unstaging hunks
- [x] Write a randomized test
- [x] Get test passing
- [x] Fix existing bug in diff_base_byte_range computation
- [x] Remote project support
- [ ] ~~Improve performance of
buffer_range_to_unchanged_diff_base_range~~
- [ ] ~~Bug: project diff editor scrolls to top when staging/unstaging
hunk~~ existing issue
- [ ] ~~UI~~ deferred
- [x] Tricky cases
  - [x] Correctly handle acting on multiple hunks for a single file
- [x] Remove path from index when unstaging the last staged hunk, if
it's absent from HEAD, or staging the only hunk, if it's deleted in the
working copy

Release Notes:

- Add `ToggleStagedSelectedDiffHunks` action for staging and unstaging
individual diff hunks
2025-02-12 19:46:42 +00:00
Finn Evers
ea8da43c6b ui: Fix keybind sizing for non-default UI font sizes (#24708)
Closes #24597 

This fixes the regression from
00971fbe41
which removed the `text_ui(cx)` - call from the keybinding render. The
removal caused improperly scaled font rendering as shown in the images
below.

This PR reintroduces this behaviour for all cases where `size` is not
set.

| | `main` | With this patch | Parent of
00971fbe41
| --- | ---- | ---- | --- |
| Small font size (10px) | <img width="204" alt="cur_small"
src="https://github.com/user-attachments/assets/2bb1615a-c346-4da5-855c-01894744ee74"
/> | <img width="205" alt="pr_small"
src="https://github.com/user-attachments/assets/26ab70f0-d6ca-439b-aaea-4d6ded4f8f11"
/> | <img width="203" alt="prev_small"
src="https://github.com/user-attachments/assets/92a04008-8408-4a98-83a2-59fdd6e5708a"
/> |
| Large font size | <img width="369" alt="cur_big"
src="https://github.com/user-attachments/assets/8400c205-19b6-479c-a3d1-df12ed4e84da"
/> | <img width="398" alt="pr_big"
src="https://github.com/user-attachments/assets/01f495aa-0be1-4169-ae09-4292b0e638ff"
/> | <img width="389" alt="prev_big"
src="https://github.com/user-attachments/assets/e041a8ea-a958-4a3b-8fef-0f7fe5079c34"
/> |


Release Notes:

- Fixed keybind hints being improperly scaled for custom ui font sizes
2025-02-12 14:17:00 -05:00
smit
cc97f4131b editor: Fix horizontal scroll when soft wrap is active (#24735)
Closes #22252

This PR fixes the bug introduced in
https://github.com/zed-industries/zed/pull/19495 by:

Problem:

The vertical scrollbar is currently rendered absolutely on top of the
editor. When calculating soft wrap, the editor uses its width to decide
how many words fit on a line. This causes words to overlap with the
vertical scrollbar because it doesn't account for the scrollbar's width.
To fix the overlap, extra overflow is added to the scrollbar, which
solves the issue but creates unnecessary scrolling in soft wrap mode.

Fix:

The editor width is adjusted to account for the scrollbar's width. This
makes sure the correct number of words fit on a line and prevents
overlapping with the scrollbar in soft wrap mode.

Since the scrollbar width is now accounted for in the editor's width,
there's no need to add extra overflow, unless there’s no soft wrap. In
that case, when text overflows the editor’s width, we still need to add
extra overscroll to match the scrollbar width. Without this, long lines
will overlap with the scrollbar.

Release Notes:

- Fixed issue where horizontal scrollbar would scroll few characters
width when soft wrap is active.
2025-02-12 22:52:22 +05:30
Piotr Osiewicz
ba7d2ba8c7 project: Activate buffer_diff/test-support in test-support (#24739)
Closes #ISSUE

Release Notes:

- N/A
2025-02-12 16:51:47 +00:00
Agus Zubiaga
553cc2cca5 edit predictions: Do not require modifier if in leading space but just accepted a prediction (#24733)
This makes the tab tab tab experience smoother

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: as-cii <as-cii@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Ben <ben@zed.dev>
2025-02-12 16:30:58 +00:00
Marshall Bowers
3f95d79fc5 inline_completion_button: Put "Eager Preview Mode" menu entry behind a feature flag (#24734)
This PR puts the "Eager Preview Mode" menu entry behind a feature flag
rather than a staff flag.

Currently it defaults to `false` for staff so that it doesn't leak into
any marketing/launch materials.

Folks who want to see it can opt-in to the flag explicitly, for now.

Release Notes:

- N/A
2025-02-12 16:23:23 +00:00
Agus Zubiaga
51092c4e31 Polish edit predictions (#24732)
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: as-cii <as-cii@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-02-12 15:56:31 +00:00
Antonio Scandurra
2b7d3726b4 Replace rustls-native-certs with rustls-platform-verifier (#24656)
closes https://github.com/zed-industries/zed/issues/19620.

I am not 100% sure on how to test this though. @elithrar: would you mind
giving this branch a shot and seeing if it works for you? I kicked off
bundling for this pull request and you should be able to download a DMG
from the CI artifacts as soon as it's done building.

Release Notes:

- Fixed a bug that caused OS-level CA certificate bundles to not be
respected.

---------

Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
2025-02-12 07:32:17 -08:00
Sanjeev Shrestha
1ce6e8d0e3 file_icons: Use separate keys for C#, Cue, GitLab YAML, Luau, and Solidity (#24711)
This PR updates the file icon mappings such that:

- C# (`.cs`) files map to the `csharp` key
- Cue (`.cue`) files map to the `cue` key
- GitLab YAML (`gitlab-ci.yml`) files map to the `gitlab` key
- Luau (`.luau`) files map to the `luau` key
- Solidity (`.sol`) files map to the `solidity` key

Release Notes:

- Icon themes: Added the ability to change the file icon for C# (`.cs`)
files.
- Icon themes: Added the ability to change the file icon for Cue
(`.cue`) files.
- Icon themes: Added the ability to change the file icon for GitLab YAML
(`gitlab-ci.yml`) files.
- Icon themes: Added the ability to change the file icon for Luau
(`.luau`) files.
- Icon themes: Added the ability to change the file icon for Solidity
(`.sol`) files.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-12 14:25:01 +00:00
Michael Sloan
eb389a5132 edit predictions: Update migration banner text and rename chore (#24713)
Rationale for the changes:

* `requires migration` -> `uses some deprecated settings` changed
because really it isn't required by this version of Zed, and I believe
we hope to offer support for deprecated settings and their migration for
a long time.

* Rename of `migration` -> `updated` is because to me, "updated" feels
lighter and more accurate. To me migration has connotations of moving to
a whole new format.

Formatting changes are due to shortening the line causing cargo fmt to
go from not formatting the code to doing so.

Release Notes:

- N/A

---------

Co-authored-by: smit <0xtimsb@gmail.com>
2025-02-12 19:28:29 +05:30
Adrien Tiburce
e148815e04 docs: Fix modal_max_width naming (#24719)
## Fixes `modal_max_width` doc.

The settings `modal_max_width` was `max_modal_width` in the doc.
2025-02-12 13:15:25 +00:00
Michael Sloan
fc86e7cd51 Fix modifier key logic for edit predictions preview with completions (#24709)
Release Notes:

- N/A
2025-02-12 03:26:51 +00:00
Danilo Leal
754560876b edit predictions: Refine the settings migration banner (#24706)
Just a slight design touch-up on the settings migration banner.

Release Notes:

- N/A
2025-02-12 00:13:38 -03:00
smit
65934ae181 migrator: In-memory migration and improved UX (#24621)
This PR adds:

- Support for deprecated keymap and settings (In-memory migration)
- Migration prompt only shown in `settings.json` / `keymap.json`.

Release Notes:

- The migration banner will only appear in `settings.json` and
`keymap.json` if you have deprecated settings or keybindings, allowing
you to migrate them to work with the new version on Zed.
2025-02-12 06:47:08 +05:30
Michael Sloan
498bb518ff Require alt-tab for AcceptEditPrediction when tab inserting whitespace is desired (#24705)
Moves tab whitespace insertion logic out of `AcceptEditPrediction`
handler.

`edit_prediction_requires_modifier` context will now be true when on a
line with leading whitespace, so that `alt-tab` is used to accept
predictions in this case. This way leading indentation can be typed when
edit predictions are visible

Release Notes:

- N/A

Co-authored-by: Ben <ben@zed.dev>
Co-authored-by: Joao <joao@zed.dev>
2025-02-12 01:14:09 +00:00
Danilo Leal
2e7a89c5e3 edit predictions: Improve copywriting (#24689) 2025-02-11 21:43:48 -03:00
Conrad Irwin
f5fd3d98ad Fix project diff focus (#24691)
Release Notes:

- N/A
2025-02-11 16:40:40 -08:00
Max Brunsfeld
9a9fdce253 Fixes for accept edit popovers (#24703)
Follow-up to #24700

Release Notes:

- N/A

---------

Co-authored-by: danilo-leal <daniloleal09@gmail.com>
Co-authored-by: agu-z <hi@aguz.me>
2025-02-11 16:32:15 -08:00
Marshall Bowers
cc931a8fcc theme: Add support for setting light/dark icon themes (#24702)
This PR adds support for configuring both a light and dark icon theme in
`settings.json`.

In addition to accepting just an icon theme name, the `icon_theme` field
now also accepts an object in the following form:

```jsonc
{
  "icon_theme": {
    "mode": "system",
    "light": "Zed (Default)",
    "dark": "Zed (Default)"
  }
}
```

Both `light` and `dark` are required, and indicate which icon theme
should be used when the system is in light mode and dark mode,
respectively.

The `mode` field is optional and indicates which icon theme should be
used:
- `"system"` - Use the icon theme that corresponds to the system's
appearance.
- `"light"` - Use the icon theme indicated by the `light` field.
- `"dark"` - Use the icon theme indicated by the `dark` field.

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

Release Notes:

- Added support for configuring both a light and dark icon theme and
switching between them based on system preference.
2025-02-11 23:45:37 +00:00
Max Brunsfeld
148547ecd1 Rework edit prediction preview mode (#24700)
Don't animate the cursor when previewing jumps.
Instead, display the jump popover with a line that resembles a cursor,
indicating the jump destination. If the jump destination is outside of
the view port, there is an extra step in which `tab` scrolls the
viewport to reveal the jump destination.

Release Notes:

- N/A

---------

Co-authored-by: danilo-leal <daniloleal09@gmail.com>
Co-authored-by: agu-z <hi@aguz.me>
2025-02-11 23:14:12 +00:00
Mikayla Maki
5293f5724c Add a script that sets up a trusted MITM proxy (#24698)
In an effort to squash bugs like:
https://github.com/zed-industries/zed/issues/19620, and improve
confidence on PRs like:
https://github.com/zed-industries/zed/pull/24656, I created this little
test script using `mitmproxy` to simulate the situation.

Unfortunately, I don't see any issues with our current usage of the
local certificate store using this script. But I'd like to have it as a
base to build off of.

Release Notes:

- N/A
2025-02-11 22:43:35 +00:00
Kirill Bulatov
73b32a20e2 Fix editor::GoToDiagnostics cycle (#24697)
Re-lands https://github.com/zed-industries/zed/pull/24446 with a more
appropriate fix


https://github.com/user-attachments/assets/45f665f0-473a-49bd-b013-b9d1bdb902bd

After activating 2nd diagnostics group, `find_map` code for next
diagnostics did not skip the previous group for the same place.

This time, instead of fiddling with the diagnostics group comparison,
the code splits the diagnostics by search place, looks up the active
group (if any) in both split parts, and selects the entries after the
group elements.

Release Notes:

- Fixed `editor::GoToDiagnostics` action stuck when multiple diagnostics
groups belong to the same place
2025-02-11 22:41:23 +00:00
Marshall Bowers
7da913c801 collab: Update user email and name when signing in (#24694)
This PR updates the `GET /user` endpoint to update the user's email and
name from the provided GitHub profile information on sign-in.

Currently, these fields were only set when the user was first created.

Release Notes:

- N/A
2025-02-11 22:02:11 +00:00
Marshall Bowers
a2592a3a37 Clean up edit predictions settings (#24692)
This PR does some clean up for the edit predictions settings:

- Removed `editor.show_edit_predictions_in_menu`
- Renamed `edit_predictions.inline_preview` to `edit_predictions.mode`

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
2025-02-11 21:33:31 +00:00
Kirill Bulatov
636253d2dc Prefer names over github logins when filling co-authors (#24693)
Follow-up of https://github.com/zed-industries/zed/pull/24575

Release Notes:

- N/A
2025-02-11 21:32:03 +00:00
Marshall Bowers
e851abd2ec migrator: Do some cleanup (#24687)
This PR does some clean up of the `migrator` crate:

- Remove `.unwrap`s
- Don't suppress `rustfmt`

Release Notes:

- N/A
2025-02-11 20:46:21 +00:00
Joseph T. Lyons
b3814ce4e3 Fix "Project Diff Opened" event name (#24686)
Release Notes:

- N/A
2025-02-11 20:43:58 +00:00
Nate Butler
2d71733490 ui: Update Label component (#24653)
- Standardize style methods
- Convert label story to a component preview
- update component preview styles  

Release Notes:

- N/A
2025-02-11 20:16:59 +00:00
Marshall Bowers
aab3e0495d inline_completion_button: Add menu option to toggle "Eager Preview"s for edit predictions (#24685)
This PR adds a menu option to the edit prediction menu to toggle the
"Eager Preview" behavior:

<img width="252" alt="Screenshot 2025-02-11 at 2 44 52 PM"
src="https://github.com/user-attachments/assets/232e879b-3c11-4edd-a549-f284e2bca391"
/>

Release Notes:

- N/A
2025-02-11 20:02:52 +00:00
Michael Sloan
12163c9b45 Add Editor && to accept edit contexts in vim keymap (#24684)
Without this, these default vim bindings were taking precedence over
user keybindings

Release Notes:

- N/A
2025-02-11 19:42:43 +00:00
Mikayla Maki
477cec0ef1 Add more view tracking (#24683)
This should fix a panic in `Window::current_view()`

Release Notes:

- N/A
2025-02-11 19:18:54 +00:00
5brian
0a146793ea vim: Prevent around word operations from selecting indentation (#24635)
Closes https://github.com/zed-industries/zed/issues/15323

Changes:
Added check for first word on line

Tested `v/c/d/y aw`. Matches standard neovim.

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

|![image](https://github.com/user-attachments/assets/725b74e6-3aa0-40dc-9fd2-4d2b593e9926)|![image](https://github.com/user-attachments/assets/eeebd267-b4c6-4ea6-bb9a-fb913614754c)|![image](https://github.com/user-attachments/assets/fb695e54-b4c2-44a6-a588-909c1fd415e0)



Release Notes:

- vim: Prevent around word operations from selecting indentation
2025-02-11 11:35:59 -07:00
Peter Tripp
7378ab9ba5 Correctly handle [[ autoclosing in Markdown (#24662) 2025-02-11 13:06:45 -05:00
Cole Miller
759ea0ec48 Touch up stale hunks fix (#24669)
Release Notes:

- N/A

Co-authored-by: Max <max@zed.dev>
2025-02-11 17:47:41 +00:00
João Marcos
7c00eec08b edit predictions: Fix popover hint not scrolling horizontally (#24602)
Release Notes:

- N/A
2025-02-11 17:29:00 +00:00
Marshall Bowers
eaab7da2d8 zeta: Add ability to change predict edits URL via environment variable (#24668)
This PR adds the ability to change the predict edits URL using the
`ZED_PREDICT_EDITS_URL` environment variable.

This allows for easily pointing Zed to a development version of the
Cloudflare Worker.

Release Notes:

- N/A
2025-02-11 17:28:13 +00:00
Agus Zubiaga
14d9788ba3 edit predictions: Don't animate cursor when jumping in eager mode without LSP completions (#24664)
We should only do this in "holding modifier" mode OR when there's a
language server completions menu.

Release Notes:

- N/A
2025-02-11 16:47:34 +00:00
Cameron Radmore
b395beaf93 file_icons: Add Stylelint file icon associations (#24605)
This PR adds file associations for stylelint files.

This is how it looks like in Zed (the icon doesn't exist):
![default javascript file icon is shown for
stylelint.config.js](https://github.com/user-attachments/assets/a873d7fc-1b8a-4a9c-8e92-1be56d5b01b1)

In a dev version of an icon theme it looks like this (icon sourced from:
https://github.com/vscode-icons/vscode-icons/blob/master/icons/file_type_stylelint.svg
):
![stylelint file icon is shown for
stylelint.config.js](https://github.com/user-attachments/assets/ddbb068d-7986-43de-94f8-9c844cb6b96f)

Release Notes:

- Icon themes: Added Stylelint file icon associations.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-11 16:42:54 +00:00
Cole Miller
6e7416eb00 Fix stale hunks after commit (#24663)
Fixes a regression introduced in #24475.

Release Notes:

- N/A
2025-02-11 11:39:57 -05:00
smit
bbea3a2184 editor: Fix crash caused by editor::SelectPrevious (#24660)
Closes #24345

Release Notes:

- Fixed a crash caused by calling `editor::SelectPrevious` twice in a
row.

Co-authored-by: conrad <conrad@zed.dev>
2025-02-11 22:07:44 +05:30
Twilight
7950368bc2 file_icons: Add missing React file icon associations (#24659)
The `.mjsx`, `.cjsx`, `.mtsx`, and `.ctsx` file extensions are also
designed to contain JSX code.

Release Notes:

- Added file icon associations for more React files (`.mjsx`, `.cjsx`,
`.mtsx`, `.ctsx`).
2025-02-11 16:23:53 +00:00
ANKDDEV
7fe6943d89 Add command to copy current file name (#22174)
Closes #21967

Add actions `CopyFileName` and `CopyFileNameWithoutExtension` to be used in the command palette.

Release Notes:

- Added commands `editor: copy file name` and `editor: copy file name without extensions`.
2025-02-11 12:42:23 -03:00
Bennet Bo Fenner
8fa85c41a1 edit prediction: Try to fix panic in Buffer::preview_edits (#24654)
We've seen a few crashes in `SyntaxSnapshot::reparse_with_ranges` during
`Buffer::preview_edits`, where an offset conversion fails because it is
out of range.
We are not sure how exactly this is happening. 
Our theory is that the syntax snapshot is using an outdated state when
edits happen in the meantime (while interpolating). This is an attempt
to see if it helps with the panics, hopefully we can revisit this when
we have a better understanding of the issue.


Co-Authored-by: Antonio <antonio@zed.dev>

Release Notes:

- N/A

Co-authored-by: Antonio <antonio@zed.dev>
2025-02-11 16:35:09 +01:00
Marshall Bowers
7b45901d96 gpui: Update asset paths for more examples (#24646)
This PR updates the asset paths used in more GPUI examples such that
they work when run from the repository root or from within
`crates/gpui`.

Release Notes:

- N/A
2025-02-11 14:37:55 +00:00
Agus Zubiaga
22e2b8e832 edit predictions: Preview jumps by animating cursor to target (#24604)
https://github.com/user-attachments/assets/977d08fb-a2b1-4826-9d95-8f35c6cb9f13




Release Notes:

- N/A

---------

Co-authored-by: Danilo <danilo@zed.dev>
Co-authored-by: Smit <smit@zed.dev>
Co-authored-by: Max <max@zed.dev>
2025-02-11 11:19:51 -03:00
Marshall Bowers
5778e1e6f0 theme: Fix Svelte file icon (#24650)
This PR fixes the file icon used for Svelte files in the default icon
theme, as I used the wrong icon name in #24644.

Release Notes:

- N/A
2025-02-11 14:09:03 +00:00
Marshall Bowers
c61f2dff47 file_icons: Use a separate icon key for Markdown files (#24648)
This PR updates the file icon mappings such that Markdown (`.md`,
`.markdown`) files map to the `markdown` key.

Release Notes:

- Icon themes: Added the ability to change the file icon for Markdown
(`.md`, `.markdown`) files.
2025-02-11 13:58:53 +00:00
Marshall Bowers
04d65cb3cd gpui: Make image example work regardless of how it is run (#24645)
This PR updates the GPUI `image` example such that it works when run in
the following ways:

- `cargo run -p gpui --example image` from the repository root
- `cargo run --example image` from within `crates/gpui`

Release Notes:

- N/A
2025-02-11 13:34:38 +00:00
Marshall Bowers
6a40a400bd file_icons: Use a separate icon key for Svelte files (#24644)
This PR updates the file icon mappings such that Svelte (`.svelte`)
files map to the `svelte` key.

Release Notes:

- Icon themes: Added the ability to change the file icon for Svelte
(`.svelte`) files.
2025-02-11 13:22:23 +00:00
smit
c8c4ec21f3 docs: Fix vim Subword and Push example snippet (#24641)
All other vim examples are objects in Keymap file, where these two
examples are stated as Keymap file itself.

PR fixes this confusion.  

Release Notes:

- N/A
2025-02-11 18:10:16 +05:30
Piotr Osiewicz
64ae5093af chore: Remove settings dependency on migrator (#24642)
Closes #ISSUE

Release Notes:

- N/A
2025-02-11 13:34:33 +01:00
zfx
7f4957cf20 Remove non-existent icon IndicatorX (#24636)
There is no file indicator_x.svg in the assets directory.

09:49:05 [ERROR] could not find asset at path "icons/indicator_x.svg"

![image](https://github.com/user-attachments/assets/e9cc5ae3-4eb3-4e1e-b012-43ac7297c0a7)

Release Notes:

- N/A
2025-02-11 11:32:38 +00:00
Bennet Bo Fenner
8c349057e5 edit prediction: Fix zeta: Rate completions action not working when using keybinding (#24569)
Release Notes:

- N/A
2025-02-11 12:14:42 +01:00
Michael Sloan
a1d4bd94c9 Make alt-l the default linux/windows binding for AcceptEditPrediction (#24630)
Release Notes:

- N/A
2025-02-11 08:34:32 +00:00
Michael Sloan
9e178f128d Revert "Make ctrl-l the default vim binding for AcceptEditPrediction (#24599) (#24614)
Didn't realize that the base keymap binds this to `editor::SelectLine`.

This reverts commit c5fe5f1139.

Release Notes:

- N/A
2025-02-11 07:14:16 +00:00
5brian
236f51cddd vim: Update vi{ (#24601)
Small fix: Following up on
https://github.com/zed-industries/zed/pull/24518 where i missed `vi{`.

Matching neovim(tree-sitter), `vi{` should not have the newline selected
(Now `vi{d`/`vi{c` can match `di{`/`ci{`).

Also moved the cursor to the start.

|prev|new|neovim|
|---|---|---|

|![image](https://github.com/user-attachments/assets/0311fbe5-df2e-4feb-977d-de33a3af7fdc)|![image](https://github.com/user-attachments/assets/a940c6ba-268b-4401-8c43-38ca17848542)|![image](https://github.com/user-attachments/assets/dab2c47d-660c-4ae3-bf79-635265222cc1)|

Release Notes:

- N/A
2025-02-10 22:26:26 -07:00
Max Bucknell
37785a54d5 vim: :set support (#24209)
Closes #21147 

Release Notes:

- vim: First version of `:set` with support for `[no]wrap`,
`[no]number`, `[no]relativenumber`

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-02-11 04:55:40 +00:00
Peter Tripp
2e7bb11b7d docs: Improve StyLua Lua formatter suggested settings (#24144) 2025-02-10 22:12:01 -05:00
Peter Tripp
cf9661a56b Improve extension extraction documentation (#24590)
- Add .gitignore
- Update extension.toml URL
- Script cleanup of Cargo.toml workspace lines
2025-02-10 22:11:23 -05:00
Michael Sloan
c5fe5f1139 Make ctrl-l the default vim binding for AcceptEditPrediction (#24599)
Release Notes:

- N/A
2025-02-11 03:03:53 +00:00
Cole Miller
8f75fe25e5 Add staged status information to diff hunks (#24475)
Release Notes:

- Render unstaged hunks in the project diff editor with a slashed
background

---------

Co-authored-by: maxbrunsfeld <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-02-10 21:43:25 -05:00
Michael Sloan
a9de9e3cb4 Remove alt-tab / alt-enter binds in vim keymap, as they are in base (#24598)
Was unnecessary to include these in #24596 as they will be available
from the base keymap.

Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2025-02-11 02:41:09 +00:00
Michael Sloan
4c881b6a12 Use editor::AcceptEditPrediction in vim keymap (#24596)
* Overrides the action handler to switch to insert mode after jumps.

* Returns `vim::Tab` to its behavior from before #24418

Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2025-02-10 19:20:13 -07:00
Danilo Leal
c89ad65782 edit predictions: Show user if current project is open source (#24587)
Release Notes:

- N/A

---------

Co-authored-by: João Marcos <marcospb19@hotmail.com>
2025-02-10 22:28:56 -03:00
Michael Sloan
3d9f70946c Use last keybind (highest precedence) for AcceptEditPrediction display (#24595)
Fix of PR #24582

Release Notes:

- N/A
2025-02-11 01:12:46 +00:00
Michael Sloan
7fe331f83d Remove old keymap comments about AcceptEditPrediction modifier changes (#24592)
This was fixed in #24442

Release Notes:

- N/A
2025-02-10 17:58:08 -07:00
张小白
c1f162abc6 collab: Fix project sharing between Windows and Unix (#23680)
Closes #14258

Windows user(host) sharing a project to a guest(using macOS), and host
follows guest:


https://github.com/user-attachments/assets/ba306b6b-23f7-48b1-8ba8-fdc5992d8f00

macOS user(host) sharing a project to a guest(using Windows), and host
follows guest:



https://github.com/user-attachments/assets/c5ee5e78-870d-49e5-907d-8565977a01ae

macOS user edits files in a windows project through collab:



https://github.com/user-attachments/assets/581057cf-e7df-4e56-a0ce-ced74339906a





Release Notes:

- N/A
2025-02-10 16:12:01 -08:00
Max Brunsfeld
929c5e76b4 Fix some visual bugs w/ edit predictions (#24591)
* correct the size of key binding icons
* avoid spurious modifier in 'jump to edit' popover when already
previewing
* fix height of the edit preview popover

Release Notes:

- N/A

Co-authored-by: agu-z <hi@aguz.me>
2025-02-10 23:49:08 +00:00
Ben Kunkle
dab9c41799 Fix formatters not running in order (#24584)
Previously, if multiple formatters were specified for the same language,
they would be run in parallel on the state of the file, and then all
edits would be applied. This lead to incorrect output with many unwanted
artifacts.
This PR refactors the formatting code to clean it up, and ensure results
from previous formatters are passed in to subsequent formatters.

Closes #15544

Release Notes:

- Fixed an issue where when running multiple formatters they would be
ran in parallel rather than sequentially, leading to unwanted artifacts
and incorrect output.

---------

Co-authored-by: Conrad <conrad@zed.dev>
2025-02-10 16:18:14 -06:00
Agus Zubiaga
1f288f7327 edit predictions: Fix predictions bar disappearing while loading (#24582)
Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
2025-02-10 21:49:46 +00:00
Peter Tripp
89e051d650 Update extension extraction documentation (2025-02-10) (#24585)
Include lessons learned from PHP Extension extraction.
2025-02-10 21:13:26 +00:00
Peter Tripp
62bb3398ed Migate PHP Extension to zed-extensions/php (#24583)
PHP Extension has been extracted to it's own repository available here:
- https://github.com/zed-extensions/php
2025-02-10 16:07:38 -05:00
Agus Zubiaga
0af048a7cf edit predictions: Cache settings across renders (#24581)
We were reading edit prediction settings too often, causing frames to be
dropped. We'll now cache them and update them from
`update_visible_inline_completion`.

Release Notes:

- N/A
2025-02-10 20:57:25 +00:00
Conrad Irwin
973cb916f3 Fix fill-co-authors, and collaborator cursors (#24575)
Co-authored-by: mikayla-maki <mikayla.c.maki@gmail.com>

Release Notes:

- N/A

Co-authored-by: mikayla-maki <mikayla.c.maki@gmail.com>
2025-02-10 13:57:07 -07:00
Michael Sloan
63c0150cc2 Fix handling of holding modifier to show edit prediction (#24580)
Meant to include this in #24442

Release Notes:

- N/A
2025-02-10 13:39:03 -07:00
Michael Sloan
78a5cf0257 Fix display of bindings for editor::AcceptInlineCompletion + add validation + use modifiers from keymap (#24442)
Release Notes:

- N/A
2025-02-10 13:01:42 -07:00
Michael Sloan
43afa68dab Make migration notification not display if some bug causes no changes (#24578)
When working on #24442, I did a project wide replacement of
`AcceptInlineCompletion` with `AcceptEditPrediction`, as I was updating
the branch to mmain and that rename had happened. This also replaced it
in the migrator, causing the migration notification to always pop up on
keymap changes.

Checking if the migration actually changes the text makes it behave
better if this variety of bug happens in the future.

Release Notes:

- N/A
2025-02-10 13:01:10 -07:00
Finn Evers
0fd2203665 context_menu: Use when instead of if-block (#24566)
See
https://github.com/zed-industries/zed/pull/24562#issuecomment-2648343416
. Should have just added that to my original comment btw - sorry!

CC @danilo-leal 

Release Notes:

- N/A
2025-02-10 16:47:09 -03:00
Liam Murphy
72e1947025 Update tree-sitter to 0.24 (#24492)
I didn't update it to 0.25 because its Wasm support seems to be
partially broken due to
https://github.com/tree-sitter/tree-sitter/pull/3938: it didn't
introduce a check that the Wasm module's ABI is new enough to include
supertype info while parsing it, and so in the case where it isn't it
ends up interpreting random bytes as the number of supertypes, causing
out-of-bounds memory accesses.

Closes #24489

Release Notes:

- Fixed a rare crash during syntax highlighting
2025-02-10 10:52:27 -08:00
Max Brunsfeld
d9909c691d Fix panic when outline items have no name (#24574)
Closes #23787

Release Notes:

- Fixed a crash when searching the outline view in certain Ruby files.
2025-02-10 18:50:26 +00:00
Kirill Bulatov
4f7200527c Revert "Fix editor::GoToDiagnostics cycle (#24446)" (#24568)
This reverts commit 4f65cfa93d.

Release Notes:

- N/A
2025-02-10 17:13:12 +00:00
Nate Butler
de8d4d00ce git_ui: Update git panel commit editor, start on quick commit
- Fixes commit editor issues & updates style
- Starts on quick commit (not hooked up to anything)
- Updates some panel styles
- Adds SwitchWithLabel
- 
Release Notes:

- N/A
2025-02-10 15:52:09 +00:00
5brian
69d415c8d0 vim: Multiline operation improvements (#24518)
Closes #15711

Discussed changes to match neovim in
https://github.com/zed-industries/zed/pull/24481#issuecomment-2644504695
-- `vi{` matches neovim with treesitter instead of vanilla neovim.
Change and delete matches standard neovim.

Not sure if this is the best way to do it, implemented post processing
to change and delete objects.
I think another way would be adjust the range to trim the trailing
newline char on change and delete operations, instead of having to add
it back.

||Before|After|
|---|---|---|

|initial|![image](https://github.com/user-attachments/assets/0bab37b7-c0ac-4992-a365-b7ec304a6800)||
| `vi{` |
![image](https://github.com/user-attachments/assets/4c802fcd-fa7e-45ba-b7d4-3283ed538e10)
|
![image](https://github.com/user-attachments/assets/4394bb6e-418b-4463-9737-f9bdfc6d31c2)
|
| `ci{` |
![image](https://github.com/user-attachments/assets/b5eabb58-4a93-4c98-80b6-f34a6525b1fb)
|
![image](https://github.com/user-attachments/assets/79af57e4-260c-4432-af66-eba5285d97a0)
|
| `di{` |
![image](https://github.com/user-attachments/assets/190a70e7-71fd-47fe-9d6c-2082f2034d0f)
|
![image](https://github.com/user-attachments/assets/775b86a9-68c1-4397-a44b-c645a772de63)
|

Release Notes:

- vim: Improved multi-line operations
2025-02-10 08:45:06 -07:00
Danilo Leal
d292b7c96d context menu: Use invisible() to hide the check icon (#24562)
Follow up to: https://github.com/zed-industries/zed/pull/24549

Release Notes:

- N/A
2025-02-10 12:16:33 -03:00
Piotr Osiewicz
ca4378cbaa ui: Use cursor: pointer for Toggles (#24563)
Closes #ISSUE

Release Notes:

- N/A
2025-02-10 15:10:35 +00:00
Finn Evers
d42322ab06 php: Update brackets.scm (#24558)
Closes #24550 
Adds some missing brackets to the PHP language extension.
2025-02-10 09:42:16 -05:00
Danilo Leal
3f0288e52a docs: Add a light border to h2s (#24554)
I was finding hard to navigate the "Configuring Zed" page with just
white space creating a boundary between the different chunks of content.
I think a slight border below the h2 heading helps a lot with that!

Release Notes:

- N/A
2025-02-10 09:43:51 -03:00
Danilo Leal
d15a61a1aa context menu: Adjust toggleable entry label alignment (#24549)
Previously, we were passing an `IconSize` that had a default size. Given
the check icon is small by default, when the entry is not toggled, that
caused a slight misalignment between the toggled and not-toggled items.
I'm passing now the same icon element but inside an opacity 0 div. Open
to other suggestions if this feels clunky.

| Before | After |
|--------|--------|
| <img width="946" alt="Screenshot 2025-02-10 at 7 58 28 AM"
src="https://github.com/user-attachments/assets/4d2b3f12-72c5-4c8d-acaf-c16230250560"
/> | <img width="943" alt="Screenshot 2025-02-10 at 7 58 37 AM"
src="https://github.com/user-attachments/assets/2df64752-7273-4bdc-9f6b-5153ed52c889"
/> |

Release Notes:

- N/A
2025-02-10 08:19:58 -03:00
Danilo Leal
e72f7b4e22 edit predictions: Put back status bar button tooltips (#24548)
These were wrongly removed in
https://github.com/zed-industries/zed/pull/24540; putting them back.

cc @SomeoneToIgnore 

Release Notes:

- N/A
2025-02-10 08:19:46 -03:00
Libon
d0c4c664b0 Brighten yellow and black terminal colors in One themes (#24420)
Closes https://github.com/zed-industries/zed/issues/24419

I made some fine adjustments to the color of the theme with reference
to' Window Terminal' to make it look good. If there is anything
inappropriate in this revision, please also point it out. :)


![window-terminal-one-light](https://github.com/user-attachments/assets/86c4002f-a5a7-4ab1-81de-e6ed0529fe06)

![window-terminal-one-dark](https://github.com/user-attachments/assets/c57095d7-0131-4978-ae6d-7105639110b5)

Release Notes:
- N/A
2025-02-10 11:03:47 +00:00
CharlesChen0823
994bea0003 workspace: Fix pane focus transfer when closing another pane (#23175)
Closes #23123 

Only close current active_pane should move focus to other pane.

Release Notes:

- N/A
2025-02-10 11:54:06 +02:00
Kirill Bulatov
6f7f0f30e2 Fix hover tooltips appearing after related element is pressed (#24540)
Closes https://github.com/zed-industries/zed/issues/23894

Reworks all trigger declarations from
`.trigger(element.tooltip(tooltip))` into
`.trigger_with_tooltip(element, tooltip)` , with new API disallowing
simultaneous trigger and tooltip display.

All existing `.trigger(` calls were replaced, except 2 not applicable
(in dock.rs and pane.rs), 15 left as ones without tooltips, and 2
unchanged places in `inline_completion_button.rs`, where


0f7bb2e9fd/crates/inline_completion_button/src/inline_completion_button.rs (L311-L319)

`with_animation` does not allow us to simply use the same approach.

Release Notes:

- Fixed hover tooltips appearing after related element is pressed

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-02-10 00:16:12 +00:00
Michael Sloan
1a133ab9d8 Settings/keymap backup path next to files + update notification messages (#24517)
Before:


![image](https://github.com/user-attachments/assets/5b7d8677-b0db-4a66-ac30-e4751ba4182d)

After:


![image](https://github.com/user-attachments/assets/94743bc2-2902-43a3-8d6e-e0e0e6e469ec)

Release Notes:

- N/A
2025-02-09 16:51:37 -07:00
Kirill Bulatov
cf74d653bd Fix outline panel issues in a multi-worktree set-up (#24538)
Closes https://github.com/zed-industries/zed/issues/22993

Properly calculates depth and maintains worktree order, when displaying
multiple worktrees in the outline panel.

Release Notes:

- Fixed outline panel issues in a multi-worktree set-up
2025-02-09 21:29:29 +00:00
Nate Butler
8f1ff189cc component: Add component and component_preview crates to power UI components (#24456)
This PR formalizes design components with the Component and
ComponentPreview traits.

You can open the preview UI with `workspace: open component preview`.

Component previews no longer need to return `Self` allowing for more
complex previews, and previews of components like `ui::Tooltip` that
supplement other components rather than are rendered by default.

`cargo-machete` incorrectly identifies `linkme` as an unused dep on
crates that have components deriving `IntoComponent`, so you may need to
add this to that crate's `Cargo.toml`:

```toml
# cargo-machete doesn't understand that linkme is used in the component macro
[package.metadata.cargo-machete]
ignored = ["linkme"]
```

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-09 13:25:03 -05:00
Agus Zubiaga
56cfc60875 ui: Add buffer_font method to labels (#24479)
Now you don't need to wrap the `Label` in a `div` anymore 

Release Notes:

- N/A

Co-authored-by: Danilo <danilo@zed.dev>
2025-02-09 15:23:39 -03:00
Kirill Bulatov
6ee447ee58 Move focus into editor for outline_panel::Open action on outlines and search results (#24535)
Follow-up of
https://github.com/zed-industries/zed/discussions/19782#discussioncomment-12055976

Release Notes:

- Fixed outline panel not focusing editor when outlines and search
results were opened with `outline_panel::Open`
2025-02-09 17:27:41 +00:00
Marshall Bowers
f42177a912 ci: Pin Prettier to a specific version for docs formatting (#24531)
This PR pins Prettier to a specific version when we run the docs
formatting check.

This should prevent drift when new Prettier versions are released that
may impact the formatting.

Release Notes:

- N/A
2025-02-09 16:46:33 +00:00
Marshall Bowers
072d2b061a ui: Remove ToolStrip component (#24529)
This PR removes the `ToolStrip` component.

Pulling this change out of
https://github.com/zed-industries/zed/pull/24456.

Release Notes:

- N/A
2025-02-09 16:07:40 +00:00
Caleb!
065fdcb86b language_tools: Add background color to syntax tree view (#24524)
Closes #22830 

@jansol, please take a look. I don't know if this is correct as I
couldn't really tell the difference. I just added the active theme's
background color to the main container of the tree view.

<img width="1309" alt="Screenshot 2025-02-09 at 10 29 15 AM"
src="https://github.com/user-attachments/assets/dadf9333-0074-4bfa-bb06-ed4c4f275200"
/>
 
Release Notes:

- Added an explicit background color to the syntax tree view.

cc: @iamnbutler

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-09 15:54:14 +00:00
Henrikh Kantuni
e84d77e879 Fix typo in elm.md (#24519)
Removes duplicate mention of `elm`.
2025-02-09 11:43:09 +02:00
smit
f1693e6129 project_panel: Fix worktree root rename (#24487)
Closes #7923

This PR fixes root worktree renaming by:  

1. Handling the case where `new_path` is the new root name instead of a
relative path from the root.
2. [#20313](https://github.com/zed-industries/zed/pull/20313) added
functionality to watch for root worktree renames made externally, e.g.,
via Finder. This PR avoids relying on that watcher because, when
renaming explicitly from Zed, we can eagerly perform the necessary work
(of course after fs rename) instead of waiting for the watcher to detect
the rename. This prevents UI glitches during renaming root.

Todo:

- [x] Fix wrong abs paths when root is renamed
- [x] Fix explicit scan entry func to handle renamed root dir
- [x] Tests
- [x] Test on Linux
- [x] Tested with single and multipe worktrees
- [x] Tested when single file is root file

Release Notes:

- Fixed an issue where worktree root name couldn't be renamed in project
panel.
2025-02-09 14:16:27 +05:30
Affan Shahid
4207b194e3 docs: Fix typo in the Icon Themes page (#24516)
Release Notes:

- N/A
2025-02-08 18:35:43 -05:00
Marshall Bowers
fe6d180a1a Sort Prettier files in file_types.json (#24505)
This PR sorts the Prettier files added in #24496.

Release Notes:

- N/A
2025-02-08 16:11:31 +00:00
João Marcos
0294b19694 Track caller on <usize as ToOffset>::to_offset (#24503)
To get useful logs when reporting bugs involving offsets out of range

Release Notes:

- N/A
2025-02-08 15:29:29 +00:00
Kirill Bulatov
b1055878c7 Improve outline panel initial update (#24500)
Closes https://github.com/zed-industries/zed/issues/24128

* removed unnecessary debounces when updating the panel data
* removed all "loading"-related messages to snow nothing when initial
data is loaded, thus reducing flickering

Release Notes:

- Improved outline panel initial update
2025-02-08 12:33:47 +00:00
Sanjeev Shrestha
3582fc4636 File icons add icon association for Prettier config (#24496)
This PR adds icon association for more Prettier's config files.

Here is the list:

```
.prettierrc.cjs
.prettierrc.js
.prettierrc.json5
.prettierrc.mjs
.prettierrc.toml
.prettierrc.yaml
.prettierrc.yml
prettier.config.cjs
prettier.config.js
prettier.config.mjs
```

Release Notes:

- Added icon support for additional Prettier config file types.
2025-02-08 17:03:01 +05:30
Mikayla Maki
ca4e8043d4 Add branch to git panel (#24485)
This PR adds the branch selector to the git panel and fixes a few bugs
in the repository selector.

Release Notes:

- N/A

---------

Co-authored-by: ConradIrwin <conrad.irwin@gmail.com>
Co-authored-by: Conrad <conrad@zed.dev>
2025-02-08 03:27:58 +00:00
roycrippen4
d9183c7669 vim: Escape to normal mode when visual surround operation pending (#24484)
Closes #24382

Release Notes:
Added a default keymap that returns the user to `normal` mode after
pressing escape during a pending `visual-surround` operation.

- N/A

---------

Co-authored-by: roy.crippen4 <roy.crippen4@archarithms.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-02-07 20:23:10 -07:00
5brian
7bddb390ca vim: Preserve trailing whitespace in inner text object selections (#24481)
Closes #24438

Changes: Adjusted loop to only trim whitespace between last newline and
closing marker, when using inner objects like `y/d/c i b`

| Start   | Fixed `vib`   | Previous `vib`   |
| ---------- | ---------- | ---------- |
|
![image](https://github.com/user-attachments/assets/3d64dd7d-ed3d-4a85-9f98-f2f83799a738)
|
![image](https://github.com/user-attachments/assets/841beb59-31b1-475e-93f0-f4deaf18939c)
|
![image](https://github.com/user-attachments/assets/736d4c6f-20e1-4563-9471-1e8195455df4)
|



Release Notes:

- vim: Preserve trailing whitespace in inner text object selections

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-02-08 02:50:34 +00:00
Michael Sloan
146b9c232c Sort and dedupe .gitignore files (#24491)
Release Notes:

- N/A
2025-02-08 02:17:17 +00:00
Michael Sloan
be26acccca Cargo.lock update (#24486)
Release Notes:

- N/A
2025-02-08 00:18:20 +00:00
Beniamin Zagan
4be89ea60f title_bar: Add menu item to deploy icon theme selector (#24482)
Added the icons option in the title bar between Themes and Extension.

| Before | After |
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------------------------------------------------------------
|
| <img width="215" alt="Screenshot 2025-02-07 at 5 18 10 PM"
src="https://github.com/user-attachments/assets/ff8bf5ce-c176-4d8c-8b0e-bb1cc65ec1d8"
/> | <img width="206" alt="Screenshot 2025-02-07 at 5 18 01 PM"
src="https://github.com/user-attachments/assets/c47a302e-98af-4530-a908-097b8306f2f0"
/> |

Release Notes:

- Added an option to open the icon theme selector from the user menu.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-07 22:33:00 +00:00
Marshall Bowers
e17e838c07 Include prediction ID on edit prediction accepted/discarded events (#24480)
This PR updates the edit predictions to include the prediction ID
returned from the server on the resulting telemetry events indicating
whether the prediction was accepted or discarded.

The `prediction_id` on the events can then be correlated with the
`request_id` on the server-side prediction events.

Release Notes:

- N/A
2025-02-07 22:06:37 +00:00
Marshall Bowers
ed5656813c inline_completion: Add missing punctuation (#24477)
This PR adds some missing punctuation.

Release Notes:

- N/A
2025-02-07 21:03:37 +00:00
Danilo Leal
c4bcff1e87 edit predictions: Add binding to the prediction toggle (#24468)
This PR primary goal is to add a keybinding to the (ephemeral)
prediction toggle. In doing that, we also standardized the keybinding to
open the status bar menu with it.

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2025-02-07 18:01:39 -03:00
Danilo Leal
07f1b612cf edit predictions: Fix translucent "jump to edit" background color (#24473)
This PR uses a pretty cool GPUI method called `blend` to make this
callout's background color not translucent.

| Before | Header |
|--------|--------|
| <img width="732" alt="Screenshot 2025-02-07 at 4 58 16 PM"
src="https://github.com/user-attachments/assets/2a5df61b-dfa0-4edc-bffa-a605a2aa491a"
/> | <img width="732" alt="Screenshot 2025-02-07 at 4 56 48 PM"
src="https://github.com/user-attachments/assets/5dee9fca-6239-4ae0-80f5-dcc6abf0e779"
/> |

Release Notes:

- N/A
2025-02-07 17:58:20 -03:00
Marshall Bowers
9e5bc81f1c zeta: Promote line comment to doc comment (#24476)
This PR promotes a line comment for the `tos_accepted` field to a doc
comment.

Release Notes:

- N/A
2025-02-07 20:57:42 +00:00
Conrad Irwin
7148092e12 Fix adding new git repos to a project (#24471)
Release Notes:

- N/A
2025-02-07 20:08:09 +00:00
Jason Lee
ead5a836a1 gpui: Add data table example (#24373)
Release Notes:

- N/A

As https://github.com/zed-industries/zed/discussions/24260 I mentioned
issue.

Make a complex data table example to test the text rendering
performance.

This example also can be an example to show how to build a large data
table.

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

<img width="2004" alt="image"
src="https://github.com/user-attachments/assets/653771e5-ef08-4d76-97b9-90ea4b78be59"
/>

----

I will try to do some test. 

For example: With a threshold for the hold number of caches in
`FrameCache`, and only when the threshold is greater than a certain
number, some caches are released, or when a certain time has passed. I
am not sure if this is feasible.

This example is added to help us to test.
2025-02-07 11:54:34 -08:00
Conrad Irwin
3be8066415 Newlines in commit editor (#24465)
Release Notes:

- N/A
2025-02-07 12:31:12 -07:00
Antonio Scandurra
f6e396837c Re-introduce syntax-based context and use new model (#24469)
Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
2025-02-07 19:19:57 +00:00
Agus Zubiaga
fd7fa87939 edit predictions: Restore red dot in status buttons when pending ToS (#24408)
In one of the recent changes to the edit predictions status bar menu, we
lost the red dot that is displayed when the user has Zed as the provider
but hasn't accepted terms of service. Note: All the checks were still in
place, just the visual indicator was missing.

![CleanShot 2025-02-06 at 20 22
21@2x](https://github.com/user-attachments/assets/da8f25dd-5ed2-4bf9-8453-10b80f00bf63)


Release Notes:

- N/A

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2025-02-07 15:42:27 -03:00
Danilo Leal
a7a14e59bf edit predictions: Clarify disabled_globs documentation (#24460)
This PR clarifies how the `disabled_globs` work.

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com>
2025-02-07 19:35:55 +01:00
Marshall Bowers
8ff8dbdb2b assistant_context_editor: Fix patch block not rendering due to window reborrow (#24461)
This PR fixes an issue where the Assistant patch block was not being
rendered when using "Suggest Edits".

The issue was that the `BlockContext` already has a borrow of the
`Window`, so we can't use `update_in` to reborrow the window.

The fix is to reuse the existing `&mut Window` reference from the
`BlockContext` so we don't need to `update_in`.

Closes #24169.

Release Notes:

- Assistant: Fixed an issue where the patch block was not being rendered
when using "Suggest Edits".

---------

Co-authored-by: Max <max@zed.dev>
2025-02-07 18:03:19 +00:00
smit
44c6a54f95 pane: Improve close active item to better handle pinned tabs (#23488)
Closes #22247

- [x] Do not close pinned tab on keyboard shortcuts like `ctrl+w` or
`alt+f4`
- [x] Close pinned tab on context menu action, menu bar action, or vim
bang
- [x] While closing pinned tab via shortcut (where it won't close),
instead activate any other non-pinned tab in same pane
- [x] Else, if any other pane contains non-pinned tab, activate that
- [x] Tests

Co-authored-by: uncenter <47499684+uncenter@users.noreply.github.com>

Release Notes:

- Pinned tab now stay open when using close shortcuts, auto focuses to
any other non-pinned tab instead.
2025-02-07 22:54:57 +05:30
Bennet Bo Fenner
f0565b4e2e edit prediction: Do not show icon as disabled when there is no buffer open (#24458)
Release Notes:

- N/A
2025-02-07 17:02:14 +00:00
Conrad Irwin
a6e15dda4b Make it a bit clearer when people are running dev builds (#24457)
Release Notes:

- Include an indicator in About/CopySystemSpecs when running in debug mode
2025-02-07 09:57:37 -07:00
Finn Evers
144487bf1a theme: Implement icon theme reloading (#24449)
Closes #24353 

This PR implements icon theme reload to ensure file icons are properly
updated whenever an icon theme extension is upgraded or uninstalled.

Currently, on both upgrade and uninstall of an icon theme extension the
file icons from the previously installed version will stay visibile and
will not be updated as shown in the linked issue. With this change, file
icons will properly be updated on extension upgrade or reinstall.

The code is primarily a copy for reloading the current color theme
adapted to work for icon themes. Happy for any feedback!


Release Notes:

- Fixed file icons not being properly updated upon icon theme upgrade or
uninstall.
2025-02-07 11:30:53 -05:00
Wilhelm Klopp
2d57e43e34 docs: Emphasize that Rust must be installed via rustup (#24447)
Just tried installing a dev extension and kept getting "error: failed to install dev extension".

Turns out this was because I had rust installed via homebrew and not rust. Once I switched to rustup, it worked perfectly fine.

Release Notes:

- N/A
2025-02-07 11:29:45 -05:00
Peter Tripp
c484374b2f Make OpenKeyContextView open to the right (#24452)
Match the behavior of OpenSyntaxTreeView logs and OpenLanguageServerLogs

Release Notes:

- Make `debug::OpenSyntaxTreeView` automatically open in split to the
right
2025-02-07 11:20:27 -05:00
IaVashik
8114d17cba google_ai: Add support for Gemini 2.0 models (#24448)
Add support for the newly released Gemini 2.0 models from Google announced this new family of models earlier this week (2025-02-05).

Release Notes:

- Added support for Google's new Gemini 2.0 models.
2025-02-07 11:18:18 -05:00
Sanjeev Shrestha
c7cd5b019b file_icons: Use separate icon key for JSON files (#24432)
This PR updates the file icon mappings for JSON (`.json`) file map to
the`json` key. Also, updates `.json` icon from `storage` to `code`.

This allows for the JSON file icons to be replaced in icon themes.

Release Notes:

- Icon themes: Added the ability to change the file icon for JSON
(`.json`) files.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-07 16:14:26 +00:00
Marshall Bowers
07929229ae migrator: Sort dependencies in Cargo.toml (#24455)
This PR sorts the dependencies in the `Cargo.toml` for the `migrator`
crate.

Release Notes:

- N/A
2025-02-07 16:10:07 +00:00
smit
00c2a30059 Migrate keymap and settings + edit predictions rename (#23834)
- [x] snake case keymap properties
- [x] flatten actions
- [x] keymap migration + notfication
- [x] settings migration + notification
- [x] inline completions -> edit predictions 

### future: 
- keymap notification doesn't show up on start up, only on keymap save.
this is existing bug in zed, will be addressed in seperate PR.

Release Notes:

- Added a notification for deprecated settings and keymaps, allowing you
to migrate them with a single click. A backup of your existing keymap
and settings will be created in your home directory.
- Modified some keymap actions and settings for consistency.

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-02-07 21:17:07 +05:30
Ben Kunkle
a1544f47ad Fix incorrect assumption about Path.extension() (#24443)
Release Notes:

- N/A
2025-02-07 09:37:07 -06:00
Kirill Bulatov
4f65cfa93d Fix editor::GoToDiagnostics cycle (#24446)
https://github.com/user-attachments/assets/45f665f0-473a-49bd-b013-b9d1bdb902bd


After activating 2nd diagnostics group, `find_map` code for next
diagnostics did not skip the previous group for the same place.

Release Notes:

- Fixed `editor::GoToDiagnostics` action stuck when multiple diagnostics
groups belong to the same place
2025-02-07 16:49:13 +02:00
Piotr Osiewicz
b6b06cf6d8 lsp: Send DidOpen notifications when changing selections in multi buffer (#22958)
Fixes #22773

Release Notes:

- Fixed an edge case with multibuffers that could break language
features within them.
2025-02-07 12:33:35 +01:00
Michael Sloan
f700268029 Improve vim interactions with edit predictions (#24418)
* When an edit prediction is present in non-insertion modes, hide it but
show `tab Jump to edit`.
* Removes discarding of edit predictions when going from insert mode to
normal mode, instead just hide them in non-insertion modes.
* Removes zeta-specific showing of predictions in normal mode. This
behavior was only happening in special cases anyway - where the discard
of completions wasn't happening due to some other thing taking
precedence in `dismiss_menus_and_popups`.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2025-02-07 03:53:38 -07:00
Michael Sloan
92c21a2814 Fix undismissed app notifications appearing on new workspaces (#24437)
Bug in #23817

Release Notes:

- N/A
2025-02-07 10:29:05 +00:00
Conrad Irwin
1f9d02607b Fixes to commit button in Git Panel (#24425)
Git Panel updates:

* Fixes commit/commit all button to work (and be disabled correctly in
merge conflict status)
* Updates keyboard shortcuts and sets focus on the button (enter now
does the same as click; tab cycles between editor and change list)


Closes #ISSUE

Release Notes:

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

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-02-07 00:21:28 -07:00
smit
6534e0bafd linux: Fix crash when NoKeymap event is received on Wayland (#24379)
Closes #24139

For weird reasons, Sway on few linux distoros sends `NoKeymap` event when
switching windows. Zed crashes due to assertion on this event to be `XkbV1`.

To fix this, we ignore `NoKeymap` event instead crashing Zed.

Release Notes:

- Fixed a crash in Wayland-based compositors like Sway when switching windows via the keyboard.
2025-02-07 12:31:46 +05:30
Cole Miller
5ffacb9ca5 Revert "Move git status updates to a background thread (#24307)" (#24415)
This reverts commit 980ce5fbf2.

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-02-07 04:46:43 +00:00
Michael Sloan
864c1ff00c Use commondir from libgit2 instead of walking fs (#22028)
Release Notes:

- N/A
2025-02-07 04:38:09 +00:00
Michael Sloan
35ef269233 Fix build of remote_server when not in git repo (#24424)
Followup to #24258

Release Notes:

- N/A
2025-02-07 04:35:22 +00:00
Cole Miller
d97adfc540 Fix pairs of almost-adjacent hunks toggling together (#24355)
Release Notes:

- Fixed a bug where toggling a diff hunk that immediately precedes
another hunk would act on both hunks
2025-02-07 04:18:59 +00:00
Michael Sloan
a42e040660 Remove use of use_key_equivalents from linux keymap as it does nothing (#24422)
`use_key_equivalents` does nothing on linux, as key equivalents are only
supported on mac. While it could be sensible to anticipate support,
right now it is only used in these few spots, so removing it.

Release Notes:

- N/A
2025-02-07 03:46:15 +00:00
Conrad Irwin
8646d37c0c vim: Replace with Register (#24326)
Closes #18813

Release Notes:

- vim: Add `gr` for [replace with
register](https://github.com/vim-scripts/ReplaceWithRegister)
2025-02-06 20:24:41 -07:00
Anthony Eid
d83c316e6d Fix Project Panel select_next_git_entry action (#24217)
## Context

I noticed that the project panel `select_next_git_entry` wasn't behaving
correctly. Turns out it was searching in reverse, which caused the
action to select itself or the last entry.

This PR corrects the behavior and adds a unit test that should stop
regressions.

Note: Since select next/prev git entry uses the same function as select
next/prev diagnostic, the test partially works for that as well.

Release Notes:

- Fix bug where `select_next_git_entry` project panel action would only
select a previous entry or the currently selected entry.

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-02-07 03:04:02 +00:00
Jason Lee
c5913899d9 gpui: Fix text-align with nowrap mode (#24116)
Release Notes:

- N/A


------

- Continue #24090 to fix text align for when used `whitespace_nowrap`.
- Fix wrapped line length calculation.

And add example

```
cargo run -p gpui --example text_layout
```

<img width="760" alt="image"
src="https://github.com/user-attachments/assets/a087c300-0e0e-4a80-98c6-90161a9b0905"
/>

---------

Co-authored-by: Owen Law <owenlaw222@gmail.com>
2025-02-06 18:51:00 -08:00
Stanislav Alekseev
e689c8c01b markdown: Use parsed text (#24388)
Fixes #15463

Release Notes:

- Fixed display of symbols such as `&nbsp;` in hover popovers
2025-02-06 18:37:50 -08:00
Marshall Bowers
888a2df3f0 Sort Cargo.tomls (#24417)
This PR sorts the dependencies in a number of `Cargo.toml` files.

Release Notes:

- N/A
2025-02-07 02:14:57 +00:00
Caleb!
d6d0d7d3e4 Add image dimension and file size information (#21675)
Closes https://github.com/zed-industries/zed/issues/21281

@jansol, kindly take a look when you're free.


![image](https://github.com/user-attachments/assets/da9a54fa-6284-4012-a243-7e355a5290d3)

Release Notes:

- Added dimensions and file size information for images.

---------

Co-authored-by: tims <0xtimsb@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-02-07 00:56:34 +00:00
Ben Kunkle
a1ed1a00b3 Fix issue with Vim test instead of cheating (#24411)
Appears this test was failing, and someone edited the expected test
output instead of fixing it. Well no longer!

Release Notes:

- N/A

Co-authored-by: Conrad <conrad@zed.dev>
2025-02-06 18:43:30 -06:00
Cole Miller
a190f42ccc Fix double-lease panic in Repository::activate (#24414)
Release Notes:

- N/A

Co-authored-by: maxbrunsfeld <max@zed.dev>
2025-02-07 00:33:41 +00:00
321 changed files with 16667 additions and 8692 deletions

View File

@@ -37,28 +37,28 @@ jobs:
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Deploy Docs
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy target/deploy --project-name=docs
- name: Deploy Install
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
- name: Deploy Docs Workers
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Deploy Install Workers
uses: cloudflare/wrangler-action@7a5f8bbdfeedcde38e6777a50fe685f89259d4ca # v3
uses: cloudflare/wrangler-action@392082e81ffbcb9ebdde27400634aa004b35ea37 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

View File

@@ -24,11 +24,13 @@ jobs:
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx prettier . --check || {
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
echo "To fix, run from the root of the zed repo:"
echo " cd docs && pnpm dlx prettier . --write && cd .."
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
false
}
env:
PRETTIER_VERSION: 3.5.0
- name: Check for Typos with Typos-CLI
uses: crate-ci/typos@8e6a4285bcbde632c5d79900a7779746e8b7ea3f # v1.24.6

47
.gitignore vendored
View File

@@ -1,36 +1,35 @@
/.direnv
.envrc
.idea
**/target
**/*.db
**/cargo-target
/zed.xcworkspace
.DS_Store
/plugins/bin
/script/node_modules
/crates/theme/schemas/theme.json
/crates/collab/seed.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/dev.zed.Zed*.json
/assets/*licenses.*
**/target
**/venv
.build
*.wasm
Packages
*.xcodeproj
xcuserdata/
DerivedData/
.DS_Store
.blob_store
.build
.envrc
.flatpak-builder
.idea
.netrc
.pytest_cache
.swiftpm
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.swiftpm
**/*.db
.pytest_cache
.venv
.blob_store
.vscode
.wrangler
.flatpak-builder
.envrc
/.direnv
/assets/*licenses.*
/crates/collab/seed.json
/crates/theme/schemas/theme.json
/crates/zed/resources/flatpak/flatpak-cargo-sources.json
/dev.zed.Zed*.json
/plugins/bin
/script/node_modules
/zed.xcworkspace
DerivedData/
Packages
xcuserdata/
# Don't commit any secrets to the repo.
.env.secret.toml

420
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,13 +26,15 @@ members = [
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
"crates/component",
"crates/component_preview",
"crates/context_server",
"crates/context_server_settings",
"crates/copilot",
"crates/db",
"crates/deepseek",
"crates/diagnostics",
"crates/diff",
"crates/buffer_diff",
"crates/docs_preprocessor",
"crates/editor",
"crates/evals",
@@ -81,6 +83,7 @@ members = [
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/migrator",
"crates/multi_buffer",
"crates/node_runtime",
"crates/notifications",
@@ -146,7 +149,6 @@ members = [
"crates/ui_macros",
"crates/util",
"crates/util_macros",
"crates/vcs_menu",
"crates/vim",
"crates/vim_mode_setting",
"crates/welcome",
@@ -169,7 +171,6 @@ members = [
"extensions/haskell",
"extensions/html",
"extensions/lua",
"extensions/php",
"extensions/perplexity",
"extensions/proto",
"extensions/purescript",
@@ -226,13 +227,15 @@ collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
component_preview = { path = "crates/component_preview" }
context_server = { path = "crates/context_server" }
context_server_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
deepseek = { path = "crates/deepseek" }
diagnostics = { path = "crates/diagnostics" }
diff = { path = "crates/diff" }
buffer_diff = { path = "crates/buffer_diff" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" }
@@ -279,6 +282,7 @@ markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
media = { path = "crates/media" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
multi_buffer = { path = "crates/multi_buffer" }
node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
@@ -344,7 +348,6 @@ ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
welcome = { path = "crates/welcome" }
@@ -419,6 +422,7 @@ ignore = "0.4.22"
image = "0.25.1"
indexmap = { version = "2.7.0", features = ["serde"] }
indoc = "2"
inventory = "0.3.19"
itertools = "0.14.0"
jsonwebtoken = "9.3"
jupyter-protocol = { version = "0.6.0" }
@@ -426,6 +430,7 @@ jupyter-websocket-client = { version = "0.9.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
linkme = "0.3.31"
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
"dispatcher",
"services-dispatcher",
@@ -477,7 +482,7 @@ rustc-demangle = "0.1.23"
rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.22" }
rustls-native-certs = "0.8.0"
rustls-platform-verifier = "0.5.0"
schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
@@ -497,6 +502,7 @@ simplelog = "0.12.2"
smallvec = { version = "1.6", features = ["union"] }
smol = "2.0"
sqlformat = "0.2"
streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0"
@@ -518,7 +524,7 @@ tiny_http = "0.8"
toml = "0.8"
tokio = { version = "1" }
tower-http = "0.4.4"
tree-sitter = { version = "0.23", features = ["wasm"] }
tree-sitter = { version = "0.24", features = ["wasm"] }
tree-sitter-bash = "0.23"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
@@ -547,19 +553,19 @@ unicode-segmentation = "1.10"
unicode-script = "0.5.7"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] }
wasmparser = "0.215"
wasm-encoder = "0.215"
wasmtime = { version = "24", default-features = false, features = [
wasmparser = "0.217"
wasm-encoder = "0.217"
wasmtime = { version = "25", default-features = false, features = [
"async",
"demangle",
"runtime",
"cranelift",
"component-model",
] }
wasmtime-wasi = "24"
wasmtime-wasi = "25"
which = "6.0.0"
wit-component = "0.201"
zed_llm_client = "0.2"
zed_llm_client = "0.4"
zstd = "0.11"
metal = "0.31"
@@ -674,7 +680,6 @@ telemetry_events = { codegen-units = 1 }
theme_selector = { codegen-units = 1 }
time_format = { codegen-units = 1 }
ui_input = { codegen-units = 1 }
vcs_menu = { codegen-units = 1 }
zed_actions = { codegen-units = 1 }
[profile.release]

View File

@@ -18,18 +18,23 @@
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bicep": "bicep",
"bmp": "image",
"c": "c",
"c++": "cpp",
"cc": "cpp",
"cjs": "javascript",
"cjsx": "react",
"coffee": "coffeescript",
"conf": "settings",
"cpp": "cpp",
"cs": "csharp",
"css": "css",
"csv": "storage",
"cxx": "cpp",
"cts": "typescript",
"ctsx": "react",
"cue": "cue",
"dart": "dart",
"dat": "storage",
"db": "storage",
@@ -64,6 +69,7 @@
"gitattributes": "vcs",
"gitignore": "vcs",
"gitkeep": "vcs",
"gitlab-ci.yml": "gitlab",
"gitmodules": "vcs",
"TAG_EDITMSG": "vcs",
"MERGE_MSG": "vcs",
@@ -101,7 +107,7 @@
"jpeg": "image",
"jpg": "image",
"js": "javascript",
"json": "storage",
"json": "json",
"jsonc": "storage",
"jsx": "react",
"jxl": "image",
@@ -111,16 +117,18 @@
"lockb": "bun",
"log": "log",
"lua": "lua",
"luau": "luau",
"m4a": "audio",
"m4v": "video",
"markdown": "document",
"md": "document",
"markdown": "markdown",
"md": "markdown",
"mdb": "storage",
"mdf": "storage",
"mdx": "document",
"metadata": "code",
"metal": "metal",
"mjs": "javascript",
"mjsx": "react",
"mka": "audio",
"mkv": "video",
"ml": "ocaml",
@@ -130,6 +138,7 @@
"mp3": "audio",
"mp4": "video",
"mts": "typescript",
"mtsx": "react",
"myd": "storage",
"myi": "storage",
"nim": "nim",
@@ -150,8 +159,19 @@
"postcss": "css",
"ppt": "document",
"pptx": "document",
"prettier.config.cjs": "prettier",
"prettier.config.js": "prettier",
"prettier.config.mjs": "prettier",
"prettierignore": "prettier",
"prettierrc": "prettier",
"prettierrc.cjs": "prettier",
"prettierrc.js": "prettier",
"prettierrc.json": "prettier",
"prettierrc.json5": "prettier",
"prettierrc.mjs": "prettier",
"prettierrc.toml": "prettier",
"prettierrc.yaml": "prettier",
"prettierrc.yml": "prettier",
"prisma": "prisma",
"profile": "terminal",
"ps1": "terminal",
@@ -173,9 +193,21 @@
"scss": "sass",
"sdf": "storage",
"sh": "terminal",
"sol": "solidity",
"sql": "storage",
"sqlite": "storage",
"svelte": "template",
"stylelint.config.cjs": "stylelint",
"stylelint.config.js": "stylelint",
"stylelint.config.mjs": "stylelint",
"stylelintignore": "stylelint",
"stylelintrc": "stylelint",
"stylelintrc.cjs": "stylelint",
"stylelintrc.js": "stylelint",
"stylelintrc.json": "stylelint",
"stylelintrc.mjs": "stylelint",
"stylelintrc.yaml": "stylelint",
"stylelintrc.yml": "stylelint",
"svelte": "svelte",
"svg": "image",
"swift": "swift",
"tcl": "tcl",

View File

@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5C5 3.89543 5.89543 3 7 3H9C10.1046 3 11 3.89543 11 5V6H5V5Z" stroke="black" stroke-width="1.5"/>
<path d="M8 9V11" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="8" cy="9" r="1" fill="black"/>
<rect x="3.75" y="5.75" width="8.5" height="7.5" rx="1.25" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 452 B

View File

@@ -1,6 +1,6 @@
<svg width="440" height="128" xmlns="http://www.w3.org/2000/svg">
<svg width="550" height="128" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="tilePattern" width="22" height="22" patternUnits="userSpaceOnUse">
<pattern id="tilePattern" width="23" height="23" patternUnits="userSpaceOnUse">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 5L14 8L12 11" stroke="black" stroke-width="1.5"/>
<path d="M10 6.5L11 8L10 9.5" stroke="black" stroke-width="1.5"/>

Before

Width:  |  Height:  |  Size: 971 B

After

Width:  |  Height:  |  Size: 971 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 9.33333L14.5 9.66667L12.5 11L10.5 9.66667L10 9.33333" stroke="black" stroke-width="1.5"/>
<path d="M12.5 11V4.5" stroke="black" stroke-width="1.5"/>
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 375 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 6.66667L10.5 6.33333L12.5 5L14.5 6.33333L15 6.66667" stroke="black" stroke-width="1.5"/>
<path d="M12.5 11V5" stroke="black" stroke-width="1.5"/>
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 372 B

View File

@@ -31,8 +31,8 @@
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f11": "zed::ToggleFullScreen",
"ctrl-alt-z": "zeta::RateCompletions",
"ctrl-shift-i": "inline_completion::ToggleMenu"
"ctrl-alt-z": "edit_prediction::RateCompletions",
"ctrl-shift-i": "edit_prediction::ToggleMenu"
}
},
{
@@ -122,7 +122,8 @@
"ctrl-i": "editor::ShowSignatureHelp",
"alt-g b": "editor::ToggleGitBlame",
"menu": "editor::OpenContextMenu",
"shift-f10": "editor::OpenContextMenu"
"shift-f10": "editor::OpenContextMenu",
"ctrl-shift-e": "editor::ToggleEditPrediction"
}
},
{
@@ -145,17 +146,17 @@
}
},
{
"context": "Editor && mode == full && inline_completion",
"context": "Editor && mode == full && edit_prediction",
"bindings": {
"alt-]": "editor::NextInlineCompletion",
"alt-[": "editor::PreviousInlineCompletion",
"alt-right": "editor::AcceptPartialInlineCompletion"
"alt-]": "editor::NextEditPrediction",
"alt-[": "editor::PreviousEditPrediction",
"alt-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Editor && !inline_completion",
"context": "Editor && !edit_prediction",
"bindings": {
"alt-\\": "editor::ShowInlineCompletion"
"alt-\\": "editor::ShowEditPrediction"
}
},
{
@@ -274,8 +275,8 @@
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"ctrl-f4": "pane::CloseActiveItem",
"ctrl-w": "pane::CloseActiveItem",
"ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }],
"ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
@@ -348,15 +349,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", { "level": 1 }],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", { "level": 2 }],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", { "level": 3 }],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", { "level": 4 }],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", { "level": 5 }],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", { "level": 6 }],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", { "level": 7 }],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", { "level": 8 }],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", { "level": 9 }],
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -432,14 +433,14 @@
"ctrl-alt-s": "workspace::SaveAll",
"ctrl-k m": "language_selector::Toggle",
"escape": "workspace::Unfollow",
"ctrl-k ctrl-left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-k ctrl-right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-k ctrl-up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-k ctrl-down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-k ctrl-left": "workspace::ActivatePaneLeft",
"ctrl-k ctrl-right": "workspace::ActivatePaneRight",
"ctrl-k ctrl-up": "workspace::ActivatePaneUp",
"ctrl-k ctrl-down": "workspace::ActivatePaneDown",
"ctrl-k shift-left": "workspace::SwapPaneLeft",
"ctrl-k shift-right": "workspace::SwapPaneRight",
"ctrl-k shift-up": "workspace::SwapPaneUp",
"ctrl-k shift-down": "workspace::SwapPaneDown",
"ctrl-shift-x": "zed::Extensions",
"ctrl-shift-r": "task::Rerun",
"ctrl-alt-r": "task::Rerun",
@@ -453,8 +454,8 @@
{
"context": "ApplicationMenu",
"bindings": {
"left": ["app_menu::NavigateApplicationMenuInDirection", "Left"],
"right": ["app_menu::NavigateApplicationMenuInDirection", "Right"]
"left": "app_menu::ActivateMenuLeft",
"right": "app_menu::ActivateMenuRight"
}
},
// Bindings from Sublime Text
@@ -496,24 +497,27 @@
},
{
"context": "Editor && showing_completions",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::ConfirmCompletion",
"tab": "editor::ComposeCompletion"
}
},
// Bindings for accepting edit predictions
//
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This is
// because alt-tab may not be available, as it is often used for window switching.
{
"context": "Editor && inline_completion",
"context": "Editor && edit_prediction",
"bindings": {
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
"alt-enter": "editor::AcceptInlineCompletion"
"alt-tab": "editor::AcceptEditPrediction",
"alt-l": "editor::AcceptEditPrediction"
}
},
{
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
"use_key_equivalents": true,
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"bindings": {
"tab": "editor::AcceptInlineCompletion"
"tab": "editor::AcceptEditPrediction",
"alt-l": "editor::AcceptEditPrediction"
}
},
{
@@ -537,8 +541,7 @@
{
"bindings": {
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
"ctrl-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints"
"ctrl-alt-i": "zed::DebugElements"
}
},
{
@@ -556,7 +559,8 @@
"ctrl-shift-e": "pane::RevealInProjectPanel",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
{
@@ -602,14 +606,12 @@
},
{
"context": "MessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "assistant2::Chat"
}
},
{
"context": "ContextStrip",
"use_key_equivalents": true,
"bindings": {
"up": "assistant2::FocusUp",
"right": "assistant2::FocusRight",
@@ -702,30 +704,32 @@
},
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrev",
"down": "menu::SelectNext",
"enter": "menu::Confirm",
"space": "git::ToggleStaged",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll"
"ctrl-shift-space": "git::UnstageAll",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus"
}
},
{
"context": "GitPanel && CommitEditor > Editor",
"use_key_equivalents": true,
"context": "GitPanel > Editor",
"bindings": {
"escape": "git_panel::FocusChanges",
"ctrl-enter": "git::CommitChanges",
"ctrl-shift-enter": "git::CommitAllChanges"
"ctrl-enter": "git::Commit",
"tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges"
}
},
{
@@ -833,7 +837,6 @@
},
{
"context": "ZedPredictModal",
"use_key_equivalents": true,
"bindings": {
"escape": "menu::Cancel"
}

View File

@@ -39,8 +39,8 @@
"cmd-m": "zed::Minimize",
"fn-f": "zed::ToggleFullScreen",
"ctrl-cmd-f": "zed::ToggleFullScreen",
"ctrl-shift-z": "zeta::RateCompletions",
"ctrl-shift-i": "inline_completion::ToggleMenu"
"ctrl-cmd-z": "edit_prediction::RateCompletions",
"ctrl-cmd-i": "edit_prediction::ToggleMenu"
}
},
{
@@ -132,7 +132,8 @@
"cmd-alt-g b": "editor::ToggleGitBlame",
"cmd-i": "editor::ShowSignatureHelp",
"ctrl-f12": "editor::GoToDeclaration",
"alt-ctrl-f12": "editor::GoToDeclarationSplit"
"alt-ctrl-f12": "editor::GoToDeclarationSplit",
"ctrl-cmd-e": "editor::ToggleEditPrediction"
}
},
{
@@ -155,19 +156,19 @@
}
},
{
"context": "Editor && mode == full && inline_completion",
"context": "Editor && mode == full && edit_prediction",
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::NextInlineCompletion",
"alt-shift-tab": "editor::PreviousInlineCompletion",
"ctrl-cmd-right": "editor::AcceptPartialInlineCompletion"
"alt-tab": "editor::NextEditPrediction",
"alt-shift-tab": "editor::PreviousEditPrediction",
"ctrl-cmd-right": "editor::AcceptPartialEditPrediction"
}
},
{
"context": "Editor && !inline_completion",
"context": "Editor && !edit_prediction",
"use_key_equivalents": true,
"bindings": {
"alt-tab": "editor::ShowInlineCompletion"
"alt-tab": "editor::ShowEditPrediction"
}
},
{
@@ -349,7 +350,7 @@
"cmd-}": "pane::ActivateNextItem",
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"cmd-w": "pane::CloseActiveItem",
"cmd-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
@@ -413,15 +414,15 @@
"cmd-k cmd-l": "editor::ToggleFold",
"cmd-k cmd-[": "editor::FoldRecursive",
"cmd-k cmd-]": "editor::UnfoldRecursive",
"cmd-k cmd-1": ["editor::FoldAtLevel", { "level": 1 }],
"cmd-k cmd-2": ["editor::FoldAtLevel", { "level": 2 }],
"cmd-k cmd-3": ["editor::FoldAtLevel", { "level": 3 }],
"cmd-k cmd-4": ["editor::FoldAtLevel", { "level": 4 }],
"cmd-k cmd-5": ["editor::FoldAtLevel", { "level": 5 }],
"cmd-k cmd-6": ["editor::FoldAtLevel", { "level": 6 }],
"cmd-k cmd-7": ["editor::FoldAtLevel", { "level": 7 }],
"cmd-k cmd-8": ["editor::FoldAtLevel", { "level": 8 }],
"cmd-k cmd-9": ["editor::FoldAtLevel", { "level": 9 }],
"cmd-k cmd-1": ["editor::FoldAtLevel", 1],
"cmd-k cmd-2": ["editor::FoldAtLevel", 2],
"cmd-k cmd-3": ["editor::FoldAtLevel", 3],
"cmd-k cmd-4": ["editor::FoldAtLevel", 4],
"cmd-k cmd-5": ["editor::FoldAtLevel", 5],
"cmd-k cmd-6": ["editor::FoldAtLevel", 6],
"cmd-k cmd-7": ["editor::FoldAtLevel", 7],
"cmd-k cmd-8": ["editor::FoldAtLevel", 8],
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
// Using `ctrl-space` in Zed requires disabling the macOS global shortcut.
@@ -509,14 +510,14 @@
"cmd-alt-s": "workspace::SaveAll",
"cmd-k m": "language_selector::Toggle",
"escape": "workspace::Unfollow",
"cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"],
"cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"],
"cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"],
"cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"],
"cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"],
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"cmd-k cmd-left": "workspace::ActivatePaneLeft",
"cmd-k cmd-right": "workspace::ActivatePaneRight",
"cmd-k cmd-up": "workspace::ActivatePaneUp",
"cmd-k cmd-down": "workspace::ActivatePaneDown",
"cmd-k shift-left": "workspace::SwapPaneLeft",
"cmd-k shift-right": "workspace::SwapPaneRight",
"cmd-k shift-up": "workspace::SwapPaneUp",
"cmd-k shift-down": "workspace::SwapPaneDown",
"cmd-shift-x": "zed::Extensions"
}
},
@@ -580,17 +581,16 @@
}
},
{
"context": "Editor && inline_completion",
"context": "Editor && edit_prediction",
"bindings": {
// Changing the modifier currently breaks accepting while you also an LSP completions menu open
"alt-tab": "editor::AcceptInlineCompletion"
"alt-tab": "editor::AcceptEditPrediction"
}
},
{
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"use_key_equivalents": true,
"bindings": {
"tab": "editor::AcceptInlineCompletion"
"tab": "editor::AcceptEditPrediction"
}
},
{
@@ -619,8 +619,7 @@
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
"cmd-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints"
"cmd-alt-i": "zed::DebugElements"
}
},
{
@@ -633,7 +632,8 @@
"cmd-shift-e": "pane::RevealInProjectPanel",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"
"ctrl-enter": "assistant::InlineAssist",
"ctrl-:": "editor::ToggleInlayHints"
}
},
{
@@ -715,13 +715,6 @@
"space": "project_panel::Open"
}
},
{
"context": "GitPanel && !CommitEditor",
"use_key_equivalents": true,
"bindings": {
"escape": "git_panel::Close"
}
},
{
"context": "GitPanel && ChangesList",
"use_key_equivalents": true,
@@ -734,17 +727,21 @@
"space": "git::ToggleStaged",
"cmd-shift-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll",
"alt-down": "git_panel::FocusEditor"
"alt-down": "git_panel::FocusEditor",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus"
}
},
{
"context": "GitPanel && CommitEditor > Editor",
"context": "GitPanel > Editor",
"use_key_equivalents": true,
"bindings": {
"alt-up": "git_panel::FocusChanges",
"escape": "git_panel::FocusChanges",
"cmd-enter": "git::CommitChanges",
"cmd-alt-enter": "git::CommitAllChanges"
"enter": "editor::Newline",
"cmd-enter": "git::Commit",
"tab": "git_panel::FocusChanges",
"shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges"
}
},
{

View File

@@ -2,8 +2,8 @@
{
"context": "VimControl && !menu",
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"i": ["vim::PushObject", { "around": false }],
"a": ["vim::PushObject", { "around": true }],
"left": "vim::Left",
"h": "vim::Left",
"backspace": "vim::Backspace",
@@ -54,10 +54,10 @@
// "b": "vim::PreviousSubwordStart",
// "e": "vim::NextSubwordEnd",
// "g e": "vim::PreviousSubwordEnd",
"shift-w": ["vim::NextWordStart", { "ignorePunctuation": true }],
"shift-e": ["vim::NextWordEnd", { "ignorePunctuation": true }],
"shift-b": ["vim::PreviousWordStart", { "ignorePunctuation": true }],
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
"shift-w": ["vim::NextWordStart", { "ignore_punctuation": true }],
"shift-e": ["vim::NextWordEnd", { "ignore_punctuation": true }],
"shift-b": ["vim::PreviousWordStart", { "ignore_punctuation": true }],
"g shift-e": ["vim::PreviousWordEnd", { "ignore_punctuation": true }],
"/": "vim::Search",
"g /": "pane::DeploySearch",
"?": ["vim::Search", { "backwards": true }],
@@ -70,20 +70,20 @@
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
"] )": ["vim::UnmatchedForward", { "char": ")" }],
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
"f": ["vim::PushOperator", { "FindForward": { "before": false } }],
"t": ["vim::PushOperator", { "FindForward": { "before": true } }],
"shift-f": ["vim::PushOperator", { "FindBackward": { "after": false } }],
"shift-t": ["vim::PushOperator", { "FindBackward": { "after": true } }],
"m": ["vim::PushOperator", "Mark"],
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
"f": ["vim::PushFindForward", { "before": false }],
"t": ["vim::PushFindForward", { "before": true }],
"shift-f": ["vim::PushFindBackward", { "after": false }],
"shift-t": ["vim::PushFindBackward", { "after": true }],
"m": "vim::PushMark",
"'": ["vim::PushJump", { "line": true }],
"`": ["vim::PushJump", { "line": false }],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
"ctrl-i": "pane::GoForward",
"ctrl-]": "editor::GoToDefinition",
"escape": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"escape": "vim::SwitchToNormalMode",
"ctrl-[": "vim::SwitchToNormalMode",
"v": "vim::ToggleVisual",
"shift-v": "vim::ToggleVisualLine",
"ctrl-g": "vim::ShowLocation",
@@ -102,6 +102,7 @@
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
// "g" commands
"g r": "vim::PushReplaceWithRegister",
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g t": "pane::ActivateNextItem",
@@ -124,17 +125,17 @@
"g .": "editor::ToggleCodeActions", // zed specific
"g shift-a": "editor::FindAllReferences", // zed specific
"g space": "editor::OpenExcerpts", // zed specific
"g *": ["vim::MoveToNext", { "partialWord": true }],
"g #": ["vim::MoveToPrev", { "partialWord": true }],
"g j": ["vim::Down", { "displayLines": true }],
"g down": ["vim::Down", { "displayLines": true }],
"g k": ["vim::Up", { "displayLines": true }],
"g up": ["vim::Up", { "displayLines": true }],
"g $": ["vim::EndOfLine", { "displayLines": true }],
"g end": ["vim::EndOfLine", { "displayLines": true }],
"g 0": ["vim::StartOfLine", { "displayLines": true }],
"g home": ["vim::StartOfLine", { "displayLines": true }],
"g ^": ["vim::FirstNonWhitespace", { "displayLines": true }],
"g *": ["vim::MoveToNext", { "partial_word": true }],
"g #": ["vim::MoveToPrev", { "partial_word": true }],
"g j": ["vim::Down", { "display_lines": true }],
"g down": ["vim::Down", { "display_lines": true }],
"g k": ["vim::Up", { "display_lines": true }],
"g up": ["vim::Up", { "display_lines": true }],
"g $": ["vim::EndOfLine", { "display_lines": true }],
"g end": ["vim::EndOfLine", { "display_lines": true }],
"g 0": ["vim::StartOfLine", { "display_lines": true }],
"g home": ["vim::StartOfLine", { "display_lines": true }],
"g ^": ["vim::FirstNonWhitespace", { "display_lines": true }],
"g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic",
"g [": "editor::GoToPrevDiagnostic",
@@ -146,7 +147,7 @@
"shift-l": "vim::WindowBottom",
"q": "vim::ToggleRecord",
"shift-q": "vim::ReplayLastRecording",
"@": ["vim::PushOperator", "ReplayRegister"],
"@": "vim::PushReplayRegister",
// z commands
"z enter": ["workspace::SendKeystrokes", "z t ^"],
"z -": ["workspace::SendKeystrokes", "z b ^"],
@@ -165,8 +166,8 @@
"z f": "editor::FoldSelectedRanges",
"z shift-m": "editor::FoldAll",
"z shift-r": "editor::UnfoldAll",
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
"shift-z shift-q": ["pane::CloseActiveItem", { "save_intent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "save_intent": "save_all" }],
// Count support
"1": ["vim::Number", 1],
"2": ["vim::Number", 2],
@@ -193,13 +194,13 @@
"escape": "editor::Cancel",
":": "command_palette::Toggle",
".": "vim::Repeat",
"c": ["vim::PushOperator", "Change"],
"c": "vim::PushChange",
"shift-c": "vim::ChangeToEndOfLine",
"d": ["vim::PushOperator", "Delete"],
"d": "vim::PushDelete",
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"g shift-j": "vim::JoinLinesNoWhitespace",
"y": ["vim::PushOperator", "Yank"],
"y": "vim::PushYank",
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
@@ -216,19 +217,19 @@
"shift-p": ["vim::Paste", { "before": true }],
"u": "vim::Undo",
"ctrl-r": "vim::Redo",
"r": ["vim::PushOperator", "Replace"],
"r": "vim::PushReplace",
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
">": ["vim::PushOperator", "Indent"],
"<": ["vim::PushOperator", "Outdent"],
"=": ["vim::PushOperator", "AutoIndent"],
"!": ["vim::PushOperator", "ShellCommand"],
"g u": ["vim::PushOperator", "Lowercase"],
"g shift-u": ["vim::PushOperator", "Uppercase"],
"g ~": ["vim::PushOperator", "OppositeCase"],
"\"": ["vim::PushOperator", "Register"],
"g w": ["vim::PushOperator", "Rewrap"],
"g q": ["vim::PushOperator", "Rewrap"],
">": "vim::PushIndent",
"<": "vim::PushOutdent",
"=": "vim::PushAutoIndent",
"!": "vim::PushShellCommand",
"g u": "vim::PushLowercase",
"g shift-u": "vim::PushUppercase",
"g ~": "vim::PushOppositeCase",
"\"": "vim::PushRegister",
"g w": "vim::PushRewrap",
"g q": "vim::PushRewrap",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore",
@@ -239,7 +240,7 @@
"[ d": "editor::GoToPrevDiagnostic",
"] c": "editor::GoToHunk",
"[ c": "editor::GoToPrevHunk",
"g c": ["vim::PushOperator", "ToggleComments"]
"g c": "vim::PushToggleComments"
}
},
{
@@ -264,14 +265,14 @@
"y": "vim::VisualYank",
"shift-y": "vim::VisualYankLine",
"p": "vim::Paste",
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
"shift-p": ["vim::Paste", { "preserve_clipboard": true }],
"c": "vim::Substitute",
"s": "vim::Substitute",
"shift-r": "vim::SubstituteLine",
"shift-s": "vim::SubstituteLine",
"~": "vim::ChangeCase",
"*": ["vim::MoveToNext", { "partialWord": true }],
"#": ["vim::MoveToPrev", { "partialWord": true }],
"*": ["vim::MoveToNext", { "partial_word": true }],
"#": ["vim::MoveToPrev", { "partial_word": true }],
"ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement",
"g ctrl-a": ["vim::Increment", { "step": true }],
@@ -282,19 +283,19 @@
"g shift-a": "vim::VisualInsertEndOfLine",
"shift-j": "vim::JoinLines",
"g shift-j": "vim::JoinLinesNoWhitespace",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
"ctrl-[": ["vim::SwitchMode", "Normal"],
"escape": ["vim::SwitchMode", "Normal"],
"r": "vim::PushReplace",
"ctrl-c": "vim::SwitchToNormalMode",
"ctrl-[": "vim::SwitchToNormalMode",
"escape": "vim::SwitchToNormalMode",
">": "vim::Indent",
"<": "vim::Outdent",
"=": "vim::AutoIndent",
"!": "vim::ShellCommand",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"i": ["vim::PushObject", { "around": false }],
"a": ["vim::PushObject", { "around": true }],
"g c": "vim::ToggleComments",
"g q": "vim::Rewrap",
"\"": ["vim::PushOperator", "Register"],
"\"": "vim::PushRegister",
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
"] x": "editor::SelectSmallerSyntaxNode"
@@ -309,19 +310,19 @@
"ctrl-x": null,
"ctrl-x ctrl-o": "editor::ShowCompletions",
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
"ctrl-x ctrl-c": "editor::ShowEditPrediction", // zed specific
"ctrl-x ctrl-l": "editor::ToggleCodeActions", // zed specific
"ctrl-x ctrl-z": "editor::Cancel",
"ctrl-w": "editor::DeleteToPreviousWordStart",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-k": ["vim::PushDigraph", {}],
"ctrl-v": ["vim::PushLiteral", {}],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-r": ["vim::PushOperator", "Register"],
"ctrl-q": ["vim::PushLiteral", {}],
"ctrl-shift-q": ["vim::PushLiteral", {}],
"ctrl-r": "vim::PushRegister",
"insert": "vim::ToggleReplace",
"ctrl-o": "vim::TemporaryNormal"
}
@@ -356,11 +357,11 @@
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"escape": "vim::NormalBefore",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-k": ["vim::PushDigraph", {}],
"ctrl-v": ["vim::PushLiteral", {}],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-q": ["vim::PushLiteral", {}],
"ctrl-shift-q": ["vim::PushLiteral", {}],
"backspace": "vim::UndoReplace",
"tab": "vim::Tab",
"enter": "vim::Enter",
@@ -375,9 +376,15 @@
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators",
"escape": "vim::ClearOperators",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
"ctrl-k": ["vim::PushDigraph", {}],
"ctrl-v": ["vim::PushLiteral", {}],
"ctrl-q": ["vim::PushLiteral", {}]
}
},
{
"context": "Editor && vim_mode == waiting && (vim_operator == ys || vim_operator == cs)",
"bindings": {
"escape": "vim::SwitchToNormalMode"
}
},
{
@@ -393,10 +400,10 @@
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
"shift-w": ["vim::Word", { "ignore_punctuation": true }],
// Subword TextObject
// "w": "vim::Subword",
// "shift-w": ["vim::Subword", { "ignorePunctuation": true }],
// "shift-w": ["vim::Subword", { "ignore_punctuation": true }],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
@@ -419,7 +426,7 @@
">": "vim::AngleBrackets",
"a": "vim::Argument",
"i": "vim::IndentObj",
"shift-i": ["vim::IndentObj", { "includeBelow": true }],
"shift-i": ["vim::IndentObj", { "include_below": true }],
"f": "vim::Method",
"c": "vim::Class",
"e": "vim::EntireFile"
@@ -430,14 +437,14 @@
"bindings": {
"c": "vim::CurrentLine",
"d": "editor::Rename", // zed specific
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
"s": ["vim::PushChangeSurrounds", {}]
}
},
{
"context": "vim_operator == d",
"bindings": {
"d": "vim::CurrentLine",
"s": ["vim::PushOperator", "DeleteSurrounds"],
"s": "vim::PushDeleteSurrounds",
"o": "editor::ToggleSelectedDiffHunks", // "d o"
"p": "editor::RevertSelectedHunks" // "d p"
}
@@ -476,7 +483,7 @@
"context": "vim_operator == y",
"bindings": {
"y": "vim::CurrentLine",
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
"s": ["vim::PushAddSurrounds", {}]
}
},
{
@@ -566,34 +573,34 @@
}
},
{
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"context": "GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w >": ["vim::ResizePane", "Widen"],
"ctrl-w <": ["vim::ResizePane", "Narrow"],
"ctrl-w -": ["vim::ResizePane", "Shorten"],
"ctrl-w +": ["vim::ResizePane", "Lengthen"],
"ctrl-w left": "workspace::ActivatePaneLeft",
"ctrl-w right": "workspace::ActivatePaneRight",
"ctrl-w up": "workspace::ActivatePaneUp",
"ctrl-w down": "workspace::ActivatePaneDown",
"ctrl-w ctrl-h": "workspace::ActivatePaneLeft",
"ctrl-w ctrl-l": "workspace::ActivatePaneRight",
"ctrl-w ctrl-k": "workspace::ActivatePaneUp",
"ctrl-w ctrl-j": "workspace::ActivatePaneDown",
"ctrl-w h": "workspace::ActivatePaneLeft",
"ctrl-w l": "workspace::ActivatePaneRight",
"ctrl-w k": "workspace::ActivatePaneUp",
"ctrl-w j": "workspace::ActivatePaneDown",
"ctrl-w shift-left": "workspace::SwapPaneLeft",
"ctrl-w shift-right": "workspace::SwapPaneRight",
"ctrl-w shift-up": "workspace::SwapPaneUp",
"ctrl-w shift-down": "workspace::SwapPaneDown",
"ctrl-w shift-h": "workspace::SwapPaneLeft",
"ctrl-w shift-l": "workspace::SwapPaneRight",
"ctrl-w shift-k": "workspace::SwapPaneUp",
"ctrl-w shift-j": "workspace::SwapPaneDown",
"ctrl-w >": "vim::ResizePaneRight",
"ctrl-w <": "vim::ResizePaneLeft",
"ctrl-w -": "vim::ResizePaneDown",
"ctrl-w +": "vim::ResizePaneUp",
"ctrl-w _": "vim::MaximizePane",
"ctrl-w =": "vim::ResetPaneSizes",
"ctrl-w g t": "pane::ActivateNextItem",
@@ -624,7 +631,7 @@
}
},
{
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"context": "ChangesList || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
@@ -687,5 +694,22 @@
"shift-x": "git::StageAll",
"shift-u": "git::UnstageAll"
}
},
{
"context": "Editor && edit_prediction && !edit_prediction_requires_modifier",
"bindings": {
// This is identical to the binding in the base keymap, but the vim bindings above to
// "vim::Tab" shadow it, so it needs to be bound again.
"tab": "editor::AcceptEditPrediction"
}
},
{
"context": "os != macos && Editor && edit_prediction",
"bindings": {
// alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This
// is because alt-tab may not be available, as it is often used for window switching on Linux
// and Windows.
"alt-l": "editor::AcceptEditPrediction"
}
}
]

View File

@@ -25,7 +25,7 @@
// Features that can be globally enabled or disabled
"features": {
// Which edit prediction provider to use.
"inline_completion_provider": "copilot"
"edit_prediction_provider": "copilot"
},
// The name of a font to use for rendering text in the editor
"buffer_font_family": "Zed Plex Mono",
@@ -93,6 +93,13 @@
// workspace when the centered layout is used.
"right_padding": 0.2
},
// All settings related to the image viewer.
"image_viewer": {
// The unit for image file sizes.
// By default we're setting it to binary.
// The second option is decimal.
"unit": "binary"
},
// The key to use for adding multiple cursors
// Currently "alt" or "cmd_or_ctrl" (also aliased as
// "cmd" and "ctrl") are supported.
@@ -161,9 +168,6 @@
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
"show_signature_help_after_edits": false,
/// Whether to show the edit predictions next to the completions provided by a language server.
/// Only has an effect if edit prediction provider supports it.
"show_inline_completions_in_menu": true,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
@@ -197,11 +201,11 @@
// no matter how they were inserted.
"always_treat_brackets_as_autoclosed": false,
// Controls whether edit predictions are shown immediately (true)
// or manually by triggering `editor::ShowInlineCompletion` (false).
"show_inline_completions": true,
// or manually by triggering `editor::ShowEditPrediction` (false).
"show_edit_predictions": true,
// Controls whether edit predictions are shown in a given language scope.
// Example: ["string", "comment"]
"inline_completions_disabled_in": [],
"edit_predictions_disabled_in": [],
// Whether to show tabs and spaces in the editor.
// This setting can take four values:
//
@@ -648,15 +652,15 @@
// There are 5 possible width values:
//
// 1. Small: This value is essentially a fixed width.
// "modal_width": "small"
// "modal_max_width": "small"
// 2. Medium:
// "modal_width": "medium"
// "modal_max_width": "medium"
// 3. Large:
// "modal_width": "large"
// "modal_max_width": "large"
// 4. Extra Large:
// "modal_width": "xlarge"
// "modal_max_width": "xlarge"
// 5. Fullscreen: This value removes any horizontal padding, as it consumes the whole viewport width.
// "modal_width": "full"
// "modal_max_width": "full"
//
// Default: small
"modal_max_width": "small"
@@ -774,8 +778,10 @@
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
"load_direnv": "direct",
"inline_completions": {
"edit_predictions": {
// A list of globs representing files that edit predictions should be disabled for.
// There's a sensible default list of globs already included.
// Any addition to this list will be merged with the default list.
"disabled_globs": [
"**/.env*",
"**/*.pem",
@@ -787,10 +793,10 @@
// When to show edit predictions previews in buffer.
// This setting takes two possible values:
// 1. Display inline when there are no language server completions available.
// "inline_preview": "auto"
// "mode": "eager_preview"
// 2. Display inline when holding modifier key (alt by default).
// "inline_preview": "when_holding_modifier"
"inline_preview": "auto"
// "mode": "auto"
"mode": "eager_preview"
},
// Settings specific to journaling
"journal": {

View File

@@ -81,7 +81,7 @@
"terminal.ansi.bright_green": "#4d6140ff",
"terminal.ansi.dim_green": "#d1e0bfff",
"terminal.ansi.yellow": "#dec184ff",
"terminal.ansi.bright_yellow": "#786441ff",
"terminal.ansi.bright_yellow": "#e5c07bff",
"terminal.ansi.dim_yellow": "#f1dfc1ff",
"terminal.ansi.blue": "#74ade8ff",
"terminal.ansi.bright_blue": "#385378ff",
@@ -457,7 +457,7 @@
"terminal.ansi.bright_green": "#b2cfa9ff",
"terminal.ansi.dim_green": "#354d2eff",
"terminal.ansi.yellow": "#dec184ff",
"terminal.ansi.bright_yellow": "#f1dfc1ff",
"terminal.ansi.bright_yellow": "#826221ff",
"terminal.ansi.dim_yellow": "#786441ff",
"terminal.ansi.blue": "#5c78e2ff",
"terminal.ansi.bright_blue": "#b5baf2ff",

View File

@@ -250,10 +250,10 @@ impl AssistantPanel {
)
.child(
PopoverMenu::new("assistant-panel-popover-menu")
.trigger(
.trigger_with_tooltip(
IconButton::new("menu", IconName::EllipsisVertical)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Toggle Assistant Menu")),
.icon_size(IconSize::Small),
Tooltip::text("Toggle Assistant Menu"),
)
.menu(move |window, cx| {
let zoom_label = if _pane.read(cx).is_zoomed() {

View File

@@ -1255,7 +1255,7 @@ impl InlineAssistant {
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_show_scrollbars(false, cx);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), window, cx);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.highlight_rows::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,
@@ -1595,22 +1595,22 @@ impl Render for PromptEditor {
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
}),
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
))
.map(|el| {
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {

View File

@@ -646,22 +646,22 @@ impl Render for PromptEditor {
IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
}),
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
format!(
"Using {}",
LanguageModelRegistry::read_global(cx)
.active_model()
.map(|model| model.name().0)
.unwrap_or_else(|| "No model selected".into()),
),
None,
"Change Model",
window,
cx,
)
},
))
.children(
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {

View File

@@ -74,16 +74,16 @@ impl Render for AssistantModelSelector {
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
}),
},
)
.with_handle(self.menu_handle.clone())
}

View File

@@ -660,11 +660,11 @@ impl AssistantPanel {
.gap(DynamicSpacing::Base02.rems(cx))
.child(
PopoverMenu::new("assistant-toolbar-new-popover-menu")
.trigger(
.trigger_with_tooltip(
IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(Tooltip::text("New…")),
.style(ButtonStyle::Subtle),
Tooltip::text("New…"),
)
.anchor(Corner::TopRight)
.with_handle(self.new_item_context_menu_handle.clone())
@@ -677,11 +677,11 @@ impl AssistantPanel {
)
.child(
PopoverMenu::new("assistant-toolbar-history-popover-menu")
.trigger(
.trigger_with_tooltip(
IconButton::new("open-history", IconName::HistoryRerun)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(Tooltip::text("History…")),
.style(ButtonStyle::Subtle),
Tooltip::text("History…"),
)
.anchor(Corner::TopRight)
.with_handle(self.open_history_context_menu_handle.clone())

View File

@@ -411,22 +411,22 @@ impl Render for ContextStrip {
Some(context_picker.clone())
})
.trigger(
.trigger_with_tooltip(
IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
}),
.style(ui::ButtonStyle::Filled),
{
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
window,
cx,
)
}
},
)
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)

View File

@@ -1345,7 +1345,7 @@ impl InlineAssistant {
editor.scroll_manager.set_forbid_vertical_scroll(true);
editor.set_show_scrollbars(false, cx);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), window, cx);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.highlight_rows::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,

View File

@@ -832,12 +832,13 @@ impl ContextEditor {
let render_block: RenderBlock = Arc::new({
let this = this.clone();
let patch_range = range.clone();
move |cx: &mut BlockContext<'_, '_>| {
move |cx: &mut BlockContext| {
let max_width = cx.max_width;
let gutter_width = cx.gutter_dimensions.full_width();
let block_id = cx.block_id;
let selected = cx.selected;
this.update_in(cx, |this, window, cx| {
let window = &mut cx.window;
this.update(cx.app, |this, cx| {
this.render_patch_block(
patch_range.clone(),
max_width,
@@ -2358,8 +2359,8 @@ impl ContextEditor {
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.tooltip(Tooltip::text("Type / to insert via keyboard")),
.icon_position(IconPosition::Start),
Tooltip::text("Type / to insert via keyboard"),
)
}
@@ -3322,10 +3323,10 @@ impl Render for ContextEditorToolbarItem {
.color(Color::Muted)
.size(IconSize::XSmall),
),
)
.tooltip(move |window, cx| {
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
}),
),
move |window, cx| {
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
},
)
.with_handle(self.language_model_selector_menu_handle.clone()),
)

View File

@@ -1,17 +1,22 @@
use std::sync::Arc;
use assistant_slash_command::SlashCommandWorkingSet;
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakEntity};
use gpui::{AnyElement, AnyView, DismissEvent, SharedString, Task, WeakEntity};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger, Tooltip};
use crate::context_editor::ContextEditor;
#[derive(IntoElement)]
pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
pub(super) struct SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakEntity<ContextEditor>,
trigger: T,
tooltip: TT,
}
#[derive(Clone)]
@@ -48,16 +53,22 @@ pub(crate) struct SlashCommandDelegate {
selected_index: usize,
}
impl<T: PopoverTrigger> SlashCommandSelector<T> {
impl<T, TT> SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub(crate) fn new(
working_set: Arc<SlashCommandWorkingSet>,
active_context_editor: WeakEntity<ContextEditor>,
trigger: T,
tooltip: TT,
) -> Self {
SlashCommandSelector {
working_set,
active_context_editor,
trigger,
tooltip,
}
}
}
@@ -241,7 +252,11 @@ impl PickerDelegate for SlashCommandDelegate {
}
}
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
impl<T, TT> RenderOnce for SlashCommandSelector<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let all_models = self
.working_set
@@ -322,7 +337,7 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
.ok();
PopoverMenu::new("model-switcher")
.menu(move |_window, _cx| Some(picker_view.clone()))
.trigger(self.trigger)
.trigger_with_tooltip(self.trigger, self.tooltip)
.attach(gpui::Corner::TopLeft)
.anchor(gpui::Corner::BottomLeft)
.offset(gpui::Point {

View File

@@ -1,5 +1,5 @@
[package]
name = "diff"
name = "buffer_diff"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,9 +9,13 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/diff.rs"
path = "src/buffer_diff.rs"
[features]
test-support = []
[dependencies]
anyhow.workspace = true
futures.workspace = true
git2.workspace = true
gpui.workspace = true
@@ -23,10 +27,11 @@ text.workspace = true
util.workspace = true
[dev-dependencies]
unindent.workspace = true
serde_json.workspace = true
ctor.workspace = true
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
text = {workspace = true, features = ["test-support"]}
[features]
test-support = []
rand.workspace = true
serde_json.workspace = true
text = { workspace = true, features = ["test-support"] }
unindent.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -33,8 +33,6 @@ postage.workspace = true
rand.workspace = true
release_channel.workspace = true
rpc = { workspace = true, features = ["gpui"] }
rustls-native-certs.workspace = true
rustls.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -146,8 +146,6 @@ pub fn init_settings(cx: &mut App) {
}
pub fn init(client: &Arc<Client>, cx: &mut App) {
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let client = Arc::downgrade(client);
cx.on_action({
let client = client.clone();
@@ -1126,24 +1124,11 @@ impl Client {
match url_scheme {
Https => {
let client_config = {
let mut root_store = rustls::RootCertStore::empty();
let root_certs = rustls_native_certs::load_native_certs();
for error in root_certs.errors {
log::warn!("error loading native certs: {:?}", error);
}
root_store.add_parsable_certificates(root_certs.certs);
rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth()
};
let (stream, _) =
async_tungstenite::async_tls::client_async_tls_with_connector(
request,
stream,
Some(client_config.into()),
Some(http_client::tls_config().into()),
)
.await?;
Ok(Connection::new(

View File

@@ -33,7 +33,7 @@ clock.workspace = true
collections.workspace = true
dashmap.workspace = true
derive_more.workspace = true
diff.workspace = true
buffer_diff.workspace = true
envy = "0.4.2"
futures.workspace = true
google_ai.workspace = true
@@ -131,7 +131,7 @@ worktree = { workspace = true, features = ["test-support"] }
livekit_client_macos = { workspace = true, features = ["test-support"] }
[target.'cfg(not(target_os = "macos"))'.dev-dependencies]
livekit_client = {workspace = true, features = ["test-support"] }
livekit_client = { workspace = true, features = ["test-support"] }
[package.metadata.cargo-machete]
ignored = ["async-stripe"]

View File

@@ -101,6 +101,7 @@ CREATE TABLE "worktree_repositories" (
"scan_id" INTEGER NOT NULL,
"is_deleted" BOOL NOT NULL,
"current_merge_conflicts" VARCHAR,
"branch_summary" VARCHAR,
PRIMARY KEY(project_id, worktree_id, work_directory_id),
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

View File

@@ -0,0 +1,2 @@
ALTER TABLE worktree_repositories
ADD COLUMN worktree_repositories VARCHAR NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE worktree_repositories ADD COLUMN branch_summary TEXT NULL;

View File

@@ -326,16 +326,26 @@ impl Database {
if !update.updated_repositories.is_empty() {
worktree_repository::Entity::insert_many(update.updated_repositories.iter().map(
|repository| worktree_repository::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),
branch: ActiveValue::set(repository.branch.clone()),
is_deleted: ActiveValue::set(false),
current_merge_conflicts: ActiveValue::Set(Some(
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
)),
|repository| {
worktree_repository::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),
branch: ActiveValue::set(repository.branch.clone()),
is_deleted: ActiveValue::set(false),
branch_summary: ActiveValue::Set(
repository
.branch_summary
.as_ref()
.map(|summary| serde_json::to_string(summary).unwrap()),
),
current_merge_conflicts: ActiveValue::Set(Some(
serde_json::to_string(&repository.current_merge_conflicts).unwrap(),
)),
}
},
))
.on_conflict(
@@ -347,6 +357,8 @@ impl Database {
.update_columns([
worktree_repository::Column::ScanId,
worktree_repository::Column::Branch,
worktree_repository::Column::BranchSummary,
worktree_repository::Column::CurrentMergeConflicts,
])
.to_owned(),
)
@@ -779,6 +791,13 @@ impl Database {
.transpose()?
.unwrap_or_default();
let branch_summary = db_repository_entry
.branch_summary
.as_ref()
.map(|branch_summary| serde_json::from_str(&branch_summary))
.transpose()?
.unwrap_or_default();
worktree.repository_entries.insert(
db_repository_entry.work_directory_id as u64,
proto::RepositoryEntry {
@@ -787,6 +806,7 @@ impl Database {
updated_statuses,
removed_statuses: Vec::new(),
current_merge_conflicts,
branch_summary,
},
);
}

View File

@@ -743,12 +743,20 @@ impl Database {
.transpose()?
.unwrap_or_default();
let branch_summary = db_repository
.branch_summary
.as_ref()
.map(|branch_summary| serde_json::from_str(&branch_summary))
.transpose()?
.unwrap_or_default();
worktree.updated_repositories.push(proto::RepositoryEntry {
work_directory_id: db_repository.work_directory_id as u64,
branch: db_repository.branch,
updated_statuses,
removed_statuses,
current_merge_conflicts,
branch_summary,
});
}
}

View File

@@ -133,26 +133,23 @@ impl Database {
initial_channel_id: Option<ChannelId>,
tx: &DatabaseTransaction,
) -> Result<User> {
if let Some(user_by_github_user_id) = user::Entity::find()
.filter(user::Column::GithubUserId.eq(github_user_id))
.one(tx)
if let Some(existing_user) = self
.get_user_by_github_user_id_or_github_login(github_user_id, github_login, tx)
.await?
{
let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
user_by_github_user_id.github_user_created_at =
ActiveValue::set(Some(github_user_created_at));
Ok(user_by_github_user_id.update(tx).await?)
} else if let Some(user_by_github_login) = user::Entity::find()
.filter(user::Column::GithubLogin.eq(github_login))
.one(tx)
.await?
{
let mut user_by_github_login = user_by_github_login.into_active_model();
user_by_github_login.github_user_id = ActiveValue::set(github_user_id);
user_by_github_login.github_user_created_at =
ActiveValue::set(Some(github_user_created_at));
Ok(user_by_github_login.update(tx).await?)
let mut existing_user = existing_user.into_active_model();
existing_user.github_login = ActiveValue::set(github_login.into());
existing_user.github_user_created_at = ActiveValue::set(Some(github_user_created_at));
if let Some(github_email) = github_email {
existing_user.email_address = ActiveValue::set(Some(github_email.into()));
}
if let Some(github_name) = github_name {
existing_user.name = ActiveValue::set(Some(github_name.into()));
}
Ok(existing_user.update(tx).await?)
} else {
let user = user::Entity::insert(user::ActiveModel {
email_address: ActiveValue::set(github_email.map(|email| email.into())),
@@ -183,6 +180,34 @@ impl Database {
}
}
/// Tries to retrieve a user, first by their GitHub user ID, and then by their GitHub login.
///
/// Returns `None` if a user is not found with this GitHub user ID or GitHub login.
pub async fn get_user_by_github_user_id_or_github_login(
&self,
github_user_id: i32,
github_login: &str,
tx: &DatabaseTransaction,
) -> Result<Option<User>> {
if let Some(user_by_github_user_id) = user::Entity::find()
.filter(user::Column::GithubUserId.eq(github_user_id))
.one(tx)
.await?
{
return Ok(Some(user_by_github_user_id));
}
if let Some(user_by_github_login) = user::Entity::find()
.filter(user::Column::GithubLogin.eq(github_login))
.one(tx)
.await?
{
return Ok(Some(user_by_github_login));
}
Ok(None)
}
/// get_all_users returns the next page of users. To get more call again with
/// the same limit and the page incremented by 1.
pub async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>> {

View File

@@ -15,6 +15,8 @@ pub struct Model {
pub is_deleted: bool,
// JSON array typed string
pub current_merge_conflicts: Option<String>,
// A JSON object representing the current Branch values
pub branch_summary: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -395,6 +395,9 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::Stage>)
.add_request_handler(forward_mutating_project_request::<proto::Unstage>)
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)
.add_request_handler(forward_read_only_project_request::<proto::GitReset>)
.add_request_handler(forward_mutating_project_request::<proto::SetIndexText>)
.add_request_handler(forward_mutating_project_request::<proto::OpenCommitMessageBuffer>)
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
.add_message_handler(update_context)

View File

@@ -8,6 +8,7 @@ use crate::{
use anyhow::{anyhow, Result};
use assistant_context_editor::ContextStore;
use assistant_slash_command::SlashCommandWorkingSet;
use buffer_diff::{assert_hunks, DiffHunkSecondaryStatus, DiffHunkStatus};
use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet};
@@ -2613,11 +2614,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(1..2, "", "two\n")],
&[(1..2, "", "two\n", DiffHunkStatus::added())],
);
});
@@ -2641,11 +2642,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(1..2, "", "two\n")],
&[(1..2, "", "two\n", DiffHunkStatus::added())],
);
});
@@ -2663,11 +2664,16 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(committed_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(1..2, "TWO\n", "two\n")],
&[(
1..2,
"TWO\n",
"two\n",
DiffHunkStatus::Modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
)],
);
});
@@ -2689,11 +2695,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(new_staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(2..3, "", "three\n")],
&[(2..3, "", "three\n", DiffHunkStatus::added())],
);
});
@@ -2703,11 +2709,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(new_staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(2..3, "", "three\n")],
&[(2..3, "", "three\n", DiffHunkStatus::added())],
);
});
@@ -2717,11 +2723,16 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(new_committed_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(1..2, "TWO_HUNDRED\n", "two\n")],
&[(
1..2,
"TWO_HUNDRED\n",
"two\n",
DiffHunkStatus::Modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
)],
);
});
@@ -2763,11 +2774,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&diff.base_text_string().unwrap(),
&[(1..2, "", "two\n")],
&[(1..2, "", "two\n", DiffHunkStatus::added())],
);
});
@@ -2790,11 +2801,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&staged_text,
&[(1..2, "", "two\n")],
&[(1..2, "", "two\n", DiffHunkStatus::added())],
);
});
@@ -2812,11 +2823,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(new_staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&new_staged_text,
&[(2..3, "", "three\n")],
&[(2..3, "", "three\n", DiffHunkStatus::added())],
);
});
@@ -2826,11 +2837,11 @@ async fn test_git_diff_base_change(
diff.base_text_string().as_deref(),
Some(new_staged_text.as_str())
);
diff::assert_hunks(
diff.snapshot.hunks_in_row_range(0..4, buffer),
assert_hunks(
diff.hunks_in_row_range(0..4, buffer, cx),
buffer,
&new_staged_text,
&[(2..3, "", "three\n")],
&[(2..3, "", "three\n", DiffHunkStatus::added())],
);
});
}
@@ -2884,7 +2895,10 @@ async fn test_git_branch_name(
assert_eq!(worktrees.len(), 1);
let worktree = worktrees[0].clone();
let root_entry = worktree.read(cx).snapshot().root_git_entry().unwrap();
assert_eq!(root_entry.branch(), branch_name.map(Into::into));
assert_eq!(
root_entry.branch().map(|branch| branch.name.to_string()),
branch_name
);
}
// Smoke test branch reading
@@ -6772,7 +6786,7 @@ async fn test_remote_git_branches(
})
});
assert_eq!(host_branch.as_ref(), branches[2]);
assert_eq!(host_branch.name, branches[2]);
// Also try creating a new branch
cx_b.update(|cx| {
@@ -6793,5 +6807,5 @@ async fn test_remote_git_branches(
})
});
assert_eq!(host_branch.as_ref(), "totally-new-branch");
assert_eq!(host_branch.name, "totally-new-branch");
}

View File

@@ -314,7 +314,7 @@ async fn test_ssh_collaboration_git_branches(
})
});
assert_eq!(server_branch.as_ref(), branches[2]);
assert_eq!(server_branch.name, branches[2]);
// Also try creating a new branch
cx_b.update(|cx| {
@@ -337,7 +337,7 @@ async fn test_ssh_collaboration_git_branches(
})
});
assert_eq!(server_branch.as_ref(), "totally-new-branch");
assert_eq!(server_branch.name, "totally-new-branch");
}
#[gpui::test]

View File

@@ -461,7 +461,7 @@ impl Item for ChannelView {
.child(
Label::new(channel_name)
.color(params.text_color())
.italic(params.preview),
.when(params.preview, |this| this.italic()),
)
.when_some(status, |element, status| {
element.child(

View File

@@ -198,26 +198,29 @@ impl CommandPaletteDelegate {
) {
self.updating_matches.take();
let mut intercept_result = CommandPaletteInterceptor::try_global(cx)
.and_then(|interceptor| interceptor.intercept(&query, cx));
let mut intercept_results = CommandPaletteInterceptor::try_global(cx)
.map(|interceptor| interceptor.intercept(&query, cx))
.unwrap_or_default();
if parse_zed_link(&query, cx).is_some() {
intercept_result = Some(CommandInterceptResult {
intercept_results = vec![CommandInterceptResult {
action: OpenZedUrl { url: query.clone() }.boxed_clone(),
string: query.clone(),
positions: vec![],
})
}]
}
if let Some(CommandInterceptResult {
let mut new_matches = Vec::new();
for CommandInterceptResult {
action,
string,
positions,
}) = intercept_result
} in intercept_results
{
if let Some(idx) = matches
.iter()
.position(|m| commands[m.candidate_id].action.type_id() == action.type_id())
.position(|m| commands[m.candidate_id].action.partial_eq(&*action))
{
matches.remove(idx);
}
@@ -225,18 +228,16 @@ impl CommandPaletteDelegate {
name: string.clone(),
action,
});
matches.insert(
0,
StringMatch {
candidate_id: commands.len() - 1,
string,
positions,
score: 0.0,
},
)
new_matches.push(StringMatch {
candidate_id: commands.len() - 1,
string,
positions,
score: 0.0,
})
}
new_matches.append(&mut matches);
self.commands = commands;
self.matches = matches;
self.matches = new_matches;
if self.matches.is_empty() {
self.selected_ix = 0;
} else {

View File

@@ -108,7 +108,7 @@ pub struct CommandInterceptResult {
/// An interceptor for the command palette.
#[derive(Default)]
pub struct CommandPaletteInterceptor(
Option<Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>>,
Option<Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>>,
);
#[derive(Default)]
@@ -132,10 +132,12 @@ impl CommandPaletteInterceptor {
}
/// Intercepts the given query from the command palette.
pub fn intercept(&self, query: &str, cx: &App) -> Option<CommandInterceptResult> {
let handler = self.0.as_ref()?;
(handler)(query, cx)
pub fn intercept(&self, query: &str, cx: &App) -> Vec<CommandInterceptResult> {
if let Some(handler) = self.0.as_ref() {
(handler)(query, cx)
} else {
Vec::new()
}
}
/// Clears the global interceptor.
@@ -146,7 +148,7 @@ impl CommandPaletteInterceptor {
/// Sets the global interceptor.
///
/// This will override the previous interceptor, if it exists.
pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Option<CommandInterceptResult>>) {
pub fn set(&mut self, handler: Box<dyn Fn(&str, &App) -> Vec<CommandInterceptResult>>) {
self.0 = Some(handler);
}
}

View File

@@ -0,0 +1,23 @@
[package]
name = "component"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/component.rs"
[dependencies]
collections.workspace = true
gpui.workspace = true
linkme.workspace = true
once_cell = "1.20.3"
parking_lot.workspace = true
theme.workspace = true
[features]
default = []

View File

@@ -0,0 +1,327 @@
use std::ops::{Deref, DerefMut};
use collections::HashMap;
use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
use linkme::distributed_slice;
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use theme::ActiveTheme;
pub trait Component {
fn scope() -> Option<&'static str>;
fn name() -> &'static str {
std::any::type_name::<Self>()
}
fn description() -> Option<&'static str> {
None
}
}
pub trait ComponentPreview: Component {
fn preview(_window: &mut Window, _cx: &App) -> AnyElement;
}
#[distributed_slice]
pub static __ALL_COMPONENTS: [fn()] = [..];
#[distributed_slice]
pub static __ALL_PREVIEWS: [fn()] = [..];
pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
Lazy::new(|| RwLock::new(ComponentRegistry::new()));
pub struct ComponentRegistry {
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>,
}
impl ComponentRegistry {
fn new() -> Self {
ComponentRegistry {
components: Vec::new(),
previews: HashMap::default(),
}
}
}
pub fn init() {
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
for f in component_fns {
f();
}
for f in preview_fns {
f();
}
}
pub fn register_component<T: Component>() {
let component_data = (T::scope(), T::name(), T::description());
COMPONENT_DATA.write().components.push(component_data);
}
pub fn register_preview<T: ComponentPreview>() {
let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement);
COMPONENT_DATA
.write()
.previews
.insert(preview_data.0, preview_data.1);
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ComponentId(pub &'static str);
#[derive(Clone)]
pub struct ComponentMetadata {
name: SharedString,
scope: Option<SharedString>,
description: Option<SharedString>,
preview: Option<fn(&mut Window, &App) -> AnyElement>,
}
impl ComponentMetadata {
pub fn name(&self) -> SharedString {
self.name.clone()
}
pub fn scope(&self) -> Option<SharedString> {
self.scope.clone()
}
pub fn description(&self) -> Option<SharedString> {
self.description.clone()
}
pub fn preview(&self) -> Option<fn(&mut Window, &App) -> AnyElement> {
self.preview
}
}
pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
impl AllComponents {
pub fn new() -> Self {
AllComponents(HashMap::default())
}
/// Returns all components with previews
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
self.0.values().filter(|c| c.preview.is_some()).collect()
}
/// Returns all components with previews sorted by name
pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
let mut previews: Vec<ComponentMetadata> =
self.all_previews().into_iter().cloned().collect();
previews.sort_by_key(|a| a.name());
previews
}
/// Returns all components
pub fn all(&self) -> Vec<&ComponentMetadata> {
self.0.values().collect()
}
/// Returns all components sorted by name
pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
components.sort_by_key(|a| a.name());
components
}
}
impl Deref for AllComponents {
type Target = HashMap<ComponentId, ComponentMetadata>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for AllComponents {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub fn components() -> AllComponents {
let data = COMPONENT_DATA.read();
let mut all_components = AllComponents::new();
for &(scope, name, description) in &data.components {
let scope = scope.map(Into::into);
let preview = data.previews.get(name).cloned();
all_components.insert(
ComponentId(name),
ComponentMetadata {
name: name.into(),
scope,
description: description.map(Into::into),
preview,
},
);
}
all_components
}
/// Which side of the preview to show labels on
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExampleLabelSide {
/// Left side
Left,
/// Right side
Right,
#[default]
/// Top side
Top,
/// Bottom side
Bottom,
}
/// A single example of a component.
#[derive(IntoElement)]
pub struct ComponentExample {
variant_name: SharedString,
element: AnyElement,
label_side: ExampleLabelSide,
grow: bool,
}
impl RenderOnce for ComponentExample {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let base = div().flex();
let base = match self.label_side {
ExampleLabelSide::Right => base.flex_row(),
ExampleLabelSide::Left => base.flex_row_reverse(),
ExampleLabelSide::Bottom => base.flex_col(),
ExampleLabelSide::Top => base.flex_col_reverse(),
};
base.gap_1()
.p_2()
.text_sm()
.text_color(cx.theme().colors().text)
.when(self.grow, |this| this.flex_1())
.child(self.element)
.child(self.variant_name)
.into_any_element()
}
}
impl ComponentExample {
/// Create a new example with the given variant name and example value.
pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
Self {
variant_name: variant_name.into(),
element,
label_side: ExampleLabelSide::default(),
grow: false,
}
}
/// Set the example to grow to fill the available horizontal space.
pub fn grow(mut self) -> Self {
self.grow = true;
self
}
}
/// A group of component examples.
#[derive(IntoElement)]
pub struct ComponentExampleGroup {
pub title: Option<SharedString>,
pub examples: Vec<ComponentExample>,
pub grow: bool,
}
impl RenderOnce for ComponentExampleGroup {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.flex_col()
.text_sm()
.text_color(cx.theme().colors().text_muted)
.when(self.grow, |this| this.w_full().flex_1())
.when_some(self.title, |this, title| {
this.gap_4().pb_5().child(
div()
.flex()
.items_center()
.gap_3()
.child(div().h_px().w_4().bg(cx.theme().colors().border_variant))
.child(
div()
.flex_none()
.text_size(px(10.))
.child(title.to_uppercase()),
)
.child(
div()
.h_px()
.w_full()
.flex_1()
.bg(cx.theme().colors().border),
),
)
})
.child(
div()
.flex()
.items_start()
.w_full()
.gap_8()
.children(self.examples)
.into_any_element(),
)
.into_any_element()
}
}
impl ComponentExampleGroup {
/// Create a new group of examples with the given title.
pub fn new(examples: Vec<ComponentExample>) -> Self {
Self {
title: None,
examples,
grow: false,
}
}
/// Create a new group of examples with the given title.
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
Self {
title: Some(title.into()),
examples,
grow: false,
}
}
/// Set the group to grow to fill the available horizontal space.
pub fn grow(mut self) -> Self {
self.grow = true;
self
}
}
/// Create a single example
pub fn single_example(
variant_name: impl Into<SharedString>,
example: AnyElement,
) -> ComponentExample {
ComponentExample::new(variant_name, example)
}
/// Create a group of examples without a title
pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
ComponentExampleGroup::new(examples)
}
/// Create a group of examples with a title
pub fn example_group_with_title(
title: impl Into<SharedString>,
examples: Vec<ComponentExample>,
) -> ComponentExampleGroup {
ComponentExampleGroup::with_title(title, examples)
}

View File

@@ -1,5 +1,5 @@
[package]
name = "vcs_menu"
name = "component_preview"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -8,14 +8,14 @@ license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/component_preview.rs"
[features]
default = []
[dependencies]
anyhow.workspace = true
fuzzy.workspace = true
git.workspace = true
component.workspace = true
gpui.workspace = true
picker.workspace = true
project.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
zed_actions.workspace = true

View File

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

View File

@@ -0,0 +1,190 @@
//! # Component Preview
//!
//! A view for exploring Zed components.
use component::{components, ComponentMetadata};
use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
use ui::prelude::*;
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
workspace.register_action(
|workspace, _: &workspace::OpenComponentPreview, window, cx| {
let component_preview = cx.new(ComponentPreview::new);
workspace.add_item_to_active_pane(
Box::new(component_preview),
None,
true,
window,
cx,
)
},
);
})
.detach();
}
struct ComponentPreview {
focus_handle: FocusHandle,
}
impl ComponentPreview {
pub fn new(cx: &mut Context<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
}
}
fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
let components = components().all_sorted();
let sorted_components = components.clone();
v_flex()
.max_w_48()
.gap_px()
.p_1()
.children(
sorted_components
.into_iter()
.map(|component| self.render_sidebar_entry(&component, _cx)),
)
.child(
Label::new("These will be clickable once the layout is moved to a gpui::List.")
.color(Color::Muted)
.size(LabelSize::XSmall)
.italic(),
)
}
fn render_sidebar_entry(
&self,
component: &ComponentMetadata,
_cx: &Context<Self>,
) -> impl IntoElement {
h_flex()
.w_40()
.px_1p5()
.py_0p5()
.text_sm()
.child(component.name().clone())
}
fn render_preview(
&self,
component: &ComponentMetadata,
window: &mut Window,
cx: &Context<Self>,
) -> impl IntoElement {
let name = component.name();
let scope = component.scope();
let description = component.description();
v_flex()
.border_b_1()
.border_color(cx.theme().colors().border)
.w_full()
.gap_3()
.py_6()
.child(
v_flex()
.gap_1()
.child(
h_flex()
.gap_1()
.text_2xl()
.child(div().child(name))
.when_some(scope, |this, scope| {
this.child(div().opacity(0.5).child(format!("({})", scope)))
}),
)
.when_some(description, |this, description| {
this.child(
div()
.text_ui_sm(cx)
.text_color(cx.theme().colors().text_muted)
.max_w(px(600.0))
.child(description),
)
}),
)
.when_some(component.preview(), |this, preview| {
this.child(preview(window, cx))
})
.into_any_element()
}
fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
v_flex()
.id("component-previews")
.size_full()
.overflow_y_scroll()
.p_4()
.gap_4()
.children(
components()
.all_previews_sorted()
.iter()
.map(|component| self.render_preview(component, window, cx)),
)
}
}
impl Render for ComponentPreview {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
h_flex()
.id("component-preview")
.key_context("ComponentPreview")
.items_start()
.overflow_hidden()
.size_full()
.max_h_full()
.track_focus(&self.focus_handle)
.px_2()
.bg(cx.theme().colors().editor_background)
.child(self.render_sidebar(window, cx))
.child(self.render_previews(window, cx))
}
}
impl EventEmitter<ItemEvent> for ComponentPreview {}
impl Focusable for ComponentPreview {
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl Item for ComponentPreview {
type Event = ItemEvent;
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some("Component Preview".into())
}
fn telemetry_event_text(&self) -> Option<&'static str> {
None
}
fn show_toolbar(&self) -> bool {
false
}
fn clone_on_split(
&self,
_workspace_id: Option<WorkspaceId>,
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Entity<Self>>
where
Self: Sized,
{
Some(cx.new(Self::new))
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
f(*event)
}
}

View File

@@ -17,7 +17,7 @@ use gpui::{
use http_client::github::get_release_by_tag_name;
use http_client::HttpClient;
use language::{
language_settings::{all_language_settings, language_settings, InlineCompletionProvider},
language_settings::{all_language_settings, language_settings, EditPredictionProvider},
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
ToPointUtf16,
};
@@ -368,8 +368,8 @@ impl Copilot {
let server_id = self.server_id;
let http = self.http.clone();
let node_runtime = self.node_runtime.clone();
if all_language_settings(None, cx).inline_completions.provider
== InlineCompletionProvider::Copilot
if all_language_settings(None, cx).edit_predictions.provider
== EditPredictionProvider::Copilot
{
if matches!(self.server, CopilotServer::Disabled) {
let start_task = cx

View File

@@ -1,7 +1,7 @@
use crate::{Completion, Copilot};
use anyhow::Result;
use gpui::{App, Context, Entity, EntityId, Task};
use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
use inline_completion::{Direction, EditPredictionProvider, InlineCompletion};
use language::{language_settings::AllLanguageSettings, Buffer, OffsetRangeExt, ToOffset};
use project::Project;
use settings::Settings;
@@ -48,7 +48,7 @@ impl CopilotCompletionProvider {
}
}
impl InlineCompletionProvider for CopilotCompletionProvider {
impl EditPredictionProvider for CopilotCompletionProvider {
fn name() -> &'static str {
"copilot"
}
@@ -61,10 +61,6 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
false
}
fn show_completions_in_normal_mode() -> bool {
false
}
fn is_refreshing(&self) -> bool {
self.pending_refresh.is_some()
}
@@ -246,6 +242,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
} else {
let position = cursor_position.bias_right(buffer);
Some(InlineCompletion {
id: None,
edits: vec![(position..position, completion_text.into())],
edit_preview: None,
})
@@ -305,7 +302,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@@ -440,8 +437,8 @@ mod tests {
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
// AcceptInlineCompletion when there is an active suggestion inserts it.
editor.accept_inline_completion(&Default::default(), window, cx);
// AcceptEditPrediction when there is an active suggestion inserts it.
editor.accept_edit_prediction(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
@@ -486,7 +483,7 @@ mod tests {
);
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
editor.next_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -500,8 +497,8 @@ mod tests {
assert_eq!(editor.text(cx), "fn foo() {\n \n}");
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
// Using AcceptInlineCompletion again accepts the suggestion.
editor.accept_inline_completion(&Default::default(), window, cx);
// Using AcceptEditPrediction again accepts the suggestion.
editor.accept_edit_prediction(&Default::default(), window, cx);
assert!(!editor.has_active_inline_completion());
assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
@@ -530,7 +527,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
// Setup the editor with a completion request.
@@ -654,7 +651,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@@ -673,7 +670,7 @@ mod tests {
vec![],
);
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
editor.next_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -744,7 +741,7 @@ mod tests {
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
editor
.update(cx, |editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
})
.unwrap();
@@ -762,7 +759,7 @@ mod tests {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
});
editor.next_inline_completion(&Default::default(), window, cx);
editor.next_edit_prediction(&Default::default(), window, cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
_ = editor.update(cx, |editor, _, cx| {
@@ -838,7 +835,7 @@ mod tests {
.await;
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
});
cx.set_state(indoc! {"
@@ -866,7 +863,7 @@ mod tests {
vec![],
);
cx.update_editor(|editor, window, cx| {
editor.next_inline_completion(&Default::default(), window, cx)
editor.next_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, _, cx| {
@@ -934,7 +931,7 @@ mod tests {
async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings
.inline_completions
.edit_predictions
.get_or_insert(Default::default())
.disabled_globs = Some(vec![".env*".to_string()]);
});
@@ -996,7 +993,7 @@ mod tests {
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
editor
.update(cx, |editor, window, cx| {
editor.set_inline_completion_provider(Some(copilot_provider), window, cx)
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
})
.unwrap();

View File

@@ -18,7 +18,7 @@ use std::{
path::{Path, PathBuf},
};
use unindent::Unindent as _;
use util::{post_inc, RandomCharIter};
use util::{path, post_inc, RandomCharIter};
#[ctor::ctor]
fn init_logger() {
@@ -33,7 +33,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/test",
path!("/test"),
json!({
"consts.rs": "
const a: i32 = 'a';
@@ -59,7 +59,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
.await;
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*window, cx);
@@ -70,7 +70,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
language_server_id,
PathBuf::from("/test/main.rs"),
PathBuf::from(path!("/test/main.rs")),
None,
vec![
DiagnosticEntry {
@@ -234,7 +234,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
language_server_id,
PathBuf::from("/test/consts.rs"),
PathBuf::from(path!("/test/consts.rs")),
None,
vec![DiagnosticEntry {
range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)),
@@ -341,7 +341,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
language_server_id,
PathBuf::from("/test/consts.rs"),
PathBuf::from(path!("/test/consts.rs")),
None,
vec![
DiagnosticEntry {
@@ -464,7 +464,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/test",
path!("/test"),
json!({
"main.js": "
a();
@@ -479,7 +479,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
let server_id_1 = LanguageServerId(100);
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*window, cx);
@@ -504,7 +504,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
server_id_1,
PathBuf::from("/test/main.js"),
PathBuf::from(path!("/test/main.js")),
None,
vec![DiagnosticEntry {
range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)),
@@ -557,7 +557,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
server_id_2,
PathBuf::from("/test/main.js"),
PathBuf::from(path!("/test/main.js")),
None,
vec![DiagnosticEntry {
range: Unclipped(PointUtf16::new(1, 0))..Unclipped(PointUtf16::new(1, 1)),
@@ -619,7 +619,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
server_id_1,
PathBuf::from("/test/main.js"),
PathBuf::from(path!("/test/main.js")),
None,
vec![DiagnosticEntry {
range: Unclipped(PointUtf16::new(2, 0))..Unclipped(PointUtf16::new(2, 1)),
@@ -638,7 +638,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
server_id_2,
PathBuf::from("/test/main.rs"),
PathBuf::from(path!("/test/main.rs")),
None,
vec![],
cx,
@@ -689,7 +689,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
lsp_store
.update_diagnostic_entries(
server_id_2,
PathBuf::from("/test/main.js"),
PathBuf::from(path!("/test/main.js")),
None,
vec![DiagnosticEntry {
range: Unclipped(PointUtf16::new(3, 0))..Unclipped(PointUtf16::new(3, 1)),
@@ -755,9 +755,9 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
.unwrap_or(10);
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/test", json!({})).await;
fs.insert_tree(path!("/test"), json!({})).await;
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*window, cx);
@@ -817,7 +817,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
// insert a set of diagnostics for a new path
_ => {
let path: PathBuf =
format!("/test/{}.rs", post_inc(&mut next_filename)).into();
format!(path!("/test/{}.rs"), post_inc(&mut next_filename)).into();
let len = rng.gen_range(128..256);
let content =
RandomCharIter::new(&mut rng).take(len).collect::<String>();
@@ -891,7 +891,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
for diagnostic in diagnostics {
let found_excerpt = reference_excerpts.iter().any(|info| {
let row_range = info.range.context.start.row..info.range.context.end.row;
info.path == path.strip_prefix("/test").unwrap()
info.path == path.strip_prefix(path!("/test")).unwrap()
&& info.language_server == language_server_id
&& row_range.contains(&diagnostic.range.start.0.row)
});

View File

@@ -1,897 +0,0 @@
use futures::{channel::oneshot, future::OptionFuture};
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
use gpui::{App, Context, Entity, EventEmitter};
use language::{Language, LanguageRegistry};
use rope::Rope;
use std::{cmp, future::Future, iter, ops::Range, sync::Arc};
use sum_tree::SumTree;
use text::{Anchor, BufferId, OffsetRangeExt, Point};
use util::ResultExt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DiffHunkStatus {
Added,
Modified,
Removed,
}
/// A diff hunk resolved to rows in the buffer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffHunk {
/// The buffer range, expressed in terms of rows.
pub row_range: Range<u32>,
/// The range in the buffer to which this hunk corresponds.
pub buffer_range: Range<Anchor>,
/// The range in the buffer's diff base text to which this hunk corresponds.
pub diff_base_byte_range: Range<usize>,
}
/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
#[derive(Debug, Clone, PartialEq, Eq)]
struct InternalDiffHunk {
buffer_range: Range<Anchor>,
diff_base_byte_range: Range<usize>,
}
impl sum_tree::Item for InternalDiffHunk {
type Summary = DiffHunkSummary;
fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
DiffHunkSummary {
buffer_range: self.buffer_range.clone(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct DiffHunkSummary {
buffer_range: Range<Anchor>,
}
impl sum_tree::Summary for DiffHunkSummary {
type Context = text::BufferSnapshot;
fn zero(_cx: &Self::Context) -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
self.buffer_range.start = self
.buffer_range
.start
.min(&other.buffer_range.start, buffer);
self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
}
}
#[derive(Clone)]
pub struct BufferDiffSnapshot {
hunks: SumTree<InternalDiffHunk>,
pub base_text: Option<language::BufferSnapshot>,
}
impl std::fmt::Debug for BufferDiffSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BufferDiffSnapshot")
.field("hunks", &self.hunks)
.finish()
}
}
impl BufferDiffSnapshot {
pub fn new(buffer: &text::BufferSnapshot) -> BufferDiffSnapshot {
BufferDiffSnapshot {
hunks: SumTree::new(buffer),
base_text: None,
}
}
pub fn new_with_single_insertion(cx: &mut App) -> Self {
let base_text = language::Buffer::build_empty_snapshot(cx);
Self {
hunks: SumTree::from_item(
InternalDiffHunk {
buffer_range: Anchor::MIN..Anchor::MAX,
diff_base_byte_range: 0..0,
},
&base_text,
),
base_text: Some(base_text),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn build_sync(
buffer: text::BufferSnapshot,
diff_base: String,
cx: &mut gpui::TestAppContext,
) -> Self {
let snapshot =
cx.update(|cx| Self::build(buffer, Some(Arc::new(diff_base)), None, None, cx));
cx.executor().block(snapshot)
}
pub fn build(
buffer: text::BufferSnapshot,
diff_base: Option<Arc<String>>,
language: Option<Arc<Language>>,
language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut App,
) -> impl Future<Output = Self> {
let base_text_snapshot = diff_base.as_ref().map(|base_text| {
language::Buffer::build_snapshot(
Rope::from(base_text.as_str()),
language.clone(),
language_registry.clone(),
cx,
)
});
let base_text_snapshot = cx
.background_executor()
.spawn(OptionFuture::from(base_text_snapshot));
let hunks = cx.background_executor().spawn({
let buffer = buffer.clone();
async move { Self::recalculate_hunks(diff_base, buffer) }
});
async move {
let (base_text, hunks) = futures::join!(base_text_snapshot, hunks);
Self { base_text, hunks }
}
}
pub fn build_with_base_buffer(
buffer: text::BufferSnapshot,
diff_base: Option<Arc<String>>,
diff_base_buffer: Option<language::BufferSnapshot>,
cx: &App,
) -> impl Future<Output = Self> {
cx.background_executor().spawn({
let buffer = buffer.clone();
async move {
let hunks = Self::recalculate_hunks(diff_base, buffer);
Self {
hunks,
base_text: diff_base_buffer,
}
}
})
}
fn recalculate_hunks(
diff_base: Option<Arc<String>>,
buffer: text::BufferSnapshot,
) -> SumTree<InternalDiffHunk> {
let mut tree = SumTree::new(&buffer);
if let Some(diff_base) = diff_base {
let buffer_text = buffer.as_rope().to_string();
let patch = Self::diff(&diff_base, &buffer_text);
// A common case in Zed is that the empty buffer is represented as just a newline,
// but if we just compute a naive diff you get a "preserved" line in the middle,
// which is a bit odd.
if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
tree.push(
InternalDiffHunk {
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
diff_base_byte_range: 0..diff_base.len() - 1,
},
&buffer,
);
return tree;
}
if let Some(patch) = patch {
let mut divergence = 0;
for hunk_index in 0..patch.num_hunks() {
let hunk =
Self::process_patch_hunk(&patch, hunk_index, &buffer, &mut divergence);
tree.push(hunk, &buffer);
}
}
}
tree
}
pub fn is_empty(&self) -> bool {
self.hunks.is_empty()
}
pub fn hunks_in_row_range<'a>(
&'a self,
range: Range<u32>,
buffer: &'a text::BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk> {
let start = buffer.anchor_before(Point::new(range.start, 0));
let end = buffer.anchor_after(Point::new(range.end, 0));
self.hunks_intersecting_range(start..end, buffer)
}
pub fn hunks_intersecting_range<'a>(
&'a self,
range: Range<Anchor>,
buffer: &'a text::BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk> {
let range = range.to_offset(buffer);
let mut cursor = self
.hunks
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
let summary_range = summary.buffer_range.to_offset(buffer);
let before_start = summary_range.end < range.start;
let after_end = summary_range.start > range.end;
!before_start && !after_end
});
let anchor_iter = iter::from_fn(move || {
cursor.next(buffer);
cursor.item()
})
.flat_map(move |hunk| {
[
(
&hunk.buffer_range.start,
(hunk.buffer_range.start, hunk.diff_base_byte_range.start),
),
(
&hunk.buffer_range.end,
(hunk.buffer_range.end, hunk.diff_base_byte_range.end),
),
]
});
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
iter::from_fn(move || loop {
let (start_point, (start_anchor, start_base)) = summaries.next()?;
let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;
if !start_anchor.is_valid(buffer) {
continue;
}
if end_point.column > 0 {
end_point.row += 1;
end_point.column = 0;
end_anchor = buffer.anchor_before(end_point);
}
return Some(DiffHunk {
row_range: start_point.row..end_point.row,
diff_base_byte_range: start_base..end_base,
buffer_range: start_anchor..end_anchor,
});
})
}
pub fn hunks_intersecting_range_rev<'a>(
&'a self,
range: Range<Anchor>,
buffer: &'a text::BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk> {
let mut cursor = self
.hunks
.filter::<_, DiffHunkSummary>(buffer, move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
iter::from_fn(move || {
cursor.prev(buffer);
let hunk = cursor.item()?;
let range = hunk.buffer_range.to_point(buffer);
let end_row = if range.end.column > 0 {
range.end.row + 1
} else {
range.end.row
};
Some(DiffHunk {
row_range: range.start.row..end_row,
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
buffer_range: hunk.buffer_range.clone(),
})
})
}
pub fn compare(
&self,
old: &Self,
new_snapshot: &text::BufferSnapshot,
) -> Option<Range<Anchor>> {
let mut new_cursor = self.hunks.cursor::<()>(new_snapshot);
let mut old_cursor = old.hunks.cursor::<()>(new_snapshot);
old_cursor.next(new_snapshot);
new_cursor.next(new_snapshot);
let mut start = None;
let mut end = None;
loop {
match (new_cursor.item(), old_cursor.item()) {
(Some(new_hunk), Some(old_hunk)) => {
match new_hunk
.buffer_range
.start
.cmp(&old_hunk.buffer_range.start, new_snapshot)
{
cmp::Ordering::Less => {
start.get_or_insert(new_hunk.buffer_range.start);
end.replace(new_hunk.buffer_range.end);
new_cursor.next(new_snapshot);
}
cmp::Ordering::Equal => {
if new_hunk != old_hunk {
start.get_or_insert(new_hunk.buffer_range.start);
if old_hunk
.buffer_range
.end
.cmp(&new_hunk.buffer_range.end, new_snapshot)
.is_ge()
{
end.replace(old_hunk.buffer_range.end);
} else {
end.replace(new_hunk.buffer_range.end);
}
}
new_cursor.next(new_snapshot);
old_cursor.next(new_snapshot);
}
cmp::Ordering::Greater => {
start.get_or_insert(old_hunk.buffer_range.start);
end.replace(old_hunk.buffer_range.end);
old_cursor.next(new_snapshot);
}
}
}
(Some(new_hunk), None) => {
start.get_or_insert(new_hunk.buffer_range.start);
end.replace(new_hunk.buffer_range.end);
new_cursor.next(new_snapshot);
}
(None, Some(old_hunk)) => {
start.get_or_insert(old_hunk.buffer_range.start);
end.replace(old_hunk.buffer_range.end);
old_cursor.next(new_snapshot);
}
(None, None) => break,
}
}
start.zip(end).map(|(start, end)| start..end)
}
#[cfg(test)]
fn clear(&mut self, buffer: &text::BufferSnapshot) {
self.hunks = SumTree::new(buffer);
}
#[cfg(test)]
fn hunks<'a>(&'a self, text: &'a text::BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk> {
let start = text.anchor_before(Point::new(0, 0));
let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
self.hunks_intersecting_range(start..end, text)
}
fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
let mut options = GitOptions::default();
options.context_lines(0);
let patch = GitPatch::from_buffers(
head.as_bytes(),
None,
current.as_bytes(),
None,
Some(&mut options),
);
match patch {
Ok(patch) => Some(patch),
Err(err) => {
log::error!("`GitPatch::from_buffers` failed: {}", err);
None
}
}
}
fn process_patch_hunk(
patch: &GitPatch<'_>,
hunk_index: usize,
buffer: &text::BufferSnapshot,
buffer_row_divergence: &mut i64,
) -> InternalDiffHunk {
let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
assert!(line_item_count > 0);
let mut first_deletion_buffer_row: Option<u32> = None;
let mut buffer_row_range: Option<Range<u32>> = None;
let mut diff_base_byte_range: Option<Range<usize>> = None;
for line_index in 0..line_item_count {
let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
let kind = line.origin_value();
let content_offset = line.content_offset() as isize;
let content_len = line.content().len() as isize;
if kind == GitDiffLineType::Addition {
*buffer_row_divergence += 1;
let row = line.new_lineno().unwrap().saturating_sub(1);
match &mut buffer_row_range {
Some(buffer_row_range) => buffer_row_range.end = row + 1,
None => buffer_row_range = Some(row..row + 1),
}
}
if kind == GitDiffLineType::Deletion {
let end = content_offset + content_len;
match &mut diff_base_byte_range {
Some(head_byte_range) => head_byte_range.end = end as usize,
None => diff_base_byte_range = Some(content_offset as usize..end as usize),
}
if first_deletion_buffer_row.is_none() {
let old_row = line.old_lineno().unwrap().saturating_sub(1);
let row = old_row as i64 + *buffer_row_divergence;
first_deletion_buffer_row = Some(row as u32);
}
*buffer_row_divergence -= 1;
}
}
//unwrap_or deletion without addition
let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
//we cannot have an addition-less hunk without deletion(s) or else there would be no hunk
let row = first_deletion_buffer_row.unwrap();
row..row
});
//unwrap_or addition without deletion
let diff_base_byte_range = diff_base_byte_range.unwrap_or(0..0);
let start = Point::new(buffer_row_range.start, 0);
let end = Point::new(buffer_row_range.end, 0);
let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
InternalDiffHunk {
buffer_range,
diff_base_byte_range,
}
}
}
pub struct BufferDiff {
pub buffer_id: BufferId,
pub snapshot: BufferDiffSnapshot,
pub unstaged_diff: Option<Entity<BufferDiff>>,
}
impl std::fmt::Debug for BufferDiff {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BufferChangeSet")
.field("buffer_id", &self.buffer_id)
.field("snapshot", &self.snapshot)
.finish()
}
}
pub enum BufferDiffEvent {
DiffChanged { changed_range: Range<text::Anchor> },
LanguageChanged,
}
impl EventEmitter<BufferDiffEvent> for BufferDiff {}
impl BufferDiff {
pub fn set_state(
&mut self,
snapshot: BufferDiffSnapshot,
buffer: &text::BufferSnapshot,
cx: &mut Context<Self>,
) {
if let Some(base_text) = snapshot.base_text.as_ref() {
let changed_range = if Some(base_text.remote_id())
!= self
.snapshot
.base_text
.as_ref()
.map(|buffer| buffer.remote_id())
{
Some(text::Anchor::MIN..text::Anchor::MAX)
} else {
snapshot.compare(&self.snapshot, buffer)
};
if let Some(changed_range) = changed_range {
cx.emit(BufferDiffEvent::DiffChanged { changed_range });
}
}
self.snapshot = snapshot;
}
pub fn diff_hunks_intersecting_range<'a>(
&'a self,
range: Range<text::Anchor>,
buffer_snapshot: &'a text::BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk> {
self.snapshot
.hunks_intersecting_range(range, buffer_snapshot)
}
pub fn diff_hunks_intersecting_range_rev<'a>(
&'a self,
range: Range<text::Anchor>,
buffer_snapshot: &'a text::BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk> {
self.snapshot
.hunks_intersecting_range_rev(range, buffer_snapshot)
}
/// Used in cases where the change set isn't derived from git.
pub fn set_base_text(
&mut self,
base_buffer: Entity<language::Buffer>,
buffer: text::BufferSnapshot,
cx: &mut Context<Self>,
) -> oneshot::Receiver<()> {
let (tx, rx) = oneshot::channel();
let this = cx.weak_entity();
let base_buffer = base_buffer.read(cx);
let language_registry = base_buffer.language_registry();
let base_buffer = base_buffer.snapshot();
let base_text = Arc::new(base_buffer.text());
let snapshot = BufferDiffSnapshot::build(
buffer.clone(),
Some(base_text),
base_buffer.language().cloned(),
language_registry,
cx,
);
let complete_on_drop = util::defer(|| {
tx.send(()).ok();
});
cx.spawn(|_, mut cx| async move {
let snapshot = snapshot.await;
let Some(this) = this.upgrade() else {
return;
};
this.update(&mut cx, |this, cx| {
this.set_state(snapshot, &buffer, cx);
})
.log_err();
drop(complete_on_drop)
})
.detach();
rx
}
#[cfg(any(test, feature = "test-support"))]
pub fn base_text_string(&self) -> Option<String> {
self.snapshot.base_text.as_ref().map(|buffer| buffer.text())
}
pub fn new(buffer: &Entity<language::Buffer>, cx: &mut App) -> Self {
BufferDiff {
buffer_id: buffer.read(cx).remote_id(),
snapshot: BufferDiffSnapshot::new(&buffer.read(cx)),
unstaged_diff: None,
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn new_with_base_text(
base_text: &str,
buffer: &Entity<language::Buffer>,
cx: &mut App,
) -> Self {
let mut base_text = base_text.to_owned();
text::LineEnding::normalize(&mut base_text);
let snapshot = BufferDiffSnapshot::build(
buffer.read(cx).text_snapshot(),
Some(base_text.into()),
None,
None,
cx,
);
let snapshot = cx.background_executor().block(snapshot);
BufferDiff {
buffer_id: buffer.read(cx).remote_id(),
snapshot,
unstaged_diff: None,
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
let base_text = self
.snapshot
.base_text
.as_ref()
.map(|base_text| base_text.text());
let snapshot = BufferDiffSnapshot::build_with_base_buffer(
buffer.clone(),
base_text.clone().map(Arc::new),
self.snapshot.base_text.clone(),
cx,
);
let snapshot = cx.background_executor().block(snapshot);
self.set_state(snapshot, &buffer, cx);
}
}
/// Range (crossing new lines), old, new
#[cfg(any(test, feature = "test-support"))]
#[track_caller]
pub fn assert_hunks<Iter>(
diff_hunks: Iter,
buffer: &text::BufferSnapshot,
diff_base: &str,
expected_hunks: &[(Range<u32>, &str, &str)],
) where
Iter: Iterator<Item = DiffHunk>,
{
let actual_hunks = diff_hunks
.map(|hunk| {
(
hunk.row_range.clone(),
&diff_base[hunk.diff_base_byte_range],
buffer
.text_for_range(
Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
)
.collect::<String>(),
)
})
.collect::<Vec<_>>();
let expected_hunks: Vec<_> = expected_hunks
.iter()
.map(|(r, s, h)| (r.clone(), *s, h.to_string()))
.collect();
assert_eq!(actual_hunks, expected_hunks);
}
#[cfg(test)]
mod tests {
use std::assert_eq;
use super::*;
use gpui::TestAppContext;
use text::{Buffer, BufferId};
use unindent::Unindent as _;
#[gpui::test]
async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
let diff_base = "
one
two
three
"
.unindent();
let buffer_text = "
one
HELLO
three
"
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiffSnapshot::build_sync(buffer.clone(), diff_base.clone(), cx);
assert_hunks(
diff.hunks(&buffer),
&buffer,
&diff_base,
&[(1..2, "two\n", "HELLO\n")],
);
buffer.edit([(0..0, "point five\n")]);
diff = BufferDiffSnapshot::build_sync(buffer.clone(), diff_base.clone(), cx);
assert_hunks(
diff.hunks(&buffer),
&buffer,
&diff_base,
&[(0..1, "", "point five\n"), (2..3, "two\n", "HELLO\n")],
);
diff.clear(&buffer);
assert_hunks(diff.hunks(&buffer), &buffer, &diff_base, &[]);
}
#[gpui::test]
async fn test_buffer_diff_range(cx: &mut TestAppContext) {
let diff_base = Arc::new(
"
one
two
three
four
five
six
seven
eight
nine
ten
"
.unindent(),
);
let buffer_text = "
A
one
B
two
C
three
HELLO
four
five
SIXTEEN
seven
eight
WORLD
nine
ten
"
.unindent();
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let diff = cx
.update(|cx| {
BufferDiffSnapshot::build(
buffer.snapshot(),
Some(diff_base.clone()),
None,
None,
cx,
)
})
.await;
assert_eq!(diff.hunks(&buffer).count(), 8);
assert_hunks(
diff.hunks_in_row_range(7..12, &buffer),
&buffer,
&diff_base,
&[
(6..7, "", "HELLO\n"),
(9..10, "six\n", "SIXTEEN\n"),
(12..13, "", "WORLD\n"),
],
);
}
#[gpui::test]
async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
let base_text = "
zero
one
two
three
four
five
six
seven
eight
nine
"
.unindent();
let buffer_text_1 = "
one
three
four
five
SIX
seven
eight
NINE
"
.unindent();
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
let empty_diff = BufferDiffSnapshot::new(&buffer);
let diff_1 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
let range = diff_1.compare(&empty_diff, &buffer).unwrap();
assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
// Edit does not affect the diff.
buffer.edit_via_marked_text(
&"
one
three
four
five
«SIX.5»
seven
eight
NINE
"
.unindent(),
);
let diff_2 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
assert_eq!(None, diff_2.compare(&diff_1, &buffer));
// Edit turns a deletion hunk into a modification.
buffer.edit_via_marked_text(
&"
one
«THREE»
four
five
SIX.5
seven
eight
NINE
"
.unindent(),
);
let diff_3 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
let range = diff_3.compare(&diff_2, &buffer).unwrap();
assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
// Edit turns a modification hunk into a deletion.
buffer.edit_via_marked_text(
&"
one
THREE
four
five«»
seven
eight
NINE
"
.unindent(),
);
let diff_4 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx);
let range = diff_4.compare(&diff_3, &buffer).unwrap();
assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
// Edit introduces a new insertion hunk.
buffer.edit_via_marked_text(
&"
one
THREE
four«
FOUR.5
»five
seven
eight
NINE
"
.unindent(),
);
let diff_5 = BufferDiffSnapshot::build_sync(buffer.snapshot(), base_text.clone(), cx);
let range = diff_5.compare(&diff_4, &buffer).unwrap();
assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
// Edit removes a hunk.
buffer.edit_via_marked_text(
&"
one
THREE
four
FOUR.5
five
seven
eight
«nine»
"
.unindent(),
);
let diff_6 = BufferDiffSnapshot::build_sync(buffer.snapshot(), base_text, cx);
let range = diff_6.compare(&diff_5, &buffer).unwrap();
assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
}
}

View File

@@ -29,8 +29,14 @@ impl Template for KeybindingTemplate {
fn render(&self, context: &PreprocessorContext, args: &HashMap<String, String>) -> String {
let action = args.get("action").map(String::as_str).unwrap_or("");
let macos_binding = context.find_binding("macos", action).unwrap_or_default();
let linux_binding = context.find_binding("linux", action).unwrap_or_default();
let macos_binding = context
.find_binding("macos", action)
.unwrap_or_default()
.replace("\\", "&#92;");
let linux_binding = context
.find_binding("linux", action)
.unwrap_or_default()
.replace("\\", "&#92;");
format!("<kbd class=\"keybinding\">{macos_binding}|{linux_binding}</kbd>")
}
}

View File

@@ -38,7 +38,7 @@ clock.workspace = true
collections.workspace = true
convert_case.workspace = true
db.workspace = true
diff.workspace = true
buffer_diff.workspace = true
emojis.workspace = true
file_icons.workspace = true
futures.workspace = true
@@ -49,6 +49,7 @@ gpui.workspace = true
http_client.workspace = true
indoc.workspace = true
inline_completion.workspace = true
inventory.workspace = true
itertools.workspace = true
language.workspace = true
linkify.workspace = true

View File

@@ -3,56 +3,64 @@ use super::*;
use gpui::{action_as, action_with_deprecated_aliases};
use schemars::JsonSchema;
use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectNext {
#[serde(default)]
pub replace_newest: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectPrevious {
#[serde(default)]
pub replace_newest: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveToBeginningOfLine {
#[serde(default = "default_true")]
pub stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectToBeginningOfLine {
#[serde(default)]
pub(super) stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MovePageUp {
#[serde(default)]
pub(super) center_cursor: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MovePageDown {
#[serde(default)]
pub(super) center_cursor: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveToEndOfLine {
#[serde(default = "default_true")]
pub stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectToEndOfLine {
#[serde(default)]
pub(super) stop_at_soft_wraps: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ToggleCodeActions {
// Display row from which the action was deployed.
#[serde(default)]
@@ -61,24 +69,28 @@ pub struct ToggleCodeActions {
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ConfirmCompletion {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ComposeCompletion {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ConfirmCodeAction {
#[serde(default)]
pub item_ix: Option<usize>,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ToggleComments {
#[serde(default)]
pub advance_downwards: bool,
@@ -87,60 +99,70 @@ pub struct ToggleComments {
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct FoldAt {
#[serde(skip)]
pub buffer_row: MultiBufferRow,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct UnfoldAt {
#[serde(skip)]
pub buffer_row: MultiBufferRow,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveUpByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MoveDownByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectUpByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SelectDownByLines {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExpandExcerpts {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExpandExcerptsUp {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExpandExcerptsDown {
#[serde(default)]
pub(super) lines: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ShowCompletions {
#[serde(default)]
pub(super) trigger: Option<String>,
@@ -150,23 +172,24 @@ pub struct ShowCompletions {
pub struct HandleInput(pub String);
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeleteToNextWordEnd {
#[serde(default)]
pub ignore_newlines: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DeleteToPreviousWordStart {
#[serde(default)]
pub ignore_newlines: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct FoldAtLevel {
pub level: u32,
}
pub struct FoldAtLevel(pub u32);
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SpawnNearestTask {
#[serde(default)]
pub reveal: task::RevealStrategy,
@@ -216,9 +239,9 @@ impl_actions!(
gpui::actions!(
editor,
[
AcceptInlineCompletion,
AcceptEditPrediction,
AcceptPartialCopilotSuggestion,
AcceptPartialInlineCompletion,
AcceptPartialEditPrediction,
AddSelectionAbove,
AddSelectionBelow,
ApplyAllDiffHunks,
@@ -242,6 +265,8 @@ gpui::actions!(
Copy,
CopyFileLocation,
CopyHighlightJson,
CopyFileName,
CopyFileNameWithoutExtension,
CopyPath,
CopyPermalinkToLine,
CopyRelativePath,
@@ -310,7 +335,7 @@ gpui::actions!(
Newline,
NewlineAbove,
NewlineBelow,
NextInlineCompletion,
NextEditPrediction,
NextScreen,
OpenContextMenu,
OpenExcerpts,
@@ -325,7 +350,7 @@ gpui::actions!(
PageDown,
PageUp,
Paste,
PreviousInlineCompletion,
PreviousEditPrediction,
Redo,
RedoSelection,
Rename,
@@ -361,7 +386,7 @@ gpui::actions!(
SelectToStartOfParagraph,
SelectUp,
ShowCharacterPalette,
ShowInlineCompletion,
ShowEditPrediction,
ShowSignatureHelp,
ShuffleLines,
SortLinesCaseInsensitive,
@@ -375,8 +400,9 @@ gpui::actions!(
ToggleGitBlameInline,
ToggleIndentGuides,
ToggleInlayHints,
ToggleInlineCompletions,
ToggleEditPrediction,
ToggleLineNumbers,
ToggleStagedSelectedDiffHunks,
SwapSelectionEnds,
SetMark,
ToggleRelativeLineNumbers,

View File

@@ -517,7 +517,6 @@ impl CompletionsMenu {
} else {
None
};
let color_swatch = completion
.color()
.map(|color| div().size_4().bg(color).rounded_sm());

View File

@@ -1,28 +1,48 @@
use futures::Future;
use git::blame::BlameEntry;
use git::Oid;
use git::PullRequest;
use gpui::{
App, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle,
StatefulInteractiveElement, WeakEntity,
};
use language::ParsedMarkdown;
use settings::Settings;
use std::hash::Hash;
use theme::ThemeSettings;
use time::UtcOffset;
use time::{OffsetDateTime, UtcOffset};
use time_format::format_local_timestamp;
use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape};
use url::Url;
use workspace::Workspace;
use crate::git::blame::{CommitDetails, GitRemote};
use crate::git::blame::GitRemote;
use crate::EditorStyle;
#[derive(Clone, Debug)]
pub struct CommitDetails {
pub sha: SharedString,
pub committer_name: SharedString,
pub committer_email: SharedString,
pub commit_time: OffsetDateTime,
pub message: Option<ParsedCommitMessage>,
}
#[derive(Clone, Debug, Default)]
pub struct ParsedCommitMessage {
pub message: SharedString,
pub parsed_message: ParsedMarkdown,
pub permalink: Option<Url>,
pub pull_request: Option<PullRequest>,
pub remote: Option<GitRemote>,
}
struct CommitAvatar<'a> {
details: Option<&'a CommitDetails>,
sha: Oid,
commit: &'a CommitDetails,
}
impl<'a> CommitAvatar<'a> {
fn new(details: Option<&'a CommitDetails>, sha: Oid) -> Self {
Self { details, sha }
fn new(details: &'a CommitDetails) -> Self {
Self { commit: details }
}
}
@@ -30,14 +50,16 @@ impl<'a> CommitAvatar<'a> {
fn render(
&'a self,
window: &mut Window,
cx: &mut Context<BlameEntryTooltip>,
cx: &mut Context<CommitTooltip>,
) -> Option<impl IntoElement> {
let remote = self
.details
.commit
.message
.as_ref()
.and_then(|details| details.remote.as_ref())
.filter(|remote| remote.host_supports_avatars())?;
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.commit.sha.clone());
let element = match window.use_asset::<CommitAvatarAsset>(&avatar_url, cx) {
// Loading or no avatar found
@@ -54,7 +76,7 @@ impl<'a> CommitAvatar<'a> {
#[derive(Clone, Debug)]
struct CommitAvatarAsset {
sha: Oid,
sha: SharedString,
remote: GitRemote,
}
@@ -66,7 +88,7 @@ impl Hash for CommitAvatarAsset {
}
impl CommitAvatarAsset {
fn new(remote: GitRemote, sha: Oid) -> Self {
fn new(remote: GitRemote, sha: SharedString) -> Self {
Self { remote, sha }
}
}
@@ -91,50 +113,78 @@ impl Asset for CommitAvatarAsset {
}
}
pub(crate) struct BlameEntryTooltip {
blame_entry: BlameEntry,
details: Option<CommitDetails>,
pub struct CommitTooltip {
commit: CommitDetails,
editor_style: EditorStyle,
workspace: Option<WeakEntity<Workspace>>,
scroll_handle: ScrollHandle,
}
impl BlameEntryTooltip {
pub(crate) fn new(
blame_entry: BlameEntry,
details: Option<CommitDetails>,
style: &EditorStyle,
impl CommitTooltip {
pub fn blame_entry(
blame: BlameEntry,
details: Option<ParsedCommitMessage>,
style: EditorStyle,
workspace: Option<WeakEntity<Workspace>>,
) -> Self {
let commit_time = blame
.committer_time
.and_then(|t| OffsetDateTime::from_unix_timestamp(t).ok())
.unwrap_or(OffsetDateTime::now_utc());
Self::new(
CommitDetails {
sha: blame.sha.to_string().into(),
commit_time,
committer_name: blame
.committer_name
.unwrap_or("<no name>".to_string())
.into(),
committer_email: blame.committer_email.unwrap_or("".to_string()).into(),
message: details,
},
style,
workspace,
)
}
pub fn new(
commit: CommitDetails,
editor_style: EditorStyle,
workspace: Option<WeakEntity<Workspace>>,
) -> Self {
Self {
editor_style: style.clone(),
blame_entry,
details,
editor_style,
commit,
workspace,
scroll_handle: ScrollHandle::new(),
}
}
}
impl Render for BlameEntryTooltip {
impl Render for CommitTooltip {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let avatar =
CommitAvatar::new(self.details.as_ref(), self.blame_entry.sha).render(window, cx);
let avatar = CommitAvatar::new(&self.commit).render(window, cx);
let author = self
.blame_entry
.author
.clone()
.unwrap_or("<no name>".to_string());
let author = self.commit.committer_name.clone();
let author_email = self.blame_entry.author_mail.clone();
let author_email = self.commit.committer_email.clone();
let short_commit_id = self.blame_entry.sha.display_short();
let full_sha = self.blame_entry.sha.to_string().clone();
let absolute_timestamp = blame_entry_absolute_timestamp(&self.blame_entry);
let short_commit_id = self
.commit
.sha
.get(0..8)
.map(|sha| sha.to_string().into())
.unwrap_or_else(|| self.commit.sha.clone());
let full_sha = self.commit.sha.to_string().clone();
let absolute_timestamp = format_local_timestamp(
self.commit.commit_time,
OffsetDateTime::now_utc(),
time_format::TimestampFormat::MediumAbsolute,
);
let message = self
.details
.commit
.message
.as_ref()
.map(|details| {
crate::render_parsed_markdown(
@@ -149,7 +199,8 @@ impl Render for BlameEntryTooltip {
.unwrap_or("<no commit message>".into_any());
let pull_request = self
.details
.commit
.message
.as_ref()
.and_then(|details| details.pull_request.clone());
@@ -171,7 +222,7 @@ impl Render for BlameEntryTooltip {
.flex_wrap()
.children(avatar)
.child(author)
.when_some(author_email, |this, author_email| {
.when(!author_email.is_empty(), |this| {
this.child(
div()
.text_color(cx.theme().colors().text_muted)
@@ -231,12 +282,16 @@ impl Render for BlameEntryTooltip {
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.disabled(
self.details.as_ref().map_or(true, |details| {
details.permalink.is_none()
}),
self.commit
.message
.as_ref()
.map_or(true, |details| {
details.permalink.is_none()
}),
)
.when_some(
self.details
self.commit
.message
.as_ref()
.and_then(|details| details.permalink.clone()),
|this, url| {
@@ -284,7 +339,3 @@ fn blame_entry_timestamp(blame_entry: &BlameEntry, format: time_format::Timestam
pub fn blame_entry_relative_timestamp(blame_entry: &BlameEntry) -> String {
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::Relative)
}
fn blame_entry_absolute_timestamp(blame_entry: &BlameEntry) -> String {
blame_entry_timestamp(blame_entry, time_format::TimestampFormat::MediumAbsolute)
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,6 @@ pub struct EditorSettings {
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
pub jupyter: Jupyter,
pub show_inline_completions_in_menu: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -368,12 +367,6 @@ pub struct EditorSettingsContent {
/// Default: false
pub show_signature_help_after_edits: Option<bool>,
/// Whether to show the edit predictions next to the completions provided by a language server.
/// Only has an effect if edit prediction provider supports it.
///
/// Default: true
pub show_inline_completions_in_menu: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<JupyterContent>,
}

View File

@@ -7,7 +7,7 @@ use crate::{
},
JoinLines,
};
use diff::{BufferDiff, DiffHunkStatus};
use buffer_diff::{BufferDiff, DiffHunkStatus};
use futures::StreamExt;
use gpui::{
div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
@@ -1159,7 +1159,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
});
_ = editor.update(cx, |editor, window, cx| {
editor.fold_at_level(&FoldAtLevel { level: 2 }, window, cx);
editor.fold_at_level(&FoldAtLevel(2), window, cx);
assert_eq!(
editor.display_text(cx),
"
@@ -1183,7 +1183,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
.unindent(),
);
editor.fold_at_level(&FoldAtLevel { level: 1 }, window, cx);
editor.fold_at_level(&FoldAtLevel(1), window, cx);
assert_eq!(
editor.display_text(cx),
"
@@ -1198,7 +1198,7 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
);
editor.unfold_all(&UnfoldAll, window, cx);
editor.fold_at_level(&FoldAtLevel { level: 0 }, window, cx);
editor.fold_at_level(&FoldAtLevel(0), window, cx);
assert_eq!(
editor.display_text(cx),
"
@@ -5362,6 +5362,21 @@ async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
}
#[gpui::test]
async fn test_select_previous_empty_buffer(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.set_state("");
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
.unwrap();
cx.assert_editor_state("«aˇ»");
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
.unwrap();
cx.assert_editor_state("«aˇ»");
}
#[gpui::test]
async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -9711,7 +9726,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
&r#"
<!-- ˇ<script> -->
// ˇvar x = new Y();
// ˇ</script>
<!-- ˇ</script> -->
"#
.unindent(),
);
@@ -10653,6 +10668,176 @@ async fn go_to_prev_overlapping_diagnostic(
"});
}
#[gpui::test]
async fn cycle_through_same_place_diagnostics(
executor: BackgroundExecutor,
cx: &mut gpui::TestAppContext,
) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let lsp_store =
cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
cx.set_state(indoc! {"
ˇfn func(abc def: i32) -> u32 {
}
"});
cx.update(|_, cx| {
lsp_store.update(cx, |lsp_store, cx| {
lsp_store
.update_diagnostics(
LanguageServerId(0),
lsp::PublishDiagnosticsParams {
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
version: None,
diagnostics: vec![
lsp::Diagnostic {
range: lsp::Range::new(
lsp::Position::new(0, 11),
lsp::Position::new(0, 12),
),
severity: Some(lsp::DiagnosticSeverity::ERROR),
..Default::default()
},
lsp::Diagnostic {
range: lsp::Range::new(
lsp::Position::new(0, 12),
lsp::Position::new(0, 15),
),
severity: Some(lsp::DiagnosticSeverity::ERROR),
..Default::default()
},
lsp::Diagnostic {
range: lsp::Range::new(
lsp::Position::new(0, 12),
lsp::Position::new(0, 15),
),
severity: Some(lsp::DiagnosticSeverity::ERROR),
..Default::default()
},
lsp::Diagnostic {
range: lsp::Range::new(
lsp::Position::new(0, 25),
lsp::Position::new(0, 28),
),
severity: Some(lsp::DiagnosticSeverity::ERROR),
..Default::default()
},
],
},
&[],
cx,
)
.unwrap()
});
});
executor.run_until_parked();
//// Backward
// Fourth diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc def: i32) -> ˇu32 {
}
"});
// Third diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc ˇdef: i32) -> u32 {
}
"});
// Second diagnostic, same place
cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc ˇdef: i32) -> u32 {
}
"});
// First diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 {
}
"});
// Wrapped over, fourth diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc def: i32) -> ˇu32 {
}
"});
cx.update_editor(|editor, window, cx| {
editor.move_to_beginning(&MoveToBeginning, window, cx);
});
cx.assert_editor_state(indoc! {"
ˇfn func(abc def: i32) -> u32 {
}
"});
//// Forward
// First diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 {
}
"});
// Second diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc ˇdef: i32) -> u32 {
}
"});
// Third diagnostic, same place
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc ˇdef: i32) -> u32 {
}
"});
// Fourth diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abc def: i32) -> ˇu32 {
}
"});
// Wrapped around, first diagnostic
cx.update_editor(|editor, window, cx| {
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
});
cx.assert_editor_state(indoc! {"
fn func(abcˇ def: i32) -> u32 {
}
"});
}
#[gpui::test]
async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -11989,7 +12174,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
struct Row9.2;
struct Row9.3;
struct Row10;"#},
vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
indoc! {r#"struct Row;
struct Row1;
struct Row1.1;
@@ -12027,7 +12212,7 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
struct Row8;
struct Row9;
struct Row10;"#},
vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
vec![DiffHunkStatus::added(), DiffHunkStatus::added()],
indoc! {r#"struct Row;
struct Row1;
struct Row2;
@@ -12074,11 +12259,11 @@ async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
«ˇ// something on bottom»
struct Row10;"#},
vec![
DiffHunkStatus::Added,
DiffHunkStatus::Added,
DiffHunkStatus::Added,
DiffHunkStatus::Added,
DiffHunkStatus::Added,
DiffHunkStatus::added(),
DiffHunkStatus::added(),
DiffHunkStatus::added(),
DiffHunkStatus::added(),
DiffHunkStatus::added(),
],
indoc! {r#"struct Row;
ˇstruct Row1;
@@ -12126,7 +12311,7 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
struct Row99;
struct Row9;
struct Row10;"#},
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
indoc! {r#"struct Row;
struct Row1;
struct Row33;
@@ -12153,7 +12338,7 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
struct Row99;
struct Row9;
struct Row10;"#},
vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
vec![DiffHunkStatus::modified(), DiffHunkStatus::modified()],
indoc! {r#"struct Row;
struct Row1;
struct Row33;
@@ -12182,12 +12367,12 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
struct Row9;
struct Row1011;ˇ"#},
vec![
DiffHunkStatus::Modified,
DiffHunkStatus::Modified,
DiffHunkStatus::Modified,
DiffHunkStatus::Modified,
DiffHunkStatus::Modified,
DiffHunkStatus::Modified,
DiffHunkStatus::modified(),
DiffHunkStatus::modified(),
DiffHunkStatus::modified(),
DiffHunkStatus::modified(),
DiffHunkStatus::modified(),
DiffHunkStatus::modified(),
],
indoc! {r#"struct Row;
ˇstruct Row1;
@@ -12265,7 +12450,7 @@ struct Row10;"#};
ˇ
struct Row8;
struct Row10;"#},
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
indoc! {r#"struct Row;
struct Row2;
@@ -12288,7 +12473,7 @@ struct Row10;"#};
ˇ»
struct Row8;
struct Row10;"#},
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
indoc! {r#"struct Row;
struct Row2;
@@ -12313,7 +12498,7 @@ struct Row10;"#};
struct Row8;ˇ
struct Row10;"#},
vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
vec![DiffHunkStatus::removed(), DiffHunkStatus::removed()],
indoc! {r#"struct Row;
struct Row1;
ˇstruct Row2;
@@ -12338,9 +12523,9 @@ struct Row10;"#};
struct Row8;ˇ»
struct Row10;"#},
vec![
DiffHunkStatus::Removed,
DiffHunkStatus::Removed,
DiffHunkStatus::Removed,
DiffHunkStatus::removed(),
DiffHunkStatus::removed(),
DiffHunkStatus::removed(),
],
indoc! {r#"struct Row;
struct Row1;
@@ -13862,6 +14047,59 @@ async fn test_edit_after_expanded_modification_hunk(
);
}
#[gpui::test]
async fn test_stage_and_unstage_added_file_hunk(
executor: BackgroundExecutor,
cx: &mut gpui::TestAppContext,
) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.update_editor(|editor, _, cx| {
editor.set_expand_all_diff_hunks(cx);
});
let working_copy = r#"
ˇfn main() {
println!("hello, world!");
}
"#
.unindent();
cx.set_state(&working_copy);
executor.run_until_parked();
cx.assert_state_with_diff(
r#"
+ ˇfn main() {
+ println!("hello, world!");
+ }
"#
.unindent(),
);
cx.assert_index_text(None);
cx.update_editor(|editor, window, cx| {
editor.toggle_staged_selected_diff_hunks(&ToggleStagedSelectedDiffHunks, window, cx);
});
executor.run_until_parked();
cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
cx.assert_state_with_diff(
r#"
+ ˇfn main() {
+ println!("hello, world!");
+ }
"#
.unindent(),
);
cx.update_editor(|editor, window, cx| {
editor.toggle_staged_selected_diff_hunks(&ToggleStagedSelectedDiffHunks, window, cx);
});
executor.run_until_parked();
cx.assert_index_text(None);
}
async fn setup_indent_guides_editor(
text: &str,
cx: &mut gpui::TestAppContext,
@@ -14875,7 +15113,7 @@ async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/a",
path!("/a"),
json!({
"first.rs": sample_text_1,
"second.rs": sample_text_2,
@@ -14883,7 +15121,7 @@ async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
}),
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let worktree = project.update(cx, |project, cx| {
@@ -15059,7 +15297,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/a",
path!("/a"),
json!({
"first.rs": sample_text_1,
"second.rs": sample_text_2,
@@ -15067,7 +15305,7 @@ async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext
}),
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let worktree = project.update(cx, |project, cx| {
@@ -15206,13 +15444,13 @@ async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppCon
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/a",
path!("/a"),
json!({
"main.rs": sample_text,
}),
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let worktree = project.update(cx, |project, cx| {

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ use anyhow::Result;
use collections::HashMap;
use git::{
blame::{Blame, BlameEntry},
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid, PullRequest,
parse_git_remote_url, GitHostingProvider, GitHostingProviderRegistry, Oid,
};
use gpui::{App, Context, Entity, Subscription, Task};
use http_client::HttpClient;
@@ -12,8 +12,11 @@ use project::{Project, ProjectItem};
use smallvec::SmallVec;
use std::{sync::Arc, time::Duration};
use sum_tree::SumTree;
use ui::SharedString;
use url::Url;
use crate::commit_tooltip::ParsedCommitMessage;
#[derive(Clone, Debug, Default)]
pub struct GitBlameEntry {
pub rows: u32,
@@ -77,7 +80,11 @@ impl GitRemote {
self.host.supports_avatars()
}
pub async fn avatar_url(&self, commit: Oid, client: Arc<dyn HttpClient>) -> Option<Url> {
pub async fn avatar_url(
&self,
commit: SharedString,
client: Arc<dyn HttpClient>,
) -> Option<Url> {
self.host
.commit_author_avatar_url(&self.owner, &self.repo, commit, client)
.await
@@ -85,21 +92,11 @@ impl GitRemote {
.flatten()
}
}
#[derive(Clone, Debug)]
pub struct CommitDetails {
pub message: String,
pub parsed_message: ParsedMarkdown,
pub permalink: Option<Url>,
pub pull_request: Option<PullRequest>,
pub remote: Option<GitRemote>,
}
pub struct GitBlame {
project: Entity<Project>,
buffer: Entity<Buffer>,
entries: SumTree<GitBlameEntry>,
commit_details: HashMap<Oid, CommitDetails>,
commit_details: HashMap<Oid, crate::commit_tooltip::ParsedCommitMessage>,
buffer_snapshot: BufferSnapshot,
buffer_edits: text::Subscription,
task: Task<Result<()>>,
@@ -187,7 +184,7 @@ impl GitBlame {
self.generated
}
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<CommitDetails> {
pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<ParsedCommitMessage> {
self.commit_details.get(&entry.sha).cloned()
}
@@ -480,7 +477,7 @@ async fn parse_commit_messages(
deprecated_permalinks: &HashMap<Oid, Url>,
provider_registry: Arc<GitHostingProviderRegistry>,
languages: &Arc<LanguageRegistry>,
) -> HashMap<Oid, CommitDetails> {
) -> HashMap<Oid, ParsedCommitMessage> {
let mut commit_details = HashMap::default();
let parsed_remote_url = remote_url
@@ -519,8 +516,8 @@ async fn parse_commit_messages(
commit_details.insert(
oid,
CommitDetails {
message,
ParsedCommitMessage {
message: message.into(),
parsed_message,
permalink,
remote,

View File

@@ -598,7 +598,7 @@ async fn parse_blocks(
},
syntax: cx.theme().syntax().clone(),
selection_background_color: { cx.theme().players().local().selection },
break_style: Default::default(),
heading: StyleRefinement::default()
.font_weight(FontWeight::BOLD)
.text_base()
@@ -885,8 +885,10 @@ mod tests {
let slice = data;
for (range, event) in slice.iter() {
if [MarkdownEvent::Text, MarkdownEvent::Code].contains(event) {
rendered_text.push_str(&text[range.clone()])
match event {
MarkdownEvent::Text(parsed) => rendered_text.push_str(parsed),
MarkdownEvent::Code => rendered_text.push_str(&text[range.clone()]),
_ => {}
}
}
}

View File

@@ -763,7 +763,7 @@ impl Editor {
this.child({
let focus = editor.focus_handle(cx);
PopoverMenu::new("hunk-controls-dropdown")
.trigger(
.trigger_with_tooltip(
IconButton::new(
"toggle_editor_selections_icon",
IconName::EllipsisVertical,
@@ -774,19 +774,8 @@ impl Editor {
.toggle_state(
hunk_controls_menu_handle
.is_deployed(),
)
.when(
!hunk_controls_menu_handle
.is_deployed(),
|this| {
this.tooltip(|_, cx| {
Tooltip::simple(
"Hunk Controls",
cx,
)
})
},
),
Tooltip::simple("Hunk Controls", cx),
)
.anchor(Corner::TopRight)
.with_handle(hunk_controls_menu_handle)

View File

@@ -1,10 +1,9 @@
use gpui::{prelude::*, Entity};
use indoc::indoc;
use inline_completion::InlineCompletionProvider;
use language::{Language, LanguageConfig};
use inline_completion::EditPredictionProvider;
use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
use project::Project;
use std::{num::NonZeroU32, ops::Range, sync::Arc};
use std::ops::Range;
use text::{Point, ToOffset};
use crate::{
@@ -124,54 +123,6 @@ async fn test_inline_completion_jump_button(cx: &mut gpui::TestAppContext) {
"});
}
#[gpui::test]
async fn test_indentation(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
settings.defaults.tab_size = NonZeroU32::new(4)
});
let language = Arc::new(
Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(r#"(_ "(" ")" @end) @indent"#)
.unwrap(),
);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
let provider = cx.new(|_| FakeInlineCompletionProvider::default());
assign_editor_completion_provider(provider.clone(), &mut cx);
cx.set_state(indoc! {"
const a: A = (
ˇ
);
"});
propose_edits(
&provider,
vec![(Point::new(1, 0)..Point::new(1, 0), " const function()")],
&mut cx,
);
cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
assert_editor_active_edit_completion(&mut cx, |_, edits| {
assert_eq!(edits.len(), 1);
assert_eq!(edits[0].1.as_str(), " const function()");
});
// When the cursor is before the suggested indentation level, accepting a
// completion should just indent.
accept_completion(&mut cx);
cx.assert_editor_state(indoc! {"
const a: A = (
ˇ
);
"});
}
#[gpui::test]
async fn test_inline_completion_invalidation_range(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -315,7 +266,7 @@ fn assert_editor_active_move_completion(
fn accept_completion(cx: &mut EditorTestContext) {
cx.update_editor(|editor, window, cx| {
editor.accept_inline_completion(&crate::AcceptInlineCompletion, window, cx)
editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
})
}
@@ -333,6 +284,7 @@ fn propose_edits<T: ToOffset>(
cx.update(|_, cx| {
provider.update(cx, |provider, _| {
provider.set_inline_completion(Some(inline_completion::InlineCompletion {
id: None,
edits: edits.collect(),
edit_preview: None,
}))
@@ -345,7 +297,7 @@ fn assign_editor_completion_provider(
cx: &mut EditorTestContext,
) {
cx.update_editor(|editor, window, cx| {
editor.set_inline_completion_provider(Some(provider), window, cx);
editor.set_edit_prediction_provider(Some(provider), window, cx);
})
}
@@ -363,7 +315,7 @@ impl FakeInlineCompletionProvider {
}
}
impl InlineCompletionProvider for FakeInlineCompletionProvider {
impl EditPredictionProvider for FakeInlineCompletionProvider {
fn name() -> &'static str {
"fake-completion-provider"
}
@@ -376,10 +328,6 @@ impl InlineCompletionProvider for FakeInlineCompletionProvider {
false
}
fn show_completions_in_normal_mode() -> bool {
false
}
fn is_enabled(
&self,
_buffer: &gpui::Entity<language::Buffer>,

View File

@@ -36,7 +36,7 @@ use std::{
};
use text::{BufferId, Selection};
use theme::{Theme, ThemeSettings};
use ui::{h_flex, prelude::*, IconDecorationKind, Label};
use ui::{prelude::*, IconDecorationKind};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
use workspace::{
@@ -679,8 +679,8 @@ impl Item for Editor {
.child(
Label::new(self.title(cx).to_string())
.color(label_color)
.italic(params.preview)
.strikethrough(was_deleted),
.when(params.preview, |this| this.italic())
.when(was_deleted, |this| this.strikethrough()),
)
.when_some(description, |this, description| {
this.child(

View File

@@ -1,6 +1,6 @@
use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider};
use buffer_diff::BufferDiff;
use collections::HashSet;
use diff::BufferDiff;
use futures::{channel::mpsc, future::join_all};
use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task};
use language::{Buffer, BufferEvent, Capability};
@@ -185,7 +185,7 @@ impl ProposedChangesEditor {
} else {
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
new_diffs.push(cx.new(|cx| {
let mut diff = BufferDiff::new(&branch_buffer, cx);
let mut diff = BufferDiff::new(branch_buffer.read(cx));
let _ = diff.set_base_text(
location.buffer.clone(),
branch_buffer.read(cx).text_snapshot(),

View File

@@ -3,6 +3,7 @@ pub(crate) mod autoscroll;
pub(crate) mod scroll_amount;
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
use crate::EditPredictionPreview;
use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint},
hover_popover::hide_hover,
@@ -495,6 +496,15 @@ impl Editor {
hide_hover(self, cx);
let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
if let EditPredictionPreview::Active {
previous_scroll_position,
} = &mut self.edit_prediction_preview
{
if !autoscroll {
previous_scroll_position.take();
}
}
self.scroll_manager.set_scroll_position(
scroll_position,
&display_map,

View File

@@ -113,6 +113,7 @@ impl Editor {
target_bottom = target_top + 1.;
} else {
let selections = self.selections.all::<Point>(cx);
target_top = selections
.first()
.unwrap()

View File

@@ -2,8 +2,8 @@ use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
RowExt,
};
use buffer_diff::DiffHunkStatus;
use collections::BTreeMap;
use diff::DiffHunkStatus;
use futures::Future;
use gpui::{
@@ -298,6 +298,18 @@ impl EditorTestContext {
self.cx.run_until_parked();
}
pub fn assert_index_text(&mut self, expected: Option<&str>) {
let fs = self.update_editor(|editor, _, cx| {
editor.project.as_ref().unwrap().read(cx).fs().as_fake()
});
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
let mut found = None;
fs.with_git_state(&Self::root_path().join(".git"), false, |git_state| {
found = git_state.index_contents.get(path.as_ref()).cloned();
});
assert_eq!(expected, found.as_deref());
}
/// Change the editor's text and selections using a string containing
/// embedded range markers that represent the ranges and directions of
/// each selection.
@@ -459,9 +471,9 @@ pub fn assert_state_with_diff(
.zip(line_infos)
.map(|(line, info)| {
let mut marker = match info.diff_status {
Some(DiffHunkStatus::Added) => "+ ",
Some(DiffHunkStatus::Removed) => "- ",
Some(DiffHunkStatus::Modified) => unreachable!(),
Some(DiffHunkStatus::Added(_)) => "+ ",
Some(DiffHunkStatus::Removed(_)) => "- ",
Some(DiffHunkStatus::Modified(_)) => unreachable!(),
None => {
if has_diff {
" "

View File

@@ -118,6 +118,8 @@ pub trait ExtensionThemeProxy: Send + Sync + 'static {
icons_root_dir: PathBuf,
fs: Arc<dyn Fs>,
) -> Task<Result<()>>;
fn reload_current_icon_theme(&self, cx: &mut App);
}
impl ExtensionThemeProxy for ExtensionHostProxy {
@@ -185,6 +187,14 @@ impl ExtensionThemeProxy for ExtensionHostProxy {
proxy.load_icon_theme(icon_theme_path, icons_root_dir, fs)
}
fn reload_current_icon_theme(&self, cx: &mut App) {
let Some(proxy) = self.theme_proxy.read().clone() else {
return;
};
proxy.reload_current_icon_theme(cx)
}
}
pub trait ExtensionGrammarProxy: Send + Sync + 'static {

View File

@@ -1292,6 +1292,7 @@ impl ExtensionStore {
this.wasm_extensions.extend(wasm_extensions);
this.proxy.reload_current_theme(cx);
this.proxy.reload_current_icon_theme(cx);
})
.ok();
})

View File

@@ -84,7 +84,7 @@ impl HostWorktree for WasmState {
latest::HostWorktree::which(self, delegate, binary_name).await
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
Ok(())
}
}

View File

@@ -92,7 +92,7 @@ impl HostWorktree for WasmState {
latest::HostWorktree::which(self, delegate, binary_name).await
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
// We only ever hand out borrows of worktrees.
Ok(())
}

View File

@@ -147,7 +147,7 @@ impl HostWorktree for WasmState {
latest::HostWorktree::which(self, delegate, binary_name).await
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
// We only ever hand out borrows of worktrees.
Ok(())
}

View File

@@ -240,7 +240,7 @@ impl HostKeyValueStore for WasmState {
kv_store.insert(key, value).await.to_wasmtime_result()
}
fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
// We only ever hand out borrows of key-value stores.
Ok(())
}
@@ -282,7 +282,7 @@ impl HostWorktree for WasmState {
latest::HostWorktree::which(self, delegate, binary_name).await
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
// We only ever hand out borrows of worktrees.
Ok(())
}
@@ -350,7 +350,7 @@ impl http_client::HostHttpResponseStream for WasmState {
.to_wasmtime_result()
}
fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
Ok(())
}
}

View File

@@ -259,7 +259,7 @@ impl HostKeyValueStore for WasmState {
kv_store.insert(key, value).await.to_wasmtime_result()
}
fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<ExtensionKeyValueStore>) -> Result<()> {
// We only ever hand out borrows of key-value stores.
Ok(())
}
@@ -275,7 +275,7 @@ impl HostProject for WasmState {
Ok(project.worktree_ids())
}
fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
async fn drop(&mut self, _project: Resource<Project>) -> Result<()> {
// We only ever hand out borrows of projects.
Ok(())
}
@@ -325,7 +325,7 @@ impl HostWorktree for WasmState {
Ok(delegate.which(binary_name).await)
}
fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
async fn drop(&mut self, _worktree: Resource<Worktree>) -> Result<()> {
// We only ever hand out borrows of worktrees.
Ok(())
}
@@ -393,7 +393,7 @@ impl http_client::HostHttpResponseStream for WasmState {
.to_wasmtime_result()
}
fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
async fn drop(&mut self, _resource: Resource<ExtensionHttpResponseStream>) -> Result<()> {
Ok(())
}
}

View File

@@ -64,11 +64,28 @@ impl FeatureFlag for PredictEditsFeatureFlag {
const NAME: &'static str = "predict-edits";
}
/// A feature flag that controls things that shouldn't go live until the predictive edits launch.
pub struct PredictEditsLaunchFeatureFlag;
impl FeatureFlag for PredictEditsLaunchFeatureFlag {
const NAME: &'static str = "predict-edits-launch";
}
pub struct PredictEditsRateCompletionsFeatureFlag;
impl FeatureFlag for PredictEditsRateCompletionsFeatureFlag {
const NAME: &'static str = "predict-edits-rate-completions";
}
/// A feature flag that controls whether "non eager mode" (holding `alt` to preview) is publicized.
pub struct PredictEditsNonEagerModeFeatureFlag;
impl FeatureFlag for PredictEditsNonEagerModeFeatureFlag {
const NAME: &'static str = "predict-edits-non-eager-mode";
fn enabled_for_staff() -> bool {
// Don't show to staff so it doesn't leak into media for the launch.
false
}
}
pub struct GitUiFeatureFlag;
impl FeatureFlag for GitUiFeatureFlag {
const NAME: &'static str = "git-ui";

View File

@@ -191,7 +191,7 @@ impl FeedbackModal {
);
editor.set_show_gutter(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_show_inline_completions(Some(false), window, cx);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.set_vertical_scroll_margin(5, cx);
editor.set_use_modal_editing(false);
editor.set_soft_wrap();

View File

@@ -64,12 +64,17 @@ impl Display for SystemSpecs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let os_information = format!("OS: {} {}", self.os_name, self.os_version);
let app_version_information = format!(
"Zed: v{} ({})",
"Zed: v{} ({}) {}",
self.app_version,
match &self.commit_sha {
Some(commit_sha) => format!("{} {}", self.release_channel, commit_sha),
None => self.release_channel.to_string(),
}
},
if cfg!(debug_assertions) {
"(Taylor's Version)"
} else {
""
},
);
let system_specs = [
app_version_information,

View File

@@ -817,7 +817,10 @@ async fn test_external_files_history(cx: &mut gpui::TestAppContext) {
.as_u64() as usize,
)
});
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
cx.dispatch_action(workspace::CloseActiveItem {
save_intent: None,
close_pinned: false,
});
let initial_history_items =
open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
@@ -2000,7 +2003,10 @@ async fn open_close_queried_buffer(
)
.await;
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
cx.dispatch_action(workspace::CloseActiveItem {
save_intent: None,
close_pinned: false,
});
history_items
}

View File

@@ -93,6 +93,8 @@ impl PickerDelegate for OpenPathDelegate {
cx.notify();
}
// todo(windows)
// Is this method woring correctly on Windows? This method uses `/` for path separator.
fn update_matches(
&mut self,
query: String,

View File

@@ -11,6 +11,9 @@ workspace = true
[lib]
path = "src/git.rs"
[features]
test-support = []
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
@@ -32,10 +35,7 @@ url.workspace = true
util.workspace = true
[dev-dependencies]
unindent.workspace = true
serde_json.workspace = true
pretty_assertions.workspace = true
text = {workspace = true, features = ["test-support"]}
[features]
test-support = []
serde_json.workspace = true
text = { workspace = true, features = ["test-support"] }
unindent.workspace = true

View File

@@ -132,8 +132,8 @@ pub struct BlameEntry {
pub author_time: Option<i64>,
pub author_tz: Option<String>,
pub committer: Option<String>,
pub committer_mail: Option<String>,
pub committer_name: Option<String>,
pub committer_email: Option<String>,
pub committer_time: Option<i64>,
pub committer_tz: Option<String>,
@@ -255,10 +255,12 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> {
.clone_from(&existing_entry.author_mail);
new_entry.author_time = existing_entry.author_time;
new_entry.author_tz.clone_from(&existing_entry.author_tz);
new_entry.committer.clone_from(&existing_entry.committer);
new_entry
.committer_mail
.clone_from(&existing_entry.committer_mail);
.committer_name
.clone_from(&existing_entry.committer_name);
new_entry
.committer_email
.clone_from(&existing_entry.committer_email);
new_entry.committer_time = existing_entry.committer_time;
new_entry
.committer_tz
@@ -288,8 +290,8 @@ fn parse_git_blame(output: &str) -> Result<Vec<BlameEntry>> {
}
"author-tz" if is_committed => entry.author_tz = Some(value.into()),
"committer" if is_committed => entry.committer = Some(value.into()),
"committer-mail" if is_committed => entry.committer_mail = Some(value.into()),
"committer" if is_committed => entry.committer_name = Some(value.into()),
"committer-mail" if is_committed => entry.committer_email = Some(value.into()),
"committer-time" if is_committed => {
entry.committer_time = Some(value.parse::<i64>()?)
}

View File

@@ -38,8 +38,8 @@ actions!(
StageAll,
UnstageAll,
RevertAll,
CommitChanges,
CommitAllChanges,
Uncommit,
Commit,
ClearCommitMessage
]
);

View File

@@ -4,13 +4,11 @@ use anyhow::Result;
use async_trait::async_trait;
use collections::BTreeMap;
use derive_more::{Deref, DerefMut};
use gpui::{App, Global};
use gpui::{App, Global, SharedString};
use http_client::HttpClient;
use parking_lot::RwLock;
use url::Url;
use crate::Oid;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PullRequest {
pub number: u32,
@@ -83,7 +81,7 @@ pub trait GitHostingProvider {
&self,
_repo_owner: &str,
_repo: &str,
_commit: Oid,
_commit: SharedString,
_http_client: Arc<dyn HttpClient>,
) -> Result<Option<Url>> {
Ok(None)

View File

@@ -1,13 +1,15 @@
use crate::status::FileStatus;
use crate::GitHostingProviderRegistry;
use crate::{blame::Blame, status::GitStatus};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet};
use git2::BranchType;
use gpui::SharedString;
use parking_lot::Mutex;
use rope::Rope;
use std::borrow::Borrow;
use std::io::Write as _;
use std::process::Stdio;
use std::sync::LazyLock;
use std::{
cmp::Ordering,
@@ -18,12 +20,63 @@ use sum_tree::MapSeekTarget;
use util::command::new_std_command;
use util::ResultExt;
#[derive(Clone, Debug, Hash, PartialEq)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Branch {
pub is_head: bool,
pub name: SharedString,
/// Timestamp of most recent commit, normalized to Unix Epoch format.
pub unix_timestamp: Option<i64>,
pub upstream: Option<Upstream>,
pub most_recent_commit: Option<CommitSummary>,
}
impl Branch {
pub fn priority_key(&self) -> (bool, Option<i64>) {
(
self.is_head,
self.most_recent_commit
.as_ref()
.map(|commit| commit.commit_timestamp),
)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Upstream {
pub ref_name: SharedString,
pub tracking: Option<UpstreamTracking>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct UpstreamTracking {
pub ahead: u32,
pub behind: u32,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct CommitSummary {
pub sha: SharedString,
pub subject: SharedString,
/// This is a unix timestamp
pub commit_timestamp: i64,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct CommitDetails {
pub sha: SharedString,
pub message: SharedString,
pub commit_timestamp: i64,
pub committer_email: SharedString,
pub committer_name: SharedString,
}
pub enum ResetMode {
// reset the branch pointer, leave index and worktree unchanged
// (this will make it look like things that were committed are now
// staged)
Soft,
// reset the branch pointer and index, leave worktree unchanged
// (this makes it look as though things that were committed are now
// unstaged)
Mixed,
}
pub trait GitRepository: Send + Sync {
@@ -39,9 +92,10 @@ pub trait GitRepository: Send + Sync {
/// Note that for symlink entries, this will return the contents of the symlink, not the target.
fn load_committed_text(&self, path: &RepoPath) -> Option<String>;
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()>;
/// Returns the URL of the remote with the given name.
fn remote_url(&self, name: &str) -> Option<String>;
fn branch_name(&self) -> Option<String>;
/// Returns the SHA of the current HEAD.
fn head_sha(&self) -> Option<String>;
@@ -56,10 +110,23 @@ pub trait GitRepository: Send + Sync {
fn create_branch(&self, _: &str) -> Result<()>;
fn branch_exits(&self, _: &str) -> Result<bool>;
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>;
fn show(&self, commit: &str) -> Result<CommitDetails>;
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
/// Returns the path to the repository, typically the `.git` folder.
fn dot_git_dir(&self) -> PathBuf;
/// Returns the absolute path to the repository. For worktrees, this will be the path to the
/// worktree's gitdir within the main repository (typically `.git/worktrees/<name>`).
fn path(&self) -> PathBuf;
/// Returns the absolute path to the ".git" dir for the main repository, typically a `.git`
/// folder. For worktrees, this will be the path to the repository the worktree was created
/// from. Otherwise, this is the same value as `path()`.
///
/// Git documentation calls this the "commondir", and for git CLI is overridden by
/// `GIT_COMMON_DIR`.
fn main_repository_path(&self) -> PathBuf;
/// Updates the index to match the worktree at the given paths.
///
@@ -109,11 +176,63 @@ impl GitRepository for RealGitRepository {
}
}
fn dot_git_dir(&self) -> PathBuf {
fn path(&self) -> PathBuf {
let repo = self.repository.lock();
repo.path().into()
}
fn main_repository_path(&self) -> PathBuf {
let repo = self.repository.lock();
repo.commondir().into()
}
fn show(&self, commit: &str) -> Result<CommitDetails> {
let repo = self.repository.lock();
let Ok(commit) = repo.revparse_single(commit)?.into_commit() else {
anyhow::bail!("{} is not a commit", commit);
};
let details = CommitDetails {
sha: commit.id().to_string().into(),
message: String::from_utf8_lossy(commit.message_raw_bytes())
.to_string()
.into(),
commit_timestamp: commit.time().seconds(),
committer_email: String::from_utf8_lossy(commit.committer().email_bytes())
.to_string()
.into(),
committer_name: String::from_utf8_lossy(commit.committer().name_bytes())
.to_string()
.into(),
};
Ok(details)
}
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()> {
let working_directory = self
.repository
.lock()
.workdir()
.context("failed to read git work directory")?
.to_path_buf();
let mode_flag = match mode {
ResetMode::Mixed => "--mixed",
ResetMode::Soft => "--soft",
};
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["reset", mode_flag, commit])
.output()?;
if !output.status.success() {
return Err(anyhow!(
"Failed to reset:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
Ok(())
}
fn load_index_text(&self, path: &RepoPath) -> Option<String> {
fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
const STAGE_NORMAL: i32 = 0;
@@ -147,19 +266,56 @@ impl GitRepository for RealGitRepository {
Some(content)
}
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> {
let working_directory = self
.repository
.lock()
.workdir()
.context("failed to read git work directory")?
.to_path_buf();
if let Some(content) = content {
let mut child = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["hash-object", "-w", "--stdin"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
child.stdin.take().unwrap().write_all(content.as_bytes())?;
let output = child.wait_with_output()?.stdout;
let sha = String::from_utf8(output)?;
log::debug!("indexing SHA: {sha}, path {path:?}");
let status = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["update-index", "--add", "--cacheinfo", "100644", &sha])
.arg(path.as_ref())
.status()?;
if !status.success() {
return Err(anyhow!("Failed to add to index: {status:?}"));
}
} else {
let status = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["update-index", "--force-remove"])
.arg(path.as_ref())
.status()?;
if !status.success() {
return Err(anyhow!("Failed to remove from index: {status:?}"));
}
}
Ok(())
}
fn remote_url(&self, name: &str) -> Option<String> {
let repo = self.repository.lock();
let remote = repo.find_remote(name).ok()?;
remote.url().map(|url| url.to_string())
}
fn branch_name(&self) -> Option<String> {
let repo = self.repository.lock();
let head = repo.head().log_err()?;
let branch = String::from_utf8_lossy(head.shorthand_bytes());
Some(branch.to_string())
}
fn head_sha(&self) -> Option<String> {
Some(self.repository.lock().head().ok()?.target()?.to_string())
}
@@ -199,33 +355,62 @@ impl GitRepository for RealGitRepository {
}
fn branches(&self) -> Result<Vec<Branch>> {
let repo = self.repository.lock();
let local_branches = repo.branches(Some(BranchType::Local))?;
let valid_branches = local_branches
.filter_map(|branch| {
branch.ok().and_then(|(branch, _)| {
let is_head = branch.is_head();
let name = branch
.name()
.ok()
.flatten()
.map(|name| name.to_string().into())?;
let timestamp = branch.get().peel_to_commit().ok()?.time();
let unix_timestamp = timestamp.seconds();
let timezone_offset = timestamp.offset_minutes();
let utc_offset =
time::UtcOffset::from_whole_seconds(timezone_offset * 60).ok()?;
let unix_timestamp =
time::OffsetDateTime::from_unix_timestamp(unix_timestamp).ok()?;
Some(Branch {
is_head,
name,
unix_timestamp: Some(unix_timestamp.to_offset(utc_offset).unix_timestamp()),
})
})
})
.collect();
Ok(valid_branches)
let working_directory = self
.repository
.lock()
.workdir()
.context("failed to read git work directory")?
.to_path_buf();
let fields = [
"%(HEAD)",
"%(objectname)",
"%(refname)",
"%(upstream)",
"%(upstream:track)",
"%(committerdate:unix)",
"%(contents:subject)",
]
.join("%00");
let args = vec!["for-each-ref", "refs/heads/*", "--format", &fields];
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(args)
.output()?;
if !output.status.success() {
return Err(anyhow!(
"Failed to git git branches:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
let input = String::from_utf8_lossy(&output.stdout);
let mut branches = parse_branch_input(&input)?;
if branches.is_empty() {
let args = vec!["symbolic-ref", "--quiet", "--short", "HEAD"];
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(args)
.output()?;
// git symbolic-ref returns a non-0 exit code if HEAD points
// to something other than a branch
if output.status.success() {
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
branches.push(Branch {
name: name.into(),
is_head: true,
upstream: None,
most_recent_commit: None,
});
}
}
Ok(branches)
}
fn change_branch(&self, name: &str) -> Result<()> {
@@ -279,13 +464,16 @@ impl GitRepository for RealGitRepository {
.to_path_buf();
if !paths.is_empty() {
let status = new_std_command(&self.git_binary_path)
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["update-index", "--add", "--remove", "--"])
.args(paths.iter().map(|p| p.as_ref()))
.status()?;
if !status.success() {
return Err(anyhow!("Failed to stage paths: {status}"));
.output()?;
if !output.status.success() {
return Err(anyhow!(
"Failed to stage paths:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
}
Ok(())
@@ -300,13 +488,16 @@ impl GitRepository for RealGitRepository {
.to_path_buf();
if !paths.is_empty() {
let cmd = new_std_command(&self.git_binary_path)
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(["reset", "--quiet", "--"])
.args(paths.iter().map(|p| p.as_ref()))
.status()?;
if !cmd.success() {
return Err(anyhow!("Failed to unstage paths: {cmd}"));
.output()?;
if !output.status.success() {
return Err(anyhow!(
"Failed to unstage:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
}
Ok(())
@@ -326,12 +517,16 @@ impl GitRepository for RealGitRepository {
args.push(author);
}
let cmd = new_std_command(&self.git_binary_path)
let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory)
.args(args)
.status()?;
if !cmd.success() {
return Err(anyhow!("Failed to commit: {cmd}"));
.output()?;
if !output.status.success() {
return Err(anyhow!(
"Failed to commit:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
Ok(())
}
@@ -344,7 +539,7 @@ pub struct FakeGitRepository {
#[derive(Debug, Clone)]
pub struct FakeGitRepositoryState {
pub dot_git_dir: PathBuf,
pub path: PathBuf,
pub event_emitter: smol::channel::Sender<PathBuf>,
pub head_contents: HashMap<RepoPath, String>,
pub index_contents: HashMap<RepoPath, String>,
@@ -361,9 +556,9 @@ impl FakeGitRepository {
}
impl FakeGitRepositoryState {
pub fn new(dot_git_dir: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
pub fn new(path: PathBuf, event_emitter: smol::channel::Sender<PathBuf>) -> Self {
FakeGitRepositoryState {
dot_git_dir,
path,
event_emitter,
head_contents: Default::default(),
index_contents: Default::default(),
@@ -388,13 +583,22 @@ impl GitRepository for FakeGitRepository {
state.head_contents.get(path.as_ref()).cloned()
}
fn remote_url(&self, _name: &str) -> Option<String> {
None
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> {
let mut state = self.state.lock();
if let Some(content) = content {
state.index_contents.insert(path.clone(), content);
} else {
state.index_contents.remove(path);
}
state
.event_emitter
.try_send(state.path.clone())
.expect("Dropped repo change event");
Ok(())
}
fn branch_name(&self) -> Option<String> {
let state = self.state.lock();
state.current_branch_name.clone()
fn remote_url(&self, _name: &str) -> Option<String> {
None
}
fn head_sha(&self) -> Option<String> {
@@ -405,9 +609,21 @@ impl GitRepository for FakeGitRepository {
vec![]
}
fn dot_git_dir(&self) -> PathBuf {
fn show(&self, _: &str) -> Result<CommitDetails> {
unimplemented!()
}
fn reset(&self, _: &str, _: ResetMode) -> Result<()> {
unimplemented!()
}
fn path(&self) -> PathBuf {
let state = self.state.lock();
state.dot_git_dir.clone()
state.path.clone()
}
fn main_repository_path(&self) -> PathBuf {
self.path()
}
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
@@ -443,7 +659,8 @@ impl GitRepository for FakeGitRepository {
.map(|branch_name| Branch {
is_head: Some(branch_name) == current_branch.as_ref(),
name: branch_name.into(),
unix_timestamp: None,
most_recent_commit: None,
upstream: None,
})
.collect())
}
@@ -458,7 +675,7 @@ impl GitRepository for FakeGitRepository {
state.current_branch_name = Some(name.to_owned());
state
.event_emitter
.try_send(state.dot_git_dir.clone())
.try_send(state.path.clone())
.expect("Dropped repo change event");
Ok(())
}
@@ -468,7 +685,7 @@ impl GitRepository for FakeGitRepository {
state.branches.insert(name.to_owned());
state
.event_emitter
.try_send(state.dot_git_dir.clone())
.try_send(state.path.clone())
.expect("Dropped repo change event");
Ok(())
}
@@ -543,10 +760,6 @@ impl RepoPath {
RepoPath(path.into())
}
pub fn to_proto(&self) -> String {
self.0.to_string_lossy().to_string()
}
}
impl std::fmt::Display for RepoPath {
@@ -617,3 +830,106 @@ impl<'a> MapSeekTarget<RepoPath> for RepoPathDescendants<'a> {
}
}
}
fn parse_branch_input(input: &str) -> Result<Vec<Branch>> {
let mut branches = Vec::new();
for line in input.split('\n') {
if line.is_empty() {
continue;
}
let mut fields = line.split('\x00');
let is_current_branch = fields.next().context("no HEAD")? == "*";
let head_sha: SharedString = fields.next().context("no objectname")?.to_string().into();
let ref_name: SharedString = fields
.next()
.context("no refname")?
.strip_prefix("refs/heads/")
.context("unexpected format for refname")?
.to_string()
.into();
let upstream_name = fields.next().context("no upstream")?.to_string();
let upstream_tracking = parse_upstream_track(fields.next().context("no upstream:track")?)?;
let commiterdate = fields.next().context("no committerdate")?.parse::<i64>()?;
let subject: SharedString = fields
.next()
.context("no contents:subject")?
.to_string()
.into();
branches.push(Branch {
is_head: is_current_branch,
name: ref_name,
most_recent_commit: Some(CommitSummary {
sha: head_sha,
subject,
commit_timestamp: commiterdate,
}),
upstream: if upstream_name.is_empty() {
None
} else {
Some(Upstream {
ref_name: upstream_name.into(),
tracking: upstream_tracking,
})
},
})
}
Ok(branches)
}
fn parse_upstream_track(upstream_track: &str) -> Result<Option<UpstreamTracking>> {
if upstream_track == "" {
return Ok(Some(UpstreamTracking {
ahead: 0,
behind: 0,
}));
}
let upstream_track = upstream_track
.strip_prefix("[")
.ok_or_else(|| anyhow!("missing ["))?;
let upstream_track = upstream_track
.strip_suffix("]")
.ok_or_else(|| anyhow!("missing ["))?;
let mut ahead: u32 = 0;
let mut behind: u32 = 0;
for component in upstream_track.split(", ") {
if component == "gone" {
return Ok(None);
}
if let Some(ahead_num) = component.strip_prefix("ahead ") {
ahead = ahead_num.parse::<u32>()?;
}
if let Some(behind_num) = component.strip_prefix("behind ") {
behind = behind_num.parse::<u32>()?;
}
}
Ok(Some(UpstreamTracking { ahead, behind }))
}
#[test]
fn test_branches_parsing() {
// suppress "help: octal escapes are not supported, `\0` is always null"
#[allow(clippy::octal_escapes)]
let input = "*\0060964da10574cd9bf06463a53bf6e0769c5c45e\0refs/heads/zed-patches\0refs/remotes/origin/zed-patches\0\01733187470\0generated protobuf\n";
assert_eq!(
parse_branch_input(&input).unwrap(),
vec![Branch {
is_head: true,
name: "zed-patches".into(),
upstream: Some(Upstream {
ref_name: "refs/remotes/origin/zed-patches".into(),
tracking: Some(UpstreamTracking {
ahead: 0,
behind: 0
})
}),
most_recent_commit: Some(CommitSummary {
sha: "060964da10574cd9bf06463a53bf6e0769c5c45e".into(),
subject: "generated protobuf".into(),
commit_timestamp: 1733187470,
})
}]
)
}

View File

@@ -10,8 +10,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -29,8 +29,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -48,8 +48,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -67,8 +67,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -86,8 +86,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -105,8 +105,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -124,8 +124,8 @@
"author_mail": "<64036912+mmkaram@users.noreply.github.com>",
"author_time": 1708621949,
"author_tz": "-0800",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1708621949,
"committer_tz": "-0700",
"summary": "Add option to either use system clipboard or vim clipboard (#7936)",
@@ -143,8 +143,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -162,8 +162,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -181,8 +181,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -200,8 +200,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -219,8 +219,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -238,8 +238,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -257,8 +257,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -276,8 +276,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -295,8 +295,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -314,8 +314,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -333,8 +333,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1707520689,
"author_tz": "-0700",
"committer": "GitHub",
"committer_mail": "<noreply@github.com>",
"committer_name": "GitHub",
"committer_email": "<noreply@github.com>",
"committer_time": 1707520689,
"committer_tz": "-0700",
"summary": "Highlight selections on vim yank (#7638)",
@@ -352,8 +352,8 @@
"author_mail": "<maxbrunsfeld@gmail.com>",
"author_time": 1705619094,
"author_tz": "-0800",
"committer": "Max Brunsfeld",
"committer_mail": "<maxbrunsfeld@gmail.com>",
"committer_name": "Max Brunsfeld",
"committer_email": "<maxbrunsfeld@gmail.com>",
"committer_time": 1705619205,
"committer_tz": "-0800",
"summary": "Merge branch 'main' into language-api-docs",
@@ -371,8 +371,8 @@
"author_mail": "<maxbrunsfeld@gmail.com>",
"author_time": 1705619094,
"author_tz": "-0800",
"committer": "Max Brunsfeld",
"committer_mail": "<maxbrunsfeld@gmail.com>",
"committer_name": "Max Brunsfeld",
"committer_email": "<maxbrunsfeld@gmail.com>",
"committer_time": 1705619205,
"committer_tz": "-0800",
"summary": "Merge branch 'main' into language-api-docs",
@@ -390,8 +390,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1694798044,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1694798044,
"committer_tz": "-0600",
"summary": "Fix Y on last line with no trailing new line",
@@ -409,8 +409,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1694798044,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1694798044,
"committer_tz": "-0600",
"summary": "Fix Y on last line with no trailing new line",
@@ -428,8 +428,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692855942,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692856812,
"committer_tz": "-0600",
"summary": "vim: Fix linewise copy of last line with no trailing newline",
@@ -447,8 +447,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692855942,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692856812,
"committer_tz": "-0600",
"summary": "vim: Fix linewise copy of last line with no trailing newline",
@@ -466,8 +466,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692855942,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692856812,
"committer_tz": "-0600",
"summary": "vim: Fix linewise copy of last line with no trailing newline",
@@ -485,8 +485,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692855942,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692856812,
"committer_tz": "-0600",
"summary": "vim: Fix linewise copy of last line with no trailing newline",
@@ -504,8 +504,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692855942,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692856812,
"committer_tz": "-0600",
"summary": "vim: Fix linewise copy of last line with no trailing newline",
@@ -523,8 +523,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692644159,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692732477,
"committer_tz": "-0600",
"summary": "Rewrite paste",
@@ -542,8 +542,8 @@
"author_mail": "<conrad@zed.dev>",
"author_time": 1692644159,
"author_tz": "-0600",
"committer": "Conrad Irwin",
"committer_mail": "<conrad@zed.dev>",
"committer_name": "Conrad Irwin",
"committer_email": "<conrad@zed.dev>",
"committer_time": 1692732477,
"committer_tz": "-0600",
"summary": "Rewrite paste",
@@ -561,8 +561,8 @@
"author_mail": "<maxbrunsfeld@gmail.com>",
"author_time": 1659072896,
"author_tz": "-0700",
"committer": "Max Brunsfeld",
"committer_mail": "<maxbrunsfeld@gmail.com>",
"committer_name": "Max Brunsfeld",
"committer_email": "<maxbrunsfeld@gmail.com>",
"committer_time": 1659073230,
"committer_tz": "-0700",
"summary": ":art: Rename and simplify some autoindent stuff",
@@ -580,8 +580,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653424557,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Unify visual line_mode and non line_mode operators",
@@ -599,8 +599,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -618,8 +618,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -637,8 +637,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -656,8 +656,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -675,8 +675,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -694,8 +694,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -713,8 +713,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -732,8 +732,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -751,8 +751,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",
@@ -770,8 +770,8 @@
"author_mail": "<kay@the-simmons.net>",
"author_time": 1653007350,
"author_tz": "-0700",
"committer": "Kaylee Simmons",
"committer_mail": "<kay@the-simmons.net>",
"committer_name": "Kaylee Simmons",
"committer_email": "<kay@the-simmons.net>",
"committer_time": 1653609725,
"committer_tz": "-0700",
"summary": "Enable copy and paste in vim mode",

View File

@@ -10,8 +10,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1710764113,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1710764113,
"committer_tz": "+0100",
"summary": "Another commit",
@@ -29,8 +29,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1710764113,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1710764113,
"committer_tz": "+0100",
"summary": "Another commit",
@@ -48,8 +48,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1710764087,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1710764087,
"committer_tz": "+0100",
"summary": "Another commit",
@@ -67,8 +67,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1710764087,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1710764087,
"committer_tz": "+0100",
"summary": "Another commit",
@@ -86,8 +86,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1709299737,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1709299737,
"committer_tz": "+0100",
"summary": "Initial",
@@ -105,8 +105,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1709299737,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1709299737,
"committer_tz": "+0100",
"summary": "Initial",
@@ -124,8 +124,8 @@
"author_mail": "<mrnugget@gmail.com>",
"author_time": 1709299737,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@gmail.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@gmail.com>",
"committer_time": 1709299737,
"committer_tz": "+0100",
"summary": "Initial",

View File

@@ -10,8 +10,8 @@
"author_mail": "<mrnugget@example.com>",
"author_time": 1709808710,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@example.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@example.com>",
"committer_time": 1709808710,
"committer_tz": "+0100",
"summary": "Make a commit",
@@ -29,8 +29,8 @@
"author_mail": "<joe.schmoe@example.com>",
"author_time": 1709741400,
"author_tz": "+0100",
"committer": "Joe Schmoe",
"committer_mail": "<joe.schmoe@example.com>",
"committer_name": "Joe Schmoe",
"committer_email": "<joe.schmoe@example.com>",
"committer_time": 1709741400,
"committer_tz": "+0100",
"summary": "Joe's cool commit",
@@ -48,8 +48,8 @@
"author_mail": "<joe.schmoe@example.com>",
"author_time": 1709741400,
"author_tz": "+0100",
"committer": "Joe Schmoe",
"committer_mail": "<joe.schmoe@example.com>",
"committer_name": "Joe Schmoe",
"committer_email": "<joe.schmoe@example.com>",
"committer_time": 1709741400,
"committer_tz": "+0100",
"summary": "Joe's cool commit",
@@ -67,8 +67,8 @@
"author_mail": "<joe.schmoe@example.com>",
"author_time": 1709741400,
"author_tz": "+0100",
"committer": "Joe Schmoe",
"committer_mail": "<joe.schmoe@example.com>",
"committer_name": "Joe Schmoe",
"committer_email": "<joe.schmoe@example.com>",
"committer_time": 1709741400,
"committer_tz": "+0100",
"summary": "Joe's cool commit",
@@ -86,8 +86,8 @@
"author_mail": "<mrnugget@example.com>",
"author_time": 1709129122,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@example.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@example.com>",
"committer_time": 1709129122,
"committer_tz": "+0100",
"summary": "Get to a state where eslint would change code and imports",
@@ -105,8 +105,8 @@
"author_mail": "<mrnugget@example.com>",
"author_time": 1709128963,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@example.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@example.com>",
"committer_time": 1709128963,
"committer_tz": "+0100",
"summary": "Add some stuff",
@@ -124,8 +124,8 @@
"author_mail": "<mrnugget@example.com>",
"author_time": 1709128963,
"author_tz": "+0100",
"committer": "Thorsten Ball",
"committer_mail": "<mrnugget@example.com>",
"committer_name": "Thorsten Ball",
"committer_email": "<mrnugget@example.com>",
"committer_time": 1709128963,
"committer_tz": "+0100",
"summary": "Add some stuff",

View File

@@ -4,12 +4,13 @@ use std::sync::Arc;
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
use futures::AsyncReadExt;
use gpui::SharedString;
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
use serde::Deserialize;
use url::Url;
use git::{
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
RemoteUrl,
};
@@ -160,7 +161,7 @@ impl GitHostingProvider for Codeberg {
&self,
repo_owner: &str,
repo: &str,
commit: Oid,
commit: SharedString,
http_client: Arc<dyn HttpClient>,
) -> Result<Option<Url>> {
let commit = commit.to_string();

View File

@@ -4,13 +4,14 @@ use std::sync::{Arc, LazyLock};
use anyhow::{bail, Context, Result};
use async_trait::async_trait;
use futures::AsyncReadExt;
use gpui::SharedString;
use http_client::{AsyncBody, HttpClient, HttpRequestExt, Request};
use regex::Regex;
use serde::Deserialize;
use url::Url;
use git::{
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, Oid, ParsedGitRemote,
BuildCommitPermalinkParams, BuildPermalinkParams, GitHostingProvider, ParsedGitRemote,
PullRequest, RemoteUrl,
};
@@ -178,7 +179,7 @@ impl GitHostingProvider for Github {
&self,
repo_owner: &str,
repo: &str,
commit: Oid,
commit: SharedString,
http_client: Arc<dyn HttpClient>,
) -> Result<Option<Url>> {
let commit = commit.to_string();

View File

@@ -14,14 +14,16 @@ path = "src/git_ui.rs"
[dependencies]
anyhow.workspace = true
buffer_diff.workspace = true
collections.workspace = true
db.workspace = true
diff.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
menu.workspace = true
multi_buffer.workspace = true
@@ -35,9 +37,11 @@ serde_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
theme.workspace = true
time.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
zed_actions.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@@ -1,27 +1,49 @@
use anyhow::{anyhow, Context as _, Result};
use fuzzy::{StringMatch, StringMatchCandidate};
use git::repository::Branch;
use gpui::{
rems, AnyElement, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
Subscription, Task, WeakEntity, Window,
rems, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
Task, WeakEntity, Window,
};
use picker::{Picker, PickerDelegate};
use project::ProjectPath;
use std::{ops::Not, sync::Arc};
use std::sync::Arc;
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::notifications::DetachAndPromptErr;
use workspace::{ModalView, Workspace};
use zed_actions::branches::OpenRecent;
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _, _| {
workspace.register_action(BranchList::open);
workspace.register_action(open);
})
.detach();
}
pub fn open(
_: &mut Workspace,
_: &zed_actions::git::Branch,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let this = cx.entity().clone();
cx.spawn_in(window, |_, mut cx| async move {
// Modal branch picker has a longer trailoff than a popover one.
let delegate = BranchListDelegate::new(this.clone(), 70, &cx).await?;
this.update_in(&mut cx, |workspace, window, cx| {
workspace.toggle_modal(window, cx, |window, cx| {
BranchList::new(delegate, 34., window, cx)
})
})?;
Ok(())
})
.detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None)
}
pub struct BranchList {
pub picker: Entity<Picker<BranchListDelegate>>,
rem_width: f32,
@@ -29,29 +51,7 @@ pub struct BranchList {
}
impl BranchList {
pub fn open(
_: &mut Workspace,
_: &OpenRecent,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let this = cx.entity().clone();
cx.spawn_in(window, |_, mut cx| async move {
// Modal branch picker has a longer trailoff than a popover one.
let delegate = BranchListDelegate::new(this.clone(), 70, &cx).await?;
this.update_in(&mut cx, |workspace, window, cx| {
workspace.toggle_modal(window, cx, |window, cx| {
BranchList::new(delegate, 34., window, cx)
})
})?;
Ok(())
})
.detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None)
}
fn new(
pub fn new(
delegate: BranchListDelegate,
rem_width: f32,
window: &mut Window,
@@ -91,6 +91,7 @@ impl Render for BranchList {
#[derive(Debug, Clone)]
enum BranchEntry {
Branch(StringMatch),
History(String),
NewBranch { name: String },
}
@@ -98,6 +99,7 @@ impl BranchEntry {
fn name(&self) -> &str {
match self {
Self::Branch(branch) => &branch.string,
Self::History(branch) => &branch,
Self::NewBranch { name } => &name,
}
}
@@ -114,7 +116,7 @@ pub struct BranchListDelegate {
}
impl BranchListDelegate {
async fn new(
pub async fn new(
workspace: Entity<Workspace>,
branch_name_trailoff_after: usize,
cx: &AsyncApp,
@@ -141,7 +143,7 @@ impl BranchListDelegate {
})
}
fn branch_count(&self) -> usize {
pub fn branch_count(&self) -> usize {
self.matches
.iter()
.filter(|item| matches!(item, BranchEntry::Branch(_)))
@@ -188,9 +190,7 @@ impl PickerDelegate for BranchListDelegate {
// Truncate list of recent branches
// Do a partial sort to show recent-ish branches first.
branches.select_nth_unstable_by(RECENT_BRANCHES_COUNT - 1, |lhs, rhs| {
rhs.is_head
.cmp(&lhs.is_head)
.then(rhs.unix_timestamp.cmp(&lhs.unix_timestamp))
rhs.priority_key().cmp(&lhs.priority_key())
});
branches.truncate(RECENT_BRANCHES_COUNT);
}
@@ -207,16 +207,10 @@ impl PickerDelegate for BranchListDelegate {
let Some(candidates) = candidates.log_err() else {
return;
};
let matches = if query.is_empty() {
let matches: Vec<BranchEntry> = if query.is_empty() {
candidates
.into_iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string,
positions: Vec::new(),
score: 0.0,
})
.map(|candidate| BranchEntry::History(candidate.string))
.collect()
} else {
fuzzy::match_strings(
@@ -228,11 +222,15 @@ impl PickerDelegate for BranchListDelegate {
cx.background_executor().clone(),
)
.await
.iter()
.cloned()
.map(BranchEntry::Branch)
.collect()
};
picker
.update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate;
delegate.matches = matches.into_iter().map(BranchEntry::Branch).collect();
delegate.matches = matches;
if delegate.matches.is_empty() {
if !query.is_empty() {
delegate.matches.push(BranchEntry::NewBranch {
@@ -255,6 +253,25 @@ impl PickerDelegate for BranchListDelegate {
let Some(branch) = self.matches.get(self.selected_index()) else {
return;
};
let current_branch = self
.workspace
.update(cx, |workspace, cx| {
workspace
.project()
.read(cx)
.active_repository(cx)
.and_then(|repo| repo.read(cx).branch())
.map(|branch| branch.name.to_string())
})
.ok()
.flatten();
if current_branch == Some(branch.name().to_string()) {
cx.emit(DismissEvent);
return;
}
cx.spawn_in(window, {
let branch = branch.clone();
|picker, mut cx| async move {
@@ -268,6 +285,7 @@ impl PickerDelegate for BranchListDelegate {
let project = workspace.read(cx).project().read(cx);
let branch_to_checkout = match branch {
BranchEntry::Branch(branch) => branch.string,
BranchEntry::History(string) => string,
BranchEntry::NewBranch { name: branch_name } => branch_name,
};
let worktree = project
@@ -311,7 +329,14 @@ impl PickerDelegate for BranchListDelegate {
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.map(|parent| match hit {
.when(matches!(hit, BranchEntry::History(_)), |el| {
el.end_slot(
Icon::new(IconName::HistoryRerun)
.color(Color::Muted)
.size(IconSize::Small),
)
})
.map(|el| match hit {
BranchEntry::Branch(branch) => {
let highlights: Vec<_> = branch
.positions
@@ -320,40 +345,13 @@ impl PickerDelegate for BranchListDelegate {
.copied()
.collect();
parent.child(HighlightedLabel::new(shortened_branch_name, highlights))
el.child(HighlightedLabel::new(shortened_branch_name, highlights))
}
BranchEntry::History(_) => el.child(Label::new(shortened_branch_name)),
BranchEntry::NewBranch { name } => {
parent.child(Label::new(format!("Create branch '{name}'")))
el.child(Label::new(format!("Create branch '{name}'")))
}
}),
)
}
fn render_header(
&self,
_window: &mut Window,
_: &mut Context<Picker<Self>>,
) -> Option<AnyElement> {
let label = if self.last_query.is_empty() {
Label::new("Recent Branches")
.size(LabelSize::Small)
.mt_1()
.ml_3()
.into_any_element()
} else {
let match_label = self.matches.is_empty().not().then(|| {
let suffix = if self.branch_count() == 1 { "" } else { "es" };
Label::new(format!("{} match{}", self.branch_count(), suffix))
.color(Color::Muted)
.size(LabelSize::Small)
});
h_flex()
.px_3()
.justify_between()
.child(Label::new("Branches").size(LabelSize::Small))
.children(match_label)
.into_any_element()
};
Some(v_flex().mt_1().child(label).into_any_element())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,14 +5,18 @@ use gpui::App;
use project_diff::ProjectDiff;
use ui::{ActiveTheme, Color, Icon, IconName, IntoElement};
pub mod branch_picker;
pub mod git_panel;
mod git_panel_settings;
pub mod project_diff;
// mod quick_commit;
pub mod repository_selector;
pub fn init(cx: &mut App) {
GitPanelSettings::register(cx);
branch_picker::init(cx);
cx.observe_new(ProjectDiff::register).detach();
// quick_commit::init(cx);
}
// TODO: Add updated status colors to theme

View File

@@ -1,8 +1,8 @@
use std::any::{Any, TypeId};
use anyhow::Result;
use buffer_diff::BufferDiff;
use collections::HashSet;
use diff::BufferDiff;
use editor::{scroll::Autoscroll, Editor, EditorEvent};
use feature_flags::FeatureFlagViewExt;
use futures::StreamExt;
@@ -12,7 +12,7 @@ use gpui::{
};
use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point};
use multi_buffer::{MultiBuffer, PathKey};
use project::{git::GitState, Project, ProjectPath};
use project::{git::GitStore, Project, ProjectPath};
use theme::ActiveTheme;
use ui::prelude::*;
use util::ResultExt as _;
@@ -31,7 +31,7 @@ pub(crate) struct ProjectDiff {
editor: Entity<Editor>,
project: Entity<Project>,
git_panel: Entity<GitPanel>,
git_state: Entity<GitState>,
git_store: Entity<GitStore>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
update_needed: postage::watch::Sender<()>,
@@ -69,6 +69,7 @@ impl ProjectDiff {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
workspace.open_panel::<GitPanel>(window, cx);
Self::deploy_at(workspace, None, window, cx)
}
@@ -126,6 +127,7 @@ impl ProjectDiff {
window,
cx,
);
diff_display_editor.set_distinguish_unstaged_diff_hunks();
diff_display_editor.set_expand_all_diff_hunks(cx);
diff_display_editor.register_addon(GitPanelAddon {
git_panel: git_panel.clone(),
@@ -135,11 +137,11 @@ impl ProjectDiff {
cx.subscribe_in(&editor, window, Self::handle_editor_event)
.detach();
let git_state = project.read(cx).git_state().clone();
let git_state_subscription = cx.subscribe_in(
&git_state,
let git_store = project.read(cx).git_store().clone();
let git_store_subscription = cx.subscribe_in(
&git_store,
window,
move |this, _git_state, _event, _window, _cx| {
move |this, _git_store, _event, _window, _cx| {
*this.update_needed.borrow_mut() = ();
},
);
@@ -154,7 +156,7 @@ impl ProjectDiff {
Self {
project,
git_state: git_state.clone(),
git_store: git_store.clone(),
git_panel: git_panel.clone(),
workspace: workspace.downgrade(),
focus_handle,
@@ -163,7 +165,7 @@ impl ProjectDiff {
pending_scroll: None,
update_needed: send,
_task: worker,
_subscription: git_state_subscription,
_subscription: git_store_subscription,
}
}
@@ -173,7 +175,7 @@ impl ProjectDiff {
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(git_repo) = self.git_state.read(cx).active_repository() else {
let Some(git_repo) = self.git_store.read(cx).active_repository() else {
return;
};
let repo = git_repo.read(cx);
@@ -235,7 +237,7 @@ impl ProjectDiff {
.update(cx, |workspace, cx| {
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
git_panel.update(cx, |git_panel, cx| {
git_panel.set_focused_path(project_path.into(), window, cx)
git_panel.select_entry_by_path(project_path.into(), window, cx)
})
}
})
@@ -246,7 +248,7 @@ impl ProjectDiff {
}
fn load_buffers(&mut self, cx: &mut Context<Self>) -> Vec<Task<Result<DiffBuffer>>> {
let Some(repo) = self.git_state.read(cx).active_repository() else {
let Some(repo) = self.git_store.read(cx).active_repository() else {
self.multibuffer.update(cx, |multibuffer, cx| {
multibuffer.clear(cx);
});
@@ -317,10 +319,10 @@ impl ProjectDiff {
let snapshot = buffer.read(cx).snapshot();
let diff = diff.read(cx);
let diff_hunk_ranges = if diff.snapshot.base_text.is_none() {
let diff_hunk_ranges = if diff.base_text().is_none() {
vec![Point::zero()..snapshot.max_point()]
} else {
diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot)
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
.collect::<Vec<_>>()
};
@@ -334,6 +336,22 @@ impl ProjectDiff {
cx,
);
});
if self.multibuffer.read(cx).is_empty()
&& self
.editor
.read(cx)
.focus_handle(cx)
.contains_focused(window, cx)
{
self.focus_handle.focus(window);
} else if self.focus_handle.contains_focused(window, cx)
&& !self.multibuffer.read(cx).is_empty()
{
self.editor.update(cx, |editor, cx| {
editor.focus_handle(cx).focus(window);
editor.move_to_beginning(&Default::default(), window, cx);
});
}
if self.pending_scroll.as_ref() == Some(&path_key) {
self.scroll_to_path(path_key, window, cx);
}
@@ -364,8 +382,12 @@ impl ProjectDiff {
impl EventEmitter<EditorEvent> for ProjectDiff {}
impl Focusable for ProjectDiff {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
fn focus_handle(&self, cx: &App) -> FocusHandle {
if self.multibuffer.read(cx).is_empty() {
self.focus_handle.clone()
} else {
self.editor.focus_handle(cx)
}
}
}
@@ -406,7 +428,7 @@ impl Item for ProjectDiff {
}
fn telemetry_event_text(&self) -> Option<&'static str> {
Some("project diagnostics")
Some("Project Diff Opened")
}
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
@@ -536,22 +558,17 @@ impl Item for ProjectDiff {
impl Render for ProjectDiff {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_empty = self.multibuffer.read(cx).is_empty();
if is_empty {
div()
.bg(cx.theme().colors().editor_background)
.flex()
.items_center()
.justify_center()
.size_full()
.child(Label::new("No uncommitted changes"))
} else {
div()
.bg(cx.theme().colors().editor_background)
.flex()
.items_center()
.justify_center()
.size_full()
.child(self.editor.clone())
}
div()
.track_focus(&self.focus_handle)
.bg(cx.theme().colors().editor_background)
.flex()
.items_center()
.justify_center()
.size_full()
.when(is_empty, |el| {
el.child(Label::new("No uncommitted changes"))
})
.when(!is_empty, |el| el.child(self.editor.clone()))
}
}

View File

@@ -0,0 +1,307 @@
#![allow(unused, dead_code)]
use crate::repository_selector::RepositorySelector;
use anyhow::Result;
use git::{CommitAllChanges, CommitChanges};
use language::Buffer;
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
use ui::{prelude::*, Tooltip};
use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
use gpui::*;
use project::git::Repository;
use project::{Fs, Project};
use std::sync::Arc;
use workspace::{ModalView, Workspace};
actions!(
git,
[QuickCommitWithMessage, QuickCommitStaged, QuickCommitAll]
);
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
};
QuickCommitModal::register(workspace, window, cx)
})
.detach();
}
fn commit_message_editor(
commit_message_buffer: Option<Entity<Buffer>>,
window: &mut Window,
cx: &mut Context<'_, Editor>,
) -> Editor {
let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
Editor::new(
EditorMode::AutoHeight { max_lines: 10 },
buffer,
None,
false,
window,
cx,
)
} else {
Editor::auto_height(10, window, cx)
};
commit_editor.set_use_autoclose(false);
commit_editor.set_show_gutter(false, cx);
commit_editor.set_show_wrap_guides(false, cx);
commit_editor.set_show_indent_guides(false, cx);
commit_editor.set_placeholder_text("Enter commit message", cx);
commit_editor
}
pub struct QuickCommitModal {
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
project: Entity<Project>,
active_repository: Option<Entity<Repository>>,
repository_selector: Entity<RepositorySelector>,
commit_editor: Entity<Editor>,
width: Option<Pixels>,
commit_task: Task<Result<()>>,
commit_pending: bool,
can_commit: bool,
can_commit_all: bool,
enable_auto_coauthors: bool,
}
impl Focusable for QuickCommitModal {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl EventEmitter<DismissEvent> for QuickCommitModal {}
impl ModalView for QuickCommitModal {}
impl QuickCommitModal {
pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
workspace.register_action(|workspace, _: &QuickCommitWithMessage, window, cx| {
let project = workspace.project().clone();
let fs = workspace.app_state().fs.clone();
workspace.toggle_modal(window, cx, move |window, cx| {
QuickCommitModal::new(project, fs, window, None, cx)
});
});
}
pub fn new(
project: Entity<Project>,
fs: Arc<dyn Fs>,
window: &mut Window,
commit_message_buffer: Option<Entity<Buffer>>,
cx: &mut Context<Self>,
) -> Self {
let git_store = project.read(cx).git_store().clone();
let active_repository = project.read(cx).active_repository(cx);
let focus_handle = cx.focus_handle();
let commit_editor = cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
commit_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
});
let repository_selector = cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
Self {
focus_handle,
fs,
project,
active_repository,
repository_selector,
commit_editor,
width: None,
commit_task: Task::ready(Ok(())),
commit_pending: false,
can_commit: false,
can_commit_all: false,
enable_auto_coauthors: true,
}
}
pub fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let all_repositories = self
.project
.read(cx)
.git_store()
.read(cx)
.all_repositories();
let entry_count = self
.active_repository
.as_ref()
.map_or(0, |repo| repo.read(cx).entry_count());
let changes_string = match entry_count {
0 => "No changes".to_string(),
1 => "1 change".to_string(),
n => format!("{} changes", n),
};
div().absolute().top_0().right_0().child(
panel_icon_button("open_change_list", IconName::PanelRight)
.disabled(true)
.tooltip(Tooltip::text("Changes list coming soon!")),
)
}
pub fn render_commit_editor(
&self,
name_and_email: Option<(SharedString, SharedString)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone();
let can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx);
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let focus_handle_1 = self.focus_handle(cx).clone();
let focus_handle_2 = self.focus_handle(cx).clone();
let panel_editor_style = panel_editor_style(true, window, cx);
let commit_staged_button = panel_filled_button("Commit")
.tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(
"Commit all staged changes",
&CommitChanges,
&focus_handle,
window,
cx,
)
})
.when(!can_commit, |this| {
this.disabled(true).style(ButtonStyle::Transparent)
});
// .on_click({
// let name_and_email = name_and_email.clone();
// cx.listener(move |this, _: &ClickEvent, window, cx| {
// this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
// })
// });
let commit_all_button = panel_filled_button("Commit All")
.tooltip(move |window, cx| {
let focus_handle = focus_handle_2.clone();
Tooltip::for_action_in(
"Commit all changes, including unstaged changes",
&CommitAllChanges,
&focus_handle,
window,
cx,
)
})
.when(!can_commit, |this| {
this.disabled(true).style(ButtonStyle::Transparent)
});
// .on_click({
// let name_and_email = name_and_email.clone();
// cx.listener(move |this, _: &ClickEvent, window, cx| {
// this.commit_tracked_changes(
// &CommitAllChanges,
// name_and_email.clone(),
// window,
// cx,
// )
// })
// });
let co_author_button = panel_icon_button("add-co-author", IconName::UserGroup)
.icon_color(if self.enable_auto_coauthors {
Color::Muted
} else {
Color::Accent
})
.icon_size(IconSize::Small)
.toggle_state(self.enable_auto_coauthors)
// .on_click({
// cx.listener(move |this, _: &ClickEvent, _, cx| {
// this.toggle_auto_coauthors(cx);
// })
// })
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Toggle automatic co-authors",
None,
"Automatically adds current collaborators",
window,
cx,
)
});
panel_editor_container(window, cx)
.id("commit-editor-container")
.relative()
.w_full()
.border_t_1()
.border_color(cx.theme().colors().border)
.h(px(140.))
.bg(cx.theme().colors().editor_background)
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
window.focus(&editor_focus_handle);
}))
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
.child(div().flex_1())
.child(
h_flex()
.items_center()
.h_8()
.justify_between()
.gap_1()
.child(co_author_button)
.child(commit_all_button)
.child(commit_staged_button),
)
}
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.w_full()
.justify_between()
.child(h_flex().child("cmd+esc clear message"))
.child(
h_flex()
.child(panel_filled_button("Commit"))
.child(panel_filled_button("Commit All")),
)
}
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
}
impl Render for QuickCommitModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
v_flex()
.id("quick-commit-modal")
.key_context("QuickCommit")
.on_action(cx.listener(Self::dismiss))
.relative()
.bg(cx.theme().colors().elevated_surface_background)
.rounded(px(16.))
.border_1()
.border_color(cx.theme().colors().border)
.py_2()
.px_4()
.w(self.width.unwrap_or(px(640.)))
.h(px(450.))
.flex_1()
.overflow_hidden()
.child(self.render_header(window, cx))
.child(
v_flex()
.flex_1()
// TODO: pass name_and_email
.child(self.render_commit_editor(None, window, cx)),
)
.child(self.render_footer(window, cx))
}
}

View File

@@ -1,10 +1,10 @@
use gpui::{
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
Task, WeakEntity,
AnyElement, AnyView, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
Subscription, Task, WeakEntity,
};
use picker::{Picker, PickerDelegate};
use project::{
git::{GitState, Repository},
git::{GitStore, Repository},
Project,
};
use std::sync::Arc;
@@ -20,8 +20,8 @@ pub struct RepositorySelector {
impl RepositorySelector {
pub fn new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let git_state = project.read(cx).git_state().clone();
let all_repositories = git_state.read(cx).all_repositories();
let git_store = project.read(cx).git_store().clone();
let all_repositories = git_store.read(cx).all_repositories();
let filtered_repositories = all_repositories.clone();
let delegate = RepositorySelectorDelegate {
project: project.downgrade(),
@@ -34,10 +34,11 @@ impl RepositorySelector {
let picker = cx.new(|cx| {
Picker::nonsearchable_uniform_list(delegate, window, cx)
.max_height(Some(rems(20.).into()))
.width(rems(15.))
});
let _subscriptions =
vec![cx.subscribe_in(&git_state, window, Self::handle_project_git_event)];
vec![cx.subscribe_in(&git_store, window, Self::handle_project_git_event)];
RepositorySelector {
picker,
@@ -48,7 +49,7 @@ impl RepositorySelector {
fn handle_project_git_event(
&mut self,
git_state: &Entity<GitState>,
git_store: &Entity<GitStore>,
_event: &project::git::GitEvent,
window: &mut Window,
cx: &mut Context<Self>,
@@ -56,7 +57,7 @@ impl RepositorySelector {
// TODO handle events individually
let task = self.picker.update(cx, |this, cx| {
let query = this.query(cx);
this.delegate.repository_entries = git_state.read(cx).all_repositories();
this.delegate.repository_entries = git_store.read(cx).all_repositories();
this.delegate.update_matches(query, window, cx)
});
self.update_matches_task = Some(task);
@@ -78,20 +79,27 @@ impl Render for RepositorySelector {
}
#[derive(IntoElement)]
pub struct RepositorySelectorPopoverMenu<T>
pub struct RepositorySelectorPopoverMenu<T, TT>
where
T: PopoverTrigger,
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
repository_selector: Entity<RepositorySelector>,
trigger: T,
tooltip: TT,
handle: Option<PopoverMenuHandle<RepositorySelector>>,
}
impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T) -> Self {
impl<T, TT> RepositorySelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
pub fn new(repository_selector: Entity<RepositorySelector>, trigger: T, tooltip: TT) -> Self {
Self {
repository_selector,
trigger,
tooltip,
handle: None,
}
}
@@ -102,13 +110,17 @@ impl<T: PopoverTrigger> RepositorySelectorPopoverMenu<T> {
}
}
impl<T: PopoverTrigger> RenderOnce for RepositorySelectorPopoverMenu<T> {
impl<T, TT> RenderOnce for RepositorySelectorPopoverMenu<T, TT>
where
T: PopoverTrigger + ButtonCommon,
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let repository_selector = self.repository_selector.clone();
PopoverMenu::new("repository-switcher")
.menu(move |_window, _cx| Some(repository_selector.clone()))
.trigger(self.trigger)
.trigger_with_tooltip(self.trigger, self.tooltip)
.attach(gpui::Corner::BottomLeft)
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
}

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