Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
49e6656
Allow progress bar hline titling
impact-basin Jan 18, 2026
1c6463d
Test for progress bar title setting
impact-basin Jan 18, 2026
a4da95d
Allow per-job column specification at addjob!() invocation
impact-basin Jan 18, 2026
194df78
Import SpinnerColumn
impact-basin Jan 18, 2026
ca1b4fc
Progress bars: Add swapjob!(), tests.
impact-basin Jan 18, 2026
3b95d82
Cleanup SLIME remnants
impact-basin Jan 18, 2026
21ee0b5
Fix old docstring parameter spec
impact-basin Jan 18, 2026
5c5b0e5
Documentation for swapjobs!()
impact-basin Jan 18, 2026
47fa86e
Reduce sleep() in progress tests for swapjob!()
impact-basin Jan 18, 2026
c43f06a
Merge pull request #1 from FedeClaudi/master
impact-basin Jan 18, 2026
2a2b97f
Update docs
impact-basin Jan 18, 2026
31c4431
Same as last :-)
impact-basin Jan 18, 2026
844baaa
Allow progress bar hline titling
impact-basin Jan 18, 2026
bb1a8c7
Test for progress bar title setting
impact-basin Jan 18, 2026
b6c2156
Update CI.yml (#276)
t-bltg Jan 18, 2026
9ad5aed
CI: add `timeout-minutes` and fix `macos-latest` (#278)
t-bltg Jan 18, 2026
5c79f43
Merge branch 'FedeClaudi:master' into swapjob
impact-basin Jan 18, 2026
d0e38fd
LTS doesn't support UUID7
impact-basin Jan 18, 2026
e8d4c62
Merge branch 'perjob-cols'
impact-basin Jan 18, 2026
1e71f83
Merge per-column jobs with addjob!(), swapjob!() feature branches
impact-basin Jan 18, 2026
b9957cc
Cleanup HEAD
impact-basin Jan 18, 2026
fb824ab
Possible variable scoping issues
impact-basin Jan 18, 2026
5962a35
Use UUID4 in tests
impact-basin Jan 18, 2026
a57e50d
Formatting
impact-basin Jan 18, 2026
55dc632
Test formatting
impact-basin Jan 19, 2026
65c5d1c
No spooky action at a distance
impact-basin Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions docs/src/adv/progressbars.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,72 @@ end
stop!(pbar)
```

## Swapping jobs on-the-fly

Information about task state may arrive after the task has started; in other cases, tasks may change between states which we would like to represent differently. We may otherwise wish to change an in-flight `ProgressJob`. This facility is provided by `swapjob!()`. In terms of invocation, `swapjob!()` is similar to `addjob!()`, and shares most of its arguments and defaults.

`swapjob!()` is invoked with `ProgressBar` and job-identifying parameters. The latter may be a `ProgressJob` structure directly, or it may be the `ProgressJob` ID. If the job is not associated with the bar, an `ArgumentError` is raised.

As an example, we will generate three tasks with no bound. At different points in the iteration, we will introduce these bounds, and change the columns of each job to reflect its new state. The second job is marked transient, and will complete and be removed from the display when it completes; it is also marked to complete earlier than the other two bars. We will additionally remove the bound for the first job late in the iteration.

```Julia
import Term.Progress: DescriptionColumn, SeparatorColumn, CompletedColumn, ProgressColumn, SpinnerColumn

# make a progress bar, and add three jobs.
let p = ProgressBar(; title = "swapjob!() Demo")
j1 = addjob!(p; description="[1]: No N bound...")
j2 = addjob!(p; description="[2]: No N bound...", transient = true)
j3 = addjob!(p; description="[3]: No N bound...")

# when we switch on limits, we will add these columns to each ProgressJob.
cols = [DescriptionColumn, SeparatorColumn, CompletedColumn,
SeparatorColumn, ProgressColumn, SeparatorColumn, SpinnerColumn]

with(p) do
for i in 1:300

# invoke swapjob!() to change displays on-the-fly.
if i == 50
j1 = swapjob!(p, j1; N=300, description = "[1]: N bounded", columns = cols)
end
if i == 150
j2 = swapjob!(p, j2; N=200, description = "[2]: N bounded", columns = cols)
end
if i == 200
j3 = swapjob!(p, j3; N=300, description = "[3]: N bounded", columns = cols)
end
if i == 250
j1 = swapjob!(p, j1, description = "[1]: Lost bound!", N=nothing, inherit = true)
end

update!.([j1, j2, j3])
sleep(0.02)
end
end
end
```

### Inheritance in `swapjob!()`

When `swapjob!()` is called, internally, a new `ProgressJob` object is created and swapped in. There are three sources of data used to build the new `ProgressJob` object. From highest to lowest precedence, they are:

1. Arguments passed directly to `swapjob!()`.
2. Values carried over from the previous `ProgressJob` object, if the keyword argument `inherit` is set to `true`;
3. Default arguments as in `addjob!()`, if `inherit` is false.

`inherit` is set to `true` by default; this simplifies code operating on `ProgressJob` objects in state-machine contexts. Arguments to `swapjob!()` unspecified by the caller are represented as `missing`.

The list of inheritable `ProgressJob` attributes is as follows, where the default column is for `inherit = false`:

| Parameter | Type | Default | Description |
| ---------------- | -------------------- | ------------------------------- | -------------------------------------------------------------- |
| `description` | `String` | `"Running..."` | Contents of `DescriptionColumn` |
| `N` | `Union{Int,Nothing}` | `nothing` | Iteration maximum |
| `i` | `Int` | `0` | Number of iterations |
| `columns` | `Vector{DataType}` | Columns of parent `ProgressBar` | Column structure of `ProgressJob` |
| `columns_kwargs` | `Dict` | `Dict()` | Column keyword arguments |
| `transient` | `Bool` | `false` | Clean-up bar display after `ProgressJob` enters finished state |

## For each progress
Want to just wrap an iterable in a progress bar rendering? Check this out.

Expand Down
86 changes: 82 additions & 4 deletions src/progress.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export ProgressBar,
start!,
stop!,
update!,
swapjob!,
removejob!,
with,
@track,
Expand Down Expand Up @@ -115,8 +116,8 @@ mutable struct ProgressJob
0,
N,
description,
columns,
columns_kwargs,
copy(columns),
copy(columns_kwargs),
width,
false,
false,
Expand Down Expand Up @@ -341,7 +342,8 @@ Base.show(io::IO, ::MIME"text/plain", pbar::ProgressBar) =
N::Union{Int, Nothing}=nothing,
start::Bool=true,
transient::Bool=false,
id=nothing
id=nothing,
columns::Vector{DataType} = pbar.columns
)::ProgressJob

Add a new `ProgressJob` to a running `ProgressBar`
Expand All @@ -356,14 +358,15 @@ function addjob!(
transient::Bool = false,
id = nothing,
columns_kwargs::Dict = Dict(),
columns::Vector{DataType} = pbar.columns,
)::ProgressJob
pbar.running && print("\n")

# create Job
pbar.paused = true
id = isnothing(id) ? length(pbar.jobs) + 1 : id
kwargs = merge(pbar.columns_kwargs, columns_kwargs)
job = ProgressJob(id, N, description, pbar.columns, pbar.width, kwargs, transient)
job = ProgressJob(id, N, description, columns, pbar.width, kwargs, transient)

# start job
start && start!(job)
Expand All @@ -373,6 +376,81 @@ function addjob!(
return job
end

"""
swapjob!(pbar :: ProgressBar,
jobid :: Union{ProgressJob, Int, UUID};
description :: Union{String,Missing} = missing,
N :: Union{Int,Nothing,Missing} = missing,
i :: Union{Int,Missing} = missing,
columns :: Union{Vector{DataType}, Missing} = missing,
columns_kwargs :: Union{Dict, Missing} = missing,
transient :: Union{Bool,Missing} = missing,
start :: Bool = true,
inherit :: Bool = true,
)::ProgressJob

Change progress jobs on-the-fly; returns the newly-constructed `ProgressJob`.

The new progress job inherits the job ID of the old process. This allows
for a progress job which represents an ongoing task to change form appropriate
to the current task state. The job to replace can be specified either by a
`ProgressJob` structure or a job ID. An ArgumentError will be thrown if the
job ID is not found.

Fields of the new job are populated with the following priority:
1. Arguments passed to `swapjob!()`,
2. Fields of the prior job (if the kwarg `inherit` is true),
3. Default settings as in `addjob!()`.
"""
function swapjob!(
pbar::ProgressBar,
jobid::Union{ProgressJob,Int,UUID};
description::Union{String,Missing} = missing,
N::Union{Int,Nothing,Missing} = missing,
i::Union{Int,Missing} = missing,
columns::Union{Vector{DataType},Missing} = missing,
columns_kwargs::Union{Dict,Missing} = missing,
transient::Union{Bool,Missing} = missing,
start::Bool = true,
inherit::Bool = true,
)::ProgressJob

# get the job ID and ensure that it is present in the list of progress bar jobs
jobid = jobid isa ProgressJob ? jobid.id : jobid
index = findfirst(j -> j.id == jobid, pbar.jobs)
isnothing(index) && throw(
ArgumentError(
"ID $jobid is not a valid job ID. Registered jobs: $([j.id for j in pbar.jobs])",
),
)

# stop the bar and mix in any new column keyword arguments
inh(p, o, d) = !ismissing(p) ? p : (inherit ? o : d)
pbar.paused = true

# build the job and start it
job = ProgressJob(
jobid,
inh(N, pbar.jobs[index].N, nothing),
inh(description, pbar.jobs[index].description, "Running..."),
inh(columns, typeof.(pbar.jobs[index].columns), pbar.columns),
pbar.width,
merge(
pbar.columns_kwargs,
inh(columns_kwargs, pbar.jobs[index].columns_kwargs, Dict()),
),
inh(transient, pbar.jobs[index].transient, false),
)
job.i = inh(i, pbar.jobs[index].i, 0)
start && start!(job)
pbar.jobs[index] = job

# render and return
pbar.paused = false
render(pbar.jobs[index], pbar)
return pbar.jobs[index]
end

"""
removejob!(pbar::ProgressBar, job::ProgressJob)

Expand Down
Loading
Loading