Bundle editing workflow prompt as a read-only built-in prompt (#15615)

Built-in prompts can still be removed from the default prompt, but they
can't be edited and are automatically updated with new Zed releases.

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
Nathan Sobo 2024-08-01 15:56:17 +02:00 committed by GitHub
parent be3a8584ff
commit a9c6e435f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 207 additions and 103 deletions

View File

@ -0,0 +1,87 @@
Guide the user through code changes in numbered steps that focus on individual functions, type definitions, etc.
Surround each distinct step in a <step></step> XML tag.
Here's an example of a user-assistant dialog with step numbers and a follow-up question in Rust:
1. User: I want to create a User struct with name and age fields, and a method to print the user's information. Can you help me with that?
A: Certainly! Let's create a User struct with the specified fields and a method to print the information:
<step>Define the User struct
```rust
struct User {
name: String,
age: u32,
}
```
</step>
<step>Implement methods for the User struct
```rust
impl User {
fn new(name: String, age: u32) -> Self {
User { name, age }
}
fn print_info(&self) {
println!("Name: {}, Age: {}", self.name, self.age);
}
}
```
</step>
This code defines a User struct and implements two methods:
- `new`: a constructor to create a new User
- `print_info`: a method to print the user's information
2. User: Great! Now, how can I create a User instance and call the print_info method?
A: You can create a User instance and call the print_info method like this:
<step>Create a User instance and call print_info
```rust
fn main() {
let user = User::new(String::from("Alice"), 30);
user.print_info();
}
```
</step>
This will output: `Name: Alice, Age: 30`
3. User: Can you add an email field to the User struct and update the print_info method to include it?
A: Certainly! Let's modify the User struct and update the print_info method:
<step>Add email field to User struct
```rust
struct User {
name: String,
age: u32,
email: String,
}
```
</step>
<step>Update the new method and print_info method
```rust
impl User {
fn new(name: String, age: u32, email: String) -> Self {
User { name, age, email }
}
fn print_info(&self) {
println!("Name: {}, Age: {}, Email: {}", self.name, self.age, self.email);
}
}
```
</step>
<step>Update the main function
```rust
fn main() {
let user = User::new(String::from("Alice"), 30, String::from("alice@example.com"));
user.print_info();
}
```
</step>
This will now output: `Name: Alice, Age: 30, Email: alice@example.com`
The changes include:
1. Adding the `email` field to the User struct
2. Updating the `new` method to accept an email parameter
3. Modifying the `print_info` method to include the email
4. Updating the main function to provide an email when creating a User instance

View File

@ -1386,7 +1386,7 @@ impl Context {
async move {
let prompt_store = cx.update(|cx| PromptStore::global(cx))?.await?;
let mut prompt = prompt_store.operations_prompt();
let mut prompt = prompt_store.step_resolution_prompt();
prompt.push_str(&step_text);
request.messages.push(LanguageModelRequestMessage {

View File

@ -66,6 +66,11 @@ pub fn init(cx: &mut AppContext) {
cx.set_global(GlobalPromptStore(prompt_store_future))
}
const BUILT_IN_TOOLTIP_TEXT: &'static str = concat!(
"This prompt supports special functionality.\n",
"It's read-only, but you can remove it from your default prompt."
);
/// This function opens a new prompt library window if one doesn't exist already.
/// If one exists, it brings it to the foreground.
///
@ -233,15 +238,29 @@ impl PickerDelegate for PromptPickerDelegate {
.end_hover_slot(
h_flex()
.gap_2()
.child(
.child(if prompt_id.is_built_in() {
div()
.id("built-in-prompt")
.child(Icon::new(IconName::FileLock).color(Color::Muted))
.tooltip(move |cx| {
Tooltip::with_meta(
"Built-in prompt",
None,
BUILT_IN_TOOLTIP_TEXT,
cx,
)
})
.into_any()
} else {
IconButton::new("delete-prompt", IconName::Trash)
.icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::Deleted { prompt_id })
})),
)
}))
.into_any_element()
})
.child(
IconButton::new("toggle-default-prompt", IconName::Sparkle)
.selected(default)
@ -354,6 +373,10 @@ impl PromptLibrary {
pub fn save_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
const SAVE_THROTTLE: Duration = Duration::from_millis(500);
if prompt_id.is_built_in() {
return;
}
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
let title = prompt_editor.title_editor.read(cx).text(cx);
@ -463,6 +486,7 @@ impl PromptLibrary {
let mut editor = Editor::auto_width(cx);
editor.set_placeholder_text("Untitled", cx);
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
editor.set_read_only(true);
editor
});
let body_editor = cx.new_view(|cx| {
@ -474,6 +498,7 @@ impl PromptLibrary {
});
let mut editor = Editor::for_buffer(buffer, None, cx);
editor.set_read_only(true);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx);
editor.set_show_wrap_guides(false, cx);
@ -943,7 +968,23 @@ impl PromptLibrary {
)
},
))
.child(
.child(if prompt_id.is_built_in() {
div()
.id("built-in-prompt")
.child(
Icon::new(IconName::FileLock)
.color(Color::Muted),
)
.tooltip(move |cx| {
Tooltip::with_meta(
"Built-in prompt",
None,
BUILT_IN_TOOLTIP_TEXT,
cx,
)
})
.into_any()
} else {
IconButton::new(
"delete-prompt",
IconName::Trash,
@ -961,8 +1002,9 @@ impl PromptLibrary {
})
.on_click(|_, cx| {
cx.dispatch_action(Box::new(DeletePrompt));
}),
)
})
.into_any_element()
})
.child(
IconButton::new(
"duplicate-prompt",
@ -1062,7 +1104,6 @@ pub struct PromptMetadata {
pub title: Option<SharedString>,
pub default: bool,
pub saved_at: DateTime<Utc>,
pub built_in: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
@ -1078,6 +1119,10 @@ impl PromptId {
uuid: Uuid::new_v4(),
}
}
pub fn is_built_in(&self) -> bool {
!matches!(self, PromptId::User { .. })
}
}
pub struct PromptStore {
@ -1163,13 +1208,17 @@ impl PromptStore {
let metadata_cache = MetadataCache::from_db(metadata, &txn)?;
txn.commit()?;
Ok(PromptStore {
let store = PromptStore {
executor,
env: db_env,
metadata_cache: RwLock::new(metadata_cache),
metadata,
bodies,
})
};
store.save_built_in_prompts().log_err();
Ok(store)
}
})
}
@ -1237,7 +1286,6 @@ impl PromptStore {
title: metadata_v1.title.clone(),
default: metadata_v1.default,
saved_at: metadata_v1.saved_at,
built_in: false,
},
)?;
bodies_db.put(&mut txn, &prompt_id_v2, &body_v1)?;
@ -1346,12 +1394,15 @@ impl PromptStore {
default: bool,
body: Rope,
) -> Task<Result<()>> {
if id.is_built_in() {
return Task::ready(Err(anyhow!("built-in prompts cannot be saved")));
}
let prompt_metadata = PromptMetadata {
id,
title,
default,
saved_at: Utc::now(),
built_in: false,
};
self.metadata_cache.write().insert(prompt_metadata.clone());
@ -1371,20 +1422,72 @@ impl PromptStore {
})
}
fn save_built_in_prompts(&self) -> Result<()> {
self.save_built_in_prompt(
PromptId::EditWorkflow,
"Built-in: Editing Workflow",
"prompts/edit_workflow.md",
)?;
Ok(())
}
/// Write a built-in prompt to the database, preserving the value of the default field
/// if a prompt with this id already exists. This method blocks.
fn save_built_in_prompt(
&self,
id: PromptId,
title: impl Into<SharedString>,
body_path: &str,
) -> Result<()> {
let mut metadata_cache = self.metadata_cache.write();
let existing_metadata = metadata_cache.metadata_by_id.get(&id).cloned();
let prompt_metadata = PromptMetadata {
id,
title: Some(title.into()),
default: existing_metadata.map_or(true, |m| m.default),
saved_at: Utc::now(),
};
metadata_cache.insert(prompt_metadata.clone());
let db_connection = self.env.clone();
let bodies = self.bodies;
let metadata_db = self.metadata;
let mut txn = db_connection.write_txn()?;
metadata_db.put(&mut txn, &id, &prompt_metadata)?;
let body = String::from_utf8(Assets.load(body_path)?.unwrap().to_vec())?;
bodies.put(&mut txn, &id, &body)?;
txn.commit()?;
Ok(())
}
fn save_metadata(
&self,
id: PromptId,
title: Option<SharedString>,
mut title: Option<SharedString>,
default: bool,
) -> Task<Result<()>> {
let mut cache = self.metadata_cache.write();
if id.is_built_in() {
title = cache
.metadata_by_id
.get(&id)
.and_then(|metadata| metadata.title.clone());
}
let prompt_metadata = PromptMetadata {
id,
title,
default,
saved_at: Utc::now(),
built_in: false,
};
self.metadata_cache.write().insert(prompt_metadata.clone());
cache.insert(prompt_metadata.clone());
let db_connection = self.env.clone();
let metadata = self.metadata;
@ -1402,10 +1505,10 @@ impl PromptStore {
self.metadata_cache.read().metadata.first().cloned()
}
pub fn operations_prompt(&self) -> String {
pub fn step_resolution_prompt(&self) -> String {
String::from_utf8(
Assets
.load("prompts/operations.md")
.load("prompts/step_resolution.md")
.unwrap()
.unwrap()
.to_vec(),

View File

@ -1,86 +0,0 @@
When the user asks you to suggest edits for a buffer, use a strict template consisting of:
* A markdown code block with the file path as the language identifier.
* The original code that should be replaced
* A separator line (`---`)
* The new text that should replace the original lines
Each code block may only contain an edit for one single contiguous range of text. Use multiple code blocks for multiple edits.
## Example
If you have a buffer with the following lines:
```path/to/file.rs
fn quicksort(arr: &mut [i32]) {
if arr.len() <= 1 {
return;
}
let pivot_index = partition(arr);
let (left, right) = arr.split_at_mut(pivot_index);
quicksort(left);
quicksort(&mut right[1..]);
}
fn partition(arr: &mut [i32]) -> usize {
let last_index = arr.len() - 1;
let pivot = arr[last_index];
let mut i = 0;
for j in 0..last_index {
if arr[j] <= pivot {
arr.swap(i, j);
i += 1;
}
}
arr.swap(i, last_index);
i
}
```
And you want to replace the for loop inside `partition`, output the following.
```edit path/to/file.rs
for j in 0..last_index {
if arr[j] <= pivot {
arr.swap(i, j);
i += 1;
}
}
---
let mut j = 0;
while j < last_index {
if arr[j] <= pivot {
arr.swap(i, j);
i += 1;
}
j += 1;
}
```
If you wanted to insert comments above the partition function, output the following:
```edit path/to/file.rs
fn partition(arr: &mut [i32]) -> usize {
---
// A helper function used for quicksort.
fn partition(arr: &mut [i32]) -> usize {
```
If you wanted to delete the partition function, output the following:
```edit path/to/file.rs
fn partition(arr: &mut [i32]) -> usize {
let last_index = arr.len() - 1;
let pivot = arr[last_index];
let mut i = 0;
for j in 0..last_index {
if arr[j] <= pivot {
arr.swap(i, j);
i += 1;
}
}
arr.swap(i, last_index);
i
}
---
```