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 }