1use std::{cmp, sync::Arc};
2
3use editor::{
4 diagnostic_block_renderer, diagnostic_style,
5 display_map::{BlockDisposition, BlockProperties},
6 Editor, ExcerptProperties, MultiBuffer,
7};
8use gpui::{
9 action, elements::*, keymap::Binding, AppContext, Entity, ModelHandle, MutableAppContext,
10 RenderContext, View, ViewContext, ViewHandle,
11};
12use language::Point;
13use postage::watch;
14use project::Project;
15use workspace::Workspace;
16
17action!(Toggle);
18
19pub fn init(cx: &mut MutableAppContext) {
20 cx.add_bindings([Binding::new("alt-shift-D", Toggle, None)]);
21 cx.add_action(ProjectDiagnosticsEditor::toggle);
22}
23
24struct ProjectDiagnostics {
25 project: ModelHandle<Project>,
26}
27
28struct ProjectDiagnosticsEditor {
29 editor: ViewHandle<Editor>,
30 excerpts: ModelHandle<MultiBuffer>,
31}
32
33impl ProjectDiagnostics {
34 fn new(project: ModelHandle<Project>) -> Self {
35 Self { project }
36 }
37}
38
39impl Entity for ProjectDiagnostics {
40 type Event = ();
41}
42
43impl Entity for ProjectDiagnosticsEditor {
44 type Event = ();
45}
46
47impl View for ProjectDiagnosticsEditor {
48 fn ui_name() -> &'static str {
49 "ProjectDiagnosticsEditor"
50 }
51
52 fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
53 ChildView::new(self.editor.id()).boxed()
54 }
55
56 fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
57 cx.focus(&self.editor);
58 }
59}
60
61impl ProjectDiagnosticsEditor {
62 fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
63 let diagnostics = cx.add_model(|_| ProjectDiagnostics::new(workspace.project().clone()));
64 workspace.add_item(diagnostics, cx);
65 }
66}
67
68impl workspace::Item for ProjectDiagnostics {
69 type View = ProjectDiagnosticsEditor;
70
71 fn build_view(
72 handle: ModelHandle<Self>,
73 settings: watch::Receiver<workspace::Settings>,
74 cx: &mut ViewContext<Self::View>,
75 ) -> Self::View {
76 let project = handle.read(cx).project.clone();
77 let excerpts = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx)));
78 let build_settings = editor::settings_builder(excerpts.downgrade(), settings.clone());
79 let editor =
80 cx.add_view(|cx| Editor::for_buffer(excerpts.clone(), build_settings.clone(), cx));
81
82 let project_paths = project
83 .read(cx)
84 .diagnostic_summaries(cx)
85 .map(|e| e.0)
86 .collect::<Vec<_>>();
87
88 cx.spawn(|this, mut cx| {
89 let project = project.clone();
90 async move {
91 for project_path in project_paths {
92 let buffer = project
93 .update(&mut cx, |project, cx| project.open_buffer(project_path, cx))
94 .await?;
95 let snapshot = buffer.read_with(&cx, |b, _| b.snapshot());
96
97 this.update(&mut cx, |this, cx| {
98 let mut blocks = Vec::new();
99 let excerpts_snapshot =
100 this.excerpts.update(cx, |excerpts, excerpts_cx| {
101 for group in snapshot.diagnostic_groups::<Point>() {
102 let excerpt_start = cmp::min(
103 group.primary.range.start.row,
104 group
105 .supporting
106 .first()
107 .map_or(u32::MAX, |entry| entry.range.start.row),
108 );
109 let excerpt_end = cmp::max(
110 group.primary.range.end.row,
111 group
112 .supporting
113 .last()
114 .map_or(0, |entry| entry.range.end.row),
115 );
116
117 let primary_diagnostic = group.primary.diagnostic;
118 let excerpt_id = excerpts.push_excerpt(
119 ExcerptProperties {
120 buffer: &buffer,
121 range: Point::new(excerpt_start, 0)
122 ..Point::new(
123 excerpt_end,
124 snapshot.line_len(excerpt_end),
125 ),
126 header_height: primary_diagnostic
127 .message
128 .matches('\n')
129 .count()
130 as u8
131 + 1,
132 render_header: Some(Arc::new({
133 let settings = settings.clone();
134
135 move |_| {
136 let editor_style =
137 &settings.borrow().theme.editor;
138 let mut text_style = editor_style.text.clone();
139 text_style.color = diagnostic_style(
140 primary_diagnostic.severity,
141 true,
142 &editor_style,
143 )
144 .text;
145
146 Text::new(
147 primary_diagnostic.message.clone(),
148 text_style,
149 )
150 .boxed()
151 }
152 })),
153 },
154 excerpts_cx,
155 );
156
157 for entry in group.supporting {
158 let buffer_anchor =
159 snapshot.anchor_before(entry.range.start);
160 blocks.push(BlockProperties {
161 position: (excerpt_id.clone(), buffer_anchor),
162 height: entry.diagnostic.message.matches('\n').count()
163 as u8
164 + 1,
165 render: diagnostic_block_renderer(
166 entry.diagnostic,
167 true,
168 build_settings.clone(),
169 ),
170 disposition: BlockDisposition::Below,
171 });
172 }
173 }
174
175 excerpts.snapshot(excerpts_cx)
176 });
177
178 this.editor.update(cx, |editor, cx| {
179 editor.insert_blocks(
180 blocks.into_iter().map(|block| {
181 let (excerpt_id, text_anchor) = block.position;
182 BlockProperties {
183 position: excerpts_snapshot
184 .anchor_in_excerpt(excerpt_id, text_anchor),
185 height: block.height,
186 render: block.render,
187 disposition: block.disposition,
188 }
189 }),
190 cx,
191 );
192 });
193 })
194 }
195 Result::Ok::<_, anyhow::Error>(())
196 }
197 })
198 .detach();
199
200 ProjectDiagnosticsEditor { editor, excerpts }
201 }
202
203 fn project_path(&self) -> Option<project::ProjectPath> {
204 None
205 }
206}
207
208impl workspace::ItemView for ProjectDiagnosticsEditor {
209 fn title(&self, _: &AppContext) -> String {
210 "Project Diagnostics".to_string()
211 }
212
213 fn project_path(&self, _: &AppContext) -> Option<project::ProjectPath> {
214 None
215 }
216
217 fn save(
218 &mut self,
219 _: &mut ViewContext<Self>,
220 ) -> anyhow::Result<gpui::Task<anyhow::Result<()>>> {
221 todo!()
222 }
223
224 fn save_as(
225 &mut self,
226 _: ModelHandle<project::Worktree>,
227 _: &std::path::Path,
228 _: &mut ViewContext<Self>,
229 ) -> gpui::Task<anyhow::Result<()>> {
230 todo!()
231 }
232}