module Tput::Probe

Overview

Runtime terminal probing.

Unlike Features/Emulator, which infer capabilities statically from ENV and terminfo, this module discovers them by writing a batch of query escape sequences to the terminal and reading the replies back (the same technique used by Microsoft's edit):

Results are stored on Tput#features (Features#default_foreground, #default_background, #palette, #ambiguous_width, #vs16_width, #da_params).

Included Modules

Direct including types

Defined in:

tput/probe.cr

Constant Summary

Log = ::Log.for((self.name.gsub("::", '.')).underscore)

Instance Method Summary

Instance Method Detail

def build_probe_cleanup : String #

Builds the post-probe cleanup string, run after all replies are consumed.

The width probe printed … at column 1 (#build_probe_query does \r…), so the leftover sits left of wherever the cursor was when probing started. A bare \e8\e[J erases only from the saved column rightward, leaving the … (and any echoed fragments) on screen when probing didn't begin at column 1.

So: \e8 restores to the DECSC-saved cursor line (even if echoed output wrapped it downward); \r parks at column 1 of that line; \e[J erases to the end of the display, wiping the … and any echoed fragments (including ones wrapped onto following lines); a final \e8 restores the exact pre-probe cursor position.


[View source]
def build_probe_query : String #

Builds the single batched query string. DECSC (\e7) saves the cursor up front so #probe! can restore it exactly and erase anything a non-conforming terminal echoed back. For the width probe, \r parks the cursor at column 1 so the CPR column maps directly to rendered width. DA1 goes last so its reply terminates the read.


[View source]
def probe!(timeout : Time::Span = 3.seconds) : Bool #

Probes the terminal for its features by round-tripping query sequences.

Returns true if at least the DA1 terminator came back (i.e. the terminal participated), false if probing was skipped or timed out with no response. timeout bounds the wait for each individual reply; a responsive terminal returns almost immediately thanks to the DA1 sentinel.


[View source]
def probe_capable? : Bool #

Whether probing is possible (both ends must be a real terminal).


[View source]
def probe_consume(io : IO, timeout : Time::Span) : ProbeResult #

Reads and parses the terminal's replies from io until DA1 arrives or a read times out. OSC color replies are applied to features as they come in; the ambiguous-width measurement and DA1 presence are returned.

Decoupled from @input so it can be exercised against an IO::Memory holding canned responses, without a real terminal.


[View source]
def probe_vs16_consume(io : IO, timeout : Time::Span) : Int32 | Nil #

Reads replies until the CPR, returning the measured width (the reported column minus one), or nil on timeout. Other sequences are skipped.


[View source]
def probe_vs16_width!(timeout : Time::Span = 1.second) : Int32 | Nil #

Measures the VS16 cluster's width again, on its own — for a caller that has since changed the terminal's state in a way that can change the answer. iTerm2, for one, advances two columns for 🖼️ on the normal buffer but one on the alternate screen, so a full-screen application must measure after switching buffers, where it will draw. Same mechanics as #probe! (raw input, one DSR/CPR round trip, cleanup), and the same rule: only while no other reader is on the input. Stores the result as Features#vs16_width and returns it; nil when probing is not possible or the terminal did not answer in timeout.


[View source]