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}