935 lines · 37.5 KB · Rust
RawDownload
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage, VersionCheckType};use editor::Editor;use extension_host::ExtensionStore;use futures::StreamExt;use gpui::{    Animation, AnimationExt as _, App, Context, CursorStyle, Entity, EventEmitter,    InteractiveElement as _, ParentElement as _, Render, SharedString, StatefulInteractiveElement,    Styled, Transformation, Window, actions, percentage,};use language::{    BinaryStatus, LanguageRegistry, LanguageServerId, LanguageServerName,    LanguageServerStatusUpdate, ServerHealth,};use project::{    EnvironmentErrorMessage, LanguageServerProgress, LspStoreEvent, Project,    ProjectEnvironmentEvent,    git_store::{GitStoreEvent, Repository},};use smallvec::SmallVec;use std::{    cmp::Reverse,    collections::HashSet,    fmt::Write,    path::Path,    sync::Arc,    time::{Duration, Instant},};use ui::{ButtonLike, 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>,    auto_updater: Option<Entity<AutoUpdater>>,    context_menu_handle: PopoverMenuHandle<ContextMenu>,} #[derive(Debug)]struct ServerStatus {    name: LanguageServerName,    status: LanguageServerStatusUpdate,} struct PendingWork<'a> {    language_server_id: LanguageServerId,    progress_token: &'a str,    progress: &'a LanguageServerProgress,} struct Content {    icon: Option<gpui::AnyElement>,    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 auto_updater = AutoUpdater::get(cx);        let workspace_handle = cx.entity();        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();             cx.subscribe_in(                &workspace_handle,                window,                |activity_indicator, _, event, window, cx| match event {                    workspace::Event::ClearActivityIndicator { .. } => {                        if activity_indicator.statuses.pop().is_some() {                            activity_indicator.dismiss_error_message(                                &DismissErrorMessage,                                window,                                cx,                            );                            cx.notify();                        }                    }                    _ => {}                },            )            .detach();             cx.subscribe(                &project.read(cx).lsp_store(),                |activity_indicator, _, event, cx| match event {                    LspStoreEvent::LanguageServerUpdate { name, message, .. } => {                        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| match event {                    project::git_store::GitStoreEvent::JobsUpdated => cx.notify(),                    _ => {}                },            )            .detach();             if let Some(auto_updater) = auto_updater.as_ref() {                cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();            }             Self {                statuses: Vec::new(),                project: project.clone(),                auto_updater,                context_menu_handle: Default::default(),            }        });         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(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_error_message(        &mut self,        _: &DismissErrorMessage,        _: &mut Window,        cx: &mut Context<Self>,    ) {        let error_dismissed = if let Some(updater) = &self.auto_updater {            updater.update(cx, |updater, cx| updater.dismiss_error(cx))        } else {            false        };        if error_dismissed {            return;        }         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()            .filter_map(|(server_id, status)| {                if status.pending_work.is_empty() {                    None                } else {                    let mut pending_work = status                        .pending_work                        .iter()                        .map(|(token, progress)| PendingWork {                            language_server_id: server_id,                            progress_token: token.as_str(),                            progress,                        })                        .collect::<SmallVec<[_; 4]>>();                    pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));                    Some(pending_work)                }            })            .flatten()    }     fn pending_environment_errors<'a>(        &'a self,        cx: &'a App,    ) -> impl Iterator<Item = (&'a Arc<Path>, &'a EnvironmentErrorMessage)> {        self.project.read(cx).shell_environment_errors(cx)    }     fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {        // Show if any direnv calls failed        if let Some((abs_path, error)) = self.pending_environment_errors(cx).next() {            let abs_path = abs_path.clone();            return Some(Content {                icon: Some(                    Icon::new(IconName::Warning)                        .size(IconSize::Small)                        .into_any_element(),                ),                message: error.0.clone(),                on_click: Some(Arc::new(move |this, window, cx| {                    this.project.update(cx, |project, cx| {                        project.remove_environment_error(&abs_path, 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                    .as_deref()                    .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: Some(                        Icon::new(IconName::ArrowCircle)                            .size(IconSize::Small)                            .with_animation(                                "arrow-circle",                                Animation::new(Duration::from_secs(2)).repeat(),                                |icon, delta| {                                    icon.transform(Transformation::rotate(percentage(delta)))                                },                            )                            .into_any_element(),                    ),                    message,                    on_click: Some(Arc::new(Self::toggle_language_server_work_context_menu)),                    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: Some(                    Icon::new(IconName::ArrowCircle)                        .size(IconSize::Small)                        .with_animation(                            "arrow-circle",                            Animation::new(Duration::from_secs(2)).repeat(),                            |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),                        )                        .into_any_element(),                ),                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 {            if Instant::now() - job_info.start >= GIT_OPERATION_DELAY {                return Some(Content {                    icon: Some(                        Icon::new(IconName::ArrowCircle)                            .size(IconSize::Small)                            .with_animation(                                "arrow-circle",                                Animation::new(Duration::from_secs(2)).repeat(),                                |icon, delta| {                                    icon.transform(Transformation::rotate(percentage(delta)))                                },                            )                            .into_any_element(),                    ),                    message: job_info.message.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) => {

Lines 1–500 of 935.