diagnostics.rs

  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}