mle is a small, flexible console text editor written in C.
View more demos here.
View large-file startup benchmark here.
- Keep codebase small
- Require minimal dependencies
- Make extensibile and configurable
- Favor simplicity over portability
- Use shell commands to enhance functionality (e.g., grep, tree)
- Enjoy writing C
- Annoy world with another text editor
- Small codebase (~10k sloc)
- Only 1 out-of-repo dependency (pcre)
- Full UTF-8 support
- Syntax highlighting
- Stackable key maps (modes)
- Extensible via stdio
- Scriptable rc file
- Built-in text editing language (lel)
- Key macros
- Multiple splittable windows
- Regex search and replace
- Large file support
- Incremental search
- Linear undo and redo
- Multiple cursors
- Smart indent
- Headless mode
- Navigation via ctags
- Movement via less
- Fuzzy file search via fzf
- File browsing via tree
- File grep via grep
$ git clone --recursive https://github.com/adsr/mle.git
$ cd mle
$ sudo apt-get install libpcre3-dev # or `yum install pcre-devel`, `pacman -S pcre`, etc
$ make
You can run make mle_static instead to build a static binary.
To install to /usr/bin:
$ make install
To install to a custom directory, supply DESTDIR, e.g.:
$ DESTDIR=/usr/local/bin make install
(The following assumes default configuration.)
Run mle to start editing a new text file. Type to type, use directional keys
to navigate, Ctrl+F to search, Ctrl+T for search-and-replace, Ctrl+S to
save, and Ctrl+X to exit. Press F2 for full help.
Run mle existing-file to edit an existing file. To edit multiple files try
mle file1 file2 and use Alt+n and Alt+p to switch between them. Press
Alt+c to close a file. You can also specify mle file.c:100 to start the
editor at a certain line number.
mle is customized via command line options. Run mle -h to view all cli
options.
To set default options, make an rc file named ~/.mlerc (or /etc/mlerc). The
contents of the rc file are any number of cli options separated by newlines.
Lines that begin with a semi-colon are interpretted as comments.
If ~/.mlerc is executable, mle executes it and interprets the resulting stdout
as described above. For example, consider the following snippet from an
executable ~/.mlerc PHP script:
<?php if (file_exists('.git')): ?>
-kcmd_grep,M-q,git grep --color=never -P -i -I -n %s 2>/dev/null
<?php endif; ?>
This overrides the normal grep command with git grep if .git exists in the
current working directory.
mle is extensible via any program capable of standard I/O. A simple line-based request/response protocol enables user scripts to register commands and invoke internal editor functions in order to perform complex editing tasks. All messages are URL-encoded and end with a newline.
Example exchange between a user script (usx) and mle:
usx -> mle method=editor_register_cmd¶ms%5B%5D=hello&id=57cc98bb168ae
mle -> usx result%5Brc%5D=0&id=57cc98bb168ae
...
mle -> usx method=hello¶ms%5Bmark%5D=76d6e0¶ms%5Bstatic_param%5D=&id=0x76d3a0-0
usx -> mle method=mark_insert_before¶ms%5B%5D=76d6e0¶ms%5B%5D=hello%3F¶ms%5B%5D=5&id=57cc98bb6ab3d
mle -> usx result%5Brc%5D=0&id=57cc98bb6ab3d
usx -> mle result%5Brc%5D=0&error=&id=0x76d3a0-0
In the example above, the user script registers a command called hello at
startup, and mle replies with success. Later, the end-user invokes the hello
command, so mle sends a request to the user script. The user script receives the
request and sends a sub-request invoking mark_insert_before, to which mle
replies with success. Finally the user script returns overall success for the
hello command.
Currently, mle only accepts requests from user scripts while a request to the
user script itself is pending. (In other words, mle enforces an "only do stuff
if I ask you to" policy.) The exception to this is editor_register_cmd which
can be invoked by user scripts at startup time.
For end-users, user scripts are loaded via the -x cli option. Commands
registered by user scripts can be mapped to keys as normal via -k.
mle comes with a built-in text editing language called lel, inspired by the
sam command language. lel
commands can be entered manually at a prompt, or bound to any key via
-kcmd_lel,<key>,<lelcmd>.
Motions
/re/ next regex
?re? prev regex
'str' next string
"str" prev string
r/re/ next regex (/ is any delim)
R/re/ prev regex
f/str/ next string (/ is any delim)
F/str/ prev string
ta next char (a is any char)
Ta prev char
w next word outer
W prev word outer
n next word inner
N next word inner
l0 line absolute
l-2 line relative
#5 column absolute
#+1 column relative
^ beginning of line
$ end of line
g beginning of file
G end of file
h beginning of sel
H end of sel
ma mark (a is [a-z])
~ origin mark
Actions
a/str/ insert string before cursor (/ is any delim)
J insert newline before cursor
c/str/ change sel to string
i/str/ insert string after cursor
d delete sel
s/re/x/ regex replace sel
k cut sel
y copy sel
Y copy append sel
v paste
|`cmd` pipe sel to shell cmd (` is any delim)
Cursors
D drop cursor anchor
U lift cursor anchor
z drop sleeping cursor
Z awake all cursors
. collapse cursors
O swap cursor mark with anchor
Ma drop mark (a is [a-z])
! move user cursor to lel cursor
Registers
<a prepend sel to register (a is [a-z])
>a append sel to register
=a set register to sel
_a clear register
Aa write out register (before cursor)
Ia write out register (after cursor)
Sa/re/str/ like A, but regex replace output
Loops, conditionals, etc
L cmd foreach line, do cmd
x/re/cmd foreach regex match, do cmd (/ is any delim)
X/re/cmd ditto, with dropped anchor
q/re/cmd if re matches sel, do cmd
Q/re/cmd if re does not match sel, do cmd
{ c1 c2 } group commands
9cmd repeat command (9 is any number)
Examples
Delete next 4 lines
4d
Find first occurrence of 'int'
g 'int' !
Find last occurrence of 'int'
G "int" !
Remove all tabs from file
L s/\t//
Multi-cursor select all matches of 'var'
X/var/z Z
Hard wrap file at column 79
g D G |`fold -sw79`
Comment out lines containing 'todo'
L q/todo/ { ^ a|//| }
Copy func protos here, replacing bracket with semi-colon
x/^static.*{/>a ~ Sa/ {/;/ _a
mle provides support for non-interactive editing which may be useful for using the editor as a regular command line tool. In headless mode, mle reads stdin into a buffer, applies a startup macro if specified, and then writes the buffer contents to stdout. For example:
$ echo -n hello | mle -M 'test C-e space w o r l d enter' -p test
hello world
If stdin is a pipe, mle goes into headless mode automatically. Headless mode can
be explicitly enabled or disabled with the -H option.
The following programs will enable or enhance certain features of mle if they
exist in PATH.
- bash (tab completion)
- fzf (fuzzy file search)
- grep (file grep)
- less (less integration)
- readtags (ctags integration)
- tree (file browsing)
- Multi-line style rules don't work properly when overlapped/staggered.
- There's a segfault lurking in the window split code. I can't reliably reproduce it.
Also check out eon, a fork of mle with some cool features.
mle makes extensive use of the following libraries.
