cy is an experimental terminal multiplexer. It builds on the traditional paradigms of tmux
and screen
with advances in form, function, and customization.
Features:
- Replay and search through any terminal session
- Flexible configuration with Janet, a simple imperative programming language
- Built-in fuzzy finding using concepts from fzf
Why not tmux?
cy
shares some basic similarities with tmux
. For example, it runs as a daemon, so its state is preserved across sessions. But for cy
to be a compelling alternative, it has to do more than just mimic tmux
's functionality and be written in a fashionable systems programming language.
cy
improves on tmux
in three main ways:
- Session playback:
cy
records your terminal sessions and lets you play back and search through them. - Interface:
cy
has a simple layout designed for use on large screens. - Configuration:
cy
uses a real programming language, Janet, for configuration.
Session playback
If you use tmux
, you might be familiar with copy-mode
, which allows you to view lines from the scrollback buffer. This is nice when you're using a program like bash
, where you issue commands that produce static output.
But by definition, copy-mode
ceases to be useful when you use a program with any interactivity, such as vim
: you only see the last lines it left on the screen, and certainly cannot see what the screen used to look like.
cy
solves this problem by recording all of your terminal sessions. In replay mode you can seek, play back, and search through the history of a pane--regardless of the application that was running in it. cy
saves these recordings to disk and allows you to open them later at your leisure.
Without trying it for yourself, it's hard to appreciate just how useful it is to be able to go back in time to replay everything you've ever seen or done in the terminal. cy
aims to augment your memory in a way that other programs cannot.
Interface
cy
does not (yet) have windows and panes like tmux
does. It displays one terminal session at a time, and by default that session is centered on your screen with a fixed number of columns. This lets you concentrate on one "pane" at a time.
To some, this may seem like a surprising decision. The predominant abstraction for terminal multiplexers for at least a couple of decades has been the familiar pattern of vertical and horizontal splits.
Yet I found that I have spent more time fighting with this (or writing plugins to remove it) than benefiting from it. Rarely do I feel like I need to see more than one pane at a time.
This is practical because cy
makes it easy to switch between panes. It emphasizes using fuzzy finding (with previews!) so you can quickly find what you're looking for. It also contains a minimal, filesystem-like abstraction for grouping panes together.
As of writing, cy
also lacks tmux
's status line. Because there is no notion of windows, there is nothing akin to the tab-like behavior that tmux
encourages (and thus little immediate need to display it.)
Neither of these are principled omissions. My intent with cy
is to, sooner or later, address these use cases in a flexible way.
Configuration
Anyone who has tried to do anything sophisticated with tmux
runs into a familiar set of problems:
tmux
's configuration language is hacky and primitive, which makes it hard to do anything interesting without running an external command.- Its key binding system is limited.
cy
allows you to bind arbitrary sequences of keys to Janet functions. It even supports binding regexes, matches for which will be passed to the function you bound.
You can also create bindings that apply only in a specific pane or group of panes.
Installation
You can install cy
using pre-compiled binaries or from source.
Note: cy
is still highly experimental. Some things may not work very well, but I would appreciate it if you created an issue if something breaks or you spot something cy
could do better.
brew
You can use brew
to install cy
on macOS and Linux:
# Install the latest version:
brew install cfoust/taps/cy
# Or a specific one:
brew install cfoust/taps/cy@0.1.8
From binary
Download the latest version of cy
on the releases page and extract the archive that corresponds to your operating system and architecture into your $PATH
.
From source
git clone git@github.com:cfoust/cy.git
cd cy
go install ./cmd/cy/.
Quick start
This guide introduces cy
's basic concepts in a step-by-step tutorial.
1. Starting cy
To start cy
after installation, just run cy
without any arguments. cy
will connect to the cy
server, starting one if necessary.
cy
shows a splash screen on startup that looks like this. To clear it, press any key.
2. Using the viewport
When you first connect, cy
creates a new pane and attaches to it. By default, it runs your default shell. cy
centers the pane and fills the rest of the horizontal space with a patterned background. cy
refers to the state of your screen as the viewport.
All actions in cy
, such as creating panes and switching between them, are triggered by sequences of keys.
Here are a few you can try:
- To make the pane fill the entire viewport, type
ctrl+a
g
. (Repeat to center it again.) - To set the width of the pane to 80 columns, type
ctrl+a
1
. - To set it to 160 columns, type
ctrl+a
2
. - To increase the width by 5 columns, type
ctrl+a
+
. - To decrease the width by 5 columns, type
ctrl+a
-
.
3. Entering replay mode
cy
's most important feature is that it records all of your terminal sessions in their entirety and lets you jump back in time and replay them at will. It has an interface called replay mode that is conceptually similar to tmux
's copy-mode
, but also gives you access to time controls.
You open replay mode for a given pane by typing ctrl+a
p
. You can also scroll up with the mouse if the pane's content is scrollable (such as when using a shell.)
Replay mode is powerful and complicated, but for basic usage you can move through time using the left and right arrow keys and hit space
to play and pause. For more information, refer to the chapter dedicated to replay mode and the list of all of its key bindings.
4. Creating a new shell
To create a new shell, type ctrl+a
j
. This creates a new pane running your default shell in your current working directory. You can return to the old one with ctrl+l
; this cycles between all of the panes in the current group. In cy
, a group is just a container for panes or other groups.
Every group and pane in cy
has a path, just like a file in a filesystem. The new shell you created has a path like /shells/3
.
The collection of all groups and panes is referred to as the node tree. When you first start cy
, the node tree looks like this:
/ (group)
├── /shells (group)
│ └── /2 (pane) <- you start here
└── /logs (pane)
And here's how it looks after you create a new shell with ctrl+a
j
:
/ (group)
├── /shells (group)
│ ├── /2 (pane)
│ └── /3 (pane) <- now you're here
└── /logs (pane)
When you are attached to a pane in the /shells
group, ctrl+l
cycles to the next sibling pane.
5. Creating a new project
Often, you will be doing some work in a single directory, like a Git repository. cy
ships with a way to create a new group of panes for exactly this purpose. To use it, navigate to a directory and type ctrl+a
n
.
This creates two panes:
/projects/[base-name]/editor
: A pane running the program specified by the$EDITOR
environment variable./projects/[base-name]/shell
: A pane running your default shell (or the value of$SHELL
).
[base-name]
is the basename (a la the Bash basename
command) of the directory in which you opened the project.
For example, if you type ctrl+a
n
while in a pane with the working directory /tmp/test-dir
, [base-name]
would be test-dir
and the node tree would have the following structure:
/ (group)
├── /shells (group)
│ └── [...]
├── /projects (group)
│ └── /test-dir (group)
│ ├── /editor (pane) <- attached here
│ └── /shell (pane)
└── /logs (pane)
6. Switching between shells and projects
cy
allows you to quickly jump between panes using a built-in fuzzy finder. Try it out by hitting ctrl+a
;
, which presents you with a list of all of the running panes.
The controls should be familiar to you if you have ever used a fuzzy finder:
- Typing filters the list.
- Use
ctrl+j
andctrl+k
(or the arrow keys) to move up and down. - Press
enter
to make a selection. - Quit without making a choice by typing
ctrl+c
oresc
.
cy
ships with a few different key bindings for choosing a pane:
ctrl+a
k
: Jump to a project.ctrl+a
l
: Jump to a shell based on its current working directory.
7. Using the command palette
Like most modern applications, cy
includes a command palette, which lets you search through and execute all of the available actions. You can access it by typing ctrl+a
ctrl+p
.
8. Quitting cy
You can kill the cy server by hitting ctrl+a
q
and detach from it (but leave the server running with ctrl+a
d
.
Configuration
All of cy
's behavior is determined using a programming language called Janet, which is a fun, embeddable Lisp-like language that is easy to learn. If you are new to Janet, I recommend starting out with its documentation and Ian Henry's fantastic Janet for Mortals.
Janet looks like this:
(print "hello world!")
If that doesn't scare you, read on.
Configuration files
On startup, cy
will search for and execute the first file containing Janet source code that it finds in the following locations. (cy
adheres to the XDG base directory specification.)
$XDG_CONFIG_HOME/cy/cyrc.janet
$XDG_CONFIG_HOME/cyrc.janet
$XDG_CONFIG_HOME/.cy.janet
$HOME/cy/cyrc.janet
$HOME/cyrc.janet
$HOME/.cy.janet
$HOME/.config/cy/cyrc.janet
$HOME/.config/cyrc.janet
$HOME/.config/.cy.janet
You can reload your configuration at runtime using action/reload-config, which by default is bound to ctrl+ar.
Your cy
configuration can contain any valid Janet statement, but cy
also provides additions to the standard library in the form of an API for controlling every aspect of how cy
works.
Example configuration
An example configuration that uses functionality from this API is shown below. Very little of this will make sense right now; this is just to give you a taste of how configuration works in cy
before moving on to the next section.
# Define a new action (which is just a function) with the name toast-pane-path
(key/action
# the name of the function by which it can be referenced in the current
# scope
toast-pane-path
# a (required) docstring for the action
"show the path of the current pane"
# all subsequent statements are executed when this action is invoked (usually
# by a key binding)
#
# (pane/current): gets the ID of the current pane
# (cmd/path): gets the path of the pane with the given ID
# (cy/toast): shows a toast popup in the top-right corner of the screen
(cy/toast :info (cmd/path (pane/current)))
)
# Bind a key sequence to this function
(key/bind :root ["ctrl+a" "g"] toast-pane-path)
Viewport
For the time being, cy
only allows each user to view and interact with one pane at once, which is centered inside of the viewport and automatically resized as that user changes the bounds of their terminal.
By default, cy
restricts that pane to a width of 80 columns or the width of your terminal window, whichever is smaller, but this is configurable. This is practical because cy
makes it easy to switch between panes.
cy
has two main abstractions for showing terminal art in the viewport:
- frames: static, configurable backgrounds that
cy
uses to fill the empty space in the viewport - animations: shown on the splash screen and when fuzzy finding
Frames
big-hex
(viewport/set-frame "big-hex")
brick
(viewport/set-frame "brick")
cheerios
(viewport/set-frame "cheerios")
cross
(viewport/set-frame "cross")
cross-stitch
(viewport/set-frame "cross-stitch")
dot-bricks
(viewport/set-frame "dot-bricks")
hive
(viewport/set-frame "hive")
hive-thick
(viewport/set-frame "hive-thick")
none
(viewport/set-frame "none")
puzzle
(viewport/set-frame "puzzle")
squares
(viewport/set-frame "squares")
stars
(viewport/set-frame "stars")
tiles
(viewport/set-frame "tiles")
wallpaper
(viewport/set-frame "wallpaper")
zigzag
(viewport/set-frame "zigzag")
Animations
collapse
conway
cos
cy
midjo
Keybindings
In cy
, keybindings consist of a sequence of one or more keys that execute Janet code when you type them. You define new key sequences with the key/bind function.
For example:
(key/bind :root ["ctrl+l"] (fn [&] (cy/toast :info "you hit ctrl+l")))
This tells cy
that whenever you type ctrl+l
it should show a toast with the text "you hit ctrl+l".
The key/bind function takes three parameters:
- A scope: The circumstances in which this binding should apply, such as a group or mode (e.g.
:time
). In this case we use the:root
keyword, which is a handy way of saying this binding should apply everywhere. - A key sequence: A Janet tuple that indicates the keys that must be typed for the callback to execute.
- A function: The callback that should be executed when this key sequence matches.
Scopes will be covered in a later chapter: here we will cover key sequences and functions at length.
You can avoid calling key/bind over and over by using the key/bind-many macro. Here is an example:
(key/bind-many :root
["ctrl+b" "1"] do-something
["ctrl+b" "2"] do-something-else)
You can also clear previously bound key bindings with key/unbind or rebind them with key/remap.
Key sequences
Key sequences in cy
are more flexible than they appear at first glance. Valid sequences can consist of the following elements:
- Printable Unicode characters:
你
,Щ
,a
- Preset keys:
return
,ctrl+a
,f1
You can find a comprehensive list of the available keys here. - Regexes:
[:re "^[a-z]$"]
The first two work exactly as you expect them to: cy
will execute the first complete match for the keys that you type. After each key, cy
gives you a second (=1000ms) to type the next key in the sequence. If you do not, cy
does nothing. All keys that are not matched by any sequence are sent to the current pane.
Here are some valid key sequences:
# This is a match when you type these three characters in succession
["a" "b" "c"]
# This works similarly to tmux's notion of "prefixes"
["ctrl+a" "a"]
["ctrl+a" "ж"] # unicode is OK
[" " "l"]
It is important to note that cy
does not send partial sequences to the current pane. In other words, defining a sequence that begins with " "
means that you will no longer be able to type the space character.
Regexes
The most powerful aspect of cy
's keybinding engine is the ability to define key sequences that include Perl-compatible regular expressions. Each element that matches a regex is passed to the callback as a string value.
To illustrate:
(defn toast-me [key] (cy/toast :info key))
(key/bind :root ["ctrl+b" [:re "[abc]"]] toast-me)
Now if you type ctrl+b
followed by a
, the toast-me
function will be invoked with one argument, the Janet value "a"
. The same applies if you follow the ctrl+b
with b
or c
.
This allows you to build more sophisticated functionality without defining a binding for every possible character.
A practical application of this can be found in cy
's source code, where we use this functionality to support vim
-like character movements in replay mode:
(key/bind :copy ["f" [:re "."]] replay/jump-forward)
(key/bind :copy ["F" [:re "."]] replay/jump-backward)
Key specifiers are matched as though their names were typed by the user; this means that providing the pattern "ctrl\+[a-c]"
will match ctrl+a
, ctrl+b
, and ctrl+c
.
Accessing individual match groups is not supported; functions always receive the full string that matched the pattern.
Functions
Any Janet function can be passed as a callback to key/bind. The arity of that function should match the output of the provided sequence; for key sequences that do not include any regex patterns, this means that the function should not take any arguments.
Like tmux
, many users at once can connect to the same cy
server. The function provided to key/bind is executed in the context of the user that invoked it. Certain functions in cy
's API, such as pane/current, return information about the state of the current user, rather than the server as a whole. This means that if two users type the same sequence, they will get different results.
Actions
In some cases it is inconvenient to have to provide functions directly to key/bind. For example, if you are writing a plugin, you might want to be able to provide new actions that a user can take without forcing them to use your key bindings. The user also may not want to assign all of your plugin's functionality to arcane bindings they won't remember.
To assist with this, cy
has a system for actions, which are similar in nature to commands in VSCode or in Sublime Text. An action consists of a short description and a function. When the user opens the command palette (which is bound by default to "ctrl+a" "ctrl+p"
), they can search for and execute an action based on that description.
You define new actions using the key/action macro. Here is an example from cy
's source code:
(key/action
# The identifier to which this action will be bound
# This is never shown in the UI
cy/kill-current-pane
# The docstring
# The user uses this to find the action you define
"kill the current pane"
# All subsequent forms comprise the body of the action, or the lines of code
# that will be executed when it is invoked
(tree/kill (pane/current)))
(key/bind :root ["ctrl+b" "b"] cy/kill-current-pane)
key/action actually just invokes Janet's (defn)
macro under the hood. This means that actions are just ordinary Janet functions that happen to be registered with cy
. key/action exists so that you can clearly identify to the user the functionality your plugin provides.
You can also just use actions to avoid memorizing a key binding you rarely use:
(key/action
thing-i-rarely-do
"this is something I do once a year"
(pp "hi"))
Changing and deleting existing keybindings
cy
provides two API functions for manipulating existing keybindings, key/remap and key/unbind.
It is sometimes convenient to change the activation sequence for many bindings at once. For example, you may want to change the prefix used for most of cy's bindings, ctrl+a, into ctrl+v:
(key/remap :root ["ctrl+a"] ["ctrl+v"])
Similarly, you may also delete keybindings with key/unbind.
Key specifiers
cy
defines the following preset keys:
Specifier | Notes |
---|---|
"ctrl+@" | |
"ctrl+a" | |
"ctrl+b" | |
"ctrl+c" | |
"ctrl+d" | |
"ctrl+e" | |
"ctrl+f" | |
"ctrl+g" | |
"ctrl+h" | |
"tab" | (this is what "ctrl+i" outputs) |
"ctrl+j" | |
"ctrl+k" | |
"ctrl+l" | |
"enter" or "return" | |
"ctrl+n" | |
"ctrl+o" | |
"ctrl+p" | |
"ctrl+q" | |
"ctrl+r" | |
"ctrl+s" | |
"ctrl+t" | |
"ctrl+u" | |
"ctrl+v" | |
"ctrl+w" | |
"ctrl+x" | |
"ctrl+y" | |
"ctrl+z" | |
"esc" | |
"ctrl+\\" | |
"ctrl+]" | |
"ctrl+^" | |
"ctrl+\_" | |
"backspace" | |
"up" | |
"down" | |
"right" | |
" " or "space" | |
"left" | |
"shift+tab" | |
"home" | |
"end" | |
"ctrl+home" | |
"ctrl+end" | |
"shift+home" | |
"shift+end" | |
"ctrl+shift+home" | |
"ctrl+shift+end" | |
"pgup" | |
"pgdown" | |
"ctrl+pgup" | |
"ctrl+pgdown" | |
"delete" | |
"insert" | |
"ctrl+up" | |
"ctrl+down" | |
"ctrl+right" | |
"ctrl+left" | |
"shift+up" | |
"shift+down" | |
"shift+right" | |
"shift+left" | |
"ctrl+shift+up" | |
"ctrl+shift+down" | |
"ctrl+shift+left" | |
"ctrl+shift+right" | |
"f1" | |
"f2" | |
"f3" | |
"f4" | |
"f5" | |
"f6" | |
"f7" | |
"f8" | |
"f9" | |
"f10" | |
"f11" | |
"f12" | |
"f13" | |
"f14" | |
"f15" | |
"f16" | |
"f17" | |
"f18" | |
"f19" | |
"f20" |
Default key bindings
All of cy
's default key bindings use actions defined in the global scope and therefore are easy to rebind should you so desire. For example, to assign cy/command-palette
to another key sequence:
(key/bind :root ["ctrl+b" "p"] cy/command-palette)
Global
These bindings apply everywhere and can always be invoked.
Prefixed
All of the bindings in this table are prefixed by ctrl+a by default. You can change the prefix for cy's bindings using key/remap.
General
Sequence | Action | Description |
---|---|---|
ctrl+p | action/command-palette | Open the command palette. |
r | action/reload-config | Reload the cy configuration. |
d | cy/detach | Detach from the cy server. |
q | cy/kill-server | Kill the cy server, disconnecting all clients. |
P | cy/paste | Paste the text in the copy buffer to the current pane. |
p | cy/replay | Enter replay mode for the current pane. |
Panes
Sequence | Action | Description |
---|---|---|
C | action/jump-command | Jump to the output of a command. |
; | action/jump-pane | Jump to a pane. |
c | action/jump-pane-command | Jump to a pane based on a command. |
k | action/jump-project | Jump to a project. |
: | action/jump-screen-lines | Jump to a pane based on screen lines. |
l | action/jump-shell | Jump to a shell. |
x | action/kill-current-pane | Kill the current pane. |
n | action/new-project | Create a new project. |
j | action/new-shell | Create a new shell. |
Viewport
Sequence | Action | Description |
---|---|---|
2 | action/margins-160 | Set size to 160 columns. |
1 | action/margins-80 | Set size to 80 columns. |
- | action/margins-bigger | Increase margins by 5 columns. |
+ | action/margins-smaller | Decrease margins by 5 columns. |
g | action/toggle-margins | Toggle the screen's margins. |
Unprefixed
These bindings are not prefixed by ctrl+a.
Sequence | Action | Description |
---|---|---|
ctrl+l | action/next-pane | Move to the next pane. |
Fuzzy finding
input/find has several key bindings that are not yet configurable, but are worth documenting.
Sequence | Description |
---|---|
ctrl+k or up | Move up one option. |
ctrl+j or down | Move down one option. |
enter | Choose the option under the cursor. |
ctrl+c or esc | Quit without choosing. |
Replay mode
The actions found in the tables below are only valid in a pane that is in replay mode. Replay mode uses two isolated binding scopes that can be accessed by providing :time
(for time mode) or :copy
(for copy mode) to a key/bind call:
(key/bind :time ["ctrl+b"] (fn [&] (do-something)))
Time mode
Sequence | Action | Description |
---|---|---|
g g | replay/beginning | Go to the beginning of the time range (in time mode) or the first line of the screen (in copy mode). |
[ c | replay/command-backward | In time mode, jump to the moment in time just before the last command was executed. In copy mode, move the cursor to the first character of the last command that was executed. |
] c | replay/command-forward | In time mode, jump to the moment in time just before the next command was executed. In copy mode, move the cursor to the first character of the next command. |
G | replay/end | Go to the end of the time range (in time mode) or the last line of the screen (in copy mode). |
ctrl+c | replay/quit | Quit replay mode. |
esc | replay/quit | Quit replay mode. |
q | replay/quit | Quit replay mode. |
n | replay/search-again | Go to the next match in the direction of the last search. |
? | replay/search-backward | Search for a string backwards in time (in time mode) or in the scrollback buffer (in copy mode). |
/ | replay/search-forward | Search for a string forwards in time (in time mode) or in the scrollback buffer (in copy mode). |
N | replay/search-reverse | Go to the previous match in the direction of the last search. |
space | replay/time-play | Toggle playback. |
left | replay/time-step-back | Step one event backward in time. |
right | replay/time-step-forward | Step one event forward in time. |
Copy mode
Copy mode is entered from time mode by triggering any form of movement, whether that be scrolling or manipulating the cursor.
Sequence | Action | Description |
---|---|---|
y | replay/copy | Yank the selection into the copy buffer. |
v | replay/select | Enter visual select mode. |
Movements
Movements in copy mode are intended to be as close to vim
as possible, but many more will be added over time.
Sequence | Action | Description |
---|---|---|
g g | replay/beginning | Go to the beginning of the time range (in time mode) or the first line of the screen (in copy mode). |
[ c | replay/command-backward | In time mode, jump to the moment in time just before the last command was executed. In copy mode, move the cursor to the first character of the last command that was executed. |
] c | replay/command-forward | In time mode, jump to the moment in time just before the next command was executed. In copy mode, move the cursor to the first character of the next command. |
j | replay/cursor-down | Move cursor down one cell. |
backspace | replay/cursor-left | Move cursor left one cell. |
left | replay/cursor-left | Move cursor left one cell. |
ctrl+h | replay/cursor-left | Move cursor left one cell. |
h | replay/cursor-left | Move cursor left one cell. |
right | replay/cursor-right | Move cursor right one cell. |
l | replay/cursor-right | Move cursor right one cell. |
space | replay/cursor-right | Move cursor right one cell. |
k | replay/cursor-up | Move cursor up one cell. |
G | replay/end | Go to the end of the time range (in time mode) or the last line of the screen (in copy mode). |
$ | replay/end-of-line | Move to the last character of the physical line. Equivalent to vim's $ . |
g $ | replay/end-of-screen-line | Move to the end of the screen line. Equivalent to vim's g$ . |
^ | replay/first-non-blank | Move to the first non-blank character of the physical line. Equivalent to vim's ^ . |
g ^ | replay/first-non-blank-screen | Move to the first non-blank character of the screen line. Equivalent to vim's g^ . |
ctrl+d | replay/half-page-down | Scroll the viewport half a page (half the viewport height) down. |
ctrl+u | replay/half-page-up | Scroll the viewport half a page (half the viewport height) up. |
; | replay/jump-again | Repeat the last character jump. |
F re:. | replay/jump-backward | Jump to the previous instance of char on the current line. |
f re:. | replay/jump-forward | Jump to the next instance of char on the current line. |
, | replay/jump-reverse | Repeat the inverse of the last character jump. |
T re:. | replay/jump-to-backward | Jump to the cell before char after the cursor on the current line. |
t re:. | replay/jump-to-forward | Jump to the cell before char after the cursor on the current line. |
g _ | replay/last-non-blank | Move to the last non-blank character of the physical line. Equivalent to vim's g_ . |
g end | replay/last-non-blank-screen | Move to the last non-blank character of the screen line. Equivalent to vim's g<end> . |
g M | replay/middle-of-line | Move to the middle of the physical line. Equivalent to vim's gM . |
g m | replay/middle-of-screen-line | Move to the middle of the screen line. Equivalent to vim's gm . |
q | replay/quit | Quit replay mode. |
ctrl+c | replay/quit | Quit replay mode. |
esc | replay/quit | Quit replay mode. |
down | replay/scroll-down | Scroll the viewport one line down. |
up | replay/scroll-up | Scroll the viewport one line up. |
n | replay/search-again | Go to the next match in the direction of the last search. |
? | replay/search-backward | Search for a string backwards in time (in time mode) or in the scrollback buffer (in copy mode). |
/ | replay/search-forward | Search for a string forwards in time (in time mode) or in the scrollback buffer (in copy mode). |
N | replay/search-reverse | Go to the previous match in the direction of the last search. |
home | replay/start-of-line | Move to the first character of the physical line. Equivalent to vim's 0 . |
0 | replay/start-of-line | Move to the first character of the physical line. Equivalent to vim's 0 . |
g home | replay/start-of-screen-line | Move to the first character of the screen line. Equivalent to vim's g0 . |
g 0 | replay/start-of-screen-line | Move to the first character of the screen line. Equivalent to vim's g0 . |
s | replay/swap-screen | Swap between the alt screen and the main screen. This allows you to return to the pane's scrollback without quitting a program that is using the alternate screen, such as vim or htop. |
Groups and panes
cy
contains a simple abstraction for isolating settings and functionality to a particular set of panes. This is referred to as the node tree which consists of panes and groups.
Panes
A pane refers to a terminal window with a process running inside it, typically a shell or text editor. Every pane has a name. Panes in cy
work exactly the same way that they do in tmux
: you can have arbitrarily many panes open and switch between them on demand.
Groups
Every pane cy
belongs to a group. A group has a name and children, which consist of either panes or other groups.
Groups also have two unique features:
- Key bindings: You may define key bindings that will only activate when you type that sequence while attached to any descendant of that group.
- Parameters: A key-value store that can be interacted with using cy/get and cy/set. Parameters are used both to configure aspects of
cy
and also to create any functionality you desire by storing state incy
's tree.
The node tree
The combination of groups and panes in cy form a tree that is similar to a filesystem. For example, a cy
session may end up with a structure that looks like this:
/ (the root group, which has no name)
├── /my-project
│ ├── /pane-1
│ ├── /pane-2
│ └── /group-2
│ └── /pane-3
└── /another-group
Nodes in the tree can be referred to by their path, such as /my-project/pane-1
. Node paths are not required to be unique, however. They are only presented as a convenient conceptual model for the user.
Instead, each node is permanently assigned a unique identifier (which is just an integer) referred to as a node ID and the related API calls only accept those IDs.
Inheritance
cy
's flexibility comes from the way key bindings and parameters interact:
- Key bindings are inherited down the tree, but can be overridden by descendant groups.
- Parameters work the same way: cy/get will get the value of a parameter from the closest parent group that defines it.
Imagine that you are attached to /my-project/group-2/pane-3
in the example above:
- If
/my-project
defines a binding with the sequencectrl+a
b
and/my-project/group-2
also defines one that begins withctrl+a
, the latter will take precedence. - If
/my-project
defines a value for a parameter:some-parameter
and/my-project/group-2
does not,(cy/get :some-parameter)
will retrieve the value from/my-project
.
One of cy
's goals is for everything to be configured solely with key bindings and parameters; in this way cy
can have completely different behavior depending on the environment and project.
Parameters
cy
contains a primitive key-value store it refers to as parameters. In addition to being available for use from Janet for arbitrary purposes, parameters are also the primary means of configuring cy
's behavior.
Parameters are set with cy/set and retrieved with cy/get:
(cy/set :some-parameter true)
(cy/get :some-parameter)
# returns true
Default parameters
Some parameters are used by cy
to change how it performs certain operations.
Parameter | Default | Description |
---|---|---|
:data-dir | inferred on startup | the directory in which .borg files are saved; if empty, recording to file is disabled |
:animate | true | whether animations are enabled (disabled over SSH connections by default) |
:default-shell | inferred from $SHELL on startup | the default command used for cmd/new |
Replay mode
One of cy
's most important features is the ability to record, play back, and search through everything that happens in your terminal sessions. You can invoke replay mode at any time by typing the key sequence ctrl+a
p
by default or by scrolling up with the mouse if the pane is connected to a shell.
A note about recording
cy
does not and will never record what you type (otherwise known as "standard in" or stdin
). It only records the output of the process (otherwise known as "standard out" or stdout
) that is attached to your virtual terminal and nothing more.
This is a basic safety measure so that your passwords (such as for sudo
) never appear in cy
's recordings. Your secrets (such as authentication keys and tokens) still will if they ever appear on your screen, so caution is advised.
In the future, cy
may give you more fine-grained control over specifically what it records and when, but for now this is not configurable.
If you wish to opt out of recording to disk entirely, set the :data-dir
parameter to an empty string. Note that cy
will continue to hold on to your terminal sessions in memory.
Modes
Like vim
, replay mode is modal, meaning that it has several different modes that it can be in that influence both what is shown on the screen and what you can do:
- Time mode: allows you to pause, play, and move through the entire history of the current pane back to when it first began.
- Copy mode: allows you to explore the state of the screen at a particular point in time. This includes the scrollback buffer, which is traditionally all that
tmux
's copy mode gave you access to. - Visual mode: This is a submode of copy mode that permits you to select text and copy it into your user-specific copy buffer that can then be pasted elsewhere.
Time mode
Time mode is similar to a video player: you can pause, play, and skip through time to inspect particular moments in the history of a pane. While playing back terminal events, time mode skips inactivity, here defined as any idle period longer than a second.
Searching
Time mode also allows you to search through the pane's history using regular expressions. In this way you can find all instances of a string if it ever appeared on the screen--even if it was subsequently cleared away.
You can initiate a search by hitting /
to search forward in time and ?
to search backward (by default). Searching supports full regex patterns; you must escape any special characters with \
if you wish to avoid this behavior.
The search bar also supports time expressions, which you can use to jump by a fixed amount of time. Time expressions are in the format NdNhNmNs
where N
is the number of that unit that you wish to move by. You will move in time in the direction of your search.
Some examples:
1h30s # one hour 30 seconds
3d # three days
Copy mode
To enter copy mode, all you need to do is invoke any action that would cause the cursor or the viewport to move. Like tmux
's copy mode, you can explore the state of the screen and copy text to be pasted elsewhere. Copy mode supports a wide range of cursor and viewport movements that should feel familiar to users of CLI text editors such as vim
.
Visual mode
Visual mode is initiated when you press v
(by default). It works almost exactly like vim
's visual mode does; after you have some selected some text, you can yank it into your buffer with y
and paste it elsewhere with ctrl+a
P
.
Recording terminal sessions to disk
The history of a pane is not only stored in memory; it is also written to a file on your filesystem. This means that you (and only you--cy
is careful to make sure the directory is only readable by you) can play back any session, even if it is no longer running in a cy
instance.
By default, cy
records all of the activity that occurs in a terminal session to .borg
files, which it stores in one of the following locations:
$XDG_DATA_HOME/cy
(if$XDG_DATA_HOME
is set)$HOME/.local/share/cy
(if it's not)
The directory will be created if it does not exist.
You can access previous sessions through the cy/open-log
action, which by default can be invoked by searching for open an existing log file
in the command palette (ctrl+a
ctrl+p
).
You are also free to use the API call replay/open to open .borg
files anywhere on your filesystem.
Fuzzy finding
Simple, fast, and configurable fuzzy finding is one of cy
's most important features. cy
provides a purpose-built fuzzy finder (similar to fzf) in the form of input/find, which is a function available in the API.
Choosing a string from a list
In its simplest form, input/find takes a single parameter: a Janet array of strings that it will present to the user and from which they can choose a single option:
(input/find @["one" "two" "three"])
By default, the background will be animated with one of cy
's animations. If the user does not choose anything, this function returns nil
; if they do, it will return the option that they chose.
Choosing an arbitrary value from a list
input/find also allows you to ask the user to choose from a list of items, each of which has an underlying Janet value that is returned instead of the string value that the user filters.
You do this by providing a Janet array of tuples, each of which has two elements:
- The text to be filtered
- The value that should be returned
(input/find @[
["one" 1]
["two" 2]
["three" 3]])
If the user chooses "one"
, input/find will return 1
.
Filtering tabular data
It is sometimes handy to be able to have the user choose from a row in a table rather than a single line of text. input/find allows you to provide tabular data in addition to titles for each column in the form of tuples.
(input/find
@[
[["boba" "$5" "not much"] 3]
[["latte" "$7" "too much"] 2]
[["black tea" "$2" "just right"] 1]
]
# Providing headers is optional
:headers ["drink" "price" "caffeine"])
Choosing with previews
Where input/find really shines, however, is in its ability to show a preview window for each option, which is conceptually similar to fzf
's --preview
command line flag. input/find can preview three different types of content:
- Panes: Show the current state of a pane in
cy
's node tree. This is the live view of a pane, regardless of how many other clients are interacting with it or what is happening on the screen. .borg
files: Show a moment in time in a.borg
file.- Text Render some text.
Options with previews are passed to input/find as Janet tuples with three elements:
- The text (or columns) that the user will filter against
- A Janet table describing how this option should be previewed
- The value that should be returned if the user chooses this option
Here are some examples:
(input/find @[
# A standard text preview, which will be rendered in an 80x26 window
["some text" {:type :text :text "this is the preview"} 1]
# A replay preview
["this is a borg file" {:type :replay :path "some-file.borg"} 2]
# A pane preview
["this is some other pane" {:type :node :id (pane/current)} 3]
])
input/find is used extensively in cy
's default startup script. You can find several idiomatic examples of its usage there.
API
This is a complete listing of all of the API functions that cy
provides in the top-level Janet environment (ie you do not need to import
or use
anything.)
Janet code executed with cy
can also access everything from Janet's standard library except for anything in spork
. In addition, Janet's ev
family of functions probably will not work; I have never tested them.
Concepts
Binding
Several API functions related to binding keys return a Binding
. A Binding
table represents a single key sequence and its associated function. Each table has the following properties:
:node
: the NodeID where the binding is defined.:sequence
: a list of strings representing the key sequence that will execute this action. If the original call to key/bind used a regex, it will be returned as a string with are:
prefix.:function
: the Janet function that will be called when this sequence is executed.
For example:
{
:node 1
:sequence ["ctrl+a" "t"]
:function <some function>
}
NodeID
Many API functions have a parameter of type NodeID
, which can be one of two values:
:root
which is a short way of referring tocy
's top-level group.- An integer that refers to a node in
cy
's node tree. You cannot infer these yourself, but they are returned from API functions like pane/current and group/children.
Symbols
action/choose-frame action/command-palette action/jump-command action/jump-pane action/jump-pane-command action/jump-project action/jump-screen-lines action/jump-shell action/kill-current-pane action/margins-160 action/margins-80 action/margins-bigger action/margins-smaller action/new-project action/new-shell action/next-pane action/open-log action/random-frame action/reload-config action/toggle-margins cmd/commands cmd/new cmd/path cy/detach cy/env cy/get cy/kill-server cy/log cy/paste cy/reload-config cy/replay cy/set cy/toast exec/file group/children group/leaves group/new input/find key/action key/bind key/bind-many key/bind-many-tag key/current key/get key/remap key/unbind pane/attach pane/current pane/screen path/abs path/base path/glob path/join replay/beginning replay/command-backward replay/command-forward replay/copy replay/cursor-down replay/cursor-left replay/cursor-right replay/cursor-up replay/end replay/end-of-line replay/end-of-screen-line replay/first-non-blank replay/first-non-blank-screen replay/half-page-down replay/half-page-up replay/jump-again replay/jump-backward replay/jump-forward replay/jump-reverse replay/jump-to-backward replay/jump-to-forward replay/last-non-blank replay/last-non-blank-screen replay/middle-of-line replay/middle-of-screen-line replay/open replay/quit replay/scroll-down replay/scroll-up replay/search-again replay/search-backward replay/search-forward replay/search-reverse replay/select replay/start-of-line replay/start-of-screen-line replay/swap-screen replay/time-play replay/time-playback-rate replay/time-step-back replay/time-step-forward shell/attach shell/new tree/group? tree/kill tree/name tree/pane? tree/parent tree/path tree/root tree/set-name viewport/get-frames viewport/set-frame viewport/set-size viewport/size
action/choose-frame
function
(action/choose-frame)
Choose a frame.
action/command-palette
function
(action/command-palette)
Open the command palette.
action/jump-command
function
(action/jump-command)
Jump to the output of a command.
action/jump-pane
function
(action/jump-pane)
Jump to a pane.
action/jump-pane-command
function
(action/jump-pane-command)
Jump to a pane based on a command.
action/jump-project
function
(action/jump-project)
Jump to a project.
action/jump-screen-lines
function
(action/jump-screen-lines)
Jump to a pane based on screen lines.
action/jump-shell
function
(action/jump-shell)
Jump to a shell.
action/kill-current-pane
function
(action/kill-current-pane)
Kill the current pane.
action/margins-160
function
(action/margins-160)
Set size to 160 columns.
action/margins-80
function
(action/margins-80)
Set size to 80 columns.
action/margins-bigger
function
(action/margins-bigger)
Increase margins by 5 columns.
action/margins-smaller
function
(action/margins-smaller)
Decrease margins by 5 columns.
action/new-project
function
(action/new-project)
Create a new project.
action/new-shell
function
(action/new-shell)
Create a new shell.
action/next-pane
function
(action/next-pane)
Move to the next pane.
action/open-log
function
(action/open-log)
Open a .borg file.
action/random-frame
function
(action/random-frame)
Switch to a random frame.
action/reload-config
function
(action/reload-config)
Reload the cy configuration.
action/toggle-margins
function
(action/toggle-margins)
Toggle the screen's margins.
cmd/commands
function
(cmd/commands arg0)
cmd/new
function
(cmd/new parent &named path command args name)
Run command
with args
and working directory path
in a new pane as a child of the group specified by parent
. You may also provide the name
of the new pane. If command
is not specified, (cmd/new)
defaults to the current user's shell. parent
is a NodeID.
When the command exits, it will be rerun. This is not currently configurable. If the command exits with a non-zero exit code more than three times in a second, it will not be run again.
Some examples:
# Create a new shell in the root group
(cmd/new :root)
# `args` is a list of strings
(cmd/new :root :command "less" :args @["README.md"])
cmd/path
function
(cmd/path target)
Get the working directory of the program running in the pane pane specified by target
. target
is a NodeID.
cy/detach
function
(cy/detach)
Detach from the cy
server.
cy/env
function
(cy/env)
cy/get
function
(cy/get key)
Get the value of the parameter with key key
.
cy/kill-server
function
(cy/kill-server)
Kill the cy
server, disconnecting all clients.
cy/log
function
(cy/log level message)
Log message
to the /logs
pane. level
must be one of :info
, :warn
, :error
.
cy/paste
function
(cy/paste)
Paste the text in the copy buffer to the current pane.
cy/reload-config
function
(cy/reload-config)
Detect and (re)evaluate cy's configuration. This uses the same configuration detection scheme described in the Configuration chapter.
cy/replay
function
(cy/replay &named main copy location)
Enter replay mode for the current pane.
cy/set
function
(cy/set key value)
Set the value of the parameter with key key
to value value
. For the time being, parameter values can only be strings, booleans, and integers. This is expected to change in the future.
cy/toast
function
(cy/toast level message)
Send a toast with message
to all attached clients. level
must be one of :info
, :warn
, :error
.
exec/file
function
(exec/file path)
Execute the Janet file found at path
. Throws any errors that occur during execution.
group/children
function
(group/children group)
Get the NodeIDs for all of group
's child nodes.
group/leaves
function
(group/leaves group)
Get the NodeIDs for all of the leaf nodes reachable from group
. In other words, this is a list of all of the NodeIDs for all panes that are descendants of group
.
group/new
function
(group/new parent &named name)
Create a new group with parent
and (optionally) name
.
parent
is a NodeID.
input/find
function
(input/find inputs &named prompt full reverse animated headers)
(input/find)
is a general-purpose fuzzy finder that is similar to fzf
. When invoked, it prompts the user to choose from one of the items provided in inputs
. (input/find)
does not return until the user makes a choice; if they choose nothing (such as by hitting ctrl+c
), it returns nil
.
inputs
is an array with elements that can take different forms depending on the desired behavior. See more on the page about fuzzy finding.
This function supports a range of named parameters that adjust its functionality:
:full
(boolean): If true, occupy the entire screen.:prompt
(string): The text that will be shown beneath the search window.:reverse
(boolean): Display from the top of the screen (rather than the bottom.):animated
(boolean): Enable and disable background animation.:headers
([]string): Provide a title for each column. This mostly used for filtering tabular data.
key/action
macro
(key/action name docstring & body)
Register an action. Equivalent to the Janet built-in (defn
), but requires a docstring.
An action is just a Janet function that is registered to the cy server with a short human-readable string description. It provides a convenient method for making some functionality you use often more discoverable.
In a similar way to other modern applications, cy has a command palette (invoked by default with ctrl+a
ctrl+p
, see (action/command-palette)
) in which all registered actions will appear.
key/bind
function
(key/bind target sequence callback)
Bind the key sequence sequence
to callback
for node target
, which is a NodeID, :time
(for time mode), or :copy
(for copy mode). target
can refer to any group or pane.
sequence
is a key sequence, which consists of a tuple with string elements that are either key literals ("h"
), preset key specifiers ("ctrl+a"
), or regex patterns ([:re "^[a-z]$"]
).
Read more about binding keys in the dedicated chapter.
key/bind-many
macro
(key/bind-many scope & body)
Bind many bindings at once in the same scope.
For example:
(key/bind-many :root
[prefix "j"] action/new-shell
[prefix "n"] action/new-project)
key/bind-many-tag
macro
(key/bind-many-tag scope tag & body)
Bind many bindings at once in the same scope, adding the provided tag.
key/current
function
(key/current)
Get all of the bindings accessible to the current client as an array of Bindings. It contains all of the bindings defined by the node to which the client is attached and its ancestors. In other words, this is equivalent to the list of bindings against which the client's key presses are compared.
key/get
function
(key/get target)
Get all of target
's bindings. target
is a NodeID, :time
, or :copy
. Returns an array of Bindings. Note that this does not return bindings defined in an ancestor node, only those defined on the node itself.
key/remap
function
(key/remap target from to)
Remap all bindings that begin with sequence from
to sequence to
for node target
, which is a NodeID, :time
, or :copy
. Empty sequences ([]
) are not currently supported for from
and to
.
For example, to remap all of the default bindings that begin with ctrl+a
to ctrl+v
:
(key/remap :root ["ctrl+a"] ["ctrl+v"])
key/unbind
function
(key/unbind target sequence)
Clear all bindings that begin with sequence
for node target
, which is a NodeID, :time
, or :copy
. Note that the empty sequence []
will unbind all keys in the scope.
sequence
is a key sequence, which consists of a tuple with string elements that are either key literals ("h"
), preset key specifiers ("ctrl+a"
), or regex patterns ([:re "^[a-z]$"]
).
The following code snippet will unbind all of cy
's default keybindings that begin with ctrl+a
:
(key/unbind :root ["ctrl+a"])
pane/attach
function
(pane/attach pane)
Attach to pane
, which is a NodeID that must correspond to a pane. This is similar to tmux
's attach
command.
pane/current
function
(pane/current)
Get the NodeID of the current pane.
pane/screen
function
(pane/screen pane)
Get the visible screen lines of the pane referred to by NodeID. Returns an array of strings.
path/abs
function
(path/abs path)
Return the full absolute path for path
. Calls Go's path/filepath.Abs
.
path/base
function
(path/base path)
Return the last element of path
. Calls Go's path/filepath.Base
.
path/glob
function
(path/glob pattern)
Return an array of all files matching pattern
. Calls Go's path/filepath.Glob
.
path/join
function
(path/join paths)
Join the elements of the string array paths
with the OS's file path separator. Calls Go's path/filepath.Join
.
replay/beginning
function
(replay/beginning)
Go to the beginning of the time range (in time mode) or the first line of the screen (in copy mode).
replay/command-backward
function
(replay/command-backward)
In time mode, jump to the moment in time just before the last command was executed. In copy mode, move the cursor to the first character of the last command that was executed.
replay/command-forward
function
(replay/command-forward)
In time mode, jump to the moment in time just before the next command was executed. In copy mode, move the cursor to the first character of the next command.
replay/copy
function
(replay/copy)
Yank the selection into the copy buffer.
replay/cursor-down
function
(replay/cursor-down)
Move cursor down one cell.
replay/cursor-left
function
(replay/cursor-left)
Move cursor left one cell.
replay/cursor-right
function
(replay/cursor-right)
Move cursor right one cell.
replay/cursor-up
function
(replay/cursor-up)
Move cursor up one cell.
replay/end
function
(replay/end)
Go to the end of the time range (in time mode) or the last line of the screen (in copy mode).
replay/end-of-line
function
(replay/end-of-line)
Move to the last character of the physical line. Equivalent to vim's $
.
replay/end-of-screen-line
function
(replay/end-of-screen-line)
Move to the end of the screen line. Equivalent to vim's g$
.
replay/first-non-blank
function
(replay/first-non-blank)
Move to the first non-blank character of the physical line. Equivalent to vim's ^
.
replay/first-non-blank-screen
function
(replay/first-non-blank-screen)
Move to the first non-blank character of the screen line. Equivalent to vim's g^
.
replay/half-page-down
function
(replay/half-page-down)
Scroll the viewport half a page (half the viewport height) down.
replay/half-page-up
function
(replay/half-page-up)
Scroll the viewport half a page (half the viewport height) up.
replay/jump-again
function
(replay/jump-again)
Repeat the last character jump.
replay/jump-backward
function
(replay/jump-backward char)
Jump to the previous instance of char
on the current line.
replay/jump-forward
function
(replay/jump-forward char)
Jump to the next instance of char
on the current line.
replay/jump-reverse
function
(replay/jump-reverse)
Repeat the inverse of the last character jump.
replay/jump-to-backward
function
(replay/jump-to-backward char)
Jump to the cell before char
after the cursor on the current line.
replay/jump-to-forward
function
(replay/jump-to-forward char)
Jump to the cell before char
after the cursor on the current line.
replay/last-non-blank
function
(replay/last-non-blank)
Move to the last non-blank character of the physical line. Equivalent to vim's g_
.
replay/last-non-blank-screen
function
(replay/last-non-blank-screen)
Move to the last non-blank character of the screen line. Equivalent to vim's g<end>
.
replay/middle-of-line
function
(replay/middle-of-line)
Move to the middle of the physical line. Equivalent to vim's gM
.
replay/middle-of-screen-line
function
(replay/middle-of-screen-line)
Move to the middle of the screen line. Equivalent to vim's gm
.
replay/open
function
(replay/open group path)
Open the .borg
file found at path
in a new replay window in group
.
For example:
(replay/open (tree/root) "some_borg.borg")
replay/quit
function
(replay/quit)
Quit replay mode.
replay/scroll-down
function
(replay/scroll-down)
Scroll the viewport one line down.
replay/scroll-up
function
(replay/scroll-up)
Scroll the viewport one line up.
replay/search-again
function
(replay/search-again)
Go to the next match in the direction of the last search.
replay/search-backward
function
(replay/search-backward)
Search for a string backwards in time (in time mode) or in the scrollback buffer (in copy mode).
replay/search-forward
function
(replay/search-forward)
Search for a string forwards in time (in time mode) or in the scrollback buffer (in copy mode).
replay/search-reverse
function
(replay/search-reverse)
Go to the previous match in the direction of the last search.
replay/select
function
(replay/select)
Enter visual select mode.
replay/start-of-line
function
(replay/start-of-line)
Move to the first character of the physical line. Equivalent to vim's 0
.
replay/start-of-screen-line
function
(replay/start-of-screen-line)
Move to the first character of the screen line. Equivalent to vim's g0
.
replay/swap-screen
function
(replay/swap-screen)
Swap between the alt screen and the main screen. This allows you to return to the pane's scrollback without quitting a program that is using the alternate screen, such as vim or htop.
replay/time-play
function
(replay/time-play)
Toggle playback.
replay/time-playback-rate
function
(replay/time-playback-rate rate)
Set the playback rate to rate
. Positive numbers indicate a multiplier of real time moving forwards, negative numbers, backwards. For example, a rate of 2
means that time will advance at twice the normal speed; -2
means that time will go backwards at -2x.
rate
is clamped to the range [10, 10].
replay/time-step-back
function
(replay/time-step-back)
Step one event backward in time.
replay/time-step-forward
function
(replay/time-step-forward)
Step one event forward in time.
shell/attach
function
(shell/attach &opt path)
Create a new shell initialized in the working directory path
and attach to it.
shell/new
function
(shell/new &opt path)
Create a new shell initialized in the working directory path
.
tree/group?
function
(tree/group? node)
Return true
if node
is a group, false
otherwise. node
is a NodeID.
tree/kill
function
(tree/kill node)
Remove the node
and all of its child nodes. This will halt execution of any descendant panes. node
is a NodeID.
tree/name
function
(tree/name node)
Get the name of node
, which is a NodeID. This is the name of the node itself, not its path.
tree/pane?
function
(tree/pane? node)
Return true
if node
is a pane, false
otherwise. node
is a NodeID.
tree/parent
function
(tree/parent node)
Get the NodeID for the parent of node
. If node
is :root
, return (tree/parent)
returns nil
.
tree/path
function
(tree/path node)
Get the path of node
, which is a NodeID.
tree/root
function
(tree/root)
Get the NodeID that corresponds to the root node.
tree/set-name
function
(tree/set-name node name)
Set the name of node
to name
. name
will be stripped of all whitespace and slashes. node
is a NodeID.
viewport/get-frames
function
(viewport/get-frames)
Get a list of all of the available frames.
viewport/set-frame
function
(viewport/set-frame frame)
Set the frame to frame
, which is an identifier for the desired frame.
You can get all of the available frames with (viewport/get-frames)
and also browse them here.
viewport/set-size
function
(viewport/set-size size)
Set the size of the inner viewport. size
is a tuple in the form [rows, columns]
.
viewport/size
function
(viewport/size)
Get the size of the inner viewport. Returns a tuple [rows, columns]
.
Acknowledgments
cy is the result of hundreds of hours of work (and, perhaps, hundreds more.) I would like to thank the following people for their efforts to support me in this project:
- A.J., with particular gratitude for contributing the name
taro
and a few of the original ideas that inspiredcy
- S.K., for pushing me to pursue this bizarre blend of art and utility
- V.P., for being a persistent source of inspiration
- B.P., for being a sounding board in the early days when I had no idea whether
cy
was even possible - And countless others who noticed what I was doing when I worked on
cy
in coffee shops, on airplanes, and elsewhere