Improve workflow prompt, accept nonexistent directories from workflows (#16251)

Release Notes:

- Workflows can now create new files in folders that didn't exist

---------

Co-authored-by: jvmncs <7891333+jvmncs@users.noreply.github.com>
This commit is contained in:
Richard Feldman 2024-08-14 18:18:41 -04:00 committed by GitHub
parent a6461f90a1
commit 796cba9e0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 285 additions and 55 deletions

View file

@ -1,87 +1,302 @@
<workflow>
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.
Surround each distinct step in a <step></step> XML tag. The user will be performing these steps in a code editor
named Zed, which is where they will have entered this prompt and will be seeing the response.
Here's an example of a user-assistant dialog with step numbers and a follow-up question in Rust:
<instructions>
- Use the language of the file for code fence blocks unless otherwise specified.
- Include a code or file action in each step.
- Only put code in separate steps if it should either go in separate files, or in different (non-contiguous) places in the same file.
- Provide error handling and input validation where appropriate.
- Adapt explanations based on the user's perceived level of expertise.
- Include comments in code examples to enhance understanding.
- Consider more complex programming scenarios when relevant.
- Avoid using the terminal to perform filesystem operations such as creating, deleting, renaming, and editing files. Assume the user prefers to use Zed for these operations, unless the prompt suggests that the user is expecting terminal commands.
- When creating a new file, Zed will automatically create any necessary directories in that file's path. So when a step will create a new file in Zed, don't mention needing to create the directory that it will go in.
- Assume the user is performing these actions in the Zed code editor, so avoid redundancies like "In your code editor, ..." or "In Zed, ..."
- Be concise without leaving out important information.
</instructions>
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
Examples of user-assistant dialogs with step numbers and user follow-up requests:
<example language="rust">
<turn number="1">
<user>Create a Library struct with a vector of Book structs. Include methods to add books and find books by author. Use generics for the book ID.</user>
<assistant>Certainly! Let's create a Library struct with a vector of Book structs, including methods to add books and find books by author, using generics for the book ID:
<step>Define the Book and Library structs
```rust
struct User {
name: String,
age: u32,
use std::fmt::Display;
struct Book<T>
where
T: Display + Clone
{
id: T,
title: String,
author: String,
}
struct Library<T>
where
T: Display + Clone
{
books: Vec<Book<T>>,
}
```
</step>
<step>Implement methods for the User struct
<step>Implement methods for the Library struct
```rust
impl User {
fn new(name: String, age: u32) -> Self {
User { name, age }
impl<T> Library<T>
where
T: Display + Clone
{
fn new() -> Self {
Library { books: Vec::new() }
}
fn print_info(&self) {
println!("Name: {}, Age: {}", self.name, self.age);
fn add_book(&mut self, id: T, title: String, author: String) {
let book = Book { id, title, author };
self.books.push(book);
}
fn find_books_by_author(&self, author: &str) -> Vec<&Book<T>> {
self.books
.iter()
.filter(|book| book.author.to_lowercase() == author.to_lowercase())
.collect()
}
}
```
</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
This code defines:
1. A generic Book struct with an ID of type T, title, and author
2. A Library struct containing a vector of Books
3. Methods to create a new Library, add books, and find books by author
The use of generics allows flexibility in the type of ID used for books.</assistant>
</turn>
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:
<turn number="2">
<user>How can I use this Library struct in a main function?</user>
<assistant>Here's how you can use the Library struct in a main function:
<step>Create a User instance and call print_info
<step>Create a main function to demonstrate Library usage
```rust
fn main() {
let user = User::new(String::from("Alice"), 30);
user.print_info();
}
```
</step>
// Create a new Library with string IDs
let mut library = Library::new();
This will output: `Name: Alice, Age: 30`
// Add some books
library.add_book(String::from("001"), String::from("The Rust Programming Language"), String::from("Steve Klabnik"));
library.add_book(String::from("002"), String::from("Programming Rust"), String::from("Jim Blandy"));
library.add_book(String::from("003"), String::from("Rust in Action"), String::from("Tim McNamara"));
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:
// Find books by author
let author = "Steve Klabnik";
let books = library.find_books_by_author(author);
<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);
println!("Books by {}:", author);
for book in books {
println!("ID: {}, Title: {}", book.id, book.title);
}
}
```
</step>
<step>Update the main function
This main function demonstrates:
1. Creating a new Library instance
2. Adding books with string IDs
3. Finding and displaying books by a specific author</assistant>
</turn>
<turn number="3">
<user>Can you add a method to remove a book by ID and handle potential errors?</user>
<assistant>Certainly! Let's add a method to remove a book by ID and handle potential errors:
<step>Add a remove_book method to the Library struct
```rust
use std::fmt::Display;
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct BookNotFoundError;
impl Error for BookNotFoundError {}
impl fmt::Display for BookNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Book not found")
}
}
impl<T> Library<T>
where
T: Display + Clone + PartialEq
{
// ... (previous methods remain the same)
fn remove_book(&mut self, id: &T) -> Result<Book<T>, BookNotFoundError> {
if let Some(index) = self.books.iter().position(|book| &book.id == id) {
Ok(self.books.remove(index))
} else {
Err(BookNotFoundError)
}
}
}
```
</step>
<step>Update the main function to demonstrate book removal
```rust
fn main() {
let user = User::new(String::from("Alice"), 30, String::from("alice@example.com"));
user.print_info();
// ... (previous code remains the same)
// Attempt to remove a book
let id_to_remove = String::from("002");
match library.remove_book(&id_to_remove) {
Ok(removed_book) => println!("Removed book: {} by {}", removed_book.title, removed_book.author),
Err(e) => println!("Error removing book: {}", e),
}
// Attempt to remove a non-existent book
let non_existent_id = String::from("999");
match library.remove_book(&non_existent_id) {
Ok(_) => println!("Book removed successfully"),
Err(e) => println!("Error removing book: {}", e),
}
// Display remaining books
println!("\nRemaining books:");
for book in &library.books {
println!("ID: {}, Title: {} by {}", book.id, book.title, book.author);
}
}
```
</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
This update includes:
1. A new remove_book method that returns a Result type
2. A custom BookNotFoundError for error handling
3. Demonstration of successful book removal and error handling for non-existent books
4. Display of remaining books after removal attempts
The remove_book method uses the PartialEq trait to compare book IDs, allowing for flexible ID types while maintaining type safety.</assistant>
</turn>
</example>
<example language="go">
<turn number="1">
<user>Create a Person struct with name, age, and email fields, and a method to greet. Include error handling for age.</user>
<assistant>Certainly! Let's create a Person struct with the specified fields and a method to greet in Go:
<step>Define the Person struct and NewPerson function with error handling
```go
type Person struct {
Name string
Age int
Email string
}
func NewPerson(name string, age int, email string) (*Person, error) {
if age < 0 || age > 150 {
return nil, fmt.Errorf("invalid age: %d", age)
}
return &Person{Name: name, Age: age, Email: email}, nil
}
```
</step>
<step>Implement the Greet method for the Person struct
```go
func (p *Person) Greet() string {
return fmt.Sprintf("Hello, I'm %s, %d years old. You can reach me at %s.", p.Name, p.Age, p.Email)
}
```
</step>
This code defines a Person struct, a NewPerson function with age validation, and a Greet method.</assistant>
</turn>
<turn number="2">
<user>How can I use these in a main function?</user>
<assistant>Here's how to use the Person struct and its methods in a main function:
<step>Create a main function with error handling
```go
func main() {
// Create a new Person instance
person, err := NewPerson("Alice", 30, "alice@example.com")
if err != nil {
fmt.Println("Error creating person:", err)
return
}
// Use the Greet method
fmt.Println(person.Greet())
// Demonstrate error handling with invalid age
invalidPerson, err := NewPerson("Bob", 200, "bob@example.com")
if err != nil {
fmt.Println("Error creating person with invalid age:", err)
}
}
```
</step>
This main function demonstrates:
1. Creating a valid Person instance
2. Using the Greet method
3. Handling errors when creating a Person with an invalid age</assistant>
</turn>
<turn number="3">
<user>Can you add a method to update the email with validation?</user>
<assistant>Certainly! Let's add a method to update the email with validation:
<step>Add UpdateEmail method with validation
```go
import (
"fmt"
"regexp"
)
func (p *Person) UpdateEmail(newEmail string) error {
// Simple email validation using regex
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailRegex.MatchString(newEmail) {
return fmt.Errorf("invalid email format: %s", newEmail)
}
p.Email = newEmail
return nil
}
```
</step>
<step>Update the main function to demonstrate email update
```go
func main() {
// ... (previous code remains the same)
// Demonstrate email update
err = person.UpdateEmail("alice.new@example.com")
if err != nil {
fmt.Println("Error updating email:", err)
} else {
fmt.Println("Updated greeting:", person.Greet())
}
// Demonstrate email update with invalid email
err = person.UpdateEmail("invalid-email")
if err != nil {
fmt.Println("Error updating email:", err)
}
}
```
</step>
This update includes:
1. An UpdateEmail method with email format validation
2. Demonstration of successful email update in the main function
3. Handling of invalid email update attempt</assistant>
</turn>
</example>
</workflow>

View file

@ -3802,6 +3802,7 @@ mod tests {
mod tool {
use gpui::AsyncAppContext;
use project::ProjectPath;
use super::*;
@ -3862,6 +3863,20 @@ mod tool {
.update(&mut cx, |project, cx| {
let project_path = project
.find_project_path(Path::new(&path), cx)
.or_else(|| {
// If we couldn't find a project path for it, put it in the active worktree
// so that when we create the buffer, it can be saved.
let worktree = project
.active_entry()
.and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
.or_else(|| project.worktrees(cx).next())?;
let worktree = worktree.read(cx);
Some(ProjectPath {
worktree_id: worktree.id(),
path: Arc::from(Path::new(&path)),
})
})
.with_context(|| format!("worktree not found for {:?}", path))?;
anyhow::Ok(project.open_buffer(project_path, cx))
})??