|
| 1 | +--- |
| 2 | +date: '2026-02-11T15:00:00+01:00' |
| 3 | +draft: false |
| 4 | +title: 'Progress Bar' |
| 5 | +weight: 20 |
| 6 | +--- |
| 7 | + |
| 8 | +The progress bar utility displays progress feedback for long-running operations in the terminal. It supports multiple visual styles, optional labels, percentage and ratio display, and renders in-place using ANSI escape sequences. |
| 9 | + |
| 10 | +## Quick Start |
| 11 | + |
| 12 | +```java |
| 13 | +import org.aesh.util.progress.ProgressBar; |
| 14 | +import org.aesh.util.progress.ProgressBarStyle; |
| 15 | + |
| 16 | +ProgressBar progress = ProgressBar.builder() |
| 17 | + .shell(invocation.getShell()) |
| 18 | + .total(files.size()) |
| 19 | + .label("Processing") |
| 20 | + .style(ProgressBarStyle.UNICODE) |
| 21 | + .build(); |
| 22 | + |
| 23 | +for (File file : files) { |
| 24 | + processFile(file); |
| 25 | + progress.step(); |
| 26 | +} |
| 27 | +progress.complete(); |
| 28 | +``` |
| 29 | + |
| 30 | +Output (mid-progress): |
| 31 | + |
| 32 | +``` |
| 33 | +Processing │████████░░░░░░░░│ 52% |
| 34 | +``` |
| 35 | + |
| 36 | +## Builder API |
| 37 | + |
| 38 | +All configuration is done through the builder. Only `total` is required; everything else has sensible defaults. |
| 39 | + |
| 40 | +```java |
| 41 | +ProgressBar progress = ProgressBar.builder() |
| 42 | + .shell(invocation.getShell()) // Shell to write to |
| 43 | + .total(200) // total number of steps |
| 44 | + .label("Downloading") // optional text before the bar |
| 45 | + .style(ProgressBarStyle.ASCII) // visual style (default: ASCII) |
| 46 | + .showPercentage(true) // show XX% (default: true) |
| 47 | + .showRatio(true) // show current/total (default: false) |
| 48 | + .width(80) // explicit width (default: auto from terminal) |
| 49 | + .build(); |
| 50 | +``` |
| 51 | + |
| 52 | +### Builder Options |
| 53 | + |
| 54 | +| Method | Default | Description | |
| 55 | +|--------|---------|-------------| |
| 56 | +| `shell(Shell)` | `null` | The `Shell` instance for terminal output | |
| 57 | +| `total(long)` | `0` | Total number of steps (supports large values via `long`) | |
| 58 | +| `label(String)` | none | Text label displayed before the bar | |
| 59 | +| `style(ProgressBarStyle)` | `ASCII` | Visual style for bar characters | |
| 60 | +| `showPercentage(boolean)` | `true` | Whether to display the percentage | |
| 61 | +| `showRatio(boolean)` | `false` | Whether to display `current/total` | |
| 62 | +| `width(int)` | auto | Explicit terminal width; if not set, reads from `Shell.size()` or defaults to 80 | |
| 63 | + |
| 64 | +## Update Methods |
| 65 | + |
| 66 | +### `step()` |
| 67 | + |
| 68 | +Increment progress by 1 and re-render the bar: |
| 69 | + |
| 70 | +```java |
| 71 | +for (File file : files) { |
| 72 | + processFile(file); |
| 73 | + progress.step(); |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +### `step(long n)` |
| 78 | + |
| 79 | +Increment progress by a given amount: |
| 80 | + |
| 81 | +```java |
| 82 | +progress.step(10); // advance by 10 steps |
| 83 | +``` |
| 84 | + |
| 85 | +### `update(long value)` |
| 86 | + |
| 87 | +Set an absolute progress value: |
| 88 | + |
| 89 | +```java |
| 90 | +progress.update(75); // jump to 75 out of total |
| 91 | +``` |
| 92 | + |
| 93 | +### `complete()` |
| 94 | + |
| 95 | +Mark the operation as finished. This fills the bar to 100% and prints a newline so subsequent output appears below: |
| 96 | + |
| 97 | +```java |
| 98 | +progress.complete(); |
| 99 | +``` |
| 100 | + |
| 101 | +### `complete(String message)` |
| 102 | + |
| 103 | +Mark complete and replace the bar line with a custom message: |
| 104 | + |
| 105 | +```java |
| 106 | +progress.complete("Done! Processed 200 files."); |
| 107 | +``` |
| 108 | + |
| 109 | +## Progress Bar Styles |
| 110 | + |
| 111 | +Four predefined styles are available via `ProgressBarStyle`: |
| 112 | + |
| 113 | +### ASCII |
| 114 | + |
| 115 | +The default style using standard ASCII characters: |
| 116 | + |
| 117 | +```java |
| 118 | +ProgressBar.builder().style(ProgressBarStyle.ASCII)... |
| 119 | +``` |
| 120 | + |
| 121 | +``` |
| 122 | +[####------] 40% |
| 123 | +``` |
| 124 | + |
| 125 | +### UNICODE |
| 126 | + |
| 127 | +Unicode block characters for a solid, modern look: |
| 128 | + |
| 129 | +```java |
| 130 | +ProgressBar.builder().style(ProgressBarStyle.UNICODE)... |
| 131 | +``` |
| 132 | + |
| 133 | +``` |
| 134 | +│████░░░░░░│ 40% |
| 135 | +``` |
| 136 | + |
| 137 | +### SIMPLE |
| 138 | + |
| 139 | +Minimal style with equals signs and spaces: |
| 140 | + |
| 141 | +```java |
| 142 | +ProgressBar.builder().style(ProgressBarStyle.SIMPLE)... |
| 143 | +``` |
| 144 | + |
| 145 | +``` |
| 146 | +[==== ] 40% |
| 147 | +``` |
| 148 | + |
| 149 | +### ARROW |
| 150 | + |
| 151 | +Like SIMPLE but with a `>` tip character at the progress front: |
| 152 | + |
| 153 | +```java |
| 154 | +ProgressBar.builder().style(ProgressBarStyle.ARROW)... |
| 155 | +``` |
| 156 | + |
| 157 | +``` |
| 158 | +[===> ] 40% |
| 159 | +``` |
| 160 | + |
| 161 | +At 100% completion, the arrow tip disappears and the bar is fully filled with `=`. |
| 162 | + |
| 163 | +### Style Characters |
| 164 | + |
| 165 | +Each style defines five characters: |
| 166 | + |
| 167 | +| Style | Fill | Empty | Left Bracket | Right Bracket | Tip | |
| 168 | +|-------|------|-------|--------------|---------------|-----| |
| 169 | +| `ASCII` | `#` | `-` | `[` | `]` | `#` | |
| 170 | +| `UNICODE` | `█` | `░` | `│` | `│` | `█` | |
| 171 | +| `SIMPLE` | `=` | ` ` | `[` | `]` | `=` | |
| 172 | +| `ARROW` | `=` | ` ` | `[` | `]` | `>` | |
| 173 | + |
| 174 | +## Display Options |
| 175 | + |
| 176 | +### Label |
| 177 | + |
| 178 | +An optional text label is rendered before the bar: |
| 179 | + |
| 180 | +```java |
| 181 | +ProgressBar.builder() |
| 182 | + .label("Downloading") |
| 183 | + .total(100) |
| 184 | + .build(); |
| 185 | +``` |
| 186 | + |
| 187 | +``` |
| 188 | +Downloading [####------] 40% |
| 189 | +``` |
| 190 | + |
| 191 | +Without a label, the bar starts immediately: |
| 192 | + |
| 193 | +``` |
| 194 | +[####------] 40% |
| 195 | +``` |
| 196 | + |
| 197 | +### Percentage |
| 198 | + |
| 199 | +Enabled by default. Disable with `showPercentage(false)`: |
| 200 | + |
| 201 | +```java |
| 202 | +ProgressBar.builder() |
| 203 | + .showPercentage(false) |
| 204 | + .total(100) |
| 205 | + .build(); |
| 206 | +``` |
| 207 | + |
| 208 | +``` |
| 209 | +[####------] |
| 210 | +``` |
| 211 | + |
| 212 | +### Ratio |
| 213 | + |
| 214 | +Disabled by default. Enable with `showRatio(true)` to show `current/total`: |
| 215 | + |
| 216 | +```java |
| 217 | +ProgressBar.builder() |
| 218 | + .showRatio(true) |
| 219 | + .total(200) |
| 220 | + .build(); |
| 221 | +``` |
| 222 | + |
| 223 | +``` |
| 224 | +[####------] 40% (80/200) |
| 225 | +``` |
| 226 | + |
| 227 | +## Bar Width Calculation |
| 228 | + |
| 229 | +The bar automatically sizes itself to fill the available terminal width. The layout is: |
| 230 | + |
| 231 | +``` |
| 232 | +[label ] [left-bracket] [===bar===] [right-bracket] [ XX%] [ (current/total)] |
| 233 | +``` |
| 234 | + |
| 235 | +The bar width is calculated as: |
| 236 | + |
| 237 | +``` |
| 238 | +barWidth = terminalWidth - labelLength - brackets(2) - percentageLength - ratioLength |
| 239 | +``` |
| 240 | + |
| 241 | +If the calculated bar width falls below 10 characters, it is clamped to a minimum of 10. |
| 242 | + |
| 243 | +When no explicit width is set via `width()`, the builder reads the terminal width from `Shell.size().getWidth()`. If the shell is unavailable, it defaults to 80 columns. |
| 244 | + |
| 245 | +## Using in Commands |
| 246 | + |
| 247 | +Inside a command's `execute()` method, pass the `Shell` from the `CommandInvocation` to the builder: |
| 248 | + |
| 249 | +```java |
| 250 | +import org.aesh.command.Command; |
| 251 | +import org.aesh.command.CommandDefinition; |
| 252 | +import org.aesh.command.CommandResult; |
| 253 | +import org.aesh.command.invocation.CommandInvocation; |
| 254 | +import org.aesh.util.progress.ProgressBar; |
| 255 | +import org.aesh.util.progress.ProgressBarStyle; |
| 256 | + |
| 257 | +@CommandDefinition(name = "process", description = "Process files") |
| 258 | +public class ProcessCommand implements Command<CommandInvocation> { |
| 259 | + |
| 260 | + @Override |
| 261 | + public CommandResult execute(CommandInvocation invocation) { |
| 262 | + List<File> files = getFiles(); |
| 263 | + |
| 264 | + ProgressBar progress = ProgressBar.builder() |
| 265 | + .shell(invocation.getShell()) |
| 266 | + .total(files.size()) |
| 267 | + .label("Processing") |
| 268 | + .style(ProgressBarStyle.UNICODE) |
| 269 | + .showRatio(true) |
| 270 | + .build(); |
| 271 | + |
| 272 | + for (File file : files) { |
| 273 | + processFile(file); |
| 274 | + progress.step(); |
| 275 | + } |
| 276 | + progress.complete("Done! Processed " + files.size() + " files."); |
| 277 | + |
| 278 | + return CommandResult.SUCCESS; |
| 279 | + } |
| 280 | +} |
| 281 | +``` |
| 282 | + |
| 283 | +The key calls in the chain: |
| 284 | + |
| 285 | +| Call | Returns | Purpose | |
| 286 | +|------|---------|---------| |
| 287 | +| `invocation.getShell()` | `Shell` | Access the terminal | |
| 288 | +| `shell.size()` | `Size` | Get terminal dimensions (used automatically) | |
| 289 | +| `progress.step()` | `void` | Advance and re-render the bar | |
| 290 | +| `progress.complete()` | `void` | Fill to 100% and move to next line | |
| 291 | + |
| 292 | +## Edge Cases |
| 293 | + |
| 294 | +| Scenario | Behavior | |
| 295 | +|----------|----------| |
| 296 | +| `total` is 0 | Renders as 100% (avoids division by zero) | |
| 297 | +| `current` exceeds `total` | Clamped to 100% | |
| 298 | +| Very narrow terminal | Bar width clamped to minimum of 10 characters | |
| 299 | +| `shell` is null | Update methods are no-ops; `render()` still works for testing | |
| 300 | + |
| 301 | +## How It Works |
| 302 | + |
| 303 | +Each call to `step()`, `update()`, or `complete()` re-renders the bar on the current line using ANSI escape sequences from `org.aesh.terminal.utils.ANSI`: |
| 304 | + |
| 305 | +1. `ANSI.CURSOR_START` — moves the cursor to the beginning of the line |
| 306 | +2. `ANSI.ERASE_WHOLE_LINE` — clears the entire line |
| 307 | +3. `Shell.write(String)` — writes the formatted bar string |
| 308 | + |
| 309 | +This creates a smooth in-place update effect without scrolling the terminal. |
| 310 | + |
| 311 | +## API Reference |
| 312 | + |
| 313 | +### ProgressBar |
| 314 | + |
| 315 | +| Method | Description | |
| 316 | +|--------|-------------| |
| 317 | +| `ProgressBar.builder()` | Create a builder for fluent configuration | |
| 318 | +| `update(long)` | Set absolute progress and re-render | |
| 319 | +| `step()` | Increment by 1 and re-render | |
| 320 | +| `step(long)` | Increment by n and re-render | |
| 321 | +| `complete()` | Fill to 100% and print a newline | |
| 322 | +| `complete(String)` | Replace the bar with a completion message | |
| 323 | + |
| 324 | +### ProgressBarStyle |
| 325 | + |
| 326 | +| Style | Description | |
| 327 | +|-------|-------------| |
| 328 | +| `ASCII` | `#` fill, `-` empty, `[` `]` brackets | |
| 329 | +| `UNICODE` | `█` fill, `░` empty, `│` `│` brackets | |
| 330 | +| `SIMPLE` | `=` fill, space empty, `[` `]` brackets | |
| 331 | +| `ARROW` | `=` fill with `>` tip, space empty, `[` `]` brackets | |
| 332 | + |
| 333 | +## See Also |
| 334 | + |
| 335 | +- [Table Display](table) — structured data rendering utility |
| 336 | +- [CommandInvocation API](command-invocation) — accessing the Shell in commands |
0 commit comments