diagnostics.rs

  1use std::sync::Arc;
  2
  3use collections::HashMap;
  4use editor::{Editor, ExcerptProperties, MultiBuffer};
  5use gpui::{
  6    action, elements::*, keymap::Binding, AppContext, Entity, ModelContext, ModelHandle,
  7    MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
  8};
  9use language::Point;
 10use postage::watch;
 11use project::Project;
 12use workspace::Workspace;
 13
 14action!(Toggle);
 15
 16pub fn init(cx: &mut MutableAppContext) {
 17    cx.add_bindings([Binding::new("alt-shift-D", Toggle, None)]);
 18    cx.add_action(ProjectDiagnosticsEditor::toggle);
 19}
 20
 21struct ProjectDiagnostics {
 22    excerpts: ModelHandle<MultiBuffer>,
 23    project: ModelHandle<Project>,
 24}
 25
 26struct ProjectDiagnosticsEditor {
 27    editor: ViewHandle<Editor>,
 28}
 29
 30impl ProjectDiagnostics {
 31    fn new(project: ModelHandle<Project>, cx: &mut ModelContext<Self>) -> Self {
 32        let project_paths = project
 33            .read(cx)
 34            .diagnostic_summaries(cx)
 35            .map(|e| e.0)
 36            .collect::<Vec<_>>();
 37
 38        cx.spawn(|this, mut cx| {
 39            let project = project.clone();
 40            async move {
 41                for project_path in project_paths {
 42                    let buffer = project
 43                        .update(&mut cx, |project, cx| project.open_buffer(project_path, cx))
 44                        .await?;
 45                    let snapshot = buffer.read_with(&cx, |b, _| b.snapshot());
 46
 47                    let mut grouped_diagnostics = HashMap::default();
 48                    for entry in snapshot.all_diagnostics() {
 49                        let mut group = grouped_diagnostics
 50                            .entry(entry.diagnostic.group_id)
 51                            .or_insert((Point::zero(), Vec::new()));
 52                        if entry.diagnostic.is_primary {
 53                            group.0 = entry.range.start;
 54                        }
 55                        group.1.push(entry);
 56                    }
 57                    let mut sorted_diagnostic_groups =
 58                        grouped_diagnostics.into_values().collect::<Vec<_>>();
 59                    sorted_diagnostic_groups.sort_by_key(|group| group.0);
 60
 61                    for diagnostic in snapshot.all_diagnostics::<Point>() {
 62                        this.update(&mut cx, |this, cx| {
 63                            this.excerpts.update(cx, |excerpts, cx| {
 64                                excerpts.push_excerpt(
 65                                    ExcerptProperties {
 66                                        buffer: &buffer,
 67                                        range: diagnostic.range,
 68                                        header_height: 1,
 69                                        render_header: Some(Arc::new({
 70                                            let message = diagnostic.diagnostic.message.clone();
 71                                            move |_| {
 72                                                Text::new(message.clone(), Default::default())
 73                                                    .boxed()
 74                                            }
 75                                        })),
 76                                    },
 77                                    cx,
 78                                );
 79                                cx.notify();
 80                            });
 81                        })
 82                    }
 83                }
 84                Result::Ok::<_, anyhow::Error>(())
 85            }
 86        })
 87        .detach();
 88
 89        Self {
 90            excerpts: cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id(cx))),
 91            project,
 92        }
 93    }
 94}
 95
 96impl Entity for ProjectDiagnostics {
 97    type Event = ();
 98}
 99
100impl Entity for ProjectDiagnosticsEditor {
101    type Event = ();
102}
103
104impl View for ProjectDiagnosticsEditor {
105    fn ui_name() -> &'static str {
106        "ProjectDiagnosticsEditor"
107    }
108
109    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
110        ChildView::new(self.editor.id()).boxed()
111    }
112}
113
114impl ProjectDiagnosticsEditor {
115    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
116        let diagnostics =
117            cx.add_model(|cx| ProjectDiagnostics::new(workspace.project().clone(), cx));
118        workspace.add_item(diagnostics, cx);
119    }
120}
121
122impl workspace::Item for ProjectDiagnostics {
123    type View = ProjectDiagnosticsEditor;
124
125    fn build_view(
126        handle: ModelHandle<Self>,
127        settings: watch::Receiver<workspace::Settings>,
128        cx: &mut ViewContext<Self::View>,
129    ) -> Self::View {
130        let excerpts = handle.read(cx).excerpts.clone();
131        let editor = cx.add_view(|cx| {
132            Editor::for_buffer(
133                excerpts.clone(),
134                editor::settings_builder(excerpts.downgrade(), settings),
135                cx,
136            )
137        });
138        ProjectDiagnosticsEditor { editor }
139    }
140
141    fn project_path(&self) -> Option<project::ProjectPath> {
142        None
143    }
144}
145
146impl workspace::ItemView for ProjectDiagnosticsEditor {
147    fn title(&self, _: &AppContext) -> String {
148        "Project Diagnostics".to_string()
149    }
150
151    fn project_path(&self, cx: &AppContext) -> Option<project::ProjectPath> {
152        None
153    }
154
155    fn save(
156        &mut self,
157        cx: &mut ViewContext<Self>,
158    ) -> anyhow::Result<gpui::Task<anyhow::Result<()>>> {
159        todo!()
160    }
161
162    fn save_as(
163        &mut self,
164        worktree: ModelHandle<project::Worktree>,
165        path: &std::path::Path,
166        cx: &mut ViewContext<Self>,
167    ) -> gpui::Task<anyhow::Result<()>> {
168        todo!()
169    }
170}