agent: Improve edit file tool card (#29448)
⚠️ Work in progress until all of the to-dos are knocked out:
- [x] Disable soft-wrapping
- [x] Make it foldable only after a certain number of lines
- [x] Display tool status errors
- [x] Fix horizontal scroll now that we've disabled soft-wrap
- [ ] Don't render unnecessary extra lines (will be added later, on a
follow-up PR)
Release Notes:
- N/A
---------
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Michael Sloan <mgsloan@gmail.com>
This commit is contained in:
parent
fbb0fe40ec
commit
128b7d2245
4 changed files with 134 additions and 62 deletions
|
@ -11,6 +11,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, Buffer, Capability, LanguageRegistry, LineEnding, OffsetRangeExt, Rope, TextBuffer,
|
Anchor, Buffer, Capability, LanguageRegistry, LineEnding, OffsetRangeExt, Rope, TextBuffer,
|
||||||
|
language_settings::SoftWrap,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
@ -274,7 +275,9 @@ pub struct EditFileToolCard {
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
diff_task: Option<Task<Result<()>>>,
|
diff_task: Option<Task<Result<()>>>,
|
||||||
preview_expanded: bool,
|
preview_expanded: bool,
|
||||||
|
error_expanded: bool,
|
||||||
full_height_expanded: bool,
|
full_height_expanded: bool,
|
||||||
|
total_lines: Option<u32>,
|
||||||
editor_unique_id: EntityId,
|
editor_unique_id: EntityId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,11 +296,13 @@ impl EditFileToolCard {
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
editor.set_show_scrollbars(false, cx);
|
|
||||||
editor.set_show_gutter(false, cx);
|
editor.set_show_gutter(false, cx);
|
||||||
editor.disable_inline_diagnostics();
|
editor.disable_inline_diagnostics();
|
||||||
editor.disable_scrolling(cx);
|
|
||||||
editor.disable_expand_excerpt_buttons(cx);
|
editor.disable_expand_excerpt_buttons(cx);
|
||||||
|
editor.set_soft_wrap_mode(SoftWrap::None, cx);
|
||||||
|
editor.scroll_manager.set_forbid_vertical_scroll(true);
|
||||||
|
editor.set_show_scrollbars(false, cx);
|
||||||
|
editor.set_read_only(true);
|
||||||
editor.set_show_breakpoints(false, cx);
|
editor.set_show_breakpoints(false, cx);
|
||||||
editor.set_show_code_actions(false, cx);
|
editor.set_show_code_actions(false, cx);
|
||||||
editor.set_show_git_diff_gutter(false, cx);
|
editor.set_show_git_diff_gutter(false, cx);
|
||||||
|
@ -312,7 +317,9 @@ impl EditFileToolCard {
|
||||||
multibuffer,
|
multibuffer,
|
||||||
diff_task: None,
|
diff_task: None,
|
||||||
preview_expanded: true,
|
preview_expanded: true,
|
||||||
|
error_expanded: false,
|
||||||
full_height_expanded: false,
|
full_height_expanded: false,
|
||||||
|
total_lines: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +336,7 @@ impl EditFileToolCard {
|
||||||
let buffer_diff = build_buffer_diff(old_text, &buffer, &language_registry, cx).await?;
|
let buffer_diff = build_buffer_diff(old_text, &buffer, &language_registry, cx).await?;
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.multibuffer.update(cx, |multibuffer, cx| {
|
this.total_lines = this.multibuffer.update(cx, |multibuffer, cx| {
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
let diff = buffer_diff.read(cx);
|
let diff = buffer_diff.read(cx);
|
||||||
let diff_hunk_ranges = diff
|
let diff_hunk_ranges = diff
|
||||||
|
@ -345,7 +352,10 @@ impl EditFileToolCard {
|
||||||
);
|
);
|
||||||
debug_assert!(is_newly_added);
|
debug_assert!(is_newly_added);
|
||||||
multibuffer.add_diff(buffer_diff, cx);
|
multibuffer.add_diff(buffer_diff, cx);
|
||||||
|
let end = multibuffer.len(cx);
|
||||||
|
Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
@ -360,7 +370,10 @@ impl ToolCard for EditFileToolCard {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let failed = matches!(status, ToolUseStatus::Error(_));
|
let (failed, error_message) = match status {
|
||||||
|
ToolUseStatus::Error(err) => (true, Some(err.to_string())),
|
||||||
|
_ => (false, None),
|
||||||
|
};
|
||||||
|
|
||||||
let path_label_button = h_flex()
|
let path_label_button = h_flex()
|
||||||
.id(("edit-tool-path-label-button", self.editor_unique_id))
|
.id(("edit-tool-path-label-button", self.editor_unique_id))
|
||||||
|
@ -452,9 +465,26 @@ impl ToolCard for EditFileToolCard {
|
||||||
.map(|container| {
|
.map(|container| {
|
||||||
if failed {
|
if failed {
|
||||||
container.child(
|
container.child(
|
||||||
Icon::new(IconName::Close)
|
h_flex()
|
||||||
.size(IconSize::Small)
|
.gap_1()
|
||||||
.color(Color::Error),
|
.child(
|
||||||
|
Icon::new(IconName::Close)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Error),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Disclosure::new(
|
||||||
|
("edit-file-error-disclosure", self.editor_unique_id),
|
||||||
|
self.error_expanded,
|
||||||
|
)
|
||||||
|
.opened_icon(IconName::ChevronUp)
|
||||||
|
.closed_icon(IconName::ChevronDown)
|
||||||
|
.on_click(cx.listener(
|
||||||
|
move |this, _event, _window, _cx| {
|
||||||
|
this.error_expanded = !this.error_expanded;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
container.child(
|
container.child(
|
||||||
|
@ -473,8 +503,14 @@ impl ToolCard for EditFileToolCard {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let editor = self.editor.update(cx, |editor, cx| {
|
let (editor, editor_line_height) = self.editor.update(cx, |editor, cx| {
|
||||||
editor.render(window, cx).into_any_element()
|
let line_height = editor
|
||||||
|
.style()
|
||||||
|
.map(|style| style.text.line_height_in_pixels(window.rem_size()))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let element = editor.render(window, cx);
|
||||||
|
(element.into_any_element(), line_height)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (full_height_icon, full_height_tooltip_label) = if self.full_height_expanded {
|
let (full_height_icon, full_height_tooltip_label) = if self.full_height_expanded {
|
||||||
|
@ -498,6 +534,9 @@ impl ToolCard for EditFileToolCard {
|
||||||
|
|
||||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||||
|
|
||||||
|
const DEFAULT_COLLAPSED_LINES: u32 = 10;
|
||||||
|
let is_collapsible = self.total_lines.unwrap_or(0) > DEFAULT_COLLAPSED_LINES;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.mb_2()
|
.mb_2()
|
||||||
.border_1()
|
.border_1()
|
||||||
|
@ -506,50 +545,79 @@ impl ToolCard for EditFileToolCard {
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(codeblock_header)
|
.child(codeblock_header)
|
||||||
.when(!failed && self.preview_expanded, |card| {
|
.when(failed && self.error_expanded, |card| {
|
||||||
card.child(
|
card.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.relative()
|
.p_2()
|
||||||
.overflow_hidden()
|
.gap_1()
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
|
.border_dashed()
|
||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.map(|editor_container| {
|
.rounded_b_md()
|
||||||
if self.full_height_expanded {
|
.child(
|
||||||
editor_container.h_full()
|
Label::new("Error")
|
||||||
} else {
|
.size(LabelSize::XSmall)
|
||||||
editor_container.max_h_64()
|
.color(Color::Error),
|
||||||
}
|
)
|
||||||
})
|
.child(
|
||||||
.child(div().pl_1().child(editor))
|
div()
|
||||||
.when(!self.full_height_expanded, |editor_container| {
|
.rounded_md()
|
||||||
editor_container.child(gradient_overlay)
|
.text_ui_sm(cx)
|
||||||
}),
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.children(
|
||||||
|
error_message
|
||||||
|
.map(|error| div().child(error).into_any_element()),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(!failed && self.preview_expanded, |card| {
|
.when(!failed && self.preview_expanded, |card| {
|
||||||
card.child(
|
card.child(
|
||||||
h_flex()
|
v_flex()
|
||||||
.id(("edit-tool-card-inner-hflex", self.editor_unique_id))
|
.relative()
|
||||||
.flex_none()
|
.map(|editor_container| {
|
||||||
.cursor_pointer()
|
if self.full_height_expanded {
|
||||||
.h_5()
|
editor_container.h_full()
|
||||||
.justify_center()
|
} else {
|
||||||
.rounded_b_md()
|
editor_container
|
||||||
|
.h(DEFAULT_COLLAPSED_LINES as f32 * editor_line_height)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.overflow_hidden()
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(border_color)
|
.border_color(border_color)
|
||||||
.bg(cx.theme().colors().editor_background)
|
.bg(cx.theme().colors().editor_background)
|
||||||
.hover(|style| style.bg(cx.theme().colors().element_hover.opacity(0.1)))
|
.child(div().pl_1().child(editor))
|
||||||
.child(
|
.when(
|
||||||
Icon::new(full_height_icon)
|
!self.full_height_expanded && is_collapsible,
|
||||||
.size(IconSize::Small)
|
|editor_container| editor_container.child(gradient_overlay),
|
||||||
.color(Color::Muted),
|
),
|
||||||
)
|
|
||||||
.tooltip(Tooltip::text(full_height_tooltip_label))
|
|
||||||
.on_click(cx.listener(move |this, _event, _window, _cx| {
|
|
||||||
this.full_height_expanded = !this.full_height_expanded;
|
|
||||||
})),
|
|
||||||
)
|
)
|
||||||
|
.when(is_collapsible, |editor_container| {
|
||||||
|
editor_container.child(
|
||||||
|
h_flex()
|
||||||
|
.id(("expand-button", self.editor_unique_id))
|
||||||
|
.flex_none()
|
||||||
|
.cursor_pointer()
|
||||||
|
.h_5()
|
||||||
|
.justify_center()
|
||||||
|
.rounded_b_md()
|
||||||
|
.border_t_1()
|
||||||
|
.border_color(border_color)
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.hover(|style| style.bg(cx.theme().colors().element_hover.opacity(0.1)))
|
||||||
|
.child(
|
||||||
|
Icon::new(full_height_icon)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.tooltip(Tooltip::text(full_height_tooltip_label))
|
||||||
|
.on_click(cx.listener(move |this, _event, _window, _cx| {
|
||||||
|
this.full_height_expanded = !this.full_height_expanded;
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -871,7 +871,6 @@ pub struct Editor {
|
||||||
show_breadcrumbs: bool,
|
show_breadcrumbs: bool,
|
||||||
show_gutter: bool,
|
show_gutter: bool,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
disable_scrolling: bool,
|
|
||||||
disable_expand_excerpt_buttons: bool,
|
disable_expand_excerpt_buttons: bool,
|
||||||
show_line_numbers: Option<bool>,
|
show_line_numbers: Option<bool>,
|
||||||
use_relative_line_numbers: Option<bool>,
|
use_relative_line_numbers: Option<bool>,
|
||||||
|
@ -1668,7 +1667,6 @@ impl Editor {
|
||||||
blink_manager: blink_manager.clone(),
|
blink_manager: blink_manager.clone(),
|
||||||
show_local_selections: true,
|
show_local_selections: true,
|
||||||
show_scrollbars: true,
|
show_scrollbars: true,
|
||||||
disable_scrolling: false,
|
|
||||||
mode,
|
mode,
|
||||||
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
|
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
|
||||||
show_gutter: mode.is_full(),
|
show_gutter: mode.is_full(),
|
||||||
|
@ -16487,11 +16485,6 @@ impl Editor {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
|
|
||||||
self.disable_scrolling = true;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
|
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
|
||||||
self.show_line_numbers = Some(show_line_numbers);
|
self.show_line_numbers = Some(show_line_numbers);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
@ -5678,9 +5678,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
|
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
|
||||||
if !self.editor.read(cx).disable_scrolling {
|
self.paint_scroll_wheel_listener(layout, window, cx);
|
||||||
self.paint_scroll_wheel_listener(layout, window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let position_map = layout.position_map.clone();
|
let position_map = layout.position_map.clone();
|
||||||
|
|
|
@ -184,9 +184,6 @@ impl ScrollManager {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) {
|
) {
|
||||||
if self.forbid_vertical_scroll {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let (new_anchor, top_row) = if scroll_position.y <= 0. {
|
let (new_anchor, top_row) = if scroll_position.y <= 0. {
|
||||||
(
|
(
|
||||||
ScrollAnchor {
|
ScrollAnchor {
|
||||||
|
@ -258,10 +255,16 @@ impl ScrollManager {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) {
|
) {
|
||||||
if self.forbid_vertical_scroll {
|
let adjusted_anchor = if self.forbid_vertical_scroll {
|
||||||
return;
|
ScrollAnchor {
|
||||||
}
|
offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y),
|
||||||
self.anchor = anchor;
|
anchor: self.anchor.anchor,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anchor
|
||||||
|
};
|
||||||
|
|
||||||
|
self.anchor = adjusted_anchor;
|
||||||
cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
|
cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
|
||||||
self.show_scrollbars(window, cx);
|
self.show_scrollbars(window, cx);
|
||||||
self.autoscroll_request.take();
|
self.autoscroll_request.take();
|
||||||
|
@ -404,11 +407,12 @@ impl Editor {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
let mut delta = scroll_delta;
|
||||||
if self.scroll_manager.forbid_vertical_scroll {
|
if self.scroll_manager.forbid_vertical_scroll {
|
||||||
return;
|
delta.y = 0.0;
|
||||||
}
|
}
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let position = self.scroll_manager.anchor.scroll_position(&display_map) + scroll_delta;
|
let position = self.scroll_manager.anchor.scroll_position(&display_map) + delta;
|
||||||
self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
|
self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,10 +422,12 @@ impl Editor {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
let mut position = scroll_position;
|
||||||
if self.scroll_manager.forbid_vertical_scroll {
|
if self.scroll_manager.forbid_vertical_scroll {
|
||||||
return;
|
let current_position = self.scroll_position(cx);
|
||||||
|
position.y = current_position.y;
|
||||||
}
|
}
|
||||||
self.set_scroll_position_internal(scroll_position, true, false, window, cx);
|
self.set_scroll_position_internal(position, true, false, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrolls so that `row` is at the top of the editor view.
|
/// Scrolls so that `row` is at the top of the editor view.
|
||||||
|
@ -480,8 +486,15 @@ impl Editor {
|
||||||
self.edit_prediction_preview
|
self.edit_prediction_preview
|
||||||
.set_previous_scroll_position(None);
|
.set_previous_scroll_position(None);
|
||||||
|
|
||||||
|
let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
|
||||||
|
let current_position = self.scroll_manager.anchor.scroll_position(&display_map);
|
||||||
|
gpui::Point::new(scroll_position.x, current_position.y)
|
||||||
|
} else {
|
||||||
|
scroll_position
|
||||||
|
};
|
||||||
|
|
||||||
self.scroll_manager.set_scroll_position(
|
self.scroll_manager.set_scroll_position(
|
||||||
scroll_position,
|
adjusted_position,
|
||||||
&display_map,
|
&display_map,
|
||||||
local,
|
local,
|
||||||
autoscroll,
|
autoscroll,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue