lsp_status.rs

  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}