diff --git a/crates/nu-command/src/experimental/job_list.rs b/crates/nu-command/src/experimental/job_list.rs index 28c0979aca..440e0aca64 100644 --- a/crates/nu-command/src/experimental/job_list.rs +++ b/crates/nu-command/src/experimental/job_list.rs @@ -37,7 +37,7 @@ impl Command for JobList { let values = jobs .iter() .map(|(id, job)| { - let record = record! { + let mut record = record! { "id" => Value::int(id.get() as i64, head), "type" => match job { Job::Thread(_) => Value::string("thread", head), @@ -52,12 +52,16 @@ impl Command for JobList { head, ), - Job::Frozen(FrozenJob { unfreeze }) => { + Job::Frozen(FrozenJob { unfreeze, .. }) => { Value::list(vec![ Value::int(unfreeze.pid() as i64, head) ], head) } - } + }, }; + if let Some(tag) = job.tag() { + record.push("tag", Value::string(tag, head)); + } + Value::record(record, head) }) .collect::>(); diff --git a/crates/nu-command/src/experimental/job_spawn.rs b/crates/nu-command/src/experimental/job_spawn.rs index 60f30024be..37203aafb9 100644 --- a/crates/nu-command/src/experimental/job_spawn.rs +++ b/crates/nu-command/src/experimental/job_spawn.rs @@ -28,6 +28,12 @@ impl Command for JobSpawn { Signature::build("job spawn") .category(Category::Experimental) .input_output_types(vec![(Type::Nothing, Type::Int)]) + .named( + "tag", + SyntaxShape::String, + "An optional description tag for this job", + Some('t'), + ) .required( "closure", SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), @@ -50,6 +56,8 @@ impl Command for JobSpawn { let closure: Closure = call.req(engine_state, stack, 0)?; + let tag: Option = call.get_flag(engine_state, stack, "tag")?; + let mut job_state = engine_state.clone(); job_state.is_interactive = false; @@ -68,7 +76,7 @@ impl Command for JobSpawn { let mut jobs = jobs.lock().expect("jobs lock is poisoned!"); let id = { - let thread_job = ThreadJob::new(job_signals); + let thread_job = ThreadJob::new(job_signals, tag); job_state.current_thread_job = Some(thread_job.clone()); jobs.add_job(Job::Thread(thread_job)) }; diff --git a/crates/nu-command/src/experimental/job_tag.rs b/crates/nu-command/src/experimental/job_tag.rs new file mode 100644 index 0000000000..c98654ccd2 --- /dev/null +++ b/crates/nu-command/src/experimental/job_tag.rs @@ -0,0 +1,64 @@ +use nu_engine::command_prelude::*; +use nu_protocol::JobId; + +#[derive(Clone)] +pub struct JobTag; + +impl Command for JobTag { + fn name(&self) -> &str { + "job tag" + } + + fn description(&self) -> &str { + "Add a tag to a background job." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("job tag") + .category(Category::Experimental) + .required("id", SyntaxShape::Int, "The id of the job to tag.") + .input_output_types(vec![(Type::Nothing, Type::Nothing)]) + .allow_variants_without_examples(true) + } + + fn search_terms(&self) -> Vec<&str> { + vec!["describe", "desc"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let head = call.head; + + let id_arg: Spanned = call.req(engine_state, stack, 0)?; + + if id_arg.item < 0 { + return Err(ShellError::NeedsPositiveValue { span: id_arg.span }); + } + + let id: JobId = JobId::new(id_arg.item as usize); + + let mut jobs = engine_state.jobs.lock().expect("jobs lock is poisoned!"); + + if jobs.lookup(id).is_none() { + return Err(ShellError::JobNotFound { + id: id.get(), + span: head, + }); + } + + Ok(Value::nothing(head).into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![Example { + example: "let id = job spawn { sleep 10sec }; job tag $id", + description: "tag a newly spawned job", + result: None, + }] + } +} diff --git a/crates/nu-command/src/experimental/job_unfreeze.rs b/crates/nu-command/src/experimental/job_unfreeze.rs index 46ee5b46f5..67fb3c96a1 100644 --- a/crates/nu-command/src/experimental/job_unfreeze.rs +++ b/crates/nu-command/src/experimental/job_unfreeze.rs @@ -112,7 +112,10 @@ fn unfreeze_job( span, }), - Job::Frozen(FrozenJob { unfreeze: handle }) => { + Job::Frozen(FrozenJob { + unfreeze: handle, + tag, + }) => { let pid = handle.pid(); if let Some(thread_job) = &state.current_thread_job { @@ -141,8 +144,14 @@ fn unfreeze_job( Ok(ForegroundWaitStatus::Frozen(handle)) => { let mut jobs = state.jobs.lock().expect("jobs lock is poisoned!"); - jobs.add_job_with_id(old_id, Job::Frozen(FrozenJob { unfreeze: handle })) - .expect("job was supposed to be removed"); + jobs.add_job_with_id( + old_id, + Job::Frozen(FrozenJob { + unfreeze: handle, + tag, + }), + ) + .expect("job was supposed to be removed"); if state.is_interactive { println!("\nJob {} is re-frozen", old_id.get()); diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs index ff4f6b0399..94125fc5bb 100644 --- a/crates/nu-command/src/experimental/mod.rs +++ b/crates/nu-command/src/experimental/mod.rs @@ -3,6 +3,7 @@ mod job; mod job_kill; mod job_list; mod job_spawn; +mod job_tag; #[cfg(all(unix, feature = "os"))] mod job_unfreeze; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 03e2d3de02..c3d0d9ea23 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -332,7 +332,12 @@ impl Command for External { if let ForegroundWaitStatus::Frozen(unfreeze) = status { let mut jobs = jobs.lock().expect("jobs lock is poisoned!"); - let job_id = jobs.add_job(Job::Frozen(FrozenJob { unfreeze })); + // TODO: use name of process as a tag + let job_id = jobs.add_job(Job::Frozen(FrozenJob { + unfreeze, + tag: None, + })); + if is_interactive { println!("\nJob {} is frozen", job_id.get()); } diff --git a/crates/nu-protocol/src/engine/jobs.rs b/crates/nu-protocol/src/engine/jobs.rs index 7d0b4c4b56..cdde13a16a 100644 --- a/crates/nu-protocol/src/engine/jobs.rs +++ b/crates/nu-protocol/src/engine/jobs.rs @@ -38,6 +38,10 @@ impl Jobs { self.jobs.get(&id) } + pub fn lookup_mut(&mut self, id: JobId) -> Option<&mut Job> { + self.jobs.get_mut(&id) + } + pub fn remove_job(&mut self, id: JobId) -> Option { if self.last_frozen_job_id.is_some_and(|last| id == last) { self.last_frozen_job_id = None; @@ -134,13 +138,15 @@ pub enum Job { pub struct ThreadJob { signals: Signals, pids: Arc>>, + tag: Option, } impl ThreadJob { - pub fn new(signals: Signals) -> Self { + pub fn new(signals: Signals, tag: Option) -> Self { ThreadJob { signals, pids: Arc::new(Mutex::new(HashSet::default())), + tag, } } @@ -197,10 +203,18 @@ impl Job { Job::Frozen(frozen_job) => frozen_job.kill(), } } + + pub fn tag(&self) -> Option<&String> { + match self { + Job::Thread(thread_job) => thread_job.tag.as_ref(), + Job::Frozen(frozen_job) => frozen_job.tag.as_ref(), + } + } } pub struct FrozenJob { pub unfreeze: UnfreezeHandle, + pub tag: Option, } impl FrozenJob {