lsp_status.rs

  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}