recent_buffers.rs

  1use crate::{assistant_panel::Conversation, LanguageModelRequestMessage, Role};
  2use gpui::{ModelContext, Subscription, Task, WeakModel};
  3use language::{Buffer, BufferSnapshot, Rope};
  4use std::{fmt::Write, path::PathBuf, time::Duration};
  5
  6use super::ContextUpdated;
  7
  8pub struct RecentBuffersContext {
  9    pub enabled: bool,
 10    pub buffers: Vec<RecentBuffer>,
 11    pub snapshot: RecentBuffersSnapshot,
 12    pub pending_message: Option<Task<()>>,
 13}
 14
 15pub struct RecentBuffer {
 16    pub buffer: WeakModel<Buffer>,
 17    pub _subscription: Subscription,
 18}
 19
 20impl Default for RecentBuffersContext {
 21    fn default() -> Self {
 22        Self {
 23            enabled: true,
 24            buffers: Vec::new(),
 25            snapshot: RecentBuffersSnapshot::default(),
 26            pending_message: None,
 27        }
 28    }
 29}
 30
 31impl RecentBuffersContext {
 32    pub fn update(&mut self, cx: &mut ModelContext<Conversation>) -> ContextUpdated {
 33        let source_buffers = self
 34            .buffers
 35            .iter()
 36            .filter_map(|recent| {
 37                let (full_path, snapshot) = recent
 38                    .buffer
 39                    .read_with(cx, |buffer, cx| {
 40                        (
 41                            buffer.file().map(|file| file.full_path(cx)),
 42                            buffer.snapshot(),
 43                        )
 44                    })
 45                    .ok()?;
 46                Some(SourceBufferSnapshot {
 47                    full_path,
 48                    model: recent.buffer.clone(),
 49                    snapshot,
 50                })
 51            })
 52            .collect::<Vec<_>>();
 53
 54        if !self.enabled || source_buffers.is_empty() {
 55            self.snapshot.message = Default::default();
 56            self.snapshot.source_buffers.clear();
 57            self.pending_message = None;
 58            cx.notify();
 59            ContextUpdated::Disabled
 60        } else {
 61            self.pending_message = Some(cx.spawn(|this, mut cx| async move {
 62                const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
 63                cx.background_executor().timer(DEBOUNCE_TIMEOUT).await;
 64
 65                let message = if source_buffers.is_empty() {
 66                    Rope::new()
 67                } else {
 68                    cx.background_executor()
 69                        .spawn({
 70                            let source_buffers = source_buffers.clone();
 71                            async move { message_for_recent_buffers(source_buffers) }
 72                        })
 73                        .await
 74                };
 75                this.update(&mut cx, |this, cx| {
 76                    this.ambient_context.recent_buffers.snapshot.source_buffers = source_buffers;
 77                    this.ambient_context.recent_buffers.snapshot.message = message;
 78                    this.count_remaining_tokens(cx);
 79                    cx.notify();
 80                })
 81                .ok();
 82            }));
 83
 84            ContextUpdated::Updating
 85        }
 86    }
 87
 88    /// Returns the [`RecentBuffersContext`] as a message to the language model.
 89    pub fn to_message(&self) -> Option<LanguageModelRequestMessage> {
 90        self.enabled.then(|| LanguageModelRequestMessage {
 91            role: Role::System,
 92            content: self.snapshot.message.to_string(),
 93        })
 94    }
 95}
 96
 97#[derive(Clone, Default, Debug)]
 98pub struct RecentBuffersSnapshot {
 99    pub message: Rope,
100    pub source_buffers: Vec<SourceBufferSnapshot>,
101}
102
103#[derive(Clone)]
104pub struct SourceBufferSnapshot {
105    pub full_path: Option<PathBuf>,
106    pub model: WeakModel<Buffer>,
107    pub snapshot: BufferSnapshot,
108}
109
110impl std::fmt::Debug for SourceBufferSnapshot {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("SourceBufferSnapshot")
113            .field("full_path", &self.full_path)
114            .field("model (entity id)", &self.model.entity_id())
115            .field("snapshot (text)", &self.snapshot.text())
116            .finish()
117    }
118}
119
120fn message_for_recent_buffers(buffers: Vec<SourceBufferSnapshot>) -> Rope {
121    let mut message = String::new();
122    writeln!(
123        message,
124        "The following is a list of recent buffers that the user has opened."
125    )
126    .unwrap();
127
128    for buffer in buffers {
129        if let Some(path) = buffer.full_path {
130            writeln!(message, "```{}", path.display()).unwrap();
131        } else {
132            writeln!(message, "```untitled").unwrap();
133        }
134
135        for chunk in buffer.snapshot.chunks(0..buffer.snapshot.len(), false) {
136            message.push_str(chunk.text);
137        }
138        if !message.ends_with('\n') {
139            message.push('\n');
140        }
141        message.push_str("```\n");
142    }
143
144    Rope::from(message.as_str())
145}