vi.c 47 KB

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