Typed queries
Use runQuery when a predicate needs inspection,
typed fields or relationship composition. It reads an existing
Snapshot and returns ordinary lists inside
Either QueryError. Native comprehensions and filter remain the unrestricted
path for application logic; see the capture program.
This program finds active panes at least 120 cells wide. It compares the typed selection with a native predicate over the same snapshot, checks uniqueness, follows session-to-link-to-pane relationships, and inspects the required fields. The fixture links one window at indices 0 and 4: one physical pane therefore appears twice in the contextual source.
Create the private demonstration server from the README. From a provisioned checkout, run:
$ cabal run --verbose=0 query-graph -- \ "$LIBTMUX_DEMO_DIR/s" \ "$(command -v tmux)"CI runs the installed program against an owned fixture, asserts output and
empty stderr, and observes the server and initial pane exiting afterward.
The program accepts an existing socket and tmux executable. Every
query construction and selection below is pure except the explicitly named
capture and fetch.
{-# LANGUAGE OverloadedRecordDot #-}{-# LANGUAGE OverloadedStrings #-}
-- | Query one detached graph, then distinguish physical and contextual matches.module Main (main) where
import Control.Exception (ExceptionWithContext, rethrowIO)import Data.Bifunctor (first)import Data.Function ((&))import Data.Text qualified as Textimport LibTmux qualified as Tmuximport LibTmux.Query qualified as Qimport LibTmux.Query.Link qualified as Linkimport LibTmux.Query.Occurrence qualified as Occurrenceimport LibTmux.Query.Pane qualified as Paneimport LibTmux.Query.Session qualified as Sessionimport System.Environment (getArgs)import System.Exit (die)import System.OsPath.Posix qualified as Path
data ApplicationError = OperationalFailure (ExceptionWithContext Tmux.TmuxException) | InvalidQuery Q.QueryError | InvalidSelection Q.CardinalityError | UnexpectedResult String
activeWide :: Q.Expr Tmux.PaneactiveWide = Q.allOf [Q.eq Pane.active True, Q.dimensionAtLeast Pane.width 120]
selectedPanes :: Q.Query Tmux.PaneselectedPanes = Q.panes & Q.matching activeWide
main :: IO ()main = getArgs >>= \case [socketArgument, executableArgument] -> do socket <- Path.encodeFS socketArgument executable <- Path.encodeFS executableArgument let config = (Tmux.serverConfig socket) {Tmux.executable = executable} acquired <- Tmux.tryTmux $ Tmux.withServer config $ \server -> do snapshot <- Tmux.capture server fresh <- Q.fetch server selectedPanes pure (snapshot, fresh) case first OperationalFailure acquired >>= uncurry report of Left (OperationalFailure problem) -> rethrowIO problem Left (InvalidQuery problem) -> die ("Query validation failed: " <> show problem) Left (InvalidSelection problem) -> die ("Selection failed: " <> show problem) Left (UnexpectedResult problem) -> die problem Right linesToPrint -> mapM_ putStrLn linesToPrint _ -> die "Usage: query-graph SOCKET TMUX_EXECUTABLE"
report :: Tmux.Snapshot -> Either Q.QueryError [Tmux.Pane] -> Either ApplicationError [String]report snapshot acquired = do selected <- first InvalidQuery (Q.runQuery snapshot selectedPanes) let native = [pane | pane <- Tmux.panes snapshot, pane.active, Tmux.dimensionValue pane.width >= 120] if selected == native then Right () else Left (UnexpectedResult "query and native selection disagree") unique <- first InvalidSelection (Q.exactlyOne selected) occurrences <- first InvalidQuery $ Q.runQuery snapshot $ Q.paneOccurrences & Q.matching (Q.related Occurrence.pane activeWide) highest <- first InvalidQuery $ Q.runQuery snapshot $ Q.windowLinks & Q.orderBy [Q.descending Link.index] & Q.limit 1 link <- first InvalidSelection (Q.exactlyOne highest) let containingEditors = Q.sessions & Q.matching (Q.some Session.windowLinks (Q.some Link.panes activeWide)) sessions <- first InvalidQuery (Q.runQuery snapshot containingEditors) fresh <- first InvalidQuery acquired Right [ "query agrees with native predicate: True" , "unique physical pane: " <> Text.unpack (Tmux.renderPaneId (Tmux.paneId unique.ref)) , "contextual matches: " <> show (length occurrences) , "contextual uniqueness: " <> either show (const "unique") (Q.exactlyOne occurrences) , "highest link index: " <> show (Tmux.windowIndexValue link.index) , "sessions containing a match: " <> show (length sessions) , "required fields: " <> show (Q.requirements selectedPanes).fieldNames , "required relationships: " <> show (Q.requirements containingEditors).relationshipNames , "fresh capture matches: " <> show (length fresh) ]Expected output:
query agrees with native predicate: Trueunique physical pane: %0contextual matches: 2contextual uniqueness: AtLeastTwoMatcheshighest link index: 4sessions containing a match: 1required fields: ["pane.active","pane.width"]required relationships: ["session.windowLinks","windowLink.panes"]fresh capture matches: 1Choose the source before checking cardinality¶
panes selects unique physical panes.
paneOccurrences selects their appearances through
window links. Filtering preserves the chosen source’s multiplicity.
exactlyOne rejects no matches and reports AtLeastTwoMatches at the second
match; it does not count the remaining list. atMostOne also permits absence.
Proving uniqueness may still require traversing every input element.
some tests one related child against its entire
predicate. Put all required conditions inside that child predicate when one
pane must satisfy them together. Two separate existential predicates may
match different panes. Over a known empty relation, some is false, every
is true and none is true. Missing required lookups remain errors.
optionalRelated is false for a known absent target; its negation includes
absence. An unavailable relationship is a validation error, even on an empty
source or in a branch that would otherwise short-circuit.
Inspect, order and limit¶
The qualified field modules expose typed tokens from the capture
catalogue. Boolean fields cannot take numeric comparisons, and fields from
different row types cannot be mixed. dimensionAtLeast accepts an unbounded
Integer threshold while observed dimensions retain their checked type.
Text equality and containment are exact, without case folding or Unicode
normalization. Command names, paths and window labels retain their declared
byte-preserving types; there is no implicit text conversion.
requirements reports stable source, field and
relationship names, ordering keys and the final limit. This is dependency
inspection, not serialization or a backend execution plan. Validation checks
all declared requirements before examining rows, including ordering keys,
empty membership predicates and zero limits.
matching accumulates predicates. orderBy replaces the ordering keys and
uses stable sorting; tied rows keep source order. limit replaces the final
nonnegative Natural bound. Evaluation always filters, orders and then
limits, regardless of builder order. Every authored description and operand
list must be finite.
Keep acquisition and failures explicit¶
fetch captures a new complete snapshot on every
call. It checks compiled evaluator support before acquisition, then validates
version requirements against captured metadata. This implementation performs
no backend filtering. Reuse runQuery over one snapshot when several
selections should share observations; see capture costs.
The example combines operational exceptions, pure query errors and
cardinality errors in an application error type. tryTmux catches library
exceptions only. Its handler preserves the exception context with rethrowIO,
keeping cleanup and capture recovery owners reachable; asynchronous
cancellation propagates. See recovery when an application needs
to inspect and resume those owners.
The report reads detached values after the Server closes. Its equality check uses one snapshot. The final fresh-capture count demonstrates another acquisition; two independently changing captures do not prove evaluator equivalence. Cardinality and reference equality establish observed selection, not current liveness. Public mutation, query interchange, backend optimization and event subscriptions remain unfinished.
For a separate application, copy the program and package file, then use its immutable source dependency with the installation instructions.