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