Compare commits
6 Commits
fix-git-ht
...
lipstick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaba50b984 | ||
|
|
eac3b68007 | ||
|
|
a241c06672 | ||
|
|
dd816ea36f | ||
|
|
7723de1eb8 | ||
|
|
e359f275dd |
@@ -1,4 +1,4 @@
|
||||
Your task is to map a step from the conversation above to operations on symbols inside the provided source files.
|
||||
Your task is to map a step from the conversation above to suggestions on symbols inside the provided source files.
|
||||
|
||||
Guidelines:
|
||||
- There's no need to describe *what* to do, just *where* to do it.
|
||||
@@ -6,13 +6,13 @@ Guidelines:
|
||||
- Don't create and then update a file.
|
||||
- We'll create it in one shot.
|
||||
- Prefer updating symbols lower in the syntax tree if possible.
|
||||
- Never include operations on a parent symbol and one of its children in the same operations block.
|
||||
- Never nest an operation with another operation or include CDATA or other content. All operations are leaf nodes.
|
||||
- Never include suggestions on a parent symbol and one of its children in the same suggestions block.
|
||||
- Never nest an operation with another operation or include CDATA or other content. All suggestions are leaf nodes.
|
||||
- Include a description attribute for each operation with a brief, one-line description of the change to perform.
|
||||
- Descriptions are required for all operations except delete.
|
||||
- When generating multiple operations, ensure the descriptions are specific to each individual operation.
|
||||
- Descriptions are required for all suggestions except delete.
|
||||
- When generating multiple suggestions, ensure the descriptions are specific to each individual operation.
|
||||
- Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide.
|
||||
- Don't generate multiple operations at the same location. Instead, combine them together in a single operation with a succinct combined description.
|
||||
- Don't generate multiple suggestions at the same location. Instead, combine them together in a single operation with a succinct combined description.
|
||||
|
||||
Example 1:
|
||||
|
||||
@@ -33,12 +33,12 @@ impl Rectangle {
|
||||
<step>Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct</step>
|
||||
<step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||
|
||||
What are the operations for the step: <step>Add a new method 'calculate_area' to the Rectangle struct</step>
|
||||
What are the suggestions for the step: <step>Add a new method 'calculate_area' to the Rectangle struct</step>
|
||||
|
||||
A (wrong):
|
||||
{
|
||||
"title": "Add Rectangle methods",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "AppendChild",
|
||||
"path": "src/shapes.rs",
|
||||
@@ -59,7 +59,7 @@ This demonstrates what NOT to do. NEVER append multiple children at the same loc
|
||||
A (corrected):
|
||||
{
|
||||
"title": "Add Rectangle methods",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "AppendChild",
|
||||
"path": "src/shapes.rs",
|
||||
@@ -70,12 +70,12 @@ A (corrected):
|
||||
}
|
||||
|
||||
User:
|
||||
What are the operations for the step: <step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||
What are the suggestions for the step: <step>Implement the 'Display' trait for the Rectangle struct</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Implement Display for Rectangle",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "InsertSiblingAfter",
|
||||
"path": "src/shapes.rs",
|
||||
@@ -109,12 +109,12 @@ impl User {
|
||||
<step>Update the 'print_info' method to use formatted output</step>
|
||||
<step>Remove the 'email' field from the User struct</step>
|
||||
|
||||
What are the operations for the step: <step>Update the 'print_info' method to use formatted output</step>
|
||||
What are the suggestions for the step: <step>Update the 'print_info' method to use formatted output</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Use formatted output",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Update",
|
||||
"path": "src/user.rs",
|
||||
@@ -125,12 +125,12 @@ A:
|
||||
}
|
||||
|
||||
User:
|
||||
What are the operations for the step: <step>Remove the 'email' field from the User struct</step>
|
||||
What are the suggestions for the step: <step>Remove the 'email' field from the User struct</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Remove email field",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Delete",
|
||||
"path": "src/user.rs",
|
||||
@@ -163,12 +163,12 @@ impl Vehicle {
|
||||
<step>Add a 'use std::fmt;' statement at the beginning of the file</step>
|
||||
<step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||
|
||||
What are the operations for the step: <step>Add a 'use std::fmt;' statement at the beginning of the file</step>
|
||||
What are the suggestions for the step: <step>Add a 'use std::fmt;' statement at the beginning of the file</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Add use std::fmt statement",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "PrependChild",
|
||||
"path": "src/vehicle.rs",
|
||||
@@ -178,12 +178,12 @@ A:
|
||||
}
|
||||
|
||||
User:
|
||||
What are the operations for the step: <step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||
What are the suggestions for the step: <step>Add a new method 'start_engine' in the Vehicle impl block</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Add start_engine method",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "InsertSiblingAfter",
|
||||
"path": "src/vehicle.rs",
|
||||
@@ -222,12 +222,12 @@ impl Employee {
|
||||
|
||||
<step>Make salary an f32</step>
|
||||
|
||||
What are the operations for the step: <step>Make salary an f32</step>
|
||||
What are the suggestions for the step: <step>Make salary an f32</step>
|
||||
|
||||
A (wrong):
|
||||
{
|
||||
"title": "Change salary to f32",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Update",
|
||||
"path": "src/employee.rs",
|
||||
@@ -248,7 +248,7 @@ This example demonstrates what not to do. `struct Employee salary` is a child of
|
||||
A (corrected):
|
||||
{
|
||||
"title": "Change salary to f32",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Update",
|
||||
"path": "src/employee.rs",
|
||||
@@ -259,12 +259,12 @@ A (corrected):
|
||||
}
|
||||
|
||||
User:
|
||||
What are the correct operations for the step: <step>Remove the 'department' field and update the 'print_details' method</step>
|
||||
What are the correct suggestions for the step: <step>Remove the 'department' field and update the 'print_details' method</step>
|
||||
|
||||
A:
|
||||
{
|
||||
"title": "Remove department",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "Delete",
|
||||
"path": "src/employee.rs",
|
||||
@@ -311,7 +311,7 @@ impl Game {
|
||||
A:
|
||||
{
|
||||
"title": "Add level field to Player",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "InsertSiblingAfter",
|
||||
"path": "src/game.rs",
|
||||
@@ -349,7 +349,7 @@ impl Config {
|
||||
A:
|
||||
{
|
||||
"title": "Add load_from_file method",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "PrependChild",
|
||||
"path": "src/config.rs",
|
||||
@@ -389,7 +389,7 @@ impl Database {
|
||||
A:
|
||||
{
|
||||
"title": "Add error handling to query",
|
||||
"operations": [
|
||||
"suggestions": [
|
||||
{
|
||||
"kind": "PrependChild",
|
||||
"path": "src/database.rs",
|
||||
@@ -410,4 +410,4 @@ A:
|
||||
]
|
||||
}
|
||||
|
||||
Now generate the operations for the following step:
|
||||
Now generate the suggestions for the following step:
|
||||
|
||||
@@ -10,14 +10,14 @@ use crate::{
|
||||
},
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, CodegenStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
|
||||
CycleMessageRole, DebugEditSteps, DeployHistory, DeployPromptLibrary, EditSuggestionGroup,
|
||||
InlineAssist, InlineAssistId, InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector,
|
||||
CycleMessageRole, DebugEditSteps, DeployHistory, DeployPromptLibrary, InlineAssist,
|
||||
InlineAssistId, InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector,
|
||||
PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
|
||||
ResolvedWorkflowStepEditSuggestions, SavedContextMetadata, Split, ToggleFocus,
|
||||
ToggleModelSelector, WorkflowStepEditSuggestions,
|
||||
ResolvedWorkflowStep, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
|
||||
WorkflowStepStatus,
|
||||
};
|
||||
use crate::{ContextStoreEvent, ShowConfiguration};
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||
use client::{proto, Client, Status};
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
@@ -41,8 +41,7 @@ use gpui::{
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
language_settings::SoftWrap, Buffer, Capability, LanguageRegistry, LspAdapterDelegate, Point,
|
||||
ToOffset,
|
||||
language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
|
||||
};
|
||||
use language_model::{
|
||||
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
|
||||
@@ -1330,15 +1329,10 @@ struct ScrollPosition {
|
||||
cursor: Anchor,
|
||||
}
|
||||
|
||||
struct StepAssists {
|
||||
assist_ids: Vec<InlineAssistId>,
|
||||
struct WorkflowAssist {
|
||||
editor: WeakView<Editor>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct ActiveWorkflowStep {
|
||||
range: Range<language::Anchor>,
|
||||
suggestions: Option<ResolvedWorkflowStepEditSuggestions>,
|
||||
editor_was_open: bool,
|
||||
assist_ids: Vec<InlineAssistId>,
|
||||
}
|
||||
|
||||
pub struct ContextEditor {
|
||||
@@ -1353,9 +1347,9 @@ pub struct ContextEditor {
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
|
||||
workflow_assists: HashMap<Range<language::Anchor>, WorkflowAssist>,
|
||||
active_workflow_step_range: Option<Range<language::Anchor>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
assists_by_step: HashMap<Range<language::Anchor>, StepAssists>,
|
||||
active_workflow_step: Option<ActiveWorkflowStep>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
error_message: Option<SharedString>,
|
||||
}
|
||||
@@ -1413,8 +1407,8 @@ impl ContextEditor {
|
||||
pending_slash_command_creases: HashMap::default(),
|
||||
pending_slash_command_blocks: HashMap::default(),
|
||||
_subscriptions,
|
||||
assists_by_step: HashMap::default(),
|
||||
active_workflow_step: None,
|
||||
workflow_assists: HashMap::default(),
|
||||
active_workflow_step_range: None,
|
||||
assistant_panel,
|
||||
error_message: None,
|
||||
};
|
||||
@@ -1449,16 +1443,16 @@ impl ContextEditor {
|
||||
}
|
||||
|
||||
fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
|
||||
if !self.apply_edit_step(cx) {
|
||||
if !self.apply_workflow_step(cx) {
|
||||
self.error_message = None;
|
||||
self.send_to_model(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_edit_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if let Some(step) = self.active_workflow_step.as_ref() {
|
||||
if let Some(assists) = self.assists_by_step.get(&step.range) {
|
||||
fn apply_workflow_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||
if let Some(step_range) = self.active_workflow_step_range.as_ref() {
|
||||
if let Some(assists) = self.workflow_assists.get(&step_range) {
|
||||
let assist_ids = assists.assist_ids.clone();
|
||||
cx.window_context().defer(|cx| {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
@@ -1519,16 +1513,13 @@ impl ContextEditor {
|
||||
.text_for_range(step.tagged_range.clone())
|
||||
.collect::<String>()
|
||||
));
|
||||
match &step.edit_suggestions {
|
||||
WorkflowStepEditSuggestions::Resolved(ResolvedWorkflowStepEditSuggestions {
|
||||
title,
|
||||
edit_suggestions,
|
||||
}) => {
|
||||
match &step.status {
|
||||
WorkflowStepStatus::Resolved(ResolvedWorkflowStep { title, suggestions }) => {
|
||||
output.push_str("Resolution:\n");
|
||||
output.push_str(&format!(" {:?}\n", title));
|
||||
output.push_str(&format!(" {:?}\n", edit_suggestions));
|
||||
output.push_str(&format!(" {:?}\n", suggestions));
|
||||
}
|
||||
WorkflowStepEditSuggestions::Pending(_) => {
|
||||
WorkflowStepStatus::Pending(_) => {
|
||||
output.push_str("Resolution: Pending\n");
|
||||
}
|
||||
}
|
||||
@@ -1676,7 +1667,7 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
ContextEvent::WorkflowStepsChanged => {
|
||||
self.update_active_workflow_step(cx);
|
||||
self.update_active_workflow_step_from_cursor(cx);
|
||||
cx.notify();
|
||||
}
|
||||
ContextEvent::SummaryChanged => {
|
||||
@@ -1941,14 +1932,14 @@ impl ContextEditor {
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
self.scroll_position = self.cursor_scroll_position(cx);
|
||||
self.update_active_workflow_step(cx);
|
||||
self.update_active_workflow_step_from_cursor(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
cx.emit(event.clone());
|
||||
}
|
||||
|
||||
fn update_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn update_active_workflow_step_from_cursor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let new_step = self
|
||||
.workflow_step_range_for_cursor(cx)
|
||||
.as_ref()
|
||||
@@ -1957,14 +1948,11 @@ impl ContextEditor {
|
||||
.context
|
||||
.read(cx)
|
||||
.workflow_step_for_range(step_range.clone())?;
|
||||
Some(ActiveWorkflowStep {
|
||||
range: workflow_step.tagged_range.clone(),
|
||||
suggestions: workflow_step.edit_suggestions.as_resolved().cloned(),
|
||||
})
|
||||
Some(workflow_step.tagged_range.clone())
|
||||
});
|
||||
if new_step.as_ref() != self.active_workflow_step.as_ref() {
|
||||
if let Some(old_step) = self.active_workflow_step.take() {
|
||||
self.cancel_workflow_step_if_idle(old_step.range, cx);
|
||||
if new_step.as_ref() != self.active_workflow_step_range.as_ref() {
|
||||
if let Some(old_step_range) = self.active_workflow_step_range.take() {
|
||||
self.hide_workflow_step(old_step_range, cx);
|
||||
}
|
||||
|
||||
if let Some(new_step) = new_step {
|
||||
@@ -1973,21 +1961,21 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_workflow_step_if_idle(
|
||||
fn hide_workflow_step(
|
||||
&mut self,
|
||||
step_range: Range<language::Anchor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let Some(step_assists) = self.assists_by_step.get_mut(&step_range) else {
|
||||
let Some(step_assist) = self.workflow_assists.get_mut(&step_range) else {
|
||||
return;
|
||||
};
|
||||
let Some(editor) = step_assists.editor.upgrade() else {
|
||||
self.assists_by_step.remove(&step_range);
|
||||
let Some(editor) = step_assist.editor.upgrade() else {
|
||||
self.workflow_assists.remove(&step_range);
|
||||
return;
|
||||
};
|
||||
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
step_assists.assist_ids.retain(|assist_id| {
|
||||
step_assist.assist_ids.retain(|assist_id| {
|
||||
match assistant.status_for_assist(*assist_id, cx) {
|
||||
Some(CodegenStatus::Idle) | None => {
|
||||
assistant.finish_assist(*assist_id, true, cx);
|
||||
@@ -1998,14 +1986,15 @@ impl ContextEditor {
|
||||
});
|
||||
});
|
||||
|
||||
if step_assists.assist_ids.is_empty() {
|
||||
self.assists_by_step.remove(&step_range);
|
||||
if step_assist.assist_ids.is_empty() {
|
||||
let editor_was_open = step_assist.editor_was_open;
|
||||
self.workflow_assists.remove(&step_range);
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
if let Some(pane) = workspace.pane_for(&editor) {
|
||||
pane.update(cx, |pane, cx| {
|
||||
let item_id = editor.entity_id();
|
||||
if pane.is_active_preview_item(item_id) {
|
||||
if !editor_was_open && pane.is_active_preview_item(item_id) {
|
||||
pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
@@ -2016,147 +2005,200 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_workflow_step(&mut self, step: ActiveWorkflowStep, cx: &mut ViewContext<Self>) {
|
||||
if let Some(step_assists) = self.assists_by_step.get(&step.range) {
|
||||
if let Some(editor) = step_assists.editor.upgrade() {
|
||||
for assist_id in &step_assists.assist_ids {
|
||||
match InlineAssistant::global(cx).status_for_assist(*assist_id, cx) {
|
||||
Some(CodegenStatus::Idle) | None => {}
|
||||
_ => {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.activate_item(&editor, false, false, cx);
|
||||
})
|
||||
.ok();
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.scroll_to_assist(*assist_id, cx)
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ResolvedWorkflowStepEditSuggestions {
|
||||
title,
|
||||
edit_suggestions,
|
||||
}) = step.suggestions.as_ref()
|
||||
{
|
||||
if let Some((editor, assist_ids)) =
|
||||
self.suggest_edits(title.clone(), edit_suggestions.clone(), cx)
|
||||
{
|
||||
self.assists_by_step.insert(
|
||||
step.range.clone(),
|
||||
StepAssists {
|
||||
assist_ids,
|
||||
editor: editor.downgrade(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.active_workflow_step = Some(step);
|
||||
}
|
||||
|
||||
fn suggest_edits(
|
||||
fn activate_workflow_step(
|
||||
&mut self,
|
||||
title: String,
|
||||
edit_suggestions: HashMap<Model<Buffer>, Vec<EditSuggestionGroup>>,
|
||||
step_range: Range<language::Anchor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<(View<Editor>, Vec<InlineAssistId>)> {
|
||||
let assistant_panel = self.assistant_panel.upgrade()?;
|
||||
if edit_suggestions.is_empty() {
|
||||
) -> Option<()> {
|
||||
if self.scroll_to_existing_workflow_assist(&step_range, cx) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let editor;
|
||||
let mut suggestion_groups = Vec::new();
|
||||
if edit_suggestions.len() == 1 && edit_suggestions.values().next().unwrap().len() == 1 {
|
||||
// If there's only one buffer and one suggestion group, open it directly
|
||||
let (buffer, groups) = edit_suggestions.into_iter().next().unwrap();
|
||||
let group = groups.into_iter().next().unwrap();
|
||||
editor = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let active_pane = workspace.active_pane().clone();
|
||||
workspace.open_project_item::<Editor>(active_pane, buffer, false, false, cx)
|
||||
})
|
||||
.log_err()?;
|
||||
let step = self
|
||||
.workflow_step(&step_range, cx)
|
||||
.with_context(|| format!("could not find workflow step for range {:?}", step_range))
|
||||
.log_err()?;
|
||||
let Some(resolved) = step.status.as_resolved() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let (&excerpt_id, _, _) = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap();
|
||||
let title = resolved.title.clone();
|
||||
let suggestions = resolved.suggestions.clone();
|
||||
|
||||
// Scroll the editor to the suggested assist
|
||||
editor.update(cx, |editor, cx| {
|
||||
let multibuffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
|
||||
let anchor = if group.context_range.start.to_offset(buffer) == 0 {
|
||||
Anchor::min()
|
||||
} else {
|
||||
multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, group.context_range.start)
|
||||
.unwrap()
|
||||
};
|
||||
if let Some((editor, assist_ids, editor_was_open)) = {
|
||||
let assistant_panel = self.assistant_panel.upgrade()?;
|
||||
if suggestions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
editor.set_scroll_anchor(
|
||||
ScrollAnchor {
|
||||
offset: gpui::Point::default(),
|
||||
anchor,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let editor;
|
||||
let mut editor_was_open = false;
|
||||
let mut suggestion_groups = Vec::new();
|
||||
if suggestions.len() == 1 && suggestions.values().next().unwrap().len() == 1 {
|
||||
// If there's only one buffer and one suggestion group, open it directly
|
||||
let (buffer, groups) = suggestions.into_iter().next().unwrap();
|
||||
let group = groups.into_iter().next().unwrap();
|
||||
editor = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let active_pane = workspace.active_pane().clone();
|
||||
editor_was_open =
|
||||
workspace.is_project_item_open::<Editor>(&active_pane, &buffer, cx);
|
||||
workspace.open_project_item::<Editor>(active_pane, buffer, false, false, cx)
|
||||
})
|
||||
.log_err()?;
|
||||
|
||||
suggestion_groups.push((excerpt_id, group));
|
||||
} else {
|
||||
// If there are multiple buffers or suggestion groups, create a multibuffer
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let replica_id = self.project.read(cx).replica_id();
|
||||
let mut multibuffer =
|
||||
MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
|
||||
for (buffer, groups) in edit_suggestions {
|
||||
let excerpt_ids = multibuffer.push_excerpts(
|
||||
buffer,
|
||||
groups.iter().map(|suggestion_group| ExcerptRange {
|
||||
context: suggestion_group.context_range.clone(),
|
||||
primary: None,
|
||||
}),
|
||||
let (&excerpt_id, _, _) = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap();
|
||||
|
||||
// Scroll the editor to the suggested assist
|
||||
editor.update(cx, |editor, cx| {
|
||||
let multibuffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
|
||||
let anchor = if group.context_range.start.to_offset(buffer) == 0 {
|
||||
Anchor::min()
|
||||
} else {
|
||||
multibuffer
|
||||
.anchor_in_excerpt(excerpt_id, group.context_range.start)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
editor.set_scroll_anchor(
|
||||
ScrollAnchor {
|
||||
offset: gpui::Point::default(),
|
||||
anchor,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
|
||||
}
|
||||
multibuffer
|
||||
});
|
||||
});
|
||||
|
||||
editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(multibuffer, Some(self.project.clone()), true, cx)
|
||||
});
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
|
||||
})
|
||||
.log_err()?;
|
||||
suggestion_groups.push((excerpt_id, group));
|
||||
} else {
|
||||
// If there are multiple buffers or suggestion groups, create a multibuffer
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let replica_id = self.project.read(cx).replica_id();
|
||||
let mut multibuffer =
|
||||
MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
|
||||
for (buffer, groups) in suggestions {
|
||||
let excerpt_ids = multibuffer.push_excerpts(
|
||||
buffer,
|
||||
groups.iter().map(|suggestion_group| ExcerptRange {
|
||||
context: suggestion_group.context_range.clone(),
|
||||
primary: None,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
|
||||
}
|
||||
multibuffer
|
||||
});
|
||||
|
||||
editor = cx.new_view(|cx| {
|
||||
Editor::for_multibuffer(multibuffer, Some(self.project.clone()), true, cx)
|
||||
});
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
|
||||
})
|
||||
.log_err()?;
|
||||
}
|
||||
|
||||
let mut assist_ids = Vec::new();
|
||||
for (excerpt_id, suggestion_group) in suggestion_groups {
|
||||
for suggestion in suggestion_group.suggestions {
|
||||
assist_ids.extend(suggestion.show(
|
||||
&editor,
|
||||
excerpt_id,
|
||||
&self.workspace,
|
||||
&assistant_panel,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(range) = self.active_workflow_step_range.clone() {
|
||||
self.workflow_assists.insert(
|
||||
range,
|
||||
WorkflowAssist {
|
||||
assist_ids: assist_ids.clone(),
|
||||
editor: editor.downgrade(),
|
||||
editor_was_open,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Some((editor, assist_ids, editor_was_open))
|
||||
} {
|
||||
self.workflow_assists.insert(
|
||||
step_range.clone(),
|
||||
WorkflowAssist {
|
||||
assist_ids,
|
||||
editor_was_open,
|
||||
editor: editor.downgrade(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let mut assist_ids = Vec::new();
|
||||
for (excerpt_id, suggestion_group) in suggestion_groups {
|
||||
for suggestion in suggestion_group.suggestions {
|
||||
assist_ids.extend(suggestion.show(
|
||||
&editor,
|
||||
excerpt_id,
|
||||
&self.workspace,
|
||||
&assistant_panel,
|
||||
cx,
|
||||
));
|
||||
self.active_workflow_step_range = Some(step_range);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn active_workflow_step<'a>(&'a self, cx: &'a AppContext) -> Option<&'a crate::WorkflowStep> {
|
||||
self.active_workflow_step_range
|
||||
.as_ref()
|
||||
.and_then(|step_range| {
|
||||
self.context
|
||||
.read(cx)
|
||||
.workflow_step_for_range(step_range.clone())
|
||||
})
|
||||
}
|
||||
|
||||
fn workflow_step<'a>(
|
||||
&'a mut self,
|
||||
step_range: &Range<text::Anchor>,
|
||||
cx: &'a mut ViewContext<ContextEditor>,
|
||||
) -> Option<&'a crate::WorkflowStep> {
|
||||
self.context
|
||||
.read(cx)
|
||||
.workflow_step_for_range(step_range.clone())
|
||||
}
|
||||
|
||||
fn scroll_to_existing_workflow_assist(
|
||||
&self,
|
||||
step_range: &Range<language::Anchor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
let step_assists = match self.workflow_assists.get(step_range) {
|
||||
Some(assists) => assists,
|
||||
None => return false,
|
||||
};
|
||||
let editor = match step_assists.editor.upgrade() {
|
||||
Some(editor) => editor,
|
||||
None => return false,
|
||||
};
|
||||
for assist_id in &step_assists.assist_ids {
|
||||
match InlineAssistant::global(cx).status_for_assist(*assist_id, cx) {
|
||||
Some(CodegenStatus::Idle) | None => {}
|
||||
_ => {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.activate_item(&editor, false, false, cx);
|
||||
})
|
||||
.ok();
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.scroll_to_assist(*assist_id, cx)
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((editor, assist_ids))
|
||||
false
|
||||
}
|
||||
|
||||
fn handle_editor_search_event(
|
||||
@@ -2540,12 +2582,12 @@ impl ContextEditor {
|
||||
|
||||
fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle(cx).clone();
|
||||
let button_text = match self.active_workflow_step.as_ref() {
|
||||
let button_text = match self.active_workflow_step(cx) {
|
||||
Some(step) => {
|
||||
if step.suggestions.is_none() {
|
||||
"Computing Changes..."
|
||||
} else {
|
||||
if step.status.is_resolved() {
|
||||
"Apply Changes"
|
||||
} else {
|
||||
"Computing Changes..."
|
||||
}
|
||||
}
|
||||
None => "Send",
|
||||
|
||||
@@ -348,37 +348,44 @@ pub struct SlashCommandId(clock::Lamport);
|
||||
#[derive(Debug)]
|
||||
pub struct WorkflowStep {
|
||||
pub tagged_range: Range<language::Anchor>,
|
||||
pub edit_suggestions: WorkflowStepEditSuggestions,
|
||||
pub status: WorkflowStepStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct ResolvedWorkflowStepEditSuggestions {
|
||||
pub struct ResolvedWorkflowStep {
|
||||
pub title: String,
|
||||
pub edit_suggestions: HashMap<Model<Buffer>, Vec<EditSuggestionGroup>>,
|
||||
pub suggestions: HashMap<Model<Buffer>, Vec<WorkflowSuggestionGroup>>,
|
||||
}
|
||||
|
||||
pub enum WorkflowStepEditSuggestions {
|
||||
pub enum WorkflowStepStatus {
|
||||
Pending(Task<Option<()>>),
|
||||
Resolved(ResolvedWorkflowStepEditSuggestions),
|
||||
Resolved(ResolvedWorkflowStep),
|
||||
}
|
||||
|
||||
impl WorkflowStepEditSuggestions {
|
||||
pub fn as_resolved(&self) -> Option<&ResolvedWorkflowStepEditSuggestions> {
|
||||
impl WorkflowStepStatus {
|
||||
pub fn as_resolved(&self) -> Option<&ResolvedWorkflowStep> {
|
||||
match self {
|
||||
WorkflowStepEditSuggestions::Resolved(suggestions) => Some(suggestions),
|
||||
WorkflowStepEditSuggestions::Pending(_) => None,
|
||||
WorkflowStepStatus::Resolved(suggestions) => Some(suggestions),
|
||||
WorkflowStepStatus::Pending(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_resolved(&self) -> bool {
|
||||
match self {
|
||||
WorkflowStepStatus::Resolved(_) => true,
|
||||
WorkflowStepStatus::Pending(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct EditSuggestionGroup {
|
||||
pub struct WorkflowSuggestionGroup {
|
||||
pub context_range: Range<language::Anchor>,
|
||||
pub suggestions: Vec<EditSuggestion>,
|
||||
pub suggestions: Vec<WorkflowSuggestion>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum EditSuggestion {
|
||||
pub enum WorkflowSuggestion {
|
||||
Update {
|
||||
range: Range<language::Anchor>,
|
||||
description: String,
|
||||
@@ -407,40 +414,40 @@ pub enum EditSuggestion {
|
||||
},
|
||||
}
|
||||
|
||||
impl EditSuggestion {
|
||||
impl WorkflowSuggestion {
|
||||
pub fn range(&self) -> Range<language::Anchor> {
|
||||
match self {
|
||||
EditSuggestion::Update { range, .. } => range.clone(),
|
||||
EditSuggestion::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
|
||||
EditSuggestion::InsertSiblingBefore { position, .. }
|
||||
| EditSuggestion::InsertSiblingAfter { position, .. }
|
||||
| EditSuggestion::PrependChild { position, .. }
|
||||
| EditSuggestion::AppendChild { position, .. } => *position..*position,
|
||||
EditSuggestion::Delete { range } => range.clone(),
|
||||
WorkflowSuggestion::Update { range, .. } => range.clone(),
|
||||
WorkflowSuggestion::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
|
||||
WorkflowSuggestion::InsertSiblingBefore { position, .. }
|
||||
| WorkflowSuggestion::InsertSiblingAfter { position, .. }
|
||||
| WorkflowSuggestion::PrependChild { position, .. }
|
||||
| WorkflowSuggestion::AppendChild { position, .. } => *position..*position,
|
||||
WorkflowSuggestion::Delete { range } => range.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<&str> {
|
||||
match self {
|
||||
EditSuggestion::Update { description, .. }
|
||||
| EditSuggestion::CreateFile { description }
|
||||
| EditSuggestion::InsertSiblingBefore { description, .. }
|
||||
| EditSuggestion::InsertSiblingAfter { description, .. }
|
||||
| EditSuggestion::PrependChild { description, .. }
|
||||
| EditSuggestion::AppendChild { description, .. } => Some(description),
|
||||
EditSuggestion::Delete { .. } => None,
|
||||
WorkflowSuggestion::Update { description, .. }
|
||||
| WorkflowSuggestion::CreateFile { description }
|
||||
| WorkflowSuggestion::InsertSiblingBefore { description, .. }
|
||||
| WorkflowSuggestion::InsertSiblingAfter { description, .. }
|
||||
| WorkflowSuggestion::PrependChild { description, .. }
|
||||
| WorkflowSuggestion::AppendChild { description, .. } => Some(description),
|
||||
WorkflowSuggestion::Delete { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn description_mut(&mut self) -> Option<&mut String> {
|
||||
match self {
|
||||
EditSuggestion::Update { description, .. }
|
||||
| EditSuggestion::CreateFile { description }
|
||||
| EditSuggestion::InsertSiblingBefore { description, .. }
|
||||
| EditSuggestion::InsertSiblingAfter { description, .. }
|
||||
| EditSuggestion::PrependChild { description, .. }
|
||||
| EditSuggestion::AppendChild { description, .. } => Some(description),
|
||||
EditSuggestion::Delete { .. } => None,
|
||||
WorkflowSuggestion::Update { description, .. }
|
||||
| WorkflowSuggestion::CreateFile { description }
|
||||
| WorkflowSuggestion::InsertSiblingBefore { description, .. }
|
||||
| WorkflowSuggestion::InsertSiblingAfter { description, .. }
|
||||
| WorkflowSuggestion::PrependChild { description, .. }
|
||||
| WorkflowSuggestion::AppendChild { description, .. } => Some(description),
|
||||
WorkflowSuggestion::Delete { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,16 +486,16 @@ impl EditSuggestion {
|
||||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
match self {
|
||||
EditSuggestion::Update { range, description } => {
|
||||
WorkflowSuggestion::Update { range, description } => {
|
||||
initial_prompt = description.clone();
|
||||
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
|
||||
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
|
||||
}
|
||||
EditSuggestion::CreateFile { description } => {
|
||||
WorkflowSuggestion::CreateFile { description } => {
|
||||
initial_prompt = description.clone();
|
||||
suggestion_range = editor::Anchor::min()..editor::Anchor::min();
|
||||
}
|
||||
EditSuggestion::InsertSiblingBefore {
|
||||
WorkflowSuggestion::InsertSiblingBefore {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -503,7 +510,7 @@ impl EditSuggestion {
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::InsertSiblingAfter {
|
||||
WorkflowSuggestion::InsertSiblingAfter {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -518,7 +525,7 @@ impl EditSuggestion {
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::PrependChild {
|
||||
WorkflowSuggestion::PrependChild {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -533,7 +540,7 @@ impl EditSuggestion {
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::AppendChild {
|
||||
WorkflowSuggestion::AppendChild {
|
||||
position,
|
||||
description,
|
||||
} => {
|
||||
@@ -548,7 +555,7 @@ impl EditSuggestion {
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
EditSuggestion::Delete { range } => {
|
||||
WorkflowSuggestion::Delete { range } => {
|
||||
initial_prompt = "Delete".to_string();
|
||||
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
|
||||
..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
|
||||
@@ -569,17 +576,14 @@ impl EditSuggestion {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for WorkflowStepEditSuggestions {
|
||||
impl Debug for WorkflowStepStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WorkflowStepEditSuggestions::Pending(_) => write!(f, "EditStepOperations::Pending"),
|
||||
WorkflowStepEditSuggestions::Resolved(ResolvedWorkflowStepEditSuggestions {
|
||||
title,
|
||||
edit_suggestions,
|
||||
}) => f
|
||||
WorkflowStepStatus::Pending(_) => write!(f, "EditStepOperations::Pending"),
|
||||
WorkflowStepStatus::Resolved(ResolvedWorkflowStep { title, suggestions }) => f
|
||||
.debug_struct("EditStepOperations::Parsed")
|
||||
.field("title", title)
|
||||
.field("edit_suggestions", edit_suggestions)
|
||||
.field("suggestions", suggestions)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
@@ -1205,16 +1209,13 @@ impl Context {
|
||||
|
||||
if let Err(ix) = existing_step_index {
|
||||
// Step doesn't exist, so add it
|
||||
let task = self.compute_workflow_step_edit_suggestions(
|
||||
tagged_range.clone(),
|
||||
project.clone(),
|
||||
cx,
|
||||
);
|
||||
let task =
|
||||
self.resolve_workflow_step(tagged_range.clone(), project.clone(), cx);
|
||||
new_edit_steps.push((
|
||||
ix,
|
||||
WorkflowStep {
|
||||
tagged_range,
|
||||
edit_suggestions: WorkflowStepEditSuggestions::Pending(task),
|
||||
status: WorkflowStepStatus::Pending(task),
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -1235,7 +1236,7 @@ impl Context {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn compute_workflow_step_edit_suggestions(
|
||||
fn resolve_workflow_step(
|
||||
&self,
|
||||
tagged_range: Range<language::Anchor>,
|
||||
project: Model<Project>,
|
||||
@@ -1265,13 +1266,13 @@ impl Context {
|
||||
});
|
||||
|
||||
// Invoke the model to get its edit suggestions for this workflow step.
|
||||
let step_suggestions = model
|
||||
.use_tool::<tool::WorkflowStepEditSuggestions>(request, &cx)
|
||||
let resolution = model
|
||||
.use_tool::<tool::WorkflowStepResolution>(request, &cx)
|
||||
.await?;
|
||||
|
||||
// Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code.
|
||||
let suggestion_tasks: Vec<_> = step_suggestions
|
||||
.edit_suggestions
|
||||
let suggestion_tasks: Vec<_> = resolution
|
||||
.suggestions
|
||||
.iter()
|
||||
.map(|suggestion| suggestion.resolve(project.clone(), cx.clone()))
|
||||
.collect();
|
||||
@@ -1293,7 +1294,7 @@ impl Context {
|
||||
|
||||
let mut suggestion_groups_by_buffer = HashMap::default();
|
||||
for (buffer, mut suggestions) in suggestions_by_buffer {
|
||||
let mut suggestion_groups = Vec::<EditSuggestionGroup>::new();
|
||||
let mut suggestion_groups = Vec::<WorkflowSuggestionGroup>::new();
|
||||
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||
// Sort suggestions by their range so that earlier, larger ranges come first
|
||||
suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
|
||||
@@ -1328,14 +1329,14 @@ impl Context {
|
||||
last_group.suggestions.push(suggestion);
|
||||
} else {
|
||||
// Create a new group
|
||||
suggestion_groups.push(EditSuggestionGroup {
|
||||
suggestion_groups.push(WorkflowSuggestionGroup {
|
||||
context_range,
|
||||
suggestions: vec![suggestion],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Create the first group
|
||||
suggestion_groups.push(EditSuggestionGroup {
|
||||
suggestion_groups.push(WorkflowSuggestionGroup {
|
||||
context_range,
|
||||
suggestions: vec![suggestion],
|
||||
});
|
||||
@@ -1353,12 +1354,10 @@ impl Context {
|
||||
})
|
||||
.map_err(|_| anyhow!("edit step not found"))?;
|
||||
if let Some(edit_step) = this.workflow_steps.get_mut(step_index) {
|
||||
edit_step.edit_suggestions = WorkflowStepEditSuggestions::Resolved(
|
||||
ResolvedWorkflowStepEditSuggestions {
|
||||
title: step_suggestions.step_title,
|
||||
edit_suggestions: suggestion_groups_by_buffer,
|
||||
},
|
||||
);
|
||||
edit_step.status = WorkflowStepStatus::Resolved(ResolvedWorkflowStep {
|
||||
title: resolution.step_title,
|
||||
suggestions: suggestion_groups_by_buffer,
|
||||
});
|
||||
cx.emit(ContextEvent::WorkflowStepsChanged);
|
||||
}
|
||||
anyhow::Ok(())
|
||||
@@ -3022,19 +3021,17 @@ mod tests {
|
||||
|
||||
model
|
||||
.as_fake()
|
||||
.respond_to_last_tool_use(Ok(serde_json::to_value(
|
||||
tool::WorkflowStepEditSuggestions {
|
||||
step_title: "Title".into(),
|
||||
edit_suggestions: vec![tool::EditSuggestion {
|
||||
path: "/root/hello.rs".into(),
|
||||
// Simulate a symbol name that's slightly different than our outline query
|
||||
kind: tool::EditSuggestionKind::Update {
|
||||
symbol: "fn main()".into(),
|
||||
description: "Extract a greeting function".into(),
|
||||
},
|
||||
}],
|
||||
},
|
||||
)
|
||||
.respond_to_last_tool_use(Ok(serde_json::to_value(tool::WorkflowStepResolution {
|
||||
step_title: "Title".into(),
|
||||
suggestions: vec![tool::WorkflowSuggestion {
|
||||
path: "/root/hello.rs".into(),
|
||||
// Simulate a symbol name that's slightly different than our outline query
|
||||
kind: tool::WorkflowSuggestionKind::Update {
|
||||
symbol: "fn main()".into(),
|
||||
description: "Extract a greeting function".into(),
|
||||
},
|
||||
}],
|
||||
})
|
||||
.unwrap()));
|
||||
|
||||
// Wait for tool use to be processed.
|
||||
@@ -3074,11 +3071,9 @@ mod tests {
|
||||
.iter()
|
||||
.map(|step| {
|
||||
let buffer = context.buffer.read(cx);
|
||||
let status = match &step.edit_suggestions {
|
||||
WorkflowStepEditSuggestions::Pending(_) => {
|
||||
WorkflowStepEditSuggestionStatus::Pending
|
||||
}
|
||||
WorkflowStepEditSuggestions::Resolved { .. } => {
|
||||
let status = match &step.status {
|
||||
WorkflowStepStatus::Pending(_) => WorkflowStepEditSuggestionStatus::Pending,
|
||||
WorkflowStepStatus::Resolved { .. } => {
|
||||
WorkflowStepEditSuggestionStatus::Resolved
|
||||
}
|
||||
};
|
||||
@@ -3490,15 +3485,15 @@ mod tool {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct WorkflowStepEditSuggestions {
|
||||
pub struct WorkflowStepResolution {
|
||||
/// An extremely short title for the edit step represented by these operations.
|
||||
pub step_title: String,
|
||||
/// A sequence of operations to apply to the codebase.
|
||||
/// When multiple operations are required for a step, be sure to include multiple operations in this list.
|
||||
pub edit_suggestions: Vec<EditSuggestion>,
|
||||
pub suggestions: Vec<WorkflowSuggestion>,
|
||||
}
|
||||
|
||||
impl LanguageModelTool for WorkflowStepEditSuggestions {
|
||||
impl LanguageModelTool for WorkflowStepResolution {
|
||||
fn name() -> String {
|
||||
"edit".into()
|
||||
}
|
||||
@@ -3527,19 +3522,19 @@ mod tool {
|
||||
/// programmatic changes to source code. It provides a structured way to describe
|
||||
/// edits for features like refactoring tools or AI-assisted coding suggestions.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditSuggestion {
|
||||
pub struct WorkflowSuggestion {
|
||||
/// The path to the file containing the relevant operation
|
||||
pub path: String,
|
||||
#[serde(flatten)]
|
||||
pub kind: EditSuggestionKind,
|
||||
pub kind: WorkflowSuggestionKind,
|
||||
}
|
||||
|
||||
impl EditSuggestion {
|
||||
impl WorkflowSuggestion {
|
||||
pub(super) async fn resolve(
|
||||
&self,
|
||||
project: Model<Project>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<(Model<Buffer>, super::EditSuggestion)> {
|
||||
) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> {
|
||||
let path = self.path.clone();
|
||||
let kind = self.kind.clone();
|
||||
let buffer = project
|
||||
@@ -3561,7 +3556,7 @@ mod tool {
|
||||
|
||||
let suggestion;
|
||||
match kind {
|
||||
EditSuggestionKind::Update {
|
||||
WorkflowSuggestionKind::Update {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3578,12 +3573,12 @@ mod tool {
|
||||
snapshot.line_len(symbol.range.end.row),
|
||||
);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
suggestion = super::EditSuggestion::Update { range, description };
|
||||
suggestion = super::WorkflowSuggestion::Update { range, description };
|
||||
}
|
||||
EditSuggestionKind::Create { description } => {
|
||||
suggestion = super::EditSuggestion::CreateFile { description };
|
||||
WorkflowSuggestionKind::Create { description } => {
|
||||
suggestion = super::WorkflowSuggestion::CreateFile { description };
|
||||
}
|
||||
EditSuggestionKind::InsertSiblingBefore {
|
||||
WorkflowSuggestionKind::InsertSiblingBefore {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3598,12 +3593,12 @@ mod tool {
|
||||
annotation_range.start
|
||||
}),
|
||||
);
|
||||
suggestion = super::EditSuggestion::InsertSiblingBefore {
|
||||
suggestion = super::WorkflowSuggestion::InsertSiblingBefore {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
}
|
||||
EditSuggestionKind::InsertSiblingAfter {
|
||||
WorkflowSuggestionKind::InsertSiblingAfter {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3612,12 +3607,12 @@ mod tool {
|
||||
.with_context(|| format!("symbol not found: {:?}", symbol))?
|
||||
.to_point(&snapshot);
|
||||
let position = snapshot.anchor_after(symbol.range.end);
|
||||
suggestion = super::EditSuggestion::InsertSiblingAfter {
|
||||
suggestion = super::WorkflowSuggestion::InsertSiblingAfter {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
}
|
||||
EditSuggestionKind::PrependChild {
|
||||
WorkflowSuggestionKind::PrependChild {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3632,18 +3627,18 @@ mod tool {
|
||||
.body_range
|
||||
.map_or(symbol.range.start, |body_range| body_range.start),
|
||||
);
|
||||
suggestion = super::EditSuggestion::PrependChild {
|
||||
suggestion = super::WorkflowSuggestion::PrependChild {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
} else {
|
||||
suggestion = super::EditSuggestion::PrependChild {
|
||||
suggestion = super::WorkflowSuggestion::PrependChild {
|
||||
position: language::Anchor::MIN,
|
||||
description,
|
||||
};
|
||||
}
|
||||
}
|
||||
EditSuggestionKind::AppendChild {
|
||||
WorkflowSuggestionKind::AppendChild {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
@@ -3658,18 +3653,18 @@ mod tool {
|
||||
.body_range
|
||||
.map_or(symbol.range.end, |body_range| body_range.end),
|
||||
);
|
||||
suggestion = super::EditSuggestion::AppendChild {
|
||||
suggestion = super::WorkflowSuggestion::AppendChild {
|
||||
position,
|
||||
description,
|
||||
};
|
||||
} else {
|
||||
suggestion = super::EditSuggestion::PrependChild {
|
||||
suggestion = super::WorkflowSuggestion::PrependChild {
|
||||
position: language::Anchor::MAX,
|
||||
description,
|
||||
};
|
||||
}
|
||||
}
|
||||
EditSuggestionKind::Delete { symbol } => {
|
||||
WorkflowSuggestionKind::Delete { symbol } => {
|
||||
let symbol = outline
|
||||
.find_most_similar(&symbol)
|
||||
.with_context(|| format!("symbol not found: {:?}", symbol))?
|
||||
@@ -3683,7 +3678,7 @@ mod tool {
|
||||
snapshot.line_len(symbol.range.end.row),
|
||||
);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
suggestion = super::EditSuggestion::Delete { range };
|
||||
suggestion = super::WorkflowSuggestion::Delete { range };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3693,7 +3688,7 @@ mod tool {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum EditSuggestionKind {
|
||||
pub enum WorkflowSuggestionKind {
|
||||
/// Rewrites the specified symbol entirely based on the given description.
|
||||
/// This operation completely replaces the existing symbol with new content.
|
||||
Update {
|
||||
@@ -3754,7 +3749,7 @@ mod tool {
|
||||
},
|
||||
}
|
||||
|
||||
impl EditSuggestionKind {
|
||||
impl WorkflowSuggestionKind {
|
||||
pub fn symbol(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Update { symbol, .. } => Some(symbol),
|
||||
@@ -3781,14 +3776,14 @@ mod tool {
|
||||
|
||||
pub fn initial_insertion(&self) -> Option<InitialInsertion> {
|
||||
match self {
|
||||
EditSuggestionKind::InsertSiblingBefore { .. } => {
|
||||
WorkflowSuggestionKind::InsertSiblingBefore { .. } => {
|
||||
Some(InitialInsertion::NewlineAfter)
|
||||
}
|
||||
EditSuggestionKind::InsertSiblingAfter { .. } => {
|
||||
WorkflowSuggestionKind::InsertSiblingAfter { .. } => {
|
||||
Some(InitialInsertion::NewlineBefore)
|
||||
}
|
||||
EditSuggestionKind::PrependChild { .. } => Some(InitialInsertion::NewlineAfter),
|
||||
EditSuggestionKind::AppendChild { .. } => Some(InitialInsertion::NewlineBefore),
|
||||
WorkflowSuggestionKind::PrependChild { .. } => Some(InitialInsertion::NewlineAfter),
|
||||
WorkflowSuggestionKind::AppendChild { .. } => Some(InitialInsertion::NewlineBefore),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,6 +894,11 @@ impl Item for Editor {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn edited_since_preview(&self, _cx: &AppContext) -> bool {
|
||||
// todo! actually implement this
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializableItem for Editor {
|
||||
|
||||
@@ -287,6 +287,10 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
|
||||
fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn edited_since_preview(&self, _cx: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SerializableItem: Item {
|
||||
@@ -427,6 +431,7 @@ pub trait ItemHandle: 'static + Send {
|
||||
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
|
||||
fn downgrade_item(&self) -> Box<dyn WeakItemHandle>;
|
||||
fn workspace_settings<'a>(&self, cx: &'a AppContext) -> &'a WorkspaceSettings;
|
||||
fn edited_since_preview(&self, cx: &AppContext) -> bool;
|
||||
}
|
||||
|
||||
pub trait WeakItemHandle: Send + Sync {
|
||||
@@ -818,6 +823,10 @@ impl<T: Item> ItemHandle for View<T> {
|
||||
) -> Option<Box<dyn SerializableItemHandle>> {
|
||||
SerializableItemRegistry::view_to_serializable_item_handle(self.to_any(), cx)
|
||||
}
|
||||
|
||||
fn edited_since_preview(&self, cx: &AppContext) -> bool {
|
||||
self.read(cx).edited_since_preview(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn ItemHandle>> for AnyView {
|
||||
|
||||
@@ -665,6 +665,12 @@ impl Pane {
|
||||
self.preview_item_id
|
||||
}
|
||||
|
||||
pub fn preview_item(&self) -> Option<Box<dyn ItemHandle>> {
|
||||
self.preview_item_id
|
||||
.and_then(|id| self.items.iter().find(|item| item.item_id() == id))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn preview_item_idx(&self) -> Option<usize> {
|
||||
if let Some(preview_item_id) = self.preview_item_id {
|
||||
self.items
|
||||
@@ -688,9 +694,9 @@ impl Pane {
|
||||
}
|
||||
|
||||
pub fn handle_item_edit(&mut self, item_id: EntityId, cx: &AppContext) {
|
||||
if let Some(preview_item_id) = self.preview_item_id {
|
||||
if preview_item_id == item_id {
|
||||
self.set_preview_item_id(None, cx)
|
||||
if let Some(preview_item) = self.preview_item() {
|
||||
if preview_item.item_id() == item_id && preview_item.edited_since_preview(cx) {
|
||||
self.set_preview_item_id(None, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2611,6 +2611,25 @@ impl Workspace {
|
||||
open_project_item
|
||||
}
|
||||
|
||||
pub fn is_project_item_open<T>(
|
||||
&self,
|
||||
pane: &View<Pane>,
|
||||
project_item: &Model<T::Item>,
|
||||
cx: &AppContext,
|
||||
) -> bool
|
||||
where
|
||||
T: ProjectItem,
|
||||
{
|
||||
use project::Item as _;
|
||||
|
||||
project_item
|
||||
.read(cx)
|
||||
.entry_id(cx)
|
||||
.and_then(|entry_id| pane.read(cx).item_for_entry(entry_id, cx))
|
||||
.and_then(|item| item.downcast::<T>())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn open_project_item<T>(
|
||||
&mut self,
|
||||
pane: View<Pane>,
|
||||
|
||||
Reference in New Issue
Block a user