1 /* vi.c - You can't spell "evil" without "vi".
2 *
3 * Copyright 2015 Rob Landley <rob@landley.net>
4 * Copyright 2019 Jarno Mäkipää <jmakip87@gmail.com>
5 *
6 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html
7
8 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
9
10 config VI
11 bool "vi"
12 default n
13 help
14 usage: vi [-s SCRIPT] FILE
15
16 Visual text editor. Predates keyboards with standardized cursor keys.
17 If you don't know how to use it, hit the ESC key, type :q! and press ENTER.
18
19 -s run SCRIPT of commands on FILE
20
21 vi mode commands:
22
23 [count][cmd][motion]
24 cmd: c d y
25 motion: 0 b e G H h j k L l M w $ f F
26
27 [count][cmd]
28 cmd: D I J O n o p x dd yy
29
30 [cmd]
31 cmd: / ? : A a i CTRL_D CTRL_B CTRL_E CTRL_F CTRL_Y \e \b
32
33 ex mode commands:
34
35 [cmd]
36 \b \e \n w wq q! 'set list' 'set nolist' d $ % g v
37 */
38 #define FOR_vi
39 #include "toys.h"
40 #define CTL(a) a-'@'
41
42 GLOBALS(
43 char *s;
44
45 char *filename;
46 int vi_mode, tabstop, list;
47 int cur_col, cur_row, scr_row;
48 int drawn_row, drawn_col;
49 int count0, count1, vi_mov_flag;
50 unsigned screen_height, screen_width;
51 char vi_reg, *last_search;
52 struct str_line {
53 int alloc;
54 int len;
55 char *data;
56 } *il;
57 size_t screen, cursor; //offsets
58 //yank buffer
59 struct yank_buf {
60 char reg;
61 int alloc;
62 char* data;
63 } yank;
64
65 size_t filesize;
66 // mem_block contains RO data that is either original file as mmap
67 // or heap allocated inserted data
68 struct block_list {
69 struct block_list *next, *prev;
70 struct mem_block {
71 size_t size;
72 size_t len;
73 enum alloc_flag {
74 MMAP, //can be munmap() before exit()
75 HEAP, //can be free() before exit()
76 STACK, //global or stack perhaps toybuf
77 } alloc;
78 const char *data;
79 } *node;
80 } *text;
81
82 // slices do not contain actual allocated data but slices of data in mem_block
83 // when file is first opened it has only one slice.
84 // after inserting data into middle new mem_block is allocated for insert data
85 // and 3 slices are created, where first and last slice are pointing to original
86 // mem_block with offsets, and middle slice is pointing to newly allocated block
87 // When deleting, data is not freed but mem_blocks are sliced more such way that
88 // deleted data left between 2 slices
89 struct slice_list {
90 struct slice_list *next, *prev;
91 struct slice {
92 size_t len;
93 const char *data;
94 } *node;
95 } *slices;
96 )
97
98 static const char *blank = " \n\r\t";
99 static const char *specials = ",.:;=-+*/(){}<>[]!@#$%^&|\\?\"\'";
100
101 //get utf8 length and width at same time
utf8_lnw(int * width,char * s,int bytes)102 static int utf8_lnw(int *width, char *s, int bytes)
103 {
104 unsigned wc;
105 int length = 1;
106
107 if (*s == '\t') *width = TT.tabstop;
108 else {
109 length = utf8towc(&wc, s, bytes);
110 if (length < 1) length = 0, *width = 0;
111 else *width = wcwidth(wc);
112 }
113 return length;
114 }
115
utf8_dec(char key,char * utf8_scratch,int * sta_p)116 static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
117 {
118 int len = 0;
119 char *c = utf8_scratch;
120 c[*sta_p] = key;
121 if (!(*sta_p)) *c = key;
122 if (*c < 0x7F) { *sta_p = 1; return 1; }
123 if ((*c & 0xE0) == 0xc0) len = 2;
124 else if ((*c & 0xF0) == 0xE0 ) len = 3;
125 else if ((*c & 0xF8) == 0xF0 ) len = 4;
126 else {*sta_p = 0; return 0; }
127
128 (*sta_p)++;
129
130 if (*sta_p == 1) return 0;
131 if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
132
133 if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
134
135 return 0;
136 }
137
utf8_last(char * str,int size)138 static char* utf8_last(char* str, int size)
139 {
140 char* end = str+size;
141 int pos = size, len, width = 0;
142
143 for (;pos >= 0; end--, pos--) {
144 len = utf8_lnw(&width, end, size-pos);
145 if (len && width) return end;
146 }
147 return 0;
148 }
149
dlist_add_before(struct double_list ** head,struct double_list ** list,char * data)150 struct double_list *dlist_add_before(struct double_list **head,
151 struct double_list **list, char *data)
152 {
153 struct double_list *new = xmalloc(sizeof(struct double_list));
154 new->data = data;
155 if (*list == *head) *head = new;
156
157 dlist_add_nomalloc(list, new);
158 return new;
159 }
160
dlist_add_after(struct double_list ** head,struct double_list ** list,char * data)161 struct double_list *dlist_add_after(struct double_list **head,
162 struct double_list **list, char *data)
163 {
164 struct double_list *new = xmalloc(sizeof(struct double_list));
165 new->data = data;
166
167 if (*list) {
168 new->prev = *list;
169 new->next = (*list)->next;
170 (*list)->next->prev = new;
171 (*list)->next = new;
172 } else *head = *list = new->next = new->prev = new;
173 return new;
174 }
175
176 // str must be already allocated
177 // ownership of allocated data is moved
178 // data, pre allocated data
179 // offset, offset in whole text
180 // size, data allocation size of given data
181 // len, length of the string
182 // type, define allocation type for cleanup purposes at app exit
insert_str(const char * data,size_t offset,size_t size,size_t len,enum alloc_flag type)183 static int insert_str(const char *data, size_t offset, size_t size, size_t len,
184 enum alloc_flag type)
185 {
186 struct mem_block *b = xmalloc(sizeof(struct mem_block));
187 struct slice *next = xmalloc(sizeof(struct slice));
188 struct slice_list *s = TT.slices;
189
190 b->size = size;
191 b->len = len;
192 b->alloc = type;
193 b->data = data;
194 next->len = len;
195 next->data = data;
196
197 //mem blocks can be just added unordered
198 TT.text = (struct block_list *)dlist_add((struct double_list **)&TT.text,
199 (char *)b);
200
201 if (!s) {
202 TT.slices = (struct slice_list *)dlist_add(
203 (struct double_list **)&TT.slices,
204 (char *)next);
205 } else {
206 size_t pos = 0;
207 //search insertation point for slice
208 do {
209 if (pos<=offset && pos+s->node->len>offset) break;
210 pos += s->node->len;
211 s = s->next;
212 if (s == TT.slices) return -1; //error out of bounds
213 } while (1);
214 //need to cut previous slice into 2 since insert is in middle
215 if (pos+s->node->len>offset && pos!=offset) {
216 struct slice *tail = xmalloc(sizeof(struct slice));
217 tail->len = s->node->len-(offset-pos);
218 tail->data = s->node->data+(offset-pos);
219 s->node->len = offset-pos;
220 //pos = offset;
221 s = (struct slice_list *)dlist_add_after(
222 (struct double_list **)&TT.slices,
223 (struct double_list **)&s,
224 (char *)tail);
225
226 s = (struct slice_list *)dlist_add_before(
227 (struct double_list **)&TT.slices,
228 (struct double_list **)&s,
229 (char *)next);
230 } else if (pos==offset) {
231 // insert before
232 s = (struct slice_list *)dlist_add_before(
233 (struct double_list **)&TT.slices,
234 (struct double_list **)&s,
235 (char *)next);
236 } else {
237 // insert after
238 s = (void *)dlist_add_after((void *)&TT.slices, (void *)&s, (void *)next);
239 }
240 }
241 return 0;
242 }
243
244 // this will not free any memory
245 // will only create more slices depending on position
cut_str(size_t offset,size_t len)246 static int cut_str(size_t offset, size_t len)
247 {
248 struct slice_list *e, *s = TT.slices;
249 size_t end = offset+len;
250 size_t epos, spos = 0;
251
252 if (!s) return -1;
253
254 //find start and end slices
255 for (;;) {
256 if (spos<=offset && spos+s->node->len>offset) break;
257 spos += s->node->len;
258 s = s->next;
259
260 if (s == TT.slices) return -1; //error out of bounds
261 }
262
263 for (e = s, epos = spos; ; ) {
264 if (epos<=end && epos+e->node->len>end) break;
265 epos += e->node->len;
266 e = e->next;
267
268 if (e == TT.slices) return -1; //error out of bounds
269 }
270
271 for (;;) {
272 if (spos == offset && ( end >= spos+s->node->len)) {
273 //cut full
274 spos += s->node->len;
275 offset += s->node->len;
276 s = dlist_pop(&s);
277 if (s == TT.slices) TT.slices = s->next;
278
279 } else if (spos < offset && ( end >= spos+s->node->len)) {
280 //cut end
281 size_t clip = s->node->len - (offset - spos);
282 offset = spos+s->node->len;
283 spos += s->node->len;
284 s->node->len -= clip;
285 } else if (spos == offset && s == e) {
286 //cut begin
287 size_t clip = end - offset;
288 s->node->len -= clip;
289 s->node->data += clip;
290 break;
291 } else {
292 //cut middle
293 struct slice *tail = xmalloc(sizeof(struct slice));
294 size_t clip = end-offset;
295 tail->len = s->node->len-(offset-spos)-clip;
296 tail->data = s->node->data+(offset-spos)+clip;
297 s->node->len = offset-spos; //wrong?
298 s = (struct slice_list *)dlist_add_after(
299 (struct double_list **)&TT.slices,
300 (struct double_list **)&s,
301 (char *)tail);
302 break;
303 }
304 if (s == e) break;
305
306 s = s->next;
307 }
308
309 return 0;
310 }
modified()311 static int modified()
312 {
313 if (TT.text->next != TT.text->prev) return 1;
314 if (TT.slices->next != TT.slices->prev) return 1;
315 if (!TT.text || !TT.slices) return 0;
316 if (!TT.text->node || !TT.slices->node) return 0;
317 if (TT.text->node->alloc != MMAP) return 1;
318 if (TT.text->node->len != TT.slices->node->len) return 1;
319 if (!TT.text->node->len) return 1;
320 return 0;
321 }
322
323 //find offset position in slices
slice_offset(size_t * start,size_t offset)324 static struct slice_list *slice_offset(size_t *start, size_t offset)
325 {
326 struct slice_list *s = TT.slices;
327 size_t spos = 0;
328
329 //find start
330 for ( ;s ; ) {
331 if (spos<=offset && spos+s->node->len>offset) break;
332
333 spos += s->node->len;
334 s = s->next;
335
336 if (s == TT.slices) s = 0; //error out of bounds
337 }
338 if (s) *start = spos;
339 return s;
340 }
341
text_strchr(size_t offset,char c)342 static size_t text_strchr(size_t offset, char c)
343 {
344 struct slice_list *s = TT.slices;
345 size_t epos, spos = 0;
346 int i = 0;
347
348 //find start
349 if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
350
351 i = offset-spos;
352 epos = spos+i;
353 do {
354 for (; i < s->node->len; i++, epos++)
355 if (s->node->data[i] == c) return epos;
356 s = s->next;
357 i = 0;
358 } while (s != TT.slices);
359
360 return SIZE_MAX;
361 }
362
text_strrchr(size_t offset,char c)363 static size_t text_strrchr(size_t offset, char c)
364 {
365 struct slice_list *s = TT.slices;
366 size_t epos, spos = 0;
367 int i = 0;
368
369 //find start
370 if (!(s = slice_offset(&spos, offset))) return SIZE_MAX;
371
372 i = offset-spos;
373 epos = spos+i;
374 do {
375 for (; i >= 0; i--, epos--)
376 if (s->node->data[i] == c) return epos;
377 s = s->prev;
378 i = s->node->len-1;
379 } while (s != TT.slices->prev); //tail
380
381 return SIZE_MAX;
382 }
383
text_filesize()384 static size_t text_filesize()
385 {
386 struct slice_list *s = TT.slices;
387 size_t pos = 0;
388
389 if (s) do {
390 pos += s->node->len;
391 s = s->next;
392 } while (s != TT.slices);
393
394 return pos;
395 }
396
text_count(size_t start,size_t end,char c)397 static int text_count(size_t start, size_t end, char c)
398 {
399 struct slice_list *s = TT.slices;
400 size_t i, count = 0, spos = 0;
401 if (!(s = slice_offset(&spos, start))) return 0;
402 i = start-spos;
403 if (s) do {
404 for (; i < s->node->len && spos+i<end; i++)
405 if (s->node->data[i] == c) count++;
406 if (spos+i>=end) return count;
407
408 spos += s->node->len;
409 i = 0;
410 s = s->next;
411
412 } while (s != TT.slices);
413
414 return count;
415 }
416
text_byte(size_t offset)417 static char text_byte(size_t offset)
418 {
419 struct slice_list *s = TT.slices;
420 size_t spos = 0;
421
422 //find start
423 if (!(s = slice_offset(&spos, offset))) return 0;
424 return s->node->data[offset-spos];
425 }
426
427 //utf-8 codepoint -1 if not valid, 0 if out_of_bounds, len if valid
428 //copies data to dest if dest is not 0
text_codepoint(char * dest,size_t offset)429 static int text_codepoint(char *dest, size_t offset)
430 {
431 char scratch[8] = {0};
432 int state = 0, finished = 0;
433
434 for (;!(finished = utf8_dec(text_byte(offset), scratch, &state)); offset++)
435 if (!state) return -1;
436
437 if (!finished && !state) return -1;
438 if (dest) memcpy(dest, scratch, 8);
439
440 return strlen(scratch);
441 }
442
text_sol(size_t offset)443 static size_t text_sol(size_t offset)
444 {
445 size_t pos;
446
447 if (!TT.filesize || !offset) return 0;
448 else if (TT.filesize <= offset) return TT.filesize-1;
449 else if ((pos = text_strrchr(offset-1, '\n')) == SIZE_MAX) return 0;
450 else if (pos < offset) return pos+1;
451 return offset;
452 }
453
text_eol(size_t offset)454 static size_t text_eol(size_t offset)
455 {
456 if (!TT.filesize) offset = 1;
457 else if (TT.filesize <= offset) return TT.filesize-1;
458 else if ((offset = text_strchr(offset, '\n')) == SIZE_MAX)
459 return TT.filesize-1;
460 return offset;
461 }
462
text_nsol(size_t offset)463 static size_t text_nsol(size_t offset)
464 {
465 offset = text_eol(offset);
466 if (text_byte(offset) == '\n') offset++;
467 if (offset >= TT.filesize) offset--;
468 return offset;
469 }
470
text_psol(size_t offset)471 static size_t text_psol(size_t offset)
472 {
473 offset = text_sol(offset);
474 if (offset) offset--;
475 if (offset && text_byte(offset-1) != '\n') offset = text_sol(offset-1);
476 return offset;
477 }
478
text_getline(char * dest,size_t offset,size_t max_len)479 static size_t text_getline(char *dest, size_t offset, size_t max_len)
480 {
481 struct slice_list *s = TT.slices;
482 size_t end, spos = 0;
483 int i, j = 0;
484
485 if (dest) *dest = 0;
486
487 if (!s) return 0;
488 if ((end = text_strchr(offset, '\n')) == SIZE_MAX)
489 if ((end = TT.filesize) > offset+max_len) return 0;
490
491 //find start
492 if (!(s = slice_offset(&spos, offset))) return 0;
493
494 i = offset-spos;
495 j = end-offset+1;
496 if (dest) do {
497 for (; i < s->node->len && j; i++, j--, dest++)
498 *dest = s->node->data[i];
499 s = s->next;
500 i = 0;
501 } while (s != TT.slices && j);
502
503 if (dest) *dest = 0;
504
505 return end-offset;
506 }
507
508 // copying is needed when file has lot of inserts that are
509 // just few char long, but not always. Advanced search should
510 // check big slices directly and just copy edge cases.
511 // Also this is only line based search multiline
512 // and regexec should be done instead.
text_strstr(size_t offset,char * str,int dir)513 static size_t text_strstr(size_t offset, char *str, int dir)
514 {
515 size_t bytes, pos = offset;
516 char *s = 0;
517
518 do {
519 bytes = text_getline(toybuf, pos, ARRAY_LEN(toybuf));
520 if (!bytes) pos += (dir ? 1 : -1); //empty line
521 else if ((s = strstr(toybuf, str))) return pos+(s-toybuf);
522 else {
523 if (!dir) pos -= bytes;
524 else pos += bytes;
525 }
526 } while (pos < (dir ? 0 : TT.filesize));
527
528 return SIZE_MAX;
529 }
530
block_list_free(void * node)531 static void block_list_free(void *node)
532 {
533 struct block_list *d = node;
534
535 if (d->node->alloc == HEAP) free((void *)d->node->data);
536 else if (d->node->alloc == MMAP) munmap((void *)d->node->data, d->node->size);
537
538 free(d->node);
539 free(d);
540 }
541
show_error(char * fmt,...)542 static void show_error(char *fmt, ...)
543 {
544 va_list va;
545
546 printf("\a\e[%dH\e[41m\e[37m\e[K\e[1m", TT.screen_height+1);
547 va_start(va, fmt);
548 vprintf(fmt, va);
549 va_end(va);
550 printf("\e[0m");
551 fflush(0);
552 xferror(stdout);
553
554 // TODO: better integration with status line: keep
555 // message until next operation.
556 (void)getchar();
557 }
558
linelist_unload()559 static void linelist_unload()
560 {
561 llist_traverse((void *)TT.slices, llist_free_double);
562 llist_traverse((void *)TT.text, block_list_free);
563 TT.slices = 0, TT.text = 0;
564 }
565
linelist_load(char * filename,int ignore_missing)566 static void linelist_load(char *filename, int ignore_missing)
567 {
568 int fd;
569 long long size;
570
571 if (!filename) filename = TT.filename;
572 if (!filename) {
573 // `vi` with no arguments creates a new unnamed file.
574 insert_str(xstrdup("\n"), 0, 1, 1, HEAP);
575 return;
576 }
577
578 fd = open(filename, O_RDONLY);
579 if (fd == -1) {
580 if (!ignore_missing)
581 show_error("Couldn't open \"%s\" for reading: %s", filename,
582 strerror(errno));
583 insert_str(xstrdup("\n"), 0, 1, 1, HEAP);
584 return;
585 }
586
587 size = fdlength(fd);
588 if (size > 0) {
589 insert_str(xmmap(0,size,PROT_READ,MAP_SHARED,fd,0), 0, size, size, MMAP);
590 TT.filesize = text_filesize();
591 } else if (!size) insert_str(xstrdup("\n"), 0, 1, 1, HEAP);
592 xclose(fd);
593 }
594
write_file(char * filename)595 static int write_file(char *filename)
596 {
597 struct slice_list *s = TT.slices;
598 struct stat st;
599 int fd = 0;
600
601 if (!modified()) show_error("Not modified");
602 if (!filename) filename = TT.filename;
603 if (!filename) {
604 show_error("No file name");
605 return -1;
606 }
607
608 if (stat(filename, &st) == -1) st.st_mode = 0644;
609
610 sprintf(toybuf, "%s.swp", filename);
611
612 if ((fd = open(toybuf, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode)) == -1) {
613 show_error("Couldn't open \"%s\" for writing: %s", toybuf, strerror(errno));
614 return -1;
615 }
616
617 if (s) {
618 do {
619 xwrite(fd, (void *)s->node->data, s->node->len);
620 s = s->next;
621 } while (s != TT.slices);
622 }
623
624 linelist_unload();
625
626 xclose(fd);
627 if (!rename(toybuf, filename)) return 1;
628 linelist_load(filename, 0);
629 return 0;
630 }
631
632 // jump into valid offset index
633 // and valid utf8 codepoint
check_cursor_bounds()634 static void check_cursor_bounds()
635 {
636 char buf[8] = {0};
637 int len, width = 0;
638
639 if (!TT.filesize) TT.cursor = 0;
640 for (;;) {
641 if (TT.cursor < 1) {
642 TT.cursor = 0;
643 return;
644 } else if (TT.cursor >= TT.filesize-1) {
645 TT.cursor = TT.filesize-1;
646 return;
647 }
648 // if we are not in valid data try jump over
649 if ((len = text_codepoint(buf, TT.cursor)) < 1) TT.cursor--;
650 else if (utf8_lnw(&width, buf, len) && width) break;
651 else TT.cursor--; //combine char jump over
652 }
653 }
654
655 // TT.vi_mov_flag is used for special cases when certain move
656 // acts differently depending is there DELETE/YANK or NOP
657 // Also commands such as G does not default to count0=1
658 // 0x1 = Command needs argument (f,F,r...)
659 // 0x2 = Move 1 right on yank/delete/insert (e, $...)
660 // 0x4 = yank/delete last line fully
661 // 0x10000000 = redraw after cursor needed
662 // 0x20000000 = full redraw needed
663 // 0x40000000 = count0 not given
664 // 0x80000000 = move was reverse
665
666 // TODO rewrite the logic, difficulties counting lines
667 // and with big files scroll should not rely in knowing
668 // absoluteline numbers
adjust_screen_buffer()669 static void adjust_screen_buffer()
670 {
671 size_t c, s;
672 TT.cur_row = 0, TT.scr_row = 0;
673 if (!TT.cursor) {
674 TT.screen = 0;
675 TT.vi_mov_flag = 0x20000000;
676 return;
677 } else if (TT.screen > (1<<18) || TT.cursor > (1<<18)) {
678 //give up, file is big, do full redraw
679
680 TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
681 TT.vi_mov_flag = 0x20000000;
682 return;
683 }
684
685 s = text_count(0, TT.screen, '\n');
686 c = text_count(0, TT.cursor, '\n');
687 if (s >= c) {
688 TT.screen = text_strrchr(TT.cursor-1, '\n')+1;
689 s = c;
690 TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
691 } else {
692 int distance = c-s+1;
693 if (distance > (int)TT.screen_height) {
694 int n, adj = distance-TT.screen_height;
695 TT.vi_mov_flag = 0x20000000; //TODO I disabled scroll
696 for (;adj; adj--, s++)
697 if ((n = text_strchr(TT.screen, '\n'))+1 > TT.screen)
698 TT.screen = n+1;
699 }
700 }
701
702 TT.scr_row = s;
703 TT.cur_row = c;
704 }
705
706 // TODO search yank buffer by register
707 // TODO yanks could be separate slices so no need to copy data
708 // now only supports default register
vi_yank(char reg,size_t from,int flags)709 static int vi_yank(char reg, size_t from, int flags)
710 {
711 size_t start = from, end = TT.cursor;
712 char *str;
713
714 memset(TT.yank.data, 0, TT.yank.alloc);
715 if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
716 else TT.cursor = start; //yank moves cursor to left pos always?
717
718 if (TT.yank.alloc < end-from) {
719 size_t new_bounds = (1+end-from)/1024;
720 new_bounds += ((1+end-from)%1024) ? 1 : 0;
721 new_bounds *= 1024;
722 TT.yank.data = xrealloc(TT.yank.data, new_bounds);
723 TT.yank.alloc = new_bounds;
724 }
725
726 //this is naive copy
727 for (str = TT.yank.data ; start<end; start++, str++) *str = text_byte(start);
728
729 *str = 0;
730
731 return 1;
732 }
733
vi_delete(char reg,size_t from,int flags)734 static int vi_delete(char reg, size_t from, int flags)
735 {
736 size_t start = from, end = TT.cursor;
737
738 vi_yank(reg, from, flags);
739
740 if (TT.vi_mov_flag&0x80000000) start = TT.cursor, end = from;
741
742 //pre adjust cursor move one right until at next valid rune
743 if (TT.vi_mov_flag&2) {
744 //TODO
745 }
746 //do slice cut
747 cut_str(start, end-start);
748
749 //cursor is at start at after delete
750 TT.cursor = start;
751 TT.filesize = text_filesize();
752 //find line start by strrchr(/n) ++
753 //set cur_col with crunch_n_str maybe?
754 TT.vi_mov_flag |= 0x30000000;
755
756 return 1;
757 }
758
vi_change(char reg,size_t to,int flags)759 static int vi_change(char reg, size_t to, int flags)
760 {
761 vi_delete(reg, to, flags);
762 TT.vi_mode = 2;
763 return 1;
764 }
765
cur_left(int count0,int count1,char * unused)766 static int cur_left(int count0, int count1, char *unused)
767 {
768 int count = count0*count1;
769
770 TT.vi_mov_flag |= 0x80000000;
771 for (;count && TT.cursor; count--) {
772 TT.cursor--;
773 if (text_byte(TT.cursor) == '\n') TT.cursor++;
774 check_cursor_bounds();
775 }
776 return 1;
777 }
778
cur_right(int count0,int count1,char * unused)779 static int cur_right(int count0, int count1, char *unused)
780 {
781 int count = count0*count1, len, width = 0;
782 char buf[8] = {0};
783
784 for (;count; count--) {
785 len = text_codepoint(buf, TT.cursor);
786
787 if (*buf == '\n') break;
788 else if (len > 0) TT.cursor += len;
789 else TT.cursor++;
790
791 for (;TT.cursor < TT.filesize;) {
792 if ((len = text_codepoint(buf, TT.cursor)) < 1) {
793 TT.cursor++; //we are not in valid data try jump over
794 continue;
795 }
796
797 if (utf8_lnw(&width, buf, len) && width) break;
798 else TT.cursor += len;
799 }
800 }
801 check_cursor_bounds();
802 return 1;
803 }
804
805 //TODO column shift
cur_up(int count0,int count1,char * unused)806 static int cur_up(int count0, int count1, char *unused)
807 {
808 int count = count0*count1;
809
810 for (;count--;) TT.cursor = text_psol(TT.cursor);
811 TT.vi_mov_flag |= 0x80000000;
812 check_cursor_bounds();
813 return 1;
814 }
815
816 //TODO column shift
cur_down(int count0,int count1,char * unused)817 static int cur_down(int count0, int count1, char *unused)
818 {
819 int count = count0*count1;
820
821 for (;count--;) TT.cursor = text_nsol(TT.cursor);
822 check_cursor_bounds();
823 return 1;
824 }
825
vi_H(int count0,int count1,char * unused)826 static int vi_H(int count0, int count1, char *unused)
827 {
828 TT.cursor = text_sol(TT.screen);
829 return 1;
830 }
831
vi_L(int count0,int count1,char * unused)832 static int vi_L(int count0, int count1, char *unused)
833 {
834 TT.cursor = text_sol(TT.screen);
835 cur_down(TT.screen_height-1, 1, 0);
836 return 1;
837 }
838
vi_M(int count0,int count1,char * unused)839 static int vi_M(int count0, int count1, char *unused)
840 {
841 TT.cursor = text_sol(TT.screen);
842 cur_down(TT.screen_height/2, 1, 0);
843 return 1;
844 }
845
search_str(char * s,int direction)846 static int search_str(char *s, int direction)
847 {
848 size_t pos = text_strstr(TT.cursor+1, s, direction);
849
850 if (TT.last_search != s) {
851 free(TT.last_search);
852 TT.last_search = xstrdup(s);
853 }
854
855 if (pos != SIZE_MAX) TT.cursor = pos;
856 check_cursor_bounds();
857 return 0;
858 }
859
vi_yy(char reg,int count0,int count1)860 static int vi_yy(char reg, int count0, int count1)
861 {
862 size_t history = TT.cursor;
863 size_t pos = text_sol(TT.cursor); //go left to first char on line
864 TT.vi_mov_flag |= 4;
865
866 for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
867
868 vi_yank(reg, pos, 0);
869
870 TT.cursor = history;
871 return 1;
872 }
873
vi_dd(char reg,int count0,int count1)874 static int vi_dd(char reg, int count0, int count1)
875 {
876 size_t pos = text_sol(TT.cursor); //go left to first char on line
877 TT.vi_mov_flag |= 0x30000000;
878
879 for (;count0; count0--) TT.cursor = text_nsol(TT.cursor);
880
881 if (pos == TT.cursor && TT.filesize) pos--;
882 vi_delete(reg, pos, 0);
883 check_cursor_bounds();
884 return 1;
885 }
886
vi_x(char reg,int count0,int count1)887 static int vi_x(char reg, int count0, int count1)
888 {
889 size_t from = TT.cursor;
890
891 if (text_byte(TT.cursor) == '\n') {
892 cur_left(count0-1, 1, 0);
893 }
894 else {
895 cur_right(count0-1, 1, 0);
896 if (text_byte(TT.cursor) == '\n') TT.vi_mov_flag |= 2;
897 else cur_right(1, 1, 0);
898 }
899
900 vi_delete(reg, from, 0);
901 check_cursor_bounds();
902 return 1;
903 }
904
backspace(char reg,int count0,int count1)905 static int backspace(char reg, int count0, int count1)
906 {
907 size_t from = 0;
908 size_t to = TT.cursor;
909 cur_left(1, 1, 0);
910 from = TT.cursor;
911 if (from != to)
912 vi_delete(reg, to, 0);
913 check_cursor_bounds();
914 return 1;
915 }
916
vi_movw(int count0,int count1,char * unused)917 static int vi_movw(int count0, int count1, char *unused)
918 {
919 int count = count0*count1;
920 while (count--) {
921 char c = text_byte(TT.cursor);
922 do {
923 if (TT.cursor > TT.filesize-1) break;
924 //if at empty jump to non empty
925 if (c == '\n') {
926 if (++TT.cursor > TT.filesize-1) break;
927 if ((c = text_byte(TT.cursor)) == '\n') break;
928 continue;
929 } else if (strchr(blank, c)) do {
930 if (++TT.cursor > TT.filesize-1) break;
931 c = text_byte(TT.cursor);
932 } while (strchr(blank, c));
933 //if at special jump to non special
934 else if (strchr(specials, c)) do {
935 if (++TT.cursor > TT.filesize-1) break;
936 c = text_byte(TT.cursor);
937 } while (strchr(specials, c));
938 //else jump to empty or spesial
939 else do {
940 if (++TT.cursor > TT.filesize-1) break;
941 c = text_byte(TT.cursor);
942 } while (c && !strchr(blank, c) && !strchr(specials, c));
943
944 } while (strchr(blank, c) && c != '\n'); //never stop at empty
945 }
946 check_cursor_bounds();
947 return 1;
948 }
949
vi_movb(int count0,int count1,char * unused)950 static int vi_movb(int count0, int count1, char *unused)
951 {
952 int count = count0*count1;
953 int type = 0;
954 char c;
955 while (count--) {
956 c = text_byte(TT.cursor);
957 do {
958 if (!TT.cursor) break;
959 //if at empty jump to non empty
960 if (strchr(blank, c)) do {
961 if (!--TT.cursor) break;
962 c = text_byte(TT.cursor);
963 } while (strchr(blank, c));
964 //if at special jump to non special
965 else if (strchr(specials, c)) do {
966 if (!--TT.cursor) break;
967 type = 0;
968 c = text_byte(TT.cursor);
969 } while (strchr(specials, c));
970 //else jump to empty or spesial
971 else do {
972 if (!--TT.cursor) break;
973 type = 1;
974 c = text_byte(TT.cursor);
975 } while (!strchr(blank, c) && !strchr(specials, c));
976
977 } while (strchr(blank, c)); //never stop at empty
978 }
979 //find first
980 for (;TT.cursor; TT.cursor--) {
981 c = text_byte(TT.cursor-1);
982 if (type && !strchr(blank, c) && !strchr(specials, c)) break;
983 else if (!type && !strchr(specials, c)) break;
984 }
985
986 TT.vi_mov_flag |= 0x80000000;
987 check_cursor_bounds();
988 return 1;
989 }
990
vi_move(int count0,int count1,char * unused)991 static int vi_move(int count0, int count1, char *unused)
992 {
993 int count = count0*count1;
994 int type = 0;
995 char c;
996
997 if (count>1) vi_movw(count-1, 1, unused);
998
999 c = text_byte(TT.cursor);
1000 if (strchr(specials, c)) type = 1;
1001 TT.cursor++;
1002 for (;TT.cursor < TT.filesize-1; TT.cursor++) {
1003 c = text_byte(TT.cursor+1);
1004 if (!type && (strchr(blank, c) || strchr(specials, c))) break;
1005 else if (type && !strchr(specials, c)) break;
1006 }
1007
1008 TT.vi_mov_flag |= 2;
1009 check_cursor_bounds();
1010 return 1;
1011 }
1012
1013
i_insert(char * str,int len)1014 static void i_insert(char *str, int len)
1015 {
1016 if (!str || !len) return;
1017
1018 insert_str(xstrdup(str), TT.cursor, len, len, HEAP);
1019 TT.cursor += len;
1020 TT.filesize = text_filesize();
1021 TT.vi_mov_flag |= 0x30000000;
1022 }
1023
vi_zero(int count0,int count1,char * unused)1024 static int vi_zero(int count0, int count1, char *unused)
1025 {
1026 TT.cursor = text_sol(TT.cursor);
1027 TT.cur_col = 0;
1028 TT.vi_mov_flag |= 0x80000000;
1029 return 1;
1030 }
1031
vi_dollar(int count0,int count1,char * unused)1032 static int vi_dollar(int count0, int count1, char *unused)
1033 {
1034 size_t new = text_strchr(TT.cursor, '\n');
1035
1036 if (new != TT.cursor) {
1037 TT.cursor = new - 1;
1038 TT.vi_mov_flag |= 2;
1039 check_cursor_bounds();
1040 }
1041 return 1;
1042 }
1043
vi_eol()1044 static void vi_eol()
1045 {
1046 TT.cursor = text_strchr(TT.cursor, '\n');
1047 check_cursor_bounds();
1048 }
1049
ctrl_b()1050 static void ctrl_b()
1051 {
1052 int i;
1053
1054 for (i=0; i<TT.screen_height-2; ++i) {
1055 TT.screen = text_psol(TT.screen);
1056 // TODO: retain x offset.
1057 TT.cursor = text_psol(TT.screen);
1058 }
1059 }
1060
ctrl_d()1061 static void ctrl_d()
1062 {
1063 int i;
1064
1065 for (i=0; i<(TT.screen_height-2)/2; ++i) TT.screen = text_nsol(TT.screen);
1066 // TODO: real vi keeps the x position.
1067 if (TT.screen > TT.cursor) TT.cursor = TT.screen;
1068 }
1069
ctrl_f()1070 static void ctrl_f()
1071 {
1072 int i;
1073
1074 for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
1075 // TODO: real vi keeps the x position.
1076 if (TT.screen > TT.cursor) TT.cursor = TT.screen;
1077 }
1078
ctrl_e()1079 static void ctrl_e()
1080 {
1081 TT.screen = text_nsol(TT.screen);
1082 // TODO: real vi keeps the x position.
1083 if (TT.screen > TT.cursor) TT.cursor = TT.screen;
1084 }
1085
ctrl_y()1086 static void ctrl_y()
1087 {
1088 TT.screen = text_psol(TT.screen);
1089 // TODO: only if we're on the bottom line
1090 TT.cursor = text_psol(TT.cursor);
1091 // TODO: real vi keeps the x position.
1092 }
1093
1094 //TODO check register where to push from
vi_push(char reg,int count0,int count1)1095 static int vi_push(char reg, int count0, int count1)
1096 {
1097 //if row changes during push original cursor position is kept
1098 //vi inconsistancy
1099 //if yank ends with \n push is linemode else push in place+1
1100 size_t history = TT.cursor;
1101 char *start = TT.yank.data, *eol = strchr(start, '\n');
1102 if (strlen(start) == 0) return 1;
1103
1104 if (start[strlen(start)-1] == '\n') {
1105 if ((TT.cursor = text_strchr(TT.cursor, '\n')) == SIZE_MAX)
1106 TT.cursor = TT.filesize;
1107 else TT.cursor = text_nsol(TT.cursor);
1108 } else cur_right(1, 1, 0);
1109
1110 i_insert(start, strlen(start));
1111 if (eol) {
1112 TT.vi_mov_flag |= 0x10000000;
1113 TT.cursor = history;
1114 }
1115
1116 return 1;
1117 }
1118
vi_find_c(int count0,int count1,char * symbol)1119 static int vi_find_c(int count0, int count1, char *symbol)
1120 {
1121 //// int count = count0*count1;
1122 size_t pos = text_strchr(TT.cursor, *symbol);
1123 if (pos != SIZE_MAX) TT.cursor = pos;
1124 return 1;
1125 }
1126
vi_find_cb(int count0,int count1,char * symbol)1127 static int vi_find_cb(int count0, int count1, char *symbol)
1128 {
1129 // do backward search
1130 size_t pos = text_strrchr(TT.cursor, *symbol);
1131 if (pos != SIZE_MAX) TT.cursor = pos;
1132 return 1;
1133 }
1134
1135 //if count is not spesified should go to last line
vi_go(int count0,int count1,char * symbol)1136 static int vi_go(int count0, int count1, char *symbol)
1137 {
1138 size_t prev_cursor = TT.cursor;
1139 int count = count0*count1-1;
1140 TT.cursor = 0;
1141
1142 if (TT.vi_mov_flag&0x40000000 && (TT.cursor = TT.filesize) > 0)
1143 TT.cursor = text_sol(TT.cursor-1);
1144 else if (count) {
1145 size_t next = 0;
1146 for ( ;count && (next = text_strchr(next+1, '\n')) != SIZE_MAX; count--)
1147 TT.cursor = next;
1148 TT.cursor++;
1149 }
1150
1151 check_cursor_bounds(); //adjusts cursor column
1152 if (prev_cursor > TT.cursor) TT.vi_mov_flag |= 0x80000000;
1153
1154 return 1;
1155 }
1156
vi_o(char reg,int count0,int count1)1157 static int vi_o(char reg, int count0, int count1)
1158 {
1159 TT.cursor = text_eol(TT.cursor);
1160 insert_str(xstrdup("\n"), TT.cursor++, 1, 1, HEAP);
1161 TT.vi_mov_flag |= 0x30000000;
1162 TT.vi_mode = 2;
1163 return 1;
1164 }
1165
vi_O(char reg,int count0,int count1)1166 static int vi_O(char reg, int count0, int count1)
1167 {
1168 TT.cursor = text_psol(TT.cursor);
1169 return vi_o(reg, count0, count1);
1170 }
1171
vi_D(char reg,int count0,int count1)1172 static int vi_D(char reg, int count0, int count1)
1173 {
1174 size_t pos = TT.cursor;
1175 if (!count0) return 1;
1176 vi_eol();
1177 vi_delete(reg, pos, 0);
1178 if (--count0) vi_dd(reg, count0, 1);
1179
1180 check_cursor_bounds();
1181 return 1;
1182 }
1183
vi_I(char reg,int count0,int count1)1184 static int vi_I(char reg, int count0, int count1)
1185 {
1186 TT.cursor = text_sol(TT.cursor);
1187 TT.vi_mode = 2;
1188 return 1;
1189 }
1190
vi_join(char reg,int count0,int count1)1191 static int vi_join(char reg, int count0, int count1)
1192 {
1193 size_t next;
1194 while (count0--) {
1195 //just strchr(/n) and cut_str(pos, 1);
1196 if ((next = text_strchr(TT.cursor, '\n')) == SIZE_MAX) break;
1197 TT.cursor = next+1;
1198 vi_delete(reg, TT.cursor-1, 0);
1199 }
1200 return 1;
1201 }
1202
vi_find_next(char reg,int count0,int count1)1203 static int vi_find_next(char reg, int count0, int count1)
1204 {
1205 if (TT.last_search) search_str(TT.last_search, 1);
1206 return 1;
1207 }
1208
vi_find_prev(char reg,int count0,int count1)1209 static int vi_find_prev(char reg, int count0, int count1)
1210 {
1211 if (TT.last_search) search_str(TT.last_search, 0);
1212 return 1;
1213 }
1214
1215 //NOTES
1216 //vi-mode cmd syntax is
1217 //("[REG])[COUNT0]CMD[COUNT1](MOV)
1218 //where:
1219 //-------------------------------------------------------------
1220 //"[REG] is optional buffer where deleted/yanked text goes REG can be
1221 // atleast 0-9, a-z or default "
1222 //[COUNT] is optional multiplier for cmd execution if there is 2 COUNT
1223 // operations they are multiplied together
1224 //CMD is operation to be executed
1225 //(MOV) is movement operation, some CMD does not require MOV and some
1226 // have special cases such as dd, yy, also movements can work without
1227 // CMD
1228 //ex commands can be even more complicated than this....
1229
1230 //special cases without MOV and such
1231 struct vi_special_param {
1232 const char *cmd;
1233 int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1
1234 } vi_special[] = {
1235 {"D", &vi_D},
1236 {"I", &vi_I},
1237 {"J", &vi_join},
1238 {"O", &vi_O},
1239 {"N", &vi_find_prev},
1240 {"n", &vi_find_next},
1241 {"o", &vi_o},
1242 {"p", &vi_push},
1243 {"x", &vi_x},
1244 {"dd", &vi_dd},
1245 {"yy", &vi_yy},
1246 };
1247 //there is around ~47 vi moves, some of them need extra params such as f and '
1248 struct vi_mov_param {
1249 const char* mov;
1250 unsigned flags;
1251 int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params
1252 } vi_movs[] = {
1253 {"0", 0, &vi_zero},
1254 {"b", 0, &vi_movb},
1255 {"e", 0, &vi_move},
1256 {"G", 0, &vi_go},
1257 {"H", 0, &vi_H},
1258 {"h", 0, &cur_left},
1259 {"j", 0, &cur_down},
1260 {"k", 0, &cur_up},
1261 {"L", 0, &vi_L},
1262 {"l", 0, &cur_right},
1263 {"M", 0, &vi_M},
1264 {"w", 0, &vi_movw},
1265 {"$", 0, &vi_dollar},
1266 {"f", 1, &vi_find_c},
1267 {"F", 1, &vi_find_cb},
1268 };
1269 // change and delete unfortunately behave different depending on move command,
1270 // such as ce cw are same, but dw and de are not...
1271 // also dw stops at w position and cw seem to stop at e pos+1...
1272 // so after movement we need to possibly set up some flags before executing
1273 // command, and command needs to adjust...
1274 struct vi_cmd_param {
1275 const char* cmd;
1276 unsigned flags;
1277 int (*vi_cmd)(char, size_t, int);//REG,from,FLAGS
1278 } vi_cmds[] = {
1279 {"c", 1, &vi_change},
1280 {"d", 1, &vi_delete},
1281 {"y", 1, &vi_yank},
1282 };
1283
run_vi_cmd(char * cmd)1284 static int run_vi_cmd(char *cmd)
1285 {
1286 int i = 0, val = 0;
1287 char *cmd_e;
1288 int (*vi_cmd)(char, size_t, int) = 0, (*vi_mov)(int, int, char*) = 0;
1289
1290 TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0;
1291 TT.vi_reg = '"';
1292
1293 if (*cmd == '"') {
1294 cmd++;
1295 TT.vi_reg = *cmd++; //TODO check validity
1296 }
1297 errno = 0;
1298 val = strtol(cmd, &cmd_e, 10);
1299 if (errno || val == 0) val = 1, TT.vi_mov_flag |= 0x40000000;
1300 else cmd = cmd_e;
1301 TT.count0 = val;
1302
1303 for (i = 0; i < ARRAY_LEN(vi_special); i++)
1304 if (strstr(cmd, vi_special[i].cmd))
1305 return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1);
1306
1307 for (i = 0; i < ARRAY_LEN(vi_cmds); i++) {
1308 if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) {
1309 vi_cmd = vi_cmds[i].vi_cmd;
1310 cmd += strlen(vi_cmds[i].cmd);
1311 break;
1312 }
1313 }
1314 errno = 0;
1315 val = strtol(cmd, &cmd_e, 10);
1316 if (errno || val == 0) val = 1;
1317 else cmd = cmd_e;
1318 TT.count1 = val;
1319
1320 for (i = 0; i < ARRAY_LEN(vi_movs); i++) {
1321 if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) {
1322 vi_mov = vi_movs[i].vi_mov;
1323 TT.vi_mov_flag |= vi_movs[i].flags;
1324 cmd++;
1325 if (TT.vi_mov_flag&1 && !(*cmd)) return 0;
1326 break;
1327 }
1328 }
1329 if (vi_mov) {
1330 int prev_cursor = TT.cursor;
1331 if (vi_mov(TT.count0, TT.count1, cmd)) {
1332 if (vi_cmd) return (vi_cmd(TT.vi_reg, prev_cursor, TT.vi_mov_flag));
1333 else return 1;
1334 } else return 0; //return some error
1335 }
1336 return 0;
1337 }
1338
1339
1340 static void draw_page();
1341
get_endline(void)1342 static int get_endline(void)
1343 {
1344 int cln, rln;
1345
1346 draw_page();
1347 cln = TT.cur_row+1;
1348 run_vi_cmd("G");
1349 draw_page();
1350 rln = TT.cur_row+1;
1351 run_vi_cmd(xmprintf("%dG", cln));
1352
1353 return rln+1;
1354 }
1355
1356 // Return non-zero to exit.
run_ex_cmd(char * cmd)1357 static int run_ex_cmd(char *cmd)
1358 {
1359 int startline = 1, ofst = 0, endline;
1360
1361 if (*cmd == '/' || *cmd == '\?') search_str(cmd+1, *cmd == '/' ? 0 : 1);
1362 else if (*cmd == ':') {
1363 if (cmd[1] == 'q') {
1364 if (cmd[2] != '!' && modified())
1365 show_error("Unsaved changes (\"q!\" to ignore)");
1366 else return 1;
1367 } else if (!strncmp(cmd+1, "w ", 2)) write_file(&cmd[3]);
1368 else if (!strncmp(cmd+1, "wq", 2)) {
1369 if (write_file(0)) return 1;
1370 show_error("Unsaved changes (\"q!\" to ignore)");
1371 } else if (!strncmp(cmd+1, "w", 1)) write_file(0);
1372
1373 else if (!strncmp(cmd+1, "set list", sizeof("set list"))) {
1374 TT.list = 1;
1375 TT.vi_mov_flag |= 0x30000000;
1376 } else if (!strncmp(cmd+1, "set nolist", sizeof("set nolist"))) {
1377 TT.list = 0;
1378 TT.vi_mov_flag |= 0x30000000;
1379 }
1380
1381 else if (cmd[1] == 'd') {
1382 run_vi_cmd("dd");
1383 cur_up(1, 1, 0);
1384 } else if (cmd[1] == 'j') run_vi_cmd("J");
1385 else if (cmd[1] == 'g' || cmd[1] == 'v') {
1386 char *rgx = xmalloc(strlen(cmd));
1387 int el = get_endline(), ln = 0, vorg = (cmd[1] == 'v' ? REG_NOMATCH : 0);
1388 if (sscanf(cmd+2, "/%[^/]/%[^\ng]", rgx, cmd+1) == 2) {
1389 regex_t rgxc;
1390 if (!regcomp(&rgxc, rgx, 0)) {
1391 cmd[0] = ':';
1392
1393 for (; ln < el; ln++) {
1394 run_vi_cmd("yy");
1395 if (regexec(&rgxc, TT.yank.data, 0, 0, 0) == vorg) run_ex_cmd(cmd);
1396 cur_down(1, 1, 0);
1397 }
1398
1399 // Reset Frame
1400 TT.vi_mov_flag |= 0x30000000;
1401 }
1402 regfree(&rgxc);
1403 }
1404 free(rgx);
1405 }
1406
1407 // Line Ranges
1408 else if (cmd[1] >= '0' && cmd[1] <= '9') {
1409 if (strstr(cmd, ",")) {
1410 sscanf(cmd, ":%d,%d%[^\n]", &startline, &endline, cmd+2);
1411 ofst = 1;
1412 } else run_vi_cmd(xmprintf("%dG", atoi(cmd+1)));
1413 } else if (cmd[1] == '$') run_vi_cmd("G");
1414 else if (cmd[1] == '%') {
1415 endline = get_endline();
1416 ofst = 1;
1417 } else show_error("unknown command '%s'",cmd+1);
1418
1419 if (ofst) {
1420 int cline = TT.cur_row+1;
1421
1422 cmd[ofst] = ':';
1423 for (; startline <= endline; startline++) {
1424 run_ex_cmd(cmd+ofst);
1425 cur_down(1, 1, 0);
1426 }
1427 run_vi_cmd(xmprintf("%dG", cline));
1428 // Screen Reset
1429 TT.vi_mov_flag |= 0x30000000;
1430 }
1431 }
1432 return 0;
1433 }
1434
vi_crunch(FILE * out,int cols,int wc)1435 static int vi_crunch(FILE *out, int cols, int wc)
1436 {
1437 int ret = 0;
1438 if (wc < 32 && TT.list) {
1439 xputsn("\e[1m");
1440 ret = crunch_escape(out,cols,wc);
1441 xputsn("\e[m");
1442 } else if (wc == '\t') {
1443 if (out) {
1444 int i = TT.tabstop;
1445 for (;i--;) fputs(" ", out);
1446 }
1447 ret = TT.tabstop;
1448 } else if (wc == '\n') return 0;
1449 return ret;
1450 }
1451
1452 //crunch_str with n bytes restriction for printing substrings or
1453 //non null terminated strings
crunch_nstr(char ** str,int width,int n,FILE * out,char * escmore,int (* escout)(FILE * out,int cols,int wc))1454 static int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
1455 int (*escout)(FILE *out, int cols, int wc))
1456 {
1457 int columns = 0, col, bytes;
1458 char *start, *end;
1459 unsigned wc;
1460
1461 for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
1462 if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
1463 if (!escmore || wc>255 || !strchr(escmore, wc)) {
1464 if (width-columns<col) break;
1465 if (out) fwrite(end, bytes, 1, out);
1466
1467 continue;
1468 }
1469 }
1470
1471 if (bytes<1) {
1472 bytes = 1;
1473 wc = *end;
1474 }
1475 col = width-columns;
1476 if (col<1) break;
1477 if (escout) {
1478 if ((col = escout(out, col, wc))<0) break;
1479 } else if (out) fwrite(end, 1, bytes, out);
1480 }
1481 *str = end;
1482
1483 return columns;
1484 }
1485
draw_page()1486 static void draw_page()
1487 {
1488 unsigned y = 0;
1489 int x = 0, bytes = 0;
1490 char *line = 0, *end = 0;
1491 //screen coordinates for cursor
1492 int cy_scr = 0, cx_scr = 0;
1493 //variables used only for cursor handling
1494 int aw = 0, iw = 0, clip = 0, margin = 8, scroll = 0, redraw = 0, SSOL, SOL;
1495
1496 adjust_screen_buffer();
1497 //redraw = 3; //force full redraw
1498 redraw = (TT.vi_mov_flag & 0x30000000)>>28;
1499
1500 scroll = TT.drawn_row-TT.scr_row;
1501 if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
1502 else if (abs(scroll)>TT.screen_height/2) redraw = 3;
1503
1504 xputsn("\e[H"); // jump to top left
1505 if (redraw&2) xputsn("\e[2J\e[H"); //clear screen
1506 else if (scroll>0) printf("\e[%dL", scroll); //scroll up
1507 else if (scroll<0) printf("\e[%dM", -scroll); //scroll down
1508
1509 SOL = text_sol(TT.cursor);
1510 bytes = text_getline(toybuf, SOL, ARRAY_LEN(toybuf));
1511 line = toybuf;
1512
1513 for (SSOL = TT.screen, y = 0; SSOL < SOL; y++) SSOL = text_nsol(SSOL);
1514
1515 cy_scr = y;
1516
1517 // draw cursor row
1518 /////////////////////////////////////////////////////////////
1519 // for long lines line starts to scroll when cursor hits margin
1520 bytes = TT.cursor-SOL; // TT.cur_col;
1521 end = line;
1522
1523
1524 printf("\e[%u;0H\e[2K", y+1);
1525 // find cursor position
1526 aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
1527
1528 // if we need to render text that is not inserted to buffer yet
1529 if (TT.vi_mode == 2 && TT.il->len) {
1530 char* iend = TT.il->data; //input end
1531 x = 0;
1532 // find insert end position
1533 iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
1534 clip = (aw+iw) - TT.screen_width+margin;
1535
1536 // if clipped area is bigger than text before insert
1537 if (clip > aw) {
1538 clip -= aw;
1539 iend = TT.il->data;
1540
1541 iw -= crunch_str(&iend, clip, 0, "\t\n", vi_crunch);
1542 x = crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
1543 } else {
1544 iend = TT.il->data;
1545 end = line;
1546
1547 //if clipped area is substring from cursor row start
1548 aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
1549 x = crunch_str(&end, aw, stdout, "\t\n", vi_crunch);
1550 x += crunch_str(&iend, iw, stdout, "\t\n", vi_crunch);
1551 }
1552 }
1553 // when not inserting but still need to keep cursor inside screen
1554 // margin area
1555 else if ( aw+margin > TT.screen_width) {
1556 clip = aw-TT.screen_width+margin;
1557 end = line;
1558 aw -= crunch_nstr(&end, clip, bytes, 0, "\t\n", vi_crunch);
1559 x = crunch_str(&end, aw, stdout, "\t\n", vi_crunch);
1560 }
1561 else {
1562 end = line;
1563 x = crunch_nstr(&end, aw, bytes, stdout, "\t\n", vi_crunch);
1564 }
1565 cx_scr = x;
1566 cy_scr = y;
1567 x += crunch_str(&end, TT.screen_width-x, stdout, "\t\n", vi_crunch);
1568
1569 // start drawing all other rows that needs update
1570 ///////////////////////////////////////////////////////////////////
1571 y = 0, SSOL = TT.screen, line = toybuf;
1572 bytes = text_getline(toybuf, SSOL, ARRAY_LEN(toybuf));
1573
1574 // if we moved around in long line might need to redraw everything
1575 if (clip != TT.drawn_col) redraw = 3;
1576
1577 for (; y < TT.screen_height; y++ ) {
1578 int draw_line = 0;
1579 if (SSOL == SOL) {
1580 line = toybuf;
1581 SSOL += bytes+1;
1582 bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
1583 continue;
1584 } else if (redraw) draw_line++;
1585 else if (scroll<0 && TT.screen_height-y-1<-scroll)
1586 scroll++, draw_line++;
1587 else if (scroll>0) scroll--, draw_line++;
1588
1589 printf("\e[%u;0H", y+1);
1590 if (draw_line) {
1591 printf("\e[2K");
1592 if (line && strlen(line)) {
1593 aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
1594 crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
1595 if ( *line ) printf("@");
1596 } else printf("\e[2m~\e[m");
1597 }
1598 if (SSOL+bytes < TT.filesize) {
1599 line = toybuf;
1600 SSOL += bytes+1;
1601 bytes = text_getline(line, SSOL, ARRAY_LEN(toybuf));
1602 } else line = 0;
1603 }
1604
1605 TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
1606
1607 // Finished updating visual area, show status line.
1608 printf("\e[%u;0H\e[2K", TT.screen_height+1);
1609 if (TT.vi_mode == 2) printf("\e[1m-- INSERT --\e[m");
1610 if (!TT.vi_mode) {
1611 cx_scr = printf("%s", TT.il->data);
1612 cy_scr = TT.screen_height;
1613 *toybuf = 0;
1614 } else {
1615 // TODO: the row,col display doesn't show the cursor column
1616 // TODO: real vi shows the percentage by lines, not bytes
1617 sprintf(toybuf, "%zu/%zuC %zu%% %d,%d", TT.cursor, TT.filesize,
1618 (100*TT.cursor)/(TT.filesize ? : 1), TT.cur_row+1, TT.cur_col+1);
1619 if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
1620 }
1621 printf("\e[%u;%uH%s\e[%u;%uH", TT.screen_height+1,
1622 (int) (1+TT.screen_width-strlen(toybuf)),
1623 toybuf, cy_scr+1, cx_scr+1);
1624 fflush(0);
1625 xferror(stdout);
1626 }
1627
vi_main(void)1628 void vi_main(void)
1629 {
1630 char stdout_buf[8192], keybuf[16] = {0}, vi_buf[16] = {0}, utf8_code[8] = {0};
1631 int utf8_dec_p = 0, vi_buf_pos = 0;
1632 FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
1633
1634 TT.il = xzalloc(sizeof(struct str_line));
1635 TT.il->data = xzalloc(80);
1636 TT.yank.data = xzalloc(128);
1637
1638 TT.il->alloc = 80, TT.yank.alloc = 128;
1639
1640 TT.filename = *toys.optargs;
1641 linelist_load(0, 1);
1642
1643 TT.vi_mov_flag = 0x20000000;
1644 TT.vi_mode = 1, TT.tabstop = 8;
1645
1646 TT.screen_width = 80, TT.screen_height = 24;
1647 terminal_size(&TT.screen_width, &TT.screen_height);
1648 TT.screen_height -= 1;
1649
1650 // Avoid flicker.
1651 setbuffer(stdout, stdout_buf, sizeof(stdout_buf));
1652
1653 xsignal(SIGWINCH, generic_signal);
1654 set_terminal(0, 1, 0, 0);
1655 //writes stdout into different xterm buffer so when we exit
1656 //we dont get scroll log full of junk
1657 xputsn("\e[?1049h");
1658
1659 for (;;) {
1660 int key = 0;
1661
1662 draw_page();
1663 if (script) {
1664 key = fgetc(script);
1665 if (key == EOF) {
1666 fclose(script);
1667 script = 0;
1668 key = scan_key(keybuf, -1);
1669 }
1670 } else key = scan_key(keybuf, -1);
1671
1672 if (key == -1) goto cleanup_vi;
1673 else if (key == -3) {
1674 toys.signal = 0;
1675 terminal_size(&TT.screen_width, &TT.screen_height);
1676 TT.screen_height -= 1; //TODO this is hack fix visual alignment
1677 continue;
1678 }
1679
1680 // TODO: support cursor keys in ex mode too.
1681 if (TT.vi_mode && key>=256) {
1682 key -= 256;
1683 //if handling arrow keys insert what ever is in input buffer before moving
1684 if (TT.il->len) {
1685 i_insert(TT.il->data, TT.il->len);
1686 TT.il->len = 0;
1687 memset(TT.il->data, 0, TT.il->alloc);
1688 }
1689 if (key==KEY_UP) cur_up(1, 1, 0);
1690 else if (key==KEY_DOWN) cur_down(1, 1, 0);
1691 else if (key==KEY_LEFT) cur_left(1, 1, 0);
1692 else if (key==KEY_RIGHT) cur_right(1, 1, 0);
1693 else if (key==KEY_HOME) vi_zero(1, 1, 0);
1694 else if (key==KEY_END) vi_dollar(1, 1, 0);
1695 else if (key==KEY_PGDN) ctrl_f();
1696 else if (key==KEY_PGUP) ctrl_b();
1697 continue;
1698 }
1699
1700 if (TT.vi_mode == 1) { //NORMAL
1701 switch (key) {
1702 case '/':
1703 case '?':
1704 case ':':
1705 TT.vi_mode = 0;
1706 TT.il->data[0]=key;
1707 TT.il->len++;
1708 break;
1709 case 'A':
1710 vi_eol();
1711 TT.vi_mode = 2;
1712 break;
1713 case 'a':
1714 cur_right(1, 1, 0);
1715 // FALLTHROUGH
1716 case 'i':
1717 TT.vi_mode = 2;
1718 break;
1719 case CTL('D'):
1720 ctrl_d();
1721 break;
1722 case CTL('B'):
1723 ctrl_b();
1724 break;
1725 case CTL('E'):
1726 ctrl_e();
1727 break;
1728 case CTL('F'):
1729 ctrl_f();
1730 break;
1731 case CTL('Y'):
1732 ctrl_y();
1733 break;
1734 case '\e':
1735 vi_buf[0] = 0;
1736 vi_buf_pos = 0;
1737 break;
1738 case 0x7F: //FALLTHROUGH
1739 case '\b':
1740 backspace(TT.vi_reg, 1, 1);
1741 break;
1742 default:
1743 if (key > ' ' && key < '{') {
1744 vi_buf[vi_buf_pos] = key;//TODO handle input better
1745 vi_buf_pos++;
1746 if (run_vi_cmd(vi_buf)) {
1747 memset(vi_buf, 0, 16);
1748 vi_buf_pos = 0;
1749 }
1750 else if (vi_buf_pos == 15) {
1751 vi_buf_pos = 0;
1752 memset(vi_buf, 0, 16);
1753 }
1754
1755 }
1756
1757 break;
1758 }
1759 } else if (TT.vi_mode == 0) { //EX MODE
1760 switch (key) {
1761 case '\x7f':
1762 case '\b':
1763 if (TT.il->len > 1) {
1764 TT.il->data[--TT.il->len] = 0;
1765 break;
1766 }
1767 // FALLTHROUGH
1768 case '\e':
1769 TT.vi_mode = 1;
1770 TT.il->len = 0;
1771 memset(TT.il->data, 0, TT.il->alloc);
1772 break;
1773 case '\n':
1774 case '\r':
1775 if (run_ex_cmd(TT.il->data)) goto cleanup_vi;
1776 TT.vi_mode = 1;
1777 TT.il->len = 0;
1778 memset(TT.il->data, 0, TT.il->alloc);
1779 break;
1780 default: //add chars to ex command until ENTER
1781 if (key >= ' ' && key < 0x7F) { //might be utf?
1782 if (TT.il->len == TT.il->alloc) {
1783 TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
1784 TT.il->alloc *= 2;
1785 }
1786 TT.il->data[TT.il->len] = key;
1787 TT.il->len++;
1788 }
1789 break;
1790 }
1791 } else if (TT.vi_mode == 2) {//INSERT MODE
1792 switch (key) {
1793 case '\e':
1794 i_insert(TT.il->data, TT.il->len);
1795 cur_left(1, 1, 0);
1796 TT.vi_mode = 1;
1797 TT.il->len = 0;
1798 memset(TT.il->data, 0, TT.il->alloc);
1799 break;
1800 case 0x7F:
1801 case '\b':
1802 if (TT.il->len) {
1803 char *last = utf8_last(TT.il->data, TT.il->len);
1804 int shrink = strlen(last);
1805 memset(last, 0, shrink);
1806 TT.il->len -= shrink;
1807 } else backspace(TT.vi_reg, 1, 1);
1808 break;
1809 case '\n':
1810 case '\r':
1811 //insert newline
1812 TT.il->data[TT.il->len++] = '\n';
1813 i_insert(TT.il->data, TT.il->len);
1814 TT.il->len = 0;
1815 memset(TT.il->data, 0, TT.il->alloc);
1816 break;
1817 default:
1818 if ((key >= ' ' || key == '\t') &&
1819 utf8_dec(key, utf8_code, &utf8_dec_p))
1820 {
1821 if (TT.il->len+utf8_dec_p+1 >= TT.il->alloc) {
1822 TT.il->data = realloc(TT.il->data, TT.il->alloc*2);
1823 TT.il->alloc *= 2;
1824 }
1825 strcpy(TT.il->data+TT.il->len, utf8_code);
1826 TT.il->len += utf8_dec_p;
1827 utf8_dec_p = 0;
1828 *utf8_code = 0;
1829 }
1830 break;
1831 }
1832 }
1833 }
1834 cleanup_vi:
1835 linelist_unload();
1836 free(TT.il->data), free(TT.il), free(TT.yank.data);
1837 tty_reset();
1838 xputsn("\e[?1049l");
1839 }
1840