No-nonsense editor under 777 SLOC of ANSI C
1e478226 — luxferre 11 months ago
Fixed total line counting
5d100825 — luxferre 11 months ago
Rearranged status bar a bit
df633e07 — luxferre 11 months ago
Moved to \033 as the most compatible esc representation


browse  log 



You can also use your local clone with git send-email.

#nne: no-nonsense editor

#Design goal

To create a tiny public domain text editor that's usable for coding on a daily basis, while keeping the codebase under 1000 SLOC of readable and well-commented ANSI C89 code in a single file.


  • unique semi-modal controls that can't conflict with any terminal emulator or multiplexer by design
  • only depends on 8 POSIX-compatible header files
  • can be compiled with any C89 compiler (static linking is encouraged)
  • the code is well-commented and easy to understand
  • fully automatic indentation (based on how the previous line was indented)
  • matching bracket search: (), [], {}, <>
  • tabwidth (build-time configurable in NNE_TABWIDTH definition, 2 spaces by default)
  • full UTF-8 support (except right-to-left text)
  • external command runner (suitable for processing the currently opened file)

#Limitations (by design)

  • only a single file can be edited at a time
  • only VT100/ANSI-compatible terminals are supported
  • no horizontal scrolling support, lines are always wrapped
  • no syntax highlighting (ever, see note)
  • lines are only numbered in the status bar (see note)
  • limited characters in command buffer line (configurable in NNE_IOBUFSZ definition)
  • in interactive prompts, only backspace is supported, no arrow navigation
  • tab key always inserts NNE_TABWIDTH spaces, use modstring to insert tabs
  • no text replace functionality (use the shell command runner)
  • search is strictly case-sensitive and without wildcards/regexps
  • UTF-8 is the only supported text encoding (use iconv for others)
  • all \r or \r\n line endings are converted to \n (use dos2unix etc)

Note: syntax highlighing and (visual) line numbering are not implemented for two design reasons: simpler codebase and less distractions from the main text.


Just run the usual CLI compilation process (replace cc with the C compiler of your choice and adjust flags if required):

cc -std=c89 -Os -O2 -s nne.c -o nne [-DNNE_IOBUFSZ=n] [-DNNE_TABWIDTH=m] [-DNNE_NO_ALTBUF]
  • NNE_IOBUFSZ defines the internal command buffers size (in characters, default 2000)
  • NNE_TABWIDTH defines the amount of spaces that a tabulation key/char represents
  • NNE_NO_ALTBUF disables the use of advanced terminal sequence like alternate buffer switching, cursor hiding and UTF-8 mode enforcement (this macro is useful on some older terminals/OSes that don't support these sequences and can't correctly process them)

The GCC and Clang build flags are the same as above. Below are tested examples for some other C compilers.

Example for zig cc from Zig project, targeting x86_64 musl:

zig cc -target x86_64-linux-musl -std=c89 -Os -O2 -s nne.c -o nne

Example for cproc, static linking with musl:

cproc -static -s nne.c /usr/lib/musl/lib/libc.a -o nne

Example for chibicc, static linking with musl:

/path/to/chibicc -static -s nne.c /usr/lib/musl/lib/libc.a -o nne

You get the idea.

#Known build failures

Currently, nne can't be compiled with TCC and lacc because they both fail with undefined reference to '__dso_handle' error. This symbol is used by the atexit() call that sets up the handler to clean up the terminal environment on any abnormal exit. For TCC, the error can be mitigated by directly linking with the libc.a from musl (as shown above) but the resulting binary still is dynamically linked with glibc and displays unstable behavior.

Also, neatcc (+ neatlibc) can't compile nne because it gives strlen undefined error.


Invoking nne without arguments just creates a new buffer. You can specify a file name to open. If the file name doesn't exist, it will be created on the first save.

#Status bar

The status bar in nne is the last line of the terminal. Some operations may change its contents, but normally it consists of the following elements: [state] [row],[col] [file%] [W]x[H] | [filename], where:

  • [state] can be - (normal/insertion) or C (modal command),
  • [filename] refers to the file currently being edited,
  • [row],[col] display the current in-document cursor position (1-based),
  • [file%] displays the approximate percentage of the position relative to the entire file length,
  • [W]x[H] show current terminal width and height (in characters).


Controls in nne are semi-modal and use a modifier mod which stands for double-pressing the Esc key. So, for instance, mod w in the table below actually means Esc Esc w sequence. When the editor is in the modal command state, the character in the lower-left screen corner will change from - to C. To abort the command, press mod (Esc Esc) one more time.

