live-tasklist.dhover×87all
#!/usr/bin/env dub
/+ dub.sdl:
    name "live-tasklist"
    dependency "sparkles:core-cli" path="../../.."
    targetPath "build"
    // Optimised, assertions live, `debug {}` blocks out — the build every nix
    // artifact uses. Neither `debug` (which compiles those blocks in) nor
    // `release` (which deletes assert *expressions*, side effects included).
    buildType "checked" {
        buildOptions "optimize" "inline" "debugInfo"
    }
+/

// ci: build-only

// The live-rendering stack (`ui/live` + `ui/tasklist` + `runStreaming`): a
// `LiveRegion` repaints a block in place (DEC 2026 synchronized frames, static
// channel for lines graduating into scrollback, non-tty degradation to a plain
// transition log); a `TaskReporter` drives a checklist through it, streaming a
// child process's output into the running task's bounded tail pane. Animated,
// so `ci` only builds this; run it in a terminal — and pipe it through `cat`
// to see the escape-free non-tty transition log.

module 
(module) live_tasklist_example
live_tasklist_example
;
import
(package) core
core
.
(module) core.thread

The thread module provides support for thread creation and management.

Source

core/thread/package.d

@copyrightCopyright Sean Kelly 2005 - 2012.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak
thread
:
(class) core.thread.osthread.Thread

This class encapsulates all threading functionality for the D programming language. As thread manipulation is a required facility for garbage collection, all user threads should derive from this class, and instances of this class should never be explicitly deleted. A new thread may be created using either derivation or composition, as in the following example.

Thread
;
import
(package) core
core
.
(module) core.time

