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}