Allow generating code without editing it

This commit is contained in:
Antonio Scandurra 2023-08-25 12:16:28 +02:00
parent c1bd035875
commit 66a496edd7
2 changed files with 112 additions and 57 deletions

View file

@ -244,40 +244,47 @@ impl AssistantPanel {
fn new_inline_assist(&mut self, editor: &ViewHandle<Editor>, cx: &mut ViewContext<Self>) { fn new_inline_assist(&mut self, editor: &ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
let id = post_inc(&mut self.next_inline_assist_id); let id = post_inc(&mut self.next_inline_assist_id);
let (block_id, inline_assistant, selection) = editor.update(cx, |editor, cx| { let selection = editor.read(cx).selections.newest_anchor().clone();
let selection = editor.selections.newest_anchor().clone(); let assist_kind = if editor.read(cx).selections.newest::<usize>(cx).is_empty() {
let prompt_editor = cx.add_view(|cx| { InlineAssistKind::Insert
Editor::single_line( } else {
Some(Arc::new(|theme| theme.assistant.inline.editor.clone())), InlineAssistKind::Refactor
cx, };
) let prompt_editor = cx.add_view(|cx| {
}); Editor::single_line(
let assist_kind = if editor.selections.newest::<usize>(cx).is_empty() { Some(Arc::new(|theme| theme.assistant.inline.editor.clone())),
InlineAssistKind::Insert cx,
} else { )
InlineAssistKind::Edit });
}; let inline_assistant = cx.add_view(|cx| {
let assistant = cx.add_view(|_| InlineAssistant { let assistant = InlineAssistant {
id, id,
prompt_editor, prompt_editor,
confirmed: false, confirmed: false,
has_focus: false, has_focus: false,
assist_kind, assist_kind,
}); };
cx.focus(&assistant); cx.focus_self();
assistant
let block_id = editor.insert_blocks( });
let block_id = editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>(
vec![selection.start..selection.end],
|theme| theme.assistant.inline.pending_edit_background,
cx,
);
editor.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Flex, style: BlockStyle::Flex,
position: selection.head(), position: selection.head(),
height: 2, height: 2,
render: Arc::new({ render: Arc::new({
let assistant = assistant.clone(); let inline_assistant = inline_assistant.clone();
move |cx: &mut BlockContext| { move |cx: &mut BlockContext| {
ChildView::new(&assistant, cx) ChildView::new(&inline_assistant, cx)
.contained() .contained()
.with_padding_left(match assist_kind { .with_padding_left(match assist_kind {
InlineAssistKind::Edit => cx.gutter_width, InlineAssistKind::Refactor => cx.gutter_width,
InlineAssistKind::Insert => cx.anchor_x, InlineAssistKind::Insert => cx.anchor_x,
}) })
.into_any() .into_any()
@ -291,19 +298,13 @@ impl AssistantPanel {
}], }],
Some(Autoscroll::Strategy(AutoscrollStrategy::Newest)), Some(Autoscroll::Strategy(AutoscrollStrategy::Newest)),
cx, cx,
)[0]; )[0]
editor.highlight_background::<Self>(
vec![selection.start..selection.end],
|theme| theme.assistant.inline.pending_edit_background,
cx,
);
(block_id, assistant, selection)
}); });
self.pending_inline_assists.insert( self.pending_inline_assists.insert(
id, id,
PendingInlineAssist { PendingInlineAssist {
kind: assist_kind,
editor: editor.downgrade(), editor: editor.downgrade(),
selection, selection,
inline_assistant_block_id: Some(block_id), inline_assistant_block_id: Some(block_id),
@ -341,7 +342,7 @@ impl AssistantPanel {
let assist_id = inline_assistant.read(cx).id; let assist_id = inline_assistant.read(cx).id;
match event { match event {
InlineAssistantEvent::Confirmed { prompt } => { InlineAssistantEvent::Confirmed { prompt } => {
self.generate_code(assist_id, prompt, cx); self.generate(assist_id, prompt, cx);
} }
InlineAssistantEvent::Canceled => { InlineAssistantEvent::Canceled => {
self.complete_inline_assist(assist_id, true, cx); self.complete_inline_assist(assist_id, true, cx);
@ -395,12 +396,7 @@ impl AssistantPanel {
} }
} }
pub fn generate_code( fn generate(&mut self, inline_assist_id: usize, user_prompt: &str, cx: &mut ViewContext<Self>) {
&mut self,
inline_assist_id: usize,
user_prompt: &str,
cx: &mut ViewContext<Self>,
) {
let api_key = if let Some(api_key) = self.api_key.borrow().clone() { let api_key = if let Some(api_key) = self.api_key.borrow().clone() {
api_key api_key
} else { } else {
@ -426,27 +422,32 @@ impl AssistantPanel {
.text_for_range(selection.start..selection.end) .text_for_range(selection.start..selection.end)
.collect::<Rope>(); .collect::<Rope>();
let mut normalized_selected_text = selected_text.clone();
let mut base_indentation: Option<language::IndentSize> = None; let mut base_indentation: Option<language::IndentSize> = None;
let selection_start = selection.start.to_point(&snapshot); let selection_start = selection.start.to_point(&snapshot);
let selection_end = selection.end.to_point(&snapshot); let selection_end = selection.end.to_point(&snapshot);
if selection_start.row < selection_end.row { let mut start_row = selection_start.row;
for row in selection_start.row..=selection_end.row { if snapshot.is_line_blank(start_row) {
if snapshot.is_line_blank(row) { if let Some(prev_non_blank_row) = snapshot.prev_non_blank_row(start_row) {
continue; start_row = prev_non_blank_row;
}
let line_indentation = snapshot.indent_size_for_line(row);
if let Some(base_indentation) = base_indentation.as_mut() {
if line_indentation.len < base_indentation.len {
*base_indentation = line_indentation;
}
} else {
base_indentation = Some(line_indentation);
}
} }
} }
for row in start_row..=selection_end.row {
if snapshot.is_line_blank(row) {
continue;
}
let line_indentation = snapshot.indent_size_for_line(row);
if let Some(base_indentation) = base_indentation.as_mut() {
if line_indentation.len < base_indentation.len {
*base_indentation = line_indentation;
}
} else {
base_indentation = Some(line_indentation);
}
}
let mut normalized_selected_text = selected_text.clone();
if let Some(base_indentation) = base_indentation { if let Some(base_indentation) = base_indentation {
for row in selection_start.row..=selection_end.row { for row in selection_start.row..=selection_end.row {
let selection_row = row - selection_start.row; let selection_row = row - selection_start.row;
@ -472,10 +473,53 @@ impl AssistantPanel {
let language_name = language_name.as_deref().unwrap_or(""); let language_name = language_name.as_deref().unwrap_or("");
let mut prompt = String::new(); let mut prompt = String::new();
writeln!(prompt, "Given the following {language_name} snippet:").unwrap(); writeln!(prompt, "You're an expert {language_name} engineer.").unwrap();
writeln!(prompt, "{normalized_selected_text}").unwrap(); writeln!(
writeln!(prompt, "{user_prompt}.").unwrap(); prompt,
writeln!(prompt, "Never make remarks, reply only with the new code.").unwrap(); "You're currently working inside an editor on this code:"
)
.unwrap();
match pending_assist.kind {
InlineAssistKind::Refactor => {
writeln!(prompt, "```{language_name}").unwrap();
writeln!(prompt, "{normalized_selected_text}").unwrap();
writeln!(prompt, "```").unwrap();
writeln!(
prompt,
"Modify the code given the user prompt: {user_prompt}"
)
.unwrap();
}
InlineAssistKind::Insert => {
writeln!(prompt, "```{language_name}").unwrap();
for chunk in snapshot.text_for_range(Anchor::min()..selection.head()) {
write!(prompt, "{chunk}").unwrap();
}
write!(prompt, "<|>").unwrap();
for chunk in snapshot.text_for_range(selection.head()..Anchor::max()) {
write!(prompt, "{chunk}").unwrap();
}
writeln!(prompt).unwrap();
writeln!(prompt, "```").unwrap();
writeln!(
prompt,
"Assume the cursor is located where the `<|>` marker is."
)
.unwrap();
writeln!(
prompt,
"Complete the code given the user prompt: {user_prompt}"
)
.unwrap();
}
}
writeln!(
prompt,
"You MUST not return anything that isn't valid {language_name}"
)
.unwrap();
writeln!(prompt, "DO NOT wrap your response in Markdown blocks.").unwrap();
let request = OpenAIRequest { let request = OpenAIRequest {
model: "gpt-4".into(), model: "gpt-4".into(),
messages: vec![RequestMessage { messages: vec![RequestMessage {
@ -2589,7 +2633,7 @@ enum InlineAssistantEvent {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
enum InlineAssistKind { enum InlineAssistKind {
Edit, Refactor,
Insert, Insert,
} }
@ -2614,7 +2658,7 @@ impl View for InlineAssistant {
let theme = theme::current(cx); let theme = theme::current(cx);
let prompt_editor = ChildView::new(&self.prompt_editor, cx).aligned().left(); let prompt_editor = ChildView::new(&self.prompt_editor, cx).aligned().left();
match self.assist_kind { match self.assist_kind {
InlineAssistKind::Edit => prompt_editor InlineAssistKind::Refactor => prompt_editor
.contained() .contained()
.with_style(theme.assistant.inline.container) .with_style(theme.assistant.inline.container)
.into_any(), .into_any(),
@ -2651,6 +2695,7 @@ impl InlineAssistant {
} }
struct PendingInlineAssist { struct PendingInlineAssist {
kind: InlineAssistKind,
editor: WeakViewHandle<Editor>, editor: WeakViewHandle<Editor>,
selection: Selection<Anchor>, selection: Selection<Anchor>,
inline_assistant_block_id: Option<BlockId>, inline_assistant_block_id: Option<BlockId>,

View file

@ -2352,6 +2352,16 @@ impl MultiBufferSnapshot {
} }
} }
pub fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
while row > 0 {
row -= 1;
if !self.is_line_blank(row) {
return Some(row);
}
}
None
}
pub fn line_len(&self, row: u32) -> u32 { pub fn line_len(&self, row: u32) -> u32 {
if let Some((_, range)) = self.buffer_line_for_row(row) { if let Some((_, range)) = self.buffer_line_for_row(row) {
range.end.column - range.start.column range.end.column - range.start.column