attachments.rs

  1pub mod active_file;
  2
  3use anyhow::{anyhow, Result};
  4use assistant_tooling::{LanguageModelAttachment, ProjectContext, ToolOutput};
  5use editor::Editor;
  6use gpui::{Render, Task, View, WeakModel, WeakView};
  7use language::Buffer;
  8use project::ProjectPath;
  9use ui::{prelude::*, ButtonLike, Tooltip, WindowContext};
 10use util::maybe;
 11use workspace::Workspace;
 12
 13pub struct ActiveEditorAttachment {
 14    buffer: WeakModel<Buffer>,
 15    path: Option<ProjectPath>,
 16}
 17
 18pub struct FileAttachmentView {
 19    output: Result<ActiveEditorAttachment>,
 20}
 21
 22impl Render for FileAttachmentView {
 23    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 24        match &self.output {
 25            Ok(attachment) => {
 26                let filename: SharedString = attachment
 27                    .path
 28                    .as_ref()
 29                    .and_then(|p| p.path.file_name()?.to_str())
 30                    .unwrap_or("Untitled")
 31                    .to_string()
 32                    .into();
 33
 34                // todo!(): make the button link to the actual file to open
 35                ButtonLike::new("file-attachment")
 36                    .child(
 37                        h_flex()
 38                            .gap_1()
 39                            .bg(cx.theme().colors().editor_background)
 40                            .rounded_md()
 41                            .child(ui::Icon::new(IconName::File))
 42                            .child(filename.clone()),
 43                    )
 44                    .tooltip({
 45                        move |cx| Tooltip::with_meta("File Attached", None, filename.clone(), cx)
 46                    })
 47                    .into_any_element()
 48            }
 49            Err(err) => div().child(err.to_string()).into_any_element(),
 50        }
 51    }
 52}
 53
 54impl ToolOutput for FileAttachmentView {
 55    fn generate(&self, project: &mut ProjectContext, cx: &mut WindowContext) -> String {
 56        if let Ok(result) = &self.output {
 57            if let Some(path) = &result.path {
 58                project.add_file(path.clone());
 59                return format!("current file: {}", path.path.display());
 60            } else if let Some(buffer) = result.buffer.upgrade() {
 61                return format!("current untitled buffer text:\n{}", buffer.read(cx).text());
 62            }
 63        }
 64        String::new()
 65    }
 66}
 67
 68pub struct ActiveEditorAttachmentTool {
 69    workspace: WeakView<Workspace>,
 70}
 71
 72impl ActiveEditorAttachmentTool {
 73    pub fn new(workspace: WeakView<Workspace>, _cx: &mut WindowContext) -> Self {
 74        Self { workspace }
 75    }
 76}
 77
 78impl LanguageModelAttachment for ActiveEditorAttachmentTool {
 79    type Output = ActiveEditorAttachment;
 80    type View = FileAttachmentView;
 81
 82    fn run(&self, cx: &mut WindowContext) -> Task<Result<ActiveEditorAttachment>> {
 83        Task::ready(maybe!({
 84            let active_buffer = self
 85                .workspace
 86                .update(cx, |workspace, cx| {
 87                    workspace
 88                        .active_item(cx)
 89                        .and_then(|item| Some(item.act_as::<Editor>(cx)?.read(cx).buffer().clone()))
 90                })?
 91                .ok_or_else(|| anyhow!("no active buffer"))?;
 92
 93            let buffer = active_buffer.read(cx);
 94
 95            if let Some(buffer) = buffer.as_singleton() {
 96                let path =
 97                    project::File::from_dyn(buffer.read(cx).file()).map(|file| ProjectPath {
 98                        worktree_id: file.worktree_id(cx),
 99                        path: file.path.clone(),
100                    });
101                return Ok(ActiveEditorAttachment {
102                    buffer: buffer.downgrade(),
103                    path,
104                });
105            } else {
106                Err(anyhow!("no active buffer"))
107            }
108        }))
109    }
110
111    fn view(output: Result<Self::Output>, cx: &mut WindowContext) -> View<Self::View> {
112        cx.new_view(|_cx| FileAttachmentView { output })
113    }
114}