Action Key sequence Additional comments
Save file mod s or mod w
Quit mod q Prompts on unsaved changes
Move up ArrowUp
Move down ArrowDown
Move left ArrowLeft
Move right ArrowRight
Tabulation Tab Insert NNE_TABWIDTH spaces
Literal tab mod Tab Insert literal tab character
Backspace Backspace Delete previous character
Delete Del or mod Backspace Delete current character
Page Up PgUp or mod ArrowUp Jump up half a screen
Page Down PgDn or mod ArrowDown Jump down half a screen
Home Home or mod 0 Jump to the line start
End End or mod 4 Jump to the line end
Next word mod ArrowRight
Previous word mod ArrowLeft
Jump to line mod l [number] Return Prompts for line number
Jump to start mod 8 Jump to file start
Jump to end mod 9 Jump to file end
Bracket match mod 5 Jump to matching pair in (), [], {} and <>
Find text mod / [text] Return If no text is entered, looks for the next occurrence of the same pattern
Copy line mod y Copy the current line into the clipboard
Copy lines mod Y [number] Return Copy N lines (starting from current row) into the clipboard
Cut line mod d Cut the current line into the clipboard
Cut lines mod D [number] Return Cut N lines (starting from current row) into the clipboard
Paste mod p or mod v Paste the line(s) from the clipboard into the current position
Undo (pseudo) mod u Discard all unsaved changes and reload the file contents
Shell command mod e [command] Return Save current file, run an external shell command and reopen the file
Help mod h Displays on-screen help (press Return to get back to normal mode)


#Why such design goals? What is the rationale behind nne?

Well, there are several reasons why nne was created:

  1. Minimum overhead. A text editor is the most important tool on every system, and it's crucial that it does not itself get in the way in terms of resource consumption. Most well-known and established text editors, however, are already bloated beyond repair, up to the point that x86_64 static builds of Vim and Vis against musl libc are sized 3433600 and 644288 bytes respectively. And these are only two examples. On top of that, their codebase already is so large that it cannot be easily (or at all) maintained by a single person. On the contrary, nne weighs around 68k bytes when statically linked with musl, and the sub-1000 SLOC limit (that actually turned out to be sub-777) makes it easy to comprehend by anyone familiar with ANSI C whoever will be reading its source code.
  2. Maximum portability. This editor is designed to be source-compatible with any POSIX environment and with any architecture a POSIX environment can run on. There is no OS-specific code and no external dependencies. You don't need to find or build any libtermkey, terminfo, ncurses and other nonsense for the target architecture you want to compile nne for. It also doesn't require a specific build system: just a simple command line to compile a single file. By the way, it also doesn't contain any compiler-specific quirks: any C89-compatible compiler can build nne binary in a POSIX environment (GCC, Clang, zig cc, tcc).
  3. Maximum freedom. Public domain deserves a decent lightweight text editor, just like it deserves SQLite, oksh and pdpmake. Besides mg (whose portability is questionable as of now), vce (that can't into UTF-8) and ue (that is straight up unusable on modern terminals), there were no notable text editors released into public domain.

#Why not the usual combos with Ctrl key (like in Nano/uEmacs/etc)?

Because it's not convenient in general to stretch fingers to use these combos, especially when having to jump between different keyboards where the Ctrl key is located in different places. If you got used to Ctrl-chords, then it won't take much time to get used to semi-modal sequences that start with double-Esc. Additionally, this approach ensures that no sequence will conflict with your terminal emulator or a terminal multiplexer (if you use one).

#Why not full modality then, akin to vi-like editors?

The initial plan was to make nne compatible with a small subset of POSIX vi, but then a more obvious control scheme was devised that would neither involve uncomfortable key chording nor make the users think which mode they are in. Also, the minimum vi codebase is generally much larger than the nne's <1000 SLOC goal. The layout of some control keys was definitely borrowed from vi though, like mod 5 (akin to vi's %), mod y, mod d, mod p and mod w.

#Why no true undo functionality?

As nne's text modification actions don't always operate on single characters, this would complicate undo buffer management and the overall codebase well up to the point of >1000 SLOC. Instead, more frequent file saving is encouraged and a way to quickly discard all the unsaved changes is offered with mod u. It also prompts you for confirmation, so that no accidental deletion takes place.

#Is it actually usable on a daily basis with such small code size?

Yes, the author had fully switched to it from Vim since Jul 29 2023. With that said, nne can't be considered ready for mission-critical systems, as there might be many edge cases that are not covered yet. Anyway, the best way to find hidden issues is to use the editor daily.

#Is there going to be any new functionality implemented (within the required limits)?

No. All the focus is going to be on fixing bugs (if there are any found) and size/performance optimizations. As of Jul 28 2023 and further, nne is considered feature-complete.

#Is nne easy to port onto non-POSIX systems like DOS or Windows?

It should not be hard. Most (if not all) changes are going to be related to terminal I/O.

#Why does mod e (running external command) force saving the currently edited file?

Because it can't guarantee that the external command won't kill the nne process itself. Thus, saving is enforced to preserve your data. Also, this shell runner feature is introduced for easy offloading of some functionality to external tools designed to do it much better than any in-editor features would allow, e.g. running sed to perform a global substitution on the very file we're editing right now. It would be a shame if this global substitution had been performed on an old copy of the file and any subsequent save had obliterated all the changes made by the external command.

#Why did you include a help screen in the editor itself?

Because nne is expected to be equally accessible when distributed in all forms: this 3-file repo, a single C source file or a single statically-linked binary. One should not rely on the fact that external documentation like this README is always present.


Created by Luxferre in 2023. Released into public domain.

Made in Ukraine.