edit predictions: Improve UX when there's no keybinding for accepting predictions (#25815)
If the user already binds `tab`/`alt-tab`/`alt-l` to a different action in a conflicting context and hasn't assigned a different keybinding for `editor::AcceptEditPrediction`, we would show broken popovers with no bindings:  Instead, they will now see an error-variant of every popover which includes a tooltip with a short description and buttons to open the keymap, and open a new docs section explaining the issue in detail and how to fix it.  Note: I included the docs change in this PR because it's ok to deploy before the release, as it also applies to existing versions. Release Notes: - edit predictions: Improve UX when there's no keybinding for accepting predictions --------- Co-authored-by: Danilo Leal <daniloleal09@gmail.com> Co-authored-by: Danilo <danilo@zed.dev>
This commit is contained in:
parent
76a81607de
commit
f31749c81b
5 changed files with 167 additions and 26 deletions
|
@ -1,12 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2131_1193)">
|
||||
<circle cx="7" cy="7" r="6" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M6 10H7M8 10H7M7 10V7.1C7 7.04477 6.95523 7 6.9 7H6" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="7" cy="4.5" r="1" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2131_1193">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M7 11H8M8 11H9M8 11V8.1C8 8.04477 7.95523 8 7.9 8H7" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M8 6.5C8.55228 6.5 9 6.05228 9 5.5C9 4.94772 8.55228 4.5 8 4.5C7.44772 4.5 7 4.94772 7 5.5C7 6.05228 7.44772 6.5 8 6.5Z" fill="black"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 479 B After Width: | Height: | Size: 524 B |
4
assets/icons/zed_predict_error.svg
Normal file
4
assets/icons/zed_predict_error.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.6" 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"/>
|
||||
<path d="M14 8L10 12M14 12L10 8" stroke="black" stroke-width="1.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 296 B |
|
@ -85,8 +85,8 @@ use gpui::{
|
|||
ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler,
|
||||
EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global,
|
||||
HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
|
||||
ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, Task,
|
||||
TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
|
||||
ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription,
|
||||
Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
|
||||
WeakEntity, WeakFocusHandle, Window,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
|
@ -6294,6 +6294,9 @@ impl Editor {
|
|||
|
||||
const BORDER_WIDTH: Pixels = px(1.);
|
||||
|
||||
let keybind = self.render_edit_prediction_accept_keybind(window, cx);
|
||||
let has_keybind = keybind.is_some();
|
||||
|
||||
let mut element = h_flex()
|
||||
.items_start()
|
||||
.child(
|
||||
|
@ -6324,7 +6327,19 @@ impl Editor {
|
|||
.border(BORDER_WIDTH)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_r_lg()
|
||||
.children(self.render_edit_prediction_accept_keybind(window, cx)),
|
||||
.id("edit_prediction_diff_popover_keybind")
|
||||
.when(!has_keybind, |el| {
|
||||
let status_colors = cx.theme().status();
|
||||
|
||||
el.bg(status_colors.error_background)
|
||||
.border_color(status_colors.error.opacity(0.6))
|
||||
.child(Icon::new(IconName::Info).color(Color::Error))
|
||||
.cursor_default()
|
||||
.hoverable_tooltip(move |_window, cx| {
|
||||
cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
|
||||
})
|
||||
})
|
||||
.children(keybind),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
|
@ -6422,7 +6437,11 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_edit_prediction_accept_keybind(&self, window: &mut Window, cx: &App) -> Option<Div> {
|
||||
fn render_edit_prediction_accept_keybind(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &App,
|
||||
) -> Option<AnyElement> {
|
||||
let accept_binding = self.accept_edit_prediction_keybind(window, cx);
|
||||
let accept_keystroke = accept_binding.keystroke()?;
|
||||
|
||||
|
@ -6458,6 +6477,7 @@ impl Editor {
|
|||
.size(Some(IconSize::XSmall.rems().into())),
|
||||
)
|
||||
})
|
||||
.into_any()
|
||||
.into()
|
||||
}
|
||||
|
||||
|
@ -6467,10 +6487,14 @@ impl Editor {
|
|||
icon: Option<IconName>,
|
||||
window: &mut Window,
|
||||
cx: &App,
|
||||
) -> Option<Div> {
|
||||
) -> Option<Stateful<Div>> {
|
||||
let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
|
||||
|
||||
let keybind = self.render_edit_prediction_accept_keybind(window, cx);
|
||||
let has_keybind = keybind.is_some();
|
||||
|
||||
let result = h_flex()
|
||||
.id("ep-line-popover")
|
||||
.py_0p5()
|
||||
.pl_1()
|
||||
.pr(padding_right)
|
||||
|
@ -6480,8 +6504,35 @@ impl Editor {
|
|||
.bg(Self::edit_prediction_line_popover_bg_color(cx))
|
||||
.border_color(Self::edit_prediction_callout_popover_border_color(cx))
|
||||
.shadow_sm()
|
||||
.children(self.render_edit_prediction_accept_keybind(window, cx))
|
||||
.child(Label::new(label).size(LabelSize::Small))
|
||||
.when(!has_keybind, |el| {
|
||||
let status_colors = cx.theme().status();
|
||||
|
||||
el.bg(status_colors.error_background)
|
||||
.border_color(status_colors.error.opacity(0.6))
|
||||
.pl_2()
|
||||
.child(Icon::new(IconName::ZedPredictError).color(Color::Error))
|
||||
.cursor_default()
|
||||
.hoverable_tooltip(move |_window, cx| {
|
||||
cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
|
||||
})
|
||||
})
|
||||
.children(keybind)
|
||||
.child(
|
||||
Label::new(label)
|
||||
.size(LabelSize::Small)
|
||||
.when(!has_keybind, |el| {
|
||||
el.color(cx.theme().status().error.into()).strikethrough()
|
||||
}),
|
||||
)
|
||||
.when(!has_keybind, |el| {
|
||||
el.child(
|
||||
h_flex().ml_1().child(
|
||||
Icon::new(IconName::Info)
|
||||
.size(IconSize::Small)
|
||||
.color(cx.theme().status().error.into()),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when_some(icon, |element, icon| {
|
||||
element.child(
|
||||
div()
|
||||
|
@ -6578,6 +6629,9 @@ impl Editor {
|
|||
.elevation_2(cx)
|
||||
.border(BORDER_WIDTH)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.when(accept_keystroke.is_none(), |el| {
|
||||
el.border_color(cx.theme().status().error)
|
||||
})
|
||||
.rounded(RADIUS)
|
||||
.rounded_tl(px(0.))
|
||||
.overflow_hidden()
|
||||
|
@ -6606,16 +6660,37 @@ impl Editor {
|
|||
el.child(
|
||||
Label::new("Hold")
|
||||
.size(LabelSize::Small)
|
||||
.when(accept_keystroke.is_none(), |el| {
|
||||
el.strikethrough()
|
||||
})
|
||||
.line_height_style(LineHeightStyle::UiLabel),
|
||||
)
|
||||
})
|
||||
.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke?.modifiers,
|
||||
PlatformStyle::platform(),
|
||||
Some(Color::Default),
|
||||
Some(IconSize::XSmall.rems().into()),
|
||||
false,
|
||||
))),
|
||||
.id("edit_prediction_cursor_popover_keybind")
|
||||
.when(accept_keystroke.is_none(), |el| {
|
||||
let status_colors = cx.theme().status();
|
||||
|
||||
el.bg(status_colors.error_background)
|
||||
.border_color(status_colors.error.opacity(0.6))
|
||||
.child(Icon::new(IconName::Info).color(Color::Error))
|
||||
.cursor_default()
|
||||
.hoverable_tooltip(move |_window, cx| {
|
||||
cx.new(|_| MissingEditPredictionKeybindingTooltip)
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.when_some(
|
||||
accept_keystroke.as_ref(),
|
||||
|el, accept_keystroke| {
|
||||
el.child(h_flex().children(ui::render_modifiers(
|
||||
&accept_keystroke.modifiers,
|
||||
PlatformStyle::platform(),
|
||||
Some(Color::Default),
|
||||
Some(IconSize::XSmall.rems().into()),
|
||||
false,
|
||||
)))
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any(),
|
||||
);
|
||||
|
@ -18328,3 +18403,37 @@ fn all_edits_insertions_or_deletions(
|
|||
}
|
||||
all_insertions || all_deletions
|
||||
}
|
||||
|
||||
struct MissingEditPredictionKeybindingTooltip;
|
||||
|
||||
impl Render for MissingEditPredictionKeybindingTooltip {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
ui::tooltip_container(window, cx, |container, _, cx| {
|
||||
container
|
||||
.flex_shrink_0()
|
||||
.max_w_80()
|
||||
.min_h(rems_from_px(124.))
|
||||
.justify_between()
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.text_ui_sm(cx)
|
||||
.child(Label::new("Conflict with Accept Keybinding"))
|
||||
.child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.pb_1()
|
||||
.gap_1()
|
||||
.items_end()
|
||||
.w_full()
|
||||
.child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
|
||||
window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
|
||||
}))
|
||||
.child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
|
||||
cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
|
||||
})),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -329,6 +329,7 @@ pub enum IconName {
|
|||
ZedPredictUp,
|
||||
ZedPredictDown,
|
||||
ZedPredictDisabled,
|
||||
ZedPredictError,
|
||||
ZedXCopilot,
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ On Linux, `alt-tab` is often used by the window manager for switching windows, s
|
|||
|
||||
See the [Configuring GitHub Copilot](#github-copilot) and [Configuring Supermaven](#supermaven) sections below for configuration of other providers. Only text insertions at the current cursor are supported for these providers, whereas the Zeta model provides multiple predictions including deletions.
|
||||
|
||||
## Configuring Edit Prediction Keybindings
|
||||
## Configuring Edit Prediction Keybindings {#edit-predictions-keybinding}
|
||||
|
||||
By default, `tab` is used to accept edit predictions. You can use another keybinding by inserting this in your keymap:
|
||||
|
||||
|
@ -137,6 +137,40 @@ While `tab` and `alt-tab` are supported on Linux, `alt-l` is displayed instead.
|
|||
},
|
||||
```
|
||||
|
||||
### Missing keybind {#edit-predictions-missing-keybinding}
|
||||
|
||||
Zed requires at least one keybinding for the {#action editor::AcceptEditPrediction} action in both the `Editor && edit_prediction` and `Editor && edit_prediction_conflict` contexts ([learn more above](#edit-predictions-keybinding)).
|
||||
|
||||
If you have previously bound the default keybindings to different actions in the global context, you will not be able to preview or accept edit predictions. For example:
|
||||
|
||||
```json
|
||||
[
|
||||
// Your keymap
|
||||
{
|
||||
"bindings": {
|
||||
// Binds `alt-tab` to a different action globally
|
||||
"alt-tab": "menu::SelectNext"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
To fix this, you can specify your own keybinding for accepting edit predictions:
|
||||
|
||||
```json
|
||||
[
|
||||
// ...
|
||||
{
|
||||
"context": "Editor && edit_prediction_conflict",
|
||||
"bindings": {
|
||||
"alt-l": "editor::AcceptEditPrediction"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
If you would like to use the default keybinding, you can free it up by either moving yours to a more specific context or changing it to something else.
|
||||
|
||||
## Disabling Automatic Edit Prediction
|
||||
|
||||
To disable predictions that appear automatically as you type, set this within `settings.json`:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue