• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, "<1>1", TOYFLAG_USR|TOYFLAG_BIN))
9 
10 config VI
11   bool "vi"
12   default n
13   help
14     usage: vi FILE
15     Visual text editor. Predates the existence of standardized cursor keys,
16     so the controls are weird and historical.
17 */
18 
19 #define FOR_vi
20 #include "toys.h"
21 
22 GLOBALS(
23     int cur_col;
24     int cur_row;
25     int scr_row;
26     int drawn_row;
27     int drawn_col;
28     unsigned screen_height;
29     unsigned screen_width;
30     int vi_mode;
31     int count0;
32     int count1;
33     int vi_mov_flag;
34     int modified;
35     char vi_reg;
36     char *last_search;
37     int tabstop;
38     int list;
39 )
40 
41 struct str_line {
42   int alloc_len;
43   int str_len;
44   char *str_data;
45 };
46 //yank buffer
47 struct yank_buf {
48   char reg;
49   int alloc;
50   char* data;
51 };
52 
53 
54 //lib dllist uses next and prev kinda opposite what im used to so I just
55 //renamed both ends to up and down
56 struct linelist {
57   struct linelist *up;//next
58   struct linelist *down;//prev
59   struct str_line *line;
60 };
61 
62 static void draw_page();
63 
64 //utf8 support
65 static int utf8_lnw(int* width, char* str, int bytes);
66 static int utf8_dec(char key, char *utf8_scratch, int *sta_p);
67 static int utf8_len(char *str);
68 static int utf8_width(char *str, int bytes);
69 static char* utf8_last(char* str, int size);
70 
71 
72 static int cur_left(int count0, int count1, char* unused);
73 static int cur_right(int count0, int count1, char* unused);
74 static int cur_up(int count0, int count1, char* unused);
75 static int cur_down(int count0, int count1, char* unused);
76 static void check_cursor_bounds();
77 static void adjust_screen_buffer();
78 static int search_str(char *s);
79 
80 static int vi_yank(char reg, struct linelist *row, int col, int flags);
81 static int vi_delete(char reg, struct linelist *row, int col, int flags);
82 
83 //inserted line not yet pushed to buffer
84 struct str_line *il;
85 struct linelist *text; //file loaded into buffer
86 struct linelist *scr_r;//current screen coord 0 row
87 struct linelist *c_r;//cursor position row
88 
89 struct yank_buf yank; //single yank
90 
91 // TT.vi_mov_flag is used for special cases when certain move
92 // acts differently depending is there DELETE/YANK or NOP
93 // Also commands such as G does not default to count0=1
94 // 0x1 = Command needs argument (f,F,r...)
95 // 0x2 = Move 1 right on yank/delete/insert (e, $...)
96 // 0x4 = yank/delete last line fully
97 // 0x10000000 = redraw after cursor needed
98 // 0x20000000 = full redraw needed
99 // 0x40000000 = count0 not given
100 // 0x80000000 = move was reverse
101 
dlist_insert_nomalloc(struct double_list ** list,struct double_list * new)102 void dlist_insert_nomalloc(struct double_list **list, struct double_list *new)
103 {
104   if (*list) {
105     new->next = *list;
106     new->prev = (*list)->prev;
107     if ((*list)->prev) (*list)->prev->next = new;
108     (*list)->prev = new;
109   } else *list = new->next = new->prev = new;
110 }
111 
112 
113 // Add an entry to the end of a doubly linked list
dlist_insert(struct double_list ** list,char * data)114 struct double_list *dlist_insert(struct double_list **list, char *data)
115 {
116   struct double_list *new = xmalloc(sizeof(struct double_list));
117   new->data = data;
118   dlist_insert_nomalloc(list, new);
119 
120   return new;
121 }
122 //TODO implement
linelist_unload()123 void linelist_unload()
124 {
125 
126 }
127 
write_file(char * filename)128 void write_file(char *filename)
129 {
130   struct linelist *lst = text;
131   FILE *fp = 0;
132   if (!filename)
133     filename = (char*)*toys.optargs;
134   fp = fopen(filename, "w");
135   if (!fp) return;
136   while (lst) {
137     fprintf(fp, "%s\n", lst->line->str_data);
138     lst = lst->down;
139   }
140   fclose(fp);
141 }
142 
linelist_load(char * filename)143 int linelist_load(char *filename)
144 {
145   struct linelist *lst = c_r;//cursor position or 0
146   FILE *fp = 0;
147   if (!filename)
148     filename = (char*)*toys.optargs;
149 
150   fp = fopen(filename, "r");
151   if (!fp) {
152     char *line = xzalloc(80);
153     ssize_t alc = 80;
154     lst = (struct linelist*)dlist_add((struct double_list**)&lst,
155         xzalloc(sizeof(struct str_line)));
156     lst->line->alloc_len = alc;
157     lst->line->str_len = 0;
158     lst->line->str_data = line;
159     text = lst;
160     dlist_terminate(text->up);
161     return 1;
162   }
163 
164   for (;;) {
165     char *line = xzalloc(80);
166     ssize_t alc = 80;
167     ssize_t len;
168     if ((len = getline(&line, (void *)&alc, fp)) == -1) {
169       if (errno == EINVAL || errno == ENOMEM) {
170         printf("error %d\n", errno);
171       }
172       free(line);
173       break;
174     }
175     lst = (struct linelist*)dlist_add((struct double_list**)&lst,
176         xzalloc(sizeof(struct str_line)));
177     lst->line->alloc_len = alc;
178     lst->line->str_len = len;
179     lst->line->str_data = line;
180 
181     if (lst->line->str_data[len-1] == '\n') {
182       lst->line->str_data[len-1] = 0;
183       lst->line->str_len--;
184     }
185     if (text == 0) text = lst;
186   }
187 
188   if (text) dlist_terminate(text->up);
189 
190   fclose(fp);
191   return 1;
192 
193 }
194 
vi_yy(char reg,int count0,int count1)195 int vi_yy(char reg, int count0, int count1)
196 {
197   struct linelist *pos = c_r;
198   int col = TT.cur_col;
199   TT.cur_col = 0;
200   TT.vi_mov_flag |= 0x4;
201 
202   if (count0>1) cur_down(count0-1, 1, 0);
203 
204   vi_yank(reg, pos, 0, 0);
205 
206   TT.cur_col = col, c_r = pos;
207   return 1;
208 }
209 
vi_dd(char reg,int count0,int count1)210 int vi_dd(char reg, int count0, int count1)
211 {
212   struct linelist *pos = c_r;
213   TT.cur_col = 0;
214   TT.vi_mov_flag |= 0x4;
215   if (count0>1) cur_down(count0-1, 1, 0);
216 
217   vi_delete(reg, pos, 0, 0);
218   check_cursor_bounds();
219   return 1;
220 }
221 
vi_x(char reg,int count0,int count1)222 static int vi_x(char reg, int count0, int count1)
223 {
224   char *last = 0, *cpos = 0, *start = 0;
225   int len = 0;
226   struct linelist *pos = c_r;
227   int col = TT.cur_col;
228   if (!c_r) return 0;
229 
230   start = c_r->line->str_data;
231   len = c_r->line->str_len;
232 
233   last = utf8_last(start, len);
234   cpos = start+TT.cur_col;
235   if (cpos == last) {
236     cur_left(count0-1, 1, 0);
237     col = strlen(start);
238   }
239   else {
240     cur_right(count0-1, 1, 0);
241     cpos = start+TT.cur_col;
242     if (cpos == last) TT.vi_mov_flag |= 2;
243     else cur_right(1, 1, 0);
244   }
245 
246   vi_delete(reg, pos, col, 0);
247   check_cursor_bounds();
248   return 1;
249 }
250 
251 //move commands does not behave correct way yet.
vi_movw(int count0,int count1,char * unused)252 int vi_movw(int count0, int count1, char* unused)
253 {
254   int count = count0*count1;
255   const char *empties = " \t\n\r";
256   const char *specials = ",.=-+*/(){}<>[]";
257 //  char *current = 0;
258   if (!c_r)
259     return 0;
260   if (TT.cur_col == c_r->line->str_len-1 || !c_r->line->str_len)
261     goto next_line;
262   if (strchr(empties, c_r->line->str_data[TT.cur_col]))
263     goto find_non_empty;
264   if (strchr(specials, c_r->line->str_data[TT.cur_col])) {
265     for (;strchr(specials, c_r->line->str_data[TT.cur_col]); ) {
266       TT.cur_col++;
267       if (TT.cur_col == c_r->line->str_len-1)
268         goto next_line;
269     }
270   } else for (;!strchr(specials, c_r->line->str_data[TT.cur_col]) &&
271       !strchr(empties, c_r->line->str_data[TT.cur_col]);) {
272       TT.cur_col++;
273       if (TT.cur_col == c_r->line->str_len-1)
274         goto next_line;
275   }
276 
277   for (;strchr(empties, c_r->line->str_data[TT.cur_col]); ) {
278     TT.cur_col++;
279 find_non_empty:
280     if (TT.cur_col == c_r->line->str_len-1) {
281 next_line:
282       //we could call j and g0
283       if (!c_r->down) return 0;
284       c_r = c_r->down;
285       TT.cur_col = 0;
286       if (!c_r->line->str_len) break;
287     }
288   }
289   count--;
290   if (count>0)
291     return vi_movw(count, 1, 0);
292 
293   check_cursor_bounds();
294   return 1;
295 }
296 
vi_movb(int count0,int count1,char * unused)297 static int vi_movb(int count0, int count1, char* unused)
298 {
299   int count = count0*count1;
300   if (!c_r)
301     return 0;
302   if (!TT.cur_col) {
303       if (!c_r->up) return 0;
304       c_r = c_r->up;
305       TT.cur_col = (c_r->line->str_len) ? c_r->line->str_len-1 : 0;
306       goto exit_function;
307   }
308   if (TT.cur_col)
309       TT.cur_col--;
310   while (c_r->line->str_data[TT.cur_col] <= ' ') {
311     if (TT.cur_col) TT.cur_col--;
312     else goto exit_function;
313   }
314   while (c_r->line->str_data[TT.cur_col] > ' ') {
315     if (TT.cur_col)TT.cur_col--;
316     else goto exit_function;
317   }
318   TT.cur_col++;
319 exit_function:
320   count--;
321   if (count>1)
322     return vi_movb(count, 1, 0);
323   TT.vi_mov_flag |= 0x80000000;
324   check_cursor_bounds();
325   return 1;
326 }
327 
vi_move(int count0,int count1,char * unused)328 static int vi_move(int count0, int count1, char *unused)
329 {
330   int count = count0*count1;
331   if (!c_r)
332     return 0;
333   if (TT.cur_col < c_r->line->str_len)
334     TT.cur_col++;
335   if (c_r->line->str_data[TT.cur_col] <= ' ' || count > 1)
336     vi_movw(count, 1, 0); //find next word;
337   while (c_r->line->str_data[TT.cur_col] > ' ')
338     TT.cur_col++;
339   if (TT.cur_col) TT.cur_col--;
340 
341   TT.vi_mov_flag |= 2;
342   check_cursor_bounds();
343   return 1;
344 }
345 
346 
i_insert(char * str,int len)347 static void i_insert(char* str, int len)
348 {
349   char *t = xzalloc(c_r->line->alloc_len);
350   char *s = c_r->line->str_data;
351   int sel = c_r->line->str_len-TT.cur_col;
352   strncpy(t, &s[TT.cur_col], sel);
353   t[sel+1] = 0;
354   if (c_r->line->alloc_len < c_r->line->str_len+len+5) {
355     c_r->line->str_data = xrealloc(c_r->line->str_data,
356       (c_r->line->alloc_len+len)<<1);
357 
358     c_r->line->alloc_len = (c_r->line->alloc_len+len)<<1;
359     memset(&c_r->line->str_data[c_r->line->str_len], 0,
360         c_r->line->alloc_len-c_r->line->str_len);
361 
362     s = c_r->line->str_data;
363   }
364   strncpy(&s[TT.cur_col], str, len);
365   strcpy(&s[TT.cur_col+len], t);
366   TT.cur_col += len;
367   if (TT.cur_col) TT.cur_col--;
368 
369   c_r->line->str_len += len;
370   free(t);
371 
372   TT.vi_mov_flag |= 0x30000000;
373 }
374 
375 //new line at split pos;
i_split()376 void i_split()
377 {
378   int alloc = 0, len = 0, idx = 0;
379   struct str_line *l = xmalloc(sizeof(struct str_line));
380   alloc = c_r->line->alloc_len;
381 
382   if (TT.cur_col) len = c_r->line->str_len-TT.cur_col-1;
383   else len = c_r->line->str_len;
384   if (len < 0) len = 0;
385 
386   l->str_data = xzalloc(alloc);
387   l->alloc_len = alloc;
388   l->str_len = len;
389   idx = c_r->line->str_len - len;
390 
391   strncpy(l->str_data, &c_r->line->str_data[idx], len);
392   memset(&l->str_data[len], 0, alloc-len);
393 
394   c_r->line->str_len -= len;
395   if (c_r->line->str_len <= 0) c_r->line->str_len = 0;
396 
397   len = c_r->line->str_len;
398 
399   memset(&c_r->line->str_data[len], 0, alloc-len);
400   c_r = (struct linelist*)dlist_insert((struct double_list**)&c_r, (char*)l);
401   c_r->line = l;
402   TT.cur_col = 0;
403 }
404 
405 
vi_zero(int count0,int count1,char * unused)406 static int vi_zero(int count0, int count1, char *unused)
407 {
408   TT.cur_col = 0;
409   TT.vi_mov_flag |= 0x80000000;
410   return 1;
411 }
412 
vi_eol(int count0,int count1,char * unused)413 static int vi_eol(int count0, int count1, char *unused)
414 {
415   int count = count0*count1;
416   for (;count > 1 && c_r->down; count--)
417     c_r = c_r->down;
418 
419   if (c_r && c_r->line->str_len)
420     TT.cur_col = c_r->line->str_len-1;
421   TT.vi_mov_flag |= 2;
422   check_cursor_bounds();
423   return 1;
424 }
425 
426 //TODO check register where to push from
vi_push(char reg,int count0,int count1)427 static int vi_push(char reg, int count0, int count1)
428 {
429   char *start = yank.data, *end = yank.data+strlen(yank.data);
430   struct linelist *cursor = c_r;
431   int col = TT.cur_col;
432   //insert into new lines
433   if (*(end-1) == '\n') for (;start != end;) {
434     TT.vi_mov_flag |= 0x10000000;
435     char *next = strchr(start, '\n');
436     TT.cur_col = (c_r->line->str_len) ? c_r->line->str_len-1: 0;
437     i_split();
438     if (next) {
439       i_insert(start, next-start);
440       start = next+1;
441     } else start = end; //??
442   }
443 
444   //insert into cursor
445   else for (;start != end;) {
446     char *next = strchr(start, '\n');
447     if (next) {
448       TT.vi_mov_flag |= 0x10000000;
449       i_insert(start, next-start);
450       i_split();
451       start = next+1;
452     } else {
453       i_insert(start, strlen(start));
454       start = end;
455     }
456   }
457   //if row changes during push original cursor position is kept
458   //vi inconsistancy
459   if (c_r != cursor) c_r = cursor, TT.cur_col = col;
460 
461   return 1;
462 }
463 
vi_find_c(int count0,int count1,char * symbol)464 static int vi_find_c(int count0, int count1, char *symbol)
465 {
466   int count = count0*count1;
467   if (c_r && c_r->line->str_len) {
468     while (count--) {
469         char* pos = strstr(&c_r->line->str_data[TT.cur_col], symbol);
470         if (pos) {
471           TT.cur_col = pos-c_r->line->str_data;
472           return 1;
473         }
474     }
475   }
476   return 0;
477 }
478 
vi_find_cb(int count0,int count1,char * symbol)479 static int vi_find_cb(int count0, int count1, char *symbol)
480 {
481   //do backward search
482   return 1;
483 }
484 
485 //if count is not spesified should go to last line
vi_go(int count0,int count1,char * symbol)486 static int vi_go(int count0, int count1, char *symbol)
487 {
488   int prev_row = TT.cur_row;
489   c_r = text;
490 
491   if (TT.vi_mov_flag&0x40000000) for (;c_r && c_r->down; c_r = c_r->down);
492   else for (;c_r && c_r->down && --count0; c_r = c_r->down);
493 
494   TT.cur_col = 0;
495   check_cursor_bounds();  //adjusts cursor column
496   if (prev_row>TT.cur_row) TT.vi_mov_flag |= 0x80000000;
497 
498   return 1;
499 }
500 
501 //need to refactor when implementing yank buffers
vi_delete(char reg,struct linelist * row,int col,int flags)502 static int vi_delete(char reg, struct linelist *row, int col, int flags)
503 {
504   struct linelist *start = 0, *end = 0;
505   int col_s = 0, col_e = 0, bytes = 0;
506 
507   vi_yank(reg, row, col, flags);
508 
509   if (TT.vi_mov_flag&0x80000000) {
510     start = c_r, end = row;
511     col_s = TT.cur_col, col_e = col;
512   } else {
513     start = row, end = c_r;
514     col_s = col, col_e = TT.cur_col;
515   }
516   if (start == end) goto last_line_delete;
517   if (!col_s) goto full_line_delete;
518 
519   memset(start->line->str_data+col_s, 0, start->line->str_len-col_s);
520   row->line->str_len = col_s;
521   col_s = 0;
522   start = start->down;
523 
524 full_line_delete:
525   TT.vi_mov_flag |= 0x10000000;
526   for (;start != end;) {
527     struct linelist* lst = start;
528     //struct linelist *lst = dlist_pop(&start);
529     start = start->down;
530     if (lst->down) lst->down->up = lst->up;
531     if (lst->up) lst->up->down = lst->down;
532     if (scr_r == lst) scr_r = lst->down ? lst->down : lst->up;
533     if (text == lst) text = lst->down;
534     free(lst->line->str_data);
535     free(lst->line);
536     free(lst);
537   }
538 last_line_delete:
539   TT.vi_mov_flag |= 0x10000000;
540   if (TT.vi_mov_flag&2) col_e = start->line->str_len;
541   if (TT.vi_mov_flag&4) {
542     if (!end->down && !end->up)
543       col_e = start->line->str_len;
544     else {
545       col_e = 0, col_s = 0;
546       if (end->down) end->down->up = end->up;
547       if (end->up) end->up->down = end->down;
548       if (scr_r == end) scr_r = end->down ? end->down : end->up;
549       //if (text == end) text = end->down;
550       start = end->down ? end->down : end->up;
551       free(end->line->str_data);
552       free(end->line);
553       free(end);
554 
555     }
556   }
557   if (col_s < col_e) {
558     bytes = col_s + start->line->str_len - col_e;
559     memmove(start->line->str_data+col_s, start->line->str_data+col_e,
560         start->line->str_len-col_e);
561     memset(start->line->str_data+bytes, 0, start->line->str_len-bytes);
562     start->line->str_len = bytes;
563   }
564   c_r = start;
565   TT.cur_col = col_s;
566   return 1;
567 }
568 
vi_D(char reg,int count0,int count1)569 static int vi_D(char reg, int count0, int count1)
570 {
571   int prev_col = TT.cur_col;
572   struct linelist *pos = c_r;
573   if (!count0) return 1;
574   vi_eol(1, 1, 0);
575   vi_delete(reg, pos, prev_col, 0);
576   count0--;
577   if (count0 && c_r->down) {
578     c_r = c_r->down;
579     vi_dd(reg, count0, 1);
580   }
581   check_cursor_bounds();
582   return 1;
583 }
584 
vi_join(char reg,int count0,int count1)585 static int vi_join(char reg, int count0, int count1)
586 {
587   while (count0--) {
588     if (c_r && c_r->down) {
589       int size = c_r->line->str_len+c_r->down->line->str_len;
590       if (size > c_r->line->alloc_len) {
591         if (size > c_r->down->line->alloc_len) {
592           c_r->line->str_data = xrealloc(c_r->line->str_data,
593             c_r->line->alloc_len*2+il->alloc_len*2);
594           memmove(&c_r->line->str_data[c_r->line->str_len],
595               c_r->down->line->str_data,c_r->down->line->str_len);
596           c_r->line->str_len = size;
597           c_r = c_r->down;
598           c_r->line->alloc_len = c_r->line->alloc_len*2+2*il->alloc_len;
599           vi_dd(0,1,1);
600         } else {
601           memmove(&c_r->down->line->str_data[c_r->line->str_len],
602               c_r->down->line->str_data,c_r->down->line->str_len);
603           memmove(c_r->down->line->str_data,c_r->line->str_data,
604               c_r->line->str_len);
605           c_r->down->line->str_len = size;
606           vi_dd(0,1,1);
607         }
608       } else {
609           memmove(&c_r->line->str_data[c_r->line->str_len],
610               c_r->down->line->str_data,c_r->down->line->str_len);
611           c_r->line->str_len = size;
612           c_r = c_r->down;
613           vi_dd(0,1,1);
614       }
615       c_r = c_r->up;
616 
617     }
618   }
619   return 1;
620 }
621 
vi_find_next(char reg,int count0,int count1)622 static int vi_find_next(char reg, int count0, int count1)
623 {
624   if (TT.last_search) search_str(TT.last_search);
625   return 1;
626 }
627 
vi_change(char reg,struct linelist * row,int col,int flags)628 static int vi_change(char reg, struct linelist *row, int col, int flags)
629 {
630   vi_delete(reg, row, col, flags);
631   TT.vi_mode = 2;
632   return 1;
633 }
634 
635 //TODO search yank buffer by register
636 //now only supports default register
vi_yank(char reg,struct linelist * row,int col,int flags)637 static int vi_yank(char reg, struct linelist *row, int col, int flags)
638 {
639   struct linelist *start = 0, *end = 0;
640   int col_s = 0, col_e = 0, bytes = 0;
641 
642   memset(yank.data, 0, yank.alloc);
643   if (TT.vi_mov_flag&0x80000000) {
644     start = c_r, end = row;
645     col_s = TT.cur_col, col_e = col;
646   } else {
647     start = row, end = c_r;
648     col_s = col, col_e = TT.cur_col;
649   }
650   if (start == end) goto last_line_yank;
651   if (!col_s) goto full_line_yank;
652 
653   if (yank.alloc < start->line->alloc_len) {
654     yank.data = xrealloc(yank.data, start->line->alloc_len*2);
655     yank.alloc = start->line->alloc_len*2;
656   }
657 
658   sprintf(yank.data, "%s\n", start->line->str_data+col_s);
659   col_s = 0;
660   start = start->down;
661 
662 full_line_yank:
663   for (;start != end;) {
664     while (yank.alloc-1 < strlen(yank.data)+start->line->str_len)
665       yank.data = xrealloc(yank.data, yank.alloc*2), yank.alloc *= 2;
666 
667 
668     sprintf(yank.data+strlen(yank.data), "%s\n", start->line->str_data);
669     start = start->down;
670   }
671 last_line_yank:
672   while (yank.alloc-1 < strlen(yank.data)+end->line->str_len)
673     yank.data = xrealloc(yank.data, yank.alloc*2), yank.alloc *= 2;
674 
675   if (TT.vi_mov_flag & 0x4)
676     sprintf(yank.data+strlen(yank.data), "%s\n", start->line->str_data);
677   else {
678     bytes = strlen(yank.data)+col_e-col_s;
679     strncpy(yank.data+strlen(yank.data), end->line->str_data+col_s, col_e-col_s);
680     yank.data[bytes] = 0;
681   }
682   return 1;
683 }
684 
685 //NOTES
686 //vi-mode cmd syntax is
687 //("[REG])[COUNT0]CMD[COUNT1](MOV)
688 //where:
689 //-------------------------------------------------------------
690 //"[REG] is optional buffer where deleted/yanked text goes REG can be
691 //  atleast 0-9, a-z or default "
692 //[COUNT] is optional multiplier for cmd execution if there is 2 COUNT
693 //  operations they are multiplied together
694 //CMD is operation to be executed
695 //(MOV) is movement operation, some CMD does not require MOV and some
696 //  have special cases such as dd, yy, also movements can work without
697 //  CMD
698 //ex commands can be even more complicated than this....
699 //
700 struct vi_cmd_param {
701   const char* cmd;
702   unsigned flags;
703   int (*vi_cmd)(char, struct linelist*, int, int);//REG,row,col,FLAGS
704 };
705 struct vi_mov_param {
706   const char* mov;
707   unsigned flags;
708   int (*vi_mov)(int, int, char*);//COUNT0,COUNT1,params
709 };
710 //special cases without MOV and such
711 struct vi_special_param {
712   const char *cmd;
713   int (*vi_special)(char, int, int);//REG,COUNT0,COUNT1
714 };
715 struct vi_special_param vi_special[] =
716 {
717   {"dd", &vi_dd},
718   {"yy", &vi_yy},
719   {"D", &vi_D},
720   {"J", &vi_join},
721   {"n", &vi_find_next},
722   {"x", &vi_x},
723   {"p", &vi_push}
724 };
725 //there is around ~47 vi moves
726 //some of them need extra params
727 //such as f and '
728 struct vi_mov_param vi_movs[] =
729 {
730   {"0", 0, &vi_zero},
731   {"b", 0, &vi_movb},
732   {"e", 0, &vi_move},
733   {"G", 0, &vi_go},
734   {"h", 0, &cur_left},
735   {"j", 0, &cur_down},
736   {"k", 0, &cur_up},
737   {"l", 0, &cur_right},
738   {"w", 0, &vi_movw},
739   {"$", 0, &vi_eol},
740   {"f", 1, &vi_find_c},
741   {"F", 1, &vi_find_cb},
742 };
743 //change and delete unfortunately behave different depending on move command,
744 //such as ce cw are same, but dw and de are not...
745 //also dw stops at w position and cw seem to stop at e pos+1...
746 //so after movement we need to possibly set up some flags before executing
747 //command, and command needs to adjust...
748 struct vi_cmd_param vi_cmds[] =
749 {
750   {"c", 1, &vi_change},
751   {"d", 1, &vi_delete},
752   {"y", 1, &vi_yank},
753 };
754 
run_vi_cmd(char * cmd)755 int run_vi_cmd(char *cmd)
756 {
757   int i = 0, val = 0;
758   char *cmd_e;
759   int (*vi_cmd)(char, struct linelist*, int, int) = 0;
760   int (*vi_mov)(int, int, char*) = 0;
761 
762   TT.count0 = 0, TT.count1 = 0, TT.vi_mov_flag = 0;
763   TT.vi_reg = '"';
764 
765   if (*cmd == '"') {
766     cmd++;
767     TT.vi_reg = *cmd; //TODO check validity
768     cmd++;
769   }
770   errno = 0;
771   val = strtol(cmd, &cmd_e, 10);
772   if (errno || val == 0) val = 1, TT.vi_mov_flag |= 0x40000000;
773   else cmd = cmd_e;
774   TT.count0 = val;
775 
776   for (i = 0; i < ARRAY_LEN(vi_special); i++) {
777     if (strstr(cmd, vi_special[i].cmd)) {
778       return vi_special[i].vi_special(TT.vi_reg, TT.count0, TT.count1);
779     }
780   }
781 
782   for (i = 0; i < ARRAY_LEN(vi_cmds); i++) {
783     if (!strncmp(cmd, vi_cmds[i].cmd, strlen(vi_cmds[i].cmd))) {
784       vi_cmd = vi_cmds[i].vi_cmd;
785       cmd += strlen(vi_cmds[i].cmd);
786       break;
787     }
788   }
789   errno = 0;
790   val = strtol(cmd, &cmd_e, 10);
791   if (errno || val == 0) val = 1;
792   else cmd = cmd_e;
793   TT.count1 = val;
794 
795   for (i = 0; i < ARRAY_LEN(vi_movs); i++) {
796     if (!strncmp(cmd, vi_movs[i].mov, strlen(vi_movs[i].mov))) {
797       vi_mov = vi_movs[i].vi_mov;
798       TT.vi_mov_flag |= vi_movs[i].flags;
799       cmd++;
800       if (TT.vi_mov_flag&1 && !(*cmd)) return 0;
801       break;
802     }
803   }
804   if (vi_mov) {
805     int prev_col = TT.cur_col;
806     struct linelist *pos = c_r;
807     if (vi_mov(TT.count0, TT.count1, cmd)) {
808       if (vi_cmd) return (vi_cmd(TT.vi_reg, pos, prev_col, TT.vi_mov_flag));
809       else return 1;
810     } else return 0; //return some error
811   }
812   return 0;
813 }
814 
search_str(char * s)815 static int search_str(char *s)
816 {
817   struct linelist *lst = c_r;
818   char *c = strstr(&c_r->line->str_data[TT.cur_col+1], s);
819 
820   if (TT.last_search != s) {
821     free(TT.last_search);
822     TT.last_search = xstrdup(s);
823   }
824 
825   if (c) {
826     TT.cur_col = c-c_r->line->str_data;
827   } else for (; !c;) {
828     lst = lst->down;
829     if (!lst) return 1;
830     c = strstr(lst->line->str_data, s);
831   }
832   c_r = lst;
833   TT.cur_col = c-c_r->line->str_data;
834   check_cursor_bounds();
835   return 0;
836 }
837 
run_ex_cmd(char * cmd)838 int run_ex_cmd(char *cmd)
839 {
840   if (cmd[0] == '/') {
841     search_str(&cmd[1]);
842   } else if (cmd[0] == '?') {
843     // TODO: backwards search.
844   } else if (cmd[0] == ':') {
845     if (!strcmp(&cmd[1], "q") || !strcmp(&cmd[1], "q!")) {
846       // TODO: if no !, check whether file modified.
847       //exit_application;
848       return -1;
849     }
850     else if (strstr(&cmd[1], "wq")) {
851       write_file(0);
852       return -1;
853     }
854     else if (strstr(&cmd[1], "w")) {
855       write_file(0);
856       return 1;
857     }
858     else if (strstr(&cmd[1], "set list")) {
859       TT.list = 1;
860       TT.vi_mov_flag |= 0x30000000;
861       return 1;
862     }
863     else if (strstr(&cmd[1], "set nolist")) {
864       TT.list = 0;
865       TT.vi_mov_flag |= 0x30000000;
866       return 1;
867     }
868   }
869   return 0;
870 
871 }
872 
vi_main(void)873 void vi_main(void)
874 {
875   char keybuf[16];
876   char utf8_code[8];
877   int utf8_dec_p = 0;
878   char vi_buf[16];
879   int vi_buf_pos = 0;
880   il = xzalloc(sizeof(struct str_line));
881   il->str_data = xzalloc(80);
882   il->alloc_len = 80;
883   keybuf[0] = 0;
884   memset(vi_buf, 0, 16);
885   memset(utf8_code, 0, 8);
886   linelist_load(0);
887   scr_r = text;
888   c_r = text;
889   TT.cur_row = 0;
890   TT.cur_col = 0;
891   TT.screen_width = 80;
892   TT.screen_height = 24;
893   TT.vi_mode = 1;
894   TT.tabstop = 8;
895   yank.data = xzalloc(128);
896   yank.alloc = 128;
897   terminal_size(&TT.screen_width, &TT.screen_height);
898   TT.screen_height -= 2; //TODO this is hack fix visual alignment
899   set_terminal(0, 1, 0, 0);
900   //writes stdout into different xterm buffer so when we exit
901   //we dont get scroll log full of junk
902   tty_esc("?1049h");
903   tty_esc("H");
904   xflush(1);
905   TT.vi_mov_flag = 0x20000000;
906   draw_page();
907   while(1) {
908     int key = scan_key(keybuf, -1);
909 
910   terminal_size(&TT.screen_width, &TT.screen_height);
911   TT.screen_height -= 2; //TODO this is hack fix visual alignment
912     // TODO: support cursor keys in ex mode too.
913     if (TT.vi_mode && key>=256) {
914       key -= 256;
915       if (key==KEY_UP) cur_up(1, 1, 0);
916       else if (key==KEY_DOWN) cur_down(1, 1, 0);
917       else if (key==KEY_LEFT) cur_left(1, 1, 0);
918       else if (key==KEY_RIGHT) cur_right(1, 1, 0);
919       draw_page();
920       continue;
921     }
922 
923     switch (key) {
924       case -1:
925       case 3:
926       case 4:
927         goto cleanup_vi;
928     }
929     if (TT.vi_mode == 1) { //NORMAL
930       switch (key) {
931         case '/':
932         case '?':
933         case ':':
934           TT.vi_mode = 0;
935           il->str_data[0]=key;
936           il->str_len++;
937           break;
938         case 'A':
939           vi_eol(1, 1, 0);
940           // FALLTHROUGH
941         case 'a':
942           if (c_r && c_r->line->str_len) TT.cur_col++;
943           // FALLTHROUGH
944         case 'i':
945           TT.vi_mode = 2;
946           break;
947         case 27:
948           vi_buf[0] = 0;
949           vi_buf_pos = 0;
950           break;
951         default:
952           if (key > 0x20 && key < 0x7B) {
953             vi_buf[vi_buf_pos] = key;//TODO handle input better
954             vi_buf_pos++;
955             if (run_vi_cmd(vi_buf)) {
956               memset(vi_buf, 0, 16);
957               vi_buf_pos = 0;
958             }
959             else if (vi_buf_pos == 16) {
960               vi_buf_pos = 0;
961               memset(vi_buf, 0, 16);
962             }
963 
964           }
965 
966           break;
967       }
968     } else if (TT.vi_mode == 0) { //EX MODE
969       switch (key) {
970         case 0x7F:
971         case 0x08:
972           if (il->str_len > 1) {
973             il->str_data[--il->str_len] = 0;
974             break;
975           }
976           // FALLTHROUGH
977         case 27:
978           TT.vi_mode = 1;
979           il->str_len = 0;
980           memset(il->str_data, 0, il->alloc_len);
981           break;
982         case 0x0D:
983           if (run_ex_cmd(il->str_data) == -1)
984             goto cleanup_vi;
985           TT.vi_mode = 1;
986           il->str_len = 0;
987           memset(il->str_data, 0, il->alloc_len);
988           break;
989         default: //add chars to ex command until ENTER
990           if (key >= 0x20 && key < 0x7F) { //might be utf?
991             if (il->str_len == il->alloc_len) {
992               il->str_data = realloc(il->str_data, il->alloc_len*2);
993               il->alloc_len *= 2;
994             }
995             il->str_data[il->str_len] = key;
996             il->str_len++;
997           }
998           break;
999       }
1000     } else if (TT.vi_mode == 2) {//INSERT MODE
1001       switch (key) {
1002         case 27:
1003           i_insert(il->str_data, il->str_len);
1004           TT.vi_mode = 1;
1005           il->str_len = 0;
1006           memset(il->str_data, 0, il->alloc_len);
1007           break;
1008         case 0x7F:
1009         case 0x08:
1010           if (il->str_len)
1011             il->str_data[il->str_len--] = 0;
1012           break;
1013         case 0x09:
1014           il->str_data[il->str_len++] = '\t';
1015           break;
1016 
1017         case 0x0D:
1018           //insert newline
1019           //
1020           i_insert(il->str_data, il->str_len);
1021           il->str_len = 0;
1022           memset(il->str_data, 0, il->alloc_len);
1023           i_split();
1024           break;
1025         default:
1026           if (key >= 0x20 && utf8_dec(key, utf8_code, &utf8_dec_p)) {
1027             if (il->str_len+utf8_dec_p+1 >= il->alloc_len) {
1028               il->str_data = realloc(il->str_data, il->alloc_len*2);
1029               il->alloc_len *= 2;
1030             }
1031             strcpy(il->str_data+il->str_len, utf8_code);
1032             il->str_len += utf8_dec_p;
1033             utf8_dec_p = 0;
1034             *utf8_code = 0;
1035 
1036           }
1037           break;
1038       }
1039     }
1040 
1041     draw_page();
1042 
1043   }
1044 cleanup_vi:
1045   linelist_unload();
1046   tty_reset();
1047   tty_esc("?1049l");
1048 }
1049 
vi_crunch(FILE * out,int cols,int wc)1050 int vi_crunch(FILE* out, int cols, int wc)
1051 {
1052   int ret = 0;
1053   if (wc < 32 && TT.list) {
1054     tty_esc("1m");
1055     ret = crunch_escape(out,cols,wc);
1056     tty_esc("m");
1057   } else if (wc == 0x09) {
1058     if (out) {
1059       int i = TT.tabstop;
1060       for (;i--;) fputs(" ", out);
1061     }
1062     ret = TT.tabstop;
1063   }
1064   return ret;
1065 }
1066 
1067 //crunch_str with n bytes restriction for printing substrings or
1068 //non null terminated strings
crunch_nstr(char ** str,int width,int n,FILE * out,char * escmore,int (* escout)(FILE * out,int cols,int wc))1069 int crunch_nstr(char **str, int width, int n, FILE *out, char *escmore,
1070   int (*escout)(FILE *out, int cols, int wc))
1071 {
1072   int columns = 0, col, bytes;
1073   char *start, *end;
1074 
1075   for (end = start = *str; *end && n>0; columns += col, end += bytes, n -= bytes) {
1076     wchar_t wc;
1077 
1078     if ((bytes = utf8towc(&wc, end, 4))>0 && (col = wcwidth(wc))>=0) {
1079       if (!escmore || wc>255 || !strchr(escmore, wc)) {
1080         if (width-columns<col) break;
1081         if (out) fwrite(end, bytes, 1, out);
1082 
1083         continue;
1084       }
1085     }
1086 
1087     if (bytes<1) {
1088       bytes = 1;
1089       wc = *end;
1090     }
1091     col = width-columns;
1092     if (col<1) break;
1093     if (escout) {
1094       if ((col = escout(out, col, wc))<0) break;
1095     } else if (out) fwrite(end, 1, bytes, out);
1096   }
1097   *str = end;
1098 
1099   return columns;
1100 }
1101 
draw_page()1102 static void draw_page()
1103 {
1104   struct linelist *scr_buf = 0;
1105   unsigned y = 0;
1106   int x = 0;
1107 
1108   char *line = 0, *end = 0;
1109   int utf_l = 0,  bytes = 0;
1110 
1111   //screen coordinates for cursor
1112   int cy_scr = 0, cx_scr = 0;
1113 
1114   //variables used only for cursor handling
1115   int aw = 0, iw = 0, clip = 0, margin = 8;
1116 
1117   int scroll = 0, redraw = 0;
1118 
1119   adjust_screen_buffer();
1120   scr_buf = scr_r;
1121   redraw = (TT.vi_mov_flag & 0x30000000)>>28;
1122 
1123   scroll = TT.drawn_row-TT.scr_row;
1124   if (TT.drawn_row<0 || TT.cur_row<0 || TT.scr_row<0) redraw = 3;
1125   else if (abs(scroll)>TT.screen_height/2) redraw = 3;
1126 
1127   tty_jump(0, 0);
1128   if (redraw&2) tty_esc("2J"), tty_esc("H");   //clear screen
1129   else if (scroll>0) printf("\033[%dL", scroll);  //scroll up
1130   else if (scroll<0) printf("\033[%dM", -scroll); //scroll down
1131 
1132   //jump until cursor
1133   for (; y < TT.screen_height; y++ ) {
1134     if (scr_buf == c_r) break;
1135     scr_buf = scr_buf->down;
1136   }
1137   //draw cursor row
1138   /////////////////////////////////////////////////////////////
1139   //for long lines line starts to scroll when cursor hits margin
1140   line = scr_buf->line->str_data;
1141   bytes = TT.cur_col;
1142   end = line;
1143 
1144 
1145   tty_jump(0, y);
1146   tty_esc("2K");
1147   //find cursor position
1148   aw = crunch_nstr(&end, 1024, bytes, 0, "\t", vi_crunch);
1149 
1150   //if we need to render text that is not inserted to buffer yet
1151   if (TT.vi_mode == 2 && il->str_len) {
1152     char* iend = il->str_data; //input end
1153     x = 0;
1154     //find insert end position
1155     iw = crunch_str(&iend, 1024, 0, "\t", vi_crunch);
1156     clip = (aw+iw) - TT.screen_width+margin;
1157 
1158     //if clipped area is bigger than text before insert
1159     if (clip > aw) {
1160       clip -= aw;
1161       iend = il->str_data;
1162 
1163       iw -= crunch_str(&iend, clip, 0, "\t", vi_crunch);
1164       x = crunch_str(&iend, iw, stdout, "\t", vi_crunch);
1165     } else {
1166       iend = il->str_data;
1167       end = line;
1168 
1169       //if clipped area is substring from cursor row start
1170       aw -= crunch_nstr(&end, clip, bytes, 0, "\t", vi_crunch);
1171       x = crunch_str(&end, aw,  stdout, "\t", vi_crunch);
1172       x += crunch_str(&iend, iw, stdout, "\t", vi_crunch);
1173     }
1174   }
1175   //when not inserting but still need to keep cursor inside screen
1176   //margin area
1177   else if ( aw+margin > TT.screen_width) {
1178     clip = aw-TT.screen_width+margin;
1179     end = line;
1180     aw -= crunch_nstr(&end, clip, bytes, 0, "\t", vi_crunch);
1181     x = crunch_str(&end, aw,  stdout, "\t", vi_crunch);
1182   }
1183   else {
1184     end = line;
1185     x = crunch_nstr(&end, aw, bytes, stdout, "\t", vi_crunch);
1186   }
1187   cx_scr = x;
1188   cy_scr = y;
1189   if (scr_buf->line->str_len > bytes) {
1190     x += crunch_str(&end, TT.screen_width-x,  stdout, "\t", vi_crunch);
1191   }
1192 
1193   if (scr_buf) scr_buf = scr_buf->down;
1194   // drawing cursor row ends
1195   ///////////////////////////////////////////////////////////////////
1196 
1197   //start drawing all other rows that needs update
1198   ///////////////////////////////////////////////////////////////////
1199   y = 0, scr_buf = scr_r;
1200 
1201   //if we moved around in long line might need to redraw everything
1202   if (clip != TT.drawn_col) redraw = 3;
1203 
1204   for (; y < TT.screen_height; y++ ) {
1205     int draw_line = 0;
1206     if (scr_buf == c_r) {
1207       scr_buf = scr_buf->down;
1208       continue;
1209     } else if (redraw) draw_line++;
1210     else if (scroll<0 && TT.screen_height-y-1<-scroll)
1211       scroll++, draw_line++;
1212     else if (scroll>0) scroll--, draw_line++;
1213 
1214     tty_jump(0, y);
1215     if (draw_line) {
1216 
1217       tty_esc("2K");
1218       if (scr_buf) {
1219         if (draw_line && scr_buf->line->str_data && scr_buf->line->str_len) {
1220           line = scr_buf->line->str_data;
1221           bytes = scr_buf->line->str_len;
1222 
1223           aw = crunch_nstr(&line, clip, bytes, 0, "\t", vi_crunch);
1224           crunch_str(&line, TT.screen_width-1, stdout, "\t", vi_crunch);
1225           if ( *line ) printf("@");
1226 
1227         }
1228       } else if (draw_line) printf("~");
1229     }
1230     if (scr_buf) scr_buf = scr_buf->down;
1231   }
1232 
1233   TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
1234 
1235   //finished updating visual area
1236 
1237   tty_jump(0, TT.screen_height);
1238   tty_esc("2K");
1239   switch (TT.vi_mode) {
1240     case 0:
1241     tty_esc("30;44m");
1242     printf("COMMAND|");
1243     break;
1244     case 1:
1245     tty_esc("30;42m");
1246     printf("NORMAL|");
1247     break;
1248     case 2:
1249     tty_esc("30;41m");
1250     printf("INSERT|");
1251     break;
1252 
1253   }
1254   //DEBUG
1255   tty_esc("m");
1256   utf_l = utf8_len(&c_r->line->str_data[TT.cur_col]);
1257   if (utf_l) {
1258     char t[5] = {0, 0, 0, 0, 0};
1259     strncpy(t, &c_r->line->str_data[TT.cur_col], utf_l);
1260     printf("utf: %d %s", utf_l, t);
1261   }
1262   printf("| %d, %d\n", cx_scr, cy_scr); //screen coord
1263 
1264   tty_jump(TT.screen_width-12, TT.screen_height);
1265   printf("| %d, %d\n", TT.cur_row, TT.cur_col);
1266 
1267   tty_esc("m");
1268   tty_jump(0, TT.screen_height+1);
1269   tty_esc("2K");
1270   if (!TT.vi_mode) {
1271     tty_esc("1m");
1272     printf("%s", il->str_data);
1273     tty_esc("m");
1274   } else tty_jump(cx_scr, cy_scr);
1275 
1276   xflush(1);
1277 
1278 }
1279 
check_cursor_bounds()1280 static void check_cursor_bounds()
1281 {
1282   if (c_r->line->str_len == 0) {
1283     TT.cur_col = 0;
1284     return;
1285   } else if (c_r->line->str_len-1 < TT.cur_col) TT.cur_col = c_r->line->str_len-1;
1286 
1287   if (TT.cur_col && utf8_width(&c_r->line->str_data[TT.cur_col],
1288         c_r->line->str_len-TT.cur_col) <= 0)
1289     TT.cur_col--, check_cursor_bounds();
1290 }
1291 
adjust_screen_buffer()1292 static void adjust_screen_buffer()
1293 {
1294   //search cursor and screen
1295   struct linelist *t = text;
1296   int c = -1, s = -1, i = 0;
1297   //searching cursor and screen line numbers
1298   for (;((c == -1) || (s == -1)) && t != 0; i++, t = t->down) {
1299     if (t == c_r) c = i;
1300     if (t == scr_r) s = i;
1301   }
1302   //adjust screen buffer so cursor is on drawing area
1303   if (c <= s) scr_r = c_r, s = c; //scroll up
1304   else {
1305     //drawing does not have wrapping so no need to check width
1306     int distance = c-s+1;
1307 
1308     if (distance > (int)TT.screen_height) {
1309       int adj = distance-TT.screen_height;
1310       for (;adj; adj--) scr_r = scr_r->down, s++; //scroll down
1311 
1312     }
1313   }
1314   TT.cur_row = c, TT.scr_row = s;
1315 
1316 }
1317 
1318 //return 0 if not ASCII nor UTF-8
1319 //this is not fully tested
1320 //naive implementation with branches
1321 //there is better branchless lookup table versions out there
1322 //1 0xxxxxxx
1323 //2 110xxxxx  10xxxxxx
1324 //3 1110xxxx  10xxxxxx  10xxxxxx
1325 //4 11110xxx  10xxxxxx  10xxxxxx  10xxxxxx
utf8_len(char * str)1326 static int utf8_len(char *str)
1327 {
1328   int len = 0;
1329   int i = 0;
1330   uint8_t *c = (uint8_t*)str;
1331   if (!c || !(*c)) return 0;
1332   if (*c < 0x7F) return 1;
1333   if ((*c & 0xE0) == 0xc0) len = 2;
1334   else if ((*c & 0xF0) == 0xE0 ) len = 3;
1335   else if ((*c & 0xF8) == 0xF0 ) len = 4;
1336   else return 0;
1337   c++;
1338   for (i = len-1; i > 0; i--) {
1339     if ((*c++ & 0xc0) != 0x80) return 0;
1340   }
1341   return len;
1342 }
1343 
1344 //get utf8 length and width at same time
utf8_lnw(int * width,char * str,int bytes)1345 static int utf8_lnw(int* width, char* str, int bytes)
1346 {
1347   wchar_t wc;
1348   int length = 1;
1349   *width = 1;
1350   if (*str == 0x09) {
1351     *width = TT.tabstop;
1352     return 1;
1353   }
1354   length = mbtowc(&wc, str, bytes);
1355   switch (length) {
1356   case -1:
1357     mbtowc(0,0,4);
1358   case 0:
1359     *width = 0;
1360     length = 0;
1361     break;
1362   default:
1363   *width = wcwidth(wc);
1364   }
1365   return length;
1366 }
1367 
1368 //try to estimate width of next "glyph" in terminal buffer
1369 //combining chars 0x300-0x36F shall be zero width
utf8_width(char * str,int bytes)1370 static int utf8_width(char *str, int bytes)
1371 {
1372   wchar_t wc;
1373   if (*str == 0x09) return TT.tabstop;
1374   switch (mbtowc(&wc, str, bytes)) {
1375   case -1:
1376     mbtowc(0,0,4);
1377   case 0:
1378     return -1;
1379   default:
1380   return wcwidth(wc);
1381   }
1382   return 0;
1383 }
1384 
utf8_dec(char key,char * utf8_scratch,int * sta_p)1385 static int utf8_dec(char key, char *utf8_scratch, int *sta_p)
1386 {
1387   int len = 0;
1388   char *c = utf8_scratch;
1389   c[*sta_p] = key;
1390   if (!(*sta_p))  *c = key;
1391   if (*c < 0x7F) { *sta_p = 1; return 1; }
1392   if ((*c & 0xE0) == 0xc0) len = 2;
1393   else if ((*c & 0xF0) == 0xE0 ) len = 3;
1394   else if ((*c & 0xF8) == 0xF0 ) len = 4;
1395   else {*sta_p = 0; return 0; }
1396 
1397   (*sta_p)++;
1398 
1399   if (*sta_p == 1) return 0;
1400   if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; }
1401 
1402   if (*sta_p == len) { c[(*sta_p)] = 0; return 1; }
1403 
1404   return 0;
1405 }
1406 
utf8_last(char * str,int size)1407 static char* utf8_last(char* str, int size)
1408 {
1409   char* end = str+size;
1410   int pos = size;
1411   int len = 0;
1412   int width = 0;
1413   while (pos >= 0) {
1414     len = utf8_lnw(&width, end, size-pos);
1415     if (len && width) return end;
1416     end--; pos--;
1417   }
1418   return 0;
1419 }
1420 
cur_left(int count0,int count1,char * unused)1421 static int cur_left(int count0, int count1, char* unused)
1422 {
1423   int count = count0*count1;
1424   TT.vi_mov_flag |= 0x80000000;
1425   for (;count--;) {
1426     if (!TT.cur_col) return 1;
1427 
1428     TT.cur_col--;
1429     check_cursor_bounds();
1430   }
1431   return 1;
1432 }
1433 
cur_right(int count0,int count1,char * unused)1434 static int cur_right(int count0, int count1, char* unused)
1435 {
1436   int count = count0*count1;
1437   for (;count--;) {
1438     if (c_r->line->str_len <= 1) return 1;
1439     if (TT.cur_col >= c_r->line->str_len-1) {
1440       TT.cur_col = utf8_last(c_r->line->str_data, c_r->line->str_len)
1441         - c_r->line->str_data;
1442       return 1;
1443     }
1444     TT.cur_col++;
1445     if (utf8_width(&c_r->line->str_data[TT.cur_col],
1446           c_r->line->str_len-TT.cur_col) <= 0)
1447       cur_right(1, 1, 0);
1448   }
1449   return 1;
1450 }
1451 
cur_up(int count0,int count1,char * unused)1452 static int cur_up(int count0, int count1, char* unused)
1453 {
1454   int count = count0*count1;
1455   for (;count-- && c_r->up;)
1456     c_r = c_r->up;
1457 
1458   TT.vi_mov_flag |= 0x80000000;
1459   check_cursor_bounds();
1460   return 1;
1461 }
1462 
cur_down(int count0,int count1,char * unused)1463 static int cur_down(int count0, int count1, char* unused)
1464 {
1465   int count = count0*count1;
1466   for (;count-- && c_r->down;)
1467     c_r = c_r->down;
1468 
1469   check_cursor_bounds();
1470   return 1;
1471 }
1472 
1473