edo

Experimental text editor.
Log | Files | Refs | LICENSE

commit 0af80f95ce58aa55320057958a6e93cc132576a6
Author: Claudio Alessi <smoppy@gmail.com>
Date:   Sat, 22 Nov 2025 23:43:46 +0100

intial commit

Diffstat:
Aedit.c | 423+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atui.c | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aui.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