nne

No-nonsense editor under 777 SLOC of ANSI C
git clone git://git.luxferre.top/nne.git
Log | Files | Refs | README | LICENSE

README.md (12299B)


      1 # nne: no-nonsense editor
      2 
      3 ## Design goal
      4 
      5 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.
      6 
      7 ## Features
      8 
      9 - unique semi-modal controls that can't conflict with any terminal emulator or multiplexer by design
     10 - only depends on 8 POSIX-compatible header files
     11 - can be compiled with any C89 compiler (static linking is encouraged)
     12 - the code is well-commented and easy to understand
     13 - fully automatic indentation (based on how the previous line was indented)
     14 - matching bracket search: `()`, `[]`, `{}`, `<>`
     15 - tabwidth (build-time configurable in `NNE_TABWIDTH` definition, 2 spaces by default)
     16 - full UTF-8 support (except right-to-left text)
     17 - external command runner (suitable for processing the currently opened file)
     18 
     19 ## Limitations (by design)
     20 
     21 - only a single file can be edited at a time
     22 - only VT100/ANSI-compatible terminals are supported
     23 - no horizontal scrolling support, lines are always wrapped
     24 - no syntax highlighting (ever, see note)
     25 - lines are only numbered in the status bar (see note)
     26 - limited characters in command buffer line (configurable in `NNE_IOBUFSZ` definition)
     27 - in interactive prompts, only backspace is supported, no arrow navigation
     28 - tab key always inserts `NNE_TABWIDTH` spaces, use modstring to insert tabs
     29 - no text replace functionality (use the shell command runner)
     30 - search is strictly case-sensitive and without wildcards/regexps
     31 - UTF-8 is the only supported text encoding (use iconv for others)
     32 - all `\r` or `\r\n` line endings are converted to `\n` (use dos2unix etc)
     33  
     34 _Note: syntax highlighing and (visual) line numbering are not implemented for two design reasons: simpler codebase and less distractions from the main text._
     35 
     36 ## Building
     37 
     38 Just run the usual CLI compilation process (replace `cc` with the C compiler of your choice and adjust flags if required):
     39 ```
     40 cc -std=c89 -Os -O2 -s nne.c -o nne [-DNNE_IOBUFSZ=n] [-DNNE_TABWIDTH=m] [-DNNE_NO_ALTBUF]
     41 ```
     42 - `NNE_IOBUFSZ` defines the internal command buffers size (in characters, default 2000)
     43 - `NNE_TABWIDTH` defines the amount of spaces that a tabulation key/char represents
     44 - `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)
     45 
     46 The GCC and Clang build flags are the same as above. Below are tested examples for some other C compilers.
     47 
     48 Example for `zig cc` from [Zig](https://ziglang.org/) project, targeting x86_64 musl:
     49 ```
     50 zig cc -target x86_64-linux-musl -std=c89 -Os -O2 -s nne.c -o nne
     51 ```
     52 Example for [cproc](https://git.sr.ht/~mcf/cproc), static linking with musl:
     53 ```
     54 cproc -static -s nne.c /usr/lib/musl/lib/libc.a -o nne
     55 ```
     56 Example for [chibicc](https://github.com/rui314/chibicc), static linking with musl:
     57 ```
     58 /path/to/chibicc -static -s nne.c /usr/lib/musl/lib/libc.a -o nne
     59 ```
     60 You get the idea.
     61 
     62 ### Known build failures
     63 
     64 Currently, nne can't be compiled with [TCC](https://bellard.org/tcc/) and [lacc](https://github.com/larmel/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.
     65 
     66 Also, [neatcc](https://github.com/aligrudi/neatcc) (+ [neatlibc](https://github.com/aligrudi/neatlibc)) can't compile nne because it gives `strlen undefined` error.
     67 
     68 ## Usage
     69 
     70 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.
     71 
     72 ### Status bar
     73 
     74 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:
     75 
     76 - `[state]` can be `-` (normal/insertion) or `C` (modal command),
     77 - `[filename]` refers to the file currently being edited,
     78 - `[row],[col]` display the current in-document cursor position (1-based),
     79 - `[file%]` displays the approximate percentage of the position relative to the entire file length,
     80 - `[W]x[H]` show current terminal width and height (in characters).
     81 
     82 ### Controls
     83 
     84 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.
     85 
     86 Action       |Key sequence             |Additional comments
     87 -------------|-------------------------|-------------------
     88 Save file    |`mod s` or `mod w`       |
     89 Quit         |`mod q`                  |Prompts on unsaved changes
     90 Move up      |`ArrowUp`                |
     91 Move down    |`ArrowDown`              |
     92 Move left    |`ArrowLeft`              |
     93 Move right   |`ArrowRight`             |
     94 Tabulation   |`Tab`                    |Insert `NNE_TABWIDTH` spaces
     95 Literal tab  |`mod Tab`                |Insert literal tab character
     96 Backspace    |`Backspace`              |Delete previous character
     97 Delete       |`Del` or `mod Backspace` |Delete current character
     98 Page Up      |`PgUp` or `mod ArrowUp`  |Jump up half a screen
     99 Page Down    |`PgDn` or `mod ArrowDown`|Jump down half a screen
    100 Home         |`Home` or `mod 0`        |Jump to the line start
    101 End          |`End` or `mod 4`         |Jump to the line end
    102 Next word    |`mod ArrowRight`         |
    103 Previous word|`mod ArrowLeft`          |
    104 Jump to line |`mod l [number] Return`  |Prompts for line number
    105 Jump to start|`mod 8`                  |Jump to file start
    106 Jump to end  |`mod 9`                  |Jump to file end
    107 Bracket match|`mod 5`                  |Jump to **matching** pair in `()`, `[]`, `{}` and `<>`
    108 Find text    |`mod / [text] Return`    |If no text is entered, looks for the next occurrence of the same pattern
    109 Copy line    |`mod y`                  |Copy the current line into the clipboard
    110 Copy lines   |`mod Y [number] Return`  |Copy N lines (starting from current row) into the clipboard
    111 Cut line     |`mod d`                  |Cut the current line into the clipboard
    112 Cut lines    |`mod D [number] Return`  |Cut N lines (starting from current row) into the clipboard
    113 Paste        |`mod p` or `mod v`       |Paste the line(s) from the clipboard into the current position
    114 Undo (pseudo)|`mod u`                  |Discard all unsaved changes and reload the file contents
    115 Shell command|`mod e [command] Return` |Save current file, run an external shell command and reopen the file
    116 Help         |`mod h`                  |Displays on-screen help (press Return to get back to normal mode)
    117 
    118 ## FAQ
    119 
    120 ### Why such design goals? What is the rationale behind nne?
    121 
    122 Well, there are several reasons why nne was created:
    123 
    124 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.
    125 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).
    126 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.
    127 
    128 ### Why not the usual combos with Ctrl key (like in Nano/uEmacs/etc)?
    129 
    130 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).
    131 
    132 ### Why not full modality then, akin to vi-like editors?
    133 
    134 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`.
    135 
    136 ### Why no true undo functionality?
    137 
    138 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.
    139 
    140 ### Is it actually usable on a daily basis with such small code size?
    141 
    142 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.
    143 
    144 ### Is there going to be any new functionality implemented (within the required limits)?
    145 
    146 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.
    147 
    148 ### Is nne easy to port onto non-POSIX systems like DOS or Windows?
    149 
    150 It should not be hard. Most (if not all) changes are going to be related to terminal I/O.
    151 
    152 ### Why does `mod e` (running external command) force saving the currently edited file?
    153 
    154 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.
    155 
    156 ### Why did you include a help screen in the editor itself?
    157 
    158 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. 
    159 
    160 ## Credits
    161 
    162 Created by Luxferre in 2023. Released into public domain.
    163 
    164 Made in Ukraine.