| AIT(1) | General Commands Manual | AIT(1) |
ait - small yet mighty GNU Emacs style editor
ait [-hv]
ait [-b backup_dir] [-s switch_command] [-r
register_dir] [-S shell_command] [-O open_command] [-c
key=command] [file [+/-number] ...]
ait is intended to be small, portable, and powerful Emacs-like text editor. While those are the top 3 main goals, ait also is intended to be simple in both implemetation and use, support the most important GNU Emacs keybindings, support UTF8 and unicode, to not reinvent the wheel, and to be stable. You will find many differences between GNU Emacs and ait as ait is not intended to be an Emacs clone. Some of the most prominent differences are: the lacks of a config, of lisp, of 100% custom window layouts, of major syntax highlighting, and of modes in general. You'll also find that ait doesn't treat the MSGLINE as its own buffer. This keeps the interface simple and less complicated. ait instead is simple enough that you can change the source to change the keybindings, uses the existing system as the extension language (see SHELL COMMANDS), uses a simple static-window system that works for 99% of all editing purposes, and supports the bare-minimum syntax highlighting (see SYNTAX HIGHLIGHTING). Think of ait as a microEMACS implementation of GNU Emacs with concepts from Plan 9's acme editor.
ait is written using a gap buffer and therefore some of the lingo used to describe various behaviors come from this. The point is the location of the cursor in the buffer. The mark is a point that is set by the user to define either the beginning or end of the region. The region is used for a variety of functions such as cut, copy, and shell-command. The point and mark are both buffer specific. Each time the a mark is added, it is added to a mark history. The poptomark command will allow you to jump to previous marks using this history.
There are some note-worthy special cases that may confuse users at first. Firstly, persistent column. If you haven't explicitly changed the current column using something fwd-word, back-char, etc. ait will remember which column you're on. This makes editing things that are in the same column but seperated by short lines a lot easier. Secondly, brack-matching is supported for all heterogeneous bracket types (), {}, <>, and []. It is not supported for homogeneous ones ("", '', ``). You can, however, still use forward-bracket and backward-bracket to jump to them. Matched brackets are highlighted in magenta. If you've just typed the closing or opening bracket, ait will highlight the match in green until you move the cursor or the editor is repainted. Thirdly, regarding the forward/backward-bracket functions, if there is a mark they will overshoot to allow you to select the entire enclosed text and its brackets. For example, if you have the code (+ 1 2) and the point is on ( and you place a mark there and run forward-bracket, the point will actually go one character to the right of the), effectively allow you to kill that entire block of code. If the point were on the ) and you place a mark there and run backward-bracket, the mark will be moved one character to the right of the ) and then the point moved to the (. The two previous cases only work when the point is on the bracket and not next to it like in GNU Emacs. If a line goes over the allotted column wide for a window, it will automatically line-wrap. To show this, the last character of the row will be highlighted yellow.
If you have a region, and you press input a space character it will indent the entire region by 1 space character. If you press backspace/C-h, it will un-indent the region by 1 space character. This allows you to carefully indent regions to exactly where you need them.
When a file is loaded into ait, it is stored in a buffer. This buffer may be displayed on the screen in more than one window. Each window is delineated by a modeline at the bottom. The modeline contains important information about the buffer inside the window. The second position in the modeline will contain an "O" if that buffer is in overwrite mode. If changes are made to a buffer, you will see an asterisk in the third position of that buffer's window's modeline. If a file is changed outside ait and its buffer is about to be changed, ait prompts if the change should go ahead (y), not go ahead (n) or if the buffer should be reverted (r) to the latest file on disk. The default buffer is called *scratch* and is not saved when you close the program. In the modeline you will also find the buffer name. This name is usually the same as the file's name unless there is another buffer loaded with the same file name. In that case, the buffer name will contain the directory name in the name i.e. dir/foo.txt. If another buffer contains the same previous directory, the sync will continue until a non-match is found. The file name usually contains the entire path to that file and is seen when you save the buffer. Next in the modeline is the row and column inside of parenthesis. Lastly, there is the percentage of the buffer you're viewing. If you're at the top and you can't page up anymore, you'll see TOP. If you can't page down anymore you'll see BOT. Otherwise, you'll see the percent.
Unlike GNU Emacs, ait doesn't allow the user to make endless window configurations. There are only 8 supported window modes: one, horizontal, vertical, triple horizontal, triple vertical, Fibonacci right, Fibonacci left, and quad. Horizontal and vertical mode are 2 window splits in the respective direction. The triple modes are the same as the previous just with 3 windows. Fibonacci modes are modes that have 2 small windows that make up the height of the third large window. It is called this because it resembles the first 3 squares in the Fibonacci sequence. Lastly, quad mode is a 4 window mode with 4 windows, one in each quadrant. Also unlike GNU Emacs, close-window doesn't exist. You can only change window modes and so the keybinding C-x 0 will take you back to one window mode. When you change modes ait will attempt to fill the windows by following the buffer trail (explained in the next parapgraph). This isn't always right but is extremely handy.
The order of buffers and windows is not handled by any array or list. They are handled by pointers that point to other pointers, thus creating a "trail" of sorts. The buffer trail is the path to the order of the open buffers i.e. current-buffer(foo.txt->b_next(bar.txt)->b_next(README)->b_next(NULL). The list must always end with NULL.
Under all windows and modlines is the prompt area, namely, the msgline. This is where all non-editing input is handled. Unlike GNU Emacs, the msgline is not a buffer but a special place for messages and prompts. In most prompts, most of the basic movement keybindings are usable: backward-char, forward-char, back-word, fwd-word, delete, backspace, kill-line, beginning-of-line, end-of-line, back-word-delete, fwd-word-delete, and insert-control-char.
There are some unique special things that you may seen while using ait that may spark a question. First, a tab character is denoted by a 4 space line yellow UTF-8 character. This makes it easy to see whether spaces or actual tabs are being used. This character doesn't show correctly when you're in the tty. Second, if control characters make it into the file they are denoted, as GNU Emacs does, by a ^ followed by the letter that corresponds to that control character in red foreground cololr. For example, the form feed control character (ASCII 0x0C) would show up as ^L because 0x4C is an L in ASCII. Third, trailing whitespace is denoted by a red background color but only shows when you are not at the end of the trailing space. Fourth, completely empty lines that contain no buffer data are denoted by a cyan tilde (~) similarly to how vi(1) does it. This makes it easy to see when you're at the bottom of the file visually. Alternatively one could use the modeline BOT string to obtain the same conclusion.
Keybindings in ait are written similarly to other Emacs clones. "C" means control and "M" means meta. Therefore, "C-x" means control plus the x key and "M-x" means meta/alt plus the x key. Since ait is usable on pretty much any terminal, it was selected to use esc instead of "M" to describe meta. Therefore, "esc x" means the same as "M-x". The below list has the keybinding in bold, followed by the common name for the function that the keybinding runs, followed by a description on how that function works.
switch(foo) {
case 1:
}
and your point is where the colon is. You press return and now you're under the 'c'. If you wanted the point to be under the colon, you could use this function to jump to that level of indent. If negated, this will apply to the line below. Numerical argument is also supported but universal argument is not. The above example isn't the best but it does explain how this feature works.
isearch stands for incremental search and is the normal way to search for something in a buffer. isearch has two modes: isearch and isearch-reverse. isearch goes down the buffer and reverse goes up. It is paramount that you understand how the prompt for isearch works to use it to it's best ability. While in the isearch function is running, you have a few keybindings at your disposal other than the normal msgline keybinds:
Once you've reached a point where there are no more matches, pressing the respective keybind (C-s in isearch, C-r in isearch-reverse) will continue the search from the beginning or end of the buffer respectively. Lastly, if you type an all lowercase query it will search for matches regardless of case; meaning that it searches with case insensitivity. If you put any uppercase letter into the query, the search now becomes case sensitive.
It's not overtly obvious when a undo set happens, the explanation is quick. An undo set happens whenever you break a chain of similar commands - if you are typing a big paragraph but don't manually move the cursor, delete anything, or run any other commands you'll find the undo will remove that entire paragraph. This is because you haven't broken the chain of commands. A redo only becomes available once you've undone something.
Numerical argument is a way to run a keybinding many times. It is most useful when used in combination with keyboard macros but can also be nice when doing normal editting as well. When you begin entering a numeric argument you'll see "C-u x", where x is the number you've added, in the msgline. Upon entering the next number you will find that it doesn't add to the original number but rather shifts the original number into the next most significant digit. This makes it very easy to do massive recurring edits.
The query-replace function is useful to replace multiple occurances of something with another something. This function is very straightforward on how to use so an explanation isn't needed. Once in the search, 'y' will accept the replace, replace the query with the replacement, and move to the next match; 'n' will skip the current match, '!' will accept all occurances without asking, and 'q' will quit. You may also use C-g to quit before you get to searching part or C-g and enter in the searching part. If there are more instances of the query, you may use 'l' (stands for last) to replace the current result and then quit. This is useful when you want to just replace a handful but don't want to be jumped to the next result.
Similarly to GNU Emacs, if you consecutively execute the fwd-/back-word-delete commannds, it will add each new cut word to the scrap. Also, the undo/redo commands will undo these consecutive commands in 1 use similarly to how it works with consecutive character input or deletion.
Also similar to GNU Emacs (and many other editors), ait can collect keyboard data into a keyboard macro. This feature does not record everything but most commands can be used with it. You must first start recording by executing start-kbd-macro. You will see a K in the modeline of the current buffer where the O for overwrite mode would be. Then perform the actions you wish to run later. All editing and movement commands can be recorded. File, buffer, suspend, and the like commands cannot be recorded, for obvious reasons. Once recording is finished, run the end-kbd-macro command. You can then execute it with execute-kbd-macro.
Every time you cut (sometimes referred to as kill) some text, it gets placed into the scrap buffer. This buffer is placed into the kill ring once it is overwritten. The kill ring is a list of previously cut, or killed, data from the scrap buffer. You can recall this data by using the universal argument on the yank command. Each universal argument represents the next item in the kill ring. Most of the time you'll need something that you recently yanked and will only have to use 1, 2 or, at most 3 universal arguments. This feature is very handy and allows you to kill text without having to worry about yanking it somewhere else while you do some other editing.
Not to be confused with the next section SHELL COMMANDS , this section is about a special type of shell command that is to be used via a keybinding. All command keybindings are C-c prefixed, meaning you have to press C-c first and then the remaining key/s. These are only set by the -c argument. I.e. "ait -c c=/foo/bar/compiler", then you can run /foo/bar/compiler by pressing C-c c. If you want to use another control keybinding, just prefix it with a ^: "ait -c ^c=/foo/bar/complier" and now C-c C-c will compile. There isn't any support for meta/esc yet.
One thing that makes these commands different than the shell commands is that this a specfic for ait them as commands. All commands passed here must accept piped data, just like shell commands used with a region. The reason for this is that ait will pipe either the region or the entire file into the command. Unlike shell commands these commands will also be passed 4 arguments, in this order: the current line number, the current column, the current point, and the entire path for the current buffer's file. Commands can also be run as shell commands and ait will automatically pass in those arguments. This is just in case you forget that you have it assigned and run it as a shell command. You can do all the normal shell command stuff with a command that is input this way such as redirecting the output to another buffer with prefixing < and handling stdout interfaces and manually placing the output into the temp file (more on this in SHELL COMMANDS). Let's say you wanted difference behavior when you run your command in the msgline via shell command versus when you run it via a keybind. This just requires your command to be a little more argument-aware. When ait goes to run a shell command, it will check the list of commands and if there is a match. But before doing that it checks if there are any prefixes on it such as ! and <. That being said, we can tell ait we want our command to _not_ pass in the arugments by prefixing it with %. If you have a program that you want to pass the entire buffer into it as if it's a command, prefix it with |. For example, if you have a script that you run every once in a while but don't really want it as a command, you could just run it occasionally like: |my_script.
The last thing to note about commands is that these are used for modifying the current buffer, not for controlling ait piped data and don't do anything to it. When doing this you could run a compile command, view top(1), something important, or something dumb and your buffer won't be effected.
One of the most powerful features in ait is the support to open files using custom commands and running shell commands. When running a shell command (esc x) there are 2 types: input and replace. Input happens when you have no region and you want to input the output of a command. One of the best uses of this is with xclip(1), xsel(1) or pbpaste(1) (on macOS) allowing you to paste in the editor. Replace happens when there is a region. In a region command the region is passed into the shell command and the output of that command, unless empty (just contains a null terminator or newline), is then placed where the region was. One of the best uses of this is a spell checker or, once again, by using system clipboard commands. ait ships with an example script called "spell" that uses this technique, however, it requires pick(1) and aspell(1) to be installed. Shell command also allows you to input the current buffer's filename (including its path), line number, and column number by placing a single @, #, or $, respectively. For example, "cat @" would translate to "cat /path/to/foo.txt". ait will not get confused if you supply an environment variable such as $LANG or if you escape the symbol via backslash( such as @ or One last thing to note about shell-command is that these programs are run synchronously and will block the use of ait until it's finished. If you want to open a command in a new buffer, you can use a < at the beginning of the command such as "< ls $HOME/Documents" and the output will be output into the *Shell Command Output* buffer.
open-command (esc o) is very straightforward, use anything you want to find the file you want to open and make a script that returns just the file and path. You can have ait open the file at a certain line number if the filename is appended with a colon, followed by the line number. This makes it easier to work with standard commands such as grep(1)'s -n option. The caveat to this is that you cannot use open-command to open files that have a colon in the file name. While this is non-standard, it's worth mentioning. ait ships with a few examples of this "ff" (find file) and "gg" (git grep) which both require pick(1) and git(1) to be installed. There may, in the future, be a way to have commands that don't effect the buffer or commands that effect the entire buffer added in later version.
One very important note is that ait uses the system(3) function and directs the output into a temp file at the env variable AIT_TEMP. This variable is set when ait starts up and its a random temp file. The file is then read by ait and that's how it gets the output. Using system(3) is really handy because it allow certain programs to work better than when ait used popen(3) outputs something via echo(1) but in doing so the echoed text will be output to the temp file. You can prevent this from happening by writing the script like normal and then just echoing the output you want directly to AIT_TEMP. Then, in ait when you wish to run the script, prefix the entire command with !. This special character tells ait to not write the output to the temp file and expects the script to handle it itself. For example:
#!/bin/sh
echo "1. option one"
echo "2. option two"
echo "3. option three"
read -p "> " output
echo "$output" > "$AIT_TEMP"
EOF
Prefixing a shell command with % will do nothing as % is only used for commands. When you run a shell/open command, the command is placed into a history array. You can tranverse this list by using up/down arrows or C-p/C-n.
While ait supports syntax highlighting, it only supports the bare minimum of it. The reason for this is that syntax highlighting that hightlights too much isn't helpful. Instead ait uses an extremely simple and straight-forward syntax highlighting approach that is designed to be simple in implementation to avoid complexity in the code. Most of the time, the syntax highlighting just highlights keywords in the language. It does so by looking at the file extension or filename so if the file lacks an extension or name found in the list, you sadly won't get highlighting. How to create new modes can be found in the syntax.h file of the project and will not be explained here.
While prompting for a file, buffer, or shell command one can use the tab key or C-i to preform some basic tab completion. In the file prompt, one you're in a directory tabbing will cause the next alphabetic file or directory to populate. You can continue to press tab to go down the line. Note that you can type more and it will change the completion to fit that newly typed text. Once you get to the end, it will loop back. If you've come across a directory, you will need to press return or another character to use the tab completion inside of that directory. When prompted for a buffer, it works exactly the same except there are no directories. When prompted for a shell command via the shell-command or open-command commands, it will search your PATH environment variable for matches and loop around again if nothing is found. It's very important to note that tab completion only ever works when you're at the end of the msgline buffer.
Backups are created right before the buffer is written to disk. They contain the file's contents before it is overwritten.
Backup files usually end in ~ and are, by default, placed in the working directory. You can optionally set a backup directory with the -b option. Backups that are sent to the the backup directory contain the entire path with the slashes replaced with exclamation points i.e. /home/foobar/foo.txt -> !home!foobar!foo.txt. The best way to use this is by setting an alias for in your .profile for each user.
Registers are a handy way to save data that you might want to recall later, quickly. Registers are only available if you have the register_dir set via the -r option. You can change the location of the register directory so that you can point only the registers that matter for the particular work you're working on. For example, if you're logged in as root you might have commonly edited file paths saved to quickly open and edit them but if they require root you don't need them when underprivileged.
ait is a fork of an editor called atto. Atto was a fork of an editor called AE. From Atto's README: "Atto is based on the public domain code of Anthony Howe's editor (commonly known as Anthony's Editor or AE, [2])..." That being said, parts of ait are written by all three of us: Anthony Howe, Hugh Barney, and Kevin Bloom.
Kevin Bloom is the current maintainer.
You may view and track issues in the ISSUE.txt file that comes with the source of ait
Combined emojis will also cause the modeline to claim you're column number is 1 more than it really is. This is caused by the extra emoji and invisible combinator character. This bug is so minimal I don't find the need to fix it at this point.
As of right now FreeBSD, OpenBSD, and GNU/Linux with muslc are not actively tested. There may or may not be many bugs on those systems.
Report bugs to ktnb at NetBSD dot org.
Public Domain 1991, 1993 by Anthony Howe. No warranty.
Public Domain 2014-2022 by Hugh Barney. No warranty
Copyright © 2023-2026 Kevin "The Nuclear" Bloom. ait comes with ABSOLUTELY NO WARRANTY. You may redistribute copies of ait under the terms of the BSD 3-Clause License. For more information about these matters, see the file named LICENSE.
mg(1), emacs(1). vi(1).
| May 2026 | ait 1.15 |