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