781 lines · 31.6 KB · Rust
RawDownload
use auto_update::DismissMessage;use editor::Editor;use extension_host::{ExtensionOperation, ExtensionStore};use futures::StreamExt;use gpui::{    App, Context, Entity, EventEmitter, InteractiveElement as _, ParentElement as _, Render,    SharedString, Styled, Window, actions,};use language::{    BinaryStatus, LanguageRegistry, LanguageServerId, LanguageServerName,    LanguageServerStatusUpdate, ServerHealth,};use project::{    LanguageServerProgress, LspStoreEvent, ProgressToken, Project, ProjectEnvironmentEvent,    git_store::{GitStoreEvent, Repository},};use smallvec::SmallVec;use std::{    cmp::Reverse,    collections::HashSet,    fmt::Write,    sync::Arc,    time::{Duration, Instant},};use ui::{ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};use util::truncate_and_trailoff;use workspace::{StatusItemView, Workspace, item::ItemHandle}; const GIT_OPERATION_DELAY: Duration = Duration::from_millis(0); actions!(    activity_indicator,    [        /// Displays error messages from language servers in the status bar.        ShowErrorMessage    ]); pub enum Event {    ShowStatus {        server_name: LanguageServerName,        status: SharedString,    },} pub struct ActivityIndicator {    statuses: Vec<ServerStatus>,    project: Entity<Project>,    context_menu_handle: PopoverMenuHandle<ContextMenu>,    fs_jobs: Vec<fs::JobInfo>,} #[derive(Debug)]struct ServerStatus {    name: LanguageServerName,    status: LanguageServerStatusUpdate,} struct PendingWork<'a> {    language_server_id: LanguageServerId,    progress_token: &'a ProgressToken,    progress: &'a LanguageServerProgress,} enum ActivityIcon {    LoadingSpinner,    Icon(IconName),} struct Content {    icon: ActivityIcon,    message: String,    on_click:        Option<Arc<dyn Fn(&mut ActivityIndicator, &mut Window, &mut Context<ActivityIndicator>)>>,    tooltip_message: Option<String>,} impl ActivityIndicator {    pub fn new(        workspace: &mut Workspace,        languages: Arc<LanguageRegistry>,        window: &mut Window,        cx: &mut Context<Workspace>,    ) -> Entity<ActivityIndicator> {        let project = workspace.project().clone();        let this = cx.new(|cx| {            let mut status_events = languages.language_server_binary_statuses();            cx.spawn(async move |this, cx| {                while let Some((name, binary_status)) = status_events.next().await {                    this.update(cx, |this: &mut ActivityIndicator, cx| {                        this.statuses.retain(|s| s.name != name);                        this.statuses.push(ServerStatus {                            name,                            status: LanguageServerStatusUpdate::Binary(binary_status),                        });                        cx.notify();                    })?;                }                anyhow::Ok(())            })            .detach();             let fs = project.read(cx).fs().clone();            let mut job_events = fs.subscribe_to_jobs();            cx.spawn(async move |this, cx| {                while let Some(job_event) = job_events.next().await {                    this.update(cx, |this: &mut ActivityIndicator, cx| {                        match job_event {                            fs::JobEvent::Started { info } => {                                this.fs_jobs.retain(|j| j.id != info.id);                                this.fs_jobs.push(info);                            }                            fs::JobEvent::Completed { id } => {                                this.fs_jobs.retain(|j| j.id != id);                            }                        }                        cx.notify();                    })?;                }                anyhow::Ok(())            })            .detach();             cx.subscribe(                &project.read(cx).lsp_store(),                |activity_indicator, _, event, cx| {                    if let LspStoreEvent::LanguageServerUpdate { name, message, .. } = event {                        if let proto::update_language_server::Variant::StatusUpdate(status_update) =                            message                        {                            let Some(name) = name.clone() else {                                return;                            };                            let status = match &status_update.status {                                Some(proto::status_update::Status::Binary(binary_status)) => {                                    if let Some(binary_status) =                                        proto::ServerBinaryStatus::from_i32(*binary_status)                                    {                                        let binary_status = match binary_status {                                            proto::ServerBinaryStatus::None => BinaryStatus::None,                                            proto::ServerBinaryStatus::CheckingForUpdate => {                                                BinaryStatus::CheckingForUpdate                                            }                                            proto::ServerBinaryStatus::Downloading => {                                                BinaryStatus::Downloading                                            }                                            proto::ServerBinaryStatus::Starting => {                                                BinaryStatus::Starting                                            }                                            proto::ServerBinaryStatus::Stopping => {                                                BinaryStatus::Stopping                                            }                                            proto::ServerBinaryStatus::Stopped => {                                                BinaryStatus::Stopped                                            }                                            proto::ServerBinaryStatus::Failed => {                                                let Some(error) = status_update.message.clone()                                                else {                                                    return;                                                };                                                BinaryStatus::Failed { error }                                            }                                        };                                        LanguageServerStatusUpdate::Binary(binary_status)                                    } else {                                        return;                                    }                                }                                Some(proto::status_update::Status::Health(health_status)) => {                                    if let Some(health) =                                        proto::ServerHealth::from_i32(*health_status)                                    {                                        let health = match health {                                            proto::ServerHealth::Ok => ServerHealth::Ok,                                            proto::ServerHealth::Warning => ServerHealth::Warning,                                            proto::ServerHealth::Error => ServerHealth::Error,                                        };                                        LanguageServerStatusUpdate::Health(                                            health,                                            status_update.message.clone().map(SharedString::from),                                        )                                    } else {                                        return;                                    }                                }                                None => return,                            };                             activity_indicator.statuses.retain(|s| s.name != name);                            activity_indicator                                .statuses                                .push(ServerStatus { name, status });                        }                        cx.notify()                    }                },            )            .detach();             cx.subscribe(                &project.read(cx).environment().clone(),                |_, _, event, cx| match event {                    ProjectEnvironmentEvent::ErrorsUpdated => cx.notify(),                },            )            .detach();             cx.subscribe(                &project.read(cx).git_store().clone(),                |_, _, event: &GitStoreEvent, cx| {                    if let project::git_store::GitStoreEvent::JobsUpdated = event {                        cx.notify()                    }                },            )            .detach();             Self {                statuses: Vec::new(),                project: project.clone(),                context_menu_handle: PopoverMenuHandle::default(),                fs_jobs: Vec::new(),            }        });         cx.subscribe_in(&this, window, move |_, _, event, window, cx| match event {            Event::ShowStatus {                server_name,                status,            } => {                let create_buffer =                    project.update(cx, |project, cx| project.create_buffer(None, false, cx));                let status = status.clone();                let server_name = server_name.clone();                cx.spawn_in(window, async move |workspace, cx| {                    let buffer = create_buffer.await?;                    buffer.update(cx, |buffer, cx| {                        buffer.edit(                            [(0..0, format!("Language server {server_name}:\n\n{status}"))],                            None,                            cx,                        );                        buffer.set_capability(language::Capability::ReadOnly, cx);                    });                    workspace.update_in(cx, |workspace, window, cx| {                        workspace.add_item_to_active_pane(                            Box::new(cx.new(|cx| {                                let mut editor = Editor::for_buffer(buffer, None, window, cx);                                editor.set_read_only(true);                                editor                            })),                            None,                            true,                            window,                            cx,                        );                    })?;                     anyhow::Ok(())                })                .detach();            }        })        .detach();        this    }     fn show_error_message(&mut self, _: &ShowErrorMessage, _: &mut Window, cx: &mut Context<Self>) {        let mut status_message_shown = false;        self.statuses.retain(|status| match &status.status {            LanguageServerStatusUpdate::Binary(BinaryStatus::Failed { error })                if !status_message_shown =>            {                cx.emit(Event::ShowStatus {                    server_name: status.name.clone(),                    status: SharedString::from(error),                });                status_message_shown = true;                false            }            LanguageServerStatusUpdate::Health(                ServerHealth::Error | ServerHealth::Warning,                status_string,            ) if !status_message_shown => match status_string {                Some(error) => {                    cx.emit(Event::ShowStatus {                        server_name: status.name.clone(),                        status: error.clone(),                    });                    status_message_shown = true;                    false                }                None => false,            },            _ => true,        });    }     fn dismiss_message(&mut self, _: &DismissMessage, _: &mut Window, cx: &mut Context<Self>) {        self.project.update(cx, |project, cx| {            if project.last_formatting_failure(cx).is_some() {                project.reset_last_formatting_failure(cx);                true            } else {                false            }        });    }     fn pending_language_server_work<'a>(        &self,        cx: &'a App,    ) -> impl Iterator<Item = PendingWork<'a>> {        self.project            .read(cx)            .language_server_statuses(cx)            .rev()            .flat_map(|(server_id, status)| {                let mut pending_work = status                    .pending_work                    .iter()                    .map(|(progress_token, progress)| PendingWork {                        language_server_id: server_id,                        progress_token,                        progress,                    })                    .collect::<SmallVec<[_; 4]>>();                pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));                pending_work            })    }     fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a String> {        self.project.read(cx).peek_environment_error(cx)    }     fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {        // Show if any direnv calls failed        if let Some(message) = self.pending_environment_error(cx) {            return Some(Content {                icon: ActivityIcon::Icon(IconName::Warning),                message: message.clone(),                on_click: Some(Arc::new(move |this, window, cx| {                    this.project.update(cx, |project, cx| {                        project.pop_environment_error(cx);                    });                    window.dispatch_action(Box::new(workspace::OpenLog), cx);                })),                tooltip_message: None,            });        }        // Show any language server has pending activity.        {            let mut pending_work = self.pending_language_server_work(cx);            if let Some(PendingWork {                progress_token,                progress,                ..            }) = pending_work.next()            {                let mut message = progress.title.clone().unwrap_or(progress_token.to_string());                 if let Some(percentage) = progress.percentage {                    write!(&mut message, " ({}%)", percentage).unwrap();                }                 if let Some(progress_message) = progress.message.as_ref() {                    message.push_str(": ");                    message.push_str(progress_message);                }                 let additional_work_count = pending_work.count();                if additional_work_count > 0 {                    write!(&mut message, " + {} more", additional_work_count).unwrap();                }                 return Some(Content {                    icon: ActivityIcon::LoadingSpinner,                    message,                    on_click: None,                    tooltip_message: None,                });            }        }         if let Some(session) = self            .project            .read(cx)            .dap_store()            .read(cx)            .sessions()            .find(|s| !s.read(cx).is_started())        {            return Some(Content {                icon: ActivityIcon::LoadingSpinner,                message: format!("Debug: {}", session.read(cx).adapter()),                tooltip_message: session.read(cx).label().map(|label| label.to_string()),                on_click: None,            });        }         let current_job = self            .project            .read(cx)            .active_repository(cx)            .map(|r| r.read(cx))            .and_then(Repository::current_job);        // Show any long-running git command        if let Some(job_info) = current_job            && Instant::now() - job_info.start >= GIT_OPERATION_DELAY        {            return Some(Content {                icon: ActivityIcon::LoadingSpinner,                message: job_info.message.into(),                on_click: None,                tooltip_message: None,            });        }         // Show any long-running fs command        for fs_job in &self.fs_jobs {            if Instant::now().duration_since(fs_job.start) >= GIT_OPERATION_DELAY {                return Some(Content {                    icon: ActivityIcon::LoadingSpinner,                    message: fs_job.message.clone().into(),                    on_click: None,                    tooltip_message: None,                });            }        }         // Show any language server installation info.        let mut downloading = SmallVec::<[_; 3]>::new();        let mut checking_for_update = SmallVec::<[_; 3]>::new();        let mut failed = SmallVec::<[_; 3]>::new();        let mut health_messages = SmallVec::<[_; 3]>::new();        let mut servers_to_clear_statuses = HashSet::<LanguageServerName>::default();        for status in &self.statuses {            match &status.status {                LanguageServerStatusUpdate::Binary(                    BinaryStatus::Starting | BinaryStatus::Stopping,                ) => {}                LanguageServerStatusUpdate::Binary(BinaryStatus::Stopped) => {                    servers_to_clear_statuses.insert(status.name.clone());                }                LanguageServerStatusUpdate::Binary(BinaryStatus::CheckingForUpdate) => {                    checking_for_update.push(status.name.clone());                }                LanguageServerStatusUpdate::Binary(BinaryStatus::Downloading) => {                    downloading.push(status.name.clone());                }                LanguageServerStatusUpdate::Binary(BinaryStatus::Failed { .. }) => {                    failed.push(status.name.clone());                }                LanguageServerStatusUpdate::Binary(BinaryStatus::None) => {}                LanguageServerStatusUpdate::Health(health, server_status) => match server_status {                    Some(server_status) => {                        health_messages.push((status.name.clone(), *health, server_status.clone()));                    }                    None => {                        servers_to_clear_statuses.insert(status.name.clone());                    }                },            }        }        self.statuses            .retain(|status| !servers_to_clear_statuses.contains(&status.name));         health_messages.sort_by_key(|(_, health, _)| match health {            ServerHealth::Error => 2,            ServerHealth::Warning => 1,            ServerHealth::Ok => 0,        });         if !downloading.is_empty() {            return Some(Content {                icon: ActivityIcon::Icon(IconName::Download),                message: format!(                    "Downloading {}...",                    downloading.iter().map(|name| name.as_ref()).fold(                        String::new(),                        |mut acc, s| {                            if !acc.is_empty() {                                acc.push_str(", ");                            }                            acc.push_str(s);                            acc                        }                    )                ),                on_click: Some(Arc::new(move |this, window, cx| {                    this.statuses                        .retain(|status| !downloading.contains(&status.name));                    this.dismiss_message(&DismissMessage, window, cx)                })),                tooltip_message: None,            });        }         if !checking_for_update.is_empty() {

Lines 1–500 of 781.