Compare commits

..

50 Commits

Author SHA1 Message Date
Nate Butler
a3e04867ab Update git_ui.rs 2024-12-16 15:30:27 -05:00
Nate Butler
3abf165c1d Update git_panel.rs 2024-12-16 15:30:25 -05:00
Nate Butler
48eae645f2 Revert "Merge branch 'main' into git-panel-commit-editor"
This reverts commit 78b6cae754, reversing
changes made to 1cac4b6c00.
2024-12-16 15:29:44 -05:00
Nate Butler
78b6cae754 Merge branch 'main' into git-panel-commit-editor 2024-12-16 15:26:45 -05:00
Nate Butler
1cac4b6c00 Revert "wip"
This reverts commit aee641d79d.
2024-12-16 15:12:49 -05:00
Danilo Leal
ec741d61ed assistant2: Adjust thread history list item visuals (#21998)
Most notably, adding the `outlined` property in the `ListItem`
component.

<img width="800" alt="Screenshot 2024-12-13 at 20 35 39"
src="https://github.com/user-attachments/assets/adac4463-66f9-4b5e-b1c0-93c34f068dc4"
/>

Release Notes:

- N/A
2024-12-16 16:42:09 -03:00
Nate Butler
426f94b310 git_ui: Update todos (#22100)
`todo!()` -> `TODO`

Release Notes:

- N/A
2024-12-16 13:39:40 -05:00
Marshall Bowers
eff61ee764 assistant2: Remove WeakView<Workspace> optionality for inline assist (#22099)
This PR removes the optionality for the `WeakView<Workspace>` that we
pass to the inline assist.

This was always `Some` in practice, so it seems we don't need to have it
be an `Option`.

Release Notes:

- N/A
2024-12-16 13:26:11 -05:00
Nate Butler
aee641d79d wip 2024-12-16 13:24:55 -05:00
Marshall Bowers
caefdcd7f1 assistant2: Factor out ContextStrip (#22096)
This PR factors a `ContextStrip` view out of the `MessageEditor` so that
we can use it in other places.

Release Notes:

- N/A
2024-12-16 12:45:01 -05:00
Kirill Bulatov
ff2ad63037 Allow splitting terminal items in the central pane group (#22088)
Follow-up of https://github.com/zed-industries/zed/pull/22004
Closes https://github.com/zed-industries/zed/issues/22078

Release Notes:

- Fixed splitting terminal items in the center
2024-12-16 19:23:01 +02:00
Marshall Bowers
88f7942f11 assistant2: Add support for referencing other threads as context (#22092)
This PR adds the ability to reference other threads as context:

<img width="1159" alt="Screenshot 2024-12-16 at 11 29 54 AM"
src="https://github.com/user-attachments/assets/bb8a24ff-56d3-4406-ab8c-6657e65d8c70"
/>

<img width="1159" alt="Screenshot 2024-12-16 at 11 29 35 AM"
src="https://github.com/user-attachments/assets/7a02ebda-a2f5-40e9-9dd4-1bb029cb1c43"
/>


Release Notes:

- N/A
2024-12-16 11:50:57 -05:00
uncenter
188c55c8a6 docs: Fix context_servers key for example extension manifest (#22079)
Pretty sure this isn't meant to be kebab-case.

Release Notes:

- N/A
2024-12-16 11:00:26 -05:00
Danilo Leal
2562b488b1 Refine interaction in foldable multibuffer header (#22084)
- Ensuring that the fold button is big enough to avoid clicking on the
header as a whole (and then moving to the actual file)
- Adding tooltips to the fold button
- Refining the container structure so that the tooltip for the folder
button and the header click don't overlap
- Adding keybindings to tooltips


https://github.com/user-attachments/assets/82284b59-3025-4d6d-b916-ad4d1ecdb119

Release Notes:

- N/A
2024-12-16 12:54:06 -03:00
Kirill Bulatov
bc113e4b51 Move task centering code closer to user input (#22082)
Follow-up of https://github.com/zed-industries/zed/pull/22004 

* Reuse center terminals for tasks, when requested
* Extend task templates with `RevealTarget`, moving it from
`TaskSpawnTarget` into the core library
* Use `reveal_target` instead of `target` to avoid misinterpretations in
the task template context
* Do not expose `SpawnInTerminal` to user interface, avoid it
implementing `Serialize` and `Deserialize`
* Remove `NewCenterTask` action, extending `task::Spawn` interface
instead
* Do not require any extra unrelated parameters during task resolution,
instead, use task overrides on the resolved tasks on the modal side
* Add keybindings for opening the task modal in the
`RevealTarget::Center` mode

Release Notes:

- N/A
2024-12-16 16:15:58 +02:00
Thorsten Ball
ea012075fc Trigger completions even if inline completion is visible (#22077)
This is related to #22069 and #21858: before both of these PRs, we would
only ever show inline completions OR completions, never both at the same
time.

Now we show both at the same, but we still had this piece of logic here,
that prevented non-inline completions from showing up if there was
already an inline completion.

With this change, it's possible to get LSP completions without having to
dismiss inline completions before.

Release Notes:

- Inline completions (Copilot, Supermaven, ...) don't stop other
completions from showing up anymore. Both can now be visible at the same
time.

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-12-16 15:10:33 +01:00
Helge Mahrt
ce727fbc07 workspace: Fix doc comments (#22063)
Happened to see that the doc comments here were not correct while
implementing something else.

Release Notes:

- N/A
2024-12-16 08:27:14 -05:00
tims
62b3acee5f Project panel: Deselect entries on remaining blank space click + Remove hover color for selected entries (#22073)
Closes #22072

Clicking on the remaining space now allows a single click to deselect
all selected items. Check the issue for a preview of the current state
and how it works in VSCode.

Bonus: I found the hover color on selected items to be distracting. When
I have many entries selected and hover over them, it becomes hard to
tell if a particular entry is selected while the mouse pointer is on it.
This PR removes hover coloring for selected entries, mimicking how
VSCode handles it.

This PR:
<img
src="https://github.com/user-attachments/assets/9c4b20fc-df93-4868-b7fe-4045433e85b2"
alt="zed" width="450px" />

Release Notes:

- Clicking on empty space in the Project Panel now deselects all
selected items.
2024-12-16 14:05:54 +01:00
Thorsten Ball
38c0aa303e vim: Don't dismiss inline completion when switching to normal mode (#22075)
I'm not sure about this yet.

On one hand: it's nice that the completion doesn't just disappear when I
hit escape because I was typing and in the flow.

On the other hand: no other inline completion provider keeps the
suggestion when leaving insert mode.

I'm going to merge this so we can get it into nightly and try it out for
the next couple of days. cc @ConradIrwin

Release Notes:

- vim: Do not dismiss inline completions when leaving insert/replace
mode with `<esc>`.
2024-12-16 11:23:20 +01:00
Bennet Bo Fenner
040d9ae222 zeta: Prevent diff popover from going offscreen (#22070)
https://github.com/user-attachments/assets/4ce806f1-d790-41d0-9825-e68055281446

Release Notes:

- N/A
2024-12-16 11:22:20 +01:00
Thorsten Ball
d135ec2b73 completions: Restore tab behavior when both visible (#22069)
This reverts part of #21858 by changing how `tab` works again:

- If both, completions and inline completions, are visible, then `tab`
accepts the completion and `shif-tab` the inline completion.
- If only one of them is shown, then `tab` accepts it.

I'm not a fan of this solution, but I think it's a short-term fix that
avoids breaking people's `tab` muscle memory.

Release Notes:

- (These release notes invalidate the release notes contained in:
https://github.com/zed-industries/zed/pull/21858)
- Changed how inline completions (Copilot, Supermaven, ...) and normal
completions (from language servers) interact. Zed will now also show
inline completions when the completion menu is visible. The user can
accept the inline completion with `<shift-tab>` and the active entry in
the completion menu with `<tab>`.
2024-12-16 11:02:54 +01:00
Michael Sloan
a94afbc062 Switch from Arc/RwLock to Rc/RefCell for CodeContextMenu (#22035)
`CodeContextMenu` is always accessed on one thread, so only `Rc`s and
`Rc<RefCell<_>>` are needed. There should be tiny performance benefits
from this. The main benefit of this is that when seeing code accessing a
`RwLock` it would be reasonable to wonder whether it will block. The
only potential downside is the potential for panics due to overlapping
borrows of the RefCells. I think this is an acceptable risk because most
errors of this nature will be local or will be caught by clippy via the
check for holding a RefCell reference over an `await`.

Release Notes:

- N/A
2024-12-16 01:50:21 -07:00
Michael Sloan
7b721efe2c Stop mutating completion match state + reject fuzzy match text change (#22061)
This fixes #21837, where CompletionsMenu fuzzy match positions were
desynchronized from completion label text. The solution is to not mutate
`match_candidates` and instead offset the highlight positions in the
rendering code.

This solution requires that the fuzzy match text not change on
completion resolution. This is a property we want anyway, since fuzzy
match text changing means items unexpectedly changing position in the
menu.

What happened:

* #21521 updated completion resolution to modify labels on resolution.

- This interacted poorly with the code
[here](341e65e122/crates/editor/src/code_context_menus.rs (L604)),
where the fuzzy match results are updated to include the full label, and
the fuzzy match result positions are offset to be in the correct place.
The fuzzy mach positions were now invalid because they were based on the
old text.

* #21705 caused completion resolution to occur more frequently. Before
this only the selected item was being resolved. This caused the panic
due to invalid positions to happen much more frequently.

Closes #21837

Release Notes:

- N/A
2024-12-16 01:21:26 -07:00
Peter Tripp
18b6d142c3 docs: Suggest installing PHP to use PHP (#22058) 2024-12-16 01:21:20 -05:00
Michael Sloan
53c9af3e61 Add and use CodeLabel::filter_text() (#22054)
Release Notes:

- N/A
2024-12-15 22:24:41 -07:00
Kirill Bulatov
af50261ae2 Allow folding buffers inside multi buffers (#22046)
Closes https://github.com/zed-industries/zed/issues/4925


https://github.com/user-attachments/assets/e7b87375-893f-41ae-a2d9-d501499e40d1


Allows to fold any buffer inside multi buffers, either by clicking the
chevron icon on the header, or by using
`editor::Fold`/`editor::UnfoldLines`/`editor::ToggleFold`/`editor::FoldAll`
and `editor::UnfoldAll` actions inside the multi buffer (those were noop
there before).

Every fold has a fake line inside it, so it's possible to navigate into
that via the keyboard and unfold it with the corresponding editor
action.

The state is synchronized with the outline panel state: any fold inside
multi buffer folds the corresponding file entry; any file entry fold
inside the outline panel folds the corresponding buffer inside the multi
buffer, any directory fold inside the outline panel folds the
corresponding buffers inside the multi buffer for each nested file entry
in the panel.


Release Notes:

- Added a possibility to fold buffers inside multi buffers

---------

Co-authored-by: Antonio Scandurra <antonio@zed.dev>
Co-authored-by: Max Brunsfeld <max@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>
2024-12-16 00:32:07 +02:00
Michael Sloan
f64fcedabb Fix fuzzy string match invariant check (#22032)
Version in #21983 only handled out of range issues rather than utf-8
boundary issues (thanks to @s3bba for pointing this out)

Release Notes:

- N/A
2024-12-15 01:15:22 -07:00
Michael Sloan
7e6233d70f Remove an unnecessary clone in get_permalink_to_line (#22027)
Release Notes:

- N/A
2024-12-14 15:52:39 -07:00
Michael Sloan
d459f010b6 Rename GitRepository.path() to GitRepository.dot_git_dir() (#22026)
Release Notes:

- N/A
2024-12-14 15:30:56 -07:00
Michael Sloan
25970650a7 Improve StringMatchCandidate::new interface (#22011)
Release Notes:

- N/A
2024-12-14 13:35:36 -07:00
Kirill Bulatov
9daa426e93 Fix terminal pane tabs arrangement and closing (#22013)
* Fixes the inability to drag and drop terminal tabs to reorder them;
fixed incorrect terminal tab move on drag and drop into existing pane
(follow-up of https://github.com/zed-industries/zed/pull/21238)
* Fixes save dialogue appearing when on closing terminal tabs with
running tasks (follow-up of
https://github.com/zed-industries/zed/pull/21374)

Release Notes:

- Fixed terminal pane tabs arrangement and closing
2024-12-14 16:13:04 +02:00
Michael Sloan
6e1cc5dad3 Remove Task::get_ready method I added, which is unusable in practice (#22012)
Does seem like such a mechanism should be possible, but not yet sure how
to define it.

Release Notes:

- N/A
2024-12-14 03:21:41 -07:00
Michael Sloan
c5fe6ef100 Hide the implementation of Task (#22009)
The `Option<T>` within `Ready` is confusing and using `None` for it can
cause crashes. There was actually one instance of this!

Release Notes:

- N/A
2024-12-14 02:52:22 -07:00
Kirill Bulatov
1ac60289fe Fix the compilation (#22010)
https://github.com/zed-industries/zed/pull/21706 was merged after
https://github.com/zed-industries/zed/pull/22004 and the CI missed that.

Release Notes:

- N/A
2024-12-14 10:02:46 +02:00
IViktorov
cbc226597c Prefer project (worktree) tasks to language/global tasks in task::Spawn (#21706)
`Inventory::list_tasks()` in `project` crate now is ordered by task
types. Worktree tasks comes first, language tasks second and global
tasks last.
That leads to `spawn_task_with_name()` from `task_ui` crate will find
worktree task first, so it's possible to override global tasks at
project level.

* `Inventory::templates_from_settings()` splitted to
`Inventory::global_templates_from_settings()` and
`Inventory::worktree_templates_from_settings()`.

* In tests function `list_tasks()` renamed to
`list_tasks_sorted_by_last_used()`, because it call's
`Inventory::used_and_current_resolved_tasks()`. Also added
`list_tasks()` which calls `Inventory::list_tasks()`.

Closes #20987 

Release Notes:

- Fix task::Spawn to search for task name in project tasks first.
2024-12-14 09:30:48 +02:00
Aaron Feickert
ff2d20780f Add setting for hover delay (#22006)
This PR adds a new `hover_popover_delay` setting that allows the user to
specify how long to wait before showing informational hover boxes. It
defaults to the existing delay.

Release Notes:

- Added a setting to control the delay for informational hover boxes
2024-12-13 20:34:16 -08:00
Ulysse Buonomo
cd5d8b4173 settings: Add max tabs option (#18933)
Add a `max_tabs` option to the settings that ensure no more than this
amount of tabs are open in a pane. If set to `null`, there is no limit.

Closes #4784

Release Notes:

- Added a `max_tabs` option to cap the maximum number of open tabs.
2024-12-13 20:32:55 -08:00
Wes Higbee
0be7cf8ea8 Show restart transformation button after successful inline assist (#20439)
When using inline assist, after successfully generating a transformation
it's not possible to generate a new transformation. Currently, you have
to modify the prompt (i.e. add a `<SPACE>` and hit `<ENTER>`) to
regenerate.

So, I changed the restart button to be visible after a successful
transformation. And in that case I map it to a different keyboard
shortcut because `menu::Confirm` is mapped to accept the current
suggestion.

Now, I can invoke a series of transforms back to back until I get what I
want!

It might also be desired to keep the accept button visible after
modifying the prompt (before submitting it). In that case we'll need to
remap accept to an alternate key (perhaps the same alt-shift-enter I am
using for restart. That wouldn't be too insane to remember. But maybe
someone has a better idea.

I don't care what the shortcut is, I just want the ability to regenerate
without adding/deleting spaces.

## Before

**Two choices** after a suggestions is presented. Also, a great example
of why I would want to regenerate the suggestion, it left some tokens
`<rewrite_this>`!

![CleanShot 2024-12-13 at 00 34
09](https://github.com/user-attachments/assets/3c1786ca-8ec5-48e2-b3dd-64de36e61f6a)

## After

**Three choices** after a suggestion is presented, the circular icon is
for regenerate, just like you see if you modify the prompt text.
![CleanShot 2024-12-13 at 00 37
58](https://github.com/user-attachments/assets/ceda300f-0851-48bf-ad3a-be44308c734e)


## Release Notes:

- Added Restart Button to Inline Assistant When Prompt Is Unchanged

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
2024-12-13 20:31:54 -08:00
Mikayla Maki
4f96706161 Add the ability for tasks to target the center pane (#22004)
Closes #20060
Closes #20720
Closes #19873
Closes #9445

Release Notes:

- Fixed a bug where tasks would be spawned with their working directory
set to a file in some cases
- Added the ability to spawn tasks in the center pane, when spawning
from a keybinding:

```json5
[
  {
    // Assuming you have a task labeled "echo hello"
    "ctrl--": [
      "task::Spawn",
      { "task_name": "echo hello", "target": "center" }
    ]
  }
]
```
2024-12-13 19:39:46 -08:00
Nate Butler
4f439ae35f Update git_panel.rs 2024-12-13 21:19:16 -05:00
Brian Tan
85c3aec6e7 vim: Maintain block cursor for navigating/non-modifying operators (#21502)
The cursor shape now only changes to underline for operators that modify
text (like: delete, change, yank) while maintaining block shape for
navigation operators (like: find, till).

This better matches Vim/Nvim's behavior where the cursor only changes
shape when an operator that will modify text is pending.

Release Notes:

- vim: Improved cursor shape behavior to better match Vim
2024-12-13 19:06:18 -07:00
Nate Butler
c2eea3a474 start on commit editor 2024-12-13 21:06:18 -05:00
Nate Butler
695f06c020 Checkpoint 2024-12-13 20:18:09 -05:00
Marshall Bowers
901dbedf8d assistant2: Refine context pickers (#21996)
This PR adds some visual refinements to the context pickers in
Assistant2.

<img width="1159" alt="Screenshot 2024-12-13 at 5 11 24 PM"
src="https://github.com/user-attachments/assets/f85ce87f-6800-4fc2-8a10-8ec3232d30e9"
/>

<img width="1159" alt="Screenshot 2024-12-13 at 5 11 31 PM"
src="https://github.com/user-attachments/assets/9b13c76d-cb7c-4441-a855-1ec4de685e0c"
/>

Release Notes:

- N/A
2024-12-13 17:26:10 -05:00
Piotr Osiewicz
99dc85e6ec Format code/fix broken CI build (#21997)
In #21981 the CI didn't run for whatever reason. I've merged based off
of CI state alone and that led to CI breaking on main.

Release Notes:

- N/A
2024-12-13 17:25:54 -05:00
Peter Tripp
735849e201 Improve Editor::DuplicateSelection (#21976)
Improves the new `Editor::DuplicateSelection` @CharlesChen0823 added in
https://github.com/zed-industries/zed/pull/21154.

- Merge `duplicate_selection` and `duplicate_line` into single function.
- Add keyboard shortcuts to JetBrains and SublimeText keymaps.
- If the selection is empty (e.g. just a cursor) make
`Editor::DuplicateSelection` fallback to being the same as
`Editor::DuplicateLineDown`.
- Tested with multiple cursors and for multiple selections.

| Editor      | Action              | macOS       | Linux        |
| ----------- | ------------------- | ----------- | ------------ |
| VSCode      | Duplicate Selection |             |              |
| JetBrains   | Duplicate Selection | cmd-d       | ctrl-d       |
| XCode       | Duplicate           | cmd-d       | N/A          |
| SublimeText | duplicate_line      | cmd-shift-d | ctrl-shift-d |

This matches behavior of the `duplicate` functionality in all other
major editors, with one exception: other editors change the selection so
that the newly duplicated object, current Zed behavior leaves the
original selection unchanged (TODO?)
2024-12-13 16:32:33 -05:00
Silvano Cerza
06edcd18be Fix running Python commands that include paths with spaces (#21981)
This PR fixes running Python commands that include paths with spaces by
wrapping python commands and their arguments in quotation marks.

I fixed this only in Python as I noticed this while trying to run
`pytest` in Zed.

Probably this is not the best approach as it doesn't fix other languages
too, though I don't know enough about the codebase to fix it like that.
I'm not even sure if it's actually feasible right now.

I didn't add tests for this either as I couldn't really understand how
to easily to that, I tried to look at other languages but couldn't find
one that tests their `ContextProvider` directly.

Release Notes:

- Fix running Python commands that include paths with spaces

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
2024-12-13 22:31:08 +01:00
Peter Tripp
e53c1a8ee3 ci: GitHub Actions Runner Cleanup (#21993)
- Only run top issues on zed-industries/zed
- Move Nightly ARM build to BuildJet
2024-12-13 16:30:53 -05:00
Joseph T. Lyons
421974f923 Use consistent casing for provider name in telemetry (#21991)
Release Notes:

- N/A
2024-12-13 16:13:47 -05:00
Marshall Bowers
c57cc35b03 assistant2: Add ability to fetch URLs as context (#21988)
This PR adds the ability to fetch URLs as context in Assistant2.

In the picker we use the search area as an input for the user to enter
the URL they wish to fetch:

<img width="1159" alt="Screenshot 2024-12-13 at 2 45 41 PM"
src="https://github.com/user-attachments/assets/b3b20648-2c22-4509-b592-d0291d25b202"
/>

<img width="1159" alt="Screenshot 2024-12-13 at 2 45 47 PM"
src="https://github.com/user-attachments/assets/7e6bab2d-2731-467f-9781-130c6e4ea5cf"
/>

Release Notes:

- N/A
2024-12-13 15:03:55 -05:00
31 changed files with 625 additions and 317 deletions

View File

@@ -8,7 +8,7 @@ on:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries'
if: github.repository == 'zed-industries/zed'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv

View File

@@ -8,7 +8,7 @@ on:
jobs:
update_top_ranking_issues:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries'
if: github.repository == 'zed-industries/zed'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up uv

View File

@@ -140,7 +140,7 @@ jobs:
name: Create a Linux *.tar.gz bundle for ARM
if: github.repository_owner == 'zed-industries'
runs-on:
- hosted-linux-arm-1
- buildjet-16vcpu-ubuntu-2204-arm
needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}

5
Cargo.lock generated
View File

@@ -472,6 +472,8 @@ dependencies = [
"fuzzy",
"gpui",
"handlebars 4.5.0",
"html_to_markdown",
"http_client",
"indoc",
"language",
"language_model",
@@ -5180,14 +5182,17 @@ dependencies = [
"anyhow",
"collections",
"db",
"editor",
"git",
"gpui",
"language",
"project",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"theme",
"ui",
"util",
"windows 0.58.0",

View File

@@ -12,7 +12,7 @@
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-d": "editor::DuplicateLineDown",
"ctrl-d": "editor::DuplicateSelection",
"ctrl-y": "editor::DeleteLine",
"ctrl-m": "editor::ScrollCursorCenter",
"ctrl-pagedown": "editor::MovePageDown",

View File

@@ -15,7 +15,7 @@
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
"ctrl-shift-d": "editor::DuplicateLineDown",
"ctrl-shift-d": "editor::DuplicateSelection",
"alt-f3": "editor::SelectAllMatches", // find_all_under
"f12": "editor::GoToDefinition",
"ctrl-f12": "editor::GoToDefinitionSplit",

View File

@@ -11,7 +11,7 @@
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"cmd-d": "editor::DuplicateLineDown",
"cmd-d": "editor::DuplicateSelection",
"cmd-backspace": "editor::DeleteLine",
"cmd-pagedown": "editor::MovePageDown",
"cmd-pageup": "editor::MovePageUp",

View File

@@ -18,7 +18,7 @@
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
"cmd-shift-l": "editor::SplitSelectionIntoLines",
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
"cmd-shift-d": "editor::DuplicateLineDown",
"cmd-shift-d": "editor::DuplicateSelection",
"ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under
"shift-f12": "editor::FindAllReferences",
"alt-cmd-down": "editor::GoToDefinition",

View File

@@ -56,7 +56,7 @@ use terminal_view::terminal_panel::TerminalPanel;
use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings;
use ui::{
prelude::*, text_for_action, ToggleWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
};
use util::{RangeExt, ResultExt};
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
@@ -2129,7 +2129,7 @@ impl PromptEditor {
.child(
h_flex()
.justify_between()
.child(ToggleWithLabel::new(
.child(CheckboxWithLabel::new(
"dont-show-again",
Label::new("Don't show again"),
if dismissed_rate_limit_notice() {

View File

@@ -31,6 +31,8 @@ futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
handlebars.workspace = true
html_to_markdown.workspace = true
http_client.workspace = true
language.workspace = true
language_model.workspace = true
language_model_selector.workspace = true

View File

@@ -23,4 +23,5 @@ pub struct Context {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ContextKind {
File,
FetchedUrl,
}

View File

@@ -1,3 +1,4 @@
mod fetch_context_picker;
mod file_context_picker;
use std::sync::Arc;
@@ -11,6 +12,7 @@ use ui::{prelude::*, ListItem, ListItemSpacing, Tooltip};
use util::ResultExt;
use workspace::Workspace;
use crate::context_picker::fetch_context_picker::FetchContextPicker;
use crate::context_picker::file_context_picker::FileContextPicker;
use crate::message_editor::MessageEditor;
@@ -18,6 +20,7 @@ use crate::message_editor::MessageEditor;
enum ContextPickerMode {
Default,
File(View<FileContextPicker>),
Fetch(View<FetchContextPicker>),
}
pub(super) struct ContextPicker {
@@ -47,7 +50,7 @@ impl ContextPicker {
icon: IconName::File,
},
ContextPickerEntry {
name: "web".into(),
name: "fetch".into(),
description: "Fetch content from URL".into(),
icon: IconName::Globe,
},
@@ -77,16 +80,21 @@ impl FocusableView for ContextPicker {
match &self.mode {
ContextPickerMode::Default => self.picker.focus_handle(cx),
ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
}
}
}
impl Render for ContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex().min_w(px(400.)).map(|parent| match &self.mode {
ContextPickerMode::Default => parent.child(self.picker.clone()),
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
})
v_flex()
.w(px(400.))
.min_w(px(400.))
.map(|parent| match &self.mode {
ContextPickerMode::Default => parent.child(self.picker.clone()),
ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
})
}
}
@@ -144,6 +152,16 @@ impl PickerDelegate for ContextPickerDelegate {
)
}));
}
"fetch" => {
this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
FetchContextPicker::new(
self.context_picker.clone(),
self.workspace.clone(),
self.message_editor.clone(),
cx,
)
}));
}
_ => {}
}
@@ -157,7 +175,7 @@ impl PickerDelegate for ContextPickerDelegate {
self.context_picker
.update(cx, |this, cx| match this.mode {
ContextPickerMode::Default => cx.emit(DismissEvent),
ContextPickerMode::File(_) => {}
ContextPickerMode::File(_) | ContextPickerMode::Fetch(_) => {}
})
.log_err();
}

View File

@@ -0,0 +1,218 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use anyhow::{bail, Context as _, Result};
use futures::AsyncReadExt as _;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClientWithUrl};
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, ListItemSpacing, ViewContext};
use workspace::Workspace;
use crate::context::ContextKind;
use crate::context_picker::ContextPicker;
use crate::message_editor::MessageEditor;
pub struct FetchContextPicker {
picker: View<Picker<FetchContextPickerDelegate>>,
}
impl FetchContextPicker {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
message_editor: WeakView<MessageEditor>,
cx: &mut ViewContext<Self>,
) -> Self {
let delegate = FetchContextPickerDelegate::new(context_picker, workspace, message_editor);
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
}
}
impl FocusableView for FetchContextPicker {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl Render for FetchContextPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
self.picker.clone()
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
enum ContentType {
Html,
Plaintext,
Json,
}
pub struct FetchContextPickerDelegate {
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
message_editor: WeakView<MessageEditor>,
url: String,
}
impl FetchContextPickerDelegate {
pub fn new(
context_picker: WeakView<ContextPicker>,
workspace: WeakView<Workspace>,
message_editor: WeakView<MessageEditor>,
) -> Self {
FetchContextPickerDelegate {
context_picker,
workspace,
message_editor,
url: String::new(),
}
}
async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {
let mut url = url.to_owned();
if !url.starts_with("https://") && !url.starts_with("http://") {
url = format!("https://{url}");
}
let mut response = http_client.get(&url, AsyncBody::default(), true).await?;
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading response body")?;
if response.status().is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!(
"status error {}, response: {text:?}",
response.status().as_u16()
);
}
let Some(content_type) = response.headers().get("content-type") else {
bail!("missing Content-Type header");
};
let content_type = content_type
.to_str()
.context("invalid Content-Type header")?;
let content_type = match content_type {
"text/html" => ContentType::Html,
"text/plain" => ContentType::Plaintext,
"application/json" => ContentType::Json,
_ => ContentType::Html,
};
match content_type {
ContentType::Html => {
let mut handlers: Vec<TagHandler> = vec![
Rc::new(RefCell::new(markdown::WebpageChromeRemover)),
Rc::new(RefCell::new(markdown::ParagraphHandler)),
Rc::new(RefCell::new(markdown::HeadingHandler)),
Rc::new(RefCell::new(markdown::ListHandler)),
Rc::new(RefCell::new(markdown::TableHandler::new())),
Rc::new(RefCell::new(markdown::StyledTextHandler)),
];
if url.contains("wikipedia.org") {
use html_to_markdown::structure::wikipedia;
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaChromeRemover)));
handlers.push(Rc::new(RefCell::new(wikipedia::WikipediaInfoboxHandler)));
handlers.push(Rc::new(
RefCell::new(wikipedia::WikipediaCodeHandler::new()),
));
} else {
handlers.push(Rc::new(RefCell::new(markdown::CodeHandler)));
}
convert_html_to_markdown(&body[..], &mut handlers)
}
ContentType::Plaintext => Ok(std::str::from_utf8(&body)?.to_owned()),
ContentType::Json => {
let json: serde_json::Value = serde_json::from_slice(&body)?;
Ok(format!(
"```json\n{}\n```",
serde_json::to_string_pretty(&json)?
))
}
}
}
}
impl PickerDelegate for FetchContextPickerDelegate {
type ListItem = ListItem;
fn match_count(&self) -> usize {
1
}
fn selected_index(&self) -> usize {
0
}
fn set_selected_index(&mut self, _ix: usize, _cx: &mut ViewContext<Picker<Self>>) {}
fn placeholder_text(&self, _cx: &mut ui::WindowContext) -> Arc<str> {
"Enter a URL…".into()
}
fn update_matches(&mut self, query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
self.url = query;
Task::ready(())
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let http_client = workspace.read(cx).client().http_client().clone();
let url = self.url.clone();
cx.spawn(|this, mut cx| async move {
let text = Self::build_message(http_client, &url).await?;
this.update(&mut cx, |this, cx| {
this.delegate
.message_editor
.update(cx, |message_editor, _cx| {
message_editor.insert_context(ContextKind::FetchedUrl, url, text);
})
})??;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
self.context_picker
.update(cx, |this, cx| {
this.reset_mode();
cx.emit(DismissEvent);
})
.ok();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> {
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(self.url.clone()),
)
}
}

View File

@@ -245,7 +245,7 @@ impl PickerDelegate for FileContextPickerDelegate {
this.reset_mode();
cx.emit(DismissEvent);
})
.log_err();
.ok();
}
fn render_match(

View File

@@ -53,7 +53,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings;
use ui::{prelude::*, ToggleWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip};
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip};
use util::{RangeExt, ResultExt};
use workspace::{dock::Panel, ShowConfiguration};
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
@@ -2061,7 +2061,7 @@ impl PromptEditor {
.child(
h_flex()
.justify_between()
.child(ToggleWithLabel::new(
.child(CheckboxWithLabel::new(
"dont-show-again",
Label::new("Don't show again"),
if dismissed_rate_limit_notice() {

View File

@@ -7,7 +7,7 @@ use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopove
use settings::Settings;
use theme::ThemeSettings;
use ui::{
prelude::*, ButtonLike, ToggleWithLabel, ElevationIndex, IconButtonShape, KeyBinding,
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, IconButtonShape, KeyBinding,
PopoverMenu, PopoverMenuHandle, Tooltip,
};
use workspace::Workspace;
@@ -262,7 +262,7 @@ impl Render for MessageEditor {
.child(
h_flex()
.justify_between()
.child(h_flex().gap_2().child(ToggleWithLabel::new(
.child(h_flex().gap_2().child(CheckboxWithLabel::new(
"use-tools",
Label::new("Tools"),
self.use_tools.into(),

View File

@@ -193,12 +193,19 @@ impl Thread {
if let Some(context) = self.context_for_message(message.id) {
let mut file_context = String::new();
let mut fetch_context = String::new();
for context in context.iter() {
match context.kind {
ContextKind::File => {
file_context.push_str(&context.text);
file_context.push_str("\n");
file_context.push('\n');
}
ContextKind::FetchedUrl => {
fetch_context.push_str(&context.name);
fetch_context.push('\n');
fetch_context.push_str(&context.text);
fetch_context.push('\n');
}
}
}
@@ -209,6 +216,11 @@ impl Thread {
context_text.push_str(&file_context);
}
if !fetch_context.is_empty() {
context_text.push_str("The following fetched results are available\n");
context_text.push_str(&fetch_context);
}
request_message
.content
.push(MessageContent::Text(context_text))

View File

@@ -11,7 +11,7 @@ use gpui::{
};
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::{prelude::*, Avatar, ToggleWithLabel, ContextMenu, ListItem, ListItemSpacing};
use ui::{prelude::*, Avatar, CheckboxWithLabel, ContextMenu, ListItem, ListItemSpacing};
use util::TryFutureExt;
use workspace::{notifications::DetachAndPromptErr, ModalView};
@@ -155,7 +155,7 @@ impl Render for ChannelModal {
.h(rems_from_px(22.))
.justify_between()
.line_height(rems(1.25))
.child(ToggleWithLabel::new(
.child(CheckboxWithLabel::new(
"is-public",
Label::new("Public").size(LabelSize::Small),
if visibility == ChannelVisibility::Public {

View File

@@ -6135,29 +6135,7 @@ impl Editor {
});
}
pub fn duplicate_selection(&mut self, _: &DuplicateSelection, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let selections = self.selections.all::<Point>(cx);
let mut edits = Vec::new();
for selection in selections.iter() {
let start = selection.start;
let end = selection.end;
let text = buffer.text_for_range(start..end).collect::<String>();
edits.push((selection.end..selection.end, text));
}
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.request_autoscroll(Autoscroll::fit(), cx);
});
}
pub fn duplicate_line(&mut self, upwards: bool, cx: &mut ViewContext<Self>) {
pub fn duplicate(&mut self, upwards: bool, whole_lines: bool, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let selections = self.selections.all::<Point>(cx);
@@ -6165,36 +6143,44 @@ impl Editor {
let mut edits = Vec::new();
let mut selections_iter = selections.iter().peekable();
while let Some(selection) = selections_iter.next() {
// Avoid duplicating the same lines twice.
let mut rows = selection.spanned_rows(false, &display_map);
while let Some(next_selection) = selections_iter.peek() {
let next_rows = next_selection.spanned_rows(false, &display_map);
if next_rows.start < rows.end {
rows.end = next_rows.end;
selections_iter.next().unwrap();
} else {
break;
// duplicate line-wise
if whole_lines || selection.start == selection.end {
// Avoid duplicating the same lines twice.
while let Some(next_selection) = selections_iter.peek() {
let next_rows = next_selection.spanned_rows(false, &display_map);
if next_rows.start < rows.end {
rows.end = next_rows.end;
selections_iter.next().unwrap();
} else {
break;
}
}
}
// Copy the text from the selected row region and splice it either at the start
// or end of the region.
let start = Point::new(rows.start.0, 0);
let end = Point::new(
rows.end.previous_row().0,
buffer.line_len(rows.end.previous_row()),
);
let text = buffer
.text_for_range(start..end)
.chain(Some("\n"))
.collect::<String>();
let insert_location = if upwards {
Point::new(rows.end.0, 0)
// Copy the text from the selected row region and splice it either at the start
// or end of the region.
let start = Point::new(rows.start.0, 0);
let end = Point::new(
rows.end.previous_row().0,
buffer.line_len(rows.end.previous_row()),
);
let text = buffer
.text_for_range(start..end)
.chain(Some("\n"))
.collect::<String>();
let insert_location = if upwards {
Point::new(rows.end.0, 0)
} else {
start
};
edits.push((insert_location..insert_location, text));
} else {
start
};
edits.push((insert_location..insert_location, text));
// duplicate character-wise
let start = selection.start;
let end = selection.end;
let text = buffer.text_for_range(start..end).collect::<String>();
edits.push((selection.end..selection.end, text));
}
}
self.transact(cx, |this, cx| {
@@ -6207,11 +6193,15 @@ impl Editor {
}
pub fn duplicate_line_up(&mut self, _: &DuplicateLineUp, cx: &mut ViewContext<Self>) {
self.duplicate_line(true, cx);
self.duplicate(true, true, cx);
}
pub fn duplicate_line_down(&mut self, _: &DuplicateLineDown, cx: &mut ViewContext<Self>) {
self.duplicate_line(false, cx);
self.duplicate(false, true, cx);
}
pub fn duplicate_selection(&mut self, _: &DuplicateSelection, cx: &mut ViewContext<Self>) {
self.duplicate(false, false, cx);
}
pub fn move_line_up(&mut self, _: &MoveLineUp, cx: &mut ViewContext<Self>) {

View File

@@ -5,7 +5,7 @@ use project::project_settings::{InlineBlameSettings, ProjectSettings};
use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, ThemeSettings};
use ui::{
prelude::*, ToggleWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
SettingsGroup,
};
@@ -258,7 +258,7 @@ impl RenderOnce for BufferFontLigaturesControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let value = Self::read(cx);
ToggleWithLabel::new(
CheckboxWithLabel::new(
"buffer-font-ligatures",
Label::new(self.name()),
value.into(),
@@ -311,7 +311,7 @@ impl RenderOnce for InlineGitBlameControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let value = Self::read(cx);
ToggleWithLabel::new(
CheckboxWithLabel::new(
"inline-git-blame",
Label::new(self.name()),
value.into(),
@@ -364,7 +364,7 @@ impl RenderOnce for LineNumbersControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let value = Self::read(cx);
ToggleWithLabel::new(
CheckboxWithLabel::new(
"line-numbers",
Label::new(self.name()),
value.into(),

View File

@@ -23,7 +23,7 @@ use project::DirectoryLister;
use release_channel::ReleaseChannel;
use settings::Settings;
use theme::ThemeSettings;
use ui::{prelude::*, ToggleWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use ui::{prelude::*, CheckboxWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use vim_mode_setting::VimModeSetting;
use workspace::{
item::{Item, ItemEvent},
@@ -994,7 +994,7 @@ impl ExtensionsPage {
.docs_url("https://zed.dev/docs/git#git-integrations"),
Feature::Vim => FeatureUpsell::new(telemetry, "Vim support is built-in to Zed!")
.docs_url("https://zed.dev/docs/vim")
.child(ToggleWithLabel::new(
.child(CheckboxWithLabel::new(
"enable-vim",
Label::new("Enable vim mode"),
if VimModeSetting::get_global(cx).0 {

View File

@@ -14,19 +14,22 @@ path = "src/git_ui.rs"
[dependencies]
anyhow.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
git.workspace = true
gpui.workspace = true
language.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
git.workspace = true
collections.workspace = true
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@@ -1,4 +1,7 @@
use anyhow::Context;
use collections::HashMap;
use editor::Editor;
use language::Buffer;
use std::{
cell::OnceCell,
collections::HashSet,
@@ -8,6 +11,7 @@ use std::{
sync::Arc,
time::Duration,
};
use theme::ThemeSettings;
use git::repository::GitFileStatus;
@@ -24,7 +28,7 @@ use ui::{
use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace;
use crate::{git_status_icon, settings::GitPanelSettings};
use crate::{git_status_icon, settings::GitPanelSettings, GitState};
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
actions!(git_panel, [ToggleFocus]);
@@ -84,6 +88,8 @@ pub struct GitPanel {
selected_item: Option<usize>,
show_scrollbar: bool,
expanded_dir_ids: HashMap<WorktreeId, Vec<ProjectEntryId>>,
git_state: Model<GitState>,
editor: View<Editor>,
// The entries that are currently shown in the panel, aka
// not hidden by folding or such
@@ -104,11 +110,17 @@ impl GitPanel {
}
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let git_state = GitState::get_global(cx);
let fs = workspace.app_state().fs.clone();
let weak_workspace = workspace.weak_handle();
let project = workspace.project().clone();
let language_registry = workspace.app_state().languages.clone();
let git_panel = cx.new_view(|cx: &mut ViewContext<Self>| {
let state = git_state.read(cx);
let current_commit_message = state.commit_message.clone();
let focus_handle = cx.focus_handle();
cx.on_focus(&focus_handle, Self::focus_in).detach();
cx.on_focus_out(&focus_handle, |this, _, cx| {
@@ -131,6 +143,55 @@ impl GitPanel {
})
.detach();
let editor = cx.new_view(|cx| {
let theme = ThemeSettings::get_global(cx);
let mut text_style = cx.text_style();
let refinement = TextStyleRefinement {
font_family: Some(theme.buffer_font.family.clone()),
font_features: Some(FontFeatures::disable_ligatures()),
font_size: Some(px(12.).into()),
color: Some(cx.theme().colors().editor_foreground),
background_color: Some(gpui::transparent_black()),
..Default::default()
};
text_style.refine(&refinement);
let mut editor = Editor::auto_height(10, cx);
if let Some(message) = current_commit_message {
editor.set_text(message, cx);
} else {
editor.set_text("", cx);
}
// editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_use_autoclose(false);
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
editor.set_text_style_refinement(refinement);
editor.set_placeholder_text("Enter commit message", cx);
editor
});
let buffer = editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.expect("commit editor must be singleton");
cx.subscribe(&buffer, Self::on_buffer_event).detach();
let markdown = language_registry.language_for_name("Markdown");
cx.spawn(|_, mut cx| async move {
let markdown = markdown.await.context("failed to load Markdown language")?;
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(Some(markdown), cx)
})
})
.detach_and_log_err(cx);
let scroll_handle = UniformListScrollHandle::new();
let mut this = Self {
@@ -142,6 +203,8 @@ impl GitPanel {
visible_entries: Vec::new(),
current_modifiers: cx.modifiers(),
expanded_dir_ids: Default::default(),
git_state,
editor,
width: Some(px(360.)),
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
@@ -188,12 +251,12 @@ impl GitPanel {
}
fn should_show_scrollbar(_cx: &AppContext) -> bool {
// todo!(): plug into settings
// TODO: plug into settings
true
}
fn should_autohide_scrollbar(_cx: &AppContext) -> bool {
// todo!(): plug into settings
// TODO: plug into settings
true
}
@@ -255,34 +318,44 @@ impl GitPanel {
impl GitPanel {
fn stage_all(&mut self, _: &StageAll, _cx: &mut ViewContext<Self>) {
// todo!(): Implement stage all
// TODO: Implement stage all
println!("Stage all triggered");
}
fn unstage_all(&mut self, _: &UnstageAll, _cx: &mut ViewContext<Self>) {
// todo!(): Implement unstage all
// TODO: Implement unstage all
println!("Unstage all triggered");
}
fn discard_all(&mut self, _: &DiscardAll, _cx: &mut ViewContext<Self>) {
// todo!(): Implement discard all
// TODO: Implement discard all
println!("Discard all triggered");
}
fn clear_message(&mut self, cx: &mut ViewContext<Self>) {
let git_state = self.git_state.clone();
git_state.update(cx, |state, _cx| state.clear_message());
self.editor.update(cx, |editor, cx| editor.set_text("", cx));
}
/// Commit all staged changes
fn commit_staged_changes(&mut self, _: &CommitStagedChanges, _cx: &mut ViewContext<Self>) {
// todo!(): Implement commit all staged
fn commit_staged_changes(&mut self, _: &CommitStagedChanges, cx: &mut ViewContext<Self>) {
self.clear_message(cx);
// TODO: Implement commit all staged
println!("Commit staged changes triggered");
}
/// Commit all changes, regardless of whether they are staged or not
fn commit_all_changes(&mut self, _: &CommitAllChanges, _cx: &mut ViewContext<Self>) {
// todo!(): Implement commit all changes
fn commit_all_changes(&mut self, _: &CommitAllChanges, cx: &mut ViewContext<Self>) {
self.clear_message(cx);
// TODO: Implement commit all changes
println!("Commit all changes triggered");
}
fn all_staged(&self) -> bool {
// todo!(): Implement all_staged
// TODO: Implement all_staged
true
}
@@ -378,7 +451,7 @@ impl GitPanel {
}
}
// todo!(): Update expanded directory state
// TODO: Update expanded directory state
fn update_visible_entries(
&mut self,
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
@@ -426,6 +499,23 @@ impl GitPanel {
cx.notify();
}
fn on_buffer_event(
&mut self,
_buffer: Model<Buffer>,
event: &language::BufferEvent,
cx: &mut ViewContext<Self>,
) {
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
let commit_message = self.editor.update(cx, |editor, cx| editor.text(cx));
self.git_state.update(cx, |state, _cx| {
state.commit_message = Some(commit_message.into());
});
cx.notify();
}
}
}
impl GitPanel {
@@ -499,6 +589,13 @@ impl GitPanel {
}
pub fn render_commit_editor(&self, cx: &ViewContext<Self>) -> impl IntoElement {
let git_state = self.git_state.clone();
let commit_message = git_state.read(cx).commit_message.clone();
let editor = self.editor.clone();
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
println!("{:?}", commit_message);
let focus_handle_1 = self.focus_handle(cx).clone();
let focus_handle_2 = self.focus_handle(cx).clone();
@@ -534,25 +631,26 @@ impl GitPanel {
div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
v_flex()
.id("commit-editor-container")
.relative()
.h_full()
.py_2p5()
.px_3()
.bg(cx.theme().colors().editor_background)
.font_buffer(cx)
.text_ui_sm(cx)
.text_color(cx.theme().colors().text_muted)
.child("Add a message")
.gap_1()
.child(div().flex_grow())
.child(h_flex().child(div().gap_1().flex_grow()).child(
if self.current_modifiers.alt {
commit_all_button
} else {
commit_staged_button
},
))
.cursor(CursorStyle::OperationNotAllowed)
.opacity(0.5),
.on_click(cx.listener(move |_, _: &ClickEvent, cx| cx.focus(&editor_focus_handle)))
.child(self.editor.clone())
.child(
h_flex()
.absolute()
.bottom_2p5()
.right_3()
.child(div().gap_1().flex_grow())
.child(if self.current_modifiers.alt {
commit_all_button
} else {
commit_staged_button
}),
),
)
}

View File

@@ -1,8 +1,8 @@
use ::settings::Settings;
use git::repository::GitFileStatus;
use gpui::{actions, AppContext, Hsla};
use gpui::{actions, prelude::*, AppContext, Global, Hsla, Model};
use settings::GitPanelSettings;
use ui::{Color, Icon, IconName, IntoElement};
use ui::{Color, Icon, IconName, IntoElement, SharedString};
pub mod git_panel;
mod settings;
@@ -14,14 +14,50 @@ actions!(
UnstageAll,
DiscardAll,
CommitStagedChanges,
CommitAllChanges
CommitAllChanges,
ClearMessage
]
);
pub fn init(cx: &mut AppContext) {
GitPanelSettings::register(cx);
let git_state = cx.new_model(|_cx| GitState::new());
cx.set_global(GlobalGitState(git_state));
}
struct GlobalGitState(Model<GitState>);
impl Global for GlobalGitState {}
pub struct GitState {
commit_message: Option<SharedString>,
}
impl GitState {
pub fn new() -> Self {
GitState {
commit_message: None,
}
}
pub fn set_message(&mut self, message: Option<SharedString>) {
self.commit_message = message;
}
pub fn clear_message(&mut self) {
self.commit_message = None;
}
pub fn get_global(cx: &mut AppContext) -> Model<GitState> {
cx.global::<GlobalGitState>().0.clone()
}
}
// impl EventEmitter<Event> for GitState {}
// #[derive(Clone, Debug, PartialEq, Eq)]
// pub enum Event {}
const ADDED_COLOR: Hsla = Hsla {
h: 142. / 360.,
s: 0.68,
@@ -41,7 +77,7 @@ const REMOVED_COLOR: Hsla = Hsla {
a: 1.0,
};
// todo!(): Add updated status colors to theme
// TODO: Add updated status colors to theme
pub fn git_status_icon(status: GitFileStatus) -> impl IntoElement {
match status {
GitFileStatus::Added => Icon::new(IconName::SquarePlus).color(Color::Custom(ADDED_COLOR)),

View File

@@ -315,7 +315,10 @@ impl ContextProvider for PythonContextProvider {
toolchains
.active_toolchain(worktree_id, "Python".into(), &mut cx)
.await
.map_or_else(|| "python3".to_owned(), |toolchain| toolchain.path.into())
.map_or_else(
|| "python3".to_owned(),
|toolchain| format!("\"{}\"", toolchain.path),
)
} else {
String::from("python3")
};
@@ -336,14 +339,17 @@ impl ContextProvider for PythonContextProvider {
TaskTemplate {
label: "execute selection".to_owned(),
command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
args: vec!["-c".to_owned(), VariableName::SelectedText.template_value()],
args: vec![
"-c".to_owned(),
VariableName::SelectedText.template_value_with_whitespace(),
],
..TaskTemplate::default()
},
// Execute an entire file
TaskTemplate {
label: format!("run '{}'", VariableName::File.template_value()),
command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
args: vec![VariableName::File.template_value()],
args: vec![VariableName::File.template_value_with_whitespace()],
..TaskTemplate::default()
},
];
@@ -358,7 +364,7 @@ impl ContextProvider for PythonContextProvider {
args: vec![
"-m".to_owned(),
"unittest".to_owned(),
VariableName::File.template_value(),
VariableName::File.template_value_with_whitespace(),
],
..TaskTemplate::default()
},
@@ -369,7 +375,7 @@ impl ContextProvider for PythonContextProvider {
args: vec![
"-m".to_owned(),
"unittest".to_owned(),
"$ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(),
PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace()
],
tags: vec![
"python-unittest-class".to_owned(),
@@ -388,7 +394,7 @@ impl ContextProvider for PythonContextProvider {
args: vec![
"-m".to_owned(),
"pytest".to_owned(),
VariableName::File.template_value(),
VariableName::File.template_value_with_whitespace(),
],
..TaskTemplate::default()
},
@@ -399,7 +405,7 @@ impl ContextProvider for PythonContextProvider {
args: vec![
"-m".to_owned(),
"pytest".to_owned(),
"$ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(),
PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace(),
],
tags: vec![
"python-pytest-class".to_owned(),

View File

@@ -4,7 +4,7 @@ use gpui::{AppContext, FontFeatures, FontWeight};
use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings};
use ui::{
prelude::*, ToggleWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
prelude::*, CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer,
SettingsGroup, ToggleButton,
};
@@ -368,7 +368,7 @@ impl RenderOnce for UiFontLigaturesControl {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let value = Self::read(cx);
ToggleWithLabel::new(
CheckboxWithLabel::new(
"ui-font-ligatures",
Label::new(self.name()),
value.into(),

View File

@@ -135,6 +135,10 @@ impl VariableName {
pub fn template_value(&self) -> String {
format!("${self}")
}
/// Generates a `"$VARIABLE"`-like string, to be used instead of `Self::template_value` when expanded value could contain spaces or special characters.
pub fn template_value_with_whitespace(&self) -> String {
format!("\"${self}\"")
}
}
impl FromStr for VariableName {

View File

@@ -17,38 +17,6 @@ pub fn switch(id: impl Into<ElementId>, toggle_state: ToggleState) -> Switch {
Switch::new(id, toggle_state)
}
/// Creates a new checkbox with a label
///
/// [`ToggleWithLabel`] with [`ToggleKind::Checkbox`]
pub fn labeled_checkbox(
id: impl Into<ElementId>,
label: Label,
toggle_state: ToggleState,
on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,
) -> ToggleWithLabel {
ToggleWithLabel::new(id, label, toggle_state, on_click).kind(ToggleKind::Checkbox)
}
/// Creates a new switch with a label
///
/// [`ToggleWithLabel`] with [`ToggleKind::Switch`]
pub fn labeled_switch(
id: impl Into<ElementId>,
label: Label,
toggle_state: ToggleState,
on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,
) -> ToggleWithLabel {
ToggleWithLabel::new(id, label, toggle_state, on_click).kind(ToggleKind::Switch)
}
/// The types of toggles, defaulting to [`Checkbox`]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ToggleKind {
#[default]
Checkbox,
Switch,
}
/// # Checkbox
///
/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
@@ -164,15 +132,14 @@ impl RenderOnce for Checkbox {
/// A [`Checkbox`] that has a [`Label`].
#[derive(IntoElement)]
pub struct ToggleWithLabel {
pub struct CheckboxWithLabel {
id: ElementId,
label: Label,
kind: ToggleKind,
checked: ToggleState,
on_click: Arc<dyn Fn(&ToggleState, &mut WindowContext) + 'static>,
}
impl ToggleWithLabel {
impl CheckboxWithLabel {
pub fn new(
id: impl Into<ElementId>,
label: Label,
@@ -182,38 +149,22 @@ impl ToggleWithLabel {
Self {
id: id.into(),
label,
kind: ToggleKind::default(),
checked,
on_click: Arc::new(on_click),
}
}
pub fn kind(mut self, kind: ToggleKind) -> Self {
self.kind = kind;
self
}
}
impl RenderOnce for ToggleWithLabel {
impl RenderOnce for CheckboxWithLabel {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
h_flex()
.gap(DynamicSpacing::Base08.rems(cx))
.when(self.kind == ToggleKind::Checkbox, |this| {
this.child(Checkbox::new(self.id.clone(), self.checked).on_click({
let on_click = self.on_click.clone();
move |checked, cx| {
(on_click)(checked, cx);
}
}))
})
.when(self.kind == ToggleKind::Switch, |this| {
this.child(Switch::new(self.id.clone(), self.checked).on_click({
let on_click = self.on_click.clone();
move |checked, cx| {
(on_click)(checked, cx);
}
}))
})
.child(Checkbox::new(self.id.clone(), self.checked).on_click({
let on_click = self.on_click.clone();
move |checked, cx| {
(on_click)(checked, cx);
}
}))
.child(
div()
.id(SharedString::from(format!("{}-label", self.id)))
@@ -419,70 +370,40 @@ impl ComponentPreview for Switch {
}
}
impl ComponentPreview for ToggleWithLabel {
impl ComponentPreview for CheckboxWithLabel {
fn description() -> impl Into<Option<&'static str>> {
"A toggle with an associated label, allowing users to select an option while providing a descriptive text. By default, the toggle is presented as a checkbox, but can also render a switch."
"A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
}
fn examples(_: &mut WindowContext) -> Vec<ComponentExampleGroup<Self>> {
vec![
example_group_with_title(
"Checkbox",
vec![
single_example(
"Unselected",
ToggleWithLabel::new(
"checkbox_with_label_unselected",
Label::new("Always save on quit"),
ToggleState::Unselected,
|_, _| {},
),
),
single_example(
"Indeterminate",
ToggleWithLabel::new(
"checkbox_with_label_indeterminate",
Label::new("Always save on quit"),
ToggleState::Indeterminate,
|_, _| {},
),
),
single_example(
"Selected",
ToggleWithLabel::new(
"checkbox_with_label_selected",
Label::new("Always save on quit"),
ToggleState::Selected,
|_, _| {},
),
),
],
vec![example_group(vec![
single_example(
"Unselected",
CheckboxWithLabel::new(
"checkbox_with_label_unselected",
Label::new("Always save on quit"),
ToggleState::Unselected,
|_, _| {},
),
),
example_group_with_title(
"Switch",
vec![
single_example(
"Off",
ToggleWithLabel::new(
"switch_with_label_off",
Label::new("Dark mode"),
ToggleState::Unselected,
|_, _| {},
)
.kind(ToggleKind::Switch),
),
single_example(
"On",
ToggleWithLabel::new(
"switch_with_label_on",
Label::new("Dark mode"),
ToggleState::Selected,
|_, _| {},
)
.kind(ToggleKind::Switch),
),
],
single_example(
"Indeterminate",
CheckboxWithLabel::new(
"checkbox_with_label_indeterminate",
Label::new("Always save on quit"),
ToggleState::Indeterminate,
|_, _| {},
),
),
]
single_example(
"Selected",
CheckboxWithLabel::new(
"checkbox_with_label_selected",
Label::new("Always save on quit"),
ToggleState::Selected,
|_, _| {},
),
),
])]
}
}

View File

@@ -11,7 +11,7 @@ use gpui::{
};
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use ui::{labeled_checkbox, prelude::*, Tooltip};
use ui::{prelude::*, CheckboxWithLabel, Tooltip};
use vim_mode_setting::VimModeSetting;
use workspace::{
dock::DockPosition,
@@ -269,26 +269,24 @@ impl Render for WelcomePage {
.child(
h_flex()
.justify_between()
.child(
labeled_checkbox(
"enable-vim",
Label::new("Enable Vim Mode"),
if VimModeSetting::get_global(cx).0 {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry
.report_app_event("welcome page: toggle vim".to_string());
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
);
})
)
)
.child(CheckboxWithLabel::new(
"enable-vim",
Label::new("Enable Vim Mode"),
if VimModeSetting::get_global(cx).0 {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry
.report_app_event("welcome page: toggle vim".to_string());
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
);
}),
))
.child(
IconButton::new("vim-mode", IconName::Info)
.icon_size(IconSize::XSmall)
@@ -296,62 +294,58 @@ impl Render for WelcomePage {
.tooltip(|cx| Tooltip::text("You can also toggle Vim Mode via the command palette or Editor Controls menu.", cx)),
)
)
.child(
labeled_checkbox(
"enable-crash",
Label::new("Send Crash Reports"),
if TelemetrySettings::get_global(cx).diagnostics {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry.report_app_event(
"welcome page: toggle diagnostic telemetry".to_string(),
);
this.update_settings::<TelemetrySettings>(selection, cx, {
let telemetry = this.telemetry.clone();
.child(CheckboxWithLabel::new(
"enable-crash",
Label::new("Send Crash Reports"),
if TelemetrySettings::get_global(cx).diagnostics {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry.report_app_event(
"welcome page: toggle diagnostic telemetry".to_string(),
);
this.update_settings::<TelemetrySettings>(selection, cx, {
let telemetry = this.telemetry.clone();
move |settings, value| {
settings.diagnostics = Some(value);
move |settings, value| {
settings.diagnostics = Some(value);
telemetry.report_setting_event(
"diagnostic telemetry",
value.to_string(),
);
}
});
})
)
)
.child(
labeled_checkbox(
"enable-telemetry",
Label::new("Send Telemetry"),
if TelemetrySettings::get_global(cx).metrics {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry.report_app_event(
"welcome page: toggle metric telemetry".to_string(),
);
this.update_settings::<TelemetrySettings>(selection, cx, {
let telemetry = this.telemetry.clone();
telemetry.report_setting_event(
"diagnostic telemetry",
value.to_string(),
);
}
});
}),
))
.child(CheckboxWithLabel::new(
"enable-telemetry",
Label::new("Send Telemetry"),
if TelemetrySettings::get_global(cx).metrics {
ui::ToggleState::Selected
} else {
ui::ToggleState::Unselected
},
cx.listener(move |this, selection, cx| {
this.telemetry.report_app_event(
"welcome page: toggle metric telemetry".to_string(),
);
this.update_settings::<TelemetrySettings>(selection, cx, {
let telemetry = this.telemetry.clone();
move |settings, value| {
settings.metrics = Some(value);
move |settings, value| {
settings.metrics = Some(value);
telemetry.report_setting_event(
"metric telemetry",
value.to_string(),
);
}
});
})
)
),
telemetry.report_setting_event(
"metric telemetry",
value.to_string(),
);
}
});
}),
)),
),
)
}

View File

@@ -5,8 +5,8 @@ use theme::all_theme_colors;
use ui::{
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
Checkbox, ContentGroup, DecoratedIcon, ElevationIndex, Facepile, IconDecoration, Indicator,
Switch, Table, TintColor, ToggleWithLabel, Tooltip,
Checkbox, CheckboxWithLabel, ContentGroup, DecoratedIcon, ElevationIndex, Facepile,
IconDecoration, Indicator, Switch, Table, TintColor, Tooltip,
};
use crate::{Item, Workspace};
@@ -369,12 +369,12 @@ impl ThemePreview {
.overflow_scroll()
.size_full()
.gap_2()
.child(Switch::render_component_previews(cx))
.child(ContentGroup::render_component_previews(cx))
.child(IconDecoration::render_component_previews(cx))
.child(DecoratedIcon::render_component_previews(cx))
.child(Checkbox::render_component_previews(cx))
.child(Switch::render_component_previews(cx))
.child(ToggleWithLabel::render_component_previews(cx))
.child(CheckboxWithLabel::render_component_previews(cx))
.child(Facepile::render_component_previews(cx))
.child(Button::render_component_previews(cx))
.child(Indicator::render_component_previews(cx))

View File

@@ -927,7 +927,7 @@ impl ZetaInlineCompletionProvider {
impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvider {
fn name() -> &'static str {
"Zeta"
"zeta"
}
fn is_enabled(