Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
12 KiB

  1. #include <X11/keysym.h>
  2. #include <X11/XKBlib.h>
  3. #include "normalMode.h"
  4. #include "utils.h"
  5. extern Glyph const styleSearch, style[];
  6. extern char const wDelS[], wDelL[], *nmKeys[];
  7. extern unsigned int bg[], fg, currentBg, highlightBg, highlightFg, amountNmKeys;
  8. typedef struct { int p[3]; } Pos;
  9. struct NormalModeState {
  10. struct OperationState {
  11. enum Op {noop=0, visual='v', visualLine='V', yank = 'y'} op;
  12. enum Infix {infix_none=0, infix_i='i', infix_a='a'} infix;
  13. } cmd;
  14. struct MotionState {
  15. uint32_t c; int active; Pos searchPos;
  16. enum Search {none=0, fw='/', bw='?'} search;
  17. } m;
  18. } defaultNormalMode, state;
  19. DynamicArray searchStr=UTF8_ARRAY, cCmd=UTF8_ARRAY, lCmd=UTF8_ARRAY;
  20. Glyph styleCmd;
  21. char posBuffer[10], brack[6][2] = { {"()"}, {"<>"}, {"{}"}, {"[]"}, {"\"\""}, {"''"}};
  22. int exited=1, overlay=1;
  23. static inline uint32_t cchar() { return term.line[term.c.y][term.c.x].u; }
  24. static inline int pos(int p, int h) {return IS_SET(MODE_ALTSCREEN)?p:rangeY(p+h*histOff-insertOff);}
  25. static inline int contains(char c, char const * values, uint32_t memSize) {
  26. for (uint32_t i = 0; i < memSize; ++i) if (c == values[i]) return 1;
  27. return 0;
  28. }
  29. static inline void decodeTo(char const *cs, int len, DynamicArray *darr) {
  30. char *var = expand(darr);
  31. if (!var) empty(darr); else utf8decode(cs, (Rune*)(var), len);
  32. }
  33. static inline void applyPos(Pos p) {
  34. term.c.x = p.p[0], term.c.y = p.p[1];
  35. if (!IS_SET(MODE_ALTSCREEN) && histOp) term.line = &buf[histOff = p.p[2]];
  36. }
  37. /// Find string in history buffer, and provide string-match-lookup for highlighting matches
  38. static int highlighted(int x, int y) {
  39. int const s=term.row*term.col, i=y*term.col+x, sz=size(&searchStr);
  40. return sz && i<s && mark[i]!=sz && i+mark[i]<s && !mark[i+mark[i]];
  41. }
  42. static void markSearchMatches(int all) {
  43. int sz = size(&searchStr), ox = 0, oy = 0, oi=0;
  44. for (int y=0; sz && all && y<term.row; ++y)
  45. for (int x=0; x<term.col; ++x) term.dirty[y] |= highlighted(x, y);
  46. for (int y = 0, wi=0, owi=0, i=0; sz && y < term.row; ++y)
  47. for (int x=0; x<term.col; ++x, wi%=sz, ++i, owi=wi)
  48. if (all || term.dirty[y]) {
  49. mark[i]=sz-(wi=(getU32(&searchStr,wi,1)==term.line[y][x].u?wi+1:0));
  50. if (wi==1) ox=x, oy=y, oi=i; else if (!wi && owi) x=ox, y=oy, i=oi;
  51. }
  52. for (int y=0; sz &&all &&y<term.row; ++y)
  53. for (int x=0; x<term.col; ++x) term.dirty[y] |= highlighted(x, y);
  54. }
  55. static int findString(int8_t s, int all) {
  56. Pos p = (Pos) {.p={term.c.x, term.c.y, IS_SET(MODE_ALTSCREEN) ? 0 : histOff}};
  57. historyMove(s, 0, 0);
  58. uint32_t strSz=size(&searchStr), maxIter=rows()*term.col+strSz, widx=0;
  59. for (uint32_t i=0, wi = 0; widx<strSz && ++i<=maxIter; historyMove(s, 0, 0), wi=widx) {
  60. widx = (getU32(&searchStr, widx, s>0)==cchar())?widx+1:0;
  61. if (wi && !widx) historyMove(-s*wi, 0, 0);
  62. }
  63. if (widx == strSz && widx) historyMove(-s * strSz, 0, 0);
  64. else applyPos(p);
  65. markSearchMatches(all);
  66. return widx == strSz;
  67. }
  68. /// Execute series of normal-mode commands from char array / decoded from dynamic array
  69. static ExitState pressKeys(char const* s, size_t e) {
  70. ExitState x=succ;
  71. for (size_t i=0; i<e && (x=(!s[i] ? x : kpressHist(&s[i], 1, 0, NULL))); ++i);
  72. return x;
  73. }
  74. static ExitState executeCommand(uint32_t *c, size_t z) {
  75. ExitState x=succ;
  76. char dc [32];
  77. for (size_t i=0; i<z && (x=kpressHist(dc,utf8encode(c[i],dc),0,NULL));++i);
  78. return x;
  79. }
  80. /// Get character for overlay, if the overlay (st) has something to show, else normal char.
  81. static void getChar(DynamicArray *st, Glyph *glyphChange, int y, int xEnd, int width, int x) {
  82. if (x < xEnd - min(width=min(width,xEnd), size(st))) *glyphChange = term.line[y][x];
  83. else if (x<xEnd) glyphChange->u = *((Rune*)(st->content + (size(st)+x-xEnd)*st->elSize));
  84. }
  85. /// Expand "infix" expression: for instance (w =>) l b | | v e | | y
  86. static ExitState expandExpression(char c) { // ({ =>) l ? { \n | l | v / } \n | h | y
  87. int a=state.cmd.infix==infix_a, yank=state.cmd.op=='y', lc=tolower(c), found=1;
  88. state.cmd.infix = infix_none;
  89. if(!yank && state.cmd.op!=visual && state.cmd.op!=visualLine) return failed;
  90. char mot[11] = {'l', 0, 'b', 0, 0, 'v', 0, 'e', 0, 0, yank ? 'y' : 0};
  91. if (lc == 'w') mot[2] = 'b' - lc + c, mot[7] = (a ? 'w' : 'e') - lc + c, mot[9]=a?'h':0;
  92. else {
  93. mot[1]='?', mot[3]=mot[8]='\n', mot[6]='/', mot[4]=a?0:'l', mot[9]=a?0:'h';
  94. for (int i=found=0; !found && i < 6; ++i)
  95. if ((found=contains(c,brack[i],2))) mot[2]=brack[i][0], mot[7]=brack[i][1];
  96. }
  97. if (!found) return failed;
  98. assign(&lCmd, &cCmd);
  99. empty(&cCmd);
  100. state.cmd = defaultNormalMode.cmd;
  101. return pressKeys(mot, 11);
  102. }
  103. int executeMotion(char const cs, int len, KeySym const *const ks) {
  104. state.m.c = max(state.m.c, 1);
  105. if (ks && *ks == XK_d) historyMove(0, 0, term.row / 2);
  106. else if (ks && *ks == XK_u) historyMove(0, 0, -term.row / 2);
  107. else if (ks && *ks == XK_f) historyMove(0, 0, term.row-1+(term.c.y=0));
  108. else if (ks && *ks == XK_b) historyMove(0, 0, -(term.c.y=term.row-1));
  109. else if (ks && *ks == XK_h) overlay = !overlay;
  110. else if (!len) return failed;
  111. else if (cs == 'K') historyMove(0, 0, -state.m.c);
  112. else if (cs == 'J') historyMove(0, 0, state.m.c);
  113. else if (cs == 'k') historyMove(0, -state.m.c, 0);
  114. else if (cs == 'j') historyMove(0, state.m.c, 0);
  115. else if (cs == 'h') historyMove(-state.m.c, 0, 0);
  116. else if (cs == 'l') historyMove( state.m.c, 0, 0);
  117. else if (cs == 'H') term.c.y = 0;
  118. else if (cs == 'M') term.c.y = term.bot / 2;
  119. else if (cs == 'L') term.c.y = term.bot;
  120. else if (cs == 's' || cs == 'S') altToggle = cs == 's' ? !altToggle : 1;
  121. else if (cs == 'G' || cs == 'g') {
  122. if (cs == 'G') term.c = c[0] = c[IS_SET(MODE_ALTSCREEN)+1];
  123. if (!IS_SET(MODE_ALTSCREEN)) term.line = &buf[histOff=insertOff];
  124. } else if (cs == '0') term.c.x = 0;
  125. else if (cs == '$') term.c.x = term.col-1;
  126. else if (cs == 't') sel.type = sel.type==SEL_REGULAR ? SEL_RECTANGULAR : SEL_REGULAR;
  127. else if (cs == 'n' || cs == 'N') {
  128. int const d = ((cs=='N')!=(state.m.search==bw))?-1:1;
  129. for (int i = state.m.c; i && findString(d, 0); --i);
  130. } else if (contains(cs, "wWeEbB", 6)) {
  131. int const low=cs<=90, off=tolower(cs)!='w', sgn=(tolower(cs)=='b')?-1:1,
  132. l=strlen(wDelL), s=strlen(wDelS), mit=rows()*term.col;
  133. for (int it=0, on=0; state.m.c > 0; ++it) {
  134. if (off || it) if (!historyMove(sgn, 0, 0)) it = mit; //< offset move
  135. int n = 1<<(contains(cchar(),wDelS,s) ?(2-low) :!contains(cchar(),wDelL,l)),
  136. found = (on|=n)^n && ((off ?on^n :n)!=1); //< state change &letter state
  137. if (found && off) historyMove(-sgn, 0, 0); //< offset move if required
  138. if (found || it>mit) it=-1, on=0, --state.m.c; //< terminate iteration
  139. }
  140. } else return failed;
  141. state.m.c = 0;
  142. return state.cmd.op == yank ? exitMotion : succ;
  143. }
  144. ExitState kpressHist(char const *cs, int len, int ctrl, KeySym const *ksym) {
  145. historyOpToggle(1, 1);
  146. int const prevYOff=IS_SET(MODE_ALTSCREEN)?0:histOff, search=state.m.search&&state.m.active,
  147. prevAltToggle=altToggle, prevOverlay=overlay;
  148. int const noOp=!state.cmd.op&&!state.cmd.infix, num=len==1&&BETWEEN(cs[0],48,57),
  149. esc=ksym&&*ksym==XK_Escape, ret=(ksym&&*ksym==XK_Return)||(len==1&&cs[0]=='\n'),
  150. quantifier=num&&(cs[0]!='0'||state.m.c), ins=!search &&noOp &&len &&cs[0]=='i';
  151. exited = 0;
  152. ExitState result = succ;
  153. if (esc || ret || ins) { result = exitMotion, len = 0;
  154. } else if (ksym && *ksym == XK_BackSpace) {
  155. if ((search || state.m.c) && size(&cCmd)) pop(&cCmd);
  156. if (search) {
  157. if (size(&searchStr)) pop(&searchStr);
  158. else result = exitMotion;
  159. if (!size(&searchStr)) tfulldirt();
  160. applyPos(state.m.searchPos);
  161. findString(state.m.search==fw ? 1 : -1, 1);
  162. } else if (state.m.c) state.m.c /= 10;
  163. len = 0;
  164. } else if (search) {
  165. if (len >= 1) decodeTo(cs, len, &searchStr);
  166. applyPos(state.m.searchPos);
  167. findString(state.m.search==fw ? 1 : -1, 1);
  168. } else if (len == 0) { result = failed;
  169. } else if (quantifier) { state.m.c = min(SHRT_MAX, state.m.c*10+cs[0]-48);
  170. } else if (state.cmd.infix && state.cmd.op && (result = expandExpression(cs[0]), len=0)) {
  171. } else if (cs[0] == '.') {
  172. if (size(&cCmd)) assign(&lCmd, &cCmd);
  173. empty(&cCmd);
  174. executeCommand((uint32_t*) lCmd.content, size(&lCmd));
  175. empty(&cCmd);
  176. len = 0;
  177. } else if (cs[0] == 'r') { tfulldirt();
  178. } else if (cs[0] == 'c') {
  179. empty(&lCmd);
  180. empty(&cCmd);
  181. empty(&searchStr);
  182. tfulldirt();
  183. len = 0;
  184. } else if (cs[0] == fw || cs[0] == bw) {
  185. empty(&searchStr);
  186. state.m.search = cs[0];
  187. state.m.searchPos = (Pos){.p={term.c.x, term.c.y, prevYOff}};
  188. state.m.active = 1;
  189. } else if (cs[0]==infix_i || cs[0]==infix_a) { state.cmd.infix=cs[0];
  190. } else if (cs[0] == 'y') {
  191. if (state.cmd.op) {
  192. result = (state.cmd.op == yank) ? exitOp : exitMotion;
  193. if (state.cmd.op == yank) selstart(0, term.c.y, 0);
  194. } else selstart(term.c.x, term.c.y, 0);
  195. state.cmd.op = yank;
  196. } else if (cs[0] == visual || cs[0] == visualLine) {
  197. if (state.cmd.op != (unsigned char) cs[0]) {
  198. state.cmd = defaultNormalMode.cmd;
  199. state.cmd.op = cs[0];
  200. selstart(cs[0] == visualLine ?0 :term.c.x, term.c.y, 0);
  201. } else result = exitOp;
  202. } else if (!(result =executeMotion(len?cs[0]:0, len, ctrl?ksym:NULL))) {
  203. result=failed;
  204. for (size_t i = 0; !ctrl && i < amountNmKeys; ++i)
  205. if (cs[0]==nmKeys[i][0] &&
  206. failed!=(result=pressKeys(&nmKeys[i][1], strlen(nmKeys[i])-1))) goto end;
  207. } // Operation/Motion finished if valid: update cmd string, extend selection, update search
  208. if (result != failed) {
  209. if (len == 1 && !ctrl) decodeTo(cs, len, &cCmd);
  210. if ((state.cmd.op == visualLine) || ((state.cmd.op == yank) && (result == exitOp))) {
  211. int const off = pos(term.c.y, 1) < pos(sel.ob.y, 0);
  212. sel.ob.x = off ? term.col - 1 : 0;
  213. selextend(off ? 0 : term.col-1, term.c.y, sel.type, 0);
  214. } else if (sel.oe.x != -1) selextend(term.c.x, term.c.y, sel.type, 0);
  215. } // Set repaint for motion or status bar
  216. if (!IS_SET(MODE_ALTSCREEN) && prevYOff != histOff) tfulldirt();
  217. // Terminate Motion / operation if thus indicated
  218. if (result == exitMotion) {
  219. if (!state.m.active) result = (exited=noOp) ? finished : exitOp;
  220. state.m.active = state.m.c = 0;
  221. }
  222. if (result == exitOp || result == finished) {
  223. if (state.cmd.op == yank) {
  224. xsetsel(getsel());
  225. xclipcopy();
  226. }
  227. state = defaultNormalMode;
  228. selclear();
  229. if (!esc) assign(&lCmd, &cCmd);
  230. empty(&cCmd);
  231. } // Update the content displayed in the history overlay
  232. styleCmd = style[state.cmd.op==yank ? 1 : (state.cmd.op==visual ? 2 :
  233. (state.cmd.op==visualLine ? 3 :0))];
  234. int const posLin = !IS_SET(MODE_ALTSCREEN) ? rangeY(insertOff-histOff):0, h=rows()-term.row;
  235. if (!posLin || posLin==h || !h) strcpy(posBuffer, posLin ? " [BOT] " : " [TOP] ");
  236. else sprintf(posBuffer, " % 3d%c ", min(100, max(0, .5 + posLin * 100. / h)),'%');
  237. if ((overlay || overlay!=prevOverlay) && term.col>9 && term.row>4) {
  238. if (!term.dirty[term.row-1]) xdrawline(term.line[term.row-1], term.col*2/3, term.row-1, term.col-1);
  239. if (!term.dirty[term.row-2]) xdrawline(term.line[term.row-2], term.col*2/3, term.row-2, term.col-1);
  240. }
  241. if (result==finished) altToggle = 0;
  242. if (altToggle != prevAltToggle) tswapscreen();
  243. end:
  244. historyOpToggle(-1, 1);
  245. return result;
  246. }
  247. void historyOverlay(int x, int y, Glyph* g) {
  248. if (!histMode) return;
  249. TCursor const *cHist = histOp ? &term.c : &c[0];
  250. if(overlay && term.col > 9 && term.row > 4 && (x > (2*term.col/3)) && (y >= (term.row-2))) {
  251. *g = (y == term.row - 2) ? styleSearch : styleCmd;
  252. if (y == term.row-2) getChar(&searchStr, g, term.row-2, term.col-2, term.col/3, x);
  253. else if (x > term.col - 7) g->u = posBuffer[x - term.col + 7];
  254. else getChar(size(&cCmd) ?&cCmd :&lCmd, g, term.row-1, term.col-7, term.col/3-6, x);
  255. } else if (highlighted(x, y)) g->bg = highlightBg, g->fg = highlightFg;
  256. else if ((x==cHist->x) ^ (y==cHist->y)) g->bg = currentBg;
  257. else if (x==cHist->x) g->mode^=ATTR_REVERSE;
  258. }
  259. void historyPreDraw() {
  260. static Pos op = {.p={0, 0, 0}};
  261. historyOpToggle(1, 0);
  262. // Draw the cursor cross if changed
  263. if (term.c.y >= term.row || op.p[1] >= term.row) tfulldirt();
  264. else if (exited || (op.p[1] != term.c.y)) term.dirty[term.c.y] = term.dirty[op.p[1]] = 1;
  265. for (int i=0; (exited || term.c.x != op.p[0]) && i<term.row; ++i) if (!term.dirty[i]) {
  266. xdrawline(term.line[i], term.c.x, i, term.c.x + 1);
  267. xdrawline(term.line[i], op.p[0], i, op.p[0] + 1);
  268. }
  269. // Update search results either only for lines with new content or all results if exiting
  270. markSearchMatches(exited);
  271. op = (Pos){.p = {term.c.x, term.c.y, 0}};
  272. historyOpToggle(-1, 0);
  273. }