Add cursor position section

Agus Zubiaga and Max Brunsfeld created

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs |   2 
crates/zeta2/src/related_excerpts.rs                |   2 
crates/zeta_cli/src/example.rs                      | 240 ++++++--------
3 files changed, 112 insertions(+), 132 deletions(-)

Detailed changes

crates/cloud_zeta2_prompt/src/cloud_zeta2_prompt.rs 🔗

@@ -212,7 +212,7 @@ pub fn write_codeblock<'a>(
     include_line_numbers: bool,
     output: &'a mut String,
 ) {
-    writeln!(output, "`````path={}", path.display()).unwrap();
+    writeln!(output, "`````{}", path.display()).unwrap();
     write_excerpts(
         excerpts,
         sorted_insertions,

crates/zeta2/src/related_excerpts.rs 🔗

@@ -64,7 +64,7 @@ const SEARCH_PROMPT: &str = indoc! {r#"
 
     ## Current cursor context
 
-    `````path={current_file_path}
+    `````{current_file_path}
     {cursor_excerpt}
     `````
 

crates/zeta_cli/src/example.rs 🔗

@@ -1,19 +1,22 @@
 use std::{
     fmt::{self, Display},
-    fs::File,
-    io::{Read, Write},
+    io::Write,
+    mem,
     os::unix::ffi::OsStrExt,
     path::{Path, PathBuf},
 };
 
 use anyhow::Result;
 use clap::ValueEnum;
+use pulldown_cmark::CowStr;
 use serde::{Deserialize, Serialize};
 
+const CURSOR_POSITION_HEADING: &str = "Cursor Position";
 const EDIT_HISTORY_HEADING: &str = "Edit History";
-const EXPECTED_HUNKS_HEADING: &str = "Expected Hunks";
 const EXPECTED_PATCH_HEADING: &str = "Expected Patch";
 const EXPECTED_EXCERPTS_HEADING: &str = "Expected Excerpts";
+const REPOSITORY_URL_FIELD: &str = "repository_url";
+const REVISION_FIELD: &str = "revision";
 
 pub struct NamedExample {
     name: String,
@@ -24,8 +27,9 @@ pub struct NamedExample {
 pub struct Example {
     repository_url: String,
     commit: String,
+    cursor_path: PathBuf,
+    cursor_position: String,
     edit_history: Vec<String>,
-    expected_hunks: Vec<String>,
     expected_patch: String,
     expected_excerpts: Vec<ExpectedExcerpt>,
 }
@@ -46,31 +50,19 @@ pub enum ExampleFormat {
 impl NamedExample {
     pub fn load(path: impl AsRef<Path>) -> Result<Self> {
         let path = path.as_ref();
-        let mut file = File::open(path)?;
+        let content = std::fs::read_to_string(path)?;
         let ext = path.extension();
 
         match ext.map(|s| s.as_bytes()) {
-            Some(b"json") => {
-                let mut content = Vec::new();
-                file.read_to_end(&mut content)?;
-                Ok(Self {
-                    name: path.file_name().unwrap_or_default().display().to_string(),
-                    example: serde_json::from_slice(&content)?,
-                })
-            }
-            Some(b"toml") => {
-                let mut content = String::new();
-                file.read_to_string(&mut content)?;
-                Ok(Self {
-                    name: path.file_name().unwrap_or_default().display().to_string(),
-                    example: toml::from_str(&content)?,
-                })
-            }
-            Some(b"md") => {
-                let mut content = String::new();
-                file.read_to_string(&mut content)?;
-                Self::parse_md(&content)
-            }
+            Some(b"json") => Ok(Self {
+                name: path.file_name().unwrap_or_default().display().to_string(),
+                example: serde_json::from_str(&content)?,
+            }),
+            Some(b"toml") => Ok(Self {
+                name: path.file_name().unwrap_or_default().display().to_string(),
+                example: toml::from_str(&content)?,
+            }),
+            Some(b"md") => Self::parse_md(&content),
             Some(_) => {
                 anyhow::bail!("Unrecognized example extension: {}", ext.unwrap().display());
             }
@@ -87,119 +79,104 @@ impl NamedExample {
 
         let parser = Parser::new(input);
 
-        let mut name = String::new();
-        let mut repository_url = String::new();
-        let mut commit = String::new();
-        let mut edit_history = Vec::new();
-        let mut expected_hunks = Vec::new();
-        let mut expected_patch = String::new();
-        let mut expected_excerpts = Vec::new();
+        let mut named = NamedExample {
+            name: String::new(),
+            example: Example {
+                repository_url: String::new(),
+                commit: String::new(),
+                cursor_path: PathBuf::new(),
+                cursor_position: String::new(),
+                edit_history: Vec::new(),
+                expected_patch: String::new(),
+                expected_excerpts: Vec::new(),
+            },
+        };
 
-        let mut current_heading_level: Option<HeadingLevel> = None;
-        let mut current_heading_text = String::new();
+        let mut text = String::new();
         let mut current_section = String::new();
-        let mut in_code_block = false;
-        let mut current_code_block = String::new();
-        let mut current_code_info = String::new();
+        let mut block_info: CowStr = "".into();
 
         for event in parser {
             match event {
-                Event::Start(Tag::Heading { level, .. }) => {
-                    current_heading_level = Some(level);
-                    current_heading_text.clear();
-                }
-                Event::End(TagEnd::Heading(_)) => {
-                    let heading_text = current_heading_text.trim();
-                    if let Some(HeadingLevel::H1) = current_heading_level {
-                        if !name.is_empty() {
-                            anyhow::bail!(
-                                "Found multiple H1 headings. There should only be one with the name of the example."
-                            );
+                Event::Text(line) => {
+                    text.push_str(&line);
+
+                    if !named.name.is_empty()
+                        && current_section.is_empty()
+                        // in h1 section
+                        && let Some((field, value)) = line.split_once('=')
+                    {
+                        match field {
+                            REPOSITORY_URL_FIELD => {
+                                named.example.repository_url = value.to_string();
+                            }
+                            REVISION_FIELD => {
+                                named.example.commit = value.to_string();
+                            }
+                            _ => {
+                                eprintln!("Warning: Unrecognized field `{field}`");
+                            }
                         }
-                        name = heading_text.to_string();
-                    } else if let Some(HeadingLevel::H2) = current_heading_level {
-                        current_section = heading_text.to_string();
                     }
-                    current_heading_level = None;
                 }
-                Event::Start(Tag::CodeBlock(kind)) => {
-                    in_code_block = true;
-                    current_code_block.clear();
-                    current_code_info = match kind {
-                        CodeBlockKind::Fenced(info) => info.to_string(),
-                        CodeBlockKind::Indented => String::new(),
-                    };
+                Event::End(TagEnd::Heading(HeadingLevel::H1)) => {
+                    if !named.name.is_empty() {
+                        anyhow::bail!(
+                            "Found multiple H1 headings. There should only be one with the name of the example."
+                        );
+                    }
+                    named.name = mem::take(&mut text);
                 }
-                Event::End(TagEnd::CodeBlock) => {
-                    in_code_block = false;
-
-                    match current_section.as_str() {
-                        EDIT_HISTORY_HEADING => {
-                            edit_history.push(current_code_block.clone());
-                        }
-                        EXPECTED_HUNKS_HEADING => {
-                            expected_hunks.push(current_code_block.clone());
-                        }
-                        EXPECTED_PATCH_HEADING => {
-                            expected_patch = current_code_block.clone();
+                Event::End(TagEnd::Heading(HeadingLevel::H2)) => {
+                    current_section = mem::take(&mut text);
+                }
+                Event::End(TagEnd::Heading(level)) => {
+                    anyhow::bail!("Unexpected heading level: {level}");
+                }
+                Event::Start(Tag::CodeBlock(kind)) => {
+                    match kind {
+                        CodeBlockKind::Fenced(info) => {
+                            block_info = info;
                         }
-                        EXPECTED_EXCERPTS_HEADING => {
-                            if let Some(path_start) = current_code_info.find("path=") {
-                                let path_str = &current_code_info[path_start + 5..];
-                                let path = PathBuf::from(path_str.trim());
-                                expected_excerpts.push(ExpectedExcerpt {
-                                    path,
-                                    text: current_code_block.clone(),
-                                });
-                            }
+                        CodeBlockKind::Indented => {
+                            anyhow::bail!("Unexpected indented codeblock");
                         }
-                        _ => {}
-                    }
+                    };
                 }
-                Event::Text(text) => {
-                    if let Some(_) = current_heading_level {
-                        current_heading_text.push_str(&text);
-                    } else if in_code_block {
-                        current_code_block.push_str(&text);
-                    } else if current_section.is_empty()
-                        && let Some(eq_pos) = text.find('=')
-                    {
-                        let key = text[..eq_pos].trim();
-                        let value = text[eq_pos + 1..].trim();
-                        match key {
-                            "repository_url" => repository_url = value.to_string(),
-                            "commit" => commit = value.to_string(),
-                            _ => {}
-                        }
+                Event::Start(_) => {
+                    text.clear();
+                    block_info = "".into();
+                }
+                Event::End(TagEnd::CodeBlock) => {
+                    if current_section.eq_ignore_ascii_case(EDIT_HISTORY_HEADING) {
+                        named.example.edit_history.push(mem::take(&mut text));
+                    } else if current_section.eq_ignore_ascii_case(CURSOR_POSITION_HEADING) {
+                        let path = PathBuf::from(block_info.trim());
+                        named.example.cursor_path = path;
+                        named.example.cursor_position = mem::take(&mut text);
+                    } else if current_section.eq_ignore_ascii_case(EXPECTED_PATCH_HEADING) {
+                        named.example.expected_patch = mem::take(&mut text);
+                    } else if current_section.eq_ignore_ascii_case(EXPECTED_EXCERPTS_HEADING) {
+                        let path = PathBuf::from(block_info.trim());
+                        named.example.expected_excerpts.push(ExpectedExcerpt {
+                            path,
+                            text: mem::take(&mut text),
+                        });
+                    } else {
+                        eprintln!("Warning: Unrecognized section `{current_section:?}`")
                     }
                 }
                 _ => {}
             }
         }
 
-        if name.is_empty() {
-            anyhow::bail!("Missing required H1 heading for example name");
+        if named.example.cursor_path.as_path() == Path::new("")
+            || named.example.cursor_position.is_empty()
+        {
+            anyhow::bail!("Missing cursor position codeblock");
         }
 
-        if repository_url.is_empty() {
-            anyhow::bail!("Missing required field: repository_url");
-        }
-
-        if commit.is_empty() {
-            anyhow::bail!("Missing required field: commit");
-        }
-
-        Ok(Self {
-            name,
-            example: Example {
-                repository_url,
-                commit,
-                edit_history,
-                expected_hunks,
-                expected_patch,
-                expected_excerpts,
-            },
-        })
+        Ok(named)
     }
 
     pub fn write(&self, format: ExampleFormat, mut out: impl Write) -> Result<()> {
@@ -216,8 +193,19 @@ impl NamedExample {
 impl Display for NamedExample {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "# {}\n\n", self.name)?;
-        write!(f, "repository_url = {}\n", self.example.repository_url)?;
-        write!(f, "commit = {}\n\n", self.example.commit)?;
+        write!(
+            f,
+            "{REPOSITORY_URL_FIELD} = {}\n",
+            self.example.repository_url
+        )?;
+        write!(f, "{REVISION_FIELD} = {}\n\n", self.example.commit)?;
+
+        write!(
+            f,
+            "## {CURSOR_POSITION_HEADING}\n\n`````{}\n{}`````\n",
+            self.example.cursor_path.display(),
+            self.example.cursor_position
+        )?;
         write!(f, "## {EDIT_HISTORY_HEADING}\n\n")?;
 
         if !self.example.edit_history.is_empty() {
@@ -228,14 +216,6 @@ impl Display for NamedExample {
             write!(f, "`````\n")?;
         }
 
-        if !self.example.expected_hunks.is_empty() {
-            write!(f, "\n## {EXPECTED_HUNKS_HEADING}\n\n`````diff\n")?;
-            for hunk in &self.example.expected_hunks {
-                write!(f, "{hunk}")?;
-            }
-            write!(f, "`````\n")?;
-        }
-
         if !self.example.expected_patch.is_empty() {
             write!(
                 f,
@@ -250,7 +230,7 @@ impl Display for NamedExample {
             for excerpt in &self.example.expected_excerpts {
                 write!(
                     f,
-                    "`````{}path={}\n{}`````\n\n",
+                    "`````{}{}\n{}`````\n\n",
                     excerpt
                         .path
                         .extension()