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 this.excerpts.update(cx, |excerpts, excerpts_cx| {
96 for group in snapshot.diagnostic_groups::<Point>() {
97 let excerpt_start = cmp::min(
98 group.primary.range.start.row,
99 group
100 .supporting
101 .first()
102 .map_or(u32::MAX, |entry| entry.range.start.row),
103 );
104 let excerpt_end = cmp::max(
105 group.primary.range.end.row,
106 group
107 .supporting
108 .last()
109 .map_or(0, |entry| entry.range.end.row),
110 );
111
112 let primary_diagnostic = group.primary.diagnostic;
113 let excerpt_id = excerpts.push_excerpt(
114 ExcerptProperties {
115 buffer: &buffer,
116 range: Point::new(excerpt_start, 0)
117 ..Point::new(
118 excerpt_end,
119 snapshot.line_len(excerpt_end),
120 ),
121 header_height: primary_diagnostic
122 .message
123 .matches('\n')
124 .count()
125 as u8
126 + 1,
127 render_header: Some(Arc::new({
128 let settings = settings.clone();
129
130 move |_| {
131 let editor_style = &settings.borrow().theme.editor;
132 let mut text_style = editor_style.text.clone();
133 text_style.color = diagnostic_style(
134 primary_diagnostic.severity,
135 true,
136 &editor_style,
137 )
138 .text;
139
140 Text::new(
141 primary_diagnostic.message.clone(),
142 text_style,
143 )
144 .boxed()
145 }
146 })),
147 },
148 excerpts_cx,
149 );
150
151 for entry in group.supporting {
152 let buffer_anchor = snapshot.anchor_before(entry.range.start);
153 blocks.push(BlockProperties {
154 position: Anchor::new(excerpt_id.clone(), buffer_anchor),
155 height: entry.diagnostic.message.matches('\n').count()
156 as u8
157 + 1,
158 render: diagnostic_block_renderer(
159 entry.diagnostic,
160 true,
161 build_settings.clone(),
162 ),
163 disposition: BlockDisposition::Below,
164 });
165 }
166 }
167 });
168 this.editor.update(cx, |editor, cx| {
169 editor.insert_blocks(blocks, cx);
170 });
171 })
172 }
173 Result::Ok::<_, anyhow::Error>(())
174 }
175 })
176 .detach();
177
178 ProjectDiagnosticsEditor { editor, excerpts }
179 }
180
181 fn project_path(&self) -> Option<project::ProjectPath> {
182 None
183 }
184}
185
186impl workspace::ItemView for ProjectDiagnosticsEditor {
187 fn title(&self, _: &AppContext) -> String {
188 "Project Diagnostics".to_string()
189 }
190
191 fn project_path(&self, _: &AppContext) -> Option<project::ProjectPath> {
192 None
193 }
194
195 fn save(
196 &mut self,
197 _: &mut ViewContext<Self>,
198 ) -> anyhow::Result<gpui::Task<anyhow::Result<()>>> {
199 todo!()
200 }
201
202 fn save_as(
203 &mut self,
204 _: ModelHandle<project::Worktree>,
205 _: &std::path::Path,
206 _: &mut ViewContext<Self>,
207 ) -> gpui::Task<anyhow::Result<()>> {
208 todo!()
209 }
210}