assistant: Improve quote selection (#16038)

https://github.com/user-attachments/assets/fe283eec-169f-4dfd-bae2-cc72c1c3f223



Release Notes:

- Include more context when using `assistant: Quote selection` to insert
text into the assistant panel

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
This commit is contained in:
Bennet Bo Fenner 2024-08-09 18:29:27 +02:00 committed by GitHub
parent 46a4dd3ab1
commit c633fa5a10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -6,6 +6,7 @@ use crate::{
slash_command::{
default_command::DefaultSlashCommand,
docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
file_command::codeblock_fence_for_path,
SlashCommandCompletionProvider, SlashCommandRegistry,
},
terminal_inline_assistant::TerminalInlineAssistant,
@ -2929,6 +2930,11 @@ impl ContextEditor {
let buffer = editor.buffer().read(cx).snapshot(cx);
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
..editor::ToOffset::to_offset(&selection.end, &buffer);
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
if selected_text.is_empty() {
return;
}
let start_language = buffer.language_at(range.start);
let end_language = buffer.language_at(range.end);
let language_name = if start_language == end_language {
@ -2938,19 +2944,66 @@ impl ContextEditor {
};
let language_name = language_name.as_deref().unwrap_or("");
let selected_text = buffer.text_for_range(range).collect::<String>();
let text = if selected_text.is_empty() {
None
let filename = buffer
.file_at(selection.start)
.map(|file| file.full_path(cx));
let text = if language_name == "markdown" {
selected_text
.lines()
.map(|line| format!("> {}", line))
.collect::<Vec<_>>()
.join("\n")
} else {
Some(if language_name == "markdown" {
selected_text
.lines()
.map(|line| format!("> {}", line))
.collect::<Vec<_>>()
.join("\n")
let start_symbols = buffer
.symbols_containing(selection.start, None)
.map(|(_, symbols)| symbols);
let end_symbols = buffer
.symbols_containing(selection.end, None)
.map(|(_, symbols)| symbols);
let outline_text =
if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
Some(
start_symbols
.into_iter()
.zip(end_symbols)
.take_while(|(a, b)| a == b)
.map(|(a, _)| a.text)
.collect::<Vec<_>>()
.join(" > "),
)
} else {
None
};
let line_comment_prefix = start_language
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
let fence = codeblock_fence_for_path(
filename.as_deref(),
Some(selection.start.row..selection.end.row),
);
if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text)
{
let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
format!("{fence}{breadcrumb}{selected_text}\n```")
} else {
format!("```{language_name}\n{selected_text}\n```")
})
format!("{fence}{selected_text}\n```")
}
};
let crease_title = if let Some(path) = filename {
let start_line = selection.start.row + 1;
let end_line = selection.end.row + 1;
if start_line == end_line {
format!("{}, Line {}", path.display(), start_line)
} else {
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
}
} else {
"Quoted selection".to_string()
};
// Activate the panel
@ -2958,24 +3011,55 @@ impl ContextEditor {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
}
if let Some(text) = text {
panel.update(cx, |_, cx| {
// Wait to create a new context until the workspace is no longer
// being updated.
cx.defer(move |panel, cx| {
if let Some(context) = panel
.active_context_editor(cx)
.or_else(|| panel.new_context(cx))
{
context.update(cx, |context, cx| {
context
.editor
.update(cx, |editor, cx| editor.insert(&text, cx))
});
};
});
panel.update(cx, |_, cx| {
// Wait to create a new context until the workspace is no longer
// being updated.
cx.defer(move |panel, cx| {
if let Some(context) = panel
.active_context_editor(cx)
.or_else(|| panel.new_context(cx))
{
context.update(cx, |context, cx| {
context.editor.update(cx, |editor, cx| {
editor.insert("\n", cx);
let point = editor.selections.newest::<Point>(cx).head();
let start_row = MultiBufferRow(point.row);
editor.insert(&text, cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let anchor_before = snapshot.anchor_after(point);
let anchor_after = editor
.selections
.newest_anchor()
.head()
.bias_left(&snapshot);
editor.insert("\n", cx);
let fold_placeholder = quote_selection_fold_placeholder(
crease_title,
cx.view().downgrade(),
);
let crease = Crease::new(
anchor_before..anchor_after,
fold_placeholder,
render_quote_selection_output_toggle,
|_, _, _| Empty.into_any(),
);
editor.insert_creases(vec![crease], cx);
editor.fold_at(
&FoldAt {
buffer_row: start_row,
},
cx,
);
})
});
};
});
}
});
}
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
@ -4028,6 +4112,47 @@ fn render_slash_command_output_toggle(
.into_any_element()
}
fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
FoldPlaceholder {
render: Arc::new({
move |fold_id, fold_range, _cx| {
let editor = editor.clone();
ButtonLike::new(fold_id)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface)
.child(Icon::new(IconName::FileText))
.child(Label::new(title.clone()).single_line())
.on_click(move |_, cx| {
editor
.update(cx, |editor, cx| {
let buffer_start = fold_range
.start
.to_point(&editor.buffer().read(cx).read(cx));
let buffer_row = MultiBufferRow(buffer_start.row);
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
})
.ok();
})
.into_any_element()
}
}),
constrain_width: false,
merge_adjacent: false,
}
}
fn render_quote_selection_output_toggle(
row: MultiBufferRow,
is_folded: bool,
fold: ToggleFold,
_cx: &mut WindowContext,
) -> AnyElement {
Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
.selected(is_folded)
.on_click(move |_e, cx| fold(!is_folded, cx))
.into_any_element()
}
fn render_pending_slash_command_gutter_decoration(
row: MultiBufferRow,
status: &PendingSlashCommandStatus,