Module containing core time functionality, such as Duration (which represents a duration of time) or MonoTime (which represents a timestamp of the system's monotonic clock).

Various functions take a string (or strings) to represent a unit of time (e.g. convert!("days", "hours")(numDays)). The valid strings to use with such functions are "years", "months", "weeks", "days", "hours", "minutes", "seconds", "msecs" (milliseconds), "usecs" (microseconds), "hnsecs" (hecto-nanoseconds - i.e. 100 ns) or some subset thereof. There are a few functions that also allow "nsecs", but very little actually has precision greater than hnsecs.

Symbol Description
Types
Duration Represents a duration of time of weeks or less (kept internally as hnsecs). (e.g. 22 days or 700 seconds).
TickDuration DEPRECATED Represents a duration of time in system clock ticks, using the highest precision that the system provides.
MonoTime Represents a monotonic timestamp in system clock ticks, using the highest precision that the system provides.
Functions
convert Generic way of converting between two time units.
dur Allows constructing a Duration from the given time units with the given length.
weeks days hours

minutes seconds msecs

usecs hnsecs nsecs | Convenience aliases for dur. | | abs | Returns the absolute value of a duration. |

From Duration
From TickDuration
From units
To Duration
tickDuration.to, std,conv!Duration()
dur!"msecs"(5) or 5.msecs()

| To TickDuration | duration.to, std,conv!TickDuration() |

  • | TickDuration.from!"msecs"(msecs) |

| To units | duration.total!"days" | tickDuration.msecs | convert!("days", "msecs")(msecs) |

Source

core/time.d

@copyrightCopyright 2010 - 2012@licenseBoost License 1.0.@authorsJonathan M Davis and Kato Shoichi
time
: msecs;
import
(package) sparkles
sparkles
.
(package) sparkles.core_cli
core_cli
.
(module) sparkles.core_cli.process_utils
process_utils
:
(alias template) live_tasklist_example.runStreaming = sparkles.core_cli.process_utils.runStreaming(Sink)(const(string)[] args, scope Sink sink, string workDir = null, const string[string] env = null)

Runs args (an argv array — no shell) streaming its output line by line into sink as the child produces it, for live progress displays (a task list's subprocess tail). stdout and stderr are merged into one stream so lines arrive in output order; the full combined output is also returned in CapturedResult.stdout (stderr stays empty).

Like runCaptured this never throws: spawn failures yield status == 127 with the error text in stderr. The single merged pipe is drained continuously, so a chatty child cannot deadlock. Attributes infer from Sink (a callable taking scope const(char)[]).

runStreaming
;
import
(package) sparkles
sparkles
.
(package) sparkles.base
base
.
(module) sparkles.base.term_caps

Terminal capability probing: the synchronous size query (terminalSize), tty and color detection (detectTermCaps), and resize notifications (setTermWindowSizeHandler).

This is the single place the "what can this terminal do" decision is made; renderers stay pure producers that take explicit widths/flags. It lives in sparkles:base rather than a UI package because it is an environment query, not a presentation concern — a logger, a CLI tool and a full-screen UI all need it, and none of them should pull in a UI stack to ask.

TermSize is deliberately a plain POD rather than a Vector specialization: base sits below sparkles:math, and a capability snapshot never does vector arithmetic. The terminal's geometry types — positions you add offsets to — live in sparkles:tui (TermPosition), which is free to specialize Vector.

term_caps
:
(alias) live_tasklist_example.detectTermCaps = sparkles.base.term_caps.TermCaps sparkles.base.term_caps.detectTermCaps(bool noColors = false) @safe

Detects capabilities and prepares the console.

Colors are on only when stdout is a terminal and neither noColors, $NO_COLOR, nor TERM=dumb disables them; a non-empty, non-"0" $CLICOLOR_FORCE forces them on for a non-tty (but never overrides an explicit disable). colorDepth is the tier (classifyColorDepth over $COLORTERM/$TERM) when colors are on, else none. On Windows this additionally sets the output code page to UTF-8 (so ✓ ✗ ⚙ render even without colors) and enables virtual-terminal processing (so ANSI escapes are interpreted rather than printed literally); colors stay off when stdout is redirected or VT can't be enabled.

detectTermCaps
;
import
(package) sparkles
sparkles
.
(package) sparkles.ui
ui
.
(package) sparkles.ui.components
components
.
(module) sparkles.ui.components.live

A live region: a block of lines at the bottom of the normal scrollback flow that is repainted in place (no alternate screen), with a static channel for lines that graduate into scrollback above it — the log-update pattern (Ink/Bubble Tea/Mosaic) per docs/specs/core-cli/tui-components §5.

Every repaint is framed in DEC 2026 synchronized-output markers (no tearing), each frame line is truncated to the terminal width (a wrapped line would break the cursor-up arithmetic), and the region re-reads the width every update so resizes are picked up. On a non-interactive sink (piped output) update is a no-op and printAbove degrades to plain appended lines, so redirected runs see only the permanent output.

The region writes through an injected sink (testable without a terminal); use stdoutLiveRegion for the real thing, and scope (exit) region.finish(); so a thrown exception never leaves the cursor hidden.

live
:
(alias) live_tasklist_example.stdoutLiveRegion = sparkles.ui.components.live.LiveRegion sparkles.ui.components.live.stdoutLiveRegion()

A LiveRegion on stdout: interactive iff stdout is a terminal (each write is flushed so frames appear atomically), width from terminalSize() per repaint.

stdoutLiveRegion
;
import
(package) sparkles
sparkles
.
(package) sparkles.ui
ui
.
(package) sparkles.ui.components
components
.
(module) sparkles.ui.components.tasklist

A checklist of tasks driven through a LiveRegion: pending/running items are repainted in place, and each task that reaches a terminal state (ok / failed / skipped) graduates as a permanent line into the scrollback above — so on a tty the block shrinks as work completes, and on piped output only the completed-task transitions appear, one line each.

renderTaskLine/renderTaskList are pure producers (unit-testable, theme- driven); TaskReporter is the stateful driver the apps use.

tasklist
:
(struct) sparkles.ui.components.tasklist.TaskReporter

The stateful checklist driver. Owns the item states; each transition repaints the live block and graduates finished tasks into the scrollback. The LiveRegion stays owned by the caller (scope (exit) region.finish();).

TaskReporter
;
import
(package) sparkles
sparkles
.
(package) sparkles.ui
ui
.
(package) sparkles.ui.components
components
.
(module) sparkles.ui.components.theme

Theme layer: one border-charset selector shared by every framed component (drawBox / drawHeader / drawTable), the status-glyph vocabulary with ASCII fallbacks, semantic styles, and a Theme snapshot derived from TermCaps.

Components stay pure producers — the theme only selects glyphs/styles once at the edge (typically makeTheme(detectTermCaps()) at app startup); callers thread the resulting values through the existing BoxProps/TableProps/colored parameters.

theme
:
(alias) live_tasklist_example.makeTheme = sparkles.ui.components.theme.Theme sparkles.ui.components.theme.makeTheme(in sparkles.base.term_caps.TermCaps caps) pure nothrow @nogc @safe

The theme for a capability snapshot: ASCII borders + fallback glyphs on a non-UTF-8 terminal, colors per the caps decision.

makeTheme
;
void
void D main()
main
()
{ const
(local variable) const(sparkles.ui.components.theme.Theme) theme
theme
=
sparkles.ui.components.theme.Theme sparkles.ui.components.theme.makeTheme(in sparkles.base.term_caps.TermCaps caps) pure nothrow @nogc @safe

The theme for a capability snapshot: ASCII borders + fallback glyphs on a non-UTF-8 terminal, colors per the caps decision.

makeTheme
(
sparkles.base.term_caps.TermCaps sparkles.base.term_caps.detectTermCaps(bool noColors = false) @safe

Detects capabilities and prepares the console.

Colors are on only when stdout is a terminal and neither noColors, $NO_COLOR, nor TERM=dumb disables them; a non-empty, non-"0" $CLICOLOR_FORCE forces them on for a non-tty (but never overrides an explicit disable). colorDepth is the tier (classifyColorDepth over $COLORTERM/$TERM) when colors are on, else none. On Windows this additionally sets the output code page to UTF-8 (so ✓ ✗ ⚙ render even without colors) and enables virtual-terminal processing (so ANSI escapes are interpreted rather than printed literally); colors stay off when stdout is redirected or VT can't be enabled.

detectTermCaps
());
auto
(local variable) sparkles.ui.components.live.LiveRegion region
region
=
sparkles.ui.components.live.LiveRegion sparkles.ui.components.live.stdoutLiveRegion()

A LiveRegion on stdout: interactive iff stdout is a terminal (each write is flushed so frames appear atomically), width from terminalSize() per repaint.

stdoutLiveRegion
();
scope (exit)
(local variable) sparkles.ui.components.live.LiveRegion region
region
.
void sparkles.ui.components.live.LiveRegion.finish(bool keepFrame = true)

End the region: restore the cursor and either keep the last frame as permanent output (default) or erase it. Idempotent; call it from a scope (exit) so exceptions can't leave the cursor hidden.

finish
();
auto
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
=
(struct) sparkles.ui.components.tasklist.TaskReporter

The stateful checklist driver. Owns the item states; each transition repaints the live block and graduates finished tasks into the scrollback. The LiveRegion stays owned by the caller (scope (exit) region.finish();).

TaskReporter
(&
sparkles.ui.components.tasklist.TaskReporter sparkles.ui.components.tasklist.TaskReporter.this(sparkles.ui.components.live.LiveRegion* region, sparkles.ui.components.theme.Theme theme) ref
region
,
(local variable) const(sparkles.ui.components.theme.Theme) theme
theme
);
// Register everything up front so the pending rows show the plan. const
(local variable) const(ulong) fetch
fetch
=
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
ulong sparkles.ui.components.tasklist.TaskReporter.add(string label, ulong indent = 0LU)

Register a task (pending) and return its handle.

add
("fetch dependencies");
const
(local variable) const(ulong) build
build
=
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
ulong sparkles.ui.components.tasklist.TaskReporter.add(string label, ulong indent = 0LU)

Register a task (pending) and return its handle.

add
("build (streams output into the tail pane)");
const
(local variable) const(ulong) lint
lint
=
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
ulong sparkles.ui.components.tasklist.TaskReporter.add(string label, ulong indent = 0LU)

Register a task (pending) and return its handle.

add
("lint");
const
(local variable) const(ulong) publish
publish
=
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
ulong sparkles.ui.components.tasklist.TaskReporter.add(string label, ulong indent = 0LU)

Register a task (pending) and return its handle.

add
("publish");
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.start(ulong id)

Mark id running (starts its clock) and repaint.

start
(
(local variable) const(ulong) fetch
fetch
);
foreach (
(local variable) int i
i
; 0 .. 8)
{
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.tick()

Advance the spinner (call from a periodic tick when one is available).

tick
(); // spinner animation between events
(class) core.thread.osthread.Thread

This class encapsulates all threading functionality for the D programming language. As thread manipulation is a required facility for garbage collection, all user threads should derive from this class, and instances of this class should never be explicitly deleted. A new thread may be created using either derivation or composition, as in the following example.

Thread
.
void core.thread.osthread.Thread.sleep(core.time.Duration val) nothrow @nogc @trusted

Suspends the calling thread for at least the supplied period. This may result in multiple OS calls if period is greater than the maximum sleep duration supported by the operating system.

In

period must be non-negative.

Example


Thread.sleep( dur!("msecs")( 50 ) );  // sleep for 50 milliseconds
Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds

@paramval The minimum duration the calling thread should be suspended.
sleep
(80.msecs);
}
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.succeed(ulong id, string detail = null)

Terminal transitions: the task's line graduates into the scrollback. A multi-line detail puts the first line on the task row and the rest as indented follow-up lines (e.g. a failure's output tail).

succeed
(
(local variable) const(ulong) fetch
fetch
);
// A real child process: each output line lands in the bounded tail pane // (last 4 lines) under the running row, and pulses the spinner.
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.start(ulong id)

Mark id running (starts its clock) and repaint.

start
(
(local variable) const(ulong) build
build
);
sparkles.core_cli.process_utils.CapturedResult sparkles.core_cli.process_utils.runStreaming!(void delegate(scope const(char)[] line) @system)(const(string)[] args, scope void delegate(scope const(char)[] line) @system sink, string workDir = null, const(string[string]) env = cast(const(string[string]))null) nothrow @system

Runs args (an argv array — no shell) streaming its output line by line into sink as the child produces it, for live progress displays (a task list's subprocess tail). stdout and stderr are merged into one stream so lines arrive in output order; the full combined output is also returned in CapturedResult.stdout (stderr stays empty).

Like runCaptured this never throws: spawn failures yield status == 127 with the error text in stderr. The single merged pipe is drained continuously, so a chatty child cannot deadlock. Attributes infer from Sink (a callable taking scope const(char)[]).

runStreaming
(["sh", "-c",
"for i in $(seq 1 12); do echo compiling module $i; sleep 0.15; done"], (scope const(char)[]
(parameter) const(char)[] line
line
) {
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.output(ulong id, scope const(char)[] line)

Feed one live-output line into the running task id's bounded tail pane (keeps the last tailLines), advancing the spinner and repainting. No-op for tasks that are not running (late output after completion is dropped — the frame no longer shows the row).

output
(
(local variable) const(ulong) build
build
,
(parameter) const(char)[] line
line
); });
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.succeed(ulong id, string detail = null)

Terminal transitions: the task's line graduates into the scrollback. A multi-line detail puts the first line on the task row and the rest as indented follow-up lines (e.g. a failure's output tail).

succeed
(
(local variable) const(ulong) build
build
);
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.start(ulong id)

Mark id running (starts its clock) and repaint.

start
(
(local variable) const(ulong) lint
lint
);
(class) core.thread.osthread.Thread

This class encapsulates all threading functionality for the D programming language. As thread manipulation is a required facility for garbage collection, all user threads should derive from this class, and instances of this class should never be explicitly deleted. A new thread may be created using either derivation or composition, as in the following example.

Thread
.
void core.thread.osthread.Thread.sleep(core.time.Duration val) nothrow @nogc @trusted

Suspends the calling thread for at least the supplied period. This may result in multiple OS calls if period is greater than the maximum sleep duration supported by the operating system.

In

period must be non-negative.

Example


Thread.sleep( dur!("msecs")( 50 ) );  // sleep for 50 milliseconds
Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds

@paramval The minimum duration the calling thread should be suspended.
sleep
(300.msecs);
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.fail(ulong id, string detail = null)

Terminal transitions: the task's line graduates into the scrollback. A multi-line detail puts the first line on the task row and the rest as indented follow-up lines (e.g. a failure's output tail).

fail
(
(local variable) const(ulong) lint
lint
, "3 warnings\nunused import in app.d\nshadowed variable in io.d");
(local variable) sparkles.ui.components.tasklist.TaskReporter tasks
tasks
.
void sparkles.ui.components.tasklist.TaskReporter.skip(ulong id, string detail = null)

Terminal transitions: the task's line graduates into the scrollback. A multi-line detail puts the first line on the task row and the rest as indented follow-up lines (e.g. a failure's output tail).

skip
(
(local variable) const(ulong) publish
publish
, "blocked by lint");
}