sandy

text editor
git clone git://git.suckless.org/sandy
Log | Files | Refs | README | LICENSE

sandy.c (60080B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <errno.h>
      3 #include <fcntl.h>
      4 #include <locale.h>
      5 #include <regex.h>
      6 #include <ncurses.h>
      7 #include <signal.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <unistd.h>
     12 #include <sys/ioctl.h>
     13 #include <sys/select.h>
     14 #include <sys/stat.h>
     15 #include <sys/types.h>
     16 #include <sys/wait.h>
     17 #include <limits.h>
     18 
     19 #include "arg.h"
     20 
     21 #define LINSIZ        128
     22 #define LEN(x)        (sizeof (x) / sizeof *(x))
     23 #define UTF8LEN(ch)   ((unsigned char)ch>=0xFC ? 6 : \
     24                       ((unsigned char)ch>=0xF8 ? 5 : \
     25                       ((unsigned char)ch>=0xF0 ? 4 : \
     26                       ((unsigned char)ch>=0xE0 ? 3 : \
     27                       ((unsigned char)ch>=0xC0 ? 2 : 1)))))
     28 #define ISASCII(ch)   ((unsigned char)ch < 0x80)
     29 #define ISCTRL(ch)    (((unsigned char)ch < ' ') || (ch == 0x7F))
     30 #define ISFILL(ch)    (isutf8 && !ISASCII(ch) && (unsigned char)ch<=0xBF)
     31 #define ISBLANK(ch)   (ch == ' ' || ch == '\t' || ch == '\0')
     32 #define ISWORDBRK(ch) (ISASCII(ch) && (ch < 0x30 || \
     33                       (ch > 0x39 && ch < 0x41) || \
     34                       (ch > 0x5A && ch < 0x5F) || \
     35                       ch == 0x60 || \
     36                       ch > 0x7A)) /* A bit flawed because we assume multibyte UTF8 chars are alnum */
     37 #define VLEN(ch,col)  (ch=='\t' ? tabstop-(col%tabstop) : (ISCTRL(ch) ? 2: (ISFILL(ch) ? 0: 1)))
     38 #define VLINES(l)     (1+(l?l->vlen/cols:0))
     39 #define FIXNEXT(pos)  while(isutf8 && ISFILL(pos.l->c[pos.o]) && ++pos.o < pos.l->len)
     40 #define FIXPREV(pos)  while(isutf8 && ISFILL(pos.l->c[pos.o]) && --pos.o > 0)
     41 #define LINESABS      (lines - (titlewin==NULL?0:1))
     42 
     43 /* Typedefs */
     44 typedef struct Line Line;
     45 struct Line {    /** The internal representation of a line of text */
     46 	char *c;     /* Line content */
     47 	size_t len;  /* Line byte length */
     48 	size_t vlen; /* On-screen line-length */
     49 	size_t mul;  /* How many times LINSIZ is c malloc'd to */
     50 	bool dirty;  /* Should I repaint on screen? */
     51 	Line *next;  /* Next line, NULL if I'm last */
     52 	Line *prev;  /* Previous line, NULL if I'm first */
     53 };
     54 
     55 typedef struct {  /** A position in the file */
     56 	Line *l;  /* Line */
     57 	size_t o; /* Offset inside the line */
     58 } Filepos;
     59 
     60 typedef union { /** An argument to a f_* function, generic */
     61 	long i;
     62 	const void *v;
     63 	Filepos(*m)(Filepos);
     64 } Arg;
     65 
     66 typedef struct {                    /** A keybinding */
     67 	union {
     68 		char c[6];                  /* Standard chars */
     69 		int i;                      /* NCurses code */
     70 	} keyv;
     71 	bool(*test[4])(void);           /* Conditions to match, make sure the last one is 0x00 */
     72 	void (*func)(const Arg * arg);  /* Function to perform */
     73 	const Arg arg;                  /* Argument to func() */
     74 } Key;
     75 
     76 typedef struct {                    /** A mouse click */
     77 	mmask_t mask;                   /* Mouse mask */
     78 	bool place[2];                  /* Place fcur / fsel at click place before testing */
     79 	bool(*test[3])(void);           /* Conditions to match, make sure the last one is 0x00 */
     80 	void (*func)(const Arg * arg);  /* Function to perform */
     81 	const Arg arg;                  /* Argument to func() */
     82 } Click;
     83 
     84 typedef struct {                    /** A command read at the fifo */
     85 	const char *re_text;            /* A regex to match the command, must have a parentheses group for argument */
     86 	bool(*test[3])(void);           /* Conditions to match, make sure the last one is 0x00 */
     87 	void (*func)(const Arg * arg);  /* Function to perform, argument is determined as arg->v from regex above */
     88 	const Arg arg;                  /* Argument to func(), if empty will fill .v = re_match */
     89 } Command;
     90 
     91 #define SYN_COLORS 8
     92 typedef struct {               /** A syntax definition */
     93 	char *name;                /* Syntax name */
     94 	char *file_re_text;        /* Apply to files matching this regex */
     95 	char *re_text[SYN_COLORS]; /* Apply colors (in order) matching these regexes */
     96 } Syntax;
     97 
     98 typedef struct Undo Undo;
     99 struct Undo {                   /** Undo information */
    100 	char flags;                 /* Flags: is insert/delete?, should concatenate with next undo/redo? */
    101 	unsigned long startl, endl; /* Line number for undo/redo start and end */
    102 	size_t starto, endo;        /* Character offset for undo/redo start and end */
    103 	char *str;                  /* Content (added or deleted text) */
    104 	Undo *prev;                 /* Previous undo/redo in the ring */
    105 };
    106 
    107 /* ENUMS */
    108 /* Colors */
    109 enum { DefFG, CurFG, SelFG, SpcFG, CtrlFG, Syn0FG, Syn1FG, Syn2FG, Syn3FG,
    110 	Syn4FG, Syn5FG, Syn6FG, Syn7FG, LastFG,
    111 };
    112 enum { DefBG, CurBG, SelBG, LastBG, }; /* NOTE: BGs MUST have a matching FG */
    113 
    114 /* arg->i to use in f_extsel() */
    115 enum { ExtDefault, ExtWord, ExtLines, ExtAll, };
    116 
    117 /* To use in lastaction */
    118 enum { LastNone, LastDelete, LastInsert, LastPipe, LastPipeRO, };
    119 
    120 /* Environment variables index */
    121 enum { EnvFind, EnvPipe, EnvLine, EnvOffset, EnvFile, EnvSyntax, EnvFifo,
    122 	EnvLast,
    123 };
    124 
    125 enum {                      /* To use in statusflags */
    126 	S_Running = 1,          /* Keep the editor running, flip to stop */
    127 	S_Readonly = 1 << 1,    /* File is readonly, this should be enforced so that file is not modified */
    128 	S_InsEsc = 1 << 2,      /* Insert next character as-is, even if it's a control sequence */
    129 	S_CaseIns = 1 << 3,     /* Search is casi insensitive */
    130 	S_Modified = 1 << 4,    /* File has been modified, set automatically */
    131 	S_DirtyScr = 1 << 5,    /* Screen is dirty and should be repainted, set automatically */
    132 	S_DirtyDown = 1 << 6,   /* Line has changed so that we must repaint from here down, set automatically */
    133 	S_NeedResize = 1 << 7,  /* Need to resize screen in next update, set automatically */
    134 	S_Warned = 1 << 8,      /* Set to warn about file not being saved */
    135 	S_GroupUndo = 1 << 9,   /* Last action was an insert, so another insert should group with it, set automatically */
    136 	S_AutoIndent = 1 << 10, /* Perform autoindenting on RET */
    137 	S_DumpStdout = 1 << 11, /* Dump to stdout instead of writing to a file */
    138 	S_Command = 1 << 12,    /* Command mode */
    139 	S_Sentence = 1 << 13,   /* Sentence mode. Pass the next command's parameters (if adjective) to the verb's function */
    140 	S_Parameter = 1 << 14,  /* Parameter mode. Pass the next character as parameter to the function of the command */
    141 	S_Multiply = 1 << 15,   /* Multiply mode. Replay a command x times */
    142 	S_Visual = 1 << 16,     /* Visual mode. You just select things */
    143 };
    144 
    145 enum {                 /* To use in Undo.flags */
    146 	UndoIns = 1,       /* This undo is an insert (otherwise a delete) */
    147 	UndoMore = 1 << 1, /* This undo must be chained with the next one when redoing */
    148 	RedoMore = 1 << 2, /* This undo must be chained with the next one when undoing */
    149 };
    150 
    151 /* Constants */
    152 static const char *envs[EnvLast] = {
    153 	[EnvFind] = "SANDY_FIND",
    154 	[EnvPipe] = "SANDY_PIPE",
    155 	[EnvLine] = "SANDY_LINE",
    156 	[EnvOffset] = "SANDY_OFFSET",
    157 	[EnvFile] = "SANDY_FILE",
    158 	[EnvSyntax] = "SANDY_SYNTAX",
    159 	[EnvFifo] = "SANDY_FIFO",
    160 };
    161 
    162 /* Variables */
    163 static Line   *fstline;            /* First line */
    164 static Line   *lstline;            /* Last line */
    165 static Line   *scrline;            /* First line seen on screen */
    166 static Filepos fsel;               /* Selection point on file */
    167 static Filepos fcur;               /* Insert position on file, cursor, current position */
    168 static Filepos fmrk = { NULL, 0 }; /* Mark */
    169 
    170 static int      syntx = -1;                          /* Current syntax index */
    171 static regex_t *find_res[2];                         /* Compiled regex for search term */
    172 static int      sel_re = 0;                          /* Index to the above, we keep 2 REs so regexec does not segfault */
    173 static int      ch;                                  /* Used to store input */
    174 static char     c[7];                                /* Used to store input */
    175 static char     fifopath[PATH_MAX];                  /* Path to command fifo */
    176 static char    *filename = NULL;                     /* Path to file loade on buffer */
    177 static char     title[BUFSIZ];                       /* Screen title */
    178 static char    *tmptitle = NULL;                     /* Screen title, temporary */
    179 static char    *tsl_str = NULL;                      /* String to print to status line */
    180 static char    *fsl_str = NULL;                      /* String to come back from status line */
    181 static WINDOW  *titlewin = NULL;                     /* Title ncurses window, NULL if there is a status line */
    182 static WINDOW  *textwin = NULL;                      /* Main ncurses window */
    183 static Undo    *undos;                               /* Undo ring */
    184 static Undo    *redos;                               /* Redo ring */
    185 static int      textattrs[LastFG][LastBG];           /* Text attributes for each color pair */
    186 static int      savestep = 0;                        /* Index to determine the need to save in undo/redo action */
    187 static int      fifofd;                              /* Command fifo file descriptor */
    188 static long     statusflags = S_Running | S_Command; /* Status flags, very important, OR'd (see enums above) */
    189 static int      lastaction = LastNone;               /* The last action we took (see enums above) */
    190 static int      cols, lines;                         /* Ncurses: to use instead of COLS and LINES, wise */
    191 static mmask_t  defmmask = 0;                        /* Ncurses: mouse event mask */
    192 static void   (*verb)(const Arg * arg);              /* Verb of current sentence */
    193 static Arg      varg;                                /* Arguments of the verb (some will be overwritten by adjective) */
    194 static int      vi;                                  /* Helping var to store place of verb in key chain */
    195 static int      multiply = 1;                        /* Times to replay a command */
    196 
    197 /* allocate memory or die. */
    198 static void *ecalloc(size_t, size_t);
    199 static void *erealloc(void *, size_t);
    200 static char *estrdup(const char *);
    201 
    202 /* Functions */
    203 /* f_* functions can be linked to an action or keybinding */
    204 static void f_adjective(const Arg *);
    205 static void f_center(const Arg *);
    206 static void f_delete(const Arg *);
    207 static void f_extsel(const Arg *);
    208 static void f_findbw(const Arg *);
    209 static void f_findfw(const Arg *);
    210 static void f_insert(const Arg *);
    211 static void f_line(const Arg *);
    212 static void f_mark(const Arg *);
    213 static void f_move(const Arg *);
    214 static void f_offset(const Arg *);
    215 static void f_pipe(const Arg *);
    216 static void f_pipero(const Arg *);
    217 static void f_repeat(const Arg *);
    218 static void f_save(const Arg *);
    219 static void f_select(const Arg *);
    220 static void f_spawn(const Arg *);
    221 static void f_suspend(const Arg *);
    222 static void f_syntax(const Arg * arg);
    223 static void f_toggle(const Arg * arg);
    224 static void f_undo(const Arg *);
    225 
    226 /* i_* funcions are called from inside the main code only */
    227 static Filepos       i_addtext(char *, Filepos);
    228 static void          i_addtoundo(Filepos, const char *);
    229 static void          i_addundo(bool, Filepos, Filepos, char *);
    230 static void          i_advpos(Filepos * pos, int o);
    231 static void          i_calcvlen(Line * l);
    232 static void          i_cleanup(int);
    233 static bool          i_deltext(Filepos, Filepos);
    234 static void          i_die(const char *str);
    235 static void          i_dirtyrange(Line *, Line *);
    236 static bool          i_dotests(bool(*const a[])(void));
    237 static void          i_dokeys(const Key[], unsigned int);
    238 static void          i_edit(void);
    239 static void          i_find(bool);
    240 static char         *i_gettext(Filepos, Filepos);
    241 static void          i_killundos(Undo **);
    242 static Line         *i_lineat(unsigned long);
    243 static unsigned long i_lineno(Line *);
    244 static void          i_mouse(void);
    245 static void          i_multiply(void (*func)(const Arg * arg), const Arg arg);
    246 static void          i_pipetext(const char *);
    247 static void          i_readfifo(void);
    248 static void          i_readfile(char *);
    249 static void          i_resize(void);
    250 static Filepos       i_scrtofpos(int, int);
    251 static bool          i_setfindterm(const char *);
    252 static void          i_setup(void);
    253 static void          i_sigwinch(int);
    254 static void          i_sigcont(int);
    255 static void          i_sortpos(Filepos *, Filepos *);
    256 static void          i_termwininit(void);
    257 static void          i_update(void);
    258 static void          i_usage(void);
    259 static bool          i_writefile(char *);
    260 
    261 /* t_* functions to know whether to process an action or keybinding */
    262 static bool t_ai(void);
    263 static bool t_bol(void);
    264 static bool t_eol(void);
    265 static bool t_ins(void);
    266 static bool t_mod(void);
    267 static bool t_rw(void);
    268 static bool t_redo(void);
    269 static bool t_sel(void);
    270 static bool t_sent(void);
    271 static bool t_undo(void);
    272 static bool t_vis(void);
    273 static bool t_warn(void);
    274 
    275 /* m_ functions represent a cursor movement and can be passed in an Arg */
    276 static Filepos m_bof(Filepos);
    277 static Filepos m_bol(Filepos);
    278 static Filepos m_smartbol(Filepos);
    279 static Filepos m_eof(Filepos);
    280 static Filepos m_eol(Filepos);
    281 static Filepos m_nextchar(Filepos);
    282 static Filepos m_prevchar(Filepos);
    283 static Filepos m_nextword(Filepos);
    284 static Filepos m_prevword(Filepos);
    285 static Filepos m_nextline(Filepos);
    286 static Filepos m_prevline(Filepos);
    287 static Filepos m_nextscr(Filepos);
    288 static Filepos m_prevscr(Filepos);
    289 static Filepos m_parameter(Filepos);
    290 static Filepos m_sentence(Filepos);
    291 static Filepos m_stay(Filepos);
    292 static Filepos m_tomark(Filepos);
    293 static Filepos m_tosel(Filepos);
    294 
    295 #include "config.h"
    296 
    297 /* Some extra stuff that depends on config.h */
    298 static regex_t *cmd_res[LEN(cmds)];
    299 static regex_t *syntax_file_res[LEN(syntaxes)];
    300 static regex_t *syntax_res[LEN(syntaxes)][SYN_COLORS];
    301 
    302 char *argv0;
    303 
    304 static void *
    305 ecalloc(size_t nmemb, size_t size) {
    306 	void *p;
    307 
    308 	p = calloc(nmemb, size);
    309 	if(p == NULL)
    310 		i_die("Can't calloc.\n");
    311 	return p;
    312 }
    313 
    314 static void *
    315 erealloc(void *ptr, size_t size) {
    316 	void *p;
    317 
    318 	p = realloc(ptr, size);
    319 	if(p == NULL) {
    320 		free(ptr);
    321 		i_die("Can't realloc.\n");
    322 	}
    323 	return p;
    324 }
    325 
    326 static char *
    327 estrdup(const char *s) {
    328 	char *p;
    329 
    330 	p = strdup(s);
    331 	if(p == NULL)
    332 		i_die("Can't strdup.\n");
    333 	return p;
    334 }
    335 
    336 /* F_* FUNCTIONS
    337  * Can be linked to an action or keybinding. Always return void and take
    338  * const Arg* */
    339 
    340 void
    341 f_adjective(const Arg * arg) {
    342 	statusflags &= ~S_Sentence;
    343 
    344 	if(arg->m)
    345 		varg.m = arg->m;
    346 	if(arg->i)
    347 		varg.i = arg->i;
    348 	if(arg->v)
    349 		varg.v = arg->v;
    350 
    351 	i_multiply(verb, varg);
    352 }
    353 
    354 /* Make cursor line the one in the middle of the screen if possible,
    355  * refresh screen */
    356 void
    357 f_center(const Arg * arg) {
    358 	(void) arg;
    359 
    360 	int i = LINESABS / 2;
    361 
    362 	scrline = fcur.l;
    363 	while((i -= VLINES(scrline)) > 0 && scrline->prev)
    364 		scrline = scrline->prev;
    365 	i_resize();
    366 }
    367 
    368 /* Delete text as per arg->m. Your responsibility: call only if t_rw() */
    369 void
    370 f_delete(const Arg * arg) {
    371 	char *s;
    372 	Filepos pos0 = fcur, pos1 = arg->m(fcur);
    373 
    374 #ifdef HOOK_DELETE_ALL
    375 	HOOK_DELETE_ALL;
    376 #endif
    377 	i_sortpos(&pos0, &pos1);
    378 	s = i_gettext(pos0, pos1);
    379 	i_addundo(FALSE, pos0, pos1, s);
    380 	free(s);
    381 	if(i_deltext(pos0, pos1))
    382 		fcur = pos0;
    383 	else
    384 		fcur = fsel = pos0;
    385 	if(fsel.o > fsel.l->len)
    386 		fsel.o = fsel.l->len;
    387 	statusflags |= S_Modified;
    388 	lastaction = LastDelete;
    389 }
    390 
    391 /* Extend the selection as per arg->i (see enums above) */
    392 void
    393 f_extsel(const Arg * arg) {
    394 	fmrk = fcur;
    395 	i_sortpos(&fsel, &fcur);
    396 	switch (arg->i) {
    397 	case ExtWord:
    398 		if(fsel.o > 0 && !ISWORDBRK(fsel.l->c[fsel.o - 1]))
    399 			fsel = m_prevword(fsel);
    400 		if(!ISWORDBRK(fcur.l->c[fcur.o]))
    401 			fcur = m_nextword(fcur);
    402 		break;
    403 	case ExtLines:
    404 		fsel.o = 0;
    405 		fcur.o = fcur.l->len;
    406 		break;
    407 	case ExtAll:
    408 		fsel.l = fstline;
    409 		fcur.l = lstline;
    410 		f_extsel(&(const Arg) { .i = ExtLines });
    411 		break;
    412 	case ExtDefault:
    413 	default:
    414 		if(fsel.o == 0 && fcur.o == fcur.l->len)
    415 			f_extsel(&(const Arg) { .i = ExtAll });
    416 		else if(t_sel() || ISWORDBRK(fcur.l->c[fcur.o]))
    417 			f_extsel(&(const Arg) { .i = ExtLines });
    418 		else
    419 			f_extsel(&(const Arg) { .i = ExtWord });
    420 	}
    421 }
    422 
    423 /* Find arg->v regex backwards, same as last if arg->v == NULL */
    424 void
    425 f_findbw(const Arg * arg) {
    426 	if(i_setfindterm((char *) arg->v))
    427 		i_find(FALSE);
    428 }
    429 
    430 /* Find arg->v regex forward, same as last if arg->v == NULL */
    431 void
    432 f_findfw(const Arg * arg) {
    433 	if(i_setfindterm((char *) arg->v))
    434 		i_find(TRUE);
    435 }
    436 
    437 /* Insert arg->v at cursor position. Your responsibility: call only if t_rw() */
    438 void
    439 f_insert(const Arg * arg) {
    440 	Filepos newcur;
    441 
    442 	newcur = i_addtext((char *) arg->v, fcur);
    443 
    444 	if((statusflags & S_GroupUndo) && undos && (undos->flags & UndoIns) &&
    445 	    fcur.o == undos->endo && undos->endl == i_lineno(fcur.l) &&
    446 	    ((char *) arg->v)[0] != '\n') {
    447 		i_addtoundo(newcur, arg->v);
    448 	} else {
    449 		i_addundo(TRUE, fcur, newcur, (char *) arg->v);
    450 		if(fcur.l != newcur.l)
    451 			fsel = newcur;
    452 	}
    453 	fcur = fsel = newcur;
    454 	statusflags |= (S_Modified | S_GroupUndo);
    455 	lastaction = LastInsert;
    456 }
    457 
    458 /* Go to atoi(arg->v) line */
    459 void
    460 f_line(const Arg * arg) {
    461 	long int l;
    462 
    463 	l = atoi(arg->v);
    464 	if(!l)
    465 		l = 1;
    466 	fcur.l = i_lineat(l);
    467 	if(fcur.o > fcur.l->len)
    468 		fcur.o = fcur.l->len;
    469 	FIXNEXT(fcur);
    470 }
    471 
    472 /* Set mark at current position */
    473 void
    474 f_mark(const Arg * arg) {
    475 	(void) arg;
    476 
    477 	fmrk = fcur;
    478 }
    479 
    480 /* Move cursor and extend/shrink selection as per arg->m */
    481 void
    482 f_move(const Arg * arg) {
    483 	fcur = arg->m(fcur);
    484 	if(!t_vis())
    485 		fsel = fcur;
    486 }
    487 
    488 /* Got to atoi(arg->v) position in the current line */
    489 void
    490 f_offset(const Arg * arg) {
    491 	fcur.o = atoi(arg->v);
    492 	if(fcur.o > fcur.l->len)
    493 		fcur.o = fcur.l->len;
    494 	FIXNEXT(fcur);
    495 }
    496 
    497 /* Pipe selection through arg->v external command. Your responsibility:
    498  * call only if t_rw() */
    499 void
    500 f_pipe(const Arg * arg) {
    501 	i_pipetext(arg->v);
    502 	statusflags |= S_Modified;
    503 	lastaction = LastPipe;
    504 }
    505 
    506 /* Pipe selection through arg->v external command but do not update text
    507  * on screen */
    508 void
    509 f_pipero(const Arg * arg) {
    510 	(void) arg;
    511 
    512 	long oldsf = statusflags;
    513 
    514 	statusflags |= S_Readonly;
    515 	i_pipetext(arg->v);
    516 	statusflags = oldsf;
    517 	lastaction = LastPipeRO;
    518 }
    519 
    520 /* Repeat the last action. Your responsibility: call only if t_rw() */
    521 void
    522 f_repeat(const Arg * arg) {
    523 	(void) arg;
    524 
    525 	i_sortpos(&fsel, &fcur);
    526 	switch (lastaction) {
    527 	case LastDelete:
    528 		if(t_sel())
    529 			f_delete(&(const Arg) { .m = m_tosel });
    530 		break;
    531 	case LastInsert:
    532 		if(undos && undos->flags & UndoIns)
    533 			f_insert(&(const Arg) { .v = undos->str });
    534 		break;
    535 	case LastPipe:
    536 		f_pipe(&(const Arg) { .v = getenv(envs[EnvPipe]) });
    537 		break;
    538 	case LastPipeRO:
    539 		f_pipero(&(const Arg) { .v = getenv(envs[EnvPipe]) });
    540 		break;
    541 	}
    542 }
    543 
    544 /* Save file with arg->v filename, same if NULL. Your responsibility: call
    545    only if t_mod() && t_rw() */
    546 void
    547 f_save(const Arg * arg) {
    548 	Undo *u;
    549 
    550 	if(arg && arg->v && *((char *) arg->v)) {
    551 		statusflags &= ~S_DumpStdout;
    552 		free(filename);
    553 		filename = estrdup((char *) arg->v);
    554 		setenv(envs[EnvFile], filename, 1);
    555 	} else if(filename == NULL && !(statusflags & S_DumpStdout)) {
    556 		unsetenv(envs[EnvFile]);
    557 #ifdef HOOK_SAVE_NO_FILE
    558 		HOOK_SAVE_NO_FILE;
    559 #endif
    560 		return;
    561 	}
    562 
    563 	if(i_writefile((statusflags & S_DumpStdout) ? NULL : filename)) {
    564 		statusflags &= ~S_Modified;
    565 		for(savestep = 0, u = undos; u; u = u->prev, savestep++)
    566 			;
    567 	}
    568 	if(statusflags & S_DumpStdout)
    569 		statusflags &= ~S_Running;
    570 }
    571 
    572 /* Move cursor as per arg->m, then move the selection point to previous cursor */
    573 void
    574 f_select(const Arg * arg) {
    575 	/* for f_select(m_tosel), which reverses the selection */
    576 	Filepos tmppos = fcur;
    577 
    578 	fcur = arg->m(fcur);
    579 	fsel = tmppos;
    580 }
    581 
    582 /* Spawn (char **)arg->v */
    583 void
    584 f_spawn(const Arg * arg) {
    585 	pid_t pid = -1;
    586 
    587 	reset_shell_mode();
    588 	if((pid = fork()) == 0) {
    589 		/* setsid() used to be called here, but it does not look as a good idea
    590 		   anymore. TODO: test and delete! */
    591 		execvp(((char **) arg->v)[0], (char **) arg->v);
    592 		fprintf(stderr, "sandy: execvp %s", ((char **) arg->v)[0]);
    593 		perror(" failed");
    594 		exit(EXIT_SUCCESS);
    595 	} else if(pid > 0) {
    596 		waitpid(pid, NULL, 0);
    597 	}
    598 	reset_prog_mode();
    599 	if(titlewin)
    600 		redrawwin(titlewin);
    601 	redrawwin(textwin); /* TODO: make this work! */
    602 }
    603 
    604 void
    605 f_suspend(const Arg * arg) {
    606 	(void) arg;
    607 
    608 	endwin();
    609 	signal(SIGCONT, i_sigcont);
    610 	raise(SIGSTOP);
    611 }
    612 
    613 /* Set syntax with name arg->v */
    614 void
    615 f_syntax(const Arg * arg) {
    616 	unsigned int i, j;
    617 
    618 	statusflags |= S_DirtyScr;
    619 	for(i = 0; i < LEN(syntaxes); i++)
    620 		if((arg && arg->v)
    621 			? !strcmp(arg->v, syntaxes[i].name)
    622 		    : !regexec(syntax_file_res[i], filename, 0, NULL, 0)) {
    623 			for(j = 0; j < SYN_COLORS; j++) {
    624 				if((syntx >= 0) && syntax_res[syntx][j])
    625 					regfree(syntax_res[syntx][j]);
    626 				if(syntaxes[i].re_text[j] &&
    627 				   regcomp(syntax_res[i][j],
    628 					syntaxes[i].re_text[j],
    629 					REG_EXTENDED | REG_NEWLINE))
    630 					i_die("Faulty regex.\n");
    631 			}
    632 			syntx = i;
    633 			setenv(envs[EnvSyntax], syntaxes[syntx].name, 1);
    634 			return;
    635 		}
    636 	for(i = 0; i < SYN_COLORS; i++) {
    637 		if((syntx >= 0) && syntax_res[syntx][i])
    638 			regfree(syntax_res[syntx][i]);
    639 	}
    640 	syntx = -1;
    641 	setenv(envs[EnvSyntax], "none", 1);
    642 }
    643 
    644 /* Toggle the arg->i statusflag. Careful with this one! */
    645 void
    646 f_toggle(const Arg * arg) {
    647 	statusflags ^= (long) arg->i;
    648 
    649 	/* Specific operations for some toggles */
    650 	switch (arg->i) {
    651 	case S_CaseIns: /* Re-compile regex with/without REG_ICASE */
    652 		i_setfindterm(getenv(envs[EnvFind]));
    653 		break;
    654 	case S_Warned: /* Set warning title */
    655 		tmptitle = "WARNING! File Modified!!!";
    656 		break;
    657 	}
    658 }
    659 
    660 /* Undo last action if arg->i >=0, redo otherwise. Your responsibility:
    661  * call only if t_undo() / t_redo() */
    662 void
    663 f_undo(const Arg * arg) {
    664 	Filepos start, end;
    665 	const bool isredo = (arg->i < 0);
    666 	Undo *u;
    667 	int n;
    668 
    669 	u = (isredo ? redos : undos);
    670 	fsel.o = u->starto, fsel.l = i_lineat(u->startl);
    671 	fcur = fsel;
    672 
    673 	while(u) {
    674 		start.o = u->starto, start.l = i_lineat(u->startl);
    675 		end.o = u->endo, end.l = i_lineat(u->endl);
    676 
    677 		if(isredo ^ (u->flags & UndoIns)) {
    678 			i_sortpos(&start, &end);
    679 			i_deltext(start, end);
    680 			fcur = fsel = start;
    681 		} else {
    682 			fcur = fsel = i_addtext(u->str, fcur);
    683 		}
    684 		if(isredo)
    685 			redos = u->prev, u->prev = undos, undos = u;
    686 		else
    687 			undos = u->prev, u->prev = redos, redos = u;
    688 
    689 		if(!(u->flags & (isredo ? RedoMore : UndoMore)))
    690 			break;
    691 		u = (isredo ? redos : undos);
    692 	}
    693 
    694 	for(n = 0, u = undos; u; u = u->prev, n++)
    695 		;
    696 	/* True if we saved at this undo point */
    697 	if(n == savestep)
    698 		statusflags ^= S_Modified;
    699 	else
    700 		statusflags |= S_Modified;
    701 }
    702 
    703 /* I_* FUNCTIONS
    704    Called internally from the program code */
    705 
    706 /* Add information to the last undo in the ring */
    707 void
    708 i_addtoundo(Filepos newend, const char *s) {
    709 	size_t oldsiz, newsiz;
    710 
    711 	if(!undos)
    712 		return;
    713 	oldsiz = strlen(undos->str);
    714 	newsiz = strlen(s);
    715 	undos->endl = i_lineno(newend.l);
    716 	undos->endo = newend.o;
    717 
    718 	undos->str = (char *) erealloc(undos->str, 1 + oldsiz + newsiz);
    719 	strncat(undos->str, s, newsiz);
    720 }
    721 
    722 /* Add new undo information to the undo ring */
    723 void
    724 i_addundo(bool ins, Filepos start, Filepos end, char *s) {
    725 	Undo *u;
    726 
    727 	if(!s || !*s)
    728 		return;
    729 	if(undos && (statusflags & S_GroupUndo)) {
    730 		end.l = i_lineat((undos->endl - undos->startl) + i_lineno(end.l));
    731 		i_addtoundo(end, s);
    732 		return;
    733 	}
    734 
    735 	/* Once you make a change, the old redos go away */
    736 	if(redos)
    737 		i_killundos(&redos);
    738 	u = (Undo *) ecalloc(1, sizeof(Undo));
    739 
    740 	u->flags = (ins ? UndoIns : 0);
    741 	u->startl = i_lineno(start.l);
    742 	u->endl = i_lineno(end.l);
    743 	u->starto = start.o;
    744 	u->endo = end.o;
    745 	u->str = estrdup(s);
    746 	u->prev = undos;
    747 	undos = u;
    748 }
    749 
    750 /* Add text at pos, return the position after the inserted text */
    751 Filepos
    752 i_addtext(char *buf, Filepos pos) {
    753 	Line *l = pos.l, *lnew = NULL;
    754 	size_t o = pos.o, vlines, i = 0, il = 0;
    755 	Filepos f;
    756 	char c;
    757 
    758 	vlines = VLINES(l);
    759 	for(c = buf[0]; c != '\0'; c = buf[++i]) {
    760 		/* newline / line feed */
    761 		if(c == '\n' || c == '\r') {
    762 			lnew = (Line *)ecalloc(1, sizeof(Line));
    763 			lnew->c = ecalloc(1, LINSIZ);
    764 			lnew->dirty = l->dirty = TRUE;
    765 			lnew->len = lnew->vlen = 0;
    766 			lnew->mul = 1;
    767 			lnew->next = l->next;
    768 			lnew->prev = l;
    769 			if(l->next)
    770 				l->next->prev = lnew;
    771 			else
    772 				lstline = lnew;
    773 			l->next = lnew;
    774 			l = lnew;
    775 			/* \n in the middle of a line */
    776 			if(o + il < l->prev->len) {
    777 				f.l = l;
    778 				f.o = 0;
    779 				i_addtext(&(l->prev->c[o + il]), f);
    780 				l->prev->len = o + il;
    781 				l->prev->c[o + il] = '\0';
    782 			}
    783 			i_calcvlen(l->prev);
    784 			o = il = 0;
    785 		} else {
    786 			/* Regular char */
    787 			if(2 + (l->len) >= LINSIZ * (l->mul))
    788 				l->c = (char *) erealloc(l->c, LINSIZ * (++(l->mul)));
    789 			memmove(l->c + il + o + 1, l->c + il + o,
    790 			    (1 + l->len - (il + o)));
    791 			l->c[il + o] = c;
    792 			l->dirty = TRUE;
    793 			if(il + o >= (l->len)++)
    794 				l->c[il + o + 1] = '\0';
    795 			il++;
    796 		}
    797 	}
    798 	i_calcvlen(l);
    799 	f.l = l;
    800 	f.o = il + o;
    801 	if(lnew != NULL || vlines != VLINES(pos.l))
    802 		statusflags |= S_DirtyDown;
    803 	return f;
    804 }
    805 
    806 /* Take a file position and advance it o bytes */
    807 void
    808 i_advpos(Filepos * pos, int o) {
    809 	int toeol;
    810 
    811 	toeol = pos->l->len - pos->o;
    812 	if(o <= toeol)
    813 		o += pos->o;
    814 	else
    815 		while(o > toeol && pos->l->next) {
    816 			pos->l = pos->l->next;
    817 			o -= (1 + toeol);
    818 			toeol = pos->l->len;
    819 		}
    820 	pos->o = o;
    821 	FIXNEXT((*pos)); /* TODO: this should not be needed here */
    822 }
    823 
    824 /* Update the vlen value of a Line */
    825 void
    826 i_calcvlen(Line * l) {
    827 	size_t i;
    828 
    829 	l->vlen = 0;
    830 	for(i = 0; i < l->len; i++)
    831 		l->vlen += VLEN(l->c[i], l->vlen);
    832 }
    833 
    834 /* Cleanup and exit */
    835 void
    836 i_cleanup(int sig) {
    837 	unsigned int i;
    838 
    839 	i_killundos(&undos);
    840 	i_killundos(&redos);
    841 	close(fifofd);
    842 	unlink(fifopath);
    843 	free(filename);
    844 	for(i = 0; i < LEN(cmds); i++)
    845 		regfree(cmd_res[i]);
    846 	for(i = 0; i < LEN(syntaxes); i++)
    847 		regfree(syntax_file_res[i]);
    848 	if(syntx >= 0) {
    849 		for(i = 0; i < SYN_COLORS; i++)
    850 			regfree(syntax_res[syntx][i]);
    851 	}
    852 	regfree(find_res[0]);
    853 	regfree(find_res[1]);
    854 	endwin();
    855 	exit(sig > 0 ? 128 + sig : t_mod()? EXIT_FAILURE : EXIT_SUCCESS);
    856 }
    857 
    858 /* Quit less gracefully */
    859 void
    860 i_die(const char *str) {
    861 	reset_shell_mode();
    862 	fputs(str, stderr);
    863 	exit(EXIT_FAILURE);
    864 }
    865 
    866 /* The lines between l0 and l1 should be redrawn to the screen */
    867 void
    868 i_dirtyrange(Line * l0, Line * l1) {
    869 	Line *l;
    870 	bool d = FALSE;
    871 
    872 	/* Warning: l0 and/or l1 may not even exist!!! */
    873 	for(l = fstline; l; l = l->next) {
    874 		if(d && (l == l0 || l == l1)) {
    875 			l->dirty = TRUE;
    876 			break;
    877 		}
    878 		if(l == l0 || l == l1)
    879 			d ^= 1;
    880 		if(d)
    881 			l->dirty = TRUE;
    882 	}
    883 }
    884 
    885 /* Delete text between pos0 and pos1, which MUST be in order, fcur integrity
    886    is NOT assured after deletion, fsel integrity is returned as a bool */
    887 bool
    888 i_deltext(Filepos pos0, Filepos pos1) {
    889 	Line *ldel = NULL;
    890 	size_t vlines = 1;
    891 	bool integrity = TRUE;
    892 
    893 	if(pos0.l == fsel.l)
    894 		integrity = (fsel.o <= pos0.o || (pos0.l == pos1.l
    895 			&& fsel.o > pos1.o));
    896 	if(pos0.l == pos1.l) {
    897 		vlines = VLINES(pos0.l);
    898 		memmove(pos0.l->c + pos0.o, pos0.l->c + pos1.o,
    899 		    (pos0.l->len - pos1.o));
    900 		pos0.l->dirty = TRUE;
    901 		pos0.l->len -= (pos1.o - pos0.o);
    902 		pos0.l->c[pos0.l->len] = '\0';
    903 		i_calcvlen(pos0.l);
    904 	} else {
    905 		pos0.l->len = pos0.o;
    906 		pos0.l->c[pos0.l->len] = '\0';
    907 		pos0.l->dirty = TRUE; /* <<-- glitch in screen updates! */
    908 		/* i_calcvlen is unneeded here, because we call i_addtext later */
    909 		while(pos1.l != ldel) {
    910 			if(pos1.l == pos0.l->next)
    911 				i_addtext(&(pos0.l->next->c[pos1.o]), pos0);
    912 			if(pos0.l->next->next)
    913 				pos0.l->next->next->prev = pos0.l;
    914 			ldel = pos0.l->next;
    915 			pos0.l->next = pos0.l->next->next;
    916 			if(scrline == ldel)
    917 				scrline = ldel->prev;
    918 			if(lstline == ldel)
    919 				lstline = ldel->prev;
    920 			if(fsel.l == ldel)
    921 				integrity = FALSE;
    922 			free(ldel->c);
    923 			free(ldel);
    924 		}
    925 	}
    926 	if(ldel != NULL || vlines != VLINES(pos0.l))
    927 		statusflags |= S_DirtyDown;
    928 	return integrity;
    929 }
    930 
    931 /* test an array of t_ functions */
    932 bool
    933 i_dotests(bool(*const a[])(void)) {
    934 	int i;
    935 
    936 	for(i = 0; a[i]; i++) {
    937 		if(!a[i]())
    938 			return FALSE;
    939 	}
    940 	return TRUE;
    941 }
    942 
    943 void
    944 i_dokeys(const Key bindings[], unsigned int length_bindings) {
    945 	unsigned int index, i, j;
    946 
    947 	for(index = 0; index < length_bindings; index++) {
    948 		if(((bindings[index].keyv.c &&
    949 		     memcmp(c, bindings[index].keyv.c,
    950 		            sizeof bindings[index].keyv.c) == 0) ||
    951 		    (bindings[index].keyv.i && ch == bindings[index].keyv.i)) &&
    952 		     i_dotests(bindings[index].test)) {
    953 
    954 			if(bindings[index].func != f_insert)
    955 				statusflags &= ~(S_GroupUndo);
    956 
    957 			/* Handle sentences */
    958 			if(t_sent()) {
    959 				if(bindings[index].func == verb) {
    960 					varg.m = m_nextline;
    961 					i_multiply(verb, varg);
    962 					statusflags &= ~S_Sentence;
    963 				} else if(bindings[index].func != f_adjective) {
    964 					statusflags &= ~S_Sentence;
    965 					break;
    966 				}
    967 			} else if(bindings[index].arg.m == m_sentence) {
    968 				statusflags |= (long) S_Sentence;
    969 				verb = bindings[index].func;
    970 				varg = bindings[index].arg;
    971 				vi = index;
    972 				break;
    973 			}
    974 
    975 			/* Handle parameter sentences (verb is used here to define the
    976 			   command to execute) */
    977 			if(statusflags & S_Parameter) {
    978 				statusflags &= ~S_Parameter;
    979 				i_multiply(verb, (const Arg) { .v = c });
    980 				break;
    981 			} else if(bindings[index].arg.m == m_parameter) {
    982 				statusflags |= (long) S_Parameter;
    983 				verb = bindings[index].func;
    984 				break;
    985 			}
    986 
    987 			i_multiply(bindings[index].func, bindings[index].arg);
    988 			if(t_sent() && bindings[index].func == f_adjective)
    989 				i = vi;
    990 			else
    991 				i = index;
    992 
    993 			/* Handle multi-function commands */
    994 			if(i + 1 < length_bindings) {
    995 				if((bindings[i + 1].keyv.c &&
    996 				    memcmp(bindings[i + 1].keyv.c, bindings[i].keyv.c,
    997 					       sizeof bindings[i].keyv.c) == 0) ||
    998 				    (bindings[i + 1].keyv.i	&&
    999 					bindings[i + 1].keyv.i == bindings[index].keyv.i)) {
   1000 
   1001 					for(j = 0; j < LEN(bindings[i].test); j++) {
   1002 						if(bindings[i].test[j] != bindings[i + 1].test[j])
   1003 							break;
   1004 					}
   1005 					if(j == LEN(bindings[i].test))
   1006 						continue;
   1007 				}
   1008 			}
   1009 			break;
   1010 		}
   1011 	}
   1012 }
   1013 
   1014 /* Main editing loop */
   1015 void
   1016 i_edit(void) {
   1017 	int i, tch;
   1018 	fd_set fds;
   1019 	Filepos oldsel, oldcur;
   1020 
   1021 	oldsel.l = oldcur.l = fstline;
   1022 	oldsel.o = oldcur.o = 0;
   1023 
   1024 	while(statusflags & S_Running) {
   1025 		if(fsel.l != oldsel.l)
   1026 			i_dirtyrange(oldsel.l, fsel.l);
   1027 		else if(fsel.o != oldsel.o)
   1028 			fsel.l->dirty = TRUE;
   1029 		if(fcur.l != oldcur.l)
   1030 			i_dirtyrange(oldcur.l, fcur.l);
   1031 		else if(fcur.o != oldcur.o)
   1032 			fcur.l->dirty = TRUE;
   1033 		oldsel = fsel, oldcur = fcur;
   1034 		i_update();
   1035 
   1036 #ifdef HOOK_SELECT_ALL
   1037 		if(fsel.l != fcur.l || fsel.o != fcur.o)
   1038 			HOOK_SELECT_ALL;
   1039 #endif
   1040 		FD_ZERO(&fds);
   1041 		FD_SET(0, &fds);
   1042 		FD_SET(fifofd, &fds);
   1043 		signal(SIGWINCH, i_sigwinch);
   1044 		if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) == -1 &&
   1045 		   errno == EINTR) {
   1046 			signal(SIGWINCH, SIG_IGN);
   1047 			continue;
   1048 		}
   1049 		signal(SIGWINCH, SIG_IGN);
   1050 		if(FD_ISSET(fifofd, &fds))
   1051 			i_readfifo();
   1052 		if(!FD_ISSET(0, &fds))
   1053 			continue;
   1054 		if((ch = wgetch(textwin)) == ERR) {
   1055 			tmptitle = "ERR";
   1056 			continue;
   1057 		}
   1058 
   1059 		/* NCurses special chars are processed first to avoid UTF-8 collision */
   1060 		if(ch >= KEY_MIN) {
   1061 			/* These are not really chars */
   1062 #if HANDLE_MOUSE
   1063 			if(ch == KEY_MOUSE)
   1064 				i_mouse();
   1065 			else
   1066 #endif /* HANDLE_MOUSE */
   1067 				i_dokeys(curskeys, LEN(curskeys));
   1068 			continue;
   1069 		}
   1070 
   1071 		/* Mundane characters are processed later */
   1072 		c[0] = (char) ch;
   1073 		if(c[0] == 0x1B || (isutf8 && !ISASCII(c[0]))) {
   1074 			/* Multi-byte char or escape sequence */
   1075 			wtimeout(textwin, 1);
   1076 			for(i = 1; i < (c[0] == 0x1B ? 6 : UTF8LEN(c[0])); i++) {
   1077 				tch = wgetch(textwin);
   1078 				c[i] = (char)tch;
   1079 				if(tch == ERR)
   1080 					break;
   1081 			}
   1082 			for(; i < 7; i++)
   1083 				c[i] = '\0';
   1084 			wtimeout(textwin, 0);
   1085 		} else {
   1086 			c[1] = c[2] = c[3] = c[4] = c[5] = c[6] = '\0';
   1087 		}
   1088 
   1089 		if(!(statusflags & S_InsEsc) && ISCTRL(c[0])) {
   1090 			i_dokeys(stdkeys, LEN(stdkeys));
   1091 			continue;
   1092 		}
   1093 		statusflags &= ~(S_InsEsc);
   1094 
   1095 		if(t_rw() && t_ins()) {
   1096 			f_insert(&(const Arg) { .v = c });
   1097 		}
   1098 #if VIM_BINDINGS
   1099 		else if(!t_ins()) {
   1100 			if(ch >= '0' && ch <= '9' && !(statusflags & S_Parameter)) {
   1101 				if(statusflags & S_Multiply) {
   1102 					multiply *= 10;
   1103 					multiply += (int) ch - '0';
   1104 				} else {
   1105 					statusflags |= S_Multiply;
   1106 					multiply = (int) ch - '0';
   1107 				}
   1108 			} else {
   1109 				i_dokeys(commkeys, LEN(commkeys));
   1110 			}
   1111 		}
   1112 #endif /* VIM_BINDINGS */
   1113 		else {
   1114 			tmptitle = "WARNING! File is read-only!!!";
   1115 		}
   1116 	}
   1117 }
   1118 
   1119 /* Find text as per the current find_res[sel_re] */
   1120 void
   1121 i_find(bool fw) {
   1122 	char *s, *subs;
   1123 	int wp, _so, _eo, status, flags;
   1124 	Filepos start, end;
   1125 	regmatch_t result[1];
   1126 
   1127 	start.l = fstline;
   1128 	start.o = 0;
   1129 	end.l = lstline;
   1130 	end.o = lstline->len;
   1131 	i_sortpos(&fsel, &fcur);
   1132 
   1133 	for(wp = 0; wp < 2; wp++) {
   1134 		if(wp > 0)
   1135 			s = i_gettext(start, end);
   1136 		else if(fw)
   1137 			s = i_gettext(fcur, end);
   1138 		else
   1139 			s = i_gettext(start, fsel);
   1140 
   1141 		flags = 0;
   1142 		if(fw && (fcur.o != 0))
   1143 			flags = REG_NOTBOL;
   1144 		else if(!fw && (fsel.o != fsel.l->len))
   1145 			flags = REG_NOTEOL;
   1146 
   1147 		if((status = regexec(find_res[sel_re], s, 1, result, flags)) == 0) {
   1148 			f_mark(NULL);
   1149 			if(wp > 0 || !fw)
   1150 				fcur = start;
   1151 			fsel = fcur;
   1152 			_so = result[0].rm_so;
   1153 			_eo = result[0].rm_eo;
   1154 			if(!fw) {
   1155 				subs = &s[_eo];
   1156 				while(!regexec(find_res[sel_re], subs, 1,
   1157 					result, REG_NOTBOL)
   1158 				    && result[0].rm_eo) {
   1159 					/* This is blatantly over-simplified: do not try to match
   1160 					 * an empty string backwards as it will match the first hit
   1161 					 * on the file. */
   1162 					_so = _eo + result[0].rm_so;
   1163 					_eo += result[0].rm_eo;
   1164 					subs = &s[_eo];
   1165 				}
   1166 			}
   1167 			i_advpos(&fsel, _so);
   1168 			i_advpos(&fcur, _eo);
   1169 			wp++;
   1170 		}
   1171 		free(s);
   1172 	}
   1173 }
   1174 
   1175 /* Return text between pos0 and pos1, which MUST be in order; you MUST free
   1176    the returned string after use */
   1177 char *
   1178 i_gettext(Filepos pos0, Filepos pos1) {
   1179 	Line *l;
   1180 	unsigned long long i = 1;
   1181 	char *buf;
   1182 
   1183 	for(l = pos0.l; l != pos1.l->next; l = l->next)
   1184 		i += 1 + (l == pos1.l ? pos1.o : l->len) - (l == pos0.l ? pos0.o : 0);
   1185 	buf = ecalloc(1, i);
   1186 	for(l = pos0.l, i = 0; l != pos1.l->next; l = l->next) {
   1187 		memcpy(buf + i, l->c + (l == pos0.l ? pos0.o : 0),
   1188 		    (l == pos1.l ? pos1.o : l->len) - (l == pos0.l ? pos0.o : 0));
   1189 		i += (l == pos1.l ? pos1.o : l->len) - (l == pos0.l ? pos0.o : 0);
   1190 		if(l != pos1.l)
   1191 			buf[i++] = '\n';
   1192 	}
   1193 	return buf;
   1194 }
   1195 
   1196 /* Kill the content of the &list undo/redo ring */
   1197 void
   1198 i_killundos(Undo ** list) {
   1199 	Undo *u;
   1200 
   1201 	for(; *list;) {
   1202 		u = (*list)->prev;
   1203 		free((*list)->str);
   1204 		free(*list);
   1205 		*list = u;
   1206 	}
   1207 }
   1208 
   1209 /* Return the Line numbered il */
   1210 Line *
   1211 i_lineat(unsigned long il) {
   1212 	unsigned long i;
   1213 	Line *l;
   1214 
   1215 	for(i = 1, l = fstline; i != il && l && l->next; i++)
   1216 		l = l->next;
   1217 	return l;
   1218 }
   1219 
   1220 /* Return the line number for l0 */
   1221 unsigned long
   1222 i_lineno(Line * l0) {
   1223 	unsigned long i;
   1224 	Line *l;
   1225 	for(i = 1, l = fstline; l != l0; l = l->next)
   1226 		i++;
   1227 	return i;
   1228 }
   1229 
   1230 #if HANDLE_MOUSE
   1231 /* Process mouse input */
   1232 void
   1233 i_mouse(void) {
   1234 	unsigned int i;
   1235 	MEVENT ev;
   1236 	Filepos f;
   1237 
   1238 	if(getmouse(&ev) == ERR)
   1239 		return;
   1240 	if(!wmouse_trafo(textwin, &ev.y, &ev.x, FALSE))
   1241 		return;
   1242 
   1243 	for(i = 0; i < LEN(clks); i++)
   1244 		/* Warning! cursor placement code takes place BEFORE tests are taken
   1245 		 * into account */
   1246 		if(ev.bstate & clks[i].mask) {
   1247 			/* While this allows to extend the selection, it may cause some confusion */
   1248 			f = i_scrtofpos(ev.x, ev.y);
   1249 			if(clks[i].place[0])
   1250 				fcur = f;
   1251 			if(clks[i].place[1])
   1252 				fsel = f;
   1253 			if(i_dotests(clks[i].test)) {
   1254 				if(clks[i].func)
   1255 					clks[i].func(&(clks[i].arg));
   1256 				break;
   1257 			}
   1258 		}
   1259 }
   1260 #endif /* HANDLE_MOUSE */
   1261 
   1262 /* Handle multiplication */
   1263 void
   1264 i_multiply(void (*func)(const Arg * arg), const Arg arg) {
   1265 	int i;
   1266 
   1267 	if(statusflags & S_Multiply) {
   1268 		func(&arg);
   1269 		statusflags |= S_GroupUndo;
   1270 
   1271 		for(i = 1; i < multiply; i++)
   1272 			func(&arg);
   1273 
   1274 		statusflags &= ~S_GroupUndo;
   1275 		statusflags &= ~S_Multiply;
   1276 		multiply = 1;
   1277 	} else
   1278 		func(&arg);
   1279 }
   1280 
   1281 /* Pipe text between fsel and fcur through cmd */
   1282 void
   1283 i_pipetext(const char *cmd) {
   1284 	struct timeval tv;
   1285 	int pin[2], pout[2], perr[2], nr = 1, nerr = 1, nw, written;
   1286 	int iw = 0, closed = 0, exstatus;
   1287 	char *buf = NULL, *ebuf = NULL, *s = NULL;
   1288 	Filepos auxp;
   1289 	fd_set fdI, fdO;
   1290 	pid_t pid = -1;
   1291 
   1292 	if(!cmd || cmd[0] == '\0')
   1293 		return;
   1294 	setenv(envs[EnvPipe], cmd, 1);
   1295 	if(pipe(pin) == -1)
   1296 		return;
   1297 	if(pipe(pout) == -1) {
   1298 		close(pin[0]);
   1299 		close(pin[1]);
   1300 		return;
   1301 	}
   1302 	if(pipe(perr) == -1) {
   1303 		close(pin[0]);
   1304 		close(pin[1]);
   1305 		close(pout[0]);
   1306 		close(pout[1]);
   1307 		return;
   1308 	}
   1309 
   1310 	i_sortpos(&fsel, &fcur);
   1311 
   1312 	/* Things I will undo or free at the end of this function */
   1313 	s = i_gettext(fsel, fcur);
   1314 
   1315 	if((pid = fork()) == 0) {
   1316 		dup2(pin[0], 0);
   1317 		dup2(pout[1], 1);
   1318 		dup2(perr[1], 2);
   1319 		close(pin[0]);
   1320 		close(pin[1]);
   1321 		close(pout[0]);
   1322 		close(pout[1]);
   1323 		close(perr[0]);
   1324 		close(perr[1]);
   1325 		/* I actually like it with sh so I can input pipes et al. */
   1326 		execl("/bin/sh", "sh", "-c", cmd, NULL);
   1327 		fprintf(stderr, "sandy: execl sh -c %s", cmd);
   1328 		perror(" failed");
   1329 		exit(EXIT_SUCCESS);
   1330 	}
   1331 
   1332 	if(pid > 0) {
   1333 		close(pin[0]);
   1334 		close(pout[1]);
   1335 		close(perr[1]);
   1336 		if(t_rw()) {
   1337 			i_addundo(FALSE, fsel, fcur, s);
   1338 			if(undos)
   1339 				undos->flags ^= RedoMore;
   1340 			i_deltext(fsel, fcur);
   1341 			fcur = fsel;
   1342 		}
   1343 		fcntl(pin[1], F_SETFL, O_NONBLOCK);
   1344 		fcntl(pout[0], F_SETFL, O_NONBLOCK);
   1345 		fcntl(perr[0], F_SETFL, O_NONBLOCK);
   1346 		buf = ecalloc(1, BUFSIZ + 1);
   1347 		ebuf = ecalloc(1, BUFSIZ + 1);
   1348 		FD_ZERO(&fdO);
   1349 		FD_SET(pin[1], &fdO);
   1350 		FD_ZERO(&fdI);
   1351 		FD_SET(pout[0], &fdI);
   1352 		FD_SET(perr[0], &fdI);
   1353 		tv.tv_sec = 5;
   1354 		tv.tv_usec = 0;
   1355 		nw = strlen(s);
   1356 		while(select(FD_SETSIZE, &fdI, &fdO, NULL, &tv) > 0 &&
   1357 		     (nw > 0 || nr > 0)) {
   1358 			fflush(NULL);
   1359 			if(FD_ISSET(pout[0], &fdI) && nr > 0) {
   1360 				nr = read(pout[0], buf, BUFSIZ);
   1361 				if(nr >= 0)
   1362 					buf[nr] = '\0';
   1363 				else
   1364 					break; /* ...not seen it yet */
   1365 				if(nr && t_rw()) {
   1366 					auxp = i_addtext(buf, fcur);
   1367 					i_addundo(TRUE, fcur, auxp, buf);
   1368 					if(undos)
   1369 						undos->flags ^= RedoMore | UndoMore;
   1370 					fcur = auxp;
   1371 				}
   1372 			} else if(nr > 0) {
   1373 				FD_SET(pout[0], &fdI);
   1374 			} else {
   1375 				FD_CLR(pout[0], &fdI);
   1376 			}
   1377 			if(FD_ISSET(perr[0], &fdI) && nerr > 0) {
   1378 				/* Blatant TODO: take last line of stderr and copy as tmptitle */
   1379 				ebuf[0] = '\0';
   1380 				nerr = read(perr[0], ebuf, BUFSIZ);
   1381 				if(nerr == -1)
   1382 					tmptitle = "WARNING! command reported an error!!!";
   1383 				if(nerr < 0)
   1384 					break;
   1385 			} else if(nerr > 0) {
   1386 				FD_SET(perr[0], &fdI);
   1387 			} else {
   1388 				FD_CLR(perr[0], &fdI);
   1389 			}
   1390 			if(FD_ISSET(pin[1], &fdO) && nw > 0) {
   1391 				written = write(pin[1], &(s[iw]),
   1392 				    (nw < BUFSIZ ? nw : BUFSIZ));
   1393 				if(written < 0)
   1394 					break; /* broken pipe? */
   1395 				iw += (nw < BUFSIZ ? nw : BUFSIZ);
   1396 				nw -= written;
   1397 			} else if(nw > 0) {
   1398 				FD_SET(pin[1], &fdO);
   1399 			} else {
   1400 				if(!closed++)
   1401 					close(pin[1]);
   1402 				FD_ZERO(&fdO);
   1403 			}
   1404 		}
   1405 		if(t_rw()) {
   1406 			if(undos)
   1407 				undos->flags ^= RedoMore;
   1408 		}
   1409 		free(buf);
   1410 		free(ebuf);
   1411 		if(!closed)
   1412 			close(pin[1]);
   1413 		waitpid(pid, &exstatus, 0); /* We don't want to close the pipe too soon */
   1414 		close(pout[0]);
   1415 		close(perr[0]);
   1416 	} else {
   1417 		close(pin[0]);
   1418 		close(pin[1]);
   1419 		close(pout[0]);
   1420 		close(pout[1]);
   1421 		close(perr[0]);
   1422 		close(perr[1]);
   1423 	}
   1424 	free(s);
   1425 }
   1426 
   1427 /* Read the command fifo */
   1428 void
   1429 i_readfifo(void) {
   1430 	char *buf, *tofree;
   1431 	regmatch_t result[2];
   1432 	unsigned int i;
   1433 	int r;
   1434 
   1435 	tofree = ecalloc(1, BUFSIZ + 1);
   1436 	buf = tofree;
   1437 	r = read(fifofd, buf, BUFSIZ);
   1438 	if(r != -1) {
   1439 		buf[r] = '\0';
   1440 		buf = strtok(buf, "\n");
   1441 		while(buf != NULL) {
   1442 			for(i = 0; i < LEN(cmds); i++) {
   1443 				if(!regexec(cmd_res[i], buf, 2, result, 0) &&
   1444 				   i_dotests(cmds[i].test)) {
   1445 					*(buf + result[1].rm_eo) = '\0';
   1446 					if(cmds[i].arg.i > 0)
   1447 						cmds[i].func(&(cmds[i].arg));
   1448 					else
   1449 						cmds[i].func(&(const Arg) { .v = (buf + result[1].rm_so) });
   1450 					break;
   1451 				}
   1452 			}
   1453 			buf = strtok(NULL, "\n");
   1454 		}
   1455 	}
   1456 	free(tofree);
   1457 
   1458 	/* Kludge: we close and reopen to circumvent a bug?
   1459 	 * if we don't do this, fifofd seems to be always ready to select()
   1460 	 * also, I was unable to mark fifofd as blocking after opening */
   1461 	close(fifofd);
   1462 	if((fifofd = open(fifopath, O_RDONLY | O_NONBLOCK)) == -1)
   1463 		i_die("Can't open FIFO for reading.\n");
   1464 }
   1465 
   1466 /* Read file content into the Line* structure */
   1467 void
   1468 i_readfile(char *fname) {
   1469 	int fd;
   1470 	ssize_t n;
   1471 	char *buf = NULL, *linestr = "1";
   1472 
   1473 	if(fname == NULL || !strcmp(fname, "-")) {
   1474 		fd = 0;
   1475 		reset_shell_mode();
   1476 	} else {
   1477 		free(filename);
   1478 		filename = estrdup(fname);
   1479 		setenv(envs[EnvFile], filename, 1);
   1480 		if((fd = open(filename, O_RDONLY)) == -1) {
   1481 			/* start at line number if specified, NOTE that filenames
   1482 			 * containing ':' are possible. */
   1483 			if((linestr = strrchr(filename, ':'))) {
   1484 				*(linestr++) = '\0';
   1485 				setenv(envs[EnvFile], filename, 1);
   1486 				fd = open(filename, O_RDONLY);
   1487 			} else {
   1488 				linestr = "1";
   1489 			}
   1490 			if(fd == -1) {
   1491 				tmptitle = "WARNING! Can't read file!!!";
   1492 				return;
   1493 			}
   1494 		}
   1495 		f_syntax(NULL); /* Try to guess a syntax */
   1496 	}
   1497 
   1498 	buf = ecalloc(1, BUFSIZ + 1);
   1499 	while((n = read(fd, buf, BUFSIZ)) > 0) {
   1500 		buf[n] = '\0';
   1501 		fcur = i_addtext(buf, fcur);
   1502 	}
   1503 	if(fd > 0) {
   1504 		close(fd);
   1505 	} else {
   1506 		if((fd = open("/dev/tty", O_RDONLY)) == -1)
   1507 			i_die("Can't reopen stdin.\n");
   1508 		dup2(fd, 0);
   1509 		close(fd);
   1510 		reset_prog_mode();
   1511 	}
   1512 	free(buf);
   1513 	fcur.l = fstline;
   1514 	fcur.o = 0;
   1515 	fsel = fcur;
   1516 	f_line(&(const Arg) { .v = linestr });
   1517 }
   1518 
   1519 /* Handle term resize, ugly. TODO: clean and change */
   1520 void
   1521 i_resize(void) {
   1522 	const char *tty;
   1523 	int fd, result;
   1524 	struct winsize ws;
   1525 
   1526 	if((tty = ttyname(0)) == NULL)
   1527 		return;
   1528 	fd = open(tty, O_RDWR);
   1529 	if(fd == -1)
   1530 		return;
   1531 	result = ioctl(fd, TIOCGWINSZ, &ws);
   1532 	close(fd);
   1533 	if(result == -1)
   1534 		return;
   1535 	cols = ws.ws_col;
   1536 	lines = ws.ws_row;
   1537 	endwin();
   1538 	doupdate();
   1539 	i_termwininit();
   1540 	statusflags |= S_DirtyScr;
   1541 }
   1542 
   1543 #if HANDLE_MOUSE
   1544 /* Return file position at screen coordinates x and y */
   1545 Filepos
   1546 i_scrtofpos(int x, int y) {
   1547 	Filepos pos;
   1548 	Line *l;
   1549 	int irow, ixrow, ivchar, vlines = 1;
   1550 
   1551 	pos.l = lstline;
   1552 	pos.o = pos.l->len;
   1553 	for(l = scrline, irow = 0; l && irow < LINESABS; l = l->next, irow += vlines) {
   1554 		vlines = VLINES(l);
   1555 		for(ixrow = ivchar = 0; ixrow < vlines && (irow + ixrow) < LINESABS; ixrow++) {
   1556 			if(irow + ixrow == y) {
   1557 				pos.l = l;
   1558 				pos.o = 0;
   1559 				while(x > (ivchar % cols)
   1560 				    || (ivchar / cols) < ixrow) {
   1561 					ivchar += VLEN(l->c[pos.o], ivchar);
   1562 					pos.o++;
   1563 				}
   1564 				if(pos.o > pos.l->len)
   1565 					pos.o = pos.l->len;
   1566 				break;
   1567 			}
   1568 		}
   1569 	}
   1570 	return pos;
   1571 }
   1572 #endif /* HANDLE_MOUSE */
   1573 
   1574 /* Update find_res[sel_re] and sel_re. Return TRUE if find term is a valid
   1575    RE or NULL */
   1576 bool
   1577 i_setfindterm(const char *find_term) {
   1578 	int flags;
   1579 
   1580 	/* Modify find term; use NULL to repeat search */
   1581 	if(!find_term)
   1582 		return TRUE;
   1583 
   1584 	flags = REG_EXTENDED | REG_NEWLINE;
   1585 	if(statusflags & S_CaseIns)
   1586 		flags |= REG_ICASE;
   1587 	if(!regcomp(find_res[sel_re ^ 1], find_term, flags)) {
   1588 		sel_re ^= 1;
   1589 		setenv(envs[EnvFind], find_term, 1);
   1590 		return TRUE;
   1591 	}
   1592 	return FALSE;
   1593 }
   1594 
   1595 /* Setup everything */
   1596 void
   1597 i_setup(void) {
   1598 	unsigned int i, j;
   1599 	Line *l = NULL;
   1600 
   1601 	/* Signal handling, default */
   1602 	/* TODO: use sigaction() ? */
   1603 	signal(SIGWINCH, SIG_IGN);
   1604 	signal(SIGINT, i_cleanup);
   1605 	signal(SIGTERM, i_cleanup);
   1606 
   1607 	/* Some allocs */
   1608 	title[0] = '\0';
   1609 	fifopath[0] = '\0';
   1610 	find_res[0] = (regex_t *) ecalloc(1, sizeof(regex_t));
   1611 	find_res[1] = (regex_t *) ecalloc(1, sizeof(regex_t));
   1612 
   1613 	for(i = 0; i < LEN(cmds); i++) {
   1614 		cmd_res[i] = (regex_t *) ecalloc(1, sizeof(regex_t));
   1615 		if(regcomp(cmd_res[i], cmds[i].re_text,
   1616 			REG_EXTENDED | REG_ICASE | REG_NEWLINE))
   1617 			i_die("Faulty regex.\n");
   1618 	}
   1619 
   1620 	for(i = 0; i < LEN(syntaxes); i++) {
   1621 		syntax_file_res[i] = (regex_t *) ecalloc(1, sizeof(regex_t));
   1622 		if(regcomp(syntax_file_res[i], syntaxes[i].file_re_text,
   1623 			REG_EXTENDED | REG_NOSUB | REG_ICASE | REG_NEWLINE))
   1624 			i_die("Faulty regex.\n");
   1625 		for(j = 0; j < SYN_COLORS; j++)
   1626 			syntax_res[i][j] = (regex_t *) ecalloc(1, sizeof(regex_t));
   1627 	}
   1628 
   1629 	snprintf(fifopath, sizeof(fifopath), "%s%d", fifobase, getpid());
   1630 	if(mkfifo(fifopath, (S_IRUSR | S_IWUSR)) != 0)
   1631 		i_die("FIFO already exists.\n");
   1632 	if((fifofd = open(fifopath, O_RDONLY | O_NONBLOCK)) == -1)
   1633 		i_die("Can't open FIFO for reading.\n");
   1634 	setenv(envs[EnvFifo], fifopath, 1);
   1635 	regcomp(find_res[0], "\0\0", 0); /* This should not match anything */
   1636 	regcomp(find_res[1], "\0\0", 0);
   1637 
   1638 	if(!newterm(NULL, stderr, stdin)) {
   1639 		if(!(newterm("xterm", stderr, stdin)))
   1640 			i_die("Can't fallback $TERM to xterm, exiting...\n");
   1641 		tmptitle = "WARNING! $TERM not recognized, using xterm as fallback!!!";
   1642 	}
   1643 	if(has_colors()) {
   1644 		start_color();
   1645 		use_default_colors();
   1646 		for(i = 0; i < LastFG; i++) {
   1647 			for(j = 0; j < LastBG; j++) {
   1648 				/* Handle more than 8 colors */
   1649 				if(fgcolors[i] > 7)
   1650 					init_color(fgcolors[i], fgcolors[i] >> 8,
   1651 					           (fgcolors[i] >> 4) & 0xF, fgcolors[i] & 0xFF);
   1652 
   1653 				if(bgcolors[j] > 7)
   1654 					init_color(bgcolors[j], bgcolors[j] >> 8, (bgcolors[j] >> 4) & 0xF,
   1655 					           bgcolors[j] & 0xFF);
   1656 				init_pair((i * LastBG) + j, fgcolors[i], bgcolors[j]);
   1657 				textattrs[i][j] = COLOR_PAIR((i * LastBG) + j) | colorattrs[i];
   1658 			}
   1659 		}
   1660 	} else {
   1661 		for(i = 0; i < LastFG; i++) {
   1662 			for(j = 0; j < LastBG; j++)
   1663 				textattrs[i][j] = bwattrs[i];
   1664 		}
   1665 	}
   1666 	lines = LINES;
   1667 	cols = COLS;
   1668 	i_termwininit();
   1669 
   1670 	/* Init line structure */
   1671 	l = (Line *) ecalloc(1, sizeof(Line));
   1672 	l->c = ecalloc(1, LINSIZ);
   1673 	l->dirty = FALSE;
   1674 	l->len = l->vlen = 0;
   1675 	l->mul = 1;
   1676 	l->next = NULL;
   1677 	l->prev = NULL;
   1678 	fstline = lstline = scrline = fcur.l = l;
   1679 	fcur.o = 0;
   1680 	fsel = fcur;
   1681 }
   1682 
   1683 /* Process SIGWINCH, the terminal has been resized */
   1684 void
   1685 i_sigwinch(int unused) {
   1686 	(void) unused;
   1687 
   1688 	statusflags |= S_NeedResize;
   1689 }
   1690 
   1691 /* Process SIGCONT to return after STOP */
   1692 void
   1693 i_sigcont(int unused) {
   1694 	(void) unused;
   1695 
   1696 	i_resize();
   1697 }
   1698 
   1699 /* Exchange pos0 and pos1 if not in order */
   1700 void
   1701 i_sortpos(Filepos * pos0, Filepos * pos1) {
   1702 	Filepos p;
   1703 
   1704 	for(p.l = fstline; p.l; p.l = p.l->next) {
   1705 		if(p.l == pos0->l || p.l == pos1->l) {
   1706 			if((p.l == pos0->l && (p.l == pos1->l
   1707 				    && pos1->o < pos0->o)) || (p.l == pos1->l
   1708 				&& p.l != pos0->l))
   1709 				p = *pos0, *pos0 = *pos1, *pos1 = p;
   1710 			break;
   1711 		}
   1712 	}
   1713 }
   1714 
   1715 /* Initialize terminal */
   1716 void
   1717 i_termwininit(void) {
   1718 	unsigned int i;
   1719 
   1720 	raw();
   1721 	noecho();
   1722 	nl();
   1723 	if(textwin)
   1724 		delwin(textwin);
   1725 	if(USE_TERM_STATUS && tigetflag("hs") > 0) {
   1726 		tsl_str = tigetstr("tsl");
   1727 		fsl_str = tigetstr("fsl");
   1728 		textwin = newwin(lines, cols, 0, 0);
   1729 	} else {
   1730 		if(titlewin)
   1731 			delwin(titlewin);
   1732 		titlewin = newwin(1, cols, BOTTOM_TITLE ? lines - 1 : 0, 0);
   1733 		wattron(titlewin, A_REVERSE);
   1734 		textwin = newwin(lines - 1, cols, BOTTOM_TITLE ? 0 : 1, 0);
   1735 	}
   1736 	idlok(textwin, TRUE);
   1737 	keypad(textwin, TRUE);
   1738 	meta(textwin, TRUE);
   1739 	nodelay(textwin, FALSE);
   1740 	wtimeout(textwin, 0);
   1741 	curs_set(1);
   1742 	ESCDELAY = 20;
   1743 	mouseinterval(20);
   1744 	scrollok(textwin, FALSE);
   1745 
   1746 #if HANDLE_MOUSE
   1747 	for(i = 0; i < LEN(clks); i++)
   1748 		defmmask |= clks[i].mask;
   1749 	mousemask(defmmask, NULL);
   1750 #endif /* HANDLE_MOUSE */
   1751 }
   1752 
   1753 /* Repaint screen. This is where everything happens. Apologies for the
   1754    unnecessary complexity and length */
   1755 void
   1756 i_update(void) {
   1757 	int iline, irow, ixrow, ivchar, i, ifg, ibg, vlines;
   1758 	int cursor_r = 0, cursor_c = 0;
   1759 	int lines3; /* How many lines fit on screen */
   1760 	long int nscr, ncur = 1, nlst = 1; /* Line number for scrline, fcur.l and lstline */
   1761 	size_t ichar;
   1762 	bool selection;
   1763 	regmatch_t match[SYN_COLORS][1];
   1764 	Line *l;
   1765 	char c[7], buf[16];
   1766 
   1767 	/* Check if we need to resize */
   1768 	if(statusflags & S_NeedResize)
   1769 		i_resize();
   1770 
   1771 	/* Check offset */
   1772 	scrollok(textwin, TRUE); /* Here I scroll */
   1773 	for(selection = FALSE, l = fstline, iline = 1;
   1774 	    l && scrline->prev && l != scrline; iline++, l = l->next) {
   1775 		if(l == fcur.l) { /* Can't have fcur.l before scrline, move scrline up */
   1776 			i = 0;
   1777 			while(l != scrline) {
   1778 				if(VLINES(scrline) > 1) {
   1779 					i = -1;
   1780 					break;
   1781 				}
   1782 				i++;
   1783 				scrline = scrline->prev;
   1784 			}
   1785 			if(i < 0 || i > LINESABS) {
   1786 				scrline = l;
   1787 				statusflags |= S_DirtyScr;
   1788 			} else
   1789 				wscrl(textwin, -i);
   1790 			break;
   1791 		}
   1792 		if(l == fsel.l) /* Selection starts before screen view */
   1793 			selection = !selection;
   1794 	}
   1795 	for(i = irow = 0, l = scrline; l; l = l->next, irow += vlines) {
   1796 		if((vlines = VLINES(l)) > 1)
   1797 			statusflags |= S_DirtyDown; /* if any line before fcur.l has vlines>1 */
   1798 		if(fcur.l == l) {
   1799 			if(irow + vlines > 2 * LINESABS)
   1800 				statusflags |= S_DirtyScr;
   1801 			/* Can't have fcur.l after screen end, move scrline down */
   1802 			while(irow + vlines > LINESABS && scrline->next) {
   1803 				irow -= VLINES(scrline);
   1804 				i += VLINES(scrline);
   1805 				if(scrline == fsel.l)
   1806 					selection = !selection; /* We just scrolled past the selection point */
   1807 				scrline = scrline->next;
   1808 				iline++;
   1809 			}
   1810 			if(!(statusflags & S_DirtyScr))
   1811 				wscrl(textwin, i);
   1812 			break;
   1813 		}
   1814 	}
   1815 	scrollok(textwin, FALSE);
   1816 	nscr = iline;
   1817 
   1818 	/* Actually update lines on screen */
   1819 	for(irow = lines3 = 0, l = scrline; irow < LINESABS;
   1820 	    irow += vlines, lines3++, iline++) {
   1821 		vlines = VLINES(l);
   1822 		if(fcur.l == l) {
   1823 			ncur = iline;
   1824 			/* Update screen cursor position */
   1825 			cursor_c = 0;
   1826 			cursor_r = irow;
   1827 			for(ichar = 0; ichar < fcur.o; ichar++)
   1828 				cursor_c += VLEN(fcur.l->c[ichar], cursor_c);
   1829 			while(cursor_c >= cols) {
   1830 				cursor_c -= cols;
   1831 				cursor_r++;
   1832 			}
   1833 		}
   1834 		if(statusflags & S_DirtyScr || (l && l->dirty
   1835 			&& (statusflags & S_DirtyDown ? statusflags |= S_DirtyScr : 1))) {
   1836 			/* Print line content */
   1837 			if(l)
   1838 				l->dirty = FALSE;
   1839 			if(syntx >= 0 && l)
   1840 				for(i = 0; i < SYN_COLORS; i++)
   1841 					if(regexec(syntax_res[syntx][i], l->c, 1, match[i], 0) ||
   1842 						match[i][0].rm_so == match[i][0].rm_eo)
   1843 						match[i][0].rm_so = match[i][0].rm_eo = -1;
   1844 			for(ixrow = ichar = ivchar = 0; ixrow < vlines && (irow + ixrow) < LINESABS; ixrow++) {
   1845 				wmove(textwin, (irow + ixrow), (ivchar % cols));
   1846 				while(ivchar < (1 + ixrow) * cols) {
   1847 					if(fcur.l == l && ichar == fcur.o)
   1848 						selection = !selection;
   1849 					if(fsel.l == l && ichar == fsel.o)
   1850 						selection = !selection;
   1851 					ifg = DefFG, ibg = DefBG;
   1852 					if(fcur.l == l)
   1853 						ifg = CurFG, ibg = CurBG;
   1854 					if(selection)
   1855 						ifg = SelFG, ibg = SelBG;
   1856 					if(syntx >= 0 && l)
   1857 						for(i = 0; i < SYN_COLORS; i++) {
   1858 							if(match[i][0].rm_so == -1)
   1859 								continue;
   1860 							if(ichar >= (size_t) match[i][0].rm_eo) {
   1861 								if(regexec(syntax_res[syntx][i], &l->c [ichar], 1, match[i],REG_NOTBOL) ||
   1862 								    match[i][0].rm_so == match[i][0].rm_eo)
   1863 									continue;
   1864 								match[i][0].rm_so += ichar;
   1865 								match[i][0].rm_eo += ichar;
   1866 							}
   1867 							if(ichar >= (size_t) match[i][0].rm_so && ichar < (size_t) match[i][0].rm_eo)
   1868 								ifg = Syn0FG + i;
   1869 						}
   1870 					wattrset(textwin, textattrs[ifg][ibg]);
   1871 					if(l && ichar < l->len) {
   1872 						/* Tab nightmare */
   1873 						if(l->c[ichar] == '\t') {
   1874 							wattrset(textwin, textattrs[SpcFG][ibg]);
   1875 							for(i = 0; i < VLEN('\t', ivchar); i++)
   1876 								waddstr(textwin, ((i == 0 && isutf8) ? tabstr : " "));
   1877 						} else if(l->c[ichar] == ' ') { /* Space */
   1878 							wattrset(textwin, textattrs[SpcFG][ibg]);
   1879 							waddstr(textwin, (isutf8 ? spcstr : " "));
   1880 						} else if(ISCTRL(l->c[ichar])) {
   1881 							/* Add Ctrl-char as string to avoid problems at right screen end */
   1882 							wattrset(textwin, textattrs[CtrlFG][ibg]);
   1883 							waddstr(textwin, unctrl(l->c[ichar]));
   1884 						} else if(isutf8 && !ISASCII(l->c[ichar])) {
   1885 							/* Begin multi-byte char, dangerous at right screen end */
   1886 							for(i = 0; i < UTF8LEN(l->c[ichar]); i++) {
   1887 								if(ichar + i < l->len)
   1888 									c[i] = l->c[ichar + i];
   1889 								else
   1890 									c[i] = '\0';
   1891 							}
   1892 							c[i] = '\0'; /* WARNING: we use i later... */
   1893 							waddstr(textwin, c);
   1894 						} else {
   1895 							waddch(textwin, l->c[ichar]);
   1896 						}
   1897 						ivchar += VLEN(l->c[ichar], ivchar);
   1898 						if(isutf8 && !ISASCII(l->c[ichar]) && i)
   1899 							ichar += i; /* ...here */
   1900 						else
   1901 							ichar++;
   1902 					} else {
   1903 						ifg = DefFG, ibg = DefBG;
   1904 						if(fcur.l == l) {
   1905 							ifg = CurFG;
   1906 							ibg = CurBG;
   1907 						}
   1908 						if(selection) {
   1909 							ifg = SelFG;
   1910 							ibg = SelBG;
   1911 						}
   1912 						wattrset(textwin, textattrs[ifg][ibg]);
   1913 						waddch(textwin, ' ');
   1914 						ivchar++;
   1915 						ichar++;
   1916 					}
   1917 				}
   1918 			}
   1919 		} else if(l == fsel.l || l == fcur.l) {
   1920 			selection = !selection;
   1921 		}
   1922 		if(l)
   1923 			l = l->next;
   1924 	}
   1925 
   1926 	/* Calculate nlst */
   1927 	for(iline = ncur, l = fcur.l; l; l = l->next, iline++) {
   1928 		if(l == lstline)
   1929 			nlst = iline;
   1930 	}
   1931 
   1932 	/* Position cursor */
   1933 	wmove(textwin, cursor_r, cursor_c);
   1934 
   1935 	/* Update env */
   1936 	snprintf(buf, 16, "%ld", ncur);
   1937 	setenv(envs[EnvLine], buf, 1);
   1938 	snprintf(buf, 16, "%d", (int) fcur.o);
   1939 	setenv(envs[EnvOffset], buf, 1);
   1940 
   1941 	/* Update title */
   1942 	if(tmptitle) {
   1943 		snprintf(title, sizeof(title), "%s", tmptitle);
   1944 	} else {
   1945 		statusflags &= ~S_Warned;	/* Reset warning */
   1946 		snprintf(buf, 4, "%ld%%", (100 * ncur) / nlst);
   1947 		snprintf(title, BUFSIZ, "%s%s [%s]%s%s%s%s %ld,%d  %s",
   1948 		    t_vis()? "Visual " :
   1949 #if VIM_BINDINGS
   1950 		    (t_ins()? "Insert " : "Command "),
   1951 #else
   1952 		    "",
   1953 #endif /* VIM_BINDINGS */
   1954 		    (statusflags & S_DumpStdout ? "<Stdout>" : (filename == NULL ? "<No file>" : filename)),
   1955 		    (syntx >= 0 ? syntaxes[syntx].name : "none"),
   1956 		    (t_mod()? "[+]" : ""), (!t_rw()? "[RO]" : ""),
   1957 		    (statusflags & S_CaseIns ? "[icase]" : ""),
   1958 		    (statusflags & S_AutoIndent ? "[ai]" : ""), ncur,
   1959 		    (int) fcur.o,
   1960 		    (scrline == fstline ? (nlst <
   1961 			    lines3 ? "All" : "Top") : (nlst - nscr <
   1962 			    lines3 ? "Bot" : buf)
   1963 		    ));
   1964 	}
   1965 	if(titlewin) {
   1966 		wmove(titlewin, 0, 0);
   1967 		for(i = 0; i < cols; i++)
   1968 			waddch(titlewin, ' ');
   1969 		mvwaddnstr(titlewin, 0, 0, title, cols);
   1970 	} else {
   1971 		putp(tsl_str);
   1972 		putp(title);
   1973 		putp(fsl_str);
   1974 	}
   1975 
   1976 	/* Clean global dirty bits */
   1977 	statusflags &= ~(S_DirtyScr | S_DirtyDown | S_NeedResize);
   1978 	tmptitle = NULL;
   1979 
   1980 	/* And go.... */
   1981 	if(titlewin)
   1982 		wnoutrefresh(titlewin);
   1983 	wnoutrefresh(textwin);
   1984 	doupdate();
   1985 }
   1986 
   1987 /* Print help, die */
   1988 void
   1989 i_usage(void) {
   1990 	i_die("sandy - simple editor\n"
   1991 	      "usage: sandy [-a] [-d] [-r] [-u] [-t TABSTOP] [-S] [-s SYNTAX] [file | -]\n");
   1992 }
   1993 
   1994 /* Write buffer to disk */
   1995 bool
   1996 i_writefile(char *fname) {
   1997 	int fd = 1; /* default: write to stdout */
   1998 	bool wok = TRUE;
   1999 	Line *l;
   2000 
   2001 	if(fname != NULL
   2002 	    && (fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT,
   2003 		    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |
   2004 		    S_IWOTH)) == -1) {
   2005 		/* error */
   2006 		tmptitle = "WARNING! Can't save file!!!";
   2007 		return FALSE;
   2008 	}
   2009 
   2010 	for(l = fstline; wok && l; l = l->next) {
   2011 		if(write(fd, l->c, l->len) == -1 ||
   2012 		    (l->next && write(fd, "\n", 1) == -1))
   2013 			wok = FALSE;
   2014 	}
   2015 	if(fd != 1)
   2016 		close(fd);
   2017 	return wok;
   2018 }
   2019 
   2020 /* M_* FUNCTIONS
   2021 	Represent a cursor motion, always take a Filepos and return an update Filepos */
   2022 
   2023 /* Go to beginning of file */
   2024 Filepos
   2025 m_bof(Filepos pos) {
   2026 	pos.l = fstline;
   2027 	pos.o = 0;
   2028 	return pos;
   2029 }
   2030 
   2031 /* Go to beginning of line */
   2032 Filepos
   2033 m_bol(Filepos pos) {
   2034 	pos.o = 0;
   2035 	return pos;
   2036 }
   2037 
   2038 /* Go to smart beginning of line */
   2039 Filepos
   2040 m_smartbol(Filepos pos) {
   2041 	Filepos vbol = pos;
   2042 
   2043 	vbol.o = 0;
   2044 	while(ISBLANK(vbol.l->c[vbol.o]) && ++vbol.o < vbol.l->len) ;
   2045 	if(pos.o != 0 && pos.o <= vbol.o)
   2046 		vbol.o = 0;
   2047 	return vbol;
   2048 }
   2049 
   2050 /* Go to end of file */
   2051 Filepos
   2052 m_eof(Filepos pos) {
   2053 	pos.l = lstline;
   2054 	pos.o = pos.l->len;
   2055 	return pos;
   2056 }
   2057 
   2058 /* Go to end of line */
   2059 Filepos
   2060 m_eol(Filepos pos) {
   2061 	pos.o = pos.l->len;
   2062 	return pos;
   2063 }
   2064 
   2065 /* Advance one char, next line if needed */
   2066 Filepos
   2067 m_nextchar(Filepos pos) {
   2068 	if(pos.o < pos.l->len) {
   2069 		pos.o++;
   2070 		FIXNEXT(pos);
   2071 	} else if(pos.l->next) {
   2072 		pos.l = pos.l->next;
   2073 		pos.o = 0;
   2074 	}
   2075 	return pos;
   2076 }
   2077 
   2078 /* Backup one char, previous line if needed */
   2079 Filepos
   2080 m_prevchar(Filepos pos) {
   2081 	if(pos.o > 0) {
   2082 		pos.o--;
   2083 		FIXPREV(pos);
   2084 	} else if(pos.l->prev) {
   2085 		pos.l = pos.l->prev;
   2086 		pos.o = pos.l->len;
   2087 	}
   2088 	return pos;
   2089 }
   2090 
   2091 /* Advance one word, next line if needed */
   2092 Filepos
   2093 m_nextword(Filepos pos) {
   2094 	Filepos p0 = m_nextchar(pos);
   2095 
   2096 	while((p0.o != pos.o || p0.l != pos.l) && ISWORDBRK(pos.l->c[pos.o]))
   2097 		pos = p0, p0 = m_nextchar(pos);	/* Find the current or next word */
   2098 
   2099 	do {
   2100 		/* Move to word end */
   2101 		p0 = pos, pos = m_nextchar(pos);
   2102 	} while((p0.o != pos.o || p0.l != pos.l) && !ISWORDBRK(pos.l->c[pos.o]));
   2103 	return pos;
   2104 }
   2105 
   2106 /* Backup one word, previous line if needed */
   2107 Filepos
   2108 m_prevword(Filepos pos) {
   2109 	Filepos p0 = m_prevchar(pos);
   2110 
   2111 	if(ISWORDBRK(pos.l->c[pos.o]))
   2112 		while((p0.o != pos.o || p0.l != pos.l)
   2113 		    && ISWORDBRK(pos.l->c[pos.o]))
   2114 			pos = p0, p0 = m_prevchar(pos);	/* Find the current or previous word */
   2115 	else
   2116 		pos = p0;
   2117 
   2118 	do {
   2119 		/* Move to word start */
   2120 		p0 = pos, pos = m_prevchar(pos);
   2121 	} while((p0.o != pos.o || p0.l != pos.l) && !ISWORDBRK(pos.l->c[pos.o]));
   2122 	return p0;
   2123 }
   2124 
   2125 /* Advance one line, or to eol if at last line */
   2126 Filepos
   2127 m_nextline(Filepos pos) {
   2128 	size_t ivchar, ichar;
   2129 
   2130 	for(ivchar = ichar = 0; ichar < pos.o; ichar++)
   2131 		ivchar += VLEN(pos.l->c[ichar], ivchar);
   2132 
   2133 	if(pos.l->next) {
   2134 		/* Remember: here we re-use ichar as a second ivchar */
   2135 		for(pos.l = pos.l->next, pos.o = ichar = 0; ichar < ivchar && pos.o < pos.l->len; pos.o++)
   2136 			ichar += VLEN(pos.l->c[pos.o], ichar);
   2137 		FIXNEXT(pos);
   2138 	} else {
   2139 		pos.o = pos.l->len;
   2140 	}
   2141 	return pos;
   2142 }
   2143 
   2144 /* Backup one line, or to bol if at first line */
   2145 Filepos
   2146 m_prevline(Filepos pos) {
   2147 	size_t ivchar, ichar;
   2148 
   2149 	for(ivchar = ichar = 0; ichar < pos.o; ichar++)
   2150 		ivchar += VLEN(pos.l->c[ichar], (ivchar % (cols - 1)));
   2151 
   2152 	if(pos.l->prev) {
   2153 		/* Remember: here we re-use ichar as a second ivchar */
   2154 		for(pos.l = pos.l->prev, pos.o = ichar = 0; ichar < ivchar && pos.o < pos.l->len; pos.o++)
   2155 			ichar += VLEN(pos.l->c[pos.o], ichar);
   2156 		FIXNEXT(pos);
   2157 	} else {
   2158 		pos.o = 0;
   2159 	}
   2160 	return pos;
   2161 }
   2162 
   2163 /* Advance as many lines as the screen size */
   2164 Filepos
   2165 m_nextscr(Filepos pos) {
   2166 	int i;
   2167 	Line *l;
   2168 
   2169 	for(i = LINESABS, l = pos.l; l->next && i > 0; i -= VLINES(l), l = l->next)
   2170 		;
   2171 	pos.l = l;
   2172 	pos.o = pos.l->len;
   2173 	return pos;
   2174 }
   2175 
   2176 /* Go to where the adjective says */
   2177 Filepos
   2178 m_parameter(Filepos pos) {
   2179 	/* WARNING: this code is actually not used */
   2180 	return pos;
   2181 }
   2182 
   2183 /* Backup as many lines as the screen size */
   2184 Filepos
   2185 m_prevscr(Filepos pos) {
   2186 	int i;
   2187 	Line *l;
   2188 
   2189 	for(i = LINESABS, l = pos.l; l->prev && i > 0; i -= VLINES(l), l = l->prev)
   2190 		;
   2191 	pos.l = l;
   2192 	pos.o = 0;
   2193 	return pos;
   2194 }
   2195 
   2196 /* Go to where the adjective says */
   2197 Filepos
   2198 m_sentence(Filepos pos) {
   2199 	/* WARNING: this code is actually not used */
   2200 	return pos;
   2201 }
   2202 
   2203 /* Do not move */
   2204 Filepos
   2205 m_stay(Filepos pos) {
   2206 	return pos;
   2207 }
   2208 
   2209 /* Go to mark if valid, stay otherwise */
   2210 Filepos
   2211 m_tomark(Filepos pos) {
   2212 	/* Be extra careful when moving to mark, as it might not exist */
   2213 	Line *l;
   2214 	for(l = fstline; l; l = l->next) {
   2215 		if(l != NULL && l == fmrk.l) {
   2216 			pos.l = fmrk.l;
   2217 			pos.o = fmrk.o;
   2218 			if(pos.o > pos.l->len)
   2219 				pos.o = pos.l->len;
   2220 			FIXNEXT(pos);
   2221 			f_mark(NULL);
   2222 			break;
   2223 		}
   2224 	}
   2225 	return pos;
   2226 }
   2227 
   2228 /* Go to selection point */
   2229 Filepos
   2230 m_tosel(Filepos pos) {
   2231 	(void) pos;
   2232 
   2233 	return fsel;
   2234 }
   2235 
   2236 /* T_* FUNCTIONS
   2237  * Used to test for conditions, take no arguments and return bool. */
   2238 
   2239 /* TRUE if autoindent is on */
   2240 bool
   2241 t_ai(void) {
   2242 	return (statusflags & S_AutoIndent);
   2243 }
   2244 
   2245 /* TRUE at the beginning of line */
   2246 bool
   2247 t_bol(void) {
   2248 	return (fcur.o == 0);
   2249 }
   2250 
   2251 /* TRUE at end of line */
   2252 bool
   2253 t_eol(void) {
   2254 	return (fcur.o == fcur.l->len);
   2255 }
   2256 
   2257 /* TRUE if the file has been modified */
   2258 bool
   2259 t_mod(void) {
   2260 	return (statusflags & S_Modified);
   2261 }
   2262 
   2263 /* TRUE if we are not in command mode */
   2264 bool
   2265 t_ins(void) {
   2266 #if VIM_BINDINGS
   2267 	return !(statusflags & S_Command);
   2268 #else
   2269 	return TRUE;
   2270 #endif /* VIM_BINDINGS */
   2271 }
   2272 
   2273 /* TRUE if the file is writable */
   2274 bool
   2275 t_rw(void) {
   2276 	return !(statusflags & S_Readonly);
   2277 }
   2278 
   2279 /* TRUE if there is anything to redo */
   2280 bool
   2281 t_redo(void) {
   2282 	return (redos != NULL);
   2283 }
   2284 
   2285 /* TRUE if any text is selected */
   2286 bool
   2287 t_sel(void) {
   2288 	return !(fcur.l == fsel.l && fcur.o == fsel.o);
   2289 }
   2290 
   2291 /* TRUE if a sentence has started */
   2292 bool
   2293 t_sent(void) {
   2294 #if VIM_BINDINGS
   2295 	return (statusflags & S_Sentence);
   2296 #else
   2297 	return FALSE;
   2298 #endif /* VIM_BINDINGS */
   2299 }
   2300 
   2301 /* TRUE if there is anything to undo */
   2302 bool
   2303 t_undo(void) {
   2304 	return (undos != NULL);
   2305 }
   2306 
   2307 /* TRUE if we are in visual mode */
   2308 bool
   2309 t_vis(void) {
   2310 	return (statusflags & S_Visual);
   2311 }
   2312 
   2313 /* TRUE if we have warned the file is modified */
   2314 bool
   2315 t_warn(void) {
   2316 	return (statusflags & S_Warned);
   2317 }
   2318 
   2319 /* main() starts everything else */
   2320 int
   2321 main(int argc, char *argv[]) {
   2322 	char *local_syn = NULL;
   2323 
   2324 	/* Use system locale, hopefully UTF-8 */
   2325 	setlocale(LC_ALL, "");
   2326 
   2327 	ARGBEGIN {
   2328 	case 'r':
   2329 		statusflags |= S_Readonly;
   2330 		break;
   2331 	case 'a':
   2332 		statusflags |= S_AutoIndent;
   2333 		break;
   2334 	case 'd':
   2335 		statusflags |= S_DumpStdout;
   2336 		break;
   2337 	case 't':
   2338 		tabstop = atoi(EARGF(i_usage()));
   2339 		break;
   2340 	case 'S':
   2341 		local_syn = "";
   2342 		break;
   2343 	case 's':
   2344 		local_syn = EARGF(i_usage());
   2345 		break;
   2346 	case 'v':
   2347 		i_die("sandy-" VERSION ", © 2014 sandy engineers, see LICENSE for details\n");
   2348 		break;
   2349 	default:
   2350 		i_usage();
   2351 		break;
   2352 	} ARGEND;
   2353 
   2354 	i_setup();
   2355 
   2356 	if(argc > 0)
   2357 		i_readfile(argv[0]);
   2358 
   2359 	if(local_syn)
   2360 		f_syntax(&(const Arg) { .v = local_syn });
   2361 
   2362 	i_edit();
   2363 	i_cleanup(EXIT_SUCCESS);
   2364 	return EXIT_SUCCESS;
   2365 }