Fixed total line counting
Rearranged status bar a bit
Moved to \033 as the most compatible esc representation
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.
()
, []
, {}
, <>
NNE_TABWIDTH
definition, 2 spaces by default)NNE_IOBUFSZ
definition)NNE_TABWIDTH
spaces, use modstring to insert tabs\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 representsNNE_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.
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.
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) |
Well, there are several reasons why nne was created:
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).
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
.
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.
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.
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.
It should not be hard. Most (if not all) changes are going to be related to terminal I/O.
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.
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.