Liking cljdoc? Tell your friends :D

Program API

The program module provides the main event loop and command system for TUI applications.

Running a Program

run

(program/run options)

Run a TUI program with the Elm Architecture pattern.

Options:

OptionTypeDefaultDescription
:initfn/valuerequiredInitial state or (fn [] [state cmd])
:updatefnrequired(fn [state msg] [new-state cmd])
:viewfnrequired(fn [state] string)
:alt-screenbooleanfalseUse alternate screen buffer
:mousekeywordnilMouse mode: nil, :normal, :cell, :all
:focus-reportingbooleanfalseReport focus in/out events
:fpsint60Frames per second
:hide-cursorbooleantrueHide terminal cursor

Example:

(program/run
  {:init {:count 0}
   :update (fn [state msg]
             (cond
               (msg/key-match? msg "q") [state program/quit-cmd]
               (msg/key-match? msg "up") [(update state :count inc) nil]
               :else [state nil]))
   :view (fn [state]
           (str "Count: " (:count state) "\nPress q to quit"))
   :alt-screen true})

run-async

(program/run-async options)

Run a TUI program in the background. Accepts the same options as run but returns immediately with a handle instead of blocking. Mainly for testing in the REPL.

Returns a map with:

KeyTypeDescription
:quit!fnCall to stop the program
:resultpromiseDeref to get the final state

Example:

;; Start the app in the background
(def app (program/run-async {:init init
                             :update update-fn
                             :view view
                             :alt-screen true}))

;; Stop it from another thread / REPL
((:quit! app))

;; Get the final state (blocks until the app has stopped)
@(:result app)

Commands

Commands are asynchronous operations that produce messages. They're returned from init and update functions.

cmd

(program/cmd f)

Create a command from a function that returns a message.

;; Command that sends a message after 1 second
(program/cmd (fn []
               (Thread/sleep 1000)
               {:type :timer-done}))

batch

(program/batch & cmds)

Combine multiple commands into one. All commands run in parallel.

(program/batch
  (program/cmd #(do-thing-1))
  (program/cmd #(do-thing-2))
  (program/cmd #(do-thing-3)))

sequence-cmds

(program/sequence-cmds & cmds)

Run commands in sequence (each waits for the previous to complete).

(program/sequence-cmds
  (program/cmd #(step-1))
  (program/cmd #(step-2))
  (program/cmd #(step-3)))

quit-cmd

program/quit-cmd

A pre-built command that exits the program.

(defn update-fn [state msg]
  (if (msg/key-match? msg "q")
    [state program/quit-cmd]
    [state nil]))

Init Function

The init option can be:

  1. A value - used as initial state, no startup command
{:init {:count 0}}
  1. A function returning [state cmd]
{:init (fn []
         (let [[t cmd] (timer/timer-init (timer/timer :timeout 5000))]
           [{:timer t} cmd]))}
  1. A function returning just state
{:init (fn [] {:count 0})}

Update Function

The update function receives the current state and a message, returning [new-state cmd].

(defn update-fn [state msg]
  (cond
    ;; Handle quit
    (msg/key-match? msg "q")
    [state program/quit-cmd]

    ;; Handle key press
    (msg/key-match? msg "up")
    [(update state :count inc) nil]

    ;; Handle custom message
    (= :data-loaded (:type msg))
    [(assoc state :data (:data msg)) nil]

    ;; Ignore unhandled messages
    :else
    [state nil]))

View Function

The view function receives state and returns a string to display.

(defn view [state]
  (str "Count: " (:count state) "\n"
       "Press up/down to change, q to quit"))

Mouse Modes

ModeDescription
nilNo mouse support
:normalBasic click events
:cellClick and drag events
:allAll mouse events including motion
(program/run {:init init
              :update update-fn
              :view view
              :mouse :normal})

Focus Reporting

When enabled, focus events are sent when the terminal gains/loses focus.

(program/run {:init init
              :update update-fn
              :view view
              :focus-reporting true})

;; In update function
(defn update-fn [state msg]
  (cond
    (msg/focus? msg)
    [(assoc state :focused true) nil]

    (msg/blur? msg)
    [(assoc state :focused false) nil]

    :else
    [state nil]))

Complete Example

(ns my-app
  (:require
   [charm.message :as msg]
   [charm.program :as program]))

(defn fetch-data-cmd []
  (program/cmd (fn []
                 ;; Simulate async data fetch
                 (Thread/sleep 1000)
                 {:type :data-loaded
                  :data ["Item 1" "Item 2" "Item 3"]})))

(defn init []
  [{:loading true :data nil}
   (fetch-data-cmd)])

(defn update-fn [state msg]
  (cond
    (msg/key-match? msg "q")
    [state program/quit-cmd]

    (= :data-loaded (:type msg))
    [(assoc state :loading false :data (:data msg)) nil]

    :else
    [state nil]))

(defn view [state]
  (if (:loading state)
    "Loading..."
    (str "Data:\n" (clojure.string/join "\n" (:data state))
         "\n\nPress q to quit")))

(defn -main [& _args]
  (program/run {:init init
                :update update-fn
                :view view
                :alt-screen true}))

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close