zeta: Always include current file's outline in telemetry (#22257)

Thorsten Ball and Agus created

This doesn't include the outline in the prompt yet, but it does send it
up via telemetry so we can use it to see whether it would have improved
generated output.

Release Notes:

- N/A

Co-authored-by: Agus <agus@zed.dev>

Change summary

crates/zeta/src/zeta.rs | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)

Detailed changes

crates/zeta/src/zeta.rs 🔗

@@ -73,6 +73,7 @@ pub struct InlineCompletion {
     excerpt_range: Range<usize>,
     edits: Arc<[(Range<Anchor>, String)]>,
     snapshot: BufferSnapshot,
+    input_outline: Arc<str>,
     input_events: Arc<str>,
     input_excerpt: Arc<str>,
     output_excerpt: Arc<str>,
@@ -306,6 +307,7 @@ impl Zeta {
                 input_events.push_str(&event.to_prompt());
             }
             let input_excerpt = prompt_for_excerpt(&snapshot, &excerpt_range, offset);
+            let input_outline = prompt_for_outline(&snapshot);
 
             log::debug!("Events:\n{}\nExcerpt:\n{}", input_events, input_excerpt);
 
@@ -324,6 +326,7 @@ impl Zeta {
                 &snapshot,
                 excerpt_range,
                 path,
+                input_outline,
                 input_events,
                 input_excerpt,
                 request_sent_at,
@@ -560,6 +563,7 @@ and then another
         snapshot: &BufferSnapshot,
         excerpt_range: Range<usize>,
         path: Arc<Path>,
+        input_outline: String,
         input_events: String,
         input_excerpt: String,
         request_sent_at: Instant,
@@ -594,6 +598,7 @@ and then another
                 excerpt_range,
                 edits: edits.into(),
                 snapshot: snapshot.clone(),
+                input_outline: input_outline.into(),
                 input_events: input_events.into(),
                 input_excerpt: input_excerpt.into(),
                 output_excerpt: output_excerpt.into(),
@@ -693,6 +698,7 @@ and then another
             rating,
             input_events = completion.input_events,
             input_excerpt = completion.input_excerpt,
+            input_outline = completion.input_outline,
             output_excerpt = completion.output_excerpt,
             feedback
         );
@@ -741,6 +747,34 @@ fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b:
         .sum()
 }
 
+fn prompt_for_outline(snapshot: &BufferSnapshot) -> String {
+    let mut input_outline = String::new();
+
+    writeln!(
+        input_outline,
+        "```{}",
+        snapshot
+            .file()
+            .map_or(Cow::Borrowed("untitled"), |file| file
+                .path()
+                .to_string_lossy())
+    )
+    .unwrap();
+
+    if let Some(outline) = snapshot.outline(None) {
+        let guess_size = outline.items.len() * 15;
+        input_outline.reserve(guess_size);
+        for item in outline.items.iter() {
+            let spacing = " ".repeat(item.depth);
+            writeln!(input_outline, "{}{}", spacing, item.text).unwrap();
+        }
+    }
+
+    writeln!(input_outline, "```").unwrap();
+
+    input_outline
+}
+
 fn prompt_for_excerpt(
     snapshot: &BufferSnapshot,
     excerpt_range: &Range<usize>,
@@ -1130,6 +1164,7 @@ mod tests {
             snapshot: buffer.read(cx).snapshot(),
             id: InlineCompletionId::new(),
             excerpt_range: 0..0,
+            input_outline: "".into(),
             input_events: "".into(),
             input_excerpt: "".into(),
             output_excerpt: "".into(),