dev_container_suggest.rs

  1use db::kvp::KeyValueStore;
  2use dev_container::find_configs_in_snapshot;
  3use gpui::{SharedString, Window};
  4use project::{Project, WorktreeId};
  5use std::sync::LazyLock;
  6use ui::prelude::*;
  7use util::ResultExt;
  8use util::rel_path::RelPath;
  9use workspace::Workspace;
 10use workspace::notifications::NotificationId;
 11use workspace::notifications::simple_message_notification::MessageNotification;
 12use worktree::UpdatedEntriesSet;
 13
 14const DEV_CONTAINER_SUGGEST_KEY: &str = "dev_container_suggest_dismissed";
 15
 16fn devcontainer_dir_path() -> &'static RelPath {
 17    static PATH: LazyLock<&'static RelPath> =
 18        LazyLock::new(|| RelPath::unix(".devcontainer").expect("valid path"));
 19    *PATH
 20}
 21
 22fn devcontainer_json_path() -> &'static RelPath {
 23    static PATH: LazyLock<&'static RelPath> =
 24        LazyLock::new(|| RelPath::unix(".devcontainer.json").expect("valid path"));
 25    *PATH
 26}
 27
 28fn project_devcontainer_key(project_path: &str) -> String {
 29    format!("{}_{}", DEV_CONTAINER_SUGGEST_KEY, project_path)
 30}
 31
 32pub fn suggest_on_worktree_updated(
 33    worktree_id: WorktreeId,
 34    updated_entries: &UpdatedEntriesSet,
 35    project: &gpui::Entity<Project>,
 36    window: &mut Window,
 37    cx: &mut Context<Workspace>,
 38) {
 39    let devcontainer_updated = updated_entries.iter().any(|(path, _, _)| {
 40        path.as_ref() == devcontainer_dir_path() || path.as_ref() == devcontainer_json_path()
 41    });
 42
 43    if !devcontainer_updated {
 44        return;
 45    }
 46
 47    let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else {
 48        return;
 49    };
 50
 51    let worktree = worktree.read(cx);
 52
 53    if !worktree.is_local() {
 54        return;
 55    }
 56
 57    if find_configs_in_snapshot(worktree).is_empty() {
 58        return;
 59    }
 60
 61    let abs_path = worktree.abs_path();
 62    let project_path = abs_path.to_string_lossy().to_string();
 63    let key_for_dismiss = project_devcontainer_key(&project_path);
 64
 65    let already_dismissed = KeyValueStore::global(cx)
 66        .read_kvp(&key_for_dismiss)
 67        .ok()
 68        .flatten()
 69        .is_some();
 70
 71    if already_dismissed {
 72        return;
 73    }
 74
 75    cx.on_next_frame(window, move |workspace, _window, cx| {
 76        struct DevContainerSuggestionNotification;
 77
 78        let notification_id = NotificationId::composite::<DevContainerSuggestionNotification>(
 79            SharedString::from(project_path.clone()),
 80        );
 81
 82        workspace.show_notification(notification_id, cx, |cx| {
 83            cx.new(move |cx| {
 84                MessageNotification::new(
 85                    "This project contains a Dev Container configuration file. Would you like to re-open it in a container?",
 86                    cx,
 87                )
 88                .primary_message("Yes, Open in Container")
 89                .primary_icon(IconName::Check)
 90                .primary_icon_color(Color::Success)
 91                .primary_on_click({
 92                    move |window, cx| {
 93                        window.dispatch_action(Box::new(zed_actions::OpenDevContainer), cx);
 94                    }
 95                })
 96                .secondary_message("Don't Show Again")
 97                .secondary_icon(IconName::Close)
 98                .secondary_icon_color(Color::Error)
 99                .secondary_on_click({
100                    move |_window, cx| {
101                        let key = key_for_dismiss.clone();
102                        let kvp = KeyValueStore::global(cx);
103                        cx.background_spawn(async move {
104                            kvp.write_kvp(key, "dismissed".to_string())
105                                .await
106                                .log_err();
107                        })
108                        .detach();
109                    }
110                })
111            })
112        });
113    });
114}