A single-file, C89-compatible amalgamation of the linenoise line-editing library, extended with Win32 console support, full UTF-8 handling, and Unicode 15.0 width/case tables.
Compile once with your project — no configure scripts, no dependencies, no separate build step.
Built on top of the linenoise-win32 fork made for JimTcl, which itself was made on top of linenoise.
Everything (except this tiny message) was written by AI (Claude Sonnet 4.6), because I'm not a C expert nor a terminal expert and I just wanted it C89 compatible and easy to use. Normally it should be working fine though.
For a few months I released this code without respecting the original author's licensing conditions. I would like to apologise and have changed the license to the BSD 2-Clause License. Legally, it was only needed for the original license to be kept in every copy and fork of the project. However to keep the freedom of the code and respect the original author's choice of liberty, I decided to change the code from being licensed under the MIT License to being licensed under the BSD 2-Clause License.
Pratically, this does not change anything, as the BSD 2-Clause License is no more restrictive than the MIT License. Please note that this applies to all versions of the code, even previous ones.
| File | Purpose |
|---|---|
linenoise89.c |
The entire library — compile this alongside your source files |
linenoise89.h |
Public API header — #include this in your code |
_unicode_mapping.c |
Auto-generated Unicode 15.0 tables — #included by linenoise89.c, not a separate compile unit |
gen_unicode_tables.py |
Python script to regenerate _unicode_mapping.c from a newer Unicode version |
All four files must be in the same directory.
Include the header in any source file that calls linenoise functions:
#include "linenoise89.h"A minimal interactive loop:
#include <stdio.h>
#include <stdlib.h>
#include "linenoise89.h"
int main(void) {
char *line;
linenoiseHistoryLoad("history.txt");
while ((line = linenoise("» ")) != NULL) {
if (line[0] != '\0') {
printf("you typed: %s\n", line);
linenoiseHistoryAdd(line);
linenoiseHistorySave("history.txt");
}
free(line);
}
return 0;
}linenoise() returns a malloc-allocated string. You must free() it. It returns NULL on EOF (Ctrl-D on Unix, Ctrl-Z on Windows) or unrecoverable error.
#include "linenoise89.h"
void my_completion(const char *buf, linenoiseCompletions *lc, void *userdata) {
(void)userdata;
if (buf[0] == 'h') {
linenoiseAddCompletion(lc, "help");
linenoiseAddCompletion(lc, "history");
}
}
/* call before your input loop */
linenoiseSetCompletionCallback(my_completion, NULL);char *my_hints(const char *buf, int *color, int *bold, void *userdata) {
(void)userdata;
if (!strcasecmp(buf, "git")) {
*color = 35; /* magenta */
*bold = 0;
return " commit -m \"...\"";
}
return NULL;
}
linenoiseSetHintsCallback(my_hints, NULL);/* second argument is the initial text placed in the edit buffer */
char *line = linenoiseWithInitial("» ", "default text");linenoiseSetMultiLine(1); /* enable multi-line editing */
linenoiseHistorySetMaxLen(200); /* default is LINENOISE_DEFAULT_HISTORY_MAX_LEN */
linenoiseHistoryLoad("hist.txt");
linenoiseHistorySave("hist.txt");linenoise89.c is a separate translation unit — compile it alongside your own sources and let the linker combine them. Never #include the .c file directly.
# With full UTF-8 support (recommended)
gcc -DUSE_UTF8 -o myapp myapp.c linenoise89.c
# Without UTF-8 (ASCII-only, slightly smaller binary)
gcc -o myapp myapp.c linenoise89.c
# Strict C89 mode
gcc -std=c89 -DUSE_UTF8 -o myapp myapp.c linenoise89.c
# Without tab-completion and hints (reduces binary size further)
gcc -DUSE_UTF8 -DNO_COMPLETION -o myapp myapp.c linenoise89.crem With UTF-8 support
cl /DUSE_UTF8 myapp.c linenoise89.c
rem Without UTF-8
cl myapp.c linenoise89.c
rem Without completion/hints
cl /DUSE_UTF8 /DNO_COMPLETION myapp.c linenoise89.cMSVC does not require a C standard flag — it compiles as C89/C90 compatible by default.
add_executable(myapp myapp.c linenoise89.c)
target_compile_definitions(myapp PRIVATE USE_UTF8)
target_include_directories(myapp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})| Flag | Effect |
|---|---|
USE_UTF8 |
Enable UTF-8 input, Unicode width/combining tables, and utf8_upper / utf8_lower. Strongly recommended. |
NO_COMPLETION |
Remove tab-completion and hints callbacks. Saves code size on constrained targets. |
On Linux/macOS, the library uses termios and poll() for raw terminal input.
On Windows (including MinGW and MSVC), it uses the Win32 Console API (ReadConsoleInputW / WriteConsoleW) and works correctly in both cmd.exe and Windows Terminal. The _stricmp / _strnicmp compatibility shims for strcasecmp / strncasecmp are provided automatically when linenoise89.h is included under MSVC.
_unicode_mapping.c is pre-generated from Unicode 15.0. To update to a newer version, run:
python3 gen_unicode_tables.pyRequires Python 3.9+ (uses the built-in unicodedata module — no downloads or extra packages needed). The Unicode version used matches the Python installation's bundled unicodedata:
python3 -c "import unicodedata; print(unicodedata.unidata_version)"| Key | Action |
|---|---|
← / → |
Move cursor left / right |
Home / Ctrl-A |
Move to start of line |
End / Ctrl-E |
Move to end of line |
↑ / ↓ |
Previous / next history entry |
Ctrl-R |
Reverse history search |
Ctrl-U |
Delete from cursor to start of line |
Ctrl-K |
Delete from cursor to end of line |
Ctrl-W |
Delete previous word |
Backspace / Ctrl-H |
Delete previous character |
Del / Ctrl-D |
Delete character under cursor (or EOF if line empty) |
Tab |
Trigger completion callback (if registered) |
Ctrl-C |
Interrupt (sends SIGINT on Unix; clears line on Windows) |
Ctrl-L |
Clear screen |
Enter |
Submit line |