Have models indicate code locations in workflows using textual search, not symbol names (#17282)
Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
c63c2015a6
commit
b41ddbd018
4 changed files with 410 additions and 820 deletions
|
@ -27,17 +27,17 @@ impl Person {
|
|||
```
|
||||
|
||||
<edit>
|
||||
<path>src/person.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<symbol>struct Person height</symbol>
|
||||
<description>Add the age field</description>
|
||||
<path>src/person.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<search>height: f32,</search>
|
||||
<description>Add the age field</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/person.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Person</symbol>
|
||||
<description>Add the age getter</description>
|
||||
<path>src/person.rs</path>
|
||||
<operation>insert_after</operation>
|
||||
<search>impl Person {</search>
|
||||
<description>Add the age getter</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
|
@ -45,15 +45,15 @@ impl Person {
|
|||
|
||||
First, each `<step>` must contain a written description of the change that should be made. The description should begin with a high-level overview, and can contain markdown code blocks as well. The description should be self-contained and actionable.
|
||||
|
||||
Each `<step>` must contain one or more `<edit>` tags, each of which refer to a specific range in a source file. Each `<edit>` tag must contain the following child tags:
|
||||
After the description, each `<step>` must contain one or more `<edit>` tags, each of which refer to a specific range in a source file. Each `<edit>` tag must contain the following child tags:
|
||||
|
||||
### `<path>` (required)
|
||||
|
||||
This tag contains the path to the file that will be changed. It can be an existing path, or a path that should be created.
|
||||
|
||||
### `<symbol>` (optional)
|
||||
### `<search>` (optional)
|
||||
|
||||
This tag contains the fully-qualified name of a symbol in the source file, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`. If not provided, the new content will be inserted at the top of the file.
|
||||
This tag contains a search string to locate in the source file, e.g. `pub fn baz() {`. If not provided, the new content will be inserted at the top of the file. Make sure to produce a string that exists in the source file and that isn't ambiguous. When there's ambiguity, add more lines to the search to eliminate it.
|
||||
|
||||
### `<description>` (required)
|
||||
|
||||
|
@ -62,110 +62,179 @@ This tag contains a single-line description of the edit that should be made at t
|
|||
### `<operation>` (required)
|
||||
|
||||
This tag indicates what type of change should be made, relative to the given location. It can be one of the following:
|
||||
- `update`: Rewrites the specified symbol entirely based on the given description.
|
||||
- `update`: Rewrites the specified string entirely based on the given description.
|
||||
- `create`: Creates a new file with the given path based on the provided description.
|
||||
- `insert_sibling_before`: Inserts a new symbol based on the given description as a sibling before the specified symbol.
|
||||
- `insert_sibling_after`: Inserts a new symbol based on the given description as a sibling after the specified symbol.
|
||||
- `prepend_child`: Inserts a new symbol as a child of the specified symbol at the start.
|
||||
- `append_child`: Inserts a new symbol as a child of the specified symbol at the end.
|
||||
- `delete`: Deletes the specified symbol from the containing file.
|
||||
- `insert_before`: Inserts new text based on the given description before the specified search string.
|
||||
- `insert_after`: Inserts new text based on the given description after the specified search string.
|
||||
- `delete`: Deletes the specified string from the containing file.
|
||||
|
||||
<guidelines>
|
||||
- There's no need to describe *what* to do, just *where* to do it.
|
||||
- Only reference locations that actually exist (unless you're creating a file).
|
||||
- If creating a file, assume any subsequent updates are included at the time of creation.
|
||||
- Don't create and then update a file. Always create new files in shot.
|
||||
- Prefer updating symbols lower in the syntax tree if possible.
|
||||
- Never include edits on a parent symbol and one of its children in the same edit block.
|
||||
- Don't create and then update a file. Always create new files in one hot.
|
||||
- Prefer multiple edits to smaller regions, as opposed to one big edit to a larger region.
|
||||
- Don't produce edits that intersect each other. In that case, merge them into a bigger edit.
|
||||
- Never nest an edit with another edit. Never include CDATA. All edits are leaf nodes.
|
||||
- Descriptions are required for all edits except delete.
|
||||
- When generating multiple edits, 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.
|
||||
- Avoid referring to the search string in the description. Focus on the change to be made, not the location where it's made. That's implicit with the `search` string you provide.
|
||||
- Don't generate multiple edits at the same location. Instead, combine them together in a single edit with a succinct combined description.
|
||||
- Always ensure imports are added if you're referencing symbols that are not in scope. To manipulate imports, produce an edit where the `"symbol"` key is set to `"#imports"`
|
||||
- Always ensure imports are added if you're referencing symbols that are not in scope.
|
||||
</guidelines>
|
||||
|
||||
Here are some concrete examples.
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<example>
|
||||
<message role="user">
|
||||
```rs src/rectangle.rs
|
||||
struct Rectangle {
|
||||
|
||||
```rs src/shapes.rs
|
||||
pub mod rectangle;
|
||||
pub mod circle;
|
||||
```
|
||||
|
||||
```rs src/shapes/rectangle.rs
|
||||
pub struct Rectangle {
|
||||
width: f64,
|
||||
height: f64,
|
||||
}
|
||||
|
||||
impl Rectangle {
|
||||
fn new(width: f64, height: f64) -> Self {
|
||||
pub fn new(width: f64, height: f64) -> Self {
|
||||
Rectangle { width, height }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We need to add methods to calculate the area and perimeter of the rectangle. Can you help with that?
|
||||
```rs src/shapes/circle.rs
|
||||
pub struct Circle {
|
||||
radius: f64,
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(radius: f64) -> Self {
|
||||
Circle { radius }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update all shapes to store their origin as an (x, y) tuple and implement Display.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
We'll need to update both the rectangle and circle modules.
|
||||
|
||||
<step>
|
||||
Add methods to calculate the area and perimeter of the rectangle
|
||||
Add origin fields to both shape types.
|
||||
|
||||
```rust
|
||||
impl Rectangle {
|
||||
// existing methods...
|
||||
struct Rectangle {
|
||||
// existing fields ...
|
||||
origin: (f64, f64),
|
||||
}
|
||||
```
|
||||
|
||||
fn calculate_area(&self) -> f64 {
|
||||
self.width * self.height
|
||||
}
|
||||
|
||||
fn calculate_perimeter(&self) -> f64 {
|
||||
2.0 * (self.width + self.height)
|
||||
}
|
||||
```rust
|
||||
struct Circle {
|
||||
// existing fields ...
|
||||
origin: (f64, f64),
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/rectangle.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Rectangle</symbol>
|
||||
<description>Add calculate_area and calculate_perimeter methods</description>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<search>
|
||||
width: f64,
|
||||
height: f64,
|
||||
</search>
|
||||
<description>Add the origin field to Rectangle</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<search>
|
||||
radius: f64,
|
||||
</search>
|
||||
<description>Add the origin field to Circle</description>
|
||||
</edit>
|
||||
|
||||
<step>
|
||||
Update both shape's constructors to take an origin.
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<operation>update</operation>
|
||||
<search>
|
||||
fn new(width: f64, height: f64) -> Self {
|
||||
Rectangle { width, height }
|
||||
}
|
||||
</search>
|
||||
<description>Update the Rectangle new function to take an origin</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<operation>update</operation>
|
||||
<search>
|
||||
fn new(radius: f64) -> Self {
|
||||
Circle { radius }
|
||||
}
|
||||
</search>
|
||||
<description>Update the Circle new function to take an origin</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
Implement the Display trait for the Rectangle struct
|
||||
|
||||
```rust
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for Rectangle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Rectangle: {}x{}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
```
|
||||
Implement Display for both shapes
|
||||
|
||||
<edit>
|
||||
<path>src/rectangle.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>impl Rectangle</symbol>
|
||||
<description>Implement Display trait for Rectangle</description>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<search>
|
||||
struct Rectangle {
|
||||
</search>
|
||||
<description>Add an import for the `std::fmt` module</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/rectangle.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Add use statement for fmt</description>
|
||||
<path>src/shapes/rectangle.rs</path>
|
||||
<operation>insert_after</operation>
|
||||
<search>
|
||||
Rectangle { width, height }
|
||||
}
|
||||
}
|
||||
</search>
|
||||
<description>Add a Display implementation for Rectangle</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<operation>insert_before</operation>
|
||||
<search>
|
||||
struct Circle {
|
||||
</search>
|
||||
<description>Add an import for the `std::fmt` module</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/shapes/circle.rs</path>
|
||||
<operation>insert_after</operation>
|
||||
<search>
|
||||
Circle { radius }
|
||||
}
|
||||
}
|
||||
</search>
|
||||
<description>Add a Display implementation for Circle</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<example number=2>
|
||||
<message role="user">
|
||||
|
||||
```rs src/user.rs
|
||||
struct User {
|
||||
pub name: String,
|
||||
|
@ -202,10 +271,14 @@ impl User {
|
|||
```
|
||||
|
||||
<edit>
|
||||
<path>src/user.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl User pub fn print_info</symbol>
|
||||
<description>Print all the user information</description>
|
||||
<path>src/user.rs</path>
|
||||
<operation>update</operation>
|
||||
<search>
|
||||
pub fn print_info(&self) {
|
||||
todo!()
|
||||
}
|
||||
</search>
|
||||
<description>Print all the user information</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
|
@ -213,442 +286,25 @@ impl User {
|
|||
Remove the 'email' field from the User struct
|
||||
|
||||
<edit>
|
||||
<path>src/user.rs</path>
|
||||
<operation>delete</operation>
|
||||
<symbol>struct User email</symbol>
|
||||
<path>src/user.rs</path>
|
||||
<operation>delete</operation>
|
||||
<search>
|
||||
email: String,
|
||||
</search>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/user.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl User fn new</symbol>
|
||||
<description>Remove email parameter from new method</description>
|
||||
</edit>
|
||||
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/vehicle.rs
|
||||
struct Vehicle {
|
||||
make: String,
|
||||
model: String,
|
||||
year: u32,
|
||||
<path>src/user.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>
|
||||
fn new(name: String, age: u32, email: String) -> Self {
|
||||
User { name, age, email }
|
||||
}
|
||||
|
||||
impl Vehicle {
|
||||
fn new(make: String, model: String, year: u32) -> Self {
|
||||
Vehicle { make, model, year }
|
||||
}
|
||||
|
||||
fn print_year(&self) {
|
||||
println!("Year: {}", self.year);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's add a `start_engine` method for the vehicle and print how long it takes to start it.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add a 'use std::time::Instant;' statement at the beginning of the file
|
||||
|
||||
<edit>
|
||||
<path>src/vehicle.rs</path>
|
||||
<operation>prepend_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Add use statement for std::time::Instant</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
Add a new method 'start_engine' in the Vehicle impl block
|
||||
|
||||
```rust
|
||||
impl Vehicle {
|
||||
// ... existing methods ...
|
||||
|
||||
fn start_engine(&self) {
|
||||
let t0 = Instant::now();
|
||||
println!("Starting the engine of {} {} {}. Took: {:?}",
|
||||
self.year, self.make, self.model, t0.elapsed());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/vehicle.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Vehicle</symbol>
|
||||
<description>Add start_engine method</description>
|
||||
</symbol>
|
||||
<description>Remove email parameter from new method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/employee.rs
|
||||
struct Employee {
|
||||
name: String,
|
||||
position: String,
|
||||
salary: u32,
|
||||
department: String,
|
||||
}
|
||||
|
||||
impl Employee {
|
||||
fn new(name: String, position: String, salary: u32, department: String) -> Self {
|
||||
Employee { name, position, salary, department }
|
||||
}
|
||||
|
||||
fn print_details(&self) {
|
||||
println!("Name: {}, Position: {}, Salary: {}, Department: {}",
|
||||
self.name, self.position, self.salary, self.department);
|
||||
}
|
||||
|
||||
fn give_raise(&mut self, amount: u32) {
|
||||
self.salary += amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's turn the salary field into an f32. Also, let's delete the department field.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Make salary an f32
|
||||
|
||||
```rust
|
||||
struct Employee {
|
||||
name: String,
|
||||
position: String,
|
||||
salary: f32,
|
||||
department: String,
|
||||
}
|
||||
|
||||
impl Employee {
|
||||
fn new(name: String, position: String, salary: f32, department: String) -> Self {
|
||||
Employee { name, position, salary, department }
|
||||
}
|
||||
|
||||
// ... other methods ...
|
||||
|
||||
fn give_raise(&mut self, amount: f32) {
|
||||
self.salary += amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>struct Employee salary</symbol>
|
||||
<description>Change salary type to f32</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn new</symbol>
|
||||
<description>Update new method to use f32 for salary</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn give_raise</symbol>
|
||||
<description>Update give_raise method to use f32 for amount</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
||||
<step>
|
||||
Remove the 'department' field and update the 'print_details' method
|
||||
|
||||
```rust
|
||||
struct Employee {
|
||||
name: String,
|
||||
position: String,
|
||||
salary: f32,
|
||||
}
|
||||
|
||||
impl Employee {
|
||||
fn new(name: String, position: String, salary: f32) -> Self {
|
||||
Employee { name, position, salary }
|
||||
}
|
||||
|
||||
fn print_details(&self) {
|
||||
println!("Name: {}, Position: {}, Salary: {}",
|
||||
self.name, self.position, self.salary);
|
||||
}
|
||||
|
||||
// ... other methods ...
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>delete</operation>
|
||||
<symbol>struct Employee department</symbol>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn new</symbol>
|
||||
<description>Remove department parameter from new method</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/employee.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Employee fn print_details</symbol>
|
||||
<description>Remove department from print_details method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/game.rs
|
||||
struct Player {
|
||||
name: String,
|
||||
health: i32,
|
||||
pub score: u32,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(name: String) -> Self {
|
||||
Player { name, health: 100, score: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct Game {
|
||||
players: Vec<Player>,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn new() -> Self {
|
||||
Game { players: Vec::new() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Introduce a level field.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add a 'level' field to Player and update the 'new' method
|
||||
|
||||
```rust
|
||||
struct Player {
|
||||
name: String,
|
||||
health: i32,
|
||||
pub score: u32,
|
||||
level: u8,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(name: String) -> Self {
|
||||
Player { name, health: 100, score: 0, level: 1 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/game.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>struct Player pub score</symbol>
|
||||
<description>Add level field to Player struct</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/game.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Player pub fn new</symbol>
|
||||
<description>Initialize level in new method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/config.rs
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct Config {
|
||||
settings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config { settings: HashMap::new() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I want to load the configuration from a file.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add a 'load_from_file' method to Config and import necessary modules
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead};
|
||||
use std::path::Path;
|
||||
|
||||
struct Config {
|
||||
settings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
Config { settings: HashMap::new() }
|
||||
}
|
||||
|
||||
fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let file = File::open(path)?;
|
||||
for line in io::BufReader::new(file).lines() {
|
||||
let line = line?;
|
||||
if let Some((key, value)) = line.split_once('=') {
|
||||
self.settings.insert(key.trim().to_string(), value.trim().to_string());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/config.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Import necessary modules for file operations</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/config.rs</path>
|
||||
<operation>append_child</operation>
|
||||
<symbol>impl Config</symbol>
|
||||
<description>Add load_from_file method</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
<example language="rust">
|
||||
|
||||
<message role="user">
|
||||
```rs src/database.rs
|
||||
pub(crate) struct Database {
|
||||
connection: Connection,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
fn new(url: &str) -> Result<Self, Error> {
|
||||
let connection = Connection::connect(url)?;
|
||||
Ok(Database { connection })
|
||||
}
|
||||
|
||||
async fn query(&self, sql: &str) -> Result<Vec<Row>, Error> {
|
||||
self.connection.query(sql, &[])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
I want to add error handling to this module.
|
||||
</message>
|
||||
|
||||
<message role="assistant">
|
||||
<step>
|
||||
Add error handling to the 'query' method and create a custom error type
|
||||
|
||||
```rust
|
||||
use std::fmt;
|
||||
use std::error::Error as StdError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DatabaseError {
|
||||
ConnectionError(String),
|
||||
QueryError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for DatabaseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DatabaseError::ConnectionError(msg) => write!(f, "Connection error: {}", msg),
|
||||
DatabaseError::QueryError(msg) => write!(f, "Query error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for DatabaseError {}
|
||||
|
||||
// ...omitted code...
|
||||
|
||||
impl Database {
|
||||
fn new(url: &str) -> Result<Self, DatabaseError> {
|
||||
let connection = Connection::connect(url)
|
||||
.map_err(|e| DatabaseError::ConnectionError(e.to_string()))?;
|
||||
Ok(Database { connection })
|
||||
}
|
||||
|
||||
async fn query(&self, sql: &str) -> Result<Vec<Row>, DatabaseError> {
|
||||
self.connection.query(sql, &[])
|
||||
.await
|
||||
.map_err(|e| DatabaseError::QueryError(e.to_string()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>prepend_child</operation>
|
||||
<symbol>#imports</symbol>
|
||||
<description>Import necessary error handling modules</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>insert_sibling_before</operation>
|
||||
<symbol>pub(crate) struct Database</symbol>
|
||||
<description>Define custom DatabaseError enum</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Database fn new</symbol>
|
||||
<description>Update new method to use DatabaseError</description>
|
||||
</edit>
|
||||
|
||||
<edit>
|
||||
<path>src/database.rs</path>
|
||||
<operation>update</operation>
|
||||
<symbol>impl Database async fn query</symbol>
|
||||
<description>Update query method to use DatabaseError</description>
|
||||
</edit>
|
||||
</step>
|
||||
</message>
|
||||
|
||||
</example>
|
||||
|
||||
You should think step by step. When possible, produce smaller, coherent logical steps as opposed to one big step that combines lots of heterogeneous edits.
|
||||
|
|
|
@ -472,7 +472,7 @@ pub enum XmlTagKind {
|
|||
Step,
|
||||
Edit,
|
||||
Path,
|
||||
Symbol,
|
||||
Search,
|
||||
Within,
|
||||
Operation,
|
||||
Description,
|
||||
|
@ -1518,7 +1518,7 @@ impl Context {
|
|||
|
||||
if tag.kind == XmlTagKind::Edit && tag.is_open_tag {
|
||||
let mut path = None;
|
||||
let mut symbol = None;
|
||||
let mut search = None;
|
||||
let mut operation = None;
|
||||
let mut description = None;
|
||||
|
||||
|
@ -1527,7 +1527,7 @@ impl Context {
|
|||
edits.push(WorkflowStepEdit::new(
|
||||
path,
|
||||
operation,
|
||||
symbol,
|
||||
search,
|
||||
description,
|
||||
));
|
||||
break;
|
||||
|
@ -1536,7 +1536,7 @@ impl Context {
|
|||
if tag.is_open_tag
|
||||
&& [
|
||||
XmlTagKind::Path,
|
||||
XmlTagKind::Symbol,
|
||||
XmlTagKind::Search,
|
||||
XmlTagKind::Operation,
|
||||
XmlTagKind::Description,
|
||||
]
|
||||
|
@ -1555,8 +1555,8 @@ impl Context {
|
|||
match kind {
|
||||
XmlTagKind::Path => path = Some(content),
|
||||
XmlTagKind::Operation => operation = Some(content),
|
||||
XmlTagKind::Symbol => {
|
||||
symbol = Some(content).filter(|s| !s.is_empty())
|
||||
XmlTagKind::Search => {
|
||||
search = Some(content).filter(|s| !s.is_empty())
|
||||
}
|
||||
XmlTagKind::Description => {
|
||||
description =
|
||||
|
|
|
@ -609,8 +609,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>«
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>fn one</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>fn one</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
@ -634,8 +634,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>fn one</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>fn one</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>»
|
||||
|
@ -643,8 +643,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
also,",
|
||||
&[&[WorkflowStepEdit {
|
||||
path: "src/lib.rs".into(),
|
||||
kind: WorkflowStepEditKind::InsertSiblingAfter {
|
||||
symbol: "fn one".into(),
|
||||
kind: WorkflowStepEditKind::InsertAfter {
|
||||
search: "fn one".into(),
|
||||
description: "add a `two` function".into(),
|
||||
},
|
||||
}]],
|
||||
|
@ -668,8 +668,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>«fn zero»</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>«fn zero»</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
@ -693,8 +693,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>fn zero</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>fn zero</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>»
|
||||
|
@ -702,8 +702,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
also,",
|
||||
&[&[WorkflowStepEdit {
|
||||
path: "src/lib.rs".into(),
|
||||
kind: WorkflowStepEditKind::InsertSiblingAfter {
|
||||
symbol: "fn zero".into(),
|
||||
kind: WorkflowStepEditKind::InsertAfter {
|
||||
search: "fn zero".into(),
|
||||
description: "add a `two` function".into(),
|
||||
},
|
||||
}]],
|
||||
|
@ -731,8 +731,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>fn zero</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>fn zero</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>
|
||||
|
@ -762,8 +762,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>fn zero</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>fn zero</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>»
|
||||
|
@ -771,8 +771,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
also,",
|
||||
&[&[WorkflowStepEdit {
|
||||
path: "src/lib.rs".into(),
|
||||
kind: WorkflowStepEditKind::InsertSiblingAfter {
|
||||
symbol: "fn zero".into(),
|
||||
kind: WorkflowStepEditKind::InsertAfter {
|
||||
search: "fn zero".into(),
|
||||
description: "add a `two` function".into(),
|
||||
},
|
||||
}]],
|
||||
|
@ -808,8 +808,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
|
||||
<edit>
|
||||
<path>src/lib.rs</path>
|
||||
<operation>insert_sibling_after</operation>
|
||||
<symbol>fn zero</symbol>
|
||||
<operation>insert_after</operation>
|
||||
<search>fn zero</search>
|
||||
<description>add a `two` function</description>
|
||||
</edit>
|
||||
</step>»
|
||||
|
@ -817,8 +817,8 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) {
|
|||
also,",
|
||||
&[&[WorkflowStepEdit {
|
||||
path: "src/lib.rs".into(),
|
||||
kind: WorkflowStepEditKind::InsertSiblingAfter {
|
||||
symbol: "fn zero".into(),
|
||||
kind: WorkflowStepEditKind::InsertAfter {
|
||||
search: "fn zero".into(),
|
||||
description: "add a `two` function".into(),
|
||||
},
|
||||
}]],
|
||||
|
|
|
@ -4,16 +4,14 @@ use collections::HashMap;
|
|||
use editor::Editor;
|
||||
use gpui::AsyncAppContext;
|
||||
use gpui::{Model, Task, UpdateGlobal as _, View, WeakView, WindowContext};
|
||||
use language::{Anchor, Buffer, BufferSnapshot, Outline, OutlineItem, ParseStatus, SymbolPath};
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use project::{Project, ProjectPath};
|
||||
use rope::Point;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{ops::Range, path::Path, sync::Arc};
|
||||
use text::Bias;
|
||||
use workspace::Workspace;
|
||||
|
||||
const IMPORTS_SYMBOL: &str = "#imports";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WorkflowStep {
|
||||
pub range: Range<language::Anchor>,
|
||||
|
@ -45,35 +43,21 @@ pub struct WorkflowSuggestionGroup {
|
|||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum WorkflowSuggestion {
|
||||
Update {
|
||||
symbol_path: SymbolPath,
|
||||
range: Range<language::Anchor>,
|
||||
description: String,
|
||||
},
|
||||
CreateFile {
|
||||
description: String,
|
||||
},
|
||||
InsertSiblingBefore {
|
||||
symbol_path: SymbolPath,
|
||||
InsertBefore {
|
||||
position: language::Anchor,
|
||||
description: String,
|
||||
},
|
||||
InsertSiblingAfter {
|
||||
symbol_path: SymbolPath,
|
||||
position: language::Anchor,
|
||||
description: String,
|
||||
},
|
||||
PrependChild {
|
||||
symbol_path: Option<SymbolPath>,
|
||||
position: language::Anchor,
|
||||
description: String,
|
||||
},
|
||||
AppendChild {
|
||||
symbol_path: Option<SymbolPath>,
|
||||
InsertAfter {
|
||||
position: language::Anchor,
|
||||
description: String,
|
||||
},
|
||||
Delete {
|
||||
symbol_path: SymbolPath,
|
||||
range: Range<language::Anchor>,
|
||||
},
|
||||
}
|
||||
|
@ -83,10 +67,9 @@ impl WorkflowSuggestion {
|
|||
match self {
|
||||
Self::Update { range, .. } => range.clone(),
|
||||
Self::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
|
||||
Self::InsertSiblingBefore { position, .. }
|
||||
| Self::InsertSiblingAfter { position, .. }
|
||||
| Self::PrependChild { position, .. }
|
||||
| Self::AppendChild { position, .. } => *position..*position,
|
||||
Self::InsertBefore { position, .. } | Self::InsertAfter { position, .. } => {
|
||||
*position..*position
|
||||
}
|
||||
Self::Delete { range, .. } => range.clone(),
|
||||
}
|
||||
}
|
||||
|
@ -95,10 +78,8 @@ impl WorkflowSuggestion {
|
|||
match self {
|
||||
Self::Update { description, .. }
|
||||
| Self::CreateFile { description }
|
||||
| Self::InsertSiblingBefore { description, .. }
|
||||
| Self::InsertSiblingAfter { description, .. }
|
||||
| Self::PrependChild { description, .. }
|
||||
| Self::AppendChild { description, .. } => Some(description),
|
||||
| Self::InsertBefore { description, .. }
|
||||
| Self::InsertAfter { description, .. } => Some(description),
|
||||
Self::Delete { .. } => None,
|
||||
}
|
||||
}
|
||||
|
@ -107,10 +88,8 @@ impl WorkflowSuggestion {
|
|||
match self {
|
||||
Self::Update { description, .. }
|
||||
| Self::CreateFile { description }
|
||||
| Self::InsertSiblingBefore { description, .. }
|
||||
| Self::InsertSiblingAfter { description, .. }
|
||||
| Self::PrependChild { description, .. }
|
||||
| Self::AppendChild { description, .. } => Some(description),
|
||||
| Self::InsertBefore { description, .. }
|
||||
| Self::InsertAfter { description, .. } => Some(description),
|
||||
Self::Delete { .. } => None,
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +140,7 @@ impl WorkflowSuggestion {
|
|||
initial_prompt = description.clone();
|
||||
suggestion_range = editor::Anchor::min()..editor::Anchor::min();
|
||||
}
|
||||
Self::InsertSiblingBefore {
|
||||
Self::InsertBefore {
|
||||
position,
|
||||
description,
|
||||
..
|
||||
|
@ -178,7 +157,7 @@ impl WorkflowSuggestion {
|
|||
line_start..line_start
|
||||
});
|
||||
}
|
||||
Self::InsertSiblingAfter {
|
||||
Self::InsertAfter {
|
||||
position,
|
||||
description,
|
||||
..
|
||||
|
@ -195,40 +174,6 @@ impl WorkflowSuggestion {
|
|||
line_start..line_start
|
||||
});
|
||||
}
|
||||
Self::PrependChild {
|
||||
position,
|
||||
description,
|
||||
..
|
||||
} => {
|
||||
let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
|
||||
initial_prompt = description.clone();
|
||||
suggestion_range = buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction(cx);
|
||||
let line_start = buffer.insert_empty_line(position, false, true, cx);
|
||||
initial_transaction_id = buffer.end_transaction(cx);
|
||||
buffer.refresh_preview(cx);
|
||||
|
||||
let line_start = buffer.read(cx).anchor_before(line_start);
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
Self::AppendChild {
|
||||
position,
|
||||
description,
|
||||
..
|
||||
} => {
|
||||
let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
|
||||
initial_prompt = description.clone();
|
||||
suggestion_range = buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction(cx);
|
||||
let line_start = buffer.insert_empty_line(position, true, false, cx);
|
||||
initial_transaction_id = buffer.end_transaction(cx);
|
||||
buffer.refresh_preview(cx);
|
||||
|
||||
let line_start = buffer.read(cx).anchor_before(line_start);
|
||||
line_start..line_start
|
||||
});
|
||||
}
|
||||
Self::Delete { range, .. } => {
|
||||
initial_prompt = "Delete".to_string();
|
||||
suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
|
||||
|
@ -254,7 +199,7 @@ impl WorkflowStepEdit {
|
|||
pub fn new(
|
||||
path: Option<String>,
|
||||
operation: Option<String>,
|
||||
symbol: Option<String>,
|
||||
search: Option<String>,
|
||||
description: Option<String>,
|
||||
) -> Result<Self> {
|
||||
let path = path.ok_or_else(|| anyhow!("missing path"))?;
|
||||
|
@ -262,27 +207,19 @@ impl WorkflowStepEdit {
|
|||
|
||||
let kind = match operation.as_str() {
|
||||
"update" => WorkflowStepEditKind::Update {
|
||||
symbol: symbol.ok_or_else(|| anyhow!("missing symbol"))?,
|
||||
search: search.ok_or_else(|| anyhow!("missing search"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
},
|
||||
"insert_sibling_before" => WorkflowStepEditKind::InsertSiblingBefore {
|
||||
symbol: symbol.ok_or_else(|| anyhow!("missing symbol"))?,
|
||||
"insert_before" => WorkflowStepEditKind::InsertBefore {
|
||||
search: search.ok_or_else(|| anyhow!("missing search"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
},
|
||||
"insert_sibling_after" => WorkflowStepEditKind::InsertSiblingAfter {
|
||||
symbol: symbol.ok_or_else(|| anyhow!("missing symbol"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
},
|
||||
"prepend_child" => WorkflowStepEditKind::PrependChild {
|
||||
symbol,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
},
|
||||
"append_child" => WorkflowStepEditKind::AppendChild {
|
||||
symbol,
|
||||
"insert_after" => WorkflowStepEditKind::InsertAfter {
|
||||
search: search.ok_or_else(|| anyhow!("missing search"))?,
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
},
|
||||
"delete" => WorkflowStepEditKind::Delete {
|
||||
symbol: symbol.ok_or_else(|| anyhow!("missing symbol"))?,
|
||||
search: search.ok_or_else(|| anyhow!("missing search"))?,
|
||||
},
|
||||
"create" => WorkflowStepEditKind::Create {
|
||||
description: description.ok_or_else(|| anyhow!("missing description"))?,
|
||||
|
@ -323,200 +260,143 @@ impl WorkflowStepEdit {
|
|||
})??
|
||||
.await?;
|
||||
|
||||
let mut parse_status = buffer.read_with(&cx, |buffer, _cx| buffer.parse_status())?;
|
||||
while *parse_status.borrow() != ParseStatus::Idle {
|
||||
parse_status.changed().await?;
|
||||
}
|
||||
|
||||
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||
let outline = snapshot.outline(None).context("no outline for buffer")?;
|
||||
|
||||
let suggestion = match kind {
|
||||
WorkflowStepEditKind::Update {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
let (symbol_path, symbol) = Self::resolve_symbol(&snapshot, &outline, &symbol)?;
|
||||
let start = symbol
|
||||
.annotation_range
|
||||
.map_or(symbol.range.start, |range| range.start);
|
||||
let start = Point::new(start.row, 0);
|
||||
let end = Point::new(
|
||||
symbol.range.end.row,
|
||||
snapshot.line_len(symbol.range.end.row),
|
||||
);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
WorkflowSuggestion::Update {
|
||||
range,
|
||||
description,
|
||||
symbol_path,
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::Create { description } => {
|
||||
WorkflowSuggestion::CreateFile { description }
|
||||
}
|
||||
WorkflowStepEditKind::InsertSiblingBefore {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
let (symbol_path, symbol) = Self::resolve_symbol(&snapshot, &outline, &symbol)?;
|
||||
let position = snapshot.anchor_before(
|
||||
symbol
|
||||
.annotation_range
|
||||
.map_or(symbol.range.start, |annotation_range| {
|
||||
annotation_range.start
|
||||
}),
|
||||
);
|
||||
WorkflowSuggestion::InsertSiblingBefore {
|
||||
position,
|
||||
description,
|
||||
symbol_path,
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::InsertSiblingAfter {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
let (symbol_path, symbol) = Self::resolve_symbol(&snapshot, &outline, &symbol)?;
|
||||
let position = snapshot.anchor_after(symbol.range.end);
|
||||
WorkflowSuggestion::InsertSiblingAfter {
|
||||
position,
|
||||
description,
|
||||
symbol_path,
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::PrependChild {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
if let Some(symbol) = symbol {
|
||||
let (symbol_path, symbol) = Self::resolve_symbol(&snapshot, &outline, &symbol)?;
|
||||
|
||||
let position = snapshot.anchor_after(
|
||||
symbol
|
||||
.body_range
|
||||
.map_or(symbol.range.start, |body_range| body_range.start),
|
||||
);
|
||||
WorkflowSuggestion::PrependChild {
|
||||
position,
|
||||
let suggestion = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
match kind {
|
||||
WorkflowStepEditKind::Update {
|
||||
search,
|
||||
description,
|
||||
symbol_path: Some(symbol_path),
|
||||
} => {
|
||||
let range = Self::resolve_location(&snapshot, &search);
|
||||
WorkflowSuggestion::Update { range, description }
|
||||
}
|
||||
} else {
|
||||
WorkflowSuggestion::PrependChild {
|
||||
position: language::Anchor::MIN,
|
||||
WorkflowStepEditKind::Create { description } => {
|
||||
WorkflowSuggestion::CreateFile { description }
|
||||
}
|
||||
WorkflowStepEditKind::InsertBefore {
|
||||
search,
|
||||
description,
|
||||
symbol_path: None,
|
||||
} => {
|
||||
let range = Self::resolve_location(&snapshot, &search);
|
||||
WorkflowSuggestion::InsertBefore {
|
||||
position: range.start,
|
||||
description,
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::InsertAfter {
|
||||
search,
|
||||
description,
|
||||
} => {
|
||||
let range = Self::resolve_location(&snapshot, &search);
|
||||
WorkflowSuggestion::InsertAfter {
|
||||
position: range.end,
|
||||
description,
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::Delete { search } => {
|
||||
let range = Self::resolve_location(&snapshot, &search);
|
||||
WorkflowSuggestion::Delete { range }
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::AppendChild {
|
||||
symbol,
|
||||
description,
|
||||
} => {
|
||||
if let Some(symbol) = symbol {
|
||||
let (symbol_path, symbol) = Self::resolve_symbol(&snapshot, &outline, &symbol)?;
|
||||
|
||||
let position = snapshot.anchor_before(
|
||||
symbol
|
||||
.body_range
|
||||
.map_or(symbol.range.end, |body_range| body_range.end),
|
||||
);
|
||||
WorkflowSuggestion::AppendChild {
|
||||
position,
|
||||
description,
|
||||
symbol_path: Some(symbol_path),
|
||||
}
|
||||
} else {
|
||||
WorkflowSuggestion::PrependChild {
|
||||
position: language::Anchor::MAX,
|
||||
description,
|
||||
symbol_path: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkflowStepEditKind::Delete { symbol } => {
|
||||
let (symbol_path, symbol) = Self::resolve_symbol(&snapshot, &outline, &symbol)?;
|
||||
let start = symbol
|
||||
.annotation_range
|
||||
.map_or(symbol.range.start, |range| range.start);
|
||||
let start = Point::new(start.row, 0);
|
||||
let end = Point::new(
|
||||
symbol.range.end.row,
|
||||
snapshot.line_len(symbol.range.end.row),
|
||||
);
|
||||
let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
|
||||
WorkflowSuggestion::Delete { range, symbol_path }
|
||||
}
|
||||
};
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok((buffer, suggestion))
|
||||
}
|
||||
|
||||
fn resolve_symbol(
|
||||
snapshot: &BufferSnapshot,
|
||||
outline: &Outline<Anchor>,
|
||||
symbol: &str,
|
||||
) -> Result<(SymbolPath, OutlineItem<Point>)> {
|
||||
if symbol == IMPORTS_SYMBOL {
|
||||
let target_row = find_first_non_comment_line(snapshot);
|
||||
Ok((
|
||||
SymbolPath(IMPORTS_SYMBOL.to_string()),
|
||||
OutlineItem {
|
||||
range: Point::new(target_row, 0)..Point::new(target_row + 1, 0),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
} else {
|
||||
let (symbol_path, symbol) = outline
|
||||
.find_most_similar(symbol)
|
||||
.with_context(|| format!("symbol not found: {symbol}"))?;
|
||||
Ok((symbol_path, symbol.to_point(snapshot)))
|
||||
fn resolve_location(buffer: &text::BufferSnapshot, search_query: &str) -> Range<text::Anchor> {
|
||||
const INSERTION_SCORE: f64 = -1.0;
|
||||
const DELETION_SCORE: f64 = -1.0;
|
||||
const REPLACEMENT_SCORE: f64 = -1.0;
|
||||
const EQUALITY_SCORE: f64 = 5.0;
|
||||
|
||||
struct Matrix {
|
||||
cols: usize,
|
||||
data: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Matrix {
|
||||
fn new(rows: usize, cols: usize) -> Self {
|
||||
Matrix {
|
||||
cols,
|
||||
data: vec![0.0; rows * cols],
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, row: usize, col: usize) -> f64 {
|
||||
self.data[row * self.cols + col]
|
||||
}
|
||||
|
||||
fn set(&mut self, row: usize, col: usize, value: f64) {
|
||||
self.data[row * self.cols + col] = value;
|
||||
}
|
||||
}
|
||||
|
||||
let buffer_len = buffer.len();
|
||||
let query_len = search_query.len();
|
||||
let mut matrix = Matrix::new(query_len + 1, buffer_len + 1);
|
||||
|
||||
for (i, query_byte) in search_query.bytes().enumerate() {
|
||||
for (j, buffer_byte) in buffer.bytes_in_range(0..buffer.len()).flatten().enumerate() {
|
||||
let match_score = if query_byte == *buffer_byte {
|
||||
EQUALITY_SCORE
|
||||
} else {
|
||||
REPLACEMENT_SCORE
|
||||
};
|
||||
let up = matrix.get(i + 1, j) + DELETION_SCORE;
|
||||
let left = matrix.get(i, j + 1) + INSERTION_SCORE;
|
||||
let diagonal = matrix.get(i, j) + match_score;
|
||||
let score = up.max(left.max(diagonal)).max(0.);
|
||||
matrix.set(i + 1, j + 1, score);
|
||||
}
|
||||
}
|
||||
|
||||
// Traceback to find the best match
|
||||
let mut best_buffer_end = buffer_len;
|
||||
let mut best_score = 0.0;
|
||||
for col in 1..=buffer_len {
|
||||
let score = matrix.get(query_len, col);
|
||||
if score > best_score {
|
||||
best_score = score;
|
||||
best_buffer_end = col;
|
||||
}
|
||||
}
|
||||
|
||||
let mut query_ix = query_len;
|
||||
let mut buffer_ix = best_buffer_end;
|
||||
while query_ix > 0 && buffer_ix > 0 {
|
||||
let current = matrix.get(query_ix, buffer_ix);
|
||||
let up = matrix.get(query_ix - 1, buffer_ix);
|
||||
let left = matrix.get(query_ix, buffer_ix - 1);
|
||||
if current == left + INSERTION_SCORE {
|
||||
buffer_ix -= 1;
|
||||
} else if current == up + DELETION_SCORE {
|
||||
query_ix -= 1;
|
||||
} else {
|
||||
query_ix -= 1;
|
||||
buffer_ix -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut start = buffer.offset_to_point(buffer.clip_offset(buffer_ix, Bias::Left));
|
||||
start.column = 0;
|
||||
let mut end = buffer.offset_to_point(buffer.clip_offset(best_buffer_end, Bias::Right));
|
||||
end.column = buffer.line_len(end.row);
|
||||
|
||||
buffer.anchor_after(start)..buffer.anchor_before(end)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_first_non_comment_line(snapshot: &BufferSnapshot) -> u32 {
|
||||
let Some(language) = snapshot.language() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let scope = language.default_scope();
|
||||
let comment_prefixes = scope.line_comment_prefixes();
|
||||
|
||||
let mut chunks = snapshot.as_rope().chunks();
|
||||
let mut target_row = 0;
|
||||
loop {
|
||||
let starts_with_comment = chunks
|
||||
.peek()
|
||||
.map(|chunk| {
|
||||
comment_prefixes
|
||||
.iter()
|
||||
.any(|s| chunk.starts_with(s.as_ref().trim_end()))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if !starts_with_comment {
|
||||
break;
|
||||
}
|
||||
|
||||
target_row += 1;
|
||||
if !chunks.next_line() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
target_row
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "operation")]
|
||||
pub enum WorkflowStepEditKind {
|
||||
/// Rewrites the specified symbol entirely based on the given description.
|
||||
/// This operation completely replaces the existing symbol with new content.
|
||||
/// Rewrites the specified text entirely based on the given description.
|
||||
/// This operation completely replaces the given text.
|
||||
Update {
|
||||
/// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
|
||||
/// The path should uniquely identify the symbol within the containing file.
|
||||
symbol: String,
|
||||
/// A string in the source text to apply the update to.
|
||||
search: String,
|
||||
/// A brief description of the transformation to apply to the symbol.
|
||||
description: String,
|
||||
},
|
||||
|
@ -526,47 +406,101 @@ pub enum WorkflowStepEditKind {
|
|||
/// A brief description of the file to be created.
|
||||
description: String,
|
||||
},
|
||||
/// Inserts a new symbol based on the given description before the specified symbol.
|
||||
/// This operation adds new content immediately preceding an existing symbol.
|
||||
InsertSiblingBefore {
|
||||
/// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
|
||||
/// The new content will be inserted immediately before this symbol.
|
||||
symbol: String,
|
||||
/// A brief description of the new symbol to be inserted.
|
||||
/// Inserts text before the specified text in the source file.
|
||||
InsertBefore {
|
||||
/// A string in the source text to insert text before.
|
||||
search: String,
|
||||
/// A brief description of how the new text should be generated.
|
||||
description: String,
|
||||
},
|
||||
/// Inserts a new symbol based on the given description after the specified symbol.
|
||||
/// This operation adds new content immediately following an existing symbol.
|
||||
InsertSiblingAfter {
|
||||
/// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
|
||||
/// The new content will be inserted immediately after this symbol.
|
||||
symbol: String,
|
||||
/// A brief description of the new symbol to be inserted.
|
||||
description: String,
|
||||
},
|
||||
/// Inserts a new symbol as a child of the specified symbol at the start.
|
||||
/// This operation adds new content as the first child of an existing symbol (or file if no symbol is provided).
|
||||
PrependChild {
|
||||
/// An optional fully-qualified reference to the symbol after the code you want to insert, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
|
||||
/// If provided, the new content will be inserted as the first child of this symbol.
|
||||
/// If not provided, the new content will be inserted at the top of the file.
|
||||
symbol: Option<String>,
|
||||
/// A brief description of the new symbol to be inserted.
|
||||
description: String,
|
||||
},
|
||||
/// Inserts a new symbol as a child of the specified symbol at the end.
|
||||
/// This operation adds new content as the last child of an existing symbol (or file if no symbol is provided).
|
||||
AppendChild {
|
||||
/// An optional fully-qualified reference to the symbol before the code you want to insert, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
|
||||
/// If provided, the new content will be inserted as the last child of this symbol.
|
||||
/// If not provided, the new content will be applied at the bottom of the file.
|
||||
symbol: Option<String>,
|
||||
/// A brief description of the new symbol to be inserted.
|
||||
/// Inserts text after the specified text in the source file.
|
||||
InsertAfter {
|
||||
/// A string in the source text to insert text after.
|
||||
search: String,
|
||||
/// A brief description of how the new text should be generated.
|
||||
description: String,
|
||||
},
|
||||
/// Deletes the specified symbol from the containing file.
|
||||
Delete {
|
||||
/// An fully-qualified reference to the symbol to be deleted, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
|
||||
symbol: String,
|
||||
/// A string in the source text to delete.
|
||||
search: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{AppContext, Context};
|
||||
use text::{OffsetRangeExt, Point};
|
||||
|
||||
#[gpui::test]
|
||||
fn test_resolve_location(cx: &mut AppContext) {
|
||||
{
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::local(
|
||||
concat!(
|
||||
" Lorem\n",
|
||||
" ipsum\n",
|
||||
" dolor sit amet\n",
|
||||
" consecteur",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
WorkflowStepEdit::resolve_location(&snapshot, "ipsum\ndolor").to_point(&snapshot),
|
||||
Point::new(1, 0)..Point::new(2, 18)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::local(
|
||||
concat!(
|
||||
"fn foo1(a: usize) -> usize {\n",
|
||||
" 42\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"fn foo2(b: usize) -> usize {\n",
|
||||
" 42\n",
|
||||
"}\n",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
WorkflowStepEdit::resolve_location(&snapshot, "fn foo1(b: usize) {\n42\n}")
|
||||
.to_point(&snapshot),
|
||||
Point::new(0, 0)..Point::new(2, 1)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let buffer = cx.new_model(|cx| {
|
||||
Buffer::local(
|
||||
concat!(
|
||||
"fn main() {\n",
|
||||
" Foo\n",
|
||||
" .bar()\n",
|
||||
" .baz()\n",
|
||||
" .qux()\n",
|
||||
"}\n",
|
||||
"\n",
|
||||
"fn foo2(b: usize) -> usize {\n",
|
||||
" 42\n",
|
||||
"}\n",
|
||||
),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_eq!(
|
||||
WorkflowStepEdit::resolve_location(&snapshot, "Foo.bar.baz.qux()")
|
||||
.to_point(&snapshot),
|
||||
Point::new(1, 0)..Point::new(4, 14)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue