feat: add explicit resume command
This commit is contained in:
@@ -275,10 +275,17 @@ async fn connect_live_pod(
|
||||
|
||||
pub(crate) async fn run_resume(
|
||||
runtime_command: PodRuntimeCommand,
|
||||
workspace_root: PathBuf,
|
||||
all: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Pick a Pod in its own inline viewport, dropping the viewport before
|
||||
// attaching/restoring so each phase gets fresh vertical room.
|
||||
let (pod_name, socket_override) = match picker::run().await? {
|
||||
let picker_options = if all {
|
||||
picker::PickerOptions::all()
|
||||
} else {
|
||||
picker::PickerOptions::workspace(workspace_root)
|
||||
};
|
||||
let (pod_name, socket_override) = match picker::run(picker_options).await? {
|
||||
PickerOutcome::Picked {
|
||||
pod_name,
|
||||
socket_override,
|
||||
|
||||
@@ -48,16 +48,17 @@ pub enum LaunchMode {
|
||||
pod_name: Option<String>,
|
||||
profile: Option<String>,
|
||||
},
|
||||
/// `yoi <name>` / `yoi --pod <name>`: attach to a live Pod by name if
|
||||
/// possible; otherwise launch the Pod runtime command with `--pod <name>` so it
|
||||
/// `yoi --pod <name>`: attach to a live Pod by name if possible;
|
||||
/// otherwise launch the Pod runtime command with `--pod <name>` so it
|
||||
/// resumes from name-keyed state or creates a fresh same-name Pod.
|
||||
PodName {
|
||||
pod_name: String,
|
||||
socket_override: Option<PathBuf>,
|
||||
},
|
||||
/// `yoi -r` / `yoi --resume`: open the Pod picker, then attach to the
|
||||
/// selected live Pod or restore the selected stopped Pod by name.
|
||||
Resume,
|
||||
/// `yoi resume`: open the Pod picker, then attach to the selected live Pod
|
||||
/// or restore the selected stopped Pod by name. Without `--all`, the picker
|
||||
/// is scoped to the current runtime workspace.
|
||||
Resume { all: bool },
|
||||
/// `yoi --session <UUID>`: skip the picker, go straight to the
|
||||
/// resume name dialog with `id` baked in.
|
||||
ResumeWithSession {
|
||||
@@ -101,7 +102,9 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
|
||||
pod_name,
|
||||
socket_override,
|
||||
} => console::run_pod_name(pod_name, socket_override, runtime_command).await,
|
||||
LaunchMode::Resume => console::run_resume(runtime_command).await,
|
||||
LaunchMode::Resume { all } => {
|
||||
console::run_resume(runtime_command, workspace_root.clone(), all).await
|
||||
}
|
||||
LaunchMode::ResumeWithSession { id, pod_name } => {
|
||||
console::run_spawn(Some(id), pod_name, None, runtime_command).await
|
||||
}
|
||||
|
||||
+104
-9
@@ -20,7 +20,7 @@ use ratatui::{Frame, TerminalOptions, Viewport};
|
||||
use session_store::FsStore;
|
||||
|
||||
use crate::pod_list::{
|
||||
PodList, PodListEntry, PodVisibilitySource, StoredMetadataState,
|
||||
LivePodInfo, PodList, PodListEntry, PodVisibilitySource, StoredMetadataState, StoredPodInfo,
|
||||
live_socket_for_pod as pod_list_live_socket_for_pod, read_reachable_live_pod_infos,
|
||||
read_stored_pod_infos,
|
||||
};
|
||||
@@ -73,6 +73,31 @@ pub enum PickerOutcome {
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PickerOptions {
|
||||
scope: PickerScope,
|
||||
}
|
||||
|
||||
impl PickerOptions {
|
||||
pub(crate) fn workspace(workspace_root: PathBuf) -> Self {
|
||||
Self {
|
||||
scope: PickerScope::Workspace(workspace_root),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn all() -> Self {
|
||||
Self {
|
||||
scope: PickerScope::All,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum PickerScope {
|
||||
Workspace(PathBuf),
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum PodRowState {
|
||||
Live,
|
||||
@@ -100,7 +125,31 @@ impl PodRowState {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run() -> Result<PickerOutcome, PickerError> {
|
||||
fn list_for_options(
|
||||
options: &PickerOptions,
|
||||
stored_pods: Vec<StoredPodInfo>,
|
||||
live_pods: Vec<LivePodInfo>,
|
||||
) -> PodList {
|
||||
match &options.scope {
|
||||
PickerScope::Workspace(workspace_root) => PodList::from_workspace_sources(
|
||||
PodVisibilitySource::ResumePicker,
|
||||
stored_pods,
|
||||
live_pods,
|
||||
None,
|
||||
MAX_ROWS,
|
||||
workspace_root,
|
||||
),
|
||||
PickerScope::All => PodList::from_sources(
|
||||
PodVisibilitySource::ResumePicker,
|
||||
stored_pods,
|
||||
live_pods,
|
||||
None,
|
||||
MAX_ROWS,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(options: PickerOptions) -> Result<PickerOutcome, PickerError> {
|
||||
let store_dir = default_store_dir()?;
|
||||
let store = FsStore::new(&store_dir)?;
|
||||
let pod_store = FsPodStore::new(default_pod_store_dir()?).map_err(io::Error::other)?;
|
||||
@@ -108,13 +157,7 @@ pub async fn run() -> Result<PickerOutcome, PickerError> {
|
||||
let live_pods = read_reachable_live_pod_infos(&store)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let mut list = PodList::from_sources(
|
||||
PodVisibilitySource::ResumePicker,
|
||||
stored_pods,
|
||||
live_pods,
|
||||
None,
|
||||
MAX_ROWS,
|
||||
);
|
||||
let mut list = list_for_options(&options, stored_pods, live_pods);
|
||||
if list.entries.is_empty() {
|
||||
return Err(PickerError::NoPods);
|
||||
}
|
||||
@@ -361,6 +404,58 @@ mod tests {
|
||||
assert_eq!(picker_title(), "resume pod pick a pod");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picker_workspace_options_filter_by_workspace_metadata() {
|
||||
let list = list_for_options(
|
||||
&PickerOptions::workspace(PathBuf::from("/workspace/current")),
|
||||
vec![
|
||||
stored_pod("current", Some("/workspace/current"), 3),
|
||||
stored_pod("other", Some("/workspace/other"), 2),
|
||||
stored_pod("legacy", None, 1),
|
||||
],
|
||||
vec![],
|
||||
);
|
||||
|
||||
let names: Vec<_> = list
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| entry.name.as_str())
|
||||
.collect();
|
||||
assert_eq!(names, vec!["current"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picker_all_options_include_host_wide_and_legacy_pods() {
|
||||
let list = list_for_options(
|
||||
&PickerOptions::all(),
|
||||
vec![
|
||||
stored_pod("current", Some("/workspace/current"), 3),
|
||||
stored_pod("other", Some("/workspace/other"), 2),
|
||||
stored_pod("legacy", None, 1),
|
||||
],
|
||||
vec![],
|
||||
);
|
||||
|
||||
let names: Vec<_> = list
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| entry.name.as_str())
|
||||
.collect();
|
||||
assert_eq!(names, vec!["current", "other", "legacy"]);
|
||||
}
|
||||
|
||||
fn stored_pod(name: &str, workspace_root: Option<&str>, updated_at: u64) -> StoredPodInfo {
|
||||
StoredPodInfo {
|
||||
pod_name: name.to_string(),
|
||||
metadata_state: StoredMetadataState::Present,
|
||||
active_session_id: None,
|
||||
active_segment_id: None,
|
||||
updated_at,
|
||||
workspace_root: workspace_root.map(PathBuf::from),
|
||||
preview: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picker_row_shows_live_pending_preview_and_runtime_segment_id() {
|
||||
let segment_id = session_store::new_segment_id();
|
||||
|
||||
Reference in New Issue
Block a user