- Introduction
- Quick Start
-
Usage
- Launching a Fatty Terminal
-
The Callback API
append(text, follow: true)append_now(text, follow: true, mode: nil)markdown(text)status(text, role: :info)good(text, replace: false, render: true)info(text, replace: false, render: true)warn(text, replace: false, render: true)error(text, replace: false, render: true)oops(text, replace: false, render: true)alert(text, role: :info)prompt(prompt, initial: "", cancel_value: nil, history_key: nil, save_history: true)check_interrupt!-
add_progress(label:, total: nil, style: :percent, role: :info, width: 40) choose(prompt, choices:, initial_choice_idx: 0, cancel_value: nil)choose_multi(prompt, choices:, cancel_value: nil)confirm(prompt, yes_label: "Yes", no_label: "No", cancel_value: false)menu(prompt, choices:, initial_choice_idx: 0, cancel_value: nil)environment
- Default Interaction
- Configuration
Introduction
Fatty aims to provide a full-featured command-line environment that provides:
- an emacs-like editing command-line input, including undo/redo and kill/yank
- a history facility,
- command completion,
- completion of partially-typed file path names,
- output into a paging environment,
- searching within the paged output,
- issuing messages to a "status" area separate from the output pane,
- binding keys to pre-defined actions,
- defining unrecognized key-codes as named keys,
- color themes that can be selected in real time,
- a way to define new themes,
- processing completed command lines with a callback procedure of your choosing,
- several user-interface widgets such as selection popups, text prompts, menus, and progress indicators,
- a way to render markdown to the output pane,
In other words, fatty allows you to write a terminal-based REPL of your choosing but takes care of all the difficult parts.
Fatty is written entirely in Ruby and was born from my frustrations at the limitations of libraries like readline and reline. It uses curses and truecolor sequences for low-level rendering and is surprisingly snappy.
Fatty is not a terminal emulator but runs on top of one.
Quick Start
Installing
Fatty is a ruby gem, so it can be installed with
$ gem install fatty
Trying it Out with the `fatty` Demo
Once installed, you can try out fatty with the included program called fatty. It allows you to easily exercise all of the important features of the fatty library. At a normal shell prompt simply type fatty, and it will launch the fatty demo program.
Once inside fatty you will be prompted with a prompt that names your current directory. Type help to get a summary of the builtin commands available to you. If you type anything other than a builtin command, fatty attempts to run it as a shell command and displays the output.
Builtin commands
Here are the commands builtin to fatty
| Command | Description |
|---|---|
| help | Display this file on the output pane |
| cd | Change the current directory used by the shell |
| choose | Present a series of choices in a popup window |
| choosevals | Also present choices in a popup window but return an associated value |
| choose_multi | Present choices with a "checkbox" for selecting multiple values |
| choosevals_multi | Also present a checkbox but return associated values |
| menu | Present a menu of labeled routines to run |
| info | Display an "info" message on the status line |
| good | Display a "good" message colored to indicate success |
| warn | Display a "warn" message colored to indicate caution |
| oops | Display an "oops" message colored to indicate failure |
| interrupt | Test fatty's interrupt handling |
| prompt | Popup a text box for entering a value in response to a prompt |
| progress count |
Display an animated progress indicator counting up to 40 or the given N |
| progress percent |
Same but also show the percent complete |
| progress count_percent |
Same but show both count and the percent complete |
| progress trail | Show progress by displaying an "indicator" character for each step |
| progress bar | Show progress by a filling bar using ASCII characters |
| progress unicode_bar | Same, but using unicode characters |
| progress braille_bar | Same, but using braille characters |
| progress spinner | Animate a "spinner" showing a busy state |
| markdown <file.md> | Render the markdown file to the output pane; defaults to a demo file |
| keytest | Enter key diagnostic mode to report keycodes, key names, and bindings |
| colors | Display ANSI, 256-color, and X11 color diagnostics |
Screenshots
Command line editing
Here is the command line, mid-edit showing a region selected and, in dim text to the right, a predictive completion based on history.

Searchable Output
While paging output, you can search for words, as here the user searches for the word tty in the output. The current match is highlighted in yellow with other matches highlighted in gray. The paging status line shows the search term and the direction of search. The user can navigate for other matches in the output using n and N.

Paging Markdown
The fatty demo running the markdown command and paging the output. It shows fatty's ability to render markdown using ANSI codes, including colorizing code blocks, as well as its paging interface.

Popup Selection
One of the many "widgets" available through fatty is the ability to present the user with a set of choices to select from. After running the demo's choose "AAA" "BBB" "CCC" "DDD" "EEE"

Usage
Launching a Fatty Terminal
on_accept
You can launch a Fatty terminal session that does arbitrary processing of an edited command line and returns a String to be displayed on the output pane by providing an on_accept proc as a parameter as shown in this file that we'll call loudrev:
#! /usr/bin/env ruby
# -*- mode: ruby -*-
require 'fatty'
loud_reversal = lambda do |line|
line.upcase.reverse
end
Fatty::Terminal.new(
on_accept: loud_reversal,
).go
Now you have an interactive application that allows you to type text and see what it looks like when written backwards in uppercase letters. It's as easy as that!
When a fatty application runs, fatty installs a few files if they do not exist:
- a history file at
~/.fatty_history - a config directory at
~/.config/fattythat containsconfig.yml, the main configuration filekeydefs.yml, a file for associating keycodes emitted by the underlying terminal with names when curses does not do so automatically,keybindings.yml, a file for associating named keys with actions above and beyond the emacs bindings that are used by default, andthemes, a directory of pre-defined theme definitions that you can choose from and add to by adding your own themes.
Parameters to on_accept
The on_accept proc passed to an instance of Fatty::Terminal can take one or two parameters: (1) line, the edited line as it exists when the user types RETURN and (2) an optional callback parameter that you can use to access the facilities of fatty.
The line parameter to on_accept
Fatty does not parse command lines for your application. When the user accepts a line, Fatty passes the line to on_accept as text. It is up to your callback to decide whether to strip whitespace, split words, interpret quotes, parse options, recognize subcommands, or treat punctuation specially.
This keeps Fatty useful for many different kinds of REPLs. Some applications want shell-like parsing. Others want to preserve the user's input exactly.
For shell-like parsing, Ruby's standard Shellwords library is often useful:
#! /usr/bin/env ruby
# -*- mode: ruby -*-
require 'fatty'
require 'shellwords'
runner = lambda do |line, fatty|
words = Shellwords.split(line)
case words
when ["echo", *rest]
fatty.append("#{rest.join(" ")}\n")
when ["count", *rest]
fatty.append("#{rest.length}\n")
when []
fatty.status("Blank line", role: :info)
else
fatty.alert("Unknown command: #{words.first}", role: :warn)
end
rescue ArgumentError => e
fatty.alert("Could not parse line: #{e.}", role: :error)
end
Fatty::Terminal.new(
prompt: "shellwords> ",
on_accept: runner,
).go
The callback parameter to on_accept
The on_accept proc can have a second parameter named whatever you like that serves as a callback into certain facilities provided by fatty. In this README we use the name fatty for the callback parameter since it provides access to the facilities provided by the fatty library. Here is a variation of the prior example that also echoes the original typed string into the so-called status area:
#! /usr/bin/env ruby
# -*- mode: ruby -*-
require 'fatty'
loud_reversal = lambda do |line, fatty|
fatty.good(line)
line.upcase.reverse
end
Fatty::Terminal.new(
on_accept: loud_reversal,
).go
The callback parameter responds to several methods that allow your application to interact with the user. They are documented below.
Output ordering
The on_accept callback can send text to the output pane in three ways:
- by returning a value from the callback;
- by calling
appendormarkdownon the callback environment; or - by calling
append_nowon the callback environment.
A returned String is the simplest output mechanism. It is displayed after the callback finishes:
Fatty::Terminal.new(
on_accept: ->(line) { line.upcase.reverse },
).go
Calls to append and markdown also queue output until the callback finishes. If a callback both calls append or markdown and returns a String, the queued output is displayed first and the returned string is displayed after it.
Calls to append_now are different. They append text and immediately render a frame, so the user can see output while the callback is still running. Use append_now for long-running callbacks that should stream progress to the output pane.
At the end of the callback, Fatty finishes the command and updates the pager state. Output produced with append_now may therefore appear during the callback, while output from append, markdown, and the callback's return value appears after the callback returns.
Long-running callbacks and interruption
The on_accept callback runs synchronously. While it is running, control does not return to Fatty's normal event loop. A callback that performs lengthy work should therefore provide explicit safe points at which the user may interrupt it:
runner = lambda do |_line, fatty|
records.find_each do |record|
process(record)
fatty.check_interrupt!
end
fatty.good("Processing complete.")
end
The check_interrupt! method performs a throttled, nonblocking input check. If the user has typed C-c or C-g, it raises Fatty::Interrupt. Fatty catches that exception at the callback boundary, finishes the current command cleanly, and returns to the normal input loop.
Calls to check_interrupt! should be placed only at points where it is safe to stop the application work. For example, they can go between records, batches, files, or other complete units of work. Fatty does not interrupt callbacks asynchronously and does not stop threads or raise exceptions at arbitrary instructions.
Fast callbacks do not need to call check_interrupt!. Long-running callbacks that already update a shared progress facility can often place the check there so that individual commands do not need separate interruption logic.
Code that needs to report interruption before allowing Fatty to perform its normal cleanup may rescue and re-raise the exception:
begin
items.each do |item|
process(item)
fatty.check_interrupt!
end
rescue Fatty::Interrupt
fatty.warn("Processing interrupted.", replace: true)
raise
else
fatty.good("Processing complete.", replace: true)
end
ANSI colors in output
Text passed to append, append_now, status, and alert may contain ANSI SGR color/style sequences. Fatty interprets those sequences relative to the current theme role, so an ANSI reset returns to the active Fatty role rather than to the terminal's physical default colors. fatty includes the nice Rainbow gem for colorizing text as a convenience.
Other parameters to Terminal.new
When building a REPL with Terminal.new you can also supply several other parameters:
prompt:: aStringor a zero-argument callable such as aProcorlambdathat returns the prompt used for the input line to be edited. Because it can be a callable, it can change after each run ofon_accept; if not given, it defaults to '> ';app_name: the name for the "app" using thefattylibrary; this allows app-specific configuration under~/.config/fatty/apps/<app_name>or a different directory specified byapp_config_dir;app_config_dir: an alternative directory for app-specific configuration instead of the default;completion_proc: a callable that takes the current text of the input field's buffer and returns anArrayof possible completions at that point in the input; by default, there is no completion proc;history_path: aStringfor the file path where command history is loaded from and saved. The default,:default, useshistory.filefrom Fatty's configuration, falling back to~/.fatty_historyif no history file is configured. Passingnilorfalsedisables persistent history and keeps history in memory only. Passing a string uses that path directly.history_ctx: optional history context used to prefer relevant history entries. It may be a hash or a callable returning a hash. The context is stored in the history file with each accepted line and is used by history navigation and history autosuggestions. Matching context entries are preferred, but history is not strictly partitioned; Fatty can still fall back to other entries. This is useful when the same application wants different history behavior in different directories, ledgers, projects, modes, or accounts.
For example, the fatty demo executable uses the current working directory as history context:
Fatty::Terminal.new(
history_ctx: -> { { pwd: Dir.pwd } },
on_accept: ->(line, env) {
# ...
},
).go
With that setup, commands entered in ~/src/byr are favored when the terminal is again in ~/src/byr, while commands entered elsewhere remain available as fallback history.
The Callback API
Here are the details on the messages that you can send to the callback parameter to the on_accept proc.
append(text, follow: true)
Add the given text to the output pane. After a full page of output is produced, enter paging mode so the user can view the output at leisure and search the output. If follow is true, keep the output pane's viewport covering the last lines of output.
append_now(text, follow: true, mode: nil)
Like append, but display output as it is produced rather than wait for a full page to be produced.
Setting the mode to :scrolling or :paging determines whether the output is scrolled continuously or paused on each page of output.
markdown(text)
Render the given text as markdown source according to the current theme then append the result to the output pane.
We will not rehearse here how to compose markdown, but one thing that is a bit special with fatty is forcing a line break: you should use <br> to force a line break in the middle of a line.
This line has a forced line break <br> and this should appear on the next line.
will render as
This line has a forced line break
and this should appear on the next line.
status(text, role: :info)
Display the text in the "status" area, the lines immediately above the input field, using one of the following "roles":
- :good: colored to indicate something affirming according to the theme, usually something greenish;
- :info: colored something neutral simply for informational purposes;
- :warn: colored something to suggest caution, usually something in the orange to yellow range;
- :error: colored something to suggest danger, usually some tone of red.
good(text, replace: false, render: true)
Display the text in the status area with the role :good.
info(text, replace: false, render: true)
Display the text in the status area with the role :info.
warn(text, replace: false, render: true)
Display the text in the status area with the role :warn.
error(text, replace: false, render: true)
Display the text in the status area with the role :error.
oops(text, replace: false, render: true)
An alias for error(text)
alert(text, role: :info)
Display the text in the one-line alert panel just below the input field. In the alert panel, the role only controls the foreground color, not the background.
prompt(prompt, initial: "", cancel_value: nil, history_key: nil, save_history: true)
Display a popup dialog box that allows the user to type in any string and return it. Combine the input line with a prompted-for string and shuffle their letters together:
#! /usr/bin/env -S ruby -Ilib
# -*- mode: ruby -*-
require 'fatty'
weaver = lambda do |line, fatty|
str = fatty.prompt("Secondary string to weave in with '#{line}'")
(line + str).downcase.gsub(/[^a-z]/, '').split('').shuffle.join
end
Fatty::Terminal.new(
on_accept: weaver,
prompt: "Primary String> "
).go
You can pre-fill the prompt's input line by passing in an initial: parameter.
The prompt input has its own history facility separate from the history in the main input command line. You can turn it off by setting save_history to false.
prompt returns whatever the user typed and returns nil if the user cancels with C-c or C-g unless you specify an alternative cancel_value.
check_interrupt!
Provide a cooperative interruption point during a long-running on_accept callback. Fatty periodically performs a nonblocking input check and raises Fatty::Interrupt if the user has typed C-c or C-g.
The polling is throttled, so check_interrupt! may be called frequently. It should nevertheless be called only at points where the callback can safely stop its work.
Fatty catches Fatty::Interrupt at the callback boundary, finishes the current command, restores the terminal state, and resumes the normal input loop.
add_progress(label:, total: nil, style: :percent, role: :info, width: 40)
Display a progress widget in the status area to show the user that the system is working, not frozen. The add_progress method returns a Progress object on which the #update method can be called to cause it to animate one step. To the left of the widget the label: is displayed, followed by the widget. For processes where the total size is known in advance, the total: parameter indicates that size. The role: parameter controls the styling of the widget. The width: parameter places a limit on the size of the widget. Fatty supports several styles of progress widgets:
- :spinner: a simple busy-wait indicator that just animates on every call;
- :count: count up to
totalon each call ofupdate(current: <count>) - :percent: count up to percent of
totalon each call ofupdate(current: <count>) - :count_percent: count up and show percent of
totalon each call ofupdate(current: <count>) - :bar: display a filling ASCII progress bar with percent and count
- :unicode_bar: display a filling Unicode progress bar with percent and count
- :braille_bar: display a filling progress bar with percent and count using braille characters
- :trail: display an "indicator" on each call of
update(indicator: <string>)
Initialization add_progress(label:, style: :percent, total: nil, role: :info, width: 40)
label:: Text displayed before the progress widget, "Progress" by default.style:: The style of the progress widget from one of those named above.total:: For all styles except :trail and :spinner, the number that represents the size of the task. As the#updatecalls increase the value ofcurrenttowardstotal, the widget indicates increasing completion.role:: One of :good, :info, :warn, or :error to color the widget according to the theme's idea of these roles. By default, :info.width: The number of characters for the full widget display: it is only relevant for the bar styles and the trail style; otherwise it is ignored.
Update update(current: nil, indicator: nil, render: false)
You update the progress widget by calling #update on the Progress object, passing as the current: parameter a number that indicates progress so far.
#! /usr/bin/env -S ruby -Ilib
# -*- mode: ruby -*-
require 'fatty'
require 'prime'
primer = lambda do |input, fatty|
if input.match?(/\A[1-9]\d*\z/)
k = input.to_i
prog = fatty.add_progress(label: "Thinking...", total: k, style: :bar)
out_line = +""
num_primes = 0
Prime.each do |p|
num_primes += 1
out = "#{p} "
if out_line.length + out.length > 100
fatty.append_now("#{out_line}\n", mode: :scrolling)
out_line.clear
end
out_line << out
prog.update(current: num_primes)
break if num_primes >= k
end
fatty.append_now("#{out_line}\n", mode: :scrolling) unless out_line.empty?
prog.finish("Done")
else
fatty.alert "Give me a positive integer", role: :error
end
end
Fatty::Terminal.new(
on_accept: primer,
prompt: 'How many primes? ',
).go
For the trail-style progress, you can use an arbitrary string to indicate progress by adding an indicator: parameter to the #update call.
Suppose, for example, you wanted to print a trail in which the last digit of the prime is displayed to indicate progress and colored so it stands out:
#! /usr/bin/env -S ruby -Ilib
# -*- mode: ruby -*-
require 'fatty'
require 'prime'
require "rainbow/refinement"
using Rainbow
primer = lambda do |input, fatty|
if input.match?(/\A[1-9]\d*\z/)
k = input.to_i
prog = fatty.add_progress(label: "Thinking...", total: k, style: :trail, width: 100)
out_line = +""
num_primes = 0
Prime.each do |p|
num_primes += 1
out = "#{p} "
if out_line.length + out.length > 100
fatty.append_now("#{out_line}\n", mode: :scrolling)
out_line.clear
end
out_line << out
last_dig = p.to_s.split('').last
sig =
case last_dig
when '1'
last_dig.red
when '3'
last_dig.blue
when '7'
last_dig.green
when '9'
last_dig.yellow
else
'!'
end
prog.update(current: num_primes, indicator: sig)
break if num_primes >= k
end
fatty.append_now("#{out_line}\n", mode: :scrolling) unless out_line.empty?
prog.finish("Done")
else
fatty.alert "Give me a positive integer", role: :error
end
end
Fatty::Terminal.new(
on_accept: primer,
prompt: 'How many primes? ',
).go
Finish
As the prior examples illustrate, you can issue an ending message at the end of the process by calling #finish on the Progress object.
Clear
And, if you have occasion, you can clear the Progress by calling #clear on the Progress object.
choose(prompt, choices:, initial_choice_idx: 0, cancel_value: nil)
Present a set of choices:, which can be either:
- an
ArrayofStringchoices, where the selected string is returned; or - a
Hashwhose keys are converted toStringlabels and whose values are returned.
choose returns:
- the selected
Stringwhenchoices:is anArray; - the associated value when
choices:is aHash; or cancel_valuewhen the user cancels with C-c or C-g.
The prompt String (by default "Choose") can guide the user about the purpose of the choices.
If you want to set one of the choices as the initial choice, set initial_choice_idx: to the Integer index of one of the choices.
If you want a value associated with the user's cancellation of the chooser with C-c or C-g, set cancel_value: to that value.
With an Array of Strings:
#! /usr/bin/env -S ruby -Ilib
# -*- mode: ruby -*-
require 'fatty'
rgb = lambda do |_line, fatty|
color = fatty.choose(
"Choose a color",
choices: ["red", "green", "blue"],
)
fatty.status("If you say so: #{Rainbow(color).send(color.to_sym)} it is!\n")
end
Fatty::Terminal.new(
prompt: "Hit RETURN to choose a color> ",
on_accept: rgb,
).go
Or with a Hash:
#! /usr/bin/env -S ruby -Ilib
# -*- mode: ruby -*-
require 'fatty'
nums = lambda do |_line, fatty|
const = fatty.choose(
"Choose a constant",
choices: { "Pi" => 3.14159, "Euler" => 2.718281828, "Golden Ratio" => 1.61803398875 }
)
fatty.status("Somewhere around #{const}\n")
end
Fatty::Terminal.new(
prompt: "Hit RETURN to choose a constant> ",
on_accept: nums,
).go
choose_multi(prompt, choices:, cancel_value: nil)
Present a set of choices:, which can either be
- an Array whose items are converted to
Stringsand presented as choices - a Hash whose keys converted to
Stringsand presented as choices
choose_multi returns
- a
Hashwhose keys and values are the user's selections ifchoices:was anArrayofStrings, - a
Hashwhose keys and values are those selected from thechoices:Hashifchoices:was anHash, - or the
cancel_valueif the user canceled the selection with C-c or C-g.
The prompt String (by default "Choose Many") can guide the user about the purpose of the choices.
confirm(prompt, yes_label: "Yes", no_label: "No", cancel_value: false)
Present the user with a simple Yes/No choice using your choice of ways to express "Yes" or "No" with yes_label: and no_label:.
confirm returns true for "Yes" and false for "No."
confirm will return false on cancellation with C-c or C-g unless you provide an alternative cancel_value:, in which case it returns that.
menu(prompt, choices:, initial_choice_idx: 0, cancel_value: nil)
Present the user with a set of choices: representing actions to execute.
choices: is a Hash whose keys are labels presented to the user and whose values are procs, lambdas, or other objects that respond to #call.
#! /usr/bin/env -S ruby -Ilib
# -*- mode: ruby -*-
require 'fatty'
asker = lambda do |_line, fatty|
result = fatty.(
"Pick an action",
choices: {
"Self Echo" => ->(fatty, label) {
fatty.append("You chose #{label}\n")
:echoed
},
"Say Hello" => ->(fatty, _label) {
name = fatty.prompt("Name?")
fatty.append("Hello, #{name}\n")
:greeted
},
},
)
fatty.good(result)
end
Fatty::Terminal.new(
on_accept: asker,
prompt: "Type RETURN for some action> ",
).go
Each callable has access to the CallbackEnvironment, called fatty here and the String label that the user selected.
Each callable can return a value that the @on_accept proc can process as it pleases. In the example, they are printed to the status area.
If you want to set one of the choices as the initial choice, set initial_choice_idx: to the Integer index of one of the choices.
If you want a value associated with the user's cancellation of the chooser with C-c or C-g, set cancel_value: to that value.
environment
This returns a Hash of runtime conditions detected by Fatty:
| Key | Description |
|---|---|
:arch |
System CPU architecture, e.g., x86_64 |
:os |
Operating system detected |
:ruby_platform |
Ruby platform, e.g., x86_64-linux |
:screen |
Whether the terminal is running under screen |
:ssh |
Whether the terminal is running under SSH |
:tmux |
Whether the terminal is running under tmux |
:terminal |
Detected underlying terminal, e.g., kitty |
:terminal_version |
Version of the terminal, if known |
:term |
The TERM terminal type |
:truecolor_detected |
Whether truecolor capability appears to be available |
:truecolor_enabled |
Whether Fatty is actually using truecolor rendering |
:curses |
Runtime curses capabilities and parameters |
The environment report also includes a nested :curses hash:
| Key | Description |
|---|---|
:started |
Whether curses has been initialized |
:truecolor |
Whether the active curses context is using truecolor |
:key_min |
Lowest keycode with a curses name |
:key_max |
Highest keycode with a curses name |
:lines |
Number of terminal rows available |
:cols |
Number of terminal columns available |
:has_colors |
Whether curses reports color support |
:colors |
Number of colors available to curses |
:color_pairs |
Number of color pairs available to curses |
:can_change_color |
Whether curses can redefine color values |
Default Interaction
Parts of the Screen
Input Field
Just above the bottom of the screen where all the action takes place: it is a line for editing the input. It displays a prompt followed by an area in which you build the command line using fatty's editing facilities.
Output Pane
Most of the top part of the screen is reserved for displaying whatever output is sent to it with the on_accept callback to the Terminal. It can render colored ANSI-encoded strings and will page long output so you can view it a page at a time and even search the output.
Status Area
The one to four lines just above the Input Field that displays output to the user that is out of band for the Output Pane. Brief messages of confirmation, warning, or error can be displayed there so as to get the user's immediate attention. Progress bars also render there where their visibility is made prominent.
Alert Area
Alerts are short-lived, non-scrolling messages shown below the input field. They are intended for user-visible conditions that require attention. Fatty uses this area to warn the user of unrecognized key codes and of unbound key presses.
Command-line Editing
fatty aims to have a full-featured command-line editor for editing a single line of text that is sent to the on_accept proc for the Terminal. The default keybindings are based on Emacs keybindings, but they are fully customizable. Implementing vim-like keybindings is not currently possible because fatty has no notion of editing "modes" as would be required for any kind of a modal editor—at least not yet. Maybe some day.
Input Context
When editing the input line or text input for widgets like the `prompt`, `fatty` provides emacs-like editing keybindings by default. Many of these commands can take a count prefix argument to repeat the command count times. For example, `M-8 M-0 #` will insert 80 '#' characters at the cursor.
Cursor Motion
Fatty implements all the expected actions for moving the cursor around the input buffer. You can move by characters or words or to the beginning or end of the buffer.
| Key | Description |
|---|---|
| C-a | move to the beginning of the line |
| :home | move to the beginning of the line |
| C-e | move to the end of the line |
| :end | move to the end of the line |
| C-f | move cursor right one character |
| :right | move cursor right one character |
| C-b | move cursor left one character |
| :left | move cursor left one character |
| M-f | move cursor right one word |
| M-:right | move cursor right one word |
| C-:right | move cursor right one word |
| M-b | move cursor left one word |
| M-:left | move cursor left one word |
| C-:left | move cursor left one word |
Clearing
This is one action that does not affect the input buffer but the output pane. It clears it.
| Key | Description |
|---|---|
| C-l | clear the output pane |
Undo and Redo
Fatty keeps a stack of buffer-modifying actions, recording the buffer contents before any change is made. The undo-redo facility allows you to traverse that stack, undo travels backward to an earlier buffer state, and, once at an earlier state, redo travels forward to a more recent buffer state.
| Key | Description |
|---|---|
| C-/ | undo |
| C-_ | undo |
| C-M-/ | redo |
| M-/ | redo |
Deletion and Killing
Fatty implements the emacs-esque notion of "killing" text by deleting it but also adding it to the "kill ring", a circular structure of all prior kills. It is circular in that successive "yanks", or pastes, traverse the kill ring and return to the beginning after reaching the end. By contrast a "deletion" merely removes text from the buffer without adding it to the kill ring.
When the region is active, destructive editing commands kill the region rather than deleting characters or words.
| Key | Description |
|---|---|
| C-d | delete character at cursor |
| :delete | delete character at cursor |
| :backspace | delete character before cursor |
| M-d | kill word at cursor |
| C-w | kill word before cursor |
| C-k | kill to end of line |
| C-t | transpose characters |
| M-t | transpose words |
Yanking, Mark, and Region
Text that has been killed can be "yanked" into the buffer at the cursor position with the "yank" action. That text can be replaced by successive entries in the kill ring with the yank_pop action.
In addition, fatty allows you to define a "region" by setting a mark and moving the cursor. The text between the mark and the cursor is the region. The region can be killed or copied, that is deleted and added to the kill ring or just added to the kill ring.
Notice that commands that normally delete characters or words, kill the region if the region is active.
| Key | Description |
|---|---|
| C-y | yank last kill at the cursor |
| M-y | replace last yank with next item in the kill ring |
| | | | C-d | when region active, kill the region | | :delete | when region active, kill the region | | :backspace | when region active, kill the region | | M-d | when region active, kill the region | | C-w | when region active, kill the region | | | | | C-:space | set the mark at the current cursor position | | C-@ | set the mark at the current cursor position | | C-g | clear the mark | | C-w | kill the region | | M-w | copy the region |
Count Prefix
By default each action acts on one item, so the default count is 1. But for certain commands that operate on characters or words, you can modify the count by typing M-<digit> keys before the command. For example, if you want to insert exactly 80 '#' characters into the buffer, M-8 M-0 # will do the trick.
There is one command, however, for which the count is not used as a repeat count: for page_top (bound in paging to g) the count is used as a line number to navigate to.
Fatty follows the emacs convention of making C-u multiply the count prefix by 4 with each press. So you can insert 64 '#' characters with C-u C-u C-u #.
| Key | Description |
|---|---|
| C-u | universal count argument (times 4 with each press) |
| M- |
accumulate a count argument |
History Navigation
Fatty sports a rather sophisticated history system. It not only records each completed item of input but can also be made to note the kind of items and the "context" in which it was entered.
History is essentially partitioned into separate "kinds" of history so that the command line can occupy one part of history and search strings a different part of history. That way, the history is made relevant to the current input buffer that you are working on.
In addition, each history item can note its "context". This is less strict than "kinds" of history but allows the history mechanism to favor history items from the same "context" as the current context. For example, in the fatty demo, any line that is not a builtin command is treated as a shell
if you traverse history items, you will be shown command executed from the directory you are in before other history items. It still will give you items from other contexts, it just pushes items from the current context to the front of the list.
You can navigate history items with the up and down keys as you would expect. But you can also have the history items presented in a popup that shows all history items then allows you to narrow the items displayed by typing text into its narrowing input field. Bonus: the narrowing input field itself has a history that you can access with M-p and M-n. History on history.
| Key | Description |
|---|---|
| C-p | replace the line with the prior history item |
| :up | replace the line with the prior history item |
| C-n | replace the line with the next history item |
| :down | replace the line with the next history item |
| C-r | search the history in a popup |
Completion
Fatty also provides a completion facility that makes entering input easier.
-
Input Completion
As you type a command-line,
fattywill auto-suggest ways to complete the line with dimmed text to the right of the cursor:
After typing 'ec'
fattysuggests a command-line pulled from history using the dimmed text to the right of the cursor. The suggestion can be accepted by moving the cursor into the suggestion, and any text then to the left of the cursor becomes part of the input buffer.fattymay have several completions suggested by the text-so-far and you can cycle through them by hitting thetabkey. And if you want, you can see all the suggestions in a popup withM-tab. That allows narrowing by the text of the completions and the narrowing filter has its own history.
-
Sources for Input Completion
For input completion,
fattyuses two sources:- The input history.
- The candidates returned by the
Terminal#completion_proc.
History completion is always available when the terminal has history enabled. It lets the user complete from lines they have previously accepted. This is especially useful for command shells, REPL-style interfaces, and applications where users often repeat or slightly modify earlier input.
Application completion is supplied by the optional
completion_procpassed toFatty::Terminal.new. The proc is called with the input buffer's text-so-far and returns candidate strings. This is where an application can offer completions for commands, file names, account names, option names, object identifiers, or anything else meaningful to the application.The two sources are combined before they are presented to the user. Fatty deduplicates candidates and then offers the result through the normal completion interface.
TABcycles through completions inline.M-TABopens a completion popup, where the user can narrow the candidate list by typing in theComplete:field.Application candidates are offered first. History candidates fill in behind them. This gives the host application priority for current, domain-specific completions while still letting users recover earlier accepted input.
The completion popup is still a popup filter. Its narrowing text has its own history, separate from command input history and from other popup filters. Use
M-pandM-ninside the completion popup to recall previous completion filters. -
File Name Completion
Apart from command-line completion,
fattyoffers completion of file names if the cursor is at the end of a string that looks like a file name: that is, it starts with './', '../', '~/' or '/'.- If the string-so-far matches only one directory or file name, it inserts that into the input buffer and continues completion from there.
- If the string-so-far matches multiple files or directories it auto-suggests the first and
TABcycles through each of the candidates which you can accept by moving the cursor past the suggestion. LikewiseS-TABcycles backwards through the list of candidates. - If the string-so-far matches no files, completion does nothing.
Just as with command line completion, you can invoke a popup of completions with
M-TABand a popup of possible file names will appear within which you may filter the candidates down by narrowing. Unlike other forms of completion, the file name completion popup allows you to fill out the path by navigating directories within the popup by hittingTABorrightto descend into a sub-directory orS-TABorleftto ascend to the parent directory. The popup title shows the path so far.
Accept or Cancel
Finally, when the command-line is to your satisfaction, you can submit the line to the on_accept proc which determines how your app responds to it.
You can end a fatty session with C-c at the command line, or (only if the buffer is empty) with C-d.
| Key | Description |
|---|---|
| :enter | feed the line to the on_accept proc and page output |
| M-:enter | feed the line to the on_accept proc and scroll output |
| C-c | quit `fatty` |
| C-d | quit `fatty` only if the input line is empty |
Paging Context
Once the command-line is accepted, `fatty` sends output to the large output pane, and if the output is more than one screen long presents a paging environment for viewing and searching the output. From there, fatty offers the expected navigation aids for examining the output:
| Key | Count? | Description |
|---|---|---|
up |
Yes | move output one line up |
k |
Yes | move output one line up |
down |
Yes | move output one line down |
j |
Yes | move output one line down |
page_up |
Yes | move back one page |
b |
Yes | move back one page |
u |
Yes | move back one page |
M-v |
Yes | move back one page |
backspace |
Yes | move back one page |
page_down |
Yes | move forward one page |
f |
Yes | move forward one page |
d |
Yes | move forward one page |
space |
Yes | move forward one page |
C-v |
Yes | move forward one page |
home |
No | move to the top of output |
M-< |
No | move to the top of output |
g |
Yes | move to the top, or to the counted original line* |
end |
No | move to the end of output |
M-> |
No | move to the end of output |
G |
No | move to the end of output |
= |
No | toggle the display of line numbers |
M-s |
No | toggle between paging and scrolling |
/ |
No | initiate a fixed-string search forward |
? |
No | initiate a fixed-string search backward |
C-s |
No | initiate an Isearch session forward |
C-r |
No | initiate an Isearch session backward |
C-M-s |
No | initiate an Regexp session forward |
C-M-r |
No | initiate an Regexp session backward |
C-/ |
No | initiate an Regexp session forward |
< |
No | narrow output to lines containing all entered terms |
> |
No | widen output by clearing the narrowing |
C-c |
No | quit paging |
q |
No | quit paging |
C-g |
No | quit paging |
escape |
No | quit paging |
n |
Yes | move to the next instance of the current search |
N |
Yes | move to the prior instance of the current search |
* Without a count, g moves to the beginning of output. With a numeric prefix, it moves to that original output line; for example, M-1 M-0 M-0 M-0 g moves to line 1000. It uses the original line numbers even when output is narrowed.
As you can see, fatty tries to accommodate a number of finger habits besides emacs, including vim and Emacs Info, by offering several alternative ways to invoke navigation actions.
One unusual action is invoked by M-s which toggles the output between scrolling mode without pause and paging mode. It can be invoked while doing either to enter the other mode.
You can add line numbers to the display while paging by using the = key.
You can also "narrow" the output with <, which will prompt you for one or more terms to match. The output will then show only lines that match all the terms typed. To return to the full output, "widen" with >.
Searching Output
While paging, you can initiate a search of the output as indicated in the above key bindings for paging. Once a search session is initiated, fatty presents an input line for entering search terms.
After a search session has ended, fatty returns to paging but remembers the last search term and allows navigation among them with n and N: n searches in the direction of the last search and N searches in the opposite direction. Notice that the current search term and direction are added to the paging bar after the information on your location within the output.
String and Regex Search
/ and ? prompt for a fixed string and search forward or backward. C-M-s (or C-/) and C-M-r (or C-?) perform the corresponding regular-expression searches. Accepting the search leaves the selected match active for later navigation with n and N.
Incremental Search
C-s and C-r begin forward or backward incremental search. The output moves to matching text as the search term is edited. Accepting commits the match; canceling restores the output position from before the search began.
Configuration
When a Fatty application starts, Fatty installs default configuration files under ~/.config/fatty if they do not already exist. Fatty does not overwrite files that are already there, so you can edit these files directly and keep them under version control.
The config files are:
config.yml: general configuration;keydefs.yml: terminal-specific keycode naming;keybindings.yml: user-defined keybindings;themes/: YAML theme files.
The default user-specific configuration directory follows XDG_CONFIG_HOME if it is set. Otherwise it is ~/.config/fatty.
In addition to the user-specific configuration, fatty will read app-specific configuration files from ~/.config/fatty/apps/<app_name> if Terminal was given an app_name parameter. It will use a different directory of your choosing if Terminal was given an app_config_dir directory name.
General Configuration config.yml
config.yml controls general behavior such as word movement, ESC handling, logging, history, the initial theme, and truecolor rendering.
A small configuration might look like this:
word_char_re: "[[:alnum:]_.:-]"
esc_delay: 0
history:
file: "~/.fatty_history"
max: 10000
theme: wordperfect
truecolor: auto
word_char_re
This setting is a Ruby regular expression fragment used to decide what counts as a word in the input buffer. It must be a String representing a Regexp that matches a single character that is meant to be a part of a "word" for purposes of cursor movement. The default setting treats letters, digits, and underscore as word characters:
word_char_re: "[[:alnum:]_]"
For command-line applications, you may want dashes to be part of a word:
word_char_re: "[[:alnum:]_-]"
For Ruby constants, hostnames, or command-like tokens, you may want a broader definition:
word_char_re: "[[:alnum:]_.:-]"
esc_delay
esc_delay controls how long curses waits after receiving ESC before deciding whether another key is part of the same Meta-key sequence. The value is in milliseconds.
esc_delay: 0
history
file
The path to the history file to use, by default ~/.fatty_history
max
The maximum number of lines of history retained, by default 10,000.
theme
The theme setting names the theme Fatty applies at startup:
theme: nordic
truecolor
The truecolor setting controls whether Fatty uses truecolor escape sequences for rendering colors. The usual setting is auto, meaning that truecolor is enabled if available. It can be set to true, false, on, off or auto.
truecolor: auto
log
Logging is configured under log:
log:
file: "~/log/fatty.log"
level: debug
tags:
- keycode
- keyevent
- keybinding
- action
- command
- session
- render
- perf
file
The path to the log file to use.
level
Can be one of the following
info: only informational messageswarn: only non-fatal warning messages andinfomessageserror: only fatal error messages andwarnandinfomessagesdebug: detailed messages to assist debuggingfattyplus all of the above.
tags
log.tags controls which tagged log messages are written. Tags are ordinary symbolic labels used by Fatty's logging calls; they are not checked against a fixed registry. A tag is useful if Fatty logs messages with that tag.
The special tag all enables all tagged log messages except perf. Performance logging is intentionally noisy and must be enabled explicitly with perf.
log:
tags:
- keycode
- keybinding
- render
- perf
Key code definitions keydefs.yml
Most keys are named correctly by curses. Some terminal emulators, multiplexers, or keyboard modes emit numeric keycodes that curses does not name usefully. The keydefs.yml file lets you assign names and modifiers to those numeric codes.
When Fatty runs inside tmux, key definitions are looked up under tmux, not under the outer terminal emulator. tmux translates the outer terminal's input before Fatty sees it, so keycodes observed inside tmux may differ from keycodes observed directly under GNOME Terminal, Kitty, Alacritty, or another terminal. Use keytest in the same environment where the application will normally run. The same comment applies to screen.
A key definition is terminal-specific. For example:
tmux:
555:
key: left
meta: true
570:
key: right
meta: true
557:
key: left
ctrl: true
572:
key: right
ctrl: true
This says that, when Fatty detects that the terminal is tmux, curses keycode 555 should be treated as M-left, keycode 570 as M-right, and so on.
The demo executable has a keytest command that is useful when building this file. Run fatty, use the keytest command, press the key you want to identify, and add an entry for the numeric code if Fatty does not already know what to call it.
Here is what I found running keytest on a variety of popular terminals to determine how each reported PageDown, C-PageDown, M-PageDown, and C-M-PageDown:
| Terminal | Key Pressed | Ctrl? | Meta? | Code | Status |
|---|---|---|---|---|---|
| gnome-terminal | PageDown | no | no | 338 | Defined and bound |
| gnome-terminal | PageDown | yes | no | 559 | Undefined |
| gnome-terminal | PageDown | no | yes | 557 | Undefined |
| gnome-terminal | PageDown | yes | yes | 561 | Undefined |
| alacritty | PageDown | no | no | 338 | Defined and bound |
| alacritty | PageDown | yes | no | 559 | Undefined |
| alacritty | PageDown | no | yes | 557 | Undefined |
| alacritty | PageDown | yes | yes | 561 | Undefined |
| kitty | PageDown | no | no | 338 | Defined and bound |
| kitty | PageDown | yes | no | 565 | Undefined |
| kitty | PageDown | no | yes | 563 | Undefined |
| kitty | PageDown | yes | yes | 567 | Undefined |
| terminator | PageDown | no | no | 338 | Defined and bound |
| terminator | PageDown | yes | no | Undefinable | |
| terminator | PageDown | no | yes | 557 | Undefined |
| terminator | PageDown | yes | yes | 561 | Undefined |
| ghostty | PageDown | no | no | 338 | Defined and bound |
| ghostty | PageDown | yes | no | 564 | Undefined |
| ghostty | PageDown | no | yes | 562 | Undefined |
| ghostty | PageDown | yes | yes | 566 | Undefined |
| konsole | PageDown | no | no | 338 | Defined and bound |
| konsole | PageDown | yes | no | 559 | Undefined |
| konsole | PageDown | no | yes | 557 | Undefined |
| konsole | PageDown | yes | yes | 561 | Undefined |
I could turn this into a keydefs.yml file that would allow me to define all those keys:
gnome-terminal:
559:
key: page_down
ctrl: true
557:
key: page_down
meta: true
561:
key: page_down
ctrl: true
meta: true
alacritty:
559:
key: page_down
ctrl: true
557:
key: page_down
meta: true
561:
key: page_down
ctrl: true
meta: true
kitty:
565:
key: page_down
ctrl: true
563:
key: page_down
meta: true
567:
key: page_down
ctrl: true
meta: true
terminator:
# 559:
# key: page_down
# ctrl: true
557:
key: page_down
meta: true
561:
key: page_down
ctrl: true
meta: true
ghostty:
564:
key: page_down
ctrl: true
562:
key: page_down
meta: true
566:
key: page_down
ctrl: true
meta: true
konsole:
559:
key: page_down
ctrl: true
557:
key: page_down
meta: true
561:
key: page_down
ctrl: true
meta: true
After installing that keydefs, I find that the PageDown variants are all recognized by name and modifiers (except for the strange failure of terminator to recognize C-PageDown!).
Key bindings keybindings.yml
Fatty has built-in Emacs-style bindings for ordinary input editing, history, completion, popup navigation, and paging. The keybindings.yml file lets you add or override bindings without changing Ruby code.
A keybinding names a key chord, the context in which it applies, and the Fatty action to run:
- key: left
meta: true
action: move_word_left
- key: right
meta: true
action: move_word_right
- key: space
context: paging
action: page_down
- key: backspace
context: paging
action: page_up
Key Names
A keybinding uses the unmodified key name under the key field:
- key: f
meta: true
context: text
action: move_word_right
Printable Keys
Most printable keys can be written as the character itself. Quote keys that YAML might otherwise treat specially or make hard to read, such as ":", "#", "[", "]", "{"`, `"}", "\"", and "\\".
- key: a
- key: /
- key: .
- key: =
- key: "\\"
- key: "\""
Curses and Fatty Named Keys
Fatty translates codes returned by curses for special keys into names:
| | | | | |
|-------------- |-------------- |--------------- |----------------- |-------------- |
| tab | insert | delete | home | end |
| left | right | page_up | page_down | up |
| down | f1 | f2 | f3 | f4 |
| f8 | f9 | f10 | f11 | f12 |
| keypad_0 | keypad_1 | keypad_2 | keypad_3 | keypad_4 |
| keypad_8 | keypad_9 | keypad_divide | keypad_multiply | keypad_minus |
| keypad_enter | keypad_comma | | | |
Some terminals can report application-keypad sequences such as keypad_divide, keypad_multiply, or keypad_enter, but numeric keypad digits are often not distinguishable from ordinary digits when NumLock is on, and are often reported as navigation keys such as end, down, page_down, or left when NumLock is off. Use keytest to see what your terminal actually sends.
Fatty also provides convenient names to other common keys:
enter,escape, andspace
Possibly Invisible Keys
Some physical keys may never reach Fatty at all. They may be handled by the keyboard firmware, the kernel, the desktop environment, the window manager, or the terminal emulator before curses can report them. Common examples include Print Screen, Scroll Lock, Pause/Break, Caps Lock, Num Lock, Fn, Menu/Application, Super/Windows, power/sleep/wake keys, brightness keys, volume/media keys, keyboard-backlight keys, airplane-mode keys, and touchpad toggle keys.
Use keytest to check. If Fatty sees a key as a raw numeric code, you can name it in keydefs.yml and bind it. If keytest shows nothing, the key is being intercepted before Fatty receives it.
Unnamed but Recognized Keys
Some terminals report modified special keys as distinct numeric keycodes that fatty and curses do not name. Use keytest to discover those codes, then assign names to them in keydefs.yml. The name can be one of Fatty's usual names, or a new name of your own.
For example, if a keyboard has an extra thumb key and keytest reports it as an uncoded keycode 601, you could name it:
gnome-terminal:
601:
key: thumb_left
Then bind it in keybindings.yml:
- key: thumb_left
context: text
action: move_word_left
Fatty does not require custom key names to come from a fixed registry. A custom name only needs to be used consistently between keydefs.yml and keybindings.yml.
Mouse Events
Mouse events can also be bound:
- button: left_clicked
context: popup
action: choose
Recognized mouse button names include:
left_pressed
left_released
left_clicked
left_double_clicked
left_triple_clicked
middle_pressed
middle_released
middle_clicked
middle_double_clicked
middle_triple_clicked
right_pressed
right_released
right_clicked
right_double_clicked
right_triple_clicked
scroll_up
scroll_down
Modifiers
A binding may include these modifiers:
ctrl: true
meta: true
shift: true
Contexts
In Fatty, the key map is partitioned by "contexts" and in different environments the contexts are tried in order from highest priority to lowest and the first binding found wins. The following table lists the contexts associated with each interaction environment:
| Environment | Contexts High to Low |
|---|---|
| Input Field | :input, :text, :terminal |
| Output Paging | :paging, :terminal |
| Output Scrolling | :terminal |
| String Search | :search, :text, :terminal |
| Incremental Search | :isearch, :text, :terminal |
| Prompt Popup | :prompt, :text, :terminal |
| Popup Choose | :popup, :text, :terminal |
| Popup Multi-Choose | :popup_multi, :popup, :text, :terminal |
The available contexts are:
| Context | Meaning |
|---|---|
:terminal |
global terminal/session commands |
:text |
ordinary text-editing commands shared by input fields |
:input |
the main command input field |
:paging |
output pager navigation |
:search |
non-incremental search field |
:isearch |
incremental search field |
:prompt |
prompt popup input |
:popup |
popup selection/navigation |
:popup_multi |
multi-select popup behavior |
The `:terminal` context is the lowest-priority fallback context for terminal-wide commands. It is where Fatty puts bindings that are not specific to text editing, paging, search, or popup selection, such as theme selection, key testing, and quitting. Because `:terminal` is searched last, any more specific context can override a terminal-wide binding.
Actions
A keybinding connects a key event in a context to an "action." An action is a named Fatty command such as `move_left`, `delete_backward`, `accept_line`, `page_down`, `choose_theme`, or `quit`.
A binding entry names the action with the `action` field:
- key: left
context: text
action: move_left
- key: f
meta: true
context: text
action: move_word_right
- key: page_down
context: paging
action: page_down
Most movement, deletion, history, completion, paging, and popup-navigation actions honor Fatty's numeric count argument. The count is entered before the command, Emacs-style. For example, M-5 C-f moves forward five characters if C-f is bound to move_right, and M-3 C-n moves down three items if C-n is bound to the current environment's next-item action.
Use `keytest` to check what action is currently bound to a key. If a key is bound, `keytest` reports the matching context, action name, arguments, and the object that handles the action.
Input buffer actions
Input buffer actions edit the contents of a text buffer. These are the ordinary line-editing operations shared by the main input field, search fields, prompt popups, and other text-like interactions. Many of these actions honor the numeric prefix count.
Terminology notes:
- kill ring: like emacs,
fattykeeps a "clipboard" of items with the newest item in the current position and older items "before" it but in a "ring" that return to the beginning; - yank: retrieve the most recent item from the kill ring
- yank pop: retrieve successively older items from the kill ring
- delete: removes text without copying it to the kill ring;
- kill: removes text and copies it to the kill ring for later yank;
- word: a sequence of contiguous characters that are each matched by the
word_char_refrom the config file or "[[:alnum]_]" by default;
| Action Name | Count? | Description |
|---|---|---|
| bol | No | Move cursor to beginning of buffer |
| clear | No | Clear the buffer |
| clear_mark | No | Clear the region mark |
| copy_region | No | Copy the text in the region to kill ring |
| delete_char_backward | Yes | Delete char to the left of cursor |
| delete_char_forward | Yes | Delete char to the right of cursor |
| delete_region | No | Delete the text in the region |
| eol | No | Move cursor to end of buffer |
| kill_region | No | Kill the text in the region |
| kill_to_bol | No | Kill the text from cursor to beginning |
| kill_to_eol | No | Kill the text from cursor to end |
| kill_word_backward | Yes | Kill the word to the left of cursor |
| kill_word_forward | Yes | Kill the word to the right of cursor |
| move_left | Yes | Move cursor one char left |
| move_right | Yes | Move cursor one char right |
| move_word_left | Yes | Move cursor one word left |
| move_word_right | Yes | Move cursor one word right |
| redo | No | Redo an undone action |
| set_mark | No | Set the region mark at cursor |
| transpose_chars | Yes | Switch position of char with prior char |
| transpose_words | Yes | Switch position of word with prior char |
| undo | No | Revert last edit action |
| yank | No | Copy kill ring to buffer at cursor |
| yank_pop | Yes | Copy next kill ring item and replace prior |
Input field actions
Input field actions operate on an editable input field. An input field occurs at the main input prompt, but also in things like search-narrowing input, incremental-search, a prompt for text, etc. It is lower level than the shell session input described in the next section. In particular, it can have its own history attached to it depending on its purpose. They handle accepting the current line, moving through history, and receiving pasted text.
| Action Name | Count? | Description |
|---|---|---|
| accept_line | No | Accept the current text as the input |
| history_prev | No | Insert the prior relevant history item in the input field |
| history_next | No | Insert the next relevant history item in the input field |
Shell session actions
Shell session actions operate at the session level around the main command input and output area. They submit input, interrupt running work, clear output, start completion, open completion/history popups, and manage numeric prefix entry.
| Action Name | Count? | Description |
|---|---|---|
| submit_line | No | Pass the current text to the on_accept proc and page output |
| submit_and_scroll | No | Pass the current text to the on_accept proc and scroll output |
| interrupt | No | End the current session |
| interrupt_if_empty | No | End the current session only if the current text is empty |
| clear_output | No | Clear the output pane |
| complete | No | Suggest the next possible completion as an autosuggestion |
| complete_previous | No | Suggest the prior possible completion as an autosuggestion |
| completion_popup | No | Display all possible completions in a popup for selection |
| history_search | No | Display history item starting with the text-so-far for selection |
| count_digit | No | Add a digit to the numeric prefix |
| universal_argument | No | Set the numeric argument to 4 or multiply the current one by 4 |
Pager actions
Pager actions navigate output when the output area is in paging mode. They move by line, page, top, bottom, or end of output, handle mouse-wheel scrolling, and switch between paging and scrolling behavior. Most movement actions honor the numeric prefix count.
| Action Name | Count? | Description |
|---|---|---|
| page_up | Yes | Move the output view one page up |
| page_down | Yes | Move the output view one page down |
| end_of_output | No | Move the output to the end of output and scroll |
| line_up | Yes | Move the output view one line up |
| line_down | Yes | Move the output view one line down |
| scroll_up | Yes | Move the output view six lines up |
| scroll_down | Yes | Move the output view six lines down |
| page_top | Yes | Move to the beginning, or to the counted line |
| page_bottom | No | Move the output to the end of output |
| paging_to_scrolling | No | Quit paging and scroll output as it is produced |
| toggle_paging | No | Toggle output mode between paging and scrolling |
| quit_paging | No | Quit paging and return control to the input line |
Pager search actions
Pager search actions can initiate a search session within the pager: a string search, a regular expression search, or an incremental-search. Once a search session is entered, other actions navigate matches in the output. There are also actions to end a search session.
While the search session is active, accepting commits the current match and canceling restores the pager to the position it had when the search began. After the search is accepted, n and N repeat the committed search in the pager; at that point the search session is no longer active.
| Action Name | Count? | Description |
|---|---|---|
| pager_search_forward | No | Initiate a string search forward from current output point |
| pager_search_backward | No | Initiate a string search backward from current output point |
| pager_regex_search_forward | No | Initiate a regex search forward from current output point |
| pager_regex_search_backward | No | Initiate a regex search backward from current output point |
| pager_isearch_forward | No | Initiate an incremental search forward from current output point |
| pager_isearch_backward | No | Initiate an incremental search backward from current output point |
| pager_search_next | No | Move output to the next search match |
| pager_search_prev | No | Move output to the prior search match |
| search_toggle_regex | No | Toggle between regex and string search |
| search_step_forward | No | Preview the next match using the current search text |
| search_step_backward | No | Preview the prior match using the current search text |
| isearch_next | No | Move output to the next isearch match |
| isearch_prev | No | Move output to the prior isearch match |
| search_accept | No | Commit the current search and close the search session |
| search_cancel | No | Cancel the active search session and restore its starting point |
| isearch_accept | No | Commit the current incremental search and close the search session |
| isearch_cancel | No | Cancel the active incremental search and restore its starting point |
Popup actions
Popup actions control selection and narrowing popups such as choose, menu, completion, and history popups. You can narrow the selections presented by typing in the input field presented. You can move through the list, accept or cancel the selection, page through choices, jump to the top or bottom, recenter the selection, and toggle selection in multi-select popups.
| Action Name | Count? | Description |
|---|---|---|
| popup_cancel | No | Close the popup and return its cancel_value |
| popup_accept | No | Close the popup and return the selection |
| popup_next | No | Move the selection cursor to the next item |
| popup_prev | No | Move the selection cursor to the prior item |
| popup_page_down | No | Move the selection list down one page |
| popup_page_up | No | Move the selection list up one page |
| popup_top | No | Move to the top of the selection list |
| popup_bottom | No | Move to the bottom of the selection list |
| popup_recenter | No | Recenter the selection list on the current item |
| popup_toggle_selected | No | In multi-select, toggle the selection of the current item |
Prompt popup actions
Prompt popup actions control prompt dialogs that collect a string from the user. They accept the prompt, cancel it, or cancel only when the prompt is empty.
| Action Name | Count? | Description |
|---|---|---|
| prompt_accept | No | Close the prompt popup and return the current string |
| prompt_cancel | No | Close the prompt popup and return its cancel_value |
| prompt_cancel_if_empty | No | Cancel only if the input buffer is empty |
Utility Actions
Some actions exist to support Fatty's own input machinery and are not normally useful as ordinary user bindings. For example, insert, replace, and paste need text supplied by code: paste is used for bracketed paste events from the terminal and normalizes pasted text for a one-line input field; self_insert is selected automatically for printable characters. They are listed here for completeness.
| Action Name | Args | Count? | Description |
|---|---|---|---|
| insert | str | Yes | Insert the string at point |
| self_insert | Yes | Insert the bound key at point | |
| replace | str | No | Replace the buffer with the string |
| set | str | No | Alias for replace |
| paste | str | No | Insert pasted text at point |
Themes themes/
Distributed Themes
As distributed, fatty comes with the following themes, most of which are based on popular themes used in many text editors and IDEs.
Within a fatty terminal, themes can be changed interactively. The default Emacs-style bindings include:
| Key | Action |
|---|---|
C-M-t |
cycle to the next loaded theme |
M-= |
preview and choose a theme from a popup |
Here are the builtin themes:
| Name | Description |
|---|---|
| capuchin_monk | A tribute to St. Francis of Assisi |
| catppuccin_latte | Pastel theme of the latte flavor |
| catppuccin_mocha | Pastel theme of the mocha flavor |
| cyberpunk | Brash colorful theme |
| dracula | Dark theme inspired by the Count |
| everforest_dark | Dark green-esque theme |
| gruvbox_dark | Engineered for focus, built for developers |
| gruvbox_light | Engineered for focus, built for developers |
| mono | Minimal black-and-white theme |
| monokai | Based on https://monokai.pro/history |
| nordic | Shiver me timbers, it's northern |
| onedark | Shameless imitation of Atom editor theme |
| solarized_dark | Ethan Schoonover's famous terminal theme |
| solarized_light | And its light variant |
| terminal | Use the terminal's color scheme |
| tokyo_night | Celebrates the neon lights and dark vibes of Tokyo |
| wordperfect | Nostalgic nod to the good old word processor |
Custom Themes
Theme Name
A Fatty theme is a YAML file in the user-specific config directory (normally ~/.config/fatty/themes) or the app-specific config directory (by default ~/.config/fatty/apps/<app_name>/themes). The filename does not determine the theme name: the theme file must have a name field to set the name:
name: nordic
Inheritance from Another Theme
Themes can inherit from other themes with the inherit: tag:
name: my_nordic
inherit: nordic
output:
fg: "#eeeeee"
This means that all the theme setting from the nordic theme apply except to the extent changed by the rest of the my_nordic theme file.
Inheritance within a Theme
Not only may one theme inherit from another theme, some roles within a theme inherit from other roles. For example, the input role inherits from the output role.
Thus, a minimal theme can define only output and inherit everything else through Fatty's role defaults:
name: simple_dark
inherit: null
output:
fg: white
bg: black
This means that the input field will have the same coloring and attributes as the output pane.
Theme Roles
Here is a listing of what roles act as the parent for each of the roles, their purpose, and the role, if any, from which they inherit.
| Role name | Used for | Parent Role |
|---|---|---|
output |
Main output area text. | |
input |
Main command/input field. | output |
input_suggestion |
Autosuggestion or completion suffix text. | input |
cursor |
Cursor cell styling where applicable. | input |
region |
Selected text / active region. | output |
status |
Status area base role. | output |
info |
Informational messages. | output |
good |
Success messages. | info |
warn |
Warning messages. | info |
error |
Error messages. | warn |
alert |
Alert panel base role. | output |
pager_status |
Pager status / prompt row. | status |
line_number |
Line numbers when toggled on | output |
search_input |
Search input field. | popup |
match_current |
Current search match. | region |
match_other |
Other search matches. | region |
popup |
Popup body. | output |
popup_frame |
Popup border/frame. | popup |
popup_input |
Popup filter/input field. | input |
popup_selection |
Selected popup item row. | region |
popup_counts |
Popup counts/status row. | popup |
markdown_h1 |
Markdown level-1 headings. | output |
markdown_h2 |
Markdown level-2 headings. | markdown_h1 |
markdown_h3 |
Markdown level-3 headings. | markdown_h2 |
markdown_code |
Inline markdown code. | output |
markdown_code_block |
Markdown fenced-code block base role. | markdown_code |
markdown_code_gutter |
Code block gutter/prefix. | markdown_code_block |
markdown_strong |
Markdown strong/bold text. | output |
markdown_emphasis |
Markdown emphasized text. | output |
markdown_link |
Markdown link text. | output |
markdown_url |
Markdown displayed URL text. | markdown_link |
markdown_quote_gutter |
Markdown block quote gutter. | output |
markdown_highlight |
Markdown highlight / marked text. | output |
markdown_table_header |
Markdown table header cells. | markdown_strong |
markdown_table_cell |
Markdown table body cells. | output |
markdown_underline |
Markdown underline text. | output |
markdown_hrule |
Markdown horizontal rules. | output |
Role Colors and Attributes and Frame Style
Each role can have assigned to it a foreground color (fg:), a background color (bg:), and one or more "attributes" (attrs:).
-
Colors
The colors can be a hex string of the form "#RRGGBB", that is a mixture of red, green, and blue at level given by three valid hexadecimal numbers RR, GG, and BB. For example "#FF0000" is pure red, "#330044" is a middling mixture of red and blue, a shade of purple. In addition,
fattyrecognizes color names defined as 16 ANSI colors, 256 xterm colors, and X11 colors, all of which you can see by invokingfatty's CLI commandcolors.The special color
defaultmeans to inherit colors from the underlying terminal program. -
Attributes
Each role can also have non-color attributes applied by specifying an
attrslist. Supported attributes arebold,dim,italic,underline, andreverse. Italic is emitted by the truecolor renderer and by markdown ANSI output, but curses terminals may display it inconsistently or not at all. -
Frame Style
In addition, one role,
popup_frame:, in addition to these have two attributes for styling the "frame" drawn around popup:|
border:| single, double, or ascii | |corners:| rounded or square | -
An Example Theme
name: example inherit: null output: fg: "#d8dee9" bg: "#2e3440" input: fg: "#eceff4" bg: "#3b4252" attrs: [bold] input_suggestion: fg: "#81a1c1" bg: "#3b4252" cursor: fg: "#2e3440" bg: "#88c0d0" region: fg: "#2e3440" bg: "#88c0d0" info: fg: "#d8dee9" bg: "#2e3440" good: fg: green bg: "#2e3440" warn: fg: "#2e3440" bg: "#ebcb8b" error: fg: "#eceff4" bg: "#bf616a" pager_status: fg: black bg: lightgreen search_input: fg: black bg: cyan match_current: fg: black bg: yellow match_other: fg: black bg: lightgray popup: fg: "#d8dee9" bg: "#3b4252" popup_input: fg: "#eceff4" bg: "#434c5e" popup_selection: fg: "#2e3440" bg: "#88c0d0" popup_counts: fg: "#2e3440" bg: white popup_frame: fg: "#81a1c1" bg: "#3b4252" border: single corners: roundedOr you can group them under
markdownwithout themarkdown_prefix:markdown: h1: fg: yellow bg: navy attrs: [bold] code: fg: lightgreen bg: black link: fg: cyan bg: navy attrs: [underline]