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