Viewer¶
The main widget. One import, one class, sensible defaults — everything works with zero configuration and every behavior has a setter/getter pair.
from ansi_text_viewer import AnsiTextViewer
viewer = AnsiTextViewer()
viewer.appendAnsiText("\x1b[32m[INFO]\x1b[0m server up\n")
| Feature | What lives here |
|---|---|
| Appending | Write, replace, clear, pause, clear-on-start |
| Non-blocking bursts | Async queue, progress, cancel |
| Search & filter | Highlight, navigate, hide lines |
| Limits | Memory caps |
| Fonts & zoom | Families, sizes, system font |
| Layout | Wrap, scroll, line numbers |
| Highlight toggles | Timestamp, levels, syntax, custom rules |
| Current line | Cursor-line highlight |
| Links | Clickable URLs and file paths |
| Copy, export, sessions | Clipboard, files, stats |
| Bookmarks | Mark, navigate, persist |
| Theme & colors | Light/dark/auto, palettes, defaults |
Shortcuts¶
| Keys | Action |
|---|---|
| Ctrl+F | Focus search (findRequested) |
| F3 / Shift+F3 | Next / previous match |
| Esc | Clear search highlight |
| Ctrl + wheel | Zoom in / out |
Shortcuts are application-wide, so F3 works while typing in a search box.
Signals¶
The widget talks back through Qt signals — no polling needed:
| Signal | Emitted when |
|---|---|
bookmarksChanged(list) |
Any bookmark is added, removed, or cleared (payload: block numbers) |
findRequested() |
Ctrl+F is pressed — focus your search field |
nextRequested() / prevRequested() |
F3 / Shift+F3 is pressed |
asyncAppendProgress(int, int) |
Each async chunk lands (done, total lines) |
asyncAppendFinished() |
The async queue drains |
viewer.bookmarksChanged.connect(refresh_bookmark_list)
viewer.findRequested.connect(search_box.setFocus)
Appending content¶
appendAnsiText() parses ANSI SGR colors, 256-color and truecolor codes,
plus cursor movement and erase sequences. A carriage return overwrites the
current line instead of appending, so progress bars render in place:
setAnsiText() replaces everything, insertAnsiText() writes at the
cursor, and clear() resets text, ANSI state, search, bookmarks and any
queued async chunks. pause_stream() buffers incoming text (capped at
5000 entries) until resume_stream() flushes it. Pair
setClearOnStart(True) with beginNewStream() to wipe the view whenever
a new stream starts.
Append ANSI text; \r overwrites the current line (progress bars).
Source code in ansi_text_viewer/ansi_text_viewer.py
Clear content, ANSI state, search, bookmarks and queued async.
Source code in ansi_text_viewer/ansi_text_viewer.py
Non-blocking bursts¶
appendAnsiTextAsync() splits text into line chunks inserted one per
event-loop turn, so a 100k-line burst never freezes the UI. Chunking
reuses the exact sync path — ANSI state, \r handling, timestamps and
highlighting are byte-identical (covered by test_async_matches_sync).
Call it on the GUI thread; from worker threads, connect a signal to it —
Qt queues the call across threads automatically.
viewer.appendAnsiTextAsync(huge_log, chunk_lines=500)
viewer.asyncAppendProgress.connect(
lambda done, total: bar.setValue(100 * done // total)
)
viewer.asyncAppendFinished.connect(lambda: status.showMessage("done"))
hasPendingAppends() reports queued work and cancelAsyncAppends()
drops it (the in-flight chunk still finishes). clear() also drops the
queue, so cleared output can never be resurrected by pending chunks.
Queue text for non-blocking appends; returns immediately.
Large bursts are split into line chunks inserted one per event-loop turn, so the UI never freezes. Order, ANSI state, timestamps and highlighting match the sync path. Must be called on the GUI thread; from worker threads, emit a signal connected to this slot instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
Text to append (may contain newlines and ANSI codes). |
required |
chunk_lines
|
int
|
Lines per event-loop turn, clamped to 1..10000. |
500
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When text is not a string. |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Drop queued async chunks; the in-flight chunk still finishes.
Returns:
| Type | Description |
|---|---|
int
|
The number of dropped chunks. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Search & filter¶
highlight_search() highlights every match and jumps to the first,
returning the count. Over-long patterns are rejected to blunt ReDoS, and
invalid regex falls back safely instead of raising:
n = viewer.highlight_search(r"ERR-\d+", use_regex=True)
viewer.next_match() # F3
viewer.prev_match() # Shift+F3
filter_search() hides non-matching blocks (line numbers and bookmarks
track the survivors); an empty query restores everything. Newly appended
lines are filtered on arrival, and match colors are tunable with
setSearchHighlightColor().
Highlight every match and jump to the first one.
Over-long regexes are rejected to blunt ReDoS from untrusted input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
Text or pattern to find (empty clears the highlight). |
required |
use_regex
|
bool
|
Treat query as a regular expression. |
False
|
match_case
|
bool
|
Case-sensitive matching. |
False
|
Returns:
| Type | Description |
|---|---|
int
|
The number of matches found. |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Jump to the next match, wrapping around.
Returns:
| Type | Description |
|---|---|
int
|
The 1-based index of the now-active match. |
Jump to the previous match, wrapping around.
Returns:
| Type | Description |
|---|---|
int
|
The 1-based index of the now-active match. |
Show only lines matching query; empty query restores all.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
Text or pattern lines must contain. |
required |
use_regex
|
bool
|
Treat query as a regular expression. |
False
|
match_case
|
bool
|
Case-sensitive matching. |
False
|
Source code in ansi_text_viewer/ansi_text_viewer.py
Set match backgrounds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color
|
QColor
|
Background for inactive matches. |
required |
active_color
|
QColor | None
|
Background for the active match, or None to leave it unchanged. |
None
|
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Limits and performance¶
Two caps bound steady-state memory: setMaximumBlocks() (default 10,000
lines, oldest trimmed) and setMaxLineLength() (default 10,000 chars per
line, truncated with a marker). Measured on this machine: 10k plain lines
add ~6MB over Qt's ~4MB baseline; a 2MB single line would spike ~70MB of
per-character overhead, hence the truncation. Bulk appends freeze widget
updates and refresh once, and bookmark scans are skipped entirely when no
bookmarks exist — 2000 streaming lines land in ~0.5s. Dense URL anchors
are the one known cost (~4KB/line); turn links off for firehose logs.
Cap kept lines for steady memory (default 10000, max 200000).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Maximum document blocks; clamped to 100..200000. |
required |
Source code in ansi_text_viewer/ansi_text_viewer.py
Cap single-line length.
Longer lines are truncated with a marker. This bounds the
per-character overhead (a 2MB single line spikes ~70MB otherwise).
0 disables truncation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Maximum characters per line, clamped to 0..1000000. |
required |
Source code in ansi_text_viewer/ansi_text_viewer.py
Fonts & zoom¶
Fonts default to the system fixed font at 10pt and ignore later
application-font changes (frozen for layout stability). setFontSize()
doubles as the zoom-reset size, setFontFamily() keeps the size, and
useSystemFont() takes a one-shot snapshot of QApplication.font().
Use a monospace font for the log view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
family
|
str | None
|
Font family name, or None for the system fixed font. |
None
|
pointSize
|
int
|
Point size, also stored as the zoom-reset size. |
10
|
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Set the point size, keeping it as the zoom-reset size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointSize
|
int
|
Clamped to 6..72; invalid input keeps the current size. |
required |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Set the font family, leaving the current size alone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
family
|
str
|
Installed family name; unknown names are ignored by Qt. |
required |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Adopt the global QApplication font as a one-shot snapshot.
Later application-font changes are not tracked; call again or use
:meth:setMonospaceFont to switch back.
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Layout, scroll and gutter¶
Word wrap is off by default (logs scroll sideways); auto-scroll pins the
bottom on new output; the gutter shows line numbers with bookmark ticks.
The lineNumberArea* methods and resizeEvent are Qt plumbing most apps
never call directly — listed here for completeness.
Toggle word wrap (off by default for log output).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
True wraps at the widget edge, False scrolls sideways. |
required |
Source code in ansi_text_viewer/ansi_text_viewer.py
Return the gutter width in pixels (0 when hidden).
Source code in ansi_text_viewer/ansi_text_viewer.py
Repaint or scroll the gutter after document updates.
Source code in ansi_text_viewer/ansi_text_viewer.py
Paint line numbers and bookmark markers in the gutter.
Source code in ansi_text_viewer/ansi_text_viewer.py
Timestamps and highlight toggles¶
setTimestampEnabled() prefixes new lines with the current time.
setLogLevelHighlighting() colors [ERROR]/[WARN]/[INFO]/[DEBUG]
markers; setSyntaxHighlighting() covers tracebacks, JSON keys, CMake and
justfiles (see Highlighting). Custom languages plug in
through add_highlight_rule() without touching the viewer.
Prefix each appended line with the current time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether to timestamp new lines. |
required |
fmt
|
str
|
|
'[%H:%M:%S]'
|
Source code in ansi_text_viewer/ansi_text_viewer.py
Colors are injectable: setLogLevelColors() replaces the level map used
for future highlights (read back with logLevelColors()).
Color [ERROR]/[WARN]/[INFO]/[DEBUG] markers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether to highlight newly appended blocks. |
required |
Source code in ansi_text_viewer/ansi_text_viewer.py
Override level-name to color mapping for future highlights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colors
|
dict
|
e.g. |
required |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Highlight tracebacks, JSON keys, CMake and justfile syntax.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether to highlight newly appended blocks. |
required |
Source code in ansi_text_viewer/ansi_text_viewer.py
Register a custom rule, applied after the built-in rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rule
|
HighlightRule
|
Any object with a |
required |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Current line¶
Highlights the cursor line, merged with — never replacing — search and
bookmark highlights. setExtraSelections() is the interception point the
search engine writes through; searchExtraSelections() reads back just
the search part.
Highlight the line under the text cursor.
Merged with search highlights, never replacing them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Whether to highlight the current line. |
required |
color
|
QColor | None
|
Background color, or None to keep the default. |
None
|
Source code in ansi_text_viewer/ansi_text_viewer.py
Links¶
https:// URLs and path/to/file.py:line references become clickable
anchors, opened with Ctrl + click (plain clicks just move the cursor —
single-click-open would let a hostile log line phish you). mousePressEvent
enforces the modifier, mouseMoveEvent shows the pointing hand, and
contextMenuEvent builds the right-click menu. Disable entirely with
setLinksEnabled(False) for untrusted logs.
Toggle clickable URLs and file paths (opened with Ctrl+Click).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
False also hardens the viewer against hostile logs. |
required |
Open links only on Ctrl+Click; plain clicks just move the cursor.
Source code in ansi_text_viewer/ansi_text_viewer.py
Build the right-click menu (copy, bookmarks, view, save).
Source code in ansi_text_viewer/ansi_text_viewer.py
Copy, export, sessions¶
copySelectedPlainText()andcopySelectedWithAnsi()(SGR codes reconstructed from the actual formats).exportToFile()writes.txt,.html, or.mdfrom the suffix.saveSession()/loadSession()persist text, bookmarks, and settings as JSON, with size and shape validation (10MB file / 5MB text caps).selectionStats()returnschars/words/linesfor status bars.
Return the selected text with paragraph separators as newlines.
Source code in ansi_text_viewer/ansi_text_viewer.py
Copy the selection as plain text.
Returns:
| Type | Description |
|---|---|
str
|
The copied text, or "" when nothing is selected. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Reconstruct ANSI SGR from fragment formats (true ANSI copy).
Source code in ansi_text_viewer/ansi_text_viewer.py
Copy the selection with ANSI color codes reconstructed.
Returns:
| Type | Description |
|---|---|
str
|
The copied text with SGR sequences, or "" when empty. |
Examples:
>>> viewer.setAnsiText("\x1b[31mred\x1b[0m\n")
... # select all, then:
>>> "31m" in viewer.selectedTextWithAnsi()
True
Source code in ansi_text_viewer/ansi_text_viewer.py
Save content to .txt, .html or .md based on suffix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Destination file path. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The path that was written. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Count the current selection.
Returns:
| Type | Description |
|---|---|
dict
|
Dict with |
Source code in ansi_text_viewer/ansi_text_viewer.py
Save text, bookmarks and settings as JSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Destination |
required |
Returns:
| Type | Description |
|---|---|
str
|
The path that was written. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Restore a session written by :meth:saveSession.
Files over 10MB or with invalid content are rejected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Source |
required |
Returns:
| Type | Description |
|---|---|
str
|
The path that was read. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the file is too large or invalid. |
Source code in ansi_text_viewer/ansi_text_viewer.py
1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 | |
Bookmarks¶
Bookmarks live on the text block itself, so trimming, filtering, or
clearing can never leave stale line numbers behind. Toggle via
toggleBookmark(), by clicking the gutter (toggleBookmarkAtY() refuses
invisible blocks), or from the context menu;
gotoNextBookmark() / gotoPrevBookmark() wrap around and skip
filtered-out lines. bookmarkedLines(), bookmarkedPreviews(), and the
bookmarksChanged signal feed list UIs like the demo's panel.
Bookmark or unbookmark a line.
Bookmarks live on the block itself, so trimming or clearing the document can never leave stale numbers behind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
blockNumber
|
int | None
|
0-based block, or None for the cursor line. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True when a bookmark was added, False when removed. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Toggle the bookmark on the visible block at gutter height y.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
int
|
Vertical position inside the gutter widget. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The toggled block number, or -1 when none is there. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Bookmark a 0-based block number (no-op when invalid).
Source code in ansi_text_viewer/ansi_text_viewer.py
Remove the bookmark on a 0-based block number.
Source code in ansi_text_viewer/ansi_text_viewer.py
Return sorted 0-based numbers of bookmarked blocks.
Source code in ansi_text_viewer/ansi_text_viewer.py
Return (block, first-80-chars) pairs for the bookmark list UI.
Source code in ansi_text_viewer/ansi_text_viewer.py
Remove every bookmark and notify listeners.
Source code in ansi_text_viewer/ansi_text_viewer.py
Jump to the next bookmark, wrapping around.
Filtered-out (invisible) bookmarks are skipped.
Returns:
| Type | Description |
|---|---|
int
|
The target block number, or -1 when none is reachable. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Jump to the previous bookmark, wrapping around.
Filtered-out (invisible) bookmarks are skipped.
Returns:
| Type | Description |
|---|---|
int
|
The target block number, or -1 when none is reachable. |
Source code in ansi_text_viewer/ansi_text_viewer.py
Set the full-width background of bookmarked lines.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color
|
QColor
|
Background color (translucency recommended). |
required |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Theme & colors¶
setTheme("light"), "dark", or "auto" (follows the OS and live-updates
on system changes). Unstyled text colors follow the theme — black on white
in light mode, #d4d4d4 on #1e1e1e in dark — so plain logs always match
the background instead of showing terminal-white pills. Explicit SGR colors
are never touched.
viewer.setTheme("auto")
viewer.setDefaultColors(QColor(0, 0, 0), QColor(255, 255, 255)) # manual
viewer.setColorsFollowTheme(True) # back to automatic
Apply the widget theme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
str
|
|
'light'
|
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Fix the SGR-reset text colors instead of following the theme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fg
|
QColor
|
Default foreground for unstyled text. |
required |
bg
|
QColor
|
Default background for unstyled text. |
required |
Examples:
Source code in ansi_text_viewer/ansi_text_viewer.py
Follow the theme for default text colors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
True re-applies the current theme's pair at once. |
required |
Source code in ansi_text_viewer/ansi_text_viewer.py
Override one of the eight ANSI base colors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
idx
|
int
|
Base color 0..7 (black, red, green, yellow, blue, magenta, cyan, white). |
required |
color
|
QColor
|
Replacement color. |
required |
bright
|
bool
|
Whether it applies to the bright (90-97) variant. |
False
|
Examples: