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                        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}