use collections::HashMap; use feature_flags::{FeatureFlag, FeatureFlagAppExt}; struct GitJobDebugViewFeatureFlag; impl FeatureFlag for GitJobDebugViewFeatureFlag { const NAME: &'static str = "git-job-debug-view"; fn enabled_for_staff() -> bool { true } } use gpui::{ App, AppContext as _, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, Task, Window, actions, }; use project::{Project, git_store::GitStoreEvent}; use ui::{Label, LabelCommon, LabelSize, div, h_flex, prelude::FluentBuilder, v_flex}; use workspace::{Item, SplitDirection, Workspace}; actions!(dev, [OpenGitJobDebugView]); pub fn init(cx: &mut App) { cx.observe_new(move |workspace: &mut Workspace, _, _| { workspace.register_action(move |workspace, _: &OpenGitJobDebugView, window, cx| { if !cx.has_flag::() { return; } let project = workspace.project().clone(); let view = cx.new(|cx| GitJobDebugView::new(project, window, cx)); workspace.split_item(SplitDirection::Right, Box::new(view), window, cx) }); }) .detach(); } pub struct GitJobDebugView { project: Entity, focus_handle: FocusHandle, _git_store_subscription: Subscription, queue_observations: HashMap, } impl GitJobDebugView { fn new(project: Entity, _window: &mut Window, cx: &mut Context) -> Self { let git_store = project.read(cx).git_store().clone(); let git_store_subscription = cx.subscribe(&git_store, |this: &mut Self, _, event, cx| match event { GitStoreEvent::RepositoryAdded | GitStoreEvent::RepositoryRemoved(_) => { this.observe_queues(cx); } _ => {} }); let mut this = Self { project, focus_handle: cx.focus_handle(), _git_store_subscription: git_store_subscription, queue_observations: HashMap::default(), }; this.observe_queues(cx); this } fn observe_queues(&mut self, cx: &mut Context) { self.queue_observations.clear(); let queues: Vec<_> = self .project .read(cx) .repositories(cx) .values() .filter_map(|repo| repo.read(cx).job_queue().upgrade()) .collect(); for queue in queues { let id = queue.entity_id(); let subscription = cx.observe(&queue, |_, _, cx| cx.notify()); self.queue_observations.insert(id, subscription); } cx.notify(); } } impl Render for GitJobDebugView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let repositories = self.project.read(cx).repositories(cx).clone(); let mut repo_sections = Vec::new(); for (repo_id, repo_entity) in &repositories { let repo = repo_entity.read(cx); let current_job = repo.current_job(cx); let repo_path: SharedString = repo .work_directory_abs_path .to_string_lossy() .to_string() .into(); let mut rows = Vec::new(); if let Some(job_info) = current_job { rows.push( h_flex() .gap_4() .child( Label::new("▶ running") .size(LabelSize::Small) .color(ui::Color::Success), ) .child(Label::new(job_info.message.clone()).size(LabelSize::Small)) .into_any_element(), ); } if let Some(queue) = repo.job_queue().upgrade() { let queue = queue.read(cx); for job in queue.jobs() { let location = job.source_location(); let location_str: SharedString = format!( "{}:{}:{}", location.file(), location.line(), location.column() ) .into(); rows.push( h_flex() .gap_4() .child( Label::new("◻ queued") .size(LabelSize::Small) .color(ui::Color::Muted), ) .child(Label::new(job.name().clone()).size(LabelSize::Small)) .when_some(job.key(), |this, key| { this.child( Label::new(key.to_string()) .size(LabelSize::Small) .color(ui::Color::Muted), ) }) .child( Label::new(location_str) .size(LabelSize::Small) .color(ui::Color::Muted), ) .into_any_element(), ); } } repo_sections.push( v_flex() .gap_1() .child( Label::new(format!("Repository {} ({})", repo_id.0, repo_path)) .size(LabelSize::Default), ) .when(rows.is_empty(), |this| { this.child( Label::new(" (idle)") .size(LabelSize::Small) .color(ui::Color::Muted), ) }) .children(rows) .into_any_element(), ); } div() .flex_1() .size_full() .p_4() .track_focus(&self.focus_handle) .child( v_flex() .gap_4() .child(Label::new("Git Job Queue Debug View")) .when(repo_sections.is_empty(), |this| { this.child( Label::new("No repositories") .size(LabelSize::Small) .color(ui::Color::Muted), ) }) .children(repo_sections), ) } } impl EventEmitter<()> for GitJobDebugView {} impl Focusable for GitJobDebugView { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() } } impl Item for GitJobDebugView { type Event = (); fn to_item_events(_: &Self::Event, _: &mut dyn FnMut(workspace::item::ItemEvent)) {} fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { "Git Job Queue".into() } fn telemetry_event_text(&self) -> Option<&'static str> { None } fn clone_on_split( &self, _: Option, window: &mut Window, cx: &mut Context, ) -> Task>> where Self: Sized, { let project = self.project.clone(); Task::ready(Some(cx.new(|cx| Self::new(project, window, cx)))) } }