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