lsp_status.rs

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