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}