mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
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:
parent
be3a8584ff
commit
a9c6e435f7
87
assets/prompts/edit_workflow.md
Normal file
87
assets/prompts/edit_workflow.md
Normal 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
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
}
|
||||
---
|
||||
```
|
Loading…
Reference in New Issue
Block a user