Unicode-aware terminal line editing library with history and completion support.
Note
Windows support is planned.
Termline is a single-header C library that provides Unicode-aware line editing functionality for terminal applications. It serves as a lightweight alternative to libraries like GNU Readline.
- Full Unicode support with via utf8proc
- Core Emacs-style keyboard shortcuts (arrow keys, basic editing)
- History management with optional persistence
- Tab completion support
Termline is a single-header library.
To use it, in exactly one C/C++ file in your project:
#define TERMLINE_IMPLEMENTATION
#include "termline.h"
In other files, just include without the define:
#include "termline.h"
Termline requires:
- utf8proc for Unicode handling
#include <stdio.h>
#define TERMLINE_IMPLEMENTATION
#include "termline.h"
int main() {
// Simple line input
char *line = termline_readline("Enter text: ");
if (line) {
printf("You entered: %s\n", line);
termline_free(line);
}
return 0;
}
#include <stdio.h>
#define TERMLINE_IMPLEMENTATION
#include "termline.h"
int main() {
// Create a context with history
TermlineContext *ctx = termline_context_create();
TermlineHistory *history = termline_history_create(100); // Store up to 100 entries
termline_history_set_ctx(ctx, history);
// Optional: Load history from file
termline_history_load(history, ".myapp_history");
// Read multiple lines with history
char *line;
while ((line = termline_readline_ctx(ctx, ">> ")) != NULL) {
if (line[0] == '\0') continue;
printf("You entered: %s\n", line);
termline_free(line);
// Process commands...
// Exit loop when done
}
// Save history on exit
termline_history_save(history, ".myapp_history");
// Clean up
termline_context_destroy(ctx); // This also frees history
return 0;
}
// Example completion callback
char **my_completer(const char *text, int start, int end, void *userdata) {
// This is just an example - implement your own logic
const char *commands[] = {"help", "hello", "history", "quit", NULL};
// Count matches
int match_count = 0;
for (int i = 0; commands[i] != NULL; i++) {
if (strncmp(commands[i], text, end) == 0) {
match_count++;
}
}
if (match_count == 0) return NULL;
// Allocate result array (NULL-terminated)
char **result = malloc(sizeof(char*) * (match_count + 1));
if (!result) return NULL;
// Fill with matches
int j = 0;
for (int i = 0; commands[i] != NULL; i++) {
if (strncmp(commands[i], text, end) == 0) {
result[j++] = strdup(commands[i]);
}
}
result[j] = NULL; // NULL-terminate
return result;
}
// In your main code:
termline_set_completion_callback(ctx, my_completer, NULL);
TermlineContext *termline_context_create(void);
void termline_context_destroy(TermlineContext *ctx);
char *termline_readline(const char *prompt);
char *termline_readline_ctx(TermlineContext *ctx, const char *prompt);
char *termline_readline_fd(const char *prompt, int in_fd, int out_fd);
char *termline_readline_fd_ctx(TermlineContext *ctx, const char *prompt, int in_fd, int out_fd);
void termline_free(void *ptr);
TermlineHistory *termline_history_create(int max_capacity);
void termline_history_destroy(TermlineHistory *history);
void termline_history_set_ctx(TermlineContext *ctx, TermlineHistory *history);
int termline_history_add(TermlineHistory *history, const char *line);
int termline_history_save(TermlineHistory *history, const char *filename);
int termline_history_load(TermlineHistory *history, const char *filename);
void termline_history_allow_duplicates(TermlineHistory *history, bool allow);
// Callback type
typedef char **(*TermlineCompletionCallback)(const char *text, int start, int end, void *userdata);
// Setting callback
void termline_set_completion_callback(TermlineContext *ctx, TermlineCompletionCallback callback, void *userdata);
0BSD.
Issues and suggestions are welcome!
If you find a portability issue, open an issue or PR.