diagnostics.rs

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