mirror of
https://github.com/nushell/nushell.git
synced 2025-08-12 14:28:09 +02:00
Add job tags (#15555)
# Description This PR implements job tagging through the usage of a new `job tag` command and a `--tag` for `job spawn` Closes #15354 # User-Facing Changes - New `job tag` command - Job list may now have an additional `tag` column for the tag of jobs (rows representing jobs without tags do not have this column filled) - New `--tag` flag for `job spawn` # Tests + Formatting Integration tests are provided to test the newly implemented features # After Submitting Possibly document job tagging in the jobs documentation
This commit is contained in:
@ -452,6 +452,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
JobSpawn,
|
||||
JobList,
|
||||
JobKill,
|
||||
JobTag,
|
||||
Job,
|
||||
};
|
||||
|
||||
|
2
crates/nu-command/src/env/config/config_.rs
vendored
2
crates/nu-command/src/env/config/config_.rs
vendored
@ -122,7 +122,7 @@ pub(super) fn start_editor(
|
||||
)
|
||||
})?;
|
||||
|
||||
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None);
|
||||
let post_wait_callback = PostWaitCallback::for_job_control(engine_state, None, None);
|
||||
|
||||
// Wrap the output into a `PipelineData::ByteStream`.
|
||||
let child = nu_protocol::process::ChildProcess::new(
|
||||
|
@ -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::<Vec<Value>>();
|
||||
|
@ -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<String> = 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))
|
||||
};
|
||||
|
81
crates/nu-command/src/experimental/job_tag.rs
Normal file
81
crates/nu-command/src/experimental/job_tag.rs
Normal file
@ -0,0 +1,81 @@
|
||||
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 description 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.")
|
||||
.required(
|
||||
"tag",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::String, SyntaxShape::Nothing]),
|
||||
"The tag to assign to the job.",
|
||||
)
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["describe", "desc"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
|
||||
let id_arg: Spanned<i64> = 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 tag: Option<String> = call.req(engine_state, stack, 1)?;
|
||||
|
||||
let mut jobs = engine_state.jobs.lock().expect("jobs lock is poisoned!");
|
||||
|
||||
match jobs.lookup_mut(id) {
|
||||
None => {
|
||||
return Err(ShellError::JobNotFound {
|
||||
id: id.get(),
|
||||
span: head,
|
||||
});
|
||||
}
|
||||
|
||||
Some(job) => job.assign_tag(tag),
|
||||
}
|
||||
|
||||
Ok(Value::nothing(head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
example: "let id = job spawn { sleep 10sec }; job tag $id abc ",
|
||||
description: "Tag a newly spawned job",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "let id = job spawn { sleep 10sec }; job tag $id abc; job tag $id null",
|
||||
description: "Remove the tag of a job",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
@ -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;
|
||||
@ -11,8 +12,8 @@ pub use is_admin::IsAdmin;
|
||||
pub use job::Job;
|
||||
pub use job_kill::JobKill;
|
||||
pub use job_list::JobList;
|
||||
|
||||
pub use job_spawn::JobSpawn;
|
||||
pub use job_tag::JobTag;
|
||||
|
||||
#[cfg(all(unix, feature = "os"))]
|
||||
pub use job_unfreeze::JobUnfreeze;
|
||||
|
@ -147,7 +147,7 @@ impl Command for External {
|
||||
};
|
||||
|
||||
// Create the command.
|
||||
let mut command = std::process::Command::new(executable);
|
||||
let mut command = std::process::Command::new(&executable);
|
||||
|
||||
// Configure PWD.
|
||||
command.current_dir(cwd);
|
||||
@ -322,6 +322,11 @@ impl Command for External {
|
||||
Some(PostWaitCallback::for_job_control(
|
||||
engine_state,
|
||||
Some(child_pid),
|
||||
executable
|
||||
.as_path()
|
||||
.file_name()
|
||||
.and_then(|it| it.to_str())
|
||||
.map(|it| it.to_string()),
|
||||
)),
|
||||
)?;
|
||||
|
||||
|
Reference in New Issue
Block a user