commit 0af80f95ce58aa55320057958a6e93cc132576a6
Author: Claudio Alessi <smoppy@gmail.com>
Date: Sat, 22 Nov 2025 23:43:46 +0100
intial commit
Diffstat:
| A | edit.c | | | 423 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | tui.c | | | 218 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | ui.h | | | 36 | ++++++++++++++++++++++++++++++++++++ |
3 files changed, 677 insertions(+), 0 deletions(-)
diff --git a/edit.c b/edit.c
@@ -0,0 +1,423 @@
+/* osaentuhaosenuhaoesnuthaoesnutha oesnthaoesuntha snethu asoenhu saoenhtuaoesn uthaoesunthaoesuntaoeh usaoneth asoenth aoesnth aoesnthaoseuthaoseuthaoesunthaoeusnh asoentuh */
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ui.h"
+
+typedef struct {
+ char *buf;
+ size_t cap;
+ int len;
+} Line;
+
+typedef struct {
+ Line **lines;
+ char *file_name;
+ int file_size;
+ int lines_cap;
+ size_t lines_tot;
+ //int ref_count;
+} Buffer;
+
+typedef struct {
+ Buffer *buf;
+ int line_num;
+ int col_num;
+ int row_offset;
+ int col_offset;
+ int screen_rows;
+ int screen_cols;
+ //int pref_col;
+} View;
+
+/* variables */
+int running = 1;
+View *vcur;
+UI *ui;
+
+/* function declarations */
+void die(const char *fmt, ...);
+void *ecalloc(size_t nmemb, size_t size);
+void insert_char(char *dst, char c, int len);
+void delete_char(char *dst, int count, int len);
+void line_insert_char(Line *line, size_t index, char c);
+void line_delete_char(Line *line, int index, int count);
+Line *line_create(char *content);
+void line_destroy(Line *l);
+void buffer_insert_line(Buffer *b, int index, Line *line);
+void buffer_delete_line(Buffer *b, int index, int count);
+int buffer_load_file(Buffer *b);
+Line *buffer_get_line(Buffer *b, int index);
+Buffer *buffer_create(char *fn);
+void buffer_destroy(Buffer *b);
+View *view_create(Buffer *b);
+void view_destroy(View *v);
+void view_cursor_hfix(View *v);
+void view_cursor_left(View *v);
+void view_cursor_right(View *v);
+void view_cursor_up(View *v);
+void view_cursor_down(View *v);
+int view_col2x(View *v, Line *line, int col);
+void view_scroll_fix(View *v);
+void draw_view(View *v);
+
+/* function implementations */
+void
+die(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+ exit(0);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size) {
+ void *p;
+
+ if(!(p = calloc(nmemb, size)))
+ die("Cannot allocate memory.");
+ return p;
+}
+
+void
+insert_char(char *dst, char c, int len) {
+ memmove(dst + 1, dst, len);
+ memcpy(dst, &c, 1);
+}
+
+void
+delete_char(char *dst, int count, int len) {
+ memmove(dst, dst + count, len);
+}
+
+void
+line_insert_char(Line *line, size_t index, char c) {
+ size_t newlen = line->len + 1;
+
+ assert(index >= 0 && index <= line->len);
+
+ if(newlen > line->cap) {
+ line->cap = line->cap ? line->cap * 2 : 16;
+ if(!(line->buf = realloc(line->buf, line->cap)))
+ die("Cannot reallocate memory.");
+ }
+ insert_char(&line->buf[index], c, line->len - index);
+ line->len = newlen;
+}
+
+void
+line_delete_char(Line *line, int index, int count) {
+ delete_char(&line->buf[index], count, line->len - index);
+ line->len -= count;
+}
+
+Line *
+line_create(char *content) {
+ Line *l = ecalloc(1, sizeof(Line));
+
+ if(content) {
+ l->buf = strdup(content);
+ l->len = strlen(l->buf);
+ l->cap = l->len + 1;
+ }
+ return l;
+}
+
+void
+line_destroy(Line *l) {
+ if(l->buf)
+ free(l->buf);
+ free(l);
+}
+
+void
+buffer_insert_line(Buffer *b, int index, Line *line) {
+ size_t nb = (b->lines_tot - index) * sizeof(Line *);
+
+ assert(index >= 0 && index <= b->lines_tot);
+ if(b->lines_tot >= b->lines_cap) {
+ b->lines_cap = b->lines_cap ? b->lines_cap * 2 : 64;
+ if(!(b->lines = realloc(b->lines, sizeof(Line *) * b->lines_cap)))
+ die("realloc:");
+ }
+ if(nb)
+ memmove(b->lines + index + 1, b->lines + index, nb);
+ b->lines[index] = line;
+ ++b->lines_tot;
+}
+
+void
+buffer_delete_line(Buffer *b, int index, int count) {
+ int lines_remaining, nb;
+ int last = index + count;
+ int i;
+
+ assert(count && index < b->lines_tot && last <= b->lines_tot);
+ for(i = 0; i < count; i++)
+ line_destroy(b->lines[index + i]);
+ lines_remaining = b->lines_tot - last;
+ if(lines_remaining) {
+ nb = lines_remaining * sizeof(Line *);
+ memmove(b->lines + index, b->lines + last, nb);
+ }
+ b->lines_tot -= count;
+}
+
+int
+buffer_load_file(Buffer *b) {
+ Line *l;
+ FILE *fp;
+ char *buf = NULL;
+ size_t cap;
+ int len;
+
+ if(!(fp = fopen(b->file_name, "r")))
+ return -1;
+ while((len = getline(&buf, &cap, fp)) != -1) {
+ buf[len - 1] = 0;
+ b->file_size += len;
+ l = line_create(buf);
+ buffer_insert_line(b, b->lines_tot, l);
+ }
+ fclose(fp);
+ return 0;
+}
+
+Line *
+buffer_get_line(Buffer *b, int index) {
+ if(!b->lines)
+ return NULL;
+ return b->lines[index];
+}
+
+Buffer *
+buffer_create(char *fn) {
+ Buffer *b = ecalloc(1, sizeof(Buffer));
+
+ b->lines = NULL;
+ b->lines_tot = 0;
+ b->file_size = 0;
+ if(fn) {
+ if(!(b->file_name = strdup(fn)))
+ return NULL;
+ if(buffer_load_file(b))
+ printf("%s: cannot load file\n", fn);
+ }
+ else {
+ /* ensure we have at least a line */
+ buffer_insert_line(b, b->lines_tot, line_create(NULL));
+ }
+ return b;
+}
+
+void
+buffer_destroy(Buffer *b) {
+ int i;
+
+ if(b->lines) {
+ for(i = 0; i < b->lines_tot; i++)
+ line_destroy(b->lines[i]);
+ free(b->lines);
+ }
+ if(b->file_name)
+ free(b->file_name);
+ free(b);
+}
+
+View *
+view_create(Buffer *b) {
+ View *v = ecalloc(1, sizeof(View));
+
+ v->line_num = 0;
+ v->col_num = 0;
+ v->row_offset = 0;
+ v->col_offset = 0;
+ v->buf = b;
+
+ ui->get_window_size(&v->screen_rows, &v->screen_cols);
+ return v;
+}
+
+void
+view_destroy(View *v) {
+ free(v);
+}
+
+/* actual invariant for the cursor */
+void
+view_cursor_hfix(View *v) {
+ assert(v->line_num >= 0 && v->line_num< v->buf->lines_tot);
+
+ Line *l = v->buf->lines[v->line_num];
+
+ /* TODO: < 0 needed? */
+ if(v->col_num < 0)
+ v->col_num = 0;
+ else if(v->col_num > l->len)
+ v->col_num = l->len;
+}
+
+void
+view_cursor_left(View *v) {
+ if(v->col_num)
+ --v->col_num;
+}
+
+void
+view_cursor_right(View *v) {
+ //if(!v->buf->lines_tot) return;
+ Line *l = v->buf->lines[v->line_num];
+
+ if(v->col_num < l->len)
+ ++v->col_num;
+}
+
+void
+view_cursor_up(View *v) {
+ if(v->line_num) {
+ --v->line_num;
+ view_cursor_hfix(v);
+ }
+}
+
+void
+view_cursor_down(View *v) {
+ //if(!v->buf->lines_tot) return;
+ if(v->line_num < v->buf->lines_tot - 1) {
+ ++v->line_num;
+ view_cursor_hfix(v);
+ }
+}
+
+int
+view_col2x(View *v, Line *line, int col) {
+ (void)v;
+ return ui->text_width(line->buf, col);
+}
+
+void
+view_scroll_fix(View *v) {
+ /* vertical */
+ if (v->line_num < v->row_offset)
+ v->row_offset = v->line_num;
+ if (v->line_num >= v->row_offset + v->screen_rows)
+ v->row_offset = v->line_num - v->screen_rows + 1;
+
+ /* horizontal */
+ if(v->col_num < v->col_offset)
+ v->col_offset = v->col_num;
+ if(v->col_num >= v->col_offset + v->screen_cols)
+ v->col_offset = v->col_num - v->screen_cols + 1;
+}
+
+void
+draw_view(View *v) {
+ Line *l;
+ int row, len, y;
+
+ ui->frame_start();
+ view_scroll_fix(v);
+
+ for(y = 0; y < v->screen_rows; y++) {
+ row = v->row_offset + y;
+ if(row >= v->buf->lines_tot) {
+ ui->draw_symbol(0, y, SYM_EMPTYLINE);
+ continue;
+ }
+
+ l = buffer_get_line(v->buf, row);
+ assert(l);
+
+ len = l->len - v->col_offset;
+ if(len > v->screen_cols)
+ len = v->screen_cols;
+ ui->draw_text(0, y, l->buf + v->col_offset, len);
+ }
+
+ Line *curline = buffer_get_line(v->buf, v->line_num);
+ int cx, cy;
+ if(curline) {
+ cx = view_col2x(v, curline, v->col_num); /* absolute */
+ cx -= view_col2x(v, curline, v->col_offset); /* offset */
+ cy = v->line_num - v->row_offset;
+ } else {
+ cx = cy = 0;
+ }
+
+#if 0
+ char *t = ecalloc(1, 128);
+ int tl;
+ tl = snprintf(t, 128, "\"%s\" %ld lines [%dx%d] (%d/%d)",
+ v->buf->file_name ? v->buf->file_name : "[none]", v->buf->lines_tot,
+ v->screen_cols, v->screen_rows, v->col_num, v->buf->lines[0]->len
+ );
+ ui->draw_text(0, v->screen_rows, t, tl);
+ free(t);
+#endif
+
+ ui->move_cursor(cx, cy);
+ ui->frame_flush();
+}
+
+void
+run(void) {
+ Event ev;
+
+ while(running) {
+ ev = ui->next_event();
+ switch(ev.type) {
+ case EV_KEY:
+ if(ev.key == 'k') view_cursor_up(vcur);
+ else if(ev.key == 'j') view_cursor_down(vcur);
+ else if(ev.key == 'h') view_cursor_left(vcur);
+ else if(ev.key == 'l') view_cursor_right(vcur);
+ else if(ev.key == 'q') running = 0;
+ else if(ev.key == '\n') {
+ Line *l = line_create(NULL);
+ buffer_insert_line(vcur->buf, vcur->line_num + 1, l);
+ view_cursor_down(vcur);
+ break;
+ } else {
+ line_insert_char(vcur->buf->lines[vcur->line_num], vcur->col_num, ev.key);
+ view_cursor_right(vcur);
+ }
+ break;
+ case EV_UKN:
+ break;
+ }
+ draw_view(vcur);
+ }
+}
+
+int
+main(int argc, char *argv[]) {
+ char *fn = NULL;
+
+ if(argc > 2) die("Usage: %s [file]", argv[0]);
+ if(argc == 2) fn = argv[1];
+
+ ui = &ui_tui; /* the one and only... */
+
+ ui->init();
+ Buffer *b = buffer_create(fn);
+ View *v = view_create(b);
+ vcur = v; /* current view */
+ draw_view(v);
+ run();
+ buffer_destroy(v->buf);
+ view_destroy(v);
+ ui->exit();
+ return 0;
+}
diff --git a/tui.c b/tui.c
@@ -0,0 +1,218 @@
+#include "ui.h"
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#define CURPOS "\33[%d;%dH"
+//#define CLEARLEFT "\33[1K"
+#define CLEARRIGHT "\33[0K"
+#define CLEARLINE "\33[2K"
+#define CURHIDE "\33[?25l"
+#define CURSHOW "\33[?25h"
+
+typedef struct {
+ char *buf;
+ int len;
+ int cap;
+} Abuf;
+
+struct termios origti;
+struct winsize ws;
+Abuf frame;
+
+extern void *ecalloc(size_t nmemb, size_t size);
+extern void die(const char *fmt, ...);
+
+/* function declarations */
+void ab_free(Abuf *ab);
+void ab_ensure_cap(Abuf *ab, size_t addlen);
+void ab_write(Abuf *ab, const char *s, size_t len);
+int ab_printf(Abuf *ab, const char *fmt, ...);
+void ab_flush(Abuf *ab);
+void tui_frame_start(void);
+void tui_frame_flush(void);
+int tui_text_width(char *s, int len);
+void tui_get_window_size(int *rows, int *cols);
+void tui_exit(void);
+void tui_move_cursor(int x, int y);
+void tui_draw_text(int c, int r, char *txt, int len);
+void tui_draw_symbol(int r, int c, Symbol sym);
+void tui_init(void);
+
+/* function implementations */
+void
+ab_free(Abuf *ab) {
+ if(!ab->buf)
+ return;
+ free(ab->buf);
+ ab->buf = NULL;
+ ab->len = 0;
+ ab->cap = 0;
+}
+
+void
+ab_ensure_cap(Abuf *ab, size_t addlen) {
+ size_t newlen = ab->len + addlen;
+
+ if(newlen <= ab->cap)
+ return;
+ while(newlen > ab->cap)
+ ab->cap = ab->cap ? ab->cap * 2 : 8;
+ /* TODO: panic and save the dump the data before exit */
+ if(!(ab->buf = realloc(ab->buf, ab->cap)))
+ die("Cannot reallocate memory.");
+}
+
+void
+ab_write(Abuf *ab, const char *s, size_t len) {
+ ab_ensure_cap(&frame, len);
+ memcpy(ab->buf + ab->len, s, len);
+ ab->len += len;
+}
+
+int
+ab_printf(Abuf *ab, const char *fmt, ...) {
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(NULL, 0, fmt, ap);
+ assert(len >= 0);
+ va_end(ap);
+
+ ab_ensure_cap(ab, len + 1);
+
+ va_start(ap, fmt);
+ vsnprintf(ab->buf + ab->len, len + 1, fmt, ap);
+ va_end(ap);
+
+ ab->len += len;
+ return len;
+}
+
+void
+ab_flush(Abuf *ab) {
+ write(STDOUT_FILENO, ab->buf, ab->len);
+ ab_free(ab);
+}
+
+void
+tui_frame_start(void) {
+ ab_printf(&frame, CURHIDE);
+}
+
+void
+tui_frame_flush(void) {
+ ab_printf(&frame, CURSHOW);
+ ab_flush(&frame);
+}
+
+int
+tui_text_width(char *s, int len) {
+ int w = 0, i;
+ int tabstop = 8;
+
+ for(i = 0; i < len; i++) {
+ if(s[i] == '\t')
+ w += tabstop - w % tabstop;
+ else
+ ++w;
+ }
+ return w;
+}
+
+void
+tui_get_window_size(int *rows, int *cols) {
+ *rows = ws.ws_row;
+ *cols = ws.ws_col;
+}
+
+void
+tui_exit(void) {
+ tcsetattr(0, TCSANOW, &origti);
+}
+
+void
+tui_move_cursor(int c, int r) {
+ int x = c + 1, y = r + 1; /* TERM coords are 1-based */
+ ab_printf(&frame, CURPOS, y, x);
+}
+
+void
+tui_draw_text(int c, int r, char *txt, int len) {
+ tui_move_cursor(c, r);
+ ab_printf(&frame, CLEARLINE "%.*s", len, txt);
+}
+
+void
+tui_draw_symbol(int c, int r, Symbol sym) {
+ int symch;
+
+ switch(sym) {
+ case SYM_EMPTYLINE: symch = '~'; break;
+ default: symch = '?'; break;
+ }
+
+ tui_move_cursor(c, r);
+ ab_printf(&frame, CLEARRIGHT "%c", symch);
+}
+
+void
+tui_init(void) {
+ struct termios ti;
+
+ setlocale(LC_CTYPE, "");
+ tcgetattr(0, &origti);
+ cfmakeraw(&ti);
+ ti.c_iflag |= ICRNL;
+ ti.c_cc[VMIN] = 1;
+ ti.c_cc[VTIME] = 0;
+ tcsetattr(0, TCSAFLUSH, &ti);
+ setbuf(stdout, NULL);
+ ioctl(0, TIOCGWINSZ, &ws);
+}
+
+int
+tui_read_byte(void) {
+ char c;
+
+ if (read(STDIN_FILENO, &c, 1) == 1)
+ return c;
+ return -1;
+}
+
+Event
+tui_next_event(void) {
+ Event ev;
+ int c = tui_read_byte();
+
+ if(c == 0x1B) {
+ ev.type = EV_UKN;
+ return ev;
+ }
+
+ ev.type = EV_KEY;
+ ev.key = c;
+ return ev;
+}
+
+UI ui_tui = {
+ .name = "TUI",
+ .init = tui_init,
+ .exit = tui_exit,
+ .frame_start = tui_frame_start,
+ .frame_flush = tui_frame_flush,
+ .text_width = tui_text_width,
+ .move_cursor = tui_move_cursor,
+ .draw_text = tui_draw_text,
+ .draw_symbol = tui_draw_symbol,
+ .get_window_size = tui_get_window_size,
+ .next_event = tui_next_event
+};
diff --git a/ui.h b/ui.h
@@ -0,0 +1,36 @@
+#ifndef UI_H
+#define UI_H
+
+typedef enum {
+ SYM_EMPTYLINE
+} Symbol;
+
+typedef enum {
+ EV_KEY,
+ EV_UKN
+} EventType;
+
+typedef struct {
+ EventType type;
+ int key;
+ int mod;
+ int col, row;
+} Event;
+
+typedef struct {
+ const char *name;
+ void (*init)(void);
+ void (*exit)(void);
+ void (*frame_start)(void);
+ void (*frame_flush)(void);
+ int (*text_width)(char *s, int len);
+ void (*move_cursor)(int x, int y);
+ void (*draw_text)(int r, int c, char *txt, int len);
+ void (*draw_symbol)(int r, int c, Symbol sym);
+ void (*get_window_size)(int *rows, int *cols);
+ Event (*next_event)(void);
+} UI;
+
+extern UI ui_tui;
+
+#endif