vi: stop in lbuf_postindents() when lbuf_lnnext() fails
[neatvi.git] / vi.c
bloba2144b0b4662f2821709d2f0ffd779d6f3ad2846
1 /*
2 * neatvi editor
4 * Copyright (C) 2015 Ali Gholami Rudi <ali at rudi dot ir>
6 * This program is released under the Modified BSD license.
7 */
8 #include <ctype.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include "vi.h"
15 char xpath[PATHLEN]; /* current file */
16 struct lbuf *xb; /* current buffer */
17 int xrow, xcol, xtop; /* current row, column, and top row */
18 int xled = 1; /* use the line editor */
19 int xdir = 'L'; /* current direction context */
20 int xquit;
22 static void vi_draw(void)
24 int i;
25 term_record();
26 for (i = xtop; i < xtop + xrows; i++) {
27 char *s = lbuf_get(xb, i);
28 led_print(s ? s : "~", i - xtop);
30 term_pos(xrow, xcol);
31 term_commit();
34 static int vi_buf[128];
35 static int vi_buflen;
37 static int vi_read(void)
39 return vi_buflen ? vi_buf[--vi_buflen] : term_read(1000);
42 static void vi_back(int c)
44 if (vi_buflen < sizeof(vi_buf))
45 vi_buf[vi_buflen++] = c;
48 static char *vi_char(void)
50 return led_keymap(vi_read());
53 static int vi_prefix(void)
55 int pre = 0;
56 int c = vi_read();
57 if ((c >= '1' && c <= '9')) {
58 while (isdigit(c)) {
59 pre = pre * 10 + c - '0';
60 c = vi_read();
63 vi_back(c);
64 return pre;
67 static int lbuf_lnnext(struct lbuf *lb, int *r, int *c, int dir)
69 char *ln = lbuf_get(lb, *r);
70 int col = ln ? ren_next(ln, *c, dir) : -1;
71 if (col < 0)
72 return -1;
73 *c = col;
74 return 0;
77 static void lbuf_eol(struct lbuf *lb, int *r, int *c, int dir)
79 char *ln = lbuf_get(lb, *r);
80 *c = ren_eol(ln ? ln : "", dir);
83 static int lbuf_next(struct lbuf *lb, int *r, int *c, int dir)
85 if (dir < 0 && *r >= lbuf_len(lb))
86 *r = MAX(0, lbuf_len(lb) - 1);
87 if (lbuf_lnnext(lb, r, c, dir)) {
88 if (!lbuf_get(lb, *r + dir))
89 return -1;
90 *r += dir;
91 lbuf_eol(lb, r, c, -dir);
92 return 0;
94 return 0;
97 /* return a static buffer to the character at visual position c of line r */
98 static char *lbuf_chr(struct lbuf *lb, int r, int c)
100 static char chr[8];
101 char *ln = lbuf_get(lb, r);
102 if (ln) {
103 int off = ren_off(ln, c);
104 char *s = uc_chr(ln, off);
105 if (s) {
106 memcpy(chr, s, uc_len(s));
107 chr[uc_len(s)] = '\0';
108 return chr;
111 return "";
114 static void lbuf_postindents(struct lbuf *lb, int *r, int *c)
116 lbuf_eol(lb, r, c, -1);
117 while (uc_isspace(lbuf_chr(lb, *r, *c)))
118 if (lbuf_lnnext(lb, r, c, +1))
119 break;
122 static void lbuf_findchar(struct lbuf *lb, int *row, int *col, char *cs, int dir, int n)
124 int c = *col;
125 if (!cs)
126 return;
127 while (n > 0 && !lbuf_lnnext(lb, row, &c, dir))
128 if (uc_code(lbuf_chr(lb, *row, c)) == uc_code(cs))
129 n--;
130 if (!n)
131 *col = c;
134 static void lbuf_tochar(struct lbuf *lb, int *row, int *col, char *cs, int dir, int n)
136 int c = *col;
137 if (!cs)
138 return;
139 while (n > 0 && !lbuf_lnnext(lb, row, &c, dir))
140 if (uc_code(lbuf_chr(lb, *row, c)) == uc_code(cs))
141 n--;
142 if (!n) {
143 *col = c;
144 lbuf_lnnext(lb, row, col, -dir);
148 static int vi_motionln(int *row, int cmd, int pre1, int pre2)
150 int pre = (pre1 ? pre1 : 1) * (pre2 ? pre2 : 1);
151 int c = vi_read();
152 int mark;
153 switch (c) {
154 case '\n':
155 case '+':
156 *row = MIN(*row + pre, lbuf_len(xb) - 1);
157 break;
158 case '-':
159 *row = MAX(*row - pre, 0);
160 break;
161 case '_':
162 *row = MIN(*row + pre - 1, lbuf_len(xb) - 1);
163 break;
164 case '\'':
165 if ((mark = vi_read()) > 0 && (isalpha(mark) || mark == '\''))
166 if (lbuf_markpos(xb, mark) >= 0)
167 *row = lbuf_markpos(xb, mark);
168 break;
169 case 'j':
170 *row = MIN(*row + pre, lbuf_len(xb) - 1);
171 break;
172 case 'k':
173 *row = MAX(*row - pre, 0);
174 break;
175 case 'G':
176 *row = (pre1 || pre2) ? pre - 1 : lbuf_len(xb) - 1;
177 break;
178 case 'H':
179 if (lbuf_len(xb))
180 *row = MIN(xtop + pre - 1, lbuf_len(xb) - 1);
181 else
182 *row = 0;
183 break;
184 case 'L':
185 if (lbuf_len(xb))
186 *row = MIN(xtop + xrows - 1 - pre + 1, lbuf_len(xb) - 1);
187 else
188 *row = 0;
189 break;
190 case 'M':
191 if (lbuf_len(xb))
192 *row = MIN(xtop + xrows / 2, lbuf_len(xb) - 1);
193 else
194 *row = 0;
195 break;
196 default:
197 if (c == cmd) {
198 *row = MIN(*row + pre - 1, lbuf_len(xb) - 1);
199 break;
201 vi_back(c);
202 return 0;
204 return c;
207 /* move to the last character of the word */
208 static int lbuf_wordlast(struct lbuf *lb, int *row, int *col, int kind, int dir)
210 if (!kind || !(uc_kind(lbuf_chr(lb, *row, *col)) & kind))
211 return 0;
212 while (uc_kind(lbuf_chr(lb, *row, *col)) & kind)
213 if (lbuf_next(lb, row, col, dir))
214 return 1;
215 if (!(uc_kind(lbuf_chr(lb, *row, *col)) & kind))
216 lbuf_next(lb, row, col, -dir);
217 return 0;
220 static int lbuf_wordbeg(struct lbuf *lb, int *row, int *col, int big, int dir)
222 int nl = 0;
223 lbuf_wordlast(lb, row, col, big ? 3 : uc_kind(lbuf_chr(lb, *row, *col)), dir);
224 if (lbuf_next(lb, row, col, dir))
225 return 1;
226 while (uc_isspace(lbuf_chr(lb, *row, *col))) {
227 nl = uc_code(lbuf_chr(lb, *row, *col)) == '\n' ? nl + 1 : 0;
228 if (nl == 2)
229 return 0;
230 if (lbuf_next(lb, row, col, dir))
231 return 1;
233 return 0;
236 static int lbuf_wordend(struct lbuf *lb, int *row, int *col, int big, int dir)
238 int nl = uc_code(lbuf_chr(lb, *row, *col)) == '\n' ? -1 : 0;
239 if (!uc_isspace(lbuf_chr(lb, *row, *col)))
240 if (lbuf_next(lb, row, col, dir))
241 return 1;
242 while (uc_isspace(lbuf_chr(lb, *row, *col))) {
243 nl = uc_code(lbuf_chr(lb, *row, *col)) == '\n' ? nl + 1 : 0;
244 if (nl == 2) {
245 if (dir < 0)
246 lbuf_next(lb, row, col, -dir);
247 return 0;
249 if (lbuf_next(lb, row, col, dir))
250 return 1;
252 if (lbuf_wordlast(lb, row, col, big ? 3 : uc_kind(lbuf_chr(lb, *row, *col)), dir))
253 return 1;
254 return 0;
257 static int vi_motion(int *row, int *col, int pre1, int pre2)
259 int c = vi_read();
260 int pre = (pre1 ? pre1 : 1) * (pre2 ? pre2 : 1);
261 char *ln = lbuf_get(xb, *row);
262 int dir = ren_dir(ln ? ln : "");
263 int i;
264 switch (c) {
265 case ' ':
266 for (i = 0; i < pre; i++)
267 if (lbuf_next(xb, row, col, 1))
268 break;
269 break;
270 case 'f':
271 lbuf_findchar(xb, row, col, vi_char(), +1, pre);
272 break;
273 case 'F':
274 lbuf_findchar(xb, row, col, vi_char(), -1, pre);
275 break;
276 case 'h':
277 for (i = 0; i < pre; i++)
278 if (lbuf_lnnext(xb, row, col, -1 * dir))
279 break;
280 break;
281 case 'l':
282 for (i = 0; i < pre; i++)
283 if (lbuf_lnnext(xb, row, col, +1 * dir))
284 break;
285 break;
286 case 't':
287 lbuf_tochar(xb, row, col, vi_char(), 1, pre);
288 break;
289 case 'T':
290 lbuf_tochar(xb, row, col, vi_char(), 0, pre);
291 break;
292 case 'B':
293 for (i = 0; i < pre; i++)
294 if (lbuf_wordend(xb, row, col, 1, -1))
295 break;
296 break;
297 case 'E':
298 for (i = 0; i < pre; i++)
299 if (lbuf_wordend(xb, row, col, 1, +1))
300 break;
301 break;
302 case 'W':
303 for (i = 0; i < pre; i++)
304 if (lbuf_wordbeg(xb, row, col, 1, +1))
305 break;
306 break;
307 case 'b':
308 for (i = 0; i < pre; i++)
309 if (lbuf_wordend(xb, row, col, 0, -1))
310 break;
311 break;
312 case 'e':
313 for (i = 0; i < pre; i++)
314 if (lbuf_wordend(xb, row, col, 0, +1))
315 break;
316 break;
317 case 'w':
318 for (i = 0; i < pre; i++)
319 if (lbuf_wordbeg(xb, row, col, 0, +1))
320 break;
321 break;
322 case '0':
323 lbuf_eol(xb, row, col, -1);
324 break;
325 case '^':
326 lbuf_postindents(xb, row, col);
327 break;
328 case '$':
329 lbuf_eol(xb, row, col, +1);
330 lbuf_lnnext(xb, row, col, -1);
331 break;
332 case '|':
333 *col = pre - 1;
334 break;
335 case 127:
336 case TERMCTRL('h'):
337 *col = ren_cursor(ln, *col);
338 for (i = 0; i < pre; i++)
339 if (lbuf_next(xb, row, col, -1))
340 break;
341 break;
342 default:
343 vi_back(c);
344 return 0;
346 return c;
349 static void swap(int *a, int *b)
351 int t = *a;
352 *a = *b;
353 *b = t;
356 static char *lbuf_region(struct lbuf *lb, int r1, int l1, int r2, int l2)
358 struct sbuf *sb;
359 char *s1, *s2, *s3;
360 if (r1 == r2)
361 return uc_sub(lbuf_get(lb, r1), l1, l2);
362 sb = sbuf_make();
363 s1 = uc_sub(lbuf_get(lb, r1), l1, -1);
364 s3 = uc_sub(lbuf_get(lb, r2), 0, l2);
365 s2 = lbuf_cp(lb, r1 + 1, r2);
366 sbuf_str(sb, s1);
367 sbuf_str(sb, s2);
368 sbuf_str(sb, s3);
369 free(s1);
370 free(s2);
371 free(s3);
372 return sbuf_done(sb);
375 static void vc_motion(int c, int pre1)
377 int r1 = xrow, r2 = xrow; /* region rows */
378 int c1 = xcol, c2 = xcol; /* visual region columns */
379 int l1, l2; /* logical region columns */
380 int ln = 0; /* line-based region */
381 int closed = 1; /* include the last character */
382 int mv, i;
383 char *pref = NULL;
384 char *post = NULL;
385 int pre2 = vi_prefix();
386 if (pre2 < 0)
387 return;
388 if (vi_motionln(&r2, c, pre1, pre2)) {
389 ln = 1;
390 lbuf_eol(xb, &r1, &c1, -1);
391 lbuf_eol(xb, &r2, &c2, +1);
392 } else if ((mv = vi_motion(&r2, &c2, pre1, pre2))) {
393 if (!strchr("fFtTeE$", mv))
394 closed = 0;
395 } else {
396 return;
398 /* make sure the first position is visually before the second */
399 if (r2 < r1 || (r2 == r1 && ren_cmp(lbuf_get(xb, r1), c1, c2) > 0)) {
400 swap(&r1, &r2);
401 swap(&c1, &c2);
403 if (c == 'y') { /* adjusting cursor position */
404 xrow = r1;
405 xcol = ln ? xcol : c1;
407 for (i = 0; i < 2; i++) {
408 l1 = ren_insertionoffset(lbuf_get(xb, r1), c1, 1);
409 l2 = ren_insertionoffset(lbuf_get(xb, r2), c2, !closed);
410 if (r1 == r2 && l2 < l1) /* offsets out of order */
411 swap(&l1, &l2);
413 pref = ln ? uc_dup("") : uc_sub(lbuf_get(xb, r1), 0, l1);
414 post = ln ? uc_dup("\n") : uc_sub(lbuf_get(xb, r2), l2, -1);
415 if (c == 'c' || c == 'd' || c == 'y') {
416 char *region = lbuf_region(xb, r1, ln ? 0 : l1, r2, ln ? -1 : l2);
417 reg_put(0, region, ln);
418 free(region);
420 if (c == 'c') {
421 int row, col;
422 char *rep = led_input(pref, post, &row, &col);
423 if (rep) {
424 lbuf_rm(xb, r1, r2 + 1);
425 lbuf_put(xb, r1, rep);
426 xrow = r1 + row;
427 xcol = col;
428 free(rep);
431 if (c == 'd') {
432 lbuf_rm(xb, r1, r2 + 1);
433 if (!ln) {
434 struct sbuf *sb = sbuf_make();
435 sbuf_str(sb, pref);
436 sbuf_str(sb, post);
437 lbuf_put(xb, r1, sbuf_buf(sb));
438 sbuf_free(sb);
440 xrow = r1;
441 xcol = c1;
442 if (ln)
443 lbuf_postindents(xb, &xrow, &xcol);
445 free(pref);
446 free(post);
449 static void vc_insert(int cmd)
451 char *pref, *post;
452 char *ln = lbuf_get(xb, xrow);
453 int row, col, off = 0;
454 char *rep;
455 if (cmd == 'I')
456 lbuf_postindents(xb, &xrow, &xcol);
457 if (cmd == 'A') {
458 lbuf_eol(xb, &xrow, &xcol, +1);
459 lbuf_lnnext(xb, &xrow, &xcol, -1);
461 if (cmd == 'o')
462 xrow += 1;
463 if (cmd == 'o' || cmd == 'O')
464 ln = NULL;
465 if (cmd == 'i' || cmd == 'I')
466 off = ln ? ren_insertionoffset(ln, xcol, 1) : 0;
467 if (cmd == 'a' || cmd == 'A')
468 off = ln ? ren_insertionoffset(ln, xcol, 0) : 0;
469 pref = ln ? uc_sub(ln, 0, off) : uc_dup("");
470 post = ln ? uc_sub(ln, off, -1) : uc_dup("\n");
471 rep = led_input(pref, post, &row, &col);
472 if (rep) {
473 if (cmd != 'o' && cmd != 'O')
474 lbuf_rm(xb, xrow, xrow + 1);
475 lbuf_put(xb, xrow, rep);
476 xrow += row;
477 xcol = col;
478 free(rep);
480 free(pref);
481 free(post);
484 static void vc_put(int cmd, int cnt)
486 int lnmode;
487 char *ln;
488 char *buf = reg_get(0, &lnmode);
489 struct sbuf *sb;
490 int off;
491 int i;
492 if (!buf)
493 return;
494 ln = lnmode ? NULL : lbuf_get(xb, xrow);
495 off = ln ? ren_insertionoffset(ln, xcol, cmd == 'P') : 0;
496 if (cmd == 'p' && !ln)
497 xrow++;
498 sb = sbuf_make();
499 if (ln) {
500 char *s = uc_sub(ln, 0, off);
501 sbuf_str(sb, s);
502 free(s);
504 for (i = 0; i < MAX(cnt, 1); i++)
505 sbuf_str(sb, buf);
506 if (ln) {
507 char *s = uc_sub(ln, off, -1);
508 sbuf_str(sb, s);
509 free(s);
511 if (ln)
512 lbuf_rm(xb, xrow, xrow + 1);
513 lbuf_put(xb, xrow, sbuf_buf(sb));
514 sbuf_free(sb);
518 static void vi(void)
520 int mark;
521 term_init();
522 xtop = 0;
523 xrow = 0;
524 lbuf_eol(xb, &xrow, &xcol, -1);
525 vi_draw();
526 term_pos(xrow, xcol);
527 while (!xquit) {
528 int redraw = 0;
529 int orow = xrow;
530 int pre1, mv;
531 if ((pre1 = vi_prefix()) < 0)
532 continue;
533 if ((mv = vi_motionln(&xrow, 0, pre1, 0))) {
534 if (strchr("\'GHML", mv))
535 lbuf_mark(xb, '\'', orow);
536 if (!strchr("jk", mv))
537 lbuf_postindents(xb, &xrow, &xcol);
538 } else if (!vi_motion(&xrow, &xcol, pre1, 0)) {
539 int c = vi_read();
540 int z;
541 if (c <= 0)
542 continue;
543 switch (c) {
544 case 'u':
545 lbuf_undo(xb);
546 redraw = 1;
547 break;
548 case TERMCTRL('b'):
549 xtop = MAX(0, xtop - xrows + 1);
550 xrow = xtop + xrows - 1;
551 lbuf_postindents(xb, &xrow, &xcol);
552 redraw = 1;
553 break;
554 case TERMCTRL('f'):
555 if (lbuf_len(xb))
556 xtop = MIN(lbuf_len(xb) - 1, xtop + xrows - 1);
557 else
558 xtop = 0;
559 xrow = xtop;
560 lbuf_postindents(xb, &xrow, &xcol);
561 redraw = 1;
562 break;
563 case TERMCTRL('r'):
564 lbuf_redo(xb);
565 redraw = 1;
566 break;
567 case ':':
568 term_pos(xrows, 0);
569 term_kill();
570 ex_command(NULL);
571 if (xquit)
572 continue;
573 redraw = 1;
574 break;
575 case 'c':
576 case 'd':
577 case 'y':
578 vc_motion(c, pre1);
579 redraw = 1;
580 break;
581 case 'i':
582 case 'I':
583 case 'a':
584 case 'A':
585 case 'o':
586 case 'O':
587 vc_insert(c);
588 redraw = 1;
589 break;
590 case 'm':
591 if ((mark = vi_read()) > 0 && isalpha(mark))
592 lbuf_mark(xb, mark, xrow);
593 break;
594 case 'p':
595 case 'P':
596 vc_put(c, pre1);
597 redraw = 1;
598 break;
599 case 'z':
600 z = vi_read();
601 switch (z) {
602 case '\n':
603 xtop = pre1 ? pre1 : xrow;
604 break;
605 case '.':
606 xtop = MAX(0, (pre1 ? pre1 : xrow) - xrows / 2);
607 break;
608 case '-':
609 xtop = MAX(0, (pre1 ? pre1 : xrow) - xrows + 1);
610 break;
611 case 'l':
612 case 'r':
613 case 'L':
614 case 'R':
615 xdir = z;
616 break;
618 redraw = 1;
619 break;
620 default:
621 continue;
624 if (xrow < 0 || xrow >= lbuf_len(xb))
625 xrow = lbuf_len(xb) ? lbuf_len(xb) - 1 : 0;
626 if (xrow < xtop || xrow >= xtop + xrows) {
627 xtop = xrow < xtop ? xrow : MAX(0, xrow - xrows + 1);
628 redraw = 1;
630 if (redraw)
631 vi_draw();
632 term_pos(xrow - xtop, ren_cursor(lbuf_get(xb, xrow), xcol));
633 lbuf_undomark(xb);
635 term_pos(xrows, 0);
636 term_kill();
637 term_done();
640 int main(int argc, char *argv[])
642 int visual = 1;
643 char ecmd[PATHLEN];
644 int i;
645 xb = lbuf_make();
646 for (i = 1; i < argc && argv[i][0] == '-'; i++) {
647 if (argv[i][1] == 's')
648 xled = 0;
649 if (argv[i][1] == 'e')
650 visual = 0;
651 if (argv[i][1] == 'v')
652 visual = 1;
654 if (i < argc) {
655 snprintf(ecmd, PATHLEN, "e %s", argv[i]);
656 ex_command(ecmd);
658 if (visual)
659 vi();
660 else
661 ex();
662 lbuf_free(xb);
663 reg_done();
664 return 0;