zep/zep.c

764 lines
19 KiB
C

/* zep.c, Zep Emacs, Public Domain, Hugh Barney, 2017, Derived from: Anthony's Editor January 93 */
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <curses.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>
#define E_NAME "zep"
#define E_VERSION "v1.3"
#define E_LABEL "Zep:"
#define B_MODIFIED 0x01 /* modified buffer */
#define MSGLINE (LINES-1)
#define CHUNK 8096L
#define K_BUFFER_LENGTH 256
#define MAX_FNAME 256
#define TEMPBUF 512
#define MIN_GAP_EXPAND 512
#define NOMARK -1
#define STRBUF_M 64
typedef unsigned char char_t;
typedef long point_t;
typedef struct keymap_t {
char *key_desc; /* name of bound function */
char *key_bytes; /* the string of bytes when this key is pressed */
void (*func)(void);
} keymap_t;
typedef struct buffer_t
{
point_t b_mark; /* the mark */
point_t b_point; /* the point */
point_t b_page; /* start of page */
point_t b_epage; /* end of page */
char_t *b_buf; /* start of buffer */
char_t *b_ebuf; /* end of buffer */
char_t *b_gap; /* start of gap */
char_t *b_egap; /* end of gap */
char w_top; /* Origin 0 top row of window */
char w_rows; /* no. of rows of text in window */
int b_row; /* cursor row */
int b_col; /* cursor col */
char b_fname[MAX_FNAME + 1]; /* filename */
char b_flags; /* buffer flags */
} buffer_t;
/*
* Some compilers define size_t as a unsigned 16 bit number while
* point_t and off_t might be defined as a signed 32 bit number.
* malloc(), realloc(), fread(), and fwrite() take size_t parameters,
* which means there will be some size limits because size_t is too
* small of a type.
*/
#define MAX_SIZE_T ((unsigned long) (size_t) ~0)
int done;
char_t *input;
int msgflag;
char msgline[TEMPBUF];
char temp[TEMPBUF];
keymap_t *key_return;
keymap_t *key_map;
buffer_t *curbp;
point_t nscrap = 0;
char_t *scrap = NULL;
char searchtext[STRBUF_M];
buffer_t* new_buffer()
{
buffer_t *bp = (buffer_t *)malloc(sizeof(buffer_t));
assert(bp != NULL);
bp->b_point = 0;
bp->b_page = 0;
bp->b_epage = 0;
bp->b_flags = 0;
bp->b_buf = NULL;
bp->b_ebuf = NULL;
bp->b_gap = NULL;
bp->b_egap = NULL;
bp->b_fname[0] = '\0';
bp->w_top = 0;
bp->w_rows = LINES - 2;
return bp;
}
void fatal(char *msg)
{
move(LINES-1, 0);
refresh();
noraw();
endwin();
printf("\n" E_NAME " " E_VERSION ": %s\n", msg);
exit(1);
}
void msg(char *msg, ...)
{
va_list args;
va_start(args, msg);
(void)vsprintf(msgline, msg, args);
va_end(args);
msgflag = TRUE;
}
/* Given a buffer offset, convert it to a pointer into the buffer */
char_t * ptr(buffer_t *bp, register point_t offset)
{
if (offset < 0) return (bp->b_buf);
return (bp->b_buf+offset + (bp->b_buf + offset < bp->b_gap ? 0 : bp->b_egap-bp->b_gap));
}
/* Given a pointer into the buffer, convert it to a buffer offset */
point_t pos(buffer_t *bp, register char_t *cp)
{
assert(bp->b_buf <= cp && cp <= bp->b_ebuf);
return (cp - bp->b_buf - (cp < bp->b_egap ? 0 : bp->b_egap - bp->b_gap));
}
/* Enlarge gap by n chars, position of gap cannot change */
int growgap(buffer_t *bp, point_t n)
{
char_t *new;
point_t buflen, newlen, xgap, xegap;
assert(bp->b_buf <= bp->b_gap);
assert(bp->b_gap <= bp->b_egap);
assert(bp->b_egap <= bp->b_ebuf);
xgap = bp->b_gap - bp->b_buf;
xegap = bp->b_egap - bp->b_buf;
buflen = bp->b_ebuf - bp->b_buf;
/* reduce number of reallocs by growing by a minimum amount */
n = (n < MIN_GAP_EXPAND ? MIN_GAP_EXPAND : n);
newlen = buflen + n * sizeof (char_t);
if (buflen == 0) {
if (newlen < 0 || MAX_SIZE_T < newlen) fatal("Failed to allocate required memory.\n");
new = (char_t*) malloc((size_t) newlen);
if (new == NULL) fatal("Failed to allocate required memory.\n");
} else {
if (newlen < 0 || MAX_SIZE_T < newlen) {
msg("Failed to allocate required memory");
return (FALSE);
}
new = (char_t*) realloc(bp->b_buf, (size_t) newlen);
if (new == NULL) {
msg("Failed to allocate required memory"); /* Report non-fatal error. */
return (FALSE);
}
}
/* Relocate pointers in new buffer and append the new
* extension to the end of the gap.
*/
bp->b_buf = new;
bp->b_gap = bp->b_buf + xgap;
bp->b_ebuf = bp->b_buf + buflen;
bp->b_egap = bp->b_buf + newlen;
while (xegap < buflen--)
*--bp->b_egap = *--bp->b_ebuf;
bp->b_ebuf = bp->b_buf + newlen;
assert(bp->b_buf < bp->b_ebuf); /* Buffer must exist. */
assert(bp->b_buf <= bp->b_gap);
assert(bp->b_gap < bp->b_egap); /* Gap must grow only. */
assert(bp->b_egap <= bp->b_ebuf);
return (TRUE);
}
point_t movegap(buffer_t *bp, point_t offset)
{
char_t *p = ptr(bp, offset);
while (p < bp->b_gap)
*--bp->b_egap = *--bp->b_gap;
while (bp->b_egap < p)
*bp->b_gap++ = *bp->b_egap++;
assert(bp->b_gap <= bp->b_egap);
assert(bp->b_buf <= bp->b_gap);
assert(bp->b_egap <= bp->b_ebuf);
return (pos(bp, bp->b_egap));
}
void save()
{
FILE *fp;
point_t length;
fp = fopen(curbp->b_fname, "w");
if (fp == NULL) msg("Failed to open file \"%s\".", curbp->b_fname);
(void) movegap(curbp, (point_t) 0);
length = (point_t) (curbp->b_ebuf - curbp->b_egap);
if (fwrite(curbp->b_egap, sizeof (char), (size_t) length, fp) != length)
msg("Failed to write file \"%s\".", curbp->b_fname);
fclose(fp);
curbp->b_flags &= ~B_MODIFIED;
msg("File \"%s\" %ld bytes saved.", curbp->b_fname, pos(curbp, curbp->b_ebuf));
}
/* reads file into buffer at point */
int insert_file(char *fn, int modflag)
{
FILE *fp;
size_t len;
struct stat sb;
if (stat(fn, &sb) < 0) {
msg("Failed to find file \"%s\".", fn);
return (FALSE);
}
if (MAX_SIZE_T < sb.st_size) {
msg("File \"%s\" is too big to load.", fn);
return (FALSE);
}
if (curbp->b_egap - curbp->b_gap < sb.st_size * sizeof (char_t) && !growgap(curbp, sb.st_size))
return (FALSE);
if ((fp = fopen(fn, "r")) == NULL) {
msg("Failed to open file \"%s\".", fn);
return (FALSE);
}
curbp->b_point = movegap(curbp, curbp->b_point);
curbp->b_gap += len = fread(curbp->b_gap, sizeof (char), (size_t) sb.st_size, fp);
if (fclose(fp) != 0) {
msg("Failed to close file \"%s\".", fn);
return (FALSE);
}
curbp->b_flags &= (modflag ? B_MODIFIED : ~B_MODIFIED);
msg("File \"%s\" %ld bytes read.", fn, len);
return (TRUE);
}
char_t *get_key(keymap_t *keys, keymap_t **key_return)
{
keymap_t *k;
int submatch;
static char_t buffer[K_BUFFER_LENGTH];
static char_t *record = buffer;
*key_return = NULL;
/* if recorded bytes remain, return next recorded byte. */
if (*record != '\0') {
*key_return = NULL;
return record++;
}
/* reset record buffer. */
record = buffer;
do {
assert(K_BUFFER_LENGTH > record - buffer);
/* read and record one byte. */
*record++ = (unsigned)getch();
*record = '\0';
/* if recorded bytes match any multi-byte sequence... */
for (k = keys, submatch = 0; k->key_bytes != NULL; ++k) {
char_t *p, *q;
for (p = buffer, q = (char_t *)k->key_bytes; *p == *q; ++p, ++q) {
/* an exact match */
if (*q == '\0' && *p == '\0') {
record = buffer;
*record = '\0';
*key_return = k;
return record; /* empty string */
}
}
/* record bytes match part of a command sequence */
if (*p == '\0' && *q != '\0') {
submatch = 1;
}
}
} while (submatch);
/* nothing matched, return recorded bytes. */
record = buffer;
return (record++);
}
/* Reverse scan for start of logical line containing offset */
point_t lnstart(buffer_t *bp, register point_t off)
{
register char_t *p;
do
p = ptr(bp, --off);
while (bp->b_buf < p && *p != '\n');
return (bp->b_buf < p ? ++off : 0);
}
/* Forward scan for start of logical line segment containing 'finish' */
point_t segstart(buffer_t *bp, point_t start, point_t finish)
{
char_t *p;
int c = 0;
point_t scan = start;
while (scan < finish) {
p = ptr(bp, scan);
if (*p == '\n') {
c = 0;
start = scan+1;
} else if (COLS <= c) {
c = 0;
start = scan;
}
++scan;
c += *p == '\t' ? 8 - (c & 7) : 1;
}
return (c < COLS ? start : finish);
}
/* Forward scan for start of logical line segment following 'finish' */
point_t segnext(buffer_t *bp, point_t start, point_t finish)
{
char_t *p;
int c = 0;
point_t scan = segstart(bp, start, finish);
for (;;) {
p = ptr(bp, scan);
if (bp->b_ebuf <= p || COLS <= c)
break;
++scan;
if (*p == '\n')
break;
c += *p == '\t' ? 8 - (c & 7) : 1;
}
return (p < bp->b_ebuf ? scan : pos(bp, bp->b_ebuf));
}
/* Move up one screen line */
point_t upup(buffer_t *bp, point_t off)
{
point_t curr = lnstart(bp, off);
point_t seg = segstart(bp, curr, off);
if (curr < seg)
off = segstart(bp, curr, seg-1);
else
off = segstart(bp, lnstart(bp,curr-1), curr-1);
return (off);
}
/* Move down one screen line */
point_t dndn(buffer_t *bp, point_t off)
{
return (segnext(bp, lnstart(bp,off), off));
}
/* Return the offset of a column on the specified line */
point_t lncolumn(buffer_t *bp, point_t offset, int column)
{
int c = 0;
char_t *p;
while ((p = ptr(bp, offset)) < bp->b_ebuf && *p != '\n' && c < column) {
c += *p == '\t' ? 8 - (c & 7) : 1;
++offset;
}
return (offset);
}
void modeline(buffer_t *bp)
{
int i;
char mch;
standout();
move(bp->w_top + bp->w_rows, 0);
mch = ((bp->b_flags & B_MODIFIED) ? '*' : '=');
sprintf(temp, "=%c " E_LABEL " == %s ", mch, bp->b_fname);
addstr(temp);
for (i = strlen(temp) + 1; i <= COLS; i++)
addch('=');
standend();
}
void dispmsg()
{
move(MSGLINE, 0);
if (msgflag) {
addstr(msgline);
msgflag = FALSE;
}
clrtoeol();
}
void display()
{
char_t *p;
int i, j, k;
buffer_t *bp = curbp;
/* find start of screen, handle scroll up off page or top of file */
/* point is always within b_page and b_epage */
if (bp->b_point < bp->b_page)
bp->b_page = segstart(bp, lnstart(bp,bp->b_point), bp->b_point);
/* reframe when scrolled off bottom */
if (bp->b_epage <= bp->b_point) {
/* Find end of screen plus one. */
bp->b_page = dndn(bp, bp->b_point);
/* if we scoll to EOF we show 1 blank line at bottom of screen */
if (pos(bp, bp->b_ebuf) <= bp->b_page) {
bp->b_page = pos(bp, bp->b_ebuf);
i = bp->w_rows - 1;
} else {
i = bp->w_rows - 0;
}
/* Scan backwards the required number of lines. */
while (0 < i--)
bp->b_page = upup(bp, bp->b_page);
}
move(bp->w_top, 0); /* start from top of window */
i = bp->w_top;
j = 0;
bp->b_epage = bp->b_page;
/* paint screen from top of page until we hit maxline */
while (1) {
/* reached point - store the cursor position */
if (bp->b_point == bp->b_epage) {
bp->b_row = i;
bp->b_col = j;
}
p = ptr(bp, bp->b_epage);
if (bp->w_top + bp->w_rows <= i || bp->b_ebuf <= p) /* maxline */
break;
if (*p != '\r') {
if (isprint(*p) || *p == '\t' || *p == '\n') {
j += *p == '\t' ? 8-(j&7) : 1;
addch(*p);
} else {
const char *ctrl = unctrl(*p);
j += (int) strlen(ctrl);
addstr(ctrl);
}
}
if (*p == '\n' || COLS <= j) {
j -= COLS;
if (j < 0)
j = 0;
++i;
}
++bp->b_epage;
}
/* replacement for clrtobot() to bottom of window */
for (k=i; k < bp->w_top + bp->w_rows; k++) {
move(k, j); /* clear from very last char not start of line */
clrtoeol();
j = 0; /* thereafter start of line */
}
modeline(bp);
dispmsg();
move(bp->b_row, bp->b_col); /* set cursor */
refresh();
}
void top() { curbp->b_point = 0; }
void bottom() { curbp->b_epage = curbp->b_point = pos(curbp, curbp->b_ebuf); }
void left() { if (0 < curbp->b_point) --curbp->b_point; }
void right() { if (curbp->b_point < pos(curbp, curbp->b_ebuf)) ++curbp->b_point; }
void up() { curbp->b_point = lncolumn(curbp, upup(curbp, curbp->b_point),curbp->b_col); }
void down() { curbp->b_point = lncolumn(curbp, dndn(curbp, curbp->b_point),curbp->b_col); }
void lnbegin() { curbp->b_point = segstart(curbp, lnstart(curbp,curbp->b_point), curbp->b_point); }
void quit() { done = 1; }
void lnend()
{
curbp->b_point = dndn(curbp, curbp->b_point);
left();
}
void pgdown()
{
curbp->b_page = curbp->b_point = upup(curbp, curbp->b_epage);
while (0 < curbp->b_row--)
down();
curbp->b_epage = pos(curbp, curbp->b_ebuf);
}
void pgup()
{
int i = curbp->w_rows;
while (0 < --i) {
curbp->b_page = upup(curbp, curbp->b_page);
up();
}
}
void insert()
{
assert(curbp->b_gap <= curbp->b_egap);
if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK)) return;
curbp->b_point = movegap(curbp, curbp->b_point);
*curbp->b_gap++ = *input == '\r' ? '\n' : *input;
curbp->b_point = pos(curbp, curbp->b_egap);
curbp->b_flags |= B_MODIFIED;
}
void backsp()
{
curbp->b_point = movegap(curbp, curbp->b_point);
if (curbp->b_buf < curbp->b_gap) {
--curbp->b_gap;
curbp->b_flags |= B_MODIFIED;
}
curbp->b_point = pos(curbp, curbp->b_egap);
}
void delete()
{
curbp->b_point = movegap(curbp, curbp->b_point);
if (curbp->b_egap < curbp->b_ebuf) {
curbp->b_point = pos(curbp, ++curbp->b_egap);
curbp->b_flags |= B_MODIFIED;
}
}
void set_mark()
{
curbp->b_mark = (curbp->b_mark == curbp->b_point ? NOMARK : curbp->b_point);
msg("Mark set");
}
void copy_cut(int cut)
{
char_t *p;
/* if no mark or point == marker, nothing doing */
if (curbp->b_mark == NOMARK || curbp->b_point == curbp->b_mark) return;
if (scrap != NULL) {
free(scrap);
scrap = NULL;
}
if (curbp->b_point < curbp->b_mark) {
/* point above marker: move gap under point, region = marker - point */
(void)movegap(curbp, curbp->b_point);
p = ptr(curbp, curbp->b_point);
nscrap = curbp->b_mark - curbp->b_point;
} else {
/* if point below marker: move gap under marker, region = point - marker */
(void)movegap(curbp, curbp->b_mark);
p = ptr(curbp, curbp->b_mark);
nscrap = curbp->b_point - curbp->b_mark;
}
if ((scrap = (char_t*) malloc(nscrap)) == NULL) {
msg("No more memory available.");
} else {
(void)memcpy(scrap, p, nscrap * sizeof (char_t));
if (cut) {
curbp->b_egap += nscrap; /* if cut expand gap down */
curbp->b_point = pos(curbp, curbp->b_egap); /* set point to after region */
curbp->b_flags |= B_MODIFIED;
msg("%ld bytes cut.", nscrap);
} else {
msg("%ld bytes copied.", nscrap);
}
curbp->b_mark = NOMARK; /* unmark */
}
}
void paste()
{
if (nscrap <= 0) {
msg("Nothing to paste.");
} else if (nscrap < curbp->b_egap - curbp->b_gap || growgap(curbp, nscrap)) {
curbp->b_point = movegap(curbp, curbp->b_point);
memcpy(curbp->b_gap, scrap, nscrap * sizeof (char_t));
curbp->b_gap += nscrap;
curbp->b_point = pos(curbp, curbp->b_egap);
curbp->b_flags |= B_MODIFIED;
}
}
void copy() { copy_cut(FALSE); }
void cut() { copy_cut(TRUE); }
void killtoeol()
{
/* point = start of empty line or last char in file */
if (*(ptr(curbp, curbp->b_point)) == 0xa || (curbp->b_point + 1 == ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap))) ) {
delete();
} else {
curbp->b_mark = curbp->b_point;
lnend();
copy_cut(TRUE);
}
}
point_t search_forward(buffer_t *bp, point_t start_p, char *stext)
{
point_t end_p = pos(bp, bp->b_ebuf);
point_t p,pp;
char* s;
if (0 == strlen(stext))
return start_p;
for (p=start_p; p < end_p; p++) {
for (s=stext, pp=p; *s == *(ptr(bp, pp)) && *s !='\0' && pp < end_p; s++, pp++)
;
if (*s == '\0')
return pp;
}
return -1;
}
void search()
{
int cpos = 0;
int c;
point_t o_point = curbp->b_point;
point_t found;
searchtext[0] = '\0';
msg("Search: %s", searchtext);
dispmsg();
cpos = strlen(searchtext);
for (;;) {
refresh();
c = getch();
/* ignore control keys other than C-g, backspace, CR, C-s, C-R, ESC */
if (c < 32 && c != 07 && c != 0x08 && c != 0x13 && c != 0x12 && c != 0x1b)
continue;
switch(c) {
case 0x1b: /* esc */
searchtext[cpos] = '\0';
flushinp(); /* discard any escape sequence without writing in buffer */
return;
case 0x07: /* ctrl-g */
curbp->b_point = o_point;
return;
case 0x13: /* ctrl-s, do the search */
found = search_forward(curbp, curbp->b_point, searchtext);
if (found != -1 ) {
curbp->b_point = found;
msg("Search: %s", searchtext);
display();
} else {
msg("Failing Search: %s", searchtext);
dispmsg();
curbp->b_point = 0;
}
break;
case 0x7f: /* del, erase */
case 0x08: /* backspace */
if (cpos == 0)
continue;
searchtext[--cpos] = '\0';
msg("Search: %s", searchtext);
dispmsg();
break;
default:
if (cpos < STRBUF_M - 1) {
searchtext[cpos++] = c;
searchtext[cpos] = '\0';
msg("Search: %s", searchtext);
dispmsg();
}
break;
}
}
}
/* the key bindings: desc, keys, func */
keymap_t keymap[] = {
{"C-a beginning-of-line ", "\x01", lnbegin },
{"C-b ", "\x02", left },
{"C-d forward-delete-char ", "\x04", delete },
{"C-e end-of-line ", "\x05", lnend },
{"C-f ", "\x06", right },
{"C-n ", "\x0E", down },
{"C-p ", "\x10", up },
{"C-h backspace ", "\x08", backsp },
{"C-k kill-to-eol ", "\x0B", killtoeol },
{"C-s search ", "\x13", search },
{"C-v ", "\x16", pgdown },
{"C-w kill-region ", "\x17", cut},
{"C-y yank ", "\x19", paste},
{"C-space set-mark ", "\x00", set_mark },
{"esc @ set-mark ", "\x1B\x40", set_mark },
{"esc k kill-region ", "\x1B\x6B", cut },
{"esc v ", "\x1B\x76", pgup },
{"esc w copy-region ", "\x1B\x77", copy},
{"esc < beg-of-buf ", "\x1B\x3C", top },
{"esc > end-of-buf ", "\x1B\x3E", bottom },
{"up previous-line ", "\x1B\x5B\x41", up },
{"down next-line ", "\x1B\x5B\x42", down },
{"left backward-character ", "\x1B\x5B\x44", left },
{"right forward-character ", "\x1B\x5B\x43", right },
{"home beginning-of-line ", "\x1B\x4F\x48", lnbegin },
{"end end-of-line ", "\x1B\x4F\x46", lnend },
{"DEL forward-delete-char ", "\x1B\x5B\x33\x7E", delete },
{"backspace delete-left ", "\x7f", backsp },
{"PgUp ", "\x1B\x5B\x35\x7E",pgup },
{"PgDn ", "\x1B\x5B\x36\x7E", pgdown },
{"C-x C-s save-buffer ", "\x18\x13", save },
{"C-x C-c exit ", "\x18\x03", quit },
{"K_ERROR ", NULL, NULL }
};
int main(int argc, char **argv)
{
if (argc != 2) fatal("usage: " E_NAME " filename\n");
initscr();
raw();
noecho();
curbp = new_buffer();
(void)insert_file(argv[1], FALSE);
/* Save filename irregardless of load() success. */
strncpy(curbp->b_fname, argv[1], MAX_FNAME);
curbp->b_fname[MAX_FNAME] = '\0'; /* force truncation */
if (!growgap(curbp, CHUNK)) fatal("Failed to allocate required memory.\n");
key_map = keymap;
while (!done) {
display();
input = get_key(key_map, &key_return);
if (key_return != NULL) {
(key_return->func)();
} else {
/* allow TAB and NEWLINE, any other control char is 'Not Bound' */
if (*input > 31 || *input == 10 || *input == 9)
insert();
else {
fflush(stdin);
msg("Not bound");
}
}
}
if (scrap != NULL) free(scrap);
if (curbp != NULL) free(curbp);
move(MSGLINE, 0);
refresh();
noraw();
endwin();
return 0;
}