1use crate::{ItemHandle, Settings, StatusItemView};
2use futures::StreamExt;
3use gpui::AppContext;
4use gpui::{
5 action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext,
6 RenderContext, View, ViewContext,
7};
8use language::{LanguageRegistry, LanguageServerBinaryStatus};
9use project::{LanguageServerProgress, Project};
10use smallvec::SmallVec;
11use std::cmp::Reverse;
12use std::fmt::Write;
13use std::sync::Arc;
14
15action!(DismissErrorMessage);
16
17pub struct LspStatus {
18 checking_for_update: Vec<String>,
19 downloading: Vec<String>,
20 failed: Vec<String>,
21 project: ModelHandle<Project>,
22}
23
24pub fn init(cx: &mut MutableAppContext) {
25 cx.add_action(LspStatus::dismiss_error_message);
26}
27
28impl LspStatus {
29 pub fn new(
30 project: &ModelHandle<Project>,
31 languages: Arc<LanguageRegistry>,
32 cx: &mut ViewContext<Self>,
33 ) -> Self {
34 let mut status_events = languages.language_server_binary_statuses();
35 cx.spawn_weak(|this, mut cx| async move {
36 while let Some((language, event)) = status_events.next().await {
37 if let Some(this) = this.upgrade(&cx) {
38 this.update(&mut cx, |this, cx| {
39 for vector in [
40 &mut this.checking_for_update,
41 &mut this.downloading,
42 &mut this.failed,
43 ] {
44 vector.retain(|name| name != language.name().as_ref());
45 }
46
47 match event {
48 LanguageServerBinaryStatus::CheckingForUpdate => {
49 this.checking_for_update.push(language.name().to_string());
50 }
51 LanguageServerBinaryStatus::Downloading => {
52 this.downloading.push(language.name().to_string());
53 }
54 LanguageServerBinaryStatus::Failed => {
55 this.failed.push(language.name().to_string());
56 }
57 LanguageServerBinaryStatus::Downloaded
58 | LanguageServerBinaryStatus::Cached => {}
59 }
60
61 cx.notify();
62 });
63 } else {
64 break;
65 }
66 }
67 })
68 .detach();
69 cx.observe(project, |_, _, cx| cx.notify()).detach();
70
71 Self {
72 checking_for_update: Default::default(),
73 downloading: Default::default(),
74 failed: Default::default(),
75 project: project.clone(),
76 }
77 }
78
79 fn dismiss_error_message(&mut self, _: &DismissErrorMessage, cx: &mut ViewContext<Self>) {
80 self.failed.clear();
81 cx.notify();
82 }
83
84 fn pending_language_server_work<'a>(
85 &self,
86 cx: &'a AppContext,
87 ) -> impl Iterator<Item = (&'a str, &'a str, &'a LanguageServerProgress)> {
88 self.project
89 .read(cx)
90 .language_server_statuses()
91 .rev()
92 .filter_map(|status| {
93 if status.pending_work.is_empty() {
94 None
95 } else {
96 let mut pending_work = status
97 .pending_work
98 .iter()
99 .map(|(token, progress)| (status.name.as_str(), token.as_str(), progress))
100 .collect::<SmallVec<[_; 4]>>();
101 pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at));
102 Some(pending_work)
103 }
104 })
105 .flatten()
106 }
107}
108
109impl Entity for LspStatus {
110 type Event = ();
111}
112
113impl View for LspStatus {
114 fn ui_name() -> &'static str {
115 "LspStatus"
116 }
117
118 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
119 let theme = &cx.global::<Settings>().theme;
120
121 let mut pending_work = self.pending_language_server_work(cx);
122 if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
123 let mut message = lang_server_name.to_string();
124
125 message.push_str(": ");
126 if let Some(progress_message) = progress.message.as_ref() {
127 message.push_str(progress_message);
128 } else {
129 message.push_str(progress_token);
130 }
131
132 if let Some(percentage) = progress.percentage {
133 write!(&mut message, " ({}%)", percentage).unwrap();
134 }
135
136 let additional_work_count = pending_work.count();
137 if additional_work_count > 0 {
138 write!(&mut message, " + {} more", additional_work_count).unwrap();
139 }
140
141 Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed()
142 } else if !self.downloading.is_empty() {
143 Label::new(
144 format!(
145 "Downloading {} language server{}...",
146 self.downloading.join(", "),
147 if self.downloading.len() > 1 { "s" } else { "" }
148 ),
149 theme.workspace.status_bar.lsp_message.clone(),
150 )
151 .boxed()
152 } else if !self.checking_for_update.is_empty() {
153 Label::new(
154 format!(
155 "Checking for updates to {} language server{}...",
156 self.checking_for_update.join(", "),
157 if self.checking_for_update.len() > 1 {
158 "s"
159 } else {
160 ""
161 }
162 ),
163 theme.workspace.status_bar.lsp_message.clone(),
164 )
165 .boxed()
166 } else if !self.failed.is_empty() {
167 drop(pending_work);
168 MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
169 let theme = &cx.global::<Settings>().theme;
170 Label::new(
171 format!(
172 "Failed to download {} language server{}. Click to dismiss.",
173 self.failed.join(", "),
174 if self.failed.len() > 1 { "s" } else { "" }
175 ),
176 theme.workspace.status_bar.lsp_message.clone(),
177 )
178 .boxed()
179 })
180 .with_cursor_style(CursorStyle::PointingHand)
181 .on_click(|cx| cx.dispatch_action(DismissErrorMessage))
182 .boxed()
183 } else {
184 Empty::new().boxed()
185 }
186 }
187}
188
189impl StatusItemView for LspStatus {
190 fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
191}