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