be

based editor
git clone git://git.kocotian.pl/be.git
Log | Files | Refs | README | LICENSE

be.c (22228B)


      1 /*
      2    be - based editor - simple editor inspired by ed, vi, emacs and acme.
      3    Copyright (C) 2021  Kacper Kocot <kocotian@kocotian.pl>
      4 
      5    This program is free software; you can redistribute it and/or modify
      6    it under the terms of the GNU General Public License as published by
      7    the Free Software Foundation; either version 3 of the License, or
      8    (at your option) any later version.
      9 
     10    This program is distributed in the hope that it will be useful,
     11    but WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13    GNU General Public License for more details.
     14 
     15    You should have received a copy of the GNU General Public License
     16    along with this program; if not, write to the Free Software Foundation,
     17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
     18 */
     19 
     20 #include <ctype.h>
     21 #include <errno.h>
     22 #include <fcntl.h>
     23 #include <stdio.h>
     24 #include <stdlib.h>
     25 #include <sys/ioctl.h>
     26 #include <sys/stat.h>
     27 #include <termios.h>
     28 #include <unistd.h>
     29 
     30 #ifdef __linux__
     31 #include <linux/limits.h>
     32 #elif __FreeBSD__
     33 #include <sys/syslimits.h>
     34 #elif __OpenBSD__
     35 #include <limits.h>
     36 #else
     37 #define UNLIMITED
     38 #endif
     39 
     40 #include <arg.h>
     41 #include <lang.h>
     42 #include <str.h>
     43 #include <util.h>
     44 
     45 #define CURBUF (be.buffers.data[be.windows.data[be.focusedwin].buffer])
     46 #define CURBUFINDEX (be.windows.data[be.focusedwin].buffer)
     47 #define CURWIN (be.windows.data[be.focusedwin])
     48 #define CURWININDEX (be.focusedwin)
     49 #define REPLACE (void *)(-1)
     50 #define SUBMODES_MAX 32
     51 
     52 #ifdef UNLIMITED
     53 #define PATH_MAX 1024
     54 #endif
     55 
     56 /* types */
     57 typedef enum Mod {
     58 	ModNone = 0xff, ModControl = 0x1f, ModShift = 0xdf,
     59 } Mod;
     60 
     61 typedef enum Mode {
     62 	ModeNormal, ModeEdit, ModeReplace,
     63 	ModeBuffer, ModeCommand,
     64 	SubModeGlobal,
     65 } Mode;
     66 
     67 typedef union Arg {
     68 	int i;
     69 	unsigned int ui;
     70 	char c;
     71 	float f;
     72 	const void *v;
     73 } Arg;
     74 
     75 typedef union IArg {
     76 	int i;
     77 	unsigned int ui;
     78 	char c;
     79 	float f;
     80 	const void *v;
     81 	String S;
     82 } IArg;
     83 
     84 typedef struct Key {
     85 	Mod mod;
     86 	unsigned char key;
     87 	void (*func)();
     88 	const Arg arg;
     89 } Key;
     90 
     91 typedef struct Command {
     92 	char *cmd;
     93 	char *alias;
     94 	void (*func)();
     95 	const Arg arg;
     96 } Command;
     97 
     98 typedef struct Line {
     99 	char *data;
    100 	size_t len;
    101 	int isMarked;
    102 } Line;
    103 
    104 typedef struct Buffer {
    105 	Array(Line) lines;
    106 	char path[PATH_MAX], name[NAME_MAX];
    107 	int anonymous, dirty;
    108 	ssize_t x, y, xvis, xoff;
    109 	Mode mode;
    110 	Mode submodes[SUBMODES_MAX];
    111 	size_t submodeslen;
    112 } Buffer;
    113 
    114 typedef struct Window {
    115 	int buffer;
    116 	int r, c, x, y;
    117 } Window;
    118 
    119 typedef struct Binding {
    120 	Key *keys;
    121 	size_t len;
    122 } Binding;
    123 
    124 /* prototypes */
    125 static inline Line newLine(size_t siz);
    126 /*********/
    127 static void rawOn(void);
    128 static void rawRestore(void);
    129 static void getws(int *r, int *c);
    130 /*********/
    131 static void termRefresh(void);
    132 static void appendRows(String *ab);
    133 static void appendContents(String *ab);
    134 static void appendStatus(String *ab);
    135 /*********/
    136 static void abAppend(String *ab, const char *str, size_t len);
    137 #define abPrintf(AB, CP, CPLEN, ...) \
    138 	(abAppend((AB), (CP), (unsigned)snprintf((CP), (CPLEN), __VA_ARGS__)))
    139 static void abFree(String *ab);
    140 /*********/
    141 static unsigned char editorGetKey(void);
    142 static void editorParseKey(unsigned char key);
    143 static inline void edit(void);
    144 static inline void switchmode(Mode mode);
    145 /*********/
    146 static Buffer createBuffer(void);
    147 static void newBuffer(void);
    148 static void editBuffer(char *filename);
    149 static void freeBuffer(Buffer *buf);
    150 static int writeBuffer(Buffer *buf, char *filename);
    151 static int minibufferPrint(const char *s);
    152 static int minibufferError(const char *s);
    153 static inline int submodePush(Buffer *b, Mode m);
    154 static inline int submodePop(Buffer *b);
    155 /*********/
    156 static void setup(char *filename);
    157 static void finish(void);
    158 static void usage(void);
    159 /*********/
    160 static void echo(const Arg *arg);
    161 static void echoe(const Arg *arg);
    162 static void normalmode(const Arg *arg);
    163 static void insertmode(const Arg *arg);
    164 static void appendmode(const Arg *arg);
    165 static void replacemode(const Arg *arg);
    166 static void globalsubmode(const Arg *arg);
    167 static void buffermode(const Arg *arg);
    168 static void commandmode(const Arg *arg);
    169 static void cursormove(const Arg *arg);
    170 static void beginning(const Arg *arg);
    171 static void ending(const Arg *arg);
    172 static void findchar(const Arg *arg);
    173 static void insertchar(const Arg *arg, const IArg *iarg);
    174 static void replacechar(const Arg *arg, const IArg *iarg);
    175 static void removechar(const Arg *arg);
    176 static void openline(const Arg *arg);
    177 static void deletelinecontent(const Arg *arg);
    178 static void deleteline(const Arg *arg);
    179 static void changeline(const Arg *arg);
    180 static void togglemark(const Arg *arg);
    181 static void execcmd(const Arg *arg);
    182 static void cmdinsertchar(const Arg *arg, const IArg *iarg);
    183 static void cmdremovechar(const Arg *arg);
    184 static void shell(const Arg *arg, const IArg *iarg);
    185 static void bufwriteclose(const Arg *arg);
    186 static void bufwrite(const Arg *arg);
    187 static void bufclose(const Arg *arg);
    188 static void bufkill(const Arg *arg);
    189 
    190 /* global variables */
    191 static struct {
    192 	struct termios origtermios;
    193 	Array(Buffer) buffers;
    194 	Array(Window) windows;
    195 	int focusedwin;
    196 	int r, c;
    197 	String cmd;
    198 } be;
    199 
    200 const Arg nullarg = {.i = 0};
    201 char *argv0;
    202 
    203 /* config */
    204 #include "config.h"
    205 
    206 /* constructors */
    207 static inline Line
    208 newLine(size_t siz)
    209 {
    210 	return (Line){ malloc(siz), 0, 0 };
    211 }
    212 
    213 /* terminal */
    214 static void
    215 rawOn(void)
    216 {
    217 	struct termios raw;
    218 
    219 	if (tcgetattr(STDIN_FILENO, &(be.origtermios)) < 0)
    220 		die("tcgetattr:");
    221 	raw = (be.origtermios);
    222 	raw.c_cflag |= (CS8);
    223 	raw.c_iflag &= (tcflag_t)~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    224 	raw.c_lflag &= (tcflag_t)~(ECHO | ICANON | IEXTEN | ISIG);
    225 	raw.c_oflag &= (tcflag_t)~(OPOST);
    226 	raw.c_cc[VMIN] = 0;
    227 	raw.c_cc[VTIME] = 1;
    228 	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) < 0)
    229 		die("tcsetattr:");
    230 }
    231 
    232 static void
    233 rawRestore(void)
    234 {
    235 	if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &(be.origtermios)) < 0)
    236 		die("tcsetattr:");
    237 }
    238 
    239 static void
    240 getws(int *r, int *c)
    241 {
    242 	struct winsize ws;
    243 	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
    244 		die("ioctl:");
    245 	if (ws.ws_col < 20 || ws.ws_row < 3)
    246 		die(lang_err[ErrScreenTooSmall], 20, 3);
    247 	*c = ws.ws_col;
    248 	*r = ws.ws_row;
    249 }
    250 
    251 /* output */
    252 static void
    253 termRefresh(void)
    254 {
    255 	String ab = { NULL, 0 };
    256 	char cp[24];
    257 
    258 	abAppend(&ab, "\033[?25l\033[H", 9);
    259 	appendRows(&ab);
    260 	appendContents(&ab);
    261 	appendStatus(&ab);
    262 	abPrintf(&ab, cp, 24, "\033[%4d;%4ldH\033[?25h\033[%c q",
    263 			FOCUSPOINT, (CURBUF.xvis - CURBUF.xoff) + 1,
    264 			CURBUF.mode == ModeEdit ? '5' : '1');
    265 	if (CURBUF.mode == ModeCommand)
    266 		abPrintf(&ab, cp, 24, "\033[%4d;%4ldH",
    267 				be.r, (be.cmd.len) + 2);
    268 
    269 	if ((unsigned)write(STDOUT_FILENO, ab.data, ab.len) != ab.len)
    270 		die("write:");
    271 
    272 	abFree(&ab);
    273 }
    274 
    275 static void
    276 appendRows(String *ab)
    277 {
    278 	size_t y;
    279 	for (y = 0; y < (unsigned)(CURWIN.r - 1); ++y)
    280 		abAppend(ab, "\033[K~\r\n", (y < (unsigned)(CURWIN.r - 1)) ? 6 : 4);
    281 }
    282 
    283 static void
    284 appendContents(String *ab)
    285 {
    286 	ssize_t y, x, xvis;
    287 	int xvf; /* xvis flag */
    288 	char cp[19], c;
    289 	String printl;
    290 
    291 	for (y = 0; y < CURWIN.r; ++y) {
    292 		printl.data = malloc(printl.len = 0);
    293 		if ((unsigned)(y + CURBUF.y - FOCUSPOINT) < CURBUF.lines.len) {
    294 			abPrintf(ab, cp, 19, "\033[%4ld;0H\033[K", y);
    295 			xvf = (y + CURBUF.y - FOCUSPOINT == CURBUF.y) ? 1 : 0;
    296 			xvis = 0;
    297 			if (xvf) CURBUF.xvis = xvis;
    298 			if (CURBUF.lines.data[y + CURBUF.y - FOCUSPOINT].isMarked)
    299 				abAppend(&printl, "\033[34m", 5);
    300 			else
    301 				abAppend(&printl, "\033[0m", 4);
    302 			for (x = 0; x < (signed)CURBUF.lines.data[y + CURBUF.y - FOCUSPOINT].len; ++x) {
    303 				c = CURBUF.lines.data[y + CURBUF.y - FOCUSPOINT].data[x];
    304 				if (isprint(c)) {
    305 					/* printable */
    306 					abAppend(&printl, &c, 1);
    307 					++xvis;
    308 				} else if (c < 0) {
    309 					/* unicode */
    310 					/* FIXME: partially broken with horiz. scrolling */
    311 					size_t bytes;
    312 					if ((unsigned)((c >> 5) & 0x07) == 0x06) bytes = 2;
    313 					else if ((unsigned)((c >> 4) & 0x0f) == 0x0e) bytes = 3;
    314 					else if ((unsigned)((c >> 3) & 0x1f) == 0x1e) bytes = 4;
    315 					else bytes = 1;
    316 					abAppend(&printl, CURBUF.lines.data[y + CURBUF.y - FOCUSPOINT].data + x, bytes);
    317 					++xvis;
    318 					x += (signed)bytes - 1;
    319 				} else {
    320 					/* control chars */
    321 					if (c == '\t') {
    322 						unsigned int tw;
    323 						for (tw = 0; tw < tabwidth; ++tw)
    324 							abAppend(&printl, &indentationchar, 1);
    325 						xvis += (signed)tabwidth;
    326 					} else {
    327 						abAppend(&printl, "^", 1);
    328 						c = c ^ 0x40;
    329 						abAppend(&printl, &c, 1);
    330 						xvis += 2;
    331 					}
    332 				}
    333 				if (xvf && x < CURBUF.x) CURBUF.xvis = xvis;
    334 			}
    335 			if (CURBUF.xvis + CURBUF.xoff > (CURWIN.c - 1))
    336 				CURBUF.xoff = CURBUF.xvis - (CURWIN.c - 1);
    337 			else
    338 				CURBUF.xoff = 0;
    339 			if ((signed)printl.len - CURBUF.xoff > 0)
    340 				abAppend(ab,
    341 						(printl.data) + CURBUF.xoff,
    342 						(unsigned)MIN((signed)printl.len - CURBUF.xoff, CURWIN.c - 1));
    343 		}
    344 		free(printl.data);
    345 	}
    346 }
    347 
    348 static void
    349 appendStatus(String *ab)
    350 {
    351 	char cp[256];
    352 	ssize_t i;
    353 	i = -1;
    354 	abPrintf(ab, cp, 256, "\033[%4d;0H\033[K",
    355 			(unsigned)(CURWIN.r));
    356 	while (++i < CURWIN.c) {
    357 		abAppend(ab, " ", 1);
    358 	}
    359 	abAppend(ab, "\r", 1);
    360 	/* status drawing */
    361 	{
    362 		abPrintf(ab, cp, 256, "\033[1;33;7m %s", lang_modes[CURBUF.mode]);
    363 		for (i = 0; i < (signed)CURBUF.submodeslen; ++i) {
    364 			abPrintf(ab, cp, 256, "/%s",
    365 					lang_modes[CURBUF.submodes[i]]
    366 			);
    367 		}
    368 		abPrintf(ab, cp, 256, " \033[0m %s | %c:%c L%ld/%ld | C%ld-%ld/%ld | %ld buffer(s)\033[0m",
    369 				CURBUF.anonymous ?
    370 					"*anonymous*" : CURBUF.name,
    371 				CURBUF.anonymous ? 'U' : '-',
    372 				CURBUF.dirty ? '*' : '-',
    373 				CURBUF.y + 1,
    374 				CURBUF.lines.len,
    375 				CURBUF.x + 1,
    376 				CURBUF.xvis + 1,
    377 				CURBUF.lines.data[CURBUF.y].len,
    378 				be.buffers.len - 1
    379 		);
    380 	}
    381 	abAppend(ab, "\r\033[0m", 6);
    382 	if (CURBUF.mode == ModeCommand) {
    383 		abAppend(ab, "\r\n\033[K:", 6);
    384 		abAppend(ab, be.cmd.data, be.cmd.len);
    385 	}
    386 }
    387 
    388 /* append buffer */
    389 static void
    390 abAppend(String *ab, const char *str, size_t len)
    391 {
    392 	char *nb;
    393 	if ((nb = realloc(ab->data, ab->len + len)) == NULL)
    394 		return;
    395 	memcpy((ab->data = nb) + ab->len, str, len);
    396 	ab->len += len;
    397 }
    398 
    399 static void
    400 abFree(String *ab)
    401 {
    402 	free(ab->data);
    403 }
    404 
    405 /* editor */
    406 static unsigned char
    407 editorGetKey(void)
    408 {
    409 	ssize_t rb;
    410 	unsigned char c;
    411 
    412 	termRefresh();
    413 	while ((rb = read(STDIN_FILENO, &c, 1)) != 1)
    414 		if (rb < 0 && errno != EAGAIN)
    415 			die("read:");
    416 	return c;
    417 }
    418 
    419 static void
    420 editorParseKey(unsigned char key)
    421 {
    422 	Binding *binds;
    423 	size_t i;
    424 	IArg ia = {.c = (char)key};
    425 	binds = &bindings[CURBUF.submodeslen ?
    426 		CURBUF.submodes[CURBUF.submodeslen - 1] : CURBUF.mode];
    427 	for (i = 0; i < binds->len; ++i)
    428 		if (key == (binds->keys[i].key
    429 						& binds->keys[i].mod)
    430 		|| i == binds->len - 1) {
    431 			(binds->keys[i].func)(&(binds->keys[i].arg), &ia);
    432 			return;
    433 		}
    434 }
    435 
    436 static inline void
    437 edit(void)
    438 {
    439 	while (be.buffers.len - 1) editorParseKey(editorGetKey());
    440 }
    441 
    442 static inline void
    443 switchmode(Mode mode)
    444 {
    445 	CURBUF.mode = mode;
    446 }
    447 
    448 /* buffers */
    449 static Buffer
    450 createBuffer(void)
    451 {
    452 	Buffer b;
    453 	newVector(b.lines);
    454 	*b.path = *b.name = '\0';
    455 	b.anonymous = 1;
    456 	b.dirty = 0;
    457 	b.x = b.y = b.xvis = b.xoff = 0;
    458 	b.mode = ModeNormal;
    459 	b.submodeslen = 0;
    460 	return b;
    461 }
    462 
    463 static void
    464 newBuffer(void)
    465 {
    466 	pushVector(be.buffers, createBuffer());
    467 	pushVector(be.buffers.data[be.buffers.len - 1].lines, newLine(0));
    468 }
    469 
    470 static void
    471 editBuffer(char *filename)
    472 {
    473 	Buffer *buf;
    474 	int fd;
    475 	struct stat sb;
    476 	char *data;
    477 	String fstr, fline;
    478 	Line fpush;
    479 
    480 	pushVector(be.buffers, createBuffer());
    481 	buf = be.buffers.data + be.buffers.len - 1;
    482 	strncpy(buf->path, filename, PATH_MAX);
    483 	strncpy(buf->name, ((strrchr(filename, '/')) == NULL ?
    484 				filename : (strrchr(filename, '/') + 1)), NAME_MAX);
    485 
    486 	buf->anonymous = 0;
    487 
    488 	if (stat(filename, &sb) < 0) {
    489 		errno = 0;
    490 		pushVector(be.buffers.data[be.buffers.len - 1].lines, newLine(0));
    491 		return;
    492 	}
    493 
    494 	if ((fd = open(filename, O_RDONLY)) < 0)
    495 		die("open:");
    496 
    497 	if (fstat(fd, &sb) < 0)
    498 		die("stat:");
    499 
    500 	/* maybe mmap will be better here? */
    501 	if (read(fd, fstr.data = data = malloc(((unsigned)sb.st_size * sizeof *data)),
    502 				fstr.len = (unsigned)sb.st_size) != (unsigned)sb.st_size)
    503 		die("read:");
    504 
    505 	while (Strtok(fstr, &fline, '\n') > 0) {
    506 		fpush.data = strndup(fline.data, (fpush.len = fline.len - 1));
    507 		fpush.isMarked = 0;
    508 		pushVector(buf->lines, fpush);
    509 		fstr.data += fline.len;
    510 		fstr.len -= fline.len;
    511 	}
    512 
    513 	free(data);
    514 }
    515 
    516 static void
    517 freeBuffer(Buffer *buf)
    518 {
    519 	size_t i;
    520 	for (i = 0; i < buf->lines.len; ++i)
    521 		free(buf->lines.data[i].data);
    522 }
    523 
    524 static int
    525 writeBuffer(Buffer *buf, char *filename)
    526 {
    527 	int fd;
    528 	size_t i;
    529 	if (filename == NULL) {
    530 		if (buf->anonymous)
    531 			return minibufferError(lang_err[ErrWriteAnon]);
    532 		else
    533 			filename = buf->path;
    534 	}
    535 	if ((fd = open(filename, O_WRONLY | O_CREAT, 0755)) < 0)
    536 		die("open:");
    537 	for (i = 0; i < buf->lines.len; ++i) {
    538 		write(fd, buf->lines.data[i].data, buf->lines.data[i].len);
    539 		write(fd, "\n", 1);
    540 	}
    541 	close(fd);
    542 	CURBUF.dirty = 0;
    543 	return 0;
    544 }
    545 
    546 static int
    547 minibufferPrint(const char *s)
    548 {
    549 	String ab = { NULL, 0 };
    550 	char cp[20];
    551 
    552 	abPrintf(&ab, cp, 20, "\033[%4d;%4dH\033[0m\033[K",
    553 			be.r, 1);
    554 
    555 	abAppend(&ab, s, strlen(s));
    556 
    557 	abAppend(&ab, "\033[0m", 4);
    558 
    559 	if ((unsigned)write(STDOUT_FILENO, ab.data, ab.len) != ab.len)
    560 		die("write:");
    561 
    562 	abFree(&ab);
    563 	return 0;
    564 }
    565 
    566 static int
    567 minibufferError(const char *s)
    568 {
    569 	String ab = { NULL, 0 };
    570 	char cp[23];
    571 
    572 	abPrintf(&ab, cp, 23, "\033[%4d;%4dH\033[0;31m\033[K",
    573 			be.r, 1);
    574 
    575 	abAppend(&ab, s, strlen(s));
    576 
    577 	abAppend(&ab, "\033[0m", 4);
    578 
    579 	if ((unsigned)write(STDOUT_FILENO, ab.data, ab.len) != ab.len)
    580 		die("write:");
    581 
    582 	abFree(&ab);
    583 	return 1;
    584 }
    585 
    586 static inline int
    587 submodePush(Buffer *b, Mode m)
    588 {
    589 	if (b->submodeslen >= SUBMODES_MAX)
    590 		return 1;
    591 	b->submodes[(b->submodeslen)++] = m;
    592 	return 0;
    593 }
    594 
    595 static inline int
    596 submodePop(Buffer *b)
    597 {
    598 	if (!b->submodeslen)
    599 		return 1;
    600 	--(b->submodeslen);
    601 	return 0;
    602 }
    603 
    604 /* other */
    605 static void
    606 setup(char *filename)
    607 {
    608 	Buffer b;
    609 	Window w;
    610 	rawOn();
    611 	getws(&(be.r), &(be.c));
    612 
    613 	newVector(be.buffers);
    614 	/* pushing fallback buffer used when no buffers left */
    615 	memset(&b, 0, sizeof b);
    616 	pushVector(be.buffers, b);
    617 
    618 	newVector(be.windows);
    619 	w.r = be.r - 1; w.c = be.c;
    620 	w.x = w.y = 0;
    621 	w.buffer = 1;
    622 	pushVector(be.windows, w);
    623 	be.focusedwin = 0;
    624 	be.cmd.data = malloc(be.cmd.len = 0);
    625 
    626 	if (filename == NULL)
    627 		newBuffer();
    628 	else
    629 		editBuffer(filename);
    630 	minibufferPrint(lang_base[ErrDirty]);
    631 }
    632 
    633 static void
    634 finish(void)
    635 {
    636 	rawRestore();
    637 	write(STDOUT_FILENO, "\033[2J\033[H", 7);
    638 	exit(0);
    639 }
    640 
    641 static void
    642 usage(void)
    643 {
    644 	die("%s: %s [-hLv] [FILE]", lang_err[ErrUsage], argv0);
    645 }
    646 
    647 /* editor functions */
    648 static void
    649 echo(const Arg *arg)
    650 {
    651 	minibufferPrint(arg->v);
    652 }
    653 
    654 static void
    655 echoe(const Arg *arg)
    656 {
    657 	minibufferError(arg->v);
    658 }
    659 
    660 static void
    661 normalmode(const Arg *arg)
    662 {
    663 	switchmode(ModeNormal);
    664 	if (arg->ui == 0 && CURBUF.x > 0) --CURBUF.x;
    665 }
    666 
    667 static void
    668 insertmode(const Arg *arg)
    669 {
    670 	Arg a = {.i = 0};
    671 	if (arg->i)
    672 		beginning(&a);
    673 	switchmode(ModeEdit);
    674 }
    675 
    676 static void
    677 appendmode(const Arg *arg)
    678 {
    679 	Arg a = {.i = 0};
    680 	++CURBUF.x;
    681 	if (arg->i)
    682 		ending(&a);
    683 	if (CURBUF.x > (signed)CURBUF.lines.data[CURBUF.y].len)
    684 		CURBUF.x = (signed)CURBUF.lines.data[CURBUF.y].len;
    685 	switchmode(ModeEdit);
    686 }
    687 
    688 static void
    689 replacemode(const Arg *arg)
    690 {
    691 	(void)arg;
    692 	switchmode(ModeReplace);
    693 }
    694 
    695 static void
    696 globalsubmode(const Arg *arg)
    697 {
    698 	(void)arg;
    699 	submodePush(&CURBUF, SubModeGlobal);
    700 	editorParseKey(editorGetKey());
    701 	submodePop(&CURBUF); /* SubModeGlobal */
    702 }
    703 
    704 static void
    705 buffermode(const Arg *arg)
    706 {
    707 	(void)arg;
    708 	switchmode(ModeBuffer);
    709 	editorParseKey(editorGetKey());
    710 	switchmode(ModeNormal);
    711 }
    712 
    713 static void
    714 commandmode(const Arg *arg)
    715 {
    716 	(void)arg;
    717 	switchmode(ModeCommand);
    718 }
    719 
    720 static void
    721 cursormove(const Arg *arg)
    722 {
    723 	switch (arg->i) {
    724 	case 0: /* left */
    725 		if (CURBUF.x > 0) --(CURBUF.x);
    726 		else minibufferPrint(lang_info[InfoAlreadyBeg]);; break;
    727 	case 1: /* down */
    728 		if (CURBUF.y < (signed)CURBUF.lines.len - 1) ++(CURBUF.y);
    729 		else minibufferPrint(lang_info[InfoAlreadyBot]);; break;
    730 	case 2: /* up */
    731 		if (CURBUF.y > 0) --(CURBUF.y);
    732 		else minibufferPrint(lang_info[InfoAlreadyTop]);; break;
    733 	case 3: /* right */
    734 		if (CURBUF.x < (signed)CURBUF.lines.data[CURBUF.y].len) ++(CURBUF.x);
    735 		else minibufferPrint(lang_info[InfoAlreadyEnd]);; break;
    736 	}
    737 	if (CURBUF.x >= (signed)CURBUF.lines.data[CURBUF.y].len)
    738 		CURBUF.x = (signed)CURBUF.lines.data[CURBUF.y].len;
    739 }
    740 
    741 static void
    742 beginning(const Arg *arg)
    743 {
    744 	if (!arg->i) CURBUF.x = 0;
    745 	else CURBUF.y = 0;
    746 }
    747 
    748 static void
    749 ending(const Arg *arg)
    750 {
    751 	if (!arg->i)
    752 		CURBUF.x = MAX(0, (signed)CURBUF.lines.data[CURBUF.y].len);
    753 	else
    754 		CURBUF.y = MAX(0, (signed)CURBUF.lines.len - 1);
    755 }
    756 
    757 static void
    758 findchar(const Arg *arg)
    759 {
    760 	unsigned char ch = editorGetKey();
    761 	Line *ln = &(CURBUF.lines.data[CURBUF.y]);
    762 	ssize_t i;
    763 	if (arg->i % 2) for (i = CURBUF.x - 1; i >= 0; --i) {
    764 		if (ln->data[i] == ch) {
    765 			CURBUF.x = i + (arg->i / 2);
    766 			break;
    767 		}
    768 	} else for (i = CURBUF.x + 1; i < (signed)ln->len; ++i) {
    769 		if (ln->data[i] == ch) {
    770 			CURBUF.x = i - (arg->i / 2) ;
    771 			break;
    772 		}
    773 	}
    774 }
    775 
    776 static void
    777 insertchar(const Arg *arg, const IArg *iarg)
    778 {
    779 	(void)arg;
    780 	CURBUF.lines.data[CURBUF.y].data =
    781 		realloc(CURBUF.lines.data[CURBUF.y].data,
    782 				++CURBUF.lines.data[CURBUF.y].len + 1);
    783 	memmove(CURBUF.lines.data[CURBUF.y].data + CURBUF.x + 1,
    784 			CURBUF.lines.data[CURBUF.y].data + CURBUF.x,
    785 			CURBUF.lines.data[CURBUF.y].len - (unsigned)CURBUF.x);
    786 	CURBUF.dirty = 1;
    787 
    788 	CURBUF.lines.data[CURBUF.y].data[CURBUF.x++] = iarg->c;
    789 }
    790 
    791 static void
    792 replacechar(const Arg *arg, const IArg *iarg)
    793 {
    794 	(void)arg;
    795 	if (CURBUF.x >= (signed)CURBUF.lines.data[CURBUF.y].len)
    796 		CURBUF.x = (signed)CURBUF.lines.data[CURBUF.y].len - 1;
    797 	CURBUF.dirty = 1;
    798 	CURBUF.lines.data[CURBUF.y].data[CURBUF.x++] = iarg->c;
    799 }
    800 
    801 static void
    802 removechar(const Arg *arg)
    803 {
    804 	(void)arg;
    805 	if (CURBUF.x <= 0) return;
    806 	memmove(CURBUF.lines.data[CURBUF.y].data + CURBUF.x - 1,
    807 			CURBUF.lines.data[CURBUF.y].data + CURBUF.x,
    808 			CURBUF.lines.data[CURBUF.y].len - (unsigned)CURBUF.x);
    809 	--(CURBUF.lines.data[CURBUF.y].len);
    810 	--CURBUF.x;
    811 }
    812 
    813 static void
    814 openline(const Arg *arg)
    815 {
    816 	CURBUF.lines.data = realloc(CURBUF.lines.data,
    817 				++(CURBUF.lines.len) * sizeof *(CURBUF.lines.data));
    818 	if (arg->i != 1) ++CURBUF.y;
    819 	memmove(CURBUF.lines.data + CURBUF.y,
    820 			CURBUF.lines.data + CURBUF.y - 1,
    821 			(CURBUF.lines.len - (unsigned)(CURBUF.y)) *
    822 				sizeof *(CURBUF.lines.data));
    823 	CURBUF.lines.data[CURBUF.y].data =
    824 		malloc(CURBUF.lines.data[CURBUF.y].len =
    825 				arg->i == 2 ? CURBUF.lines.data[CURBUF.y - 1].len -
    826 					(unsigned)CURBUF.x : 0);
    827 	if (arg->i == 2) {
    828 		memmove(CURBUF.lines.data[CURBUF.y].data,
    829 				CURBUF.lines.data[CURBUF.y - 1].data + CURBUF.x,
    830 				CURBUF.lines.data[CURBUF.y - 1].len - (unsigned)CURBUF.x);
    831 		CURBUF.lines.data[CURBUF.y - 1].len = (unsigned)CURBUF.x;
    832 	}
    833 	CURBUF.x = 0;
    834 	switchmode(ModeEdit);
    835 }
    836 
    837 static void
    838 deletelinecontent(const Arg *arg)
    839 {
    840 	if (arg->i > 0)
    841 		CURBUF.lines.data[CURBUF.y].len = (size_t)arg->i;
    842 	else if (arg->i < 0)
    843 		CURBUF.lines.data[CURBUF.y].len = (unsigned)CURBUF.x;
    844 	else
    845 		CURBUF.x = (unsigned)(CURBUF.lines.data[CURBUF.y].len = 0);
    846 }
    847 
    848 static void
    849 deleteline(const Arg *arg)
    850 {
    851 	deletelinecontent(arg);
    852 	if (CURBUF.lines.len < 2 || arg->i < 0)
    853 		return;
    854 	free(CURBUF.lines.data[CURBUF.y].data);
    855 	memmove(CURBUF.lines.data + CURBUF.y,
    856 			CURBUF.lines.data + CURBUF.y + 1,
    857 			(CURBUF.lines.len - (unsigned)(CURBUF.y) - 1) *
    858 				sizeof *(CURBUF.lines.data));
    859 	--CURBUF.lines.len;
    860 	if (CURBUF.y >= CURBUF.lines.len)
    861 		CURBUF.y = CURBUF.lines.len - 1;
    862 }
    863 
    864 static void
    865 changeline(const Arg *arg)
    866 {
    867 	deletelinecontent(arg);
    868 	switchmode(ModeEdit);
    869 }
    870 
    871 static void
    872 togglemark(const Arg *arg)
    873 {
    874 	CURBUF.lines.data[CURBUF.y].isMarked = !(CURBUF.lines.data[CURBUF.y].isMarked);
    875 }
    876 
    877 static void
    878 execcmd(const Arg *arg)
    879 {
    880 	String token;
    881 	IArg ia;
    882 	size_t i;
    883 	ia.S = be.cmd;
    884 	(void)arg;
    885 	Strtok2(&(ia.S), &token, ' ');
    886 	for (i = 0; i < LEN(commands); ++i) {
    887 		if (!Strcmpc(token, commands[i].cmd)
    888 		||  !Strcmpc(token, commands[i].alias)) {
    889 			(commands[i].func)(commands[i].arg, &(ia.S));
    890 			break;
    891 		}
    892 	}
    893 	if (i == LEN(commands)) {
    894 		minibufferError(lang_err[ErrCmdNotFound]);
    895 	}
    896 	be.cmd.len = 0;
    897 	switchmode(ModeNormal);
    898 }
    899 
    900 static void
    901 cmdinsertchar(const Arg *arg, const IArg *iarg)
    902 {
    903 	(void)arg;
    904 	be.cmd.data = realloc(be.cmd.data, ++be.cmd.len + 1);
    905 	memmove(be.cmd.data + be.cmd.len + 1,
    906 			be.cmd.data + be.cmd.len,
    907 			be.cmd.len - (unsigned)be.cmd.len);
    908 
    909 	be.cmd.data[be.cmd.len - 1] = iarg->c;
    910 }
    911 
    912 static void
    913 cmdremovechar(const Arg *arg)
    914 {
    915 	(void)arg;
    916 	if (be.cmd.len <= 0) return;
    917 	memmove(be.cmd.data + be.cmd.len - 1,
    918 			be.cmd.data + be.cmd.len,
    919 			be.cmd.len - (unsigned)be.cmd.len);
    920 	--(be.cmd.len);
    921 }
    922 
    923 static void
    924 shell(const Arg *arg, const IArg *iarg)
    925 {
    926 	char *shcmd;
    927 	rawRestore();
    928 	if (iarg->S.len) {
    929 		shcmd = malloc(iarg->S.len + 1);
    930 		strncpy(shcmd, iarg->S.data, iarg->S.len)[iarg->S.len] = 0;
    931 	} else shcmd = "$SHELL";
    932 	puts("");
    933 	if (system(shcmd))
    934 		printf("\"%s\" failed\n", shcmd);
    935 	if (iarg->S.len) free(shcmd);
    936 	puts(lang_info[InfoPressAnyKey]);
    937 	rawOn();
    938 	while ((read(STDIN_FILENO, &shcmd, 1)) != 1);
    939 }
    940 
    941 static void
    942 bufwriteclose(const Arg *arg)
    943 {
    944 	(void)arg;
    945 	if (CURBUF.dirty)
    946 		if (writeBuffer(&CURBUF, NULL))
    947 			return;
    948 	freeBuffer(be.buffers.data + CURBUFINDEX);
    949 	memmove(be.buffers.data + CURBUFINDEX,
    950 			be.buffers.data + CURBUFINDEX + 1,
    951 			be.buffers.len-- - (size_t)(CURBUFINDEX + 1));
    952 }
    953 
    954 static void
    955 bufwrite(const Arg *arg)
    956 {
    957 	(void)arg;
    958 	writeBuffer(&CURBUF, NULL);
    959 }
    960 
    961 static void
    962 bufclose(const Arg *arg)
    963 {
    964 	(void)arg;
    965 	if (CURBUF.dirty) {
    966 		minibufferError(lang_err[ErrDirty]);
    967 		return;
    968 	}
    969 	freeBuffer(be.buffers.data + CURBUFINDEX);
    970 	memmove(be.buffers.data + CURBUFINDEX,
    971 			be.buffers.data + CURBUFINDEX + 1,
    972 			be.buffers.len-- - (size_t)(CURBUFINDEX + 1));
    973 }
    974 
    975 static void
    976 bufkill(const Arg *arg)
    977 {
    978 	(void)arg;
    979 	freeBuffer(be.buffers.data + CURBUFINDEX);
    980 	memmove(be.buffers.data + CURBUFINDEX,
    981 			be.buffers.data + CURBUFINDEX + 1,
    982 			be.buffers.len-- - (size_t)(CURBUFINDEX + 1));
    983 }
    984 
    985 int
    986 main(int argc, char *argv[])
    987 {
    988 	char *filename;
    989 	ARGBEGIN {
    990 	case 'h': default: /* fallthrough */
    991 		usage();
    992 		break;
    993 	case 'L':
    994 		die("%s, %s", lang_base[LangCode], lang_base[LangName]);
    995 		break;
    996 	case 'v':
    997 		die("be-" VERSION);
    998 		break;
    999 	} ARGEND
   1000 
   1001 	filename = NULL;
   1002 
   1003 	if (argc > 1)
   1004 		usage();
   1005 	else if (argc == 1)
   1006 		filename = argv[0];
   1007 
   1008 	setup(filename);
   1009 	edit();
   1010 	finish();
   1011 
   1012 	return 0;
   1013 }