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