active_file.rs

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