vi.c 42 KB

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