diagnostics.rs

  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}