lsp_status.rs

  1use crate::{ItemHandle, 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 settings::Settings;
 11use smallvec::SmallVec;
 12use std::cmp::Reverse;
 13use std::fmt::Write;
 14use std::sync::Arc;
 15
 16action!(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 theme = &cx.global::<Settings>().theme;
121
122        let mut pending_work = self.pending_language_server_work(cx);
123        if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
124            let mut message = lang_server_name.to_string();
125
126            message.push_str(": ");
127            if let Some(progress_message) = progress.message.as_ref() {
128                message.push_str(progress_message);
129            } else {
130                message.push_str(progress_token);
131            }
132
133            if let Some(percentage) = progress.percentage {
134                write!(&mut message, " ({}%)", percentage).unwrap();
135            }
136
137            let additional_work_count = pending_work.count();
138            if additional_work_count > 0 {
139                write!(&mut message, " + {} more", additional_work_count).unwrap();
140            }
141
142            Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed()
143        } else if !self.downloading.is_empty() {
144            Label::new(
145                format!(
146                    "Downloading {} language server{}...",
147                    self.downloading.join(", "),
148                    if self.downloading.len() > 1 { "s" } else { "" }
149                ),
150                theme.workspace.status_bar.lsp_message.clone(),
151            )
152            .boxed()
153        } else if !self.checking_for_update.is_empty() {
154            Label::new(
155                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                theme.workspace.status_bar.lsp_message.clone(),
165            )
166            .boxed()
167        } else if !self.failed.is_empty() {
168            drop(pending_work);
169            MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
170                let theme = &cx.global::<Settings>().theme;
171                Label::new(
172                    format!(
173                        "Failed to download {} language server{}. Click to dismiss.",
174                        self.failed.join(", "),
175                        if self.failed.len() > 1 { "s" } else { "" }
176                    ),
177                    theme.workspace.status_bar.lsp_message.clone(),
178                )
179                .boxed()
180            })
181            .with_cursor_style(CursorStyle::PointingHand)
182            .on_click(|cx| cx.dispatch_action(DismissErrorMessage))
183            .boxed()
184        } else {
185            Empty::new().boxed()
186        }
187    }
188}
189
190impl StatusItemView for LspStatus {
191    fn set_active_pane_item(&mut self, _: Option<&dyn ItemHandle>, _: &mut ViewContext<Self>) {}
192}