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