Contour 0.8.0 turns the shell's command history into something you can see and act on:
command blocks with exit-status marks, output folding, a gutter with line numbers and
timestamps, a sticky command header and finish notifications, driven by OSC 133 or, on
recent systemd, by OSC 3008 context tracking with no setup at all. It detects URLs and file
paths in terminal output and opens them with Ctrl+click, replaces the search prompt with a
GUI-native find bar, and adds kitty graphics animation frames. Terminal output throughput
is up several-fold, reflow and resize keep text and the cursor where they belong, and a
broad round of fixes covers Windows, Flatpak, daemon mode, SSH and the settings page.
Tabs, splits and layouts
- Adds
tab_color to profiles, giving newly opened tabs a default color while preserving layout, application and user overrides (#2152)
- Fixes closing a pane or tab keeping its session — terminal, scrollback and threads — in memory until Contour exited. A program that ignores the terminal's hangup signal is now ended 200 ms after its pane closes, as WezTerm does, and a tab no longer leaks the picture of itself it takes for the drag cursor when its window closes mid-grab
Window and desktop integration
- Adds
remember_window_position to restore the first window’s position and monitor at startup without saving its size (#2164).
- Adds the global option
snap_to_rows to align window heights to complete terminal rows, avoiding unused space below a bottom status line. Available in Settings and disabled by default (#2158).
- Adds notification_close_timeout, for the one thing the notification portal cannot do: tell an application that its notification was dismissed. Where a close cannot be observed, Contour now assumes one after this long (10 seconds by default) and reports it back over OSC 99 marked `untracked`, so an application that asked for close events with `c=1` is answered instead of waiting forever, and can still tell a guess from an observation. A notification that states its own lifetime with `w=` is answered on that instead, and 0 disables the assumption entirely
- Extends OSC 9;4 progress indicators to the Windows taskbar, KDE Plasma launcher and macOS Dock, including while minimized, using combined progress from tabs and split panes.
- Changes the indicator status line to span the window's whole width: its bar reaches the window's left and right edges past the gutter and the page margins beside it, but stays its own rows' height, and its text is laid out across the gutter's columns as well as the page's, so its middle sits in the bar's centre. The host-writable status line keeps the page's width (#2139)
- Changes ending a shell within
early_exit_threshold with a successful exit status — exit or Ctrl+D — to close the window as at any other time; the "Shell terminated too quickly" notice and its key press remain for a shell that fails, is killed, or whose exit status is unknown
- Changes OSC 777 and OSC 9 notifications to show only while their pane is not the focused one, as foot and Windows Terminal do, and OSC 99's o=invisible to mean while the pane is off screen: it acted as o=unfocused
- Fixes Windows font metrics after moving between monitors with different DPI scaling, maximized windows not filling the usable area, and resize handles remaining active while maximized or fullscreen (#2164).
- Fixes the Windows custom window frame growing the window and leaving a gap below the status line at startup: the client size is now preserved when the frame is applied (#2132)
- Fixes status-line
{Command} items opening console windows on Windows and slowing down rendering: commands now run in the background at a configurable Interval= (default 10 seconds), with a 5-second timeout, and accept the {Command:<command>} shorthand (#2135)
- Fixes clicking an OSC 99 notification that asks for focus only raising its window: it now brings its tab and pane forward, and so does clicking a tray message off Linux. Off Linux, a notification from a pane in a background tab was dropped. In a contour client tab an application's o=always or o=invisible was lost, and OSC 99's lists of pending and shown notifications grew without limit
- Fixes desktop notifications never appearing in the Flatpak build. Contour asked org.freedesktop.Notifications for them directly, which a sandbox does not permit and which Flathub declines to grant a permission for; a sandboxed Contour now speaks the org.freedesktop.portal.Notification portal instead, which needs no permission at all. Outside a sandbox nothing changes — the session bus is still used, and still reports a dismissal exactly as before
- Fixes the window freezing for up to 25 seconds when a hyperlink, folder or configuration file is opened while the desktop's xdg-desktop-portal is not answering. Qt reaches the portal with a blocking call on the GUI thread, and since Qt 6.10 it takes that path whether or not Contour is sandboxed; Contour now speaks the portal itself, asynchronously, and falls back to xdg-open if the portal refuses. Where opening still fails, the log now says why instead of only that it did (#2075)
Command palette, settings and menus
- Adds a message to the settings page that says why it refused a value or could not save a change, which screen readers announce
- Fixes a profile the settings page saves cutting a hint pattern's name short at " #", or no longer loading when the name holds ": "; and saving a color scheme that sets a context tint failing
- Fixes the Rendering GPU setting never applying, while the settings page showed it as set, when settings.yml held a hand-edited renderer: value
- Fixes the settings page's grapheme clustering and text scaling settings being saved but never applied
- Fixes number fields on the settings page storing another number than the one shown or typed: in a locale that writes a decimal comma, leaving a field that showed 0.5, such as Background opacity, stored 5, and a whole number typed with the locale's thousands separator, such as 1,000, was stored as 1. A decimal field now takes a decimal point or a comma whatever the locale, and a whole-number field takes no thousands separator
- Fixes a malformed entry in settings.yml or in a GUI-written profile being reported without the file's name
- Fixes a settings.yml or a GUI-written profile file that holds a single word aborting the load of the whole configuration. Such a file is now skipped and reported, as one that does not parse already was
- Fixes a settings field, switch or menu whose change was refused, or could not be saved, going on showing the refused value instead of the stored one
- Fixes the settings page writing an override of a global setting's value already in effect, and offering a Reset for no change, when its field was left unchanged or its menu's shown entry was picked again. Escape in a field now puts the stored value back: it kept the typed text, which the next focus change saved
- Fixes the settings page accepting, and saving, a PTY read buffer size of zero or less, a negative number of recent commands and a negative interface font size. It now refuses them and says why
- Fixes the settings page's Keybindings list showing only the first action of a binding that runs several, and leaving out the built-in bindings that contour.yml leaves free, such as Ctrl+Tab and Ctrl+PageUp/PageDown switching tabs and right-click opening the context menu. They are now listed after the configured ones, marked built-in
Hyperlinks, file paths and hint mode
- Adds detection of URLs, file paths (with their
:line:column) and patterns of your own in terminal output: they are underlined like OSC 8 hyperlinks once output goes quiet and open with Ctrl+click, the context menu or vi's gx, and ]x / [x move between them. Detection runs off the parser and its file checks on a thread of their own
- Adds
link_detection to choose, per screen, whether URLs and file paths in output are detected; file paths are no longer detected on the alternate screen by default.
- Adds
hyperlink_decoration.mode (always, hover, modifier, never) and makes hyperlink_decoration.normal and .hover take effect, for OSC 8 and detected links alike
- Adds
file_opener, a command template with {path}, {line} and {column} for opening detected files, and clickable and url keys for hint_patterns; all of them are editable on the settings page
- Changes hint mode's Open on a match of your own pattern whose text names no URL scheme to open nothing, as a link opens only a scheme Contour knows; give the pattern
url: 'file://{0}' to open the absolute path it matches
- Fixes hint mode freezing on long words, ending wide-character URLs early, misplacing highlights after combining characters and cutting parenthesised URLs short
- Fixes a file:// link's tooltip and its click naming different files: the tooltip kept a #L10 fragment that the click dropped, and file://C:/dir was opened as the network path //c/dir
- Fixes Ctrl+click and hint mode resolving a relative path against a working directory that another machine reported (file://otherhost/…), opening this machine's file of the same name; in a contour client pane mirroring another machine's daemon, Ctrl+click on a file path and file:// links opened this machine's file too
- Fixes the status line's context breadcrumb and the hyperlink tooltip showing a right-to-left override or a control character a program sent as it was, which could reverse the rest of the status line or the link shown
- Fixes a link staying underlined, with its tooltip up, after the pointer left the grid for the gutter or the window's edge
- Fixes FollowHyperlink and OpenSelection launching what they open while holding the terminal's lock, so a slow launcher froze the terminal's output
Scrolling, search and selection
- Adds search_case_sensitivity (smart, insensitive, sensitive), which the find bar's Aa button also cycles at runtime. Smart, the default, is case-insensitive until the term contains a capital, and it is Unicode-aware, so it works for non-Latin scripts (#1410)
- Replaces the status-line search prompt with a GUI-native find bar floating over the pane: a real text field, a live match count, previous/next, and a case toggle. Opened by Ctrl+Shift+F, or by / in Vi normal mode; neither switches the Vi mode any more. Under ui_style: terminal the same bar is drawn as a TUI — square, cell-quantized, in the terminal's own font (#2081)
- Changes the default
auto_scroll_on_update from true to false, as in VTE, ghostty, WezTerm and Windows Terminal: a view scrolled back into the history stays there until you type. A configuration file that spells the option out keeps its value
- Deprecates search_mode_switch, which chose the Vi mode to return to after a search. Entering a search no longer changes the mode, so there is nothing left for it to select; the key is still accepted so existing configurations keep loading
- Changes {SearchPrompt} in the indicator status line to a read-only echo of the active search term, which no longer appears in the default status line
- Fixes a view scrolled back into the history being brought down to the bottom about once a second with
auto_scroll_on_update on, while a shell that redraws its prompt in place on a timer sat idle: only output that scrolls lines into the history now brings the view down, as in ghostty
- Fixes search highlighting disagreeing with search matching for any non-ASCII term: the highlighter asked std::isupper, which is undefined above U+00FF, and asked it of the term's remaining suffix rather than the whole term, so "Foo" matched its tail case-insensitively
- Fixes being unable to edit a restored search term, and pasting into the search prompt corrupting it — both consequences of the hand-rolled line editor the find bar replaces (#1353)
- Fixes search and selection around double-width characters: a search missed a match running on past one, the highlight did not paint a match across one or across a soft wrap, and a double-click selected only part of a word holding one. A double-click also joined words across a hard line break, or took in the next word after a word wrapped onto a third row; vi's * and # had the same faults
- Fixes a fast mouse click starting a drag selection from a stale position instead of where the button actually went down, making the selected region appear to jump the moment the drag started. A slow, deliberate drag never showed this, because the pointer's tracked position was refreshed by the preceding movement; a quick click could arrive with no such movement right before it
- Fixes dragging out an ordinary text selection silently overwriting the active search term, so the selected text appeared as a live search wherever a status line or the find bar echoes it. Only a double or triple click's word selection is meant to do that
Paste, buffer capture and permissions
- Fixes permissions.capture_buffer never being consulted: a profile saying deny still raised the dialog and let the capture through once approved, and one saying allow was still interrupted by it. Both now decide the request outright, and with ask, "Yes to all" / "No to all" is remembered for the rest of the session instead of being stored and ignored (#2089)
- Fixes "Yes to all" on a larger-than-512-KB paste not being remembered either, so every such paste asked again
- Fixes a refused buffer capture leaving the requesting program hanging. The reply always ends with an empty PM 314 chunk, and a refusal sent nothing at all — so a client reading until that marker waited forever. A denial now sends the terminator, and so does a capture asked of a background pane
- Fixes a second buffer-capture request arriving before the first was answered being dropped, which left that request unanswered; requests are now queued and each one gets its own reply
- Fixes a larger-than-512-KB paste bypassing the newline normalization every smaller paste gets, so a CRLF payload arrived with carriage returns that a shell reads as Enter — under bracketed paste that executed the pasted lines instead of inserting them. Such a paste also honours the repeat count and whitespace-stripping it was asked for, and is no longer re-read from the clipboard when approved, so what is pasted is what was measured and shown rather than whatever was copied while the dialog was up
- Fixes pasted text ending a bracketed paste early with an embedded ESC [ 201 ~, or its 8-bit form, so that the rest reached the shell as typed keys: copying a command's output and pasting it back was enough to run what it held. ESC and C1 controls are now dropped inside a bracketed paste, and hint mode's Paste goes the same way
Daemon mode and remote sessions
- Changes an SSH profile that cannot connect to say so on the terminal, naming the host, the port and the reason. Every such failure — a name that does not resolve, a refused connection, an agent that is not there — used to be written only to the error log, which is off by default, so what appeared was the generic "Shell terminated too quickly." with nothing in it to act on (#2075)
- Changes an SSH profile under Flatpak that fails because the sandbox permits no network to say that, rather than reporting the host as unresolvable. Contour's SSH runs in-process rather than spawning ssh, so it is subject to the sandbox's own network permission; the message is chosen from what the sandbox reports it was granted, not guessed from how the call failed (#2075)
- Fixes `contour daemon` and `contour client` silently ignoring a profile's shell, escape_sandbox and presentation (cursor, status line, scrolling, colors) settings, hosting every session with the bare OS login shell and default presentation instead — even on the very first daemon spawn, not only when an already-running daemon was reused (#2092)
- Fixes
contour daemon not realizing a profile's configured default_layout when it starts: it now does, so the first contour client to attach sees the real layout instead of always a single pane — including each pane's own command, arguments and directory, not just its tab/pane shape. A pane's per-tab profile override is not honored on the daemon and is logged.
- Fixes contour daemon retrying forever, every 100 ms, once its listening socket was gone, serving nothing and saying only that an accept had failed. It now stops accepting there and logs why
- Fixes ClearHistoryAndReset, SoftReset and WriteScreen in a tab attached to contour daemon acting only on the client's copy of the screen, which the next update could undo. They now run on the daemon
- Fixes a daemon with more than one scrollback-heavy pane becoming permanently unattachable — `contour client` was refused before any screen content arrived, and every reconnect failed the same way. Attach snapshots are now paced against the connection's send queue and split across frames, so attaching succeeds whatever the scrollback depth; a window resize could drop an already-attached client for the same reason and no longer does
- Fixes `contour client` never finding the daemon under Flatpak. A sandboxed daemon bound its socket under the sandbox's own $XDG_RUNTIME_DIR — a directory on a private mount the host cannot reach at any path — so a shell that escaped the sandbox had nothing to attach to. It now binds under $XDG_RUNTIME_DIR/app/<app-id>, which flatpak shares with the host at the same path, and exports $CONTOUR_MUX into the shells it hosts (#2075)
- Fixes an SSH connection attempt skipping every other address a host name resolved to, so a host reachable only at its second address appeared to be down
- Fixes Contour hanging when an SSH handshake failed: the failure path took a lock the calling thread already held
- Fixes Contour spinning when the SSH agent could not be reached — the normal case inside a Flatpak, where $SSH_AUTH_SOCK names a host path the sandbox does not mount — instead of falling through to the next authentication method
Keyboard and mouse input
- Adds Ctrl+Shift+Up and Ctrl+Shift+Down to move between command prompts on the primary screen, as in Ghostty (ScrollMarkUp and ScrollMarkDown); a full-screen application keeps the chord
- Fixes AltGr characters arriving on Windows as control codes: on a Swedish layout AltGr+8, 9, + and 2 typed ESC, GS, FS and NUL instead of [ ] \ and @. Windows reports AltGr as Ctrl+Alt, and under the Win32 input mode ConPTY enables, the character was Ctrl-mapped as if Ctrl had been pressed. A Ctrl+Alt that produced a printable character is now treated as AltGr, while a genuine Ctrl+Alt chord is still reported as one (#2127)
- Fixes a genuine Ctrl+Alt+key on Windows losing both modifiers outside the Win32 input mode — the case for SSH profiles: Ctrl+Alt+A typed a bare "A" instead of ESC ^A, or CSI 97;7u under the kitty keyboard protocol, and a key binding on a Ctrl+Alt chord never fired. Every Ctrl+Alt used to be taken for AltGr; only one that produced a printable character is now
- Fixes a key binding for BACKSLASH firing on the x key
- Fixes Vi mode on a page under 17 rows scrolling with every motion — j bounced the view, and H and L landed on the wrong edge — because the 8-row scroll margin was never capped. It is now capped as vim caps scrolloff
- Fixes Vi mode's M landing one row above vim's middle row on a page of odd height, which could scroll the page by a row, and counting the blank rows that end a page collapsed folds leave short. It now lands where vim 9.1 does
Rendering, fonts and Unicode
- Adds built-in drawing for more Powerline separators, including inverse dividers, thin chevrons and diagonal lines (#2160).
- Adds U+25BC and U+25B6 to the built-in box-drawing renderer, so they no longer depend on the font
- Adds `renderer.gpu` and a "Rendering GPU" settings entry to choose which GPU renders the terminal (Linux and Windows). The default, `auto`, leaves the choice to the system as before. Vulkan and Direct3D honour it with Qt 6.10 or newer; OpenGL honours it on Linux only
- Changes the dotted underline to be drawn as evenly spaced round dots that continue across cells.
- Fixes macOS configured font fallback, collection-face selection, and recovery of missing glyphs, including statusline symbols that require a proportional system fallback.
- Fixes Windows ignoring the configured font fallback list and switching surrounding text to another font when a glyph was missing. DirectWrite now tries the configured fonts first and only replaces missing clusters. Also fixes undersized emoji and incorrect text/emoji presentation (#2140)
- Fixes a combining character, such as an accent, that arrives in a separate read after a run of plain text attaching to the first character of that run instead of the last
- Fixes a read that ends inside a multi-byte UTF-8 character, right after a control sequence or at the start of the read, showing a replacement character in front of it and leaving the cursor one cell too far right
- Fixes national flags after other text pairing their letters one off: a German and a US flag written after a space showed as D, EU and S
- Fixes a cluster that starts with a combining mark or a zero-width character losing its first cell when the next character joins it
- Fixes text in a script no installed font covers rendering at roughly 3 fps: every uncovered codepoint triggered a fontconfig sort of every installed font on the render thread, and every one of those sorts returned nothing. A single probe of the union of all installed charsets now answers the same question exactly, taking the median frame from 289 ms to 12.3 ms
Images and graphics
- Adds kitty graphics animation frames (
a=f) and frame selection (a=a), so applications can send only what changed between frames instead of re-sending the whole image. Self-running playback (s=2/s=3) and frame composition (a=c) are not supported yet (#2108)
- Fixes images.sixel_scrolling: false being lost once a program reset the terminal (RIS), and never applying in a session hosted by contour daemon
- Fixes the kitty graphics protocol's APC payload being parsed one byte at a time through the escape-sequence state machine, where the equivalent sixel payload has long had a bulk path. An image-heavy workload's time in the parser falls from 47% of its wall clock to 6.6% — 7.55 s to 1.06 s over 1.4 GB of payload
Shell integration
- Adds OSC 133;N (as ;A), ;I (as ;B, for input that ends with its line) and ;L (a fresh line), the right-prompt mark ;P;k=r, and kitty's cmdline= form of the command line. A shell integration announcing commands with ;N used to get no command blocks at all, and under Nushell a command line ended in the right prompt's text (#1010)
- Adds PowerShell shell integration: contour generate integration shell pwsh to FILE. Like VS Code's, it wraps PSReadLine's PSConsoleHostReadLine to report the command line (#1010)
- Changes the bundled bash, zsh and fish integrations to report where the prompt ends (OSC 133;B) and the command line (cmdline_url), and tcsh to report the prompt end; with fish 4.0 or later, which marks its prompts itself (A, C and D from 4.0.0, the command line from 4.0.1, the prompt end from 4.3.0), the script adds only what fish leaves out (#1010)
- Fixes a multi-line command split into one command block per line in Nushell 0.111 and later, which integrates natively and marks each continuation line with OSC 133;A;k=s: an A carrying k=s or k=c (a continuation prompt) or k=r (a right-hand prompt) is now read as part of the prompt in progress. Nushell never reports the command line, so Contour reads it off the screen (#1010)
- Fixes a failed command being recorded and reported as a success when its OSC 133;D carried more parameters after the exit code, as WezTerm's 133;D;1;aid=42 does: everything after D; was read as the exit code
- Fixes a command line sent as OSC 133;C;cmdline_url= being cut at a raw semicolon (kitty, too, reads it to the end of the sequence); and, with a shell that sends ;C but never ;D, the prompt after a command that printed nothing (cd, true) being taken for that command's output, so screen readers found no live prompt there
- Fixes the tcsh shell integration on macOS printing its prompt and command marks as literal text such as
\033]133;A\033\ rather than sending them: macOS's tcsh echo expands no escapes (echo_style bsd), so the terminal saw no prompts or command blocks there
- Fixes the bundled bash, zsh, fish and tcsh integrations sending the working directory (OSC 7) as a raw path: a directory whose name held an escape character could send the terminal sequences of its own, such as a new window title, and a script sourced over ssh claimed a local directory. They now send a file:// URL with the host name and a percent-encoded path, and fish 4.0 and later, which sends its own, no longer gets a second one
- Fixes the path of an OSC 7 file:// URL being taken as written: a directory a shell announced percent-encoded, as VTE's script, fish 4 and WezTerm do, showed as /tmp/a%20b in the tab title and hint mode, and on Windows a new tab could not open in it. A new window opened in the current directory also lost the part of its path after a # or ?
- Fixes the bash integration installing no hooks when another copy of bash-preexec had been loaded first, as atuin and starship load it, and its hooks being replaced by a user's own functions named precmd or preexec
- Fixes DEC mode 2034 semantic block queries reporting a command with another command's prompt and output, or at a fresh prompt the previous command's; a shell that enables the mode from its prompt hook never seeing the command typed at that prompt; a full reset (RIS) leaving the reader's session running; and a query of an unknown type being answered as one for the last blocks
Command blocks, folding and the gutter
- Adds output folding: collapse a finished command's output down to its prompt line and expand it again. Driven by the shell's OSC 133 marks, so any shell with shell integration works (#88)
- Adds a fold column in a one-column gutter left of the grid, marking each foldable block and highlighting under the pointer; clicking it, or the new context-menu entry, toggles that fold
- Reserves U+10F000..U+10F0FF for Contour's own glyphs and assigns the fold column's two heads, which no Unicode character describes; see docs/vt-extensions/private-use-glyphs.md
- Adds the global folding config section and the actions CollapseAllFolds, CollapseLastFold, ExpandAllFolds, ToggleFold and ToggleLastFold
- Adds a record of every command block — its command line, exit status, start time, duration and working directory — kept for the last command_blocks.max_records commands (1000 by default), even after their output has left the history (#1010)
- Adds exit-status coloring to the gutter's block column: a failed command's marks turn red and a running one's yellow (color scheme block_status), a running command shows ▸ and a finished one with no output ●, and hovering the column shows the exit code, duration, start time, directory and command line (#1010)
- Adds line numbers to the gutter, off by default: gutter.line_numbers absolute, relative or hybrid, as vim's nu and rnu. Relative distances count the lines you see, so with a block collapsed 12k still lands on the line labeled 12 (#1010)
- Adds per-line timestamps to the gutter (gutter.timestamps, gutter.timestamp_format): the time output first reached each line, at no memory cost per line (#1010)
- Adds a "⋯ 1,234 lines" label after a collapsed fold, and vim's fold keys za, zo, zc, zM and zR in Vi normal mode (#1010)
- Adds command marks on the scrollbar — failures, every command, and user marks (scrollbar.marks) — where clicking one scrolls to that command (#1010)
- Adds a sticky command header: scrolled back into a long output, the command that produced it stays pinned above it with its exit status and duration (sticky_header.mode), and once that command's own line has been evicted from the history it names the command instead, so the header never just disappears (sticky_header.show_evicted) (#1010)
- Adds actions on a command's output: Ctrl+triple-click selects it (SelectCommandBlock), CopyCommandBlock and CopyCommandLine copy its output or its command line (the latter safe to paste), Ctrl+Shift+G opens the last one in a pager (OpenCommandOutput, command_blocks.pager), ClearToPrompt clears everything above the current prompt, and CopySelection can fall back to the last command's output. Relative file paths in a command's output now open relative to the directory that command ran in. Ctrl+triple-click and Ctrl+Shift+G are built-in fallbacks, so they work even with a contour.yml that has its own input_mapping (#1010)
- Adds desktop notifications when a long command finishes where you cannot see it, on by default for commands of 10 seconds or more in an unfocused pane (notify_on_command_finish) (#1010)
- Adds command blocks to contour client sessions attached to a daemon: the gutter, folds, scrollbar marks, sticky header and finish notifications work there too, and reattaching never replays notifications for commands that finished while detached. OpenCommandOutput opens no pane there; bind it with placement: detached to pipe the output into a program (#1010)
- Adds the gutter, command-block and folding settings to the settings page. Each one overrides a single key of its section in settings.yml, and leaves the rest as contour.yml says (#1010)
- Adds a Command blocks group to a profile's page in the settings: scrollbar mark sources, the sticky command header and its evicted-command naming, and when, how and for which commands a finished command notifies (#1010)
- Adds the block-status colors (success, failure, running), the gutter-text color and the sticky-header colors to the color-scheme editor. An unedited slot keeps following the colors it is derived from and says so, and an edited one can be set to follow them again (#1010)
- Adds a menu for each command in the gutter: a click on a command's status mark, or a right-click anywhere beside its output, opens the context menu for that command, which can now also run it again (RerunCommand, shown and confirmed first) (#1010)
- Adds
gutter.block_column_width, 2 by default: the fold controls and command marks are drawn across two cells at twice the size, and a click anywhere in the column lands. A failed command shows a cross, or its fold drawn heavy; the fold under the pointer is drawn bolder (gutter.bold_on_hover) in colours a scheme can set per outcome (block_status_hover), editable on the settings page; and a click on a collapsed fold's "⋯ N lines" label expands it (#1010)
- Adds "Copy Last Command" to the terminal's context menu, copying the last finished command's command line without the prompt in front of it; "Copy Last Prompt" copies the prompt's lines, PS1 included
- Adds the profile option
copy_command_preserve_multiline to preserve line breaks and tabs when using Copy Command. Disabled by default.
- Adds history.hard_limit, which makes the scrollback evict a whole command at a time. Anything above history.limit is headroom the buffer may grow into; when it fills, entire (prompt, output) blocks are dropped from the top instead of a slice taken wherever the limit happened to fall, so the oldest line kept is a prompt with its command's output intact rather than an orphaned tail. Boundaries come from the shell's OSC 133 marks, so with no shell integration — or for a single command whose output is larger than the whole headroom — the buffer simply bounds at hard_limit and evicts line by line as before. Defaults to history.limit, which is exactly today's behaviour (#836)
- Changes Vi normal mode to move in visible lines, stepping over collapsed blocks; folding.on_jump_into_fold picks whether a search hit inside one expands it or is skipped
- Changes Vi normal mode's mm to set a user mark rather than a prompt mark: it is drawn as ◆ in the gutter and as a tick on the scrollbar, and no longer counts as a command boundary for folding or for history.hard_limit's eviction of whole commands, while [m, ]m and the im/am text objects still visit both kinds (#1010)
- Changes the default scrollbar.position from Hidden to Right, so the command marks on the scrollbar are visible from the start. The scrollbar floats over the terminal's edge without taking a column, appears only while there is history to scroll through, and hides on the alternate screen unless hide_in_alt_screen is turned off. The new default applies to new configurations: a contour.yml written by an earlier version keeps scrollbar.position: Hidden, because Contour writes out the full default configuration on first run, so set position: Right in the profile's scrollbar section, or pick right for "Scrollbar position" on the settings page, to see the marks (#1010)
- Fixes stale fold ranges after a command finished: OSC 133's ;B and ;D wrote their line flag without invalidating the ranges derived from it, so anything reading them between the end of a command and the next prompt saw the state from before
- Fixes CopyLastCommandOutput on output of only blank lines putting a bare newline on the clipboard
Context tracking (OSC 3008)
- Adds support for OSC 3008, systemd's hierarchical context signalling (UAPI.15): the nested stack of contexts a shell, run0, ssh or a container runtime opens around whatever runs inside it. systemd 258 and later emit these around every interactive command with no configuration at all, so on such a system Contour learns command boundaries, exit statuses and working directories for free (#2078)
- Adds the {Context} indicator status-line placeholder: a breadcrumb of the contexts above the one you are typing in. It shows only privilege and machine boundaries by default and draws nothing at all when there are none, so an ordinary shell session is unchanged
- Adds an optional tint map to color schemes, giving the page background a different shade under an elevated, containerised, virtualised or remote context. No shipped scheme sets one, deliberately: every field OSC 3008 carries is a display hint any program on the terminal can write, so the colour is something you opt into rather than something that appears
- Adds fold controls, mark-jump stops and "copy last command output" to prompts without any setup on a system where OSC 3008 arrives and no shell integration is installed. A shell integration, if one is installed, keeps full ownership of the markers and nothing changes
- Adds reporting of how a command really ended: OSC 3008 distinguishes a crash from an interruption from a plain non-zero exit, and names the signal — which OSC 133's numeric exit code cannot express
- Changes the tab tooltip and "Open Current Folder" to follow the working directory OSC 3008 reports. A directory belonging to a container, a VM or another machine is never opened locally — the menu entry greys out rather than opening the host's directory of the same name. Set osc_context.enabled to false to opt out of all of it
- Adds the same context tracking, tinting and breadcrumb to a contour attach pane that a local one gets
New VT sequences and protocols
- Removes the deprecated SETMARK sequence (CSI > M); use OSC 133 ; A instead
- Adds DECSWBV (CSI Ps SP t), the VT520 sequence for setting the warning-bell volume: Ps 1 silences the bell, 2 to 4 halves it, 0, an omitted Ps or 5 to 8 keeps it at the profile's configured volume. An application can only mute or halve that volume, never raise it, and RIS restores the default (#732)
- Adds DECSMBV (CSI Ps SP u), the VT520 sequence for setting the margin-bell volume: Ps 0, 1 or an omitted Ps silences it (the default, per the VT510 manual), 2 to 4 halves it, 5 to 8 sets it to high, and RIS restores the default. Contour does not yet ring the margin bell itself — DEC private mode 44 remains unimplemented — so for now this only stores the value for when it does (#733)
VT conformance and correctness fixes
- Fixes a single shift (SS2 or SS3) being used up before a character that the shifted-in soft font (DRCS) has no glyph for, which then came out of the wrong character set
- Fixes resizing the window at a shell prompt leaving doubled fragments of the prompt on screen (narrower) or erasing the output line above it (wider): Contour reflowed the prompt the shell was about to redraw. The live prompt's rows are now cut or padded instead, as kitty does, and a prompt sent with OSC 133;A;redraw=0 is reflowed as before
- Fixes narrowing the window leaving the cursor where its text used to be, so input typed at a prompt afterwards landed past the prompt's end or over its last character. The cursor, and the cursor DECSC saved, now keep their place in their line across a reflow, as in foot, kitty and WezTerm
- Fixes a reflow changing the text it moves: blanks inside a wrapped line were dropped, a double-width character could be split across two rows, a block of OSC 66 text taller than one row was torn apart, and a line written with text reflow off (DECRST 2028) was reflowed anyway
- Fixes a double-width character arriving in the last column being written with half of it off the page, after which a widening dropped the character that followed it (ABCDEFGHI中X read back without the X). It now moves to the next row, as in xterm, foot and kitty
- Fixes a resize that changes only the height dropping a pending wrap, so the next character overwrote the last cell of the row; and a narrowing pushing a prompt on the top row of an otherwise empty page into the history as soon as it wrapped
- Fixes a full-page scroll down (SD, or a reverse index on the top row) erasing the newest lines of the scrollback and moving the lines pushed off the bottom into its oldest slots. The scrollback now stays as it was, as in xterm, Windows Terminal, kitty and WezTerm
- Fixes ED keeping a double-width or double-height cursor row at its size after erasing all of it, where DEC STD 070 resets it to single size; and EL 0 and ED 1 keeping two rows joined after erasing what joined them, so the next reflow or copy ran two unrelated lines together
- Fixes clearing the scrollback (ED 3) leaving the view scrolled to, or the vi cursor on, rows that no longer exist, and a hint session open over the history matching them
- Fixes writing to the host-writable status line after a narrowing resize writing past the end of its row, out of bounds in release builds
- Fixes a DEC macro that invokes itself hanging the terminal, and a macro body larger than the free input buffer overflowing it
- Fixes DEC macros (DECDMAC, DECINVM) to follow DEC STD 070: a macro runs where it is invoked, as if its bytes had come from the host, rather than after the rest of the output; the definitions share 6 KiB of macro space, whose room left DECMSR now reports (it said 0), and DECCKSR reports their checksum (it said 0000); a definition with a parameter out of range or a body that is not hex, or one made inside a running macro, is ignored whole; a body keeps no control characters; and the hex repeat sequence ! Pn ; … ; is understood
- Fixes a full reset (RIS) keeping the macros, the user-defined keys and the soft character set while a soft reset (DECSTR) cleared them, the wrong way round by DEC STD 070, the VT520 manual, xterm and Windows Terminal. A RIS inside a running macro now ends it, and DECMSR and DECCKSR are answered from VT420 on only, as in xterm
- Fixes VT screenshots (ScreenshotVT): a wide character replayed one column too far right, the line break after the last row scrolled the first row away on replay, OSC 66 text came back at ordinary size, and screenshot.vt drew from wherever the replaying terminal's cursor stood, in its colors and modes. The file now starts from a reset, cleared page
- Fixes XTGETTCAP replies spelling a byte past ASCII, such as an 8-bit CSI, as FFFFFF9B instead of 9B
- Fixes narrowing the window moving the cursor to the right: each column step doubled its column until it was pinned at the right edge, where anything printed before the application next moved it landed. Released versions showed this only with reflow_on_resize off
Stability, performance and startup
- Improves terminal output throughput, fed from memory: plain text 166.6 → 310.9 MiB/s, coloured text 42.7 → 281.9 MiB/s, full-screen applications on the alternate screen 55.6 → 593.2 MiB/s. Lines with mixed colours and the alternate screen now take the bulk-text path, plain-text stretches inside non-ASCII text are written in bulk too, scrollback lines that scroll out are reused instead of reallocated, escape-sequence parameters are scanned in bulk, and each sequence is looked up through an index instead of a binary search
- Improves reading from the terminal's PTY: the parser keeps reading while a frame is being built instead of waiting for it, and a burst of output schedules one redraw instead of one per read; plain text through a real PTY 69.8 → 104.2 MiB/s
- Changes the default
read_buffer_size from 16384 to 65536 bytes: the most read from the PTY and parsed in one go. Hosts that start sessions without a configuration file, such as contour daemon, move from 4096 to 65536 bytes
- Fixes Contour not exiting for up to a minute or more after its window closed on systems where opening a GPU device hangs (seen with nouveau): loading Qt Multimedia for the bell no longer probes the GPUs for hardware video decoding and encoding
- Fixes Vi mode's commands and mode switches changing the terminal from the GUI thread without its lock, racing with a program's output (a mode switch resized the page in the middle of it); a light/dark switch re-seeded the palette the same way
- Fixes Contour hanging while local echo was on (SRM reset): SendChars hung on every use, and with in-band resize reports (mode 2048) on as well, so did toggling the status line, the key maps or input protection, changing the font size or resizing the window, entering Vi normal mode and switching back to the tab. Each sent input while it held the terminal's lock, and the local echo of that input takes the lock again
- Fixes a reply that Contour raised from the GUI rather than from a program's output waiting for the next key press or output before it was sent, so a program blocked reading it could wait forever: the answer to an OSC 99 notification being closed or clicked, and the selection report of DECSET 2030 for SelectAll, CreateSelection or a drag that scrolls the view
- Fixes a function key from F6 to F20 occasionally crashing Contour while a program redefined the user-defined keys (DECUDK) or reset the terminal: the key read the table as output rewrote it. Typing and the key-binding lookup also read the keyboard lock (KAM), local echo (SRM), auto-repeat and the modes a binding matches on, such as application cursor keys, while output could change them
- Fixes an out-of-memory kill of any process running inside Contour, such as a compiler or a language server, closing Contour and every tab with it on desktops that start applications as systemd units. Each session now runs in a systemd scope of its own, so only the killed process ends. Building on Linux needs libsystemd unless configured with CONTOUR_WITH_SYSTEMD=OFF
- Fixes closing a terminal whose shell could not be started, which made Contour wait for any other process it had started to exit and, on builds with standard library assertions enabled, abort. A process that was never spawned held the id 0, which POSIX reads as the caller's whole process group
- Fixes a key press occasionally crashing the terminal, and a reply to the application occasionally losing its first bytes, which Neovim hit at startup: its DA1 query came back as just "2;314c" and was read as typed keys. Input flushes from the parser thread and the GUI thread could overlap, and each discarded the bytes both had written, so the next reply started that many bytes late; a key press shorter than that crashed. Flushes are now serialized
- Fixes a crash (debug builds) or out-of-bounds memory access (release builds) when long or wrapping output reached past the 80th column right after startup: startup widens the page before it makes it taller, and the lines the taller page took over from the unused scrollback kept their old, narrower width. Every line entering the page is now resized to the page width
- Fixes an application that opens a DECSET 2026 synchronized update and then stalls or dies before closing it freezing the display — not a slow frame, no frames at all. Rendering now resumes once a block has outlived 150 ms, with the mode register untouched so DECRQM still reports what the application set; on notcurses-demo the frame rate more than doubles, from 13.6 to 29.7 fps
- Fixes a column change — eight or nine of which Contour negotiates at startup, and one per column step of a window drag — costing time proportional to the configured history.limit rather than to what the scrollback actually holds. Reflow is now skipped where nothing is wrapped, and the remaining work walks only the rows in use, taking startup column work from 34.58 ms to 0.02 ms
- Fixes the scrollback being scrambled whenever the history limit changed, which happens on every profile apply and config reload. Growing it blanked the top of the scrollback and slid the real rows out of reach; shrinking it dropped the newest lines and kept the oldest
- Fixes the shell's output going to Contour's own standard output instead of the terminal on Windows whenever that was redirected: started from a script, under ctest, or as a daemon logging to a file
- Fixes program paths and arguments not being quoted on Windows: a shell under C:\Program Files\… ran C:\Program.exe wherever one existed, and arguments holding spaces, quotes or a trailing backslash were split, merged or lost
- Fixes a Windows pseudo console read ignoring its timeout and racing with the console closing, and a write racing with it too, so a tab closing mid-read could hang or write to a handle Windows had already reused. A failure to create or resize one now gives its real reason, where it gave none or an unrelated one
- Fixes names outside the ANSI code page on Windows: a shell, argument, environment value or working directory holding one was mangled (an argument ü€😀 arrived as ü€😀), Contour's own command line turned it into ? (contour -c C:\Users\李\x.yml), and the configuration under such a user profile was not found. Paths inside the configuration and layouts, screenshots, the daemon's socket and log file, SSH keys, known_hosts and ssh_config, and a new tab's working directory failed the same way, and renaming such a color scheme on the settings page saved it with the default colors. contour.exe now runs with UTF-8 as its process code page (Windows 10 1903 and later)
- Fixes starting a shell on Windows by writing its environment into Contour's own: a variable set for one tab leaked into every later one, another thread could read the child's values meanwhile, and a PATH longer than 1024 bytes was replaced instead of extended
- Fixes
contour terminal PROGRAM ARGS... — the documented way to run a command in a new window — reporting "Do not know what to do with `echo` will use it as a program" for any program named the way programs are normally named. Only a path that exists on disk counted as a program; the spawn itself resolves the name through PATH, and the check now asks the same question, so only a name that is nowhere is reported (#2102)
- Fixes the "Shell terminated too quickly." notice appearing twice for a command that exits before the first frame is drawn. Such an exit reaches the session from two directions — its exit watcher, and the display finding the device already closed — and only the paths below the notice were guarded against being run twice (#2102)
Configuration, packaging and build
- Changes the build to take the coroutine, networking and generic utility code from a verbatim copy of core-cpp, the library shared by the Contour Terminal projects, in vendor/core-cpp. It brings no new dependency, and with CONTOUR_USE_CPM=OFF it fetches nothing
- Changes the minimum libunicode version to 0.9.4, whose SIMD text scan no longer reads every block of text four times over and stops in front of ill-formed UTF-8, and whose grapheme check pays for flag bookkeeping only on flag letters
- Fixes configuration reloads: saving contour.yml with live_config, or ReloadConfig, reloaded one session only, and new tabs kept the old settings; a reload left a profile's changed colors, the status line's position and indicator, folding's auto_collapse_on_new_command and on_jump_into_fold, and the osc_context limits as they were; a full reset afterwards brought back the previous terminal_id's operating level, initial status line and cursor; and after ResetConfig no later reload read the user's file until Contour restarted
- Fixes status_line.sync_to_window_title and highlight_word_and_matches_on_double_click having no effect, and contour client starting every new daemon tab with the profile it had read at attach, even after a reload
- Fixes one configuration value of the wrong shape (a word where a section belongs, a list where a single value belongs) silently dropping everything read after it — other profiles, layouts and key bindings — or resetting its whole profile or color scheme. Now only that entry keeps its default, and the error log names it by its path, line and column, such as profiles.main.cursor. A color palette written as a list now loads
- Fixes a value of the wrong type inside contour.yml's renderer or folding section, such as enabled: maybe under folding, dropping everything read after it — profiles, layouts and key bindings. In settings.yml it dropped the renderer overrides read after it, the GPU choice among them. Now only that value keeps its default, and the error log names it by its key path, such as folding.enabled
- Fixes a configuration value holding an unterminated ${ loading with the text before it doubled: ab${x read as abab${x
- Fixes contour generate … to FILE writing Windows line endings on Windows, which broke a bash script in Git Bash, and printing nothing and exiting successfully when the file could not be written
- Fixes the build on Linux with Clang and libc++, which selected the broken <experimental/simd>. The guard excluded it by platform — Apple and FreeBSD — but the header belongs to libc++, not to those systems, so any libc++ build reached it; it is now excluded by _LIBCPP_VERSION instead (#2087)