editor: Show hints for using AI features on empty lines (#20824)
Co-Authored-by: Thorsten <thorsten@zed.dev> Co-Authored-by: Antonio <antonio@zed.dev> Screenshot:  TODO: - [x] docs Release Notes: - Added inline hints that guide users on how to invoke the inline assistant and open the assistant panel. (These hints can be disabled by setting `{"assistant": {"show_hints": false}}`.) --------- Co-authored-by: Thorsten <thorsten@zed.dev> Co-authored-by: Antonio <antonio@zed.dev> Co-authored-by: Thorsten Ball <mrnugget@gmail.com>
This commit is contained in:
parent
a35b73e63e
commit
aae39071ef
12 changed files with 283 additions and 83 deletions
|
@ -490,6 +490,9 @@
|
||||||
"version": "2",
|
"version": "2",
|
||||||
// Whether the assistant is enabled.
|
// Whether the assistant is enabled.
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
// Whether to show inline hints showing the keybindings to use the inline assistant and the
|
||||||
|
// assistant panel.
|
||||||
|
"show_hints": true,
|
||||||
// Whether to show the assistant panel button in the status bar.
|
// Whether to show the assistant panel button in the status bar.
|
||||||
"button": true,
|
"button": true,
|
||||||
// Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'.
|
// Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'.
|
||||||
|
|
|
@ -60,6 +60,7 @@ pub struct AssistantSettings {
|
||||||
pub inline_alternatives: Vec<LanguageModelSelection>,
|
pub inline_alternatives: Vec<LanguageModelSelection>,
|
||||||
pub using_outdated_settings_version: bool,
|
pub using_outdated_settings_version: bool,
|
||||||
pub enable_experimental_live_diffs: bool,
|
pub enable_experimental_live_diffs: bool,
|
||||||
|
pub show_hints: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssistantSettings {
|
impl AssistantSettings {
|
||||||
|
@ -202,6 +203,7 @@ impl AssistantSettingsContent {
|
||||||
AssistantSettingsContent::Versioned(settings) => match settings {
|
AssistantSettingsContent::Versioned(settings) => match settings {
|
||||||
VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 {
|
VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 {
|
||||||
enabled: settings.enabled,
|
enabled: settings.enabled,
|
||||||
|
show_hints: None,
|
||||||
button: settings.button,
|
button: settings.button,
|
||||||
dock: settings.dock,
|
dock: settings.dock,
|
||||||
default_width: settings.default_width,
|
default_width: settings.default_width,
|
||||||
|
@ -242,6 +244,7 @@ impl AssistantSettingsContent {
|
||||||
},
|
},
|
||||||
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
|
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
|
||||||
enabled: None,
|
enabled: None,
|
||||||
|
show_hints: None,
|
||||||
button: settings.button,
|
button: settings.button,
|
||||||
dock: settings.dock,
|
dock: settings.dock,
|
||||||
default_width: settings.default_width,
|
default_width: settings.default_width,
|
||||||
|
@ -354,6 +357,7 @@ impl Default for VersionedAssistantSettingsContent {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::V2(AssistantSettingsContentV2 {
|
Self::V2(AssistantSettingsContentV2 {
|
||||||
enabled: None,
|
enabled: None,
|
||||||
|
show_hints: None,
|
||||||
button: None,
|
button: None,
|
||||||
dock: None,
|
dock: None,
|
||||||
default_width: None,
|
default_width: None,
|
||||||
|
@ -371,6 +375,11 @@ pub struct AssistantSettingsContentV2 {
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
enabled: Option<bool>,
|
enabled: Option<bool>,
|
||||||
|
/// Whether to show inline hints that show keybindings for inline assistant
|
||||||
|
/// and assistant panel.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
show_hints: Option<bool>,
|
||||||
/// Whether to show the assistant panel button in the status bar.
|
/// Whether to show the assistant panel button in the status bar.
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
|
@ -505,6 +514,7 @@ impl Settings for AssistantSettings {
|
||||||
|
|
||||||
let value = value.upgrade();
|
let value = value.upgrade();
|
||||||
merge(&mut settings.enabled, value.enabled);
|
merge(&mut settings.enabled, value.enabled);
|
||||||
|
merge(&mut settings.show_hints, value.show_hints);
|
||||||
merge(&mut settings.button, value.button);
|
merge(&mut settings.button, value.button);
|
||||||
merge(&mut settings.dock, value.dock);
|
merge(&mut settings.dock, value.dock);
|
||||||
merge(
|
merge(
|
||||||
|
@ -575,6 +585,7 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
inline_alternatives: None,
|
inline_alternatives: None,
|
||||||
enabled: None,
|
enabled: None,
|
||||||
|
show_hints: None,
|
||||||
button: None,
|
button: None,
|
||||||
dock: None,
|
dock: None,
|
||||||
default_width: None,
|
default_width: None,
|
||||||
|
|
|
@ -540,6 +540,15 @@ pub enum IsVimMode {
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ActiveLineTrailerProvider {
|
||||||
|
fn render_active_line_trailer(
|
||||||
|
&mut self,
|
||||||
|
style: &EditorStyle,
|
||||||
|
focus_handle: &FocusHandle,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<AnyElement>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
|
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
|
||||||
///
|
///
|
||||||
/// See the [module level documentation](self) for more information.
|
/// See the [module level documentation](self) for more information.
|
||||||
|
@ -667,6 +676,7 @@ pub struct Editor {
|
||||||
next_scroll_position: NextScrollCursorCenterTopBottom,
|
next_scroll_position: NextScrollCursorCenterTopBottom,
|
||||||
addons: HashMap<TypeId, Box<dyn Addon>>,
|
addons: HashMap<TypeId, Box<dyn Addon>>,
|
||||||
_scroll_cursor_center_top_bottom_task: Task<()>,
|
_scroll_cursor_center_top_bottom_task: Task<()>,
|
||||||
|
active_line_trailer_provider: Option<Box<dyn ActiveLineTrailerProvider>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||||
|
@ -2200,6 +2210,7 @@ impl Editor {
|
||||||
addons: HashMap::default(),
|
addons: HashMap::default(),
|
||||||
_scroll_cursor_center_top_bottom_task: Task::ready(()),
|
_scroll_cursor_center_top_bottom_task: Task::ready(()),
|
||||||
text_style_refinement: None,
|
text_style_refinement: None,
|
||||||
|
active_line_trailer_provider: None,
|
||||||
};
|
};
|
||||||
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
this.tasks_update_task = Some(this.refresh_runnables(cx));
|
||||||
this._subscriptions.extend(project_subscriptions);
|
this._subscriptions.extend(project_subscriptions);
|
||||||
|
@ -2488,6 +2499,16 @@ impl Editor {
|
||||||
self.refresh_inline_completion(false, false, cx);
|
self.refresh_inline_completion(false, false, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_active_line_trailer_provider<T>(
|
||||||
|
&mut self,
|
||||||
|
provider: Option<T>,
|
||||||
|
_cx: &mut ViewContext<Self>,
|
||||||
|
) where
|
||||||
|
T: ActiveLineTrailerProvider + 'static,
|
||||||
|
{
|
||||||
|
self.active_line_trailer_provider = provider.map(|provider| Box::new(provider) as Box<_>);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
||||||
self.placeholder_text.as_deref()
|
self.placeholder_text.as_deref()
|
||||||
}
|
}
|
||||||
|
@ -11844,6 +11865,21 @@ impl Editor {
|
||||||
&& self.has_blame_entries(cx)
|
&& self.has_blame_entries(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_active_line_trailer(
|
||||||
|
&mut self,
|
||||||
|
style: &EditorStyle,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<AnyElement> {
|
||||||
|
if !self.newest_selection_head_on_empty_line(cx) || self.has_active_inline_completion(cx) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
self.active_line_trailer_provider
|
||||||
|
.as_mut()?
|
||||||
|
.render_active_line_trailer(style, &focus_handle, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
|
fn has_blame_entries(&self, cx: &mut WindowContext) -> bool {
|
||||||
self.blame()
|
self.blame()
|
||||||
.map_or(false, |blame| blame.read(cx).has_generated_entries())
|
.map_or(false, |blame| blame.read(cx).has_generated_entries())
|
||||||
|
|
|
@ -1412,7 +1412,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn layout_inline_blame(
|
fn layout_active_line_trailer(
|
||||||
&self,
|
&self,
|
||||||
display_row: DisplayRow,
|
display_row: DisplayRow,
|
||||||
display_snapshot: &DisplaySnapshot,
|
display_snapshot: &DisplaySnapshot,
|
||||||
|
@ -1424,61 +1424,71 @@ impl EditorElement {
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
if !self
|
let render_inline_blame = self
|
||||||
.editor
|
.editor
|
||||||
.update(cx, |editor, cx| editor.render_git_blame_inline(cx))
|
.update(cx, |editor, cx| editor.render_git_blame_inline(cx));
|
||||||
{
|
if render_inline_blame {
|
||||||
return None;
|
let workspace = self
|
||||||
}
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.workspace
|
||||||
|
.as_ref()
|
||||||
|
.map(|(w, _)| w.clone());
|
||||||
|
|
||||||
let workspace = self
|
let display_point = DisplayPoint::new(display_row, 0);
|
||||||
.editor
|
let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
|
||||||
.read(cx)
|
|
||||||
.workspace
|
|
||||||
.as_ref()
|
|
||||||
.map(|(w, _)| w.clone());
|
|
||||||
|
|
||||||
let display_point = DisplayPoint::new(display_row, 0);
|
let blame = self.editor.read(cx).blame.clone()?;
|
||||||
let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row);
|
let blame_entry = blame
|
||||||
|
.update(cx, |blame, cx| {
|
||||||
|
blame.blame_for_rows([Some(buffer_row)], cx).next()
|
||||||
|
})
|
||||||
|
.flatten()?;
|
||||||
|
|
||||||
let blame = self.editor.read(cx).blame.clone()?;
|
let mut element =
|
||||||
let blame_entry = blame
|
render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
|
||||||
.update(cx, |blame, cx| {
|
|
||||||
blame.blame_for_rows([Some(buffer_row)], cx).next()
|
|
||||||
})
|
|
||||||
.flatten()?;
|
|
||||||
|
|
||||||
let mut element =
|
let start_y = content_origin.y
|
||||||
render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx);
|
+ line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
|
||||||
|
|
||||||
let start_y = content_origin.y
|
let start_x = {
|
||||||
+ line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
|
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||||
|
|
||||||
let start_x = {
|
let line_end = if let Some(crease_trailer) = crease_trailer {
|
||||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
crease_trailer.bounds.right()
|
||||||
|
} else {
|
||||||
|
content_origin.x - scroll_pixel_position.x + line_layout.width
|
||||||
|
};
|
||||||
|
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
||||||
|
|
||||||
let line_end = if let Some(crease_trailer) = crease_trailer {
|
let min_column_in_pixels = ProjectSettings::get_global(cx)
|
||||||
crease_trailer.bounds.right()
|
.git
|
||||||
} else {
|
.inline_blame
|
||||||
content_origin.x - scroll_pixel_position.x + line_layout.width
|
.and_then(|settings| settings.min_column)
|
||||||
|
.map(|col| self.column_pixels(col as usize, cx))
|
||||||
|
.unwrap_or(px(0.));
|
||||||
|
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
|
||||||
|
|
||||||
|
cmp::max(padded_line_end, min_start)
|
||||||
};
|
};
|
||||||
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
|
||||||
|
|
||||||
let min_column_in_pixels = ProjectSettings::get_global(cx)
|
let absolute_offset = point(start_x, start_y);
|
||||||
.git
|
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
||||||
.inline_blame
|
|
||||||
.and_then(|settings| settings.min_column)
|
|
||||||
.map(|col| self.column_pixels(col as usize, cx))
|
|
||||||
.unwrap_or(px(0.));
|
|
||||||
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
|
|
||||||
|
|
||||||
cmp::max(padded_line_end, min_start)
|
Some(element)
|
||||||
};
|
} else if let Some(mut element) = self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.render_active_line_trailer(&self.style, cx)
|
||||||
|
}) {
|
||||||
|
let start_y = content_origin.y
|
||||||
|
+ line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height);
|
||||||
|
let start_x = content_origin.x - scroll_pixel_position.x + em_width;
|
||||||
|
let absolute_offset = point(start_x, start_y);
|
||||||
|
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
||||||
|
|
||||||
let absolute_offset = point(start_x, start_y);
|
Some(element)
|
||||||
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
} else {
|
||||||
|
None
|
||||||
Some(element)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
@ -3454,7 +3464,7 @@ impl EditorElement {
|
||||||
self.paint_lines(&invisible_display_ranges, layout, cx);
|
self.paint_lines(&invisible_display_ranges, layout, cx);
|
||||||
self.paint_redactions(layout, cx);
|
self.paint_redactions(layout, cx);
|
||||||
self.paint_cursors(layout, cx);
|
self.paint_cursors(layout, cx);
|
||||||
self.paint_inline_blame(layout, cx);
|
self.paint_active_line_trailer(layout, cx);
|
||||||
cx.with_element_namespace("crease_trailers", |cx| {
|
cx.with_element_namespace("crease_trailers", |cx| {
|
||||||
for trailer in layout.crease_trailers.iter_mut().flatten() {
|
for trailer in layout.crease_trailers.iter_mut().flatten() {
|
||||||
trailer.element.paint(cx);
|
trailer.element.paint(cx);
|
||||||
|
@ -3936,10 +3946,10 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
fn paint_active_line_trailer(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
if let Some(mut inline_blame) = layout.inline_blame.take() {
|
if let Some(mut element) = layout.active_line_trailer.take() {
|
||||||
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
||||||
inline_blame.paint(cx);
|
element.paint(cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5331,14 +5341,14 @@ impl Element for EditorElement {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut inline_blame = None;
|
let mut active_line_trailer = None;
|
||||||
if let Some(newest_selection_head) = newest_selection_head {
|
if let Some(newest_selection_head) = newest_selection_head {
|
||||||
let display_row = newest_selection_head.row();
|
let display_row = newest_selection_head.row();
|
||||||
if (start_row..end_row).contains(&display_row) {
|
if (start_row..end_row).contains(&display_row) {
|
||||||
let line_ix = display_row.minus(start_row) as usize;
|
let line_ix = display_row.minus(start_row) as usize;
|
||||||
let line_layout = &line_layouts[line_ix];
|
let line_layout = &line_layouts[line_ix];
|
||||||
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
|
let crease_trailer_layout = crease_trailers[line_ix].as_ref();
|
||||||
inline_blame = self.layout_inline_blame(
|
active_line_trailer = self.layout_active_line_trailer(
|
||||||
display_row,
|
display_row,
|
||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
line_layout,
|
line_layout,
|
||||||
|
@ -5657,7 +5667,7 @@ impl Element for EditorElement {
|
||||||
line_elements,
|
line_elements,
|
||||||
line_numbers,
|
line_numbers,
|
||||||
blamed_display_rows,
|
blamed_display_rows,
|
||||||
inline_blame,
|
active_line_trailer,
|
||||||
blocks,
|
blocks,
|
||||||
cursors,
|
cursors,
|
||||||
visible_cursors,
|
visible_cursors,
|
||||||
|
@ -5794,7 +5804,7 @@ pub struct EditorLayout {
|
||||||
line_numbers: Vec<Option<ShapedLine>>,
|
line_numbers: Vec<Option<ShapedLine>>,
|
||||||
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
||||||
blamed_display_rows: Option<Vec<AnyElement>>,
|
blamed_display_rows: Option<Vec<AnyElement>>,
|
||||||
inline_blame: Option<AnyElement>,
|
active_line_trailer: Option<AnyElement>,
|
||||||
blocks: Vec<BlockLayout>,
|
blocks: Vec<BlockLayout>,
|
||||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||||
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||||
|
|
|
@ -3050,7 +3050,7 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represent this action as a key binding string, to display in the UI.
|
/// Represent this action as a key binding string, to display in the UI.
|
||||||
pub fn keystroke_text_for(&self, action: &dyn Action) -> String {
|
pub fn keystroke_text_for_action(&self, action: &dyn Action) -> String {
|
||||||
self.bindings_for_action(action)
|
self.bindings_for_action(action)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
|
@ -3065,6 +3065,26 @@ impl<'a> WindowContext<'a> {
|
||||||
.unwrap_or_else(|| action.name().to_string())
|
.unwrap_or_else(|| action.name().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represent this action as a key binding string, to display in the UI.
|
||||||
|
pub fn keystroke_text_for_action_in(
|
||||||
|
&self,
|
||||||
|
action: &dyn Action,
|
||||||
|
focus_handle: &FocusHandle,
|
||||||
|
) -> String {
|
||||||
|
self.bindings_for_action_in(action, focus_handle)
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|binding| {
|
||||||
|
binding
|
||||||
|
.keystrokes()
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| action.name().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
/// Dispatch a mouse or keyboard event on the window.
|
/// Dispatch a mouse or keyboard event on the window.
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult {
|
pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult {
|
||||||
|
|
|
@ -3875,13 +3875,13 @@ impl OutlinePanel {
|
||||||
.child({
|
.child({
|
||||||
let keystroke = match self.position(cx) {
|
let keystroke = match self.position(cx) {
|
||||||
DockPosition::Left => {
|
DockPosition::Left => {
|
||||||
cx.keystroke_text_for(&workspace::ToggleLeftDock)
|
cx.keystroke_text_for_action(&workspace::ToggleLeftDock)
|
||||||
}
|
}
|
||||||
DockPosition::Bottom => {
|
DockPosition::Bottom => {
|
||||||
cx.keystroke_text_for(&workspace::ToggleBottomDock)
|
cx.keystroke_text_for_action(&workspace::ToggleBottomDock)
|
||||||
}
|
}
|
||||||
DockPosition::Right => {
|
DockPosition::Right => {
|
||||||
cx.keystroke_text_for(&workspace::ToggleRightDock)
|
cx.keystroke_text_for_action(&workspace::ToggleRightDock)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Label::new(format!("Toggle this panel with {keystroke}"))
|
Label::new(format!("Toggle this panel with {keystroke}"))
|
||||||
|
|
|
@ -185,13 +185,13 @@ impl PickerDelegate for RecentProjectsDelegate {
|
||||||
fn placeholder_text(&self, cx: &mut WindowContext) -> Arc<str> {
|
fn placeholder_text(&self, cx: &mut WindowContext) -> Arc<str> {
|
||||||
let (create_window, reuse_window) = if self.create_new_window {
|
let (create_window, reuse_window) = if self.create_new_window {
|
||||||
(
|
(
|
||||||
cx.keystroke_text_for(&menu::Confirm),
|
cx.keystroke_text_for_action(&menu::Confirm),
|
||||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
cx.keystroke_text_for_action(&menu::SecondaryConfirm),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
cx.keystroke_text_for(&menu::SecondaryConfirm),
|
cx.keystroke_text_for_action(&menu::SecondaryConfirm),
|
||||||
cx.keystroke_text_for(&menu::Confirm),
|
cx.keystroke_text_for_action(&menu::Confirm),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
Arc::from(format!(
|
Arc::from(format!(
|
||||||
|
|
|
@ -66,7 +66,7 @@ use zed::{
|
||||||
OpenRequest,
|
OpenRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::zed::inline_completion_registry;
|
use crate::zed::{assistant_hints, inline_completion_registry};
|
||||||
|
|
||||||
#[cfg(feature = "mimalloc")]
|
#[cfg(feature = "mimalloc")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
|
@ -401,6 +401,7 @@ fn main() {
|
||||||
stdout_is_a_pty(),
|
stdout_is_a_pty(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
assistant_hints::init(cx);
|
||||||
repl::init(
|
repl::init(
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
app_state.client.telemetry().clone(),
|
app_state.client.telemetry().clone(),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod app_menus;
|
mod app_menus;
|
||||||
|
pub mod assistant_hints;
|
||||||
pub mod inline_completion_registry;
|
pub mod inline_completion_registry;
|
||||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||||
pub(crate) mod linux_prompts;
|
pub(crate) mod linux_prompts;
|
||||||
|
|
115
crates/zed/src/zed/assistant_hints.rs
Normal file
115
crates/zed/src/zed/assistant_hints.rs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
use assistant::assistant_settings::AssistantSettings;
|
||||||
|
use collections::HashMap;
|
||||||
|
use editor::{ActiveLineTrailerProvider, Editor, EditorMode};
|
||||||
|
use gpui::{AnyWindowHandle, AppContext, ViewContext, WeakView, WindowContext};
|
||||||
|
use settings::{Settings, SettingsStore};
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::prelude::*;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
let editors: Rc<RefCell<HashMap<WeakView<Editor>, AnyWindowHandle>>> = Rc::default();
|
||||||
|
|
||||||
|
cx.observe_new_views({
|
||||||
|
let editors = editors.clone();
|
||||||
|
move |_: &mut Workspace, cx: &mut ViewContext<Workspace>| {
|
||||||
|
let workspace_handle = cx.view().clone();
|
||||||
|
cx.subscribe(&workspace_handle, {
|
||||||
|
let editors = editors.clone();
|
||||||
|
move |_, _, event, cx| match event {
|
||||||
|
workspace::Event::ItemAdded { item } => {
|
||||||
|
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||||
|
if editor.read(cx).mode() != EditorMode::Full {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.on_release({
|
||||||
|
let editor_handle = editor.downgrade();
|
||||||
|
let editors = editors.clone();
|
||||||
|
move |_, _, _| {
|
||||||
|
editors.borrow_mut().remove(&editor_handle);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
editors
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(editor.downgrade(), cx.window_handle());
|
||||||
|
|
||||||
|
let show_hints = should_show_hints(cx);
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
assign_active_line_trailer_provider(editor, show_hints, cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
let mut show_hints = AssistantSettings::get_global(cx).show_hints;
|
||||||
|
cx.observe_global::<SettingsStore>(move |cx| {
|
||||||
|
let new_show_hints = should_show_hints(cx);
|
||||||
|
if new_show_hints != show_hints {
|
||||||
|
show_hints = new_show_hints;
|
||||||
|
for (editor, window) in editors.borrow().iter() {
|
||||||
|
_ = window.update(cx, |_window, cx| {
|
||||||
|
_ = editor.update(cx, |editor, cx| {
|
||||||
|
assign_active_line_trailer_provider(editor, show_hints, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AssistantHintsProvider;
|
||||||
|
|
||||||
|
impl ActiveLineTrailerProvider for AssistantHintsProvider {
|
||||||
|
fn render_active_line_trailer(
|
||||||
|
&mut self,
|
||||||
|
style: &editor::EditorStyle,
|
||||||
|
focus_handle: &gpui::FocusHandle,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<gpui::AnyElement> {
|
||||||
|
if !focus_handle.is_focused(cx) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let chat_keybinding =
|
||||||
|
cx.keystroke_text_for_action_in(&assistant::ToggleFocus, focus_handle);
|
||||||
|
let generate_keybinding =
|
||||||
|
cx.keystroke_text_for_action_in(&zed_actions::InlineAssist::default(), focus_handle);
|
||||||
|
|
||||||
|
Some(
|
||||||
|
h_flex()
|
||||||
|
.id("inline-assistant-instructions")
|
||||||
|
.w_full()
|
||||||
|
.font_family(style.text.font().family)
|
||||||
|
.text_color(cx.theme().status().hint)
|
||||||
|
.line_height(style.text.line_height)
|
||||||
|
.child(format!(
|
||||||
|
"{chat_keybinding} to chat, {generate_keybinding} to generate"
|
||||||
|
))
|
||||||
|
.into_any(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_active_line_trailer_provider(
|
||||||
|
editor: &mut Editor,
|
||||||
|
show_hints: bool,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let provider = show_hints.then_some(AssistantHintsProvider);
|
||||||
|
editor.set_active_line_trailer_provider(provider, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_show_hints(cx: &AppContext) -> bool {
|
||||||
|
let assistant_settings = AssistantSettings::get_global(cx);
|
||||||
|
assistant_settings.enabled && assistant_settings.show_hints
|
||||||
|
}
|
|
@ -200,18 +200,28 @@ You must provide the model's Context Window in the `max_tokens` parameter, this
|
||||||
{
|
{
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"show_hints": true,
|
||||||
|
"button": true,
|
||||||
|
"dock": "right"
|
||||||
|
"default_width": 480,
|
||||||
"default_model": {
|
"default_model": {
|
||||||
"provider": "zed.dev",
|
"provider": "zed.dev",
|
||||||
"model": "claude-3-5-sonnet"
|
"model": "claude-3-5-sonnet"
|
||||||
},
|
},
|
||||||
"version": "2",
|
"version": "2",
|
||||||
"button": true,
|
|
||||||
"default_width": 480,
|
|
||||||
"dock": "right"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| key | type | default | description |
|
||||||
|
| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| enabled | boolean | true | Setting this to `false` will completely disable the assistant |
|
||||||
|
| show_hints | boolean | true | Whether to to show hints in the editor explaining how to use assistant |
|
||||||
|
| button | boolean | true | Show the assistant icon in the status bar |
|
||||||
|
| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] |
|
||||||
|
| default_height | string | null | The pixel height of the assistant panel when docked to the bottom |
|
||||||
|
| default_width | string | null | The pixel width of the assistant panel when docked to the left or right |
|
||||||
|
|
||||||
#### Custom endpoints {#custom-endpoint}
|
#### Custom endpoints {#custom-endpoint}
|
||||||
|
|
||||||
You can use a custom API endpoint for different providers, as long as it's compatible with the providers API structure.
|
You can use a custom API endpoint for different providers, as long as it's compatible with the providers API structure.
|
||||||
|
@ -271,13 +281,3 @@ will generate two outputs for every assist. One with Claude 3.5 Sonnet, and one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Common Panel Settings
|
|
||||||
|
|
||||||
| key | type | default | description |
|
|
||||||
| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- |
|
|
||||||
| enabled | boolean | true | Setting this to `false` will completely disable the assistant |
|
|
||||||
| button | boolean | true | Show the assistant icon in the status bar |
|
|
||||||
| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] |
|
|
||||||
| default_height | string | null | The pixel height of the assistant panel when docked to the bottom |
|
|
||||||
| default_width | string | null | The pixel width of the assistant panel when docked to the left or right |
|
|
||||||
|
|
|
@ -2327,15 +2327,18 @@ Run the `theme selector: toggle` action in the command palette to see a current
|
||||||
- Default:
|
- Default:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"assistant": {
|
{
|
||||||
"enabled": true,
|
"assistant": {
|
||||||
"button": true,
|
"enabled": true,
|
||||||
"dock": "right",
|
"button": true,
|
||||||
"default_width": 640,
|
"dock": "right",
|
||||||
"default_height": 320,
|
"default_width": 640,
|
||||||
"provider": "openai",
|
"default_height": 320,
|
||||||
"version": "1",
|
"provider": "openai",
|
||||||
},
|
"version": "1",
|
||||||
|
"show_hints": true
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Outline Panel
|
## Outline Panel
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue