assistant panel: automatically insert selections (#17589)

Addresses parts of feedback from
https://www.jacobcolling.com/friction-log/zed-friction-log

Release Notes:
- "Assistant::NewContext" now automatically does quote selection as well
- "Assistant::QuoteSelection" now handles multicursor selections,
inserting multiple excerpts.
This commit is contained in:
Piotr Osiewicz 2024-09-09 18:31:55 +02:00 committed by GitHub
parent dd257b8412
commit 12dde17608
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -939,9 +939,16 @@ impl AssistantPanel {
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) { ) {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| { let did_create_context = panel
panel.new_context(cx); .update(cx, |panel, cx| {
}); panel.new_context(cx)?;
Some(())
})
.is_some();
if did_create_context {
ContextEditor::quote_selection(workspace, &Default::default(), cx);
}
} }
} }
@ -3186,87 +3193,93 @@ impl ContextEditor {
return; return;
}; };
let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx)); let mut creases = vec![];
let editor = editor.read(cx); editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx).snapshot(cx); let selections = editor.selections.all_adjusted(cx);
let range = editor::ToOffset::to_offset(&selection.start, &buffer) let buffer = editor.buffer().read(cx).snapshot(cx);
..editor::ToOffset::to_offset(&selection.end, &buffer); for selection in selections {
let selected_text = buffer.text_for_range(range.clone()).collect::<String>(); let range = editor::ToOffset::to_offset(&selection.start, &buffer)
if selected_text.is_empty() { ..editor::ToOffset::to_offset(&selection.end, &buffer);
return; let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
} if selected_text.is_empty() {
continue;
let start_language = buffer.language_at(range.start); }
let end_language = buffer.language_at(range.end); let start_language = buffer.language_at(range.start);
let language_name = if start_language == end_language { let end_language = buffer.language_at(range.end);
start_language.map(|language| language.code_fence_block_name()) let language_name = if start_language == end_language {
} else { start_language.map(|language| language.code_fence_block_name())
None
};
let language_name = language_name.as_deref().unwrap_or("");
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 {
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 { } else {
None None
}; };
let language_name = language_name.as_deref().unwrap_or("");
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 {
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 line_comment_prefix = start_language let outline_text = if let Some((start_symbols, end_symbols)) =
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned()); 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 fence = codeblock_fence_for_path( let line_comment_prefix = start_language
filename.as_deref(), .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
Some(selection.start.row..selection.end.row),
);
if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text) let fence = codeblock_fence_for_path(
{ filename.as_deref(),
let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n"); Some(selection.start.row..selection.end.row),
format!("{fence}{breadcrumb}{selected_text}\n```") );
} else {
format!("{fence}{selected_text}\n```") 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!("{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()
};
creases.push((text, crease_title));
} }
}; });
if creases.is_empty() {
let crease_title = if let Some(path) = filename { return;
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 // Activate the panel
if !panel.focus_handle(cx).contains_focused(cx) { if !panel.focus_handle(cx).contains_focused(cx) {
workspace.toggle_panel_focus::<AssistantPanel>(cx); workspace.toggle_panel_focus::<AssistantPanel>(cx);
@ -3283,39 +3296,40 @@ impl ContextEditor {
context.update(cx, |context, cx| { context.update(cx, |context, cx| {
context.editor.update(cx, |editor, cx| { context.editor.update(cx, |editor, cx| {
editor.insert("\n", cx); editor.insert("\n", cx);
for (text, crease_title) in creases {
let point = editor.selections.newest::<Point>(cx).head();
let start_row = MultiBufferRow(point.row);
let point = editor.selections.newest::<Point>(cx).head(); editor.insert(&text, cx);
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);
let snapshot = editor.buffer().read(cx).snapshot(cx); editor.insert("\n", 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,
let fold_placeholder = quote_selection_fold_placeholder( cx.view().downgrade(),
crease_title, );
cx.view().downgrade(), let crease = Crease::new(
); anchor_before..anchor_after,
let crease = Crease::new( fold_placeholder,
anchor_before..anchor_after, render_quote_selection_output_toggle,
fold_placeholder, |_, _, _| Empty.into_any(),
render_quote_selection_output_toggle, );
|_, _, _| Empty.into_any(), editor.insert_creases(vec![crease], cx);
); editor.fold_at(
editor.insert_creases(vec![crease], cx); &FoldAt {
editor.fold_at( buffer_row: start_row,
&FoldAt { },
buffer_row: start_row, cx,
}, );
cx, }
);
}) })
}); });
}; };