st-vimBrowse-20200212-26cdfeb.diff (85174B)
1 From 22471790fcaf1505890bcb53752a6df87cfd3592 Mon Sep 17 00:00:00 2001 2 From: Julius Huelsmann <juliusHuelsmann@gmail.com> 3 Date: Wed, 12 Feb 2020 19:50:06 +0100 4 Subject: [PATCH] port: vim patch 2020-02 to current HEAD of st 5 6 --- 7 Makefile | 9 +- 8 config.def.h | 54 ++++ 9 config.h | 513 +++++++++++++++++++++++++++++++++ 10 dynamicArray.h | 175 ++++++++++++ 11 error.h | 47 ++++ 12 glyph.h | 30 ++ 13 normalMode.c | 750 +++++++++++++++++++++++++++++++++++++++++++++++++ 14 normalMode.h | 36 +++ 15 st.c | 249 +++++++++------- 16 st.h | 36 ++- 17 term.h | 73 +++++ 18 win.h | 2 + 19 x.c | 48 +++- 20 13 files changed, 1894 insertions(+), 128 deletions(-) 21 create mode 100644 config.h 22 create mode 100644 dynamicArray.h 23 create mode 100644 error.h 24 create mode 100644 glyph.h 25 create mode 100644 normalMode.c 26 create mode 100644 normalMode.h 27 create mode 100644 term.h 28 29 diff --git a/Makefile b/Makefile 30 index 470ac86..af8f0be 100644 31 --- a/Makefile 32 +++ b/Makefile 33 @@ -4,7 +4,7 @@ 34 35 include config.mk 36 37 -SRC = st.c x.c 38 +SRC = st.c x.c normalMode.c 39 OBJ = $(SRC:.c=.o) 40 41 all: options st 42 @@ -21,8 +21,8 @@ config.h: 43 .c.o: 44 $(CC) $(STCFLAGS) -c $< 45 46 -st.o: config.h st.h win.h 47 -x.o: arg.h config.h st.h win.h 48 +st.o: config.h st.h win.h dynamicArray.h normalMode.h term.h glyph.h error.h 49 +x.o: arg.h config.h st.h win.h dynamicArray.h normalMode.h term.h glyph.h error.h 50 51 $(OBJ): config.h config.mk 52 53 @@ -35,7 +35,8 @@ clean: 54 dist: clean 55 mkdir -p st-$(VERSION) 56 cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ 57 - config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ 58 + config.def.h st.info st.1 arg.h st.h win.h dynamicArray.h\ 59 + normalMode.h term.h error.h $(SRC)\ 60 st-$(VERSION) 61 tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz 62 rm -rf st-$(VERSION) 63 diff --git a/config.def.h b/config.def.h 64 index 546edda..92b541d 100644 65 --- a/config.def.h 66 +++ b/config.def.h 67 @@ -149,6 +149,14 @@ static unsigned int mousebg = 0; 68 * doesn't match the ones requested. 69 */ 70 static unsigned int defaultattr = 11; 71 +/// Colors for the entities that are 'highlighted' in normal mode (search 72 +/// results currently on screen) [Vim Browse]. 73 +static unsigned int highlightBg = 160; 74 +static unsigned int highlightFg = 15; 75 +/// Colors for highlighting the current cursor position (row + col) in normal 76 +/// mode [Vim Browse]. 77 +static unsigned int currentBg = 8; 78 +static unsigned int currentFg = 15; 79 80 /* 81 * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 82 @@ -170,10 +178,12 @@ static MouseShortcut mshortcuts[] = { 83 84 /* Internal keyboard shortcuts. */ 85 #define MODKEY Mod1Mask 86 +#define AltMask Mod1Mask 87 #define TERMMOD (ControlMask|ShiftMask) 88 89 static Shortcut shortcuts[] = { 90 /* mask keysym function argument */ 91 + { AltMask, XK_c, normalMode, {.i = 0} }, 92 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 93 { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 94 { ShiftMask, XK_Print, printscreen, {.i = 0} }, 95 @@ -186,6 +196,8 @@ static Shortcut shortcuts[] = { 96 { TERMMOD, XK_Y, selpaste, {.i = 0} }, 97 { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 98 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 99 + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 100 + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 101 }; 102 103 /* 104 @@ -457,3 +469,45 @@ static char ascii_printable[] = 105 " !\"#$%&'()*+,-./0123456789:;<=>?" 106 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 107 "`abcdefghijklmnopqrstuvwxyz{|}~"; 108 + 109 + 110 +/// word sepearors normal mode 111 +/// [Vim Browse]. 112 +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; 113 +char wordDelimLarge[] = " \t"; /// <Word sepearors normal mode (capital W) 114 + 115 +/// Shortcusts executed in normal mode (which should not already be in use) 116 +/// [Vim Browse]. 117 +struct NormalModeShortcuts normalModeShortcuts [] = { 118 + { 'R', "?Building\n" }, 119 + { 'r', "/Building\n" }, 120 + { 'F', "?: error:\n" }, 121 + { 'f', "/: error:\n" }, 122 + { 'Q', "?[Leaving vim, starting execution]\n" }, 123 + { 'S', "Qf" }, 124 + { 'X', "?juli@machine\n" }, 125 + { 'x', "/juli@machine\n" }, 126 +}; 127 + 128 +size_t const amountNormalModeShortcuts = sizeof(normalModeShortcuts) / sizeof(*normalModeShortcuts); 129 + 130 +/// Style of the command string visualized in normal mode in the right corner 131 +/// [Vim Browse]. 132 +Glyph const styleCommand = {' ', ATTR_ITALIC | ATTR_FAINT, 7, 16}; 133 +/// Style of the search string visualized in normal mode in the right corner. 134 +/// [Vim Browse]. 135 +Glyph const styleSearch = {' ', ATTR_ITALIC | ATTR_BOLD_FAINT, 7, 16}; 136 + 137 +/// Colors used in normal mode in order to highlight different operations and 138 +/// empathise the current position on screen in the status area [Vim Browse]. 139 +unsigned int bgCommandYank = 11; 140 +unsigned int bgCommandVisual = 4; 141 +unsigned int bgCommandVisualLine = 12; 142 + 143 +unsigned int fgCommandYank = 232; 144 +unsigned int fgCommandVisual = 232; 145 +unsigned int fgCommandVisualLine = 232; 146 + 147 +unsigned int bgPos = 15; 148 +unsigned int fgPos = 16; 149 + 150 diff --git a/config.h b/config.h 151 new file mode 100644 152 index 0000000..92b541d 153 --- /dev/null 154 +++ b/config.h 155 @@ -0,0 +1,513 @@ 156 +/* See LICENSE file for copyright and license details. */ 157 + 158 +/* 159 + * appearance 160 + * 161 + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html 162 + */ 163 +static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true"; 164 +static int borderpx = 2; 165 + 166 +/* 167 + * What program is execed by st depends of these precedence rules: 168 + * 1: program passed with -e 169 + * 2: utmp option 170 + * 3: SHELL environment variable 171 + * 4: value of shell in /etc/passwd 172 + * 5: value of shell in config.h 173 + */ 174 +static char *shell = "/bin/sh"; 175 +char *utmp = NULL; 176 +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; 177 + 178 +/* identification sequence returned in DA and DECID */ 179 +char *vtiden = "\033[?6c"; 180 + 181 +/* Kerning / character bounding-box multipliers */ 182 +static float cwscale = 1.0; 183 +static float chscale = 1.0; 184 + 185 +/* 186 + * word delimiter string 187 + * 188 + * More advanced example: L" `'\"()[]{}" 189 + */ 190 +wchar_t *worddelimiters = L" "; 191 + 192 +/* selection timeouts (in milliseconds) */ 193 +static unsigned int doubleclicktimeout = 300; 194 +static unsigned int tripleclicktimeout = 600; 195 + 196 +/* alt screens */ 197 +int allowaltscreen = 1; 198 + 199 +/* frames per second st should at maximum draw to the screen */ 200 +static unsigned int xfps = 120; 201 +static unsigned int actionfps = 30; 202 + 203 +/* 204 + * blinking timeout (set to 0 to disable blinking) for the terminal blinking 205 + * attribute. 206 + */ 207 +static unsigned int blinktimeout = 800; 208 + 209 +/* 210 + * thickness of underline and bar cursors 211 + */ 212 +static unsigned int cursorthickness = 2; 213 + 214 +/* 215 + * bell volume. It must be a value between -100 and 100. Use 0 for disabling 216 + * it 217 + */ 218 +static int bellvolume = 0; 219 + 220 +/* default TERM value */ 221 +char *termname = "st-256color"; 222 + 223 +/* 224 + * spaces per tab 225 + * 226 + * When you are changing this value, don't forget to adapt the »it« value in 227 + * the st.info and appropriately install the st.info in the environment where 228 + * you use this st version. 229 + * 230 + * it#$tabspaces, 231 + * 232 + * Secondly make sure your kernel is not expanding tabs. When running `stty 233 + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by 234 + * running following command: 235 + * 236 + * stty tabs 237 + */ 238 +unsigned int tabspaces = 8; 239 + 240 +/* Terminal colors (16 first used in escape sequence) */ 241 +static const char *colorname[] = { 242 + /* 8 normal colors */ 243 + "black", 244 + "red3", 245 + "green3", 246 + "yellow3", 247 + "blue2", 248 + "magenta3", 249 + "cyan3", 250 + "gray90", 251 + 252 + /* 8 bright colors */ 253 + "gray50", 254 + "red", 255 + "green", 256 + "yellow", 257 + "#5c5cff", 258 + "magenta", 259 + "cyan", 260 + "white", 261 + 262 + [255] = 0, 263 + 264 + /* more colors can be added after 255 to use with DefaultXX */ 265 + "#cccccc", 266 + "#555555", 267 +}; 268 + 269 + 270 +/* 271 + * Default colors (colorname index) 272 + * foreground, background, cursor, reverse cursor 273 + */ 274 +unsigned int defaultfg = 7; 275 +unsigned int defaultbg = 0; 276 +static unsigned int defaultcs = 256; 277 +static unsigned int defaultrcs = 257; 278 + 279 +/* 280 + * Default shape of cursor 281 + * 2: Block ("█") 282 + * 4: Underline ("_") 283 + * 6: Bar ("|") 284 + * 7: Snowman ("☃") 285 + */ 286 +static unsigned int cursorshape = 2; 287 + 288 +/* 289 + * Default columns and rows numbers 290 + */ 291 + 292 +static unsigned int cols = 80; 293 +static unsigned int rows = 24; 294 + 295 +/* 296 + * Default colour and shape of the mouse cursor 297 + */ 298 +static unsigned int mouseshape = XC_xterm; 299 +static unsigned int mousefg = 7; 300 +static unsigned int mousebg = 0; 301 + 302 +/* 303 + * Color used to display font attributes when fontconfig selected a font which 304 + * doesn't match the ones requested. 305 + */ 306 +static unsigned int defaultattr = 11; 307 +/// Colors for the entities that are 'highlighted' in normal mode (search 308 +/// results currently on screen) [Vim Browse]. 309 +static unsigned int highlightBg = 160; 310 +static unsigned int highlightFg = 15; 311 +/// Colors for highlighting the current cursor position (row + col) in normal 312 +/// mode [Vim Browse]. 313 +static unsigned int currentBg = 8; 314 +static unsigned int currentFg = 15; 315 + 316 +/* 317 + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 318 + * Note that if you want to use ShiftMask with selmasks, set this to an other 319 + * modifier, set to 0 to not use it. 320 + */ 321 +static uint forcemousemod = ShiftMask; 322 + 323 +/* 324 + * Internal mouse shortcuts. 325 + * Beware that overloading Button1 will disable the selection. 326 + */ 327 +static MouseShortcut mshortcuts[] = { 328 + /* mask button function argument release */ 329 + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 330 + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 331 + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 332 +}; 333 + 334 +/* Internal keyboard shortcuts. */ 335 +#define MODKEY Mod1Mask 336 +#define AltMask Mod1Mask 337 +#define TERMMOD (ControlMask|ShiftMask) 338 + 339 +static Shortcut shortcuts[] = { 340 + /* mask keysym function argument */ 341 + { AltMask, XK_c, normalMode, {.i = 0} }, 342 + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 343 + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 344 + { ShiftMask, XK_Print, printscreen, {.i = 0} }, 345 + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, 346 + { TERMMOD, XK_Prior, zoom, {.f = +1} }, 347 + { TERMMOD, XK_Next, zoom, {.f = -1} }, 348 + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, 349 + { TERMMOD, XK_C, clipcopy, {.i = 0} }, 350 + { TERMMOD, XK_V, clippaste, {.i = 0} }, 351 + { TERMMOD, XK_Y, selpaste, {.i = 0} }, 352 + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 353 + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 354 + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 355 + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 356 +}; 357 + 358 +/* 359 + * Special keys (change & recompile st.info accordingly) 360 + * 361 + * Mask value: 362 + * * Use XK_ANY_MOD to match the key no matter modifiers state 363 + * * Use XK_NO_MOD to match the key alone (no modifiers) 364 + * appkey value: 365 + * * 0: no value 366 + * * > 0: keypad application mode enabled 367 + * * = 2: term.numlock = 1 368 + * * < 0: keypad application mode disabled 369 + * appcursor value: 370 + * * 0: no value 371 + * * > 0: cursor application mode enabled 372 + * * < 0: cursor application mode disabled 373 + * 374 + * Be careful with the order of the definitions because st searches in 375 + * this table sequentially, so any XK_ANY_MOD must be in the last 376 + * position for a key. 377 + */ 378 + 379 +/* 380 + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) 381 + * to be mapped below, add them to this array. 382 + */ 383 +static KeySym mappedkeys[] = { -1 }; 384 + 385 +/* 386 + * State bits to ignore when matching key or button events. By default, 387 + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. 388 + */ 389 +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; 390 + 391 +/* 392 + * This is the huge key array which defines all compatibility to the Linux 393 + * world. Please decide about changes wisely. 394 + */ 395 +static Key key[] = { 396 + /* keysym mask string appkey appcursor */ 397 + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, 398 + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, 399 + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, 400 + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 401 + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, 402 + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, 403 + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, 404 + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, 405 + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, 406 + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, 407 + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, 408 + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, 409 + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, 410 + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, 411 + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, 412 + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, 413 + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, 414 + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 415 + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, 416 + { XK_KP_End, ControlMask, "\033[J", -1, 0}, 417 + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, 418 + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, 419 + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, 420 + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, 421 + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, 422 + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 423 + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, 424 + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, 425 + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, 426 + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, 427 + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 428 + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 429 + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, 430 + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, 431 + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, 432 + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, 433 + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 434 + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 435 + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, 436 + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, 437 + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, 438 + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, 439 + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, 440 + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, 441 + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, 442 + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, 443 + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, 444 + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, 445 + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, 446 + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, 447 + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, 448 + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, 449 + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, 450 + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, 451 + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, 452 + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, 453 + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, 454 + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, 455 + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, 456 + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, 457 + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, 458 + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, 459 + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, 460 + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, 461 + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, 462 + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, 463 + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, 464 + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, 465 + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, 466 + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, 467 + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, 468 + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, 469 + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, 470 + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, 471 + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, 472 + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, 473 + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, 474 + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, 475 + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, 476 + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, 477 + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, 478 + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, 479 + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, 480 + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, 481 + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, 482 + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, 483 + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, 484 + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, 485 + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, 486 + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, 487 + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, 488 + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, 489 + { XK_Return, Mod1Mask, "\033\r", 0, 0}, 490 + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, 491 + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, 492 + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, 493 + { XK_Insert, ControlMask, "\033[L", -1, 0}, 494 + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, 495 + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, 496 + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, 497 + { XK_Delete, ControlMask, "\033[M", -1, 0}, 498 + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, 499 + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, 500 + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, 501 + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, 502 + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, 503 + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, 504 + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, 505 + { XK_Home, ShiftMask, "\033[2J", 0, -1}, 506 + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, 507 + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, 508 + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, 509 + { XK_End, ControlMask, "\033[J", -1, 0}, 510 + { XK_End, ControlMask, "\033[1;5F", +1, 0}, 511 + { XK_End, ShiftMask, "\033[K", -1, 0}, 512 + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, 513 + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, 514 + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, 515 + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, 516 + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, 517 + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, 518 + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, 519 + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, 520 + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, 521 + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, 522 + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, 523 + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, 524 + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, 525 + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, 526 + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, 527 + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, 528 + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, 529 + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, 530 + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, 531 + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, 532 + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, 533 + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, 534 + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, 535 + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, 536 + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, 537 + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, 538 + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, 539 + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, 540 + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, 541 + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, 542 + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, 543 + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, 544 + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, 545 + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, 546 + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, 547 + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, 548 + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, 549 + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, 550 + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, 551 + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, 552 + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, 553 + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, 554 + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, 555 + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, 556 + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, 557 + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, 558 + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, 559 + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, 560 + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, 561 + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, 562 + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, 563 + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, 564 + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, 565 + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, 566 + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, 567 + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, 568 + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, 569 + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, 570 + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, 571 + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, 572 + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, 573 + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, 574 + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, 575 + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, 576 + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, 577 + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, 578 + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, 579 + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, 580 + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, 581 + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, 582 + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, 583 + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, 584 + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, 585 + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, 586 + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, 587 + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, 588 + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, 589 + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, 590 + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, 591 + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, 592 + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, 593 + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, 594 + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, 595 + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, 596 + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, 597 + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, 598 + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, 599 + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, 600 + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, 601 + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, 602 + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, 603 + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, 604 + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, 605 + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, 606 +}; 607 + 608 +/* 609 + * Selection types' masks. 610 + * Use the same masks as usual. 611 + * Button1Mask is always unset, to make masks match between ButtonPress. 612 + * ButtonRelease and MotionNotify. 613 + * If no match is found, regular selection is used. 614 + */ 615 +static uint selmasks[] = { 616 + [SEL_RECTANGULAR] = Mod1Mask, 617 +}; 618 + 619 +/* 620 + * Printable characters in ASCII, used to estimate the advance width 621 + * of single wide characters. 622 + */ 623 +static char ascii_printable[] = 624 + " !\"#$%&'()*+,-./0123456789:;<=>?" 625 + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 626 + "`abcdefghijklmnopqrstuvwxyz{|}~"; 627 + 628 + 629 +/// word sepearors normal mode 630 +/// [Vim Browse]. 631 +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; 632 +char wordDelimLarge[] = " \t"; /// <Word sepearors normal mode (capital W) 633 + 634 +/// Shortcusts executed in normal mode (which should not already be in use) 635 +/// [Vim Browse]. 636 +struct NormalModeShortcuts normalModeShortcuts [] = { 637 + { 'R', "?Building\n" }, 638 + { 'r', "/Building\n" }, 639 + { 'F', "?: error:\n" }, 640 + { 'f', "/: error:\n" }, 641 + { 'Q', "?[Leaving vim, starting execution]\n" }, 642 + { 'S', "Qf" }, 643 + { 'X', "?juli@machine\n" }, 644 + { 'x', "/juli@machine\n" }, 645 +}; 646 + 647 +size_t const amountNormalModeShortcuts = sizeof(normalModeShortcuts) / sizeof(*normalModeShortcuts); 648 + 649 +/// Style of the command string visualized in normal mode in the right corner 650 +/// [Vim Browse]. 651 +Glyph const styleCommand = {' ', ATTR_ITALIC | ATTR_FAINT, 7, 16}; 652 +/// Style of the search string visualized in normal mode in the right corner. 653 +/// [Vim Browse]. 654 +Glyph const styleSearch = {' ', ATTR_ITALIC | ATTR_BOLD_FAINT, 7, 16}; 655 + 656 +/// Colors used in normal mode in order to highlight different operations and 657 +/// empathise the current position on screen in the status area [Vim Browse]. 658 +unsigned int bgCommandYank = 11; 659 +unsigned int bgCommandVisual = 4; 660 +unsigned int bgCommandVisualLine = 12; 661 + 662 +unsigned int fgCommandYank = 232; 663 +unsigned int fgCommandVisual = 232; 664 +unsigned int fgCommandVisualLine = 232; 665 + 666 +unsigned int bgPos = 15; 667 +unsigned int fgPos = 16; 668 + 669 diff --git a/dynamicArray.h b/dynamicArray.h 670 new file mode 100644 671 index 0000000..8af5d9c 672 --- /dev/null 673 +++ b/dynamicArray.h 674 @@ -0,0 +1,175 @@ 675 +#ifndef DYNAMIC_ARRAY_H 676 +#define DYNAMIC_ARRAY_H 677 + 678 +#include "error.h" 679 + 680 +#include <stdint.h> 681 +#include <stdlib.h> 682 +#include <string.h> 683 +#include <stdbool.h> 684 + 685 +/// Struct for which this file offers functionality in order to expand the array 686 +/// and set / get its content. 687 +typedef struct DynamicArray { 688 + /// Size of the datatype contained in the array. 689 + uint8_t itemSize; 690 + /// Amount of bytes currently initialized 691 + uint32_t index; 692 + /// Amount of bytes currently reserved (not necessarily initialized) 693 + uint32_t allocated; 694 + /// Actual content. 695 + char* content; 696 +} DynamicArray; 697 + 698 +#define EXPAND_STEP 15 699 + 700 +/// Default initializers for the dynamic array. 701 +#define CHAR_ARRAY {1, 0, 0, NULL} 702 +#define WORD_ARRAY {2, 0, 0, NULL} 703 +#define DWORD_ARRAY {4, 0, 0, NULL} 704 +#define QWORD_ARRAY {8, 0, 0, NULL} 705 +/// (Wasteful) utf-8 array, that always used 4 bytes in order to display a 706 +/// character, even if the space is not required. 707 +#define UTF8_ARRAY DWORD_ARRAY 708 + 709 +/// Check that at least \p bytes are allocated, if true implying that 710 +/// \p s->content[\bytes - 1] is allocated. 711 +static inline bool 712 +isAllocated(DynamicArray const *s, uint32_t bytes) { 713 + return s != NULL && s->allocated >= bytes; 714 +} 715 + 716 +/// @see #isAllocated 717 +static inline bool 718 +isInitialized(DynamicArray const *s, uint32_t bytes) { 719 + return s != NULL && s->index >= bytes; 720 +} 721 + 722 +/// Return the next element in \p s and increment index without checking bounds. 723 +static inline char* 724 +gnext(DynamicArray *s) { 725 + ENSURE(s!=NULL, return NULL); 726 + ENSURE(s->index % s->itemSize == 0 && "(index not aligned)", 727 + s->index += s->itemSize - (s->index % s->itemSize)); 728 + ENSURE(isAllocated(s, s->index + 2 * s->itemSize), return NULL); 729 + return s->content + (s->index += s->itemSize); 730 +} 731 + 732 +/// View element \p i in \p s. 733 +static inline char* 734 +view(DynamicArray const * s, uint32_t i) { 735 + ENSURE((s != NULL) && isAllocated(s, (i+1) * s->itemSize), return NULL); 736 + return s->content + i*s->itemSize; 737 +} 738 + 739 +/// Inspect element content[size() - 1 - i]. 740 +static inline char * 741 +viewEnd(DynamicArray const *s, uint32_t i) { 742 + ENSURE((s != NULL) && isInitialized(s, i * s->itemSize), return NULL); 743 + ENSURE(s->index%s->itemSize == 0 && "(index not aligned)", return NULL); 744 + return s->content + s->index - (i + 1) * s->itemSize; 745 +} 746 + 747 +/// Set conent without applying 748 +static inline bool 749 +setValues(DynamicArray* s, char const *vals, uint32_t amount) { 750 + ENSURE(vals != NULL, return false); 751 + ENSURE((s != NULL) && isAllocated(s, s->index + amount), return false); 752 + memcpy(s->content + s->index, vals, amount); 753 + return true; 754 +} 755 + 756 +static inline bool 757 +snext(DynamicArray* s, char const *vals, uint32_t amount) { 758 + bool const success = setValues(s, vals, amount); 759 + ENSURE(success, return false); 760 + uint8_t const rest = amount % s->itemSize; 761 + uint32_t const newSize = s->index + amount + (rest ? s->itemSize : 0); 762 + ENSURE(isAllocated(s, newSize), return false); 763 + s->index = newSize; 764 + return true; 765 +} 766 + 767 +/// Empty \p s. 768 +static inline void 769 +empty(DynamicArray* s) { 770 + ENSURE((s != NULL), return); 771 + s->index = 0; 772 +} 773 + 774 +/// Check if \p s has initialized content (which can be the case even if memory 775 +/// is allocated). 776 +static inline bool 777 +isEmpty(DynamicArray const * s) { 778 + ENSURE((s != NULL), return true); 779 + return s->index == 0; 780 +} 781 + 782 +static inline int 783 +size(DynamicArray const * s) { 784 + ENSURE(s != NULL, return 0); 785 + ENSURE(s->itemSize != 0, return 0); 786 + return s->index / s->itemSize; 787 +} 788 + 789 +static inline void 790 +pop(DynamicArray* s) { 791 + ENSURE((s != NULL), return); 792 + ENSURE(s->index % s->itemSize == 0 && "(index not aligned)", 793 + s->index += s->itemSize - (s->index % s->itemSize)); 794 + ENSURE(isInitialized(s, s->itemSize), return); 795 + s->index -= s->itemSize; 796 +} 797 + 798 +static inline bool 799 +checkSetNext(DynamicArray *s, char const *c, uint32_t amount) { 800 + ENSURE(s != NULL && c != NULL, return false); 801 + if (s->allocated < s->index + s->itemSize * amount) { 802 + uint32_t const diff = s->index+s->itemSize*amount-s->allocated; 803 + uint32_t const newAlloSize = s->allocated + (diff > EXPAND_STEP 804 + ? diff : EXPAND_STEP) * s->itemSize; 805 + char* tmp = realloc(s->content, newAlloSize); 806 + if (tmp == NULL) { return false; } 807 + s->allocated = newAlloSize; 808 + s->content = tmp; 809 + assert(s->allocated >= s->index + s->itemSize * amount); 810 + } 811 + if (amount) { snext(s, c, amount); } 812 + return true; 813 +} 814 + 815 +static inline bool 816 +checkSetNextV(DynamicArray *s, char const c) { 817 + return checkSetNext(s, &c, 1); 818 +} 819 + 820 +static inline bool 821 +checkSetNextP(DynamicArray *s, char const *c) { 822 + ENSURE(c != NULL, return false); 823 + return checkSetNext(s, c, strlen(c)); 824 +} 825 + 826 +/// Expand the currently initialized content in \p s and the allocated chunk of 827 +/// memory if required. 828 +static char * 829 +expand(DynamicArray *s) { 830 + ENSURE(s != NULL, return NULL); 831 + if (s->allocated < s->index + s->itemSize) { 832 + uint32_t const diff = s->index + s->itemSize - s->allocated; 833 + uint32_t const newAlloSize = s->allocated + (diff > EXPAND_STEP 834 + ? diff : EXPAND_STEP) * s->itemSize; 835 + char* tmp = realloc(s->content, newAlloSize); 836 + if (tmp == NULL) { return NULL; } 837 + s->allocated = newAlloSize; 838 + s->content = tmp; 839 + assert(s->allocated >= s->index + s->itemSize); 840 + } 841 + s->index+=s->itemSize; 842 + return viewEnd(s, 0); 843 +} 844 + 845 +#define append(s, c) checkSetNext((s), (char const *) (c), (s)->itemSize) 846 +#define appendPartial(s, c, i) checkSetNext((s), (char const *) (c), (i)) 847 + 848 + 849 +#endif // DYNAMIC_ARRAY_H 850 diff --git a/error.h b/error.h 851 new file mode 100644 852 index 0000000..91c621f 853 --- /dev/null 854 +++ b/error.h 855 @@ -0,0 +1,47 @@ 856 +#ifndef ERROR_H 857 +#define ERROR_H 858 + 859 +#include <assert.h> 860 + 861 +// Flag which determines whether to fail if a required condition is not met, or 862 +// to adapt the condition in order to work properly. 863 +// Attention: Be sure to perform a clean build after you alter preprocessor 864 +// directives / definitions. 865 +//#define FAIL_ON_ERROR 866 + 867 +#include <stdio.h> 868 + 869 +/// 870 +/// Function used in case the fail-on-error mode is disabled (via definition) 871 +/// to report errors. In debug production mode, alias st to st 2> error.log. 872 +static void reportError(char const * cond, char const * stt, char const * file, 873 + unsigned int line ) { 874 + unsigned int const maxErrorCount = 100; 875 + static unsigned int errorCount = 0; 876 + if (++errorCount == 1) { 877 + printf("Report the following bug to " 878 + "https://github.com/juliusHuelsmann/st.\n"); 879 + } 880 + if (errorCount < maxErrorCount) { 881 + printf("Bug:\tCondition '%s' evaluates to false.\n\tPerforming" 882 + " '%s' to counteract.\n\tFile:%s:%u\n", 883 + cond, stt, file, line); 884 + } else if (errorCount == maxErrorCount) { 885 + printf("Max amount of reported errors %u is reached. From here" 886 + "on, no additional errors will be reported.\n", 887 + maxErrorCount); 888 + } 889 +} 890 + 891 +/// Note that everyting condition checked / endforced with #ENSURE is 892 +/// considered an error, and behaves like an error depending on the flag. 893 +#ifdef FAIL_ON_ERROR 894 +#define ENSURE(cond, stt) assert(cond); 895 +#else // FAIL_ON_ERROR 896 +#define ENSURE(cond, stt) if (!(cond)) { \ 897 + reportError(#cond, #stt, __FILE__, __LINE__); \ 898 + stt; \ 899 + } 900 +#endif // FAIL_ON_ERROR 901 + 902 +#endif // ERROR_H 903 diff --git a/glyph.h b/glyph.h 904 new file mode 100644 905 index 0000000..84aa252 906 --- /dev/null 907 +++ b/glyph.h 908 @@ -0,0 +1,30 @@ 909 +#ifndef LINE_H 910 +#define LINE_H 911 + 912 +// 913 +// Contains the representation of the entities in the buffer (Line, Gylph), that 914 +// is used by every part of the software implmeneting terminal logic. 915 +// 916 + 917 +#include <stdint.h> 918 + 919 +enum selection_type { 920 + SEL_REGULAR = 1, 921 + SEL_RECTANGULAR = 2 922 +}; 923 + 924 +typedef uint_least32_t Rune; 925 + 926 +#define Glyph Glyph_ 927 + 928 +typedef struct { 929 + Rune u; /* character code */ 930 + unsigned short mode; /* attribute flags */ 931 + uint32_t fg; /* foreground */ 932 + uint32_t bg; /* background */ 933 +} Glyph; 934 + 935 + 936 +typedef Glyph *Line; 937 + 938 +#endif // LINE_H 939 diff --git a/normalMode.c b/normalMode.c 940 new file mode 100644 941 index 0000000..59a5a89 942 --- /dev/null 943 +++ b/normalMode.c 944 @@ -0,0 +1,750 @@ 945 +/* See LICENSE for license details. */ 946 +#include "normalMode.h" 947 +#include "dynamicArray.h" 948 +#include "term.h" 949 +#include "win.h" 950 +#include "error.h" 951 + 952 +#include <X11/keysym.h> 953 +#include <X11/XKBlib.h> 954 + 955 +#include <ctype.h> 956 +#include <stdio.h> 957 +#include <limits.h> 958 +#include <math.h> 959 + 960 +#define LEN(a) (sizeof(a) / sizeof(a)[0]) 961 +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 962 +#define FALLTHROUGH __attribute__((fallthrough)); 963 +#define SEC(var,ini,h,r) var = ini; if (!var) { h; return r; } 964 +#define EXPAND(v1,v2,r) char *SEC(v1, expand(v2), empty(v2), true) 965 +#define currentCommand (toggle ? &commandHist0 : &commandHist1) 966 +#define lastCommand (toggle ? &commandHist1 : &commandHist0) 967 + 968 +// 969 +// Interface to the terminal 970 +extern Glyph const styleCommand, styleSearch; 971 +extern NormalModeShortcuts normalModeShortcuts[]; 972 +extern size_t const amountNormalModeShortcuts; 973 +extern char wordDelimSmall[]; 974 +extern char wordDelimLarge[]; 975 +extern unsigned int fgCommandYank, fgCommandVisual, fgCommandVisualLine, 976 + bgCommandYank, bgCommandVisual, bgCommandVisualLine, bgPos, fgPos; 977 + 978 +extern void selclear(void); 979 +extern void tsetdirt(int, int); 980 +extern size_t utf8encode(Rune, char *); 981 +extern size_t utf8decode(const char *, Rune *, size_t); 982 +extern size_t utf8decodebyte(char c, size_t *i); 983 + 984 +extern void selextend(int, int, int, int, int); 985 +extern void selstart(int, int, int, int); 986 +extern char *getsel(void); 987 +extern void tfulldirt(void); 988 + 989 +// 990 +// `Private` structs 991 +typedef struct { uint32_t x; uint32_t y; uint32_t yScr; } Position; 992 + 993 +/// Entire normal mode state, consisting of an operation and a motion. 994 +typedef struct { 995 + Position initialPosition; 996 + struct OperationState { 997 + enum Operation { 998 + noop = ' ', visual='v', visualLine='V', yank = 'y' } op; 999 + Position startPosition; 1000 + enum Infix { infix_none = 0, infix_i = 1, infix_a = 2, } infix; 1001 + } command; 1002 + struct MotionState { 1003 + uint32_t amount; 1004 + enum Search {none, forward, backward} search; 1005 + Position searchPosition; 1006 + bool finished; 1007 + } motion; 1008 +} NormalModeState; 1009 + 1010 +/// Default state if no operation is performed. 1011 +NormalModeState defaultNormalMode = { 1012 + {0,0,0}, {noop, {0, 0, 0}, false}, {0, none, {0, 0, 0}, true} 1013 +}; 1014 +NormalModeState stateVB = { 1015 + {0,0,0}, {noop, {0, 0, 0}, false}, {0, none, {0, 0, 0}, true} 1016 +}; 1017 + 1018 +DynamicArray searchString = UTF8_ARRAY; 1019 +DynamicArray commandHist0 = UTF8_ARRAY; 1020 +DynamicArray commandHist1 = UTF8_ARRAY; 1021 +DynamicArray highlights = DWORD_ARRAY; 1022 + 1023 +/// History command toggle 1024 +static bool toggle = false; 1025 + 1026 +// 1027 +// Utility functions 1028 +static inline int intervalDiff(int v, int a, int b) { 1029 + return (v < a) ? (v - a) : ((v > b) ? (v - b) : 0); 1030 +} 1031 +static inline void swap(DynamicArray *const a, DynamicArray *const b) { 1032 + DynamicArray tmp = *a; *a = *b; *b = tmp; 1033 +} 1034 +static inline int max(int a, int b) { return a > b ? a : b; } 1035 +static inline int min(int a, int b) { return a < b ? a : b; } 1036 +static inline int mod(int a, int b) { for (; a < 0; a += b); return a % b; } 1037 +static inline bool contains (char c, char const * values, uint32_t memSize) { 1038 + ENSURE(values != NULL, return false); 1039 + for (uint32_t i = 0; i < memSize; ++i) if (c == values[i]) return true; 1040 + return false; 1041 +} 1042 +static inline void applyPosition(Position const *pos) { 1043 + ENSURE(pos != NULL, return); 1044 + term.c.x = pos->x; 1045 + term.c.y = pos->y; 1046 + term.scr = pos->yScr; 1047 +} 1048 +static inline int getSearchDirection(void) { 1049 + return stateVB.motion.search == forward ? 1 : -1; 1050 +} 1051 + 1052 +// Utilities for working with the current version of the scrollback patch. 1053 +static bool moveLine(int32_t const amount) { 1054 + int32_t const reqShift = intervalDiff(term.c.y+=amount, 0, term.row-1); 1055 + term.c.y -= reqShift; 1056 + int32_t const sDiff = intervalDiff(term.scr-=reqShift, 0, HISTSIZE-1); 1057 + term.scr -= sDiff; 1058 + return sDiff == 0; 1059 +} 1060 + 1061 +static void moveLetter(int32_t const amount) { 1062 + int32_t value = (term.c.x += amount) / term.col; 1063 + if (value -= (term.c.x < 0)) { 1064 + term.c.x = moveLine(value) ? mod(term.c.x, term.col) 1065 + : max(min(term.c.x,term.col - 1), 0); 1066 + } 1067 + assert(BETWEEN(term.c.x,0,term.col-1)&&BETWEEN(term.c.y,0,term.row-1)); 1068 +} 1069 + 1070 +// 1071 +// `Private` functions: 1072 + 1073 +// Functions: Temporarily display string on screen. 1074 + 1075 +/// Display string at end of a specified line without writing it into the buffer 1076 +/// @param str string that is to be displayed 1077 +/// @param g glyph 1078 +/// @param yPos 1079 +static void 1080 +displayString(DynamicArray const *str, Glyph const *g, int yPos, bool prePos) { 1081 + ENSURE((str != NULL) && (g != NULL) && (term.row > 0), return); 1082 + ENSURE(yPos >= 0, yPos = 0); 1083 + ENSURE(yPos < term.row, yPos = term.row - 1); 1084 + // Arbritary limit to avoid withhelding too much info from user. 1085 + int const maxFractionOverridden = 3; 1086 + // Threshold: if there is no space to print, do not print, but transfer 1087 + // repsonsibility for printing back to [st]. 1088 + if (term.col < maxFractionOverridden) { // (0) 1089 + term.dirty[yPos] = 1; 1090 + return; 1091 + } 1092 + int32_t const botSz = prePos * 6; //< sz for position indication 1093 + // Determine the dimensions of used chunk of screen. 1094 + int32_t const overrideSize = min(size(str) + botSz, 1095 + term.col / maxFractionOverridden); // (1) 1096 + int32_t const overrideEnd = term.col - 2; 1097 + // Has to follow trivially hence th assert: 1098 + // overrideSize <(1)= term.col/3 <(0)= term.col = overrideEnd + 1. 1099 + assert(overrideSize <= overrideEnd + 1); 1100 + int32_t const overrideStart = 1 + overrideEnd - overrideSize; 1101 + // display history[history.size() - (overrideSize - botSz)::-1] 1102 + Glyph *SEC(line, malloc(sizeof(Glyph) * (overrideSize)),,) 1103 + int32_t offset = (size(str) - overrideSize - 1 + botSz) * str->itemSize; 1104 + for (uint32_t chr = 0; chr < overrideSize - botSz; ++chr) { 1105 + line[chr] = *g; 1106 + line[chr].u = *((Rune*) (str->content+(offset+=str->itemSize))); 1107 + } 1108 + if (prePos) { 1109 + ENSURE(term.scr < HISTSIZE, term.scr = HISTSIZE - 1); 1110 + int32_t const p=round((HISTSIZE-1-term.scr)*100./(HISTSIZE-1)); 1111 + char prc [8]; 1112 + switch (term.scr) { 1113 + case HISTSIZE - 1: strcpy(prc, " [TOP]"); break; 1114 + case 0: strcpy(prc, " [BOT]"); break; 1115 + default: sprintf(prc, " % 3d%c ", p, '%'); 1116 + } 1117 + for (uint32_t chr = 0; chr < botSz; ++chr) { 1118 + line[chr + overrideSize - botSz] =*g; 1119 + line[chr + overrideSize - botSz].fg = fgPos; 1120 + line[chr + overrideSize - botSz].bg = bgPos; 1121 + utf8decode(&prc[chr],&line[chr+overrideSize-botSz].u,1); 1122 + } 1123 + line[overrideSize - botSz] =*g; 1124 + } 1125 + xdrawline(TLINE(yPos), 0, yPos, overrideStart); 1126 + term.c.y -= term.row; term.c.x -= term.col; // not highlight hack 1127 + xdrawline(line-overrideStart, overrideStart, yPos, overrideEnd + 1); 1128 + term.c.y += term.row; term.c.x += term.col; 1129 + free(line); 1130 +} 1131 + 1132 +static inline void printCommandString(void) { 1133 + Glyph g = styleCommand; 1134 + switch(stateVB.command.op) { 1135 + case yank: g.fg = fgCommandYank; g.bg = bgCommandYank; break; 1136 + case visual: g.fg=fgCommandVisual; g.bg=bgCommandVisual; break; 1137 + case visualLine: g.fg=fgCommandVisualLine; 1138 + g.bg=bgCommandVisualLine; 1139 + } 1140 + displayString(isEmpty(currentCommand) ? lastCommand : currentCommand, 1141 + &g, term.row - 1, true); 1142 +} 1143 + 1144 +static inline void printSearchString(void) { 1145 + displayString(&searchString, &styleSearch, term.row - 2, false); 1146 +} 1147 + 1148 +// NormalMode Operation / Motion utilies. 1149 + 1150 +static inline bool isMotionFinished(void) { return stateVB.motion.finished; } 1151 + 1152 +static inline void finishMotion(void) { stateVB.motion.finished = true; } 1153 + 1154 +static inline bool isOperationFinished(void) { 1155 + return stateVB.command.op==noop && stateVB.command.infix==infix_none; 1156 +} 1157 + 1158 +/// Register that the current comamnd is finished and a new command is lgoged 1159 +static inline void startNewCommand(bool abort) { 1160 + if (!abort) { toggle = !toggle; } 1161 + empty(currentCommand); 1162 +} 1163 + 1164 +static inline void finishOperation(void) { 1165 + stateVB.command = defaultNormalMode.command; 1166 + assert(isOperationFinished()); 1167 + // After an operation is finished, the selection has to be released and 1168 + // no highlights are to be released. 1169 + selclear(); 1170 + empty(&highlights); 1171 + // THe command string is reset for a new command. 1172 + startNewCommand(true); 1173 +} 1174 + 1175 +static inline void enableOperation(enum Operation o) { 1176 + finishOperation(); 1177 + stateVB.command.op = o; 1178 + stateVB.command.infix = infix_none; 1179 + stateVB.command.startPosition.x = term.c.x; 1180 + stateVB.command.startPosition.y = term.c.y; 1181 + stateVB.command.startPosition.yScr = term.scr; 1182 +} 1183 + 1184 +/// @param abort: If enabled, the command exits without registering 1185 +/// @return Whether the the application is ready to yield control back to 1186 +//the normal command flow 1187 +static bool terminateCommand(bool abort) { 1188 + bool const exitOperation = isMotionFinished(); 1189 + bool exitNormalMode = false; 1190 + finishMotion(); 1191 + 1192 + if (exitOperation) { 1193 + exitNormalMode = isOperationFinished(); 1194 + finishOperation(); 1195 + } 1196 + printCommandString(); 1197 + printSearchString(); 1198 + return exitNormalMode; 1199 +} 1200 + 1201 +static inline void exitCommand(void) { terminateCommand(false); } 1202 + 1203 +static inline void abortCommand(void) { terminateCommand(true); } 1204 + 1205 +/// Go to next occurrence of string relative to the current location 1206 +/// conduct search, starting at start pos 1207 +static bool gotoString(int8_t sign) { 1208 + moveLetter(sign); 1209 + uint32_t const searchStrSize = size(&searchString); 1210 + uint32_t const maxIter = (HISTSIZE+term.row) * term.col + searchStrSize; 1211 + uint32_t findIdx = 0; 1212 + for (uint32_t cIteration = 0; findIdx < searchStrSize 1213 + && ++cIteration <= maxIter; moveLetter(sign)) { 1214 + char const * const SEC(next, sign==1 1215 + ? view(&searchString, findIdx) 1216 + : viewEnd(&searchString, findIdx), , false) 1217 + uint32_t const searchChar = *((uint32_t*) next); 1218 + 1219 + if (TLINE(term.c.y)[term.c.x].u == searchChar) { ++findIdx; } 1220 + else { findIdx = 0; } 1221 + } 1222 + bool const found = findIdx == searchStrSize; 1223 + for (uint32_t i = 0; found && i < searchStrSize; ++i) moveLetter(-sign); 1224 + return found; 1225 +} 1226 + 1227 +/// Highlight all found strings on the current screen. 1228 +static void highlightStringOnScreen(void) { 1229 + if (isEmpty(&searchString)) { return; } 1230 + empty(&highlights); 1231 + uint32_t const searchStringSize = size(&searchString); 1232 + uint32_t findIdx = 0; 1233 + uint32_t xStart, yStart; 1234 + bool success = true; 1235 + for (int y = 0; y < term.row && success; y++) { 1236 + for (int x = 0; x < term.col && success; x++) { 1237 + char const* const SEC(next, 1238 + view(&searchString,findIdx),,) 1239 + if (TLINE(y)[x].u == (Rune) *((uint32_t*)(next))) { 1240 + if (++findIdx == 1) { 1241 + xStart = x; 1242 + yStart = y; 1243 + } 1244 + if (findIdx == searchStringSize) { 1245 + success = success 1246 + && append(&highlights, &xStart) 1247 + && append(&highlights, &yStart); 1248 + findIdx = 0; //term.dirty[yStart] = 1; 1249 + } 1250 + } else { findIdx = 0; } 1251 + } 1252 + } 1253 + if (!success) { empty(&highlights); } 1254 +} 1255 + 1256 +static bool gotoStringAndHighlight(int8_t sign) { 1257 + // Find hte next occurrence of the #searchString in direction #sign 1258 + bool const found = gotoString(sign); 1259 + if (!found) { applyPosition(&stateVB.motion.searchPosition); } 1260 + highlightStringOnScreen(); 1261 + //tsetdirt(0, term.row-3); //< everything except for the 'status bar' 1262 + return found; 1263 +} 1264 + 1265 +static bool pressKeys(char const* nullTerminatedString, size_t end) { 1266 + bool sc = true; 1267 + for (size_t i = 0; i < end && sc; ++i) { 1268 + sc = kpressNormalMode(&nullTerminatedString[i], 1, false, NULL); 1269 + } 1270 + return sc; 1271 +} 1272 + 1273 +static bool executeCommand(DynamicArray const *command) { 1274 + size_t end=size(command); 1275 + char decoded [32]; 1276 + bool succ = true; 1277 + size_t len; 1278 + for (size_t i = 0; i < end && succ; ++i) { 1279 + char const *const SEC(nextRune, view(command, i),,false) 1280 + len = utf8encode(*((Rune *) nextRune), decoded); 1281 + succ = kpressNormalMode(decoded, len, false, NULL); 1282 + } 1283 + return succ; 1284 +} 1285 + 1286 +struct { char const first; char const second; } const Brackets [] = 1287 +{ {'(', ')'}, {'<', '>'}, {'{', '}'}, {'[', ']'}, }; 1288 + 1289 + 1290 +/// Emits Command prefix and suffix when i motion is performed (e.g. yiw). 1291 +/// 1292 +/// @param c: motion character 1293 +/// @param expandMode: 1 for 'i', 2 for 'a' 1294 +/// @param first, second: Dynamic arrays in which the prefix and postfix 1295 +/// commands will be returned 1296 +/// @return whether the command could be extracted successfully. 1297 +static bool expandExpression(char const c, enum Infix expandMode, 1298 + char operation, DynamicArray *cmd) { 1299 + empty(cmd); 1300 + bool s = true; //< used in order to detect memory allocation errors. 1301 + char const lower = tolower(c); 1302 + // Motions 1303 + if (lower == 'w') { 1304 + // translated into wb[command]e resp. WB[command]E, which works 1305 + // file even when at the fist letter. Does not work for single 1306 + // letter words though. 1307 + int const diff = c - lower; 1308 + s = s && checkSetNextV(cmd, c); 1309 + s = s && checkSetNextV(cmd, (signed char)(((int)'b') + diff)); 1310 + s = s && checkSetNextV(cmd, operation); 1311 + s = s && checkSetNextV(cmd, (signed char)(((int)'e')+ diff)); 1312 + return s; 1313 + } 1314 + // Symmetrical brackets (quotation marks) 1315 + if (c == '\'' || c == '"') { 1316 + // Local ambiguity -> do nothing. It cannot be determined if 1317 + // the current char is the 1st or last char of the selection. 1318 + // <---- search here? -- ['] -- or search here? ---> 1319 + if (TLINE(term.c.y)[term.c.x].u == c) { 1320 + return false; 1321 + } 1322 + // Prefix 1323 + char res [] = {'?', c, '\n'}; 1324 + s = s && checkSetNextP(cmd, res); 1325 + // infix 1326 + bool const iffy = expandMode == infix_i; 1327 + if (iffy) { s = s && checkSetNextV(cmd, 'l'); } 1328 + s = s && checkSetNextV(cmd, operation); 1329 + if (!iffy) { s = s && checkSetNextV(cmd, 'l'); } 1330 + // suffix 1331 + res[0] = '/'; 1332 + s = s && checkSetNextP(cmd, res); 1333 + if (iffy) { s = s && checkSetNextV(cmd, 'h'); } 1334 + return s; 1335 + } 1336 + // Brackets: Does not if in range / if the brackets belong togehter. 1337 + for (size_t pid = 0; pid < sizeof(Brackets); ++pid) { 1338 + if(Brackets[pid].first == c || Brackets[pid].second == c) { 1339 + if (TLINE(term.c.y)[term.c.x].u!=Brackets[pid].first) { 1340 + s = s && checkSetNextV(cmd, '?'); 1341 + s = s && checkSetNextV(cmd, Brackets[pid].first); 1342 + s = s && checkSetNextV(cmd, '\n'); 1343 + } 1344 + bool const iffy = expandMode == infix_i; 1345 + if (iffy) { s = s && checkSetNextV(cmd, 'l'); } 1346 + s = s && checkSetNextV(cmd, operation); 1347 + if (!iffy) { s = s && checkSetNextV(cmd, 'l'); } 1348 + s = s && checkSetNextV(cmd, '/'); 1349 + s = s && checkSetNextV(cmd, Brackets[pid].second); 1350 + s = s && checkSetNextV(cmd, '\n'); 1351 + if (iffy) { s = s && checkSetNextV(cmd, 'h'); } 1352 + return s; 1353 + } 1354 + } 1355 + /**/ 1356 + // search string 1357 + // complicated search operation: <tag> 1358 + if (c == 't') { 1359 + // XXX: (Bug in vim: @vit ) 1360 + // <tag_name attr="hier" a2="\<sch\>"> [current pos] </tag_name> 1361 + 1362 + // 1. Copy history ( tag := hist[?<\n:/ \n] ) 1363 + // 2. Copy history ( first_find := hist[?<\n: next place in 1364 + // history where count '>' > count '<' 1365 + // (can be behind current pos) ) 1366 + // 3. first := [?first_find][#first_ind]l 1367 + // second:= [/tag">"]h 1368 + //return true; // XXX: not implmented yet. 1369 + } 1370 + return false; 1371 +} 1372 + 1373 +// 1374 +// Public API 1375 +// 1376 + 1377 +void onMove(void) { 1378 + stateVB.initialPosition.x = term.c.x; 1379 + stateVB.initialPosition.y = term.c.y; 1380 + stateVB.initialPosition.yScr = term.scr; 1381 +} 1382 + 1383 +int highlighted(int x, int y) { 1384 + // Compute the legal bounds for a hit: 1385 + int32_t const stringSize = size(&searchString); 1386 + int32_t xMin = x - stringSize; 1387 + int32_t yMin = y; 1388 + while (xMin < 0 && yMin > 0) { 1389 + xMin += term.col; 1390 + --yMin; 1391 + } 1392 + if (xMin < 0) { xMin = 0; } 1393 + 1394 + uint32_t highSize = size(&highlights); 1395 + ENSURE(highSize % 2 == 0, empty(&highlights); return false;); 1396 + highSize /= 2; 1397 + uint32_t *ptr = (uint32_t*) highlights.content; 1398 + for (uint32_t i = 0; i < highSize; ++i) { 1399 + int32_t const sx = (int32_t) *(ptr++); 1400 + int32_t const sy = (int32_t) *(ptr++); 1401 + if (BETWEEN(sy, yMin, y) && (sy != yMin || sx > xMin) 1402 + && (sy != y || sx <= x)) { 1403 + return true; 1404 + } 1405 + } 1406 + return false; 1407 +} 1408 + 1409 +ExitState kpressNormalMode(char const * cs, int len, bool ctrl, void const *v) { 1410 + KeySym const * const ksym = (KeySym*) v; 1411 + bool const esc = ksym && *ksym == XK_Escape; 1412 + bool const enter = (ksym && *ksym==XK_Return) || (len==1 &&cs[0]=='\n'); 1413 + bool const quantifier = len == 1 && (BETWEEN(cs[0], 49, 57) 1414 + || (cs[0] == 48 && stateVB.motion.amount)); 1415 + int const previousScroll = term.scr; 1416 + // [ESC] or [ENTER] abort resp. finish the current level of operation. 1417 + // Typing 'i' if no operation is currently performed behaves like ESC. 1418 + if (esc || enter || (len == 1 && cs[0] == 'i' && isMotionFinished() 1419 + && isOperationFinished())) { 1420 + if (terminateCommand(!enter)) { 1421 + applyPosition(&stateVB.initialPosition); 1422 + Position const pc = stateVB.initialPosition; 1423 + stateVB = defaultNormalMode; 1424 + stateVB.initialPosition = pc; 1425 + tfulldirt(); 1426 + return finished; 1427 + } 1428 + len = 0; 1429 + goto motionFinish; 1430 + } 1431 + // Backspace 1432 + if (ksym && *ksym == XK_BackSpace) { 1433 + bool s = stateVB.motion.search!=none&&!stateVB.motion.finished; 1434 + bool q = stateVB.motion.amount != 0; 1435 + if (!(s || q)) { return failed; } 1436 + len = 0; 1437 + 1438 + if (!isEmpty(currentCommand)) { pop(currentCommand); } 1439 + if (s) { 1440 + if (!isEmpty(&searchString)) { pop(&searchString); } 1441 + else if (isEmpty(&searchString)) { 1442 + exitCommand(); 1443 + return success; 1444 + } 1445 + } else if (q) { 1446 + stateVB.motion.amount /= 10; 1447 + goto finishNoAppend; 1448 + } 1449 + } 1450 + 1451 + // Search: append to search string, then search & highlight 1452 + if (stateVB.motion.search != none && !stateVB.motion.finished) { 1453 + if (len >= 1) { 1454 + EXPAND(kSearch, &searchString, true) 1455 + utf8decode(cs, (Rune*)(kSearch), len); 1456 + } 1457 + applyPosition(&stateVB.motion.searchPosition); 1458 + gotoStringAndHighlight(getSearchDirection()); 1459 + goto finish; 1460 + } 1461 + if (len == 0) { return failed; } 1462 + // Quantifiers 1463 + if (quantifier) { 1464 + stateVB.motion.amount = min(SHRT_MAX, 1465 + stateVB.motion.amount * 10 + cs[0] - 48); 1466 + goto finish; 1467 + } 1468 + // 'i' mode enabled, hence the expression is to be expanded: 1469 + // [start_expression(cs[0])] [operation] [stop_expression(cs[0])] 1470 + if (stateVB.command.infix != infix_none && stateVB.command.op != noop) { 1471 + DynamicArray cmd = CHAR_ARRAY; 1472 + char const operation = stateVB.command.op; 1473 + bool succ = expandExpression(cs[0], 1474 + stateVB.command.infix, visual, &cmd); 1475 + if (operation == yank) { 1476 + succ = succ && checkSetNextV(&cmd, operation); 1477 + } 1478 + NormalModeState const st = stateVB; 1479 + TCursor const tc = term.c; 1480 + stateVB.command.infix = infix_none; 1481 + if (succ) { 1482 + stateVB.command.op = noop; 1483 + for (int i = 0; i < size(&cmd) && succ; ++i) { 1484 + succ = pressKeys(&cmd.content[i], 1); 1485 + } 1486 + if (!succ) { // go back to the old position, apply op 1487 + stateVB = st; 1488 + term.c = tc; 1489 + } 1490 + empty(currentCommand); 1491 + for (uint32_t i = 0; i < size(&cmd); ++i) { 1492 + EXPAND(kCommand, currentCommand, true) 1493 + utf8decode(cmd.content+i, (Rune*)(kCommand),1); 1494 + } 1495 + } 1496 + free(cmd.content); 1497 + goto finishNoAppend; 1498 + } 1499 + // Commands (V / v or y) 1500 + switch(cs[0]) { 1501 + case '.': 1502 + { 1503 + if (isEmpty(currentCommand)) { toggle = !toggle; } 1504 + DynamicArray cmd = UTF8_ARRAY; 1505 + swap(&cmd, currentCommand); 1506 + executeCommand(&cmd) ? success : failed; 1507 + swap(&cmd, currentCommand); 1508 + free(cmd.content); 1509 + goto finishNoAppend; 1510 + } 1511 + case 'i': stateVB.command.infix = infix_i; goto finish; 1512 + case 'a': stateVB.command.infix = infix_a; goto finish; 1513 + case 'y': 1514 + switch(stateVB.command.op) { 1515 + case noop: //< Start yank mode & set #op 1516 + enableOperation(yank); 1517 + selstart(term.c.x, term.c.y,term.scr,0); 1518 + goto finish; 1519 + case yank: //< Complete yank [y#amount j] 1520 + selstart(0, term.c.y, term.scr, 0); 1521 + int const origY = term.c.y; 1522 + moveLine(max(stateVB.motion.amount, 1)); 1523 + selextend(term.col-1,term.c.y,term.scr, 1524 + SEL_RECTANGULAR, 0); 1525 + term.c.y = origY; 1526 + FALLTHROUGH 1527 + case visualLine: // Yank visual selection 1528 + case visual: 1529 + xsetsel(getsel()); 1530 + xclipcopy(); 1531 + exitCommand(); 1532 + goto finish; 1533 + default: 1534 + return failed; 1535 + } 1536 + case visual: 1537 + case visualLine: 1538 + if (stateVB.command.op == cs[0]) { 1539 + finishOperation(); 1540 + return true; 1541 + } else { 1542 + enableOperation(cs[0]); 1543 + selstart(cs[0] == visualLine ? 0 : term.c.x, 1544 + term.c.y, term.scr, 0); 1545 + goto finish; 1546 + } 1547 + } 1548 + // CTRL Motions 1549 + int32_t sign = -1; //< if command goes 'forward'(1) or 'backward'(-1) 1550 + if (ctrl) { 1551 + if (ksym == NULL) { return false; } 1552 + switch(*ksym) { 1553 + case XK_f: 1554 + term.scr = max(term.scr - max(term.row-2,1), 0); 1555 + term.c.y = 0; 1556 + goto finish; 1557 + case XK_b: 1558 + term.scr = min(term.scr + max(term.row - 2, 1), 1559 + HISTSIZE - 1); 1560 + term.c.y = term.bot; 1561 + goto finish; 1562 + case XK_u: 1563 + term.scr = min(term.scr+term.row/2, HISTSIZE-1); 1564 + goto finish; 1565 + case XK_d: 1566 + term.scr = max(term.scr - term.row / 2, 0); 1567 + goto finish; 1568 + default: return false; 1569 + } 1570 + } 1571 + // Motions 1572 + switch(cs[0]) { 1573 + case 'c': empty(&commandHist0); empty(&commandHist1); 1574 + goto finishNoAppend; 1575 + case 'j': sign = 1; FALLTHROUGH 1576 + case 'k': moveLine(max(stateVB.motion.amount,1) * sign); 1577 + goto motionFinish; 1578 + case 'H': term.c.y = 0; 1579 + goto motionFinish; 1580 + case 'M': term.c.y = term.bot / 2; 1581 + goto motionFinish; 1582 + case 'L': term.c.y = term.bot; 1583 + goto motionFinish; 1584 + case 'G': applyPosition(&stateVB.initialPosition); 1585 + goto motionFinish; 1586 + case 'l': sign = 1; FALLTHROUGH 1587 + case 'h': moveLetter(sign * max(stateVB.motion.amount,1)); 1588 + goto motionFinish; 1589 + case '0': term.c.x = 0; 1590 + goto motionFinish; 1591 + case '$': term.c.x = term.col-1; 1592 + goto motionFinish; 1593 + case 'w': FALLTHROUGH 1594 + case 'W': FALLTHROUGH 1595 + case 'e': FALLTHROUGH 1596 + case 'E': sign = 1; FALLTHROUGH 1597 + case 'B': FALLTHROUGH 1598 + case 'b': { 1599 + char const * const wDelim = 1600 + cs[0] <= 90 ? wordDelimLarge : wordDelimSmall; 1601 + uint32_t const wDelimLen = strlen(wDelim); 1602 + 1603 + bool const startSpaceIsSeparator = 1604 + !(cs[0] == 'w' || cs[0] == 'W'); 1605 + // Whether to start & end with offset: 1606 + bool const performOffset = startSpaceIsSeparator; 1607 + // Max iteration := One complete hist traversal. 1608 + uint32_t const maxIter = (HISTSIZE+term.row) * term.col; 1609 + // Doesn't work exactly as in vim: Linebreak is 1610 + // counted as 'normal' separator, hence a jump can 1611 + // span multiple lines here. 1612 + stateVB.motion.amount = max(stateVB.motion.amount, 1); 1613 + for (;stateVB.motion.amount>0;--stateVB.motion.amount) { 1614 + uint8_t state = 0; 1615 + if (performOffset) { moveLetter(sign); } 1616 + for (uint32_t cIt = 0; cIt ++ < maxIter; moveLetter(sign)) { 1617 + if (startSpaceIsSeparator == contains(TLINE(term.c.y)[term.c.x].u, wDelim, wDelimLen)) { 1618 + if (state == 1) { 1619 + if (performOffset) { 1620 + moveLetter(-sign); 1621 + } 1622 + break; 1623 + } 1624 + } else if (state == 0) { state = 1; } 1625 + } 1626 + } 1627 + goto motionFinish; 1628 + } 1629 + case '/': sign = 1; FALLTHROUGH 1630 + case '?': 1631 + empty(&searchString); 1632 + stateVB.motion.search = sign == 1 ? forward : backward; 1633 + stateVB.motion.searchPosition.x = term.c.x; 1634 + stateVB.motion.searchPosition.y = term.c.y; 1635 + stateVB.motion.searchPosition.yScr = term.scr; 1636 + stateVB.motion.finished = false; 1637 + goto finish; 1638 + case 'n': sign = 1; FALLTHROUGH 1639 + case 'N': { 1640 + if (stateVB.motion.search == none) return failed; 1641 + if (stateVB.motion.search == backward) { sign *= -1; } 1642 + bool b = true; int ox = term.c.x; 1643 + int oy = term.c.y ; int scr = term.scr; 1644 + int32_t i = max(stateVB.motion.amount, 1); 1645 + for (;i>0 && (b=gotoString(sign)); --i) { 1646 + oy = term.c.y; scr = term.scr; 1647 + } 1648 + if (!b) { term.c.x = ox; term.c.y = oy; term.scr = scr;} 1649 + goto motionFinish; 1650 + } 1651 + case 't': // Toggle selection mode and set dirt. 1652 + sel.type = sel.type == SEL_REGULAR 1653 + ? SEL_RECTANGULAR : SEL_REGULAR; 1654 + //tsetdirt(sel.nb.y, sel.ne.y); 1655 + goto motionFinish; 1656 + } 1657 + // Custom commands 1658 + for (size_t i = 0; i < amountNormalModeShortcuts; ++i) { 1659 + if (cs[0] == normalModeShortcuts[i].key) { 1660 + return pressKeys(normalModeShortcuts[i].value, 1661 + strlen(normalModeShortcuts[i].value)) 1662 + ? success : failed; 1663 + } 1664 + } 1665 + return failed; 1666 +motionFinish: 1667 + stateVB.motion.amount = 0; 1668 + //if (isMotionFinished() && stateVB.command.op == yank) { 1669 + if (stateVB.command.op == yank) { 1670 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); 1671 + xsetsel(getsel()); 1672 + xclipcopy(); 1673 + exitCommand(); 1674 + } 1675 +finish: 1676 + if (len == 1 && !ctrl) { // XXX: for now. 1677 + EXPAND(kCommand, currentCommand, true) 1678 + utf8decode(cs, (Rune*)(kCommand), len); 1679 + } 1680 +finishNoAppend: 1681 + if (stateVB.command.op == visual) { 1682 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); 1683 + } else if (stateVB.command.op == visualLine) { 1684 + selextend(term.col-1, term.c.y, term.scr, sel.type, 0); 1685 + } 1686 + 1687 + if (previousScroll != term.scr && !isEmpty(&searchString)) { 1688 + highlightStringOnScreen(); 1689 + } 1690 + tsetdirt(0, term.row-3); //< Required because of the cursor cross. 1691 + printCommandString(); 1692 + printSearchString(); 1693 + return success; 1694 +} 1695 diff --git a/normalMode.h b/normalMode.h 1696 new file mode 100644 1697 index 0000000..7d88259 1698 --- /dev/null 1699 +++ b/normalMode.h 1700 @@ -0,0 +1,36 @@ 1701 +/* See LICENSE for license details. */ 1702 +#ifndef NORMAL_MODE_H 1703 +#define NORMAL_MODE_H 1704 + 1705 +#include <stdbool.h> 1706 +#include <stddef.h> 1707 +#include <stdint.h> 1708 + 1709 +/// Used in the configuration file to define custom shortcuts. 1710 +typedef struct NormalModeShortcuts { 1711 + char key; 1712 + char *value; 1713 +} NormalModeShortcuts; 1714 + 1715 +/// Holds the exit status of the #kpressNormalMode function, which informs the 1716 +/// caller when to exit normal mode. 1717 +typedef enum ExitState { 1718 + failed = 0, 1719 + success = 1, 1720 + finished = 2, 1721 +} ExitState; 1722 + 1723 +/// Called when curr position is altered. 1724 +void onMove(void); 1725 + 1726 +/// Function which returns whether the value at position provided as arguments 1727 +/// is to be highlighted. 1728 +int highlighted(int, int); 1729 + 1730 +/// Handles keys in normal mode. 1731 +ExitState kpressNormalMode(char const * decoded, int len, bool ctrlPressed, 1732 + void const * ksym); 1733 + //bool esc, bool enter, bool backspace, void* keysym); 1734 + 1735 + 1736 +#endif // NORMAL_MODE_H 1737 diff --git a/st.c b/st.c 1738 index 3e48410..d8bf7ab 100644 1739 --- a/st.c 1740 +++ b/st.c 1741 @@ -1,8 +1,10 @@ 1742 /* See LICENSE for license details. */ 1743 +#include <assert.h> 1744 #include <ctype.h> 1745 #include <errno.h> 1746 #include <fcntl.h> 1747 #include <limits.h> 1748 +#include <math.h> 1749 #include <pwd.h> 1750 #include <stdarg.h> 1751 #include <stdio.h> 1752 @@ -17,6 +19,8 @@ 1753 #include <unistd.h> 1754 #include <wchar.h> 1755 1756 + 1757 +#include "term.h" 1758 #include "st.h" 1759 #include "win.h" 1760 1761 @@ -42,6 +46,7 @@ 1762 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 1763 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 1764 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 1765 +#define INTERVAL(x, a, b) (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 1766 1767 enum term_mode { 1768 MODE_WRAP = 1 << 0, 1769 @@ -86,51 +91,6 @@ enum escape_state { 1770 ESC_DCS =128, 1771 }; 1772 1773 -typedef struct { 1774 - Glyph attr; /* current char attributes */ 1775 - int x; 1776 - int y; 1777 - char state; 1778 -} TCursor; 1779 - 1780 -typedef struct { 1781 - int mode; 1782 - int type; 1783 - int snap; 1784 - /* 1785 - * Selection variables: 1786 - * nb – normalized coordinates of the beginning of the selection 1787 - * ne – normalized coordinates of the end of the selection 1788 - * ob – original coordinates of the beginning of the selection 1789 - * oe – original coordinates of the end of the selection 1790 - */ 1791 - struct { 1792 - int x, y; 1793 - } nb, ne, ob, oe; 1794 - 1795 - int alt; 1796 -} Selection; 1797 - 1798 -/* Internal representation of the screen */ 1799 -typedef struct { 1800 - int row; /* nb row */ 1801 - int col; /* nb col */ 1802 - Line *line; /* screen */ 1803 - Line *alt; /* alternate screen */ 1804 - int *dirty; /* dirtyness of lines */ 1805 - TCursor c; /* cursor */ 1806 - int ocx; /* old cursor col */ 1807 - int ocy; /* old cursor row */ 1808 - int top; /* top scroll limit */ 1809 - int bot; /* bottom scroll limit */ 1810 - int mode; /* terminal mode flags */ 1811 - int esc; /* escape state flags */ 1812 - char trantbl[4]; /* charset table translation */ 1813 - int charset; /* current charset */ 1814 - int icharset; /* selected charset for sequence */ 1815 - int *tabs; 1816 -} Term; 1817 - 1818 /* CSI Escape sequence structs */ 1819 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 1820 typedef struct { 1821 @@ -153,6 +113,8 @@ typedef struct { 1822 int narg; /* nb of args */ 1823 } STREscape; 1824 1825 +void tfulldirt(void); 1826 + 1827 static void execsh(char *, char **); 1828 static void stty(char **); 1829 static void sigchld(int); 1830 @@ -185,16 +147,14 @@ static void tnewline(int); 1831 static void tputtab(int); 1832 static void tputc(Rune); 1833 static void treset(void); 1834 -static void tscrollup(int, int); 1835 -static void tscrolldown(int, int); 1836 +static void tscrollup(int, int, int); 1837 +static void tscrolldown(int, int, int); 1838 static void tsetattr(int *, int); 1839 static void tsetchar(Rune, Glyph *, int, int); 1840 -static void tsetdirt(int, int); 1841 static void tsetscroll(int, int); 1842 static void tswapscreen(void); 1843 static void tsetmode(int, int, int *, int); 1844 static int twrite(const char *, int, int); 1845 -static void tfulldirt(void); 1846 static void tcontrolcode(uchar ); 1847 static void tdectest(char ); 1848 static void tdefutf8(char); 1849 @@ -208,8 +168,6 @@ static void selnormalize(void); 1850 static void selscroll(int, int); 1851 static void selsnap(int *, int *, int); 1852 1853 -static size_t utf8decode(const char *, Rune *, size_t); 1854 -static Rune utf8decodebyte(char, size_t *); 1855 static char utf8encodebyte(Rune, size_t); 1856 static size_t utf8validate(Rune *, size_t); 1857 1858 @@ -219,8 +177,8 @@ static char base64dec_getc(const char **); 1859 static ssize_t xwrite(int, const char *, size_t); 1860 1861 /* Globals */ 1862 -static Term term; 1863 -static Selection sel; 1864 +Term term; 1865 +Selection sel; 1866 static CSIEscape csiescseq; 1867 static STREscape strescseq; 1868 static int iofd = 1; 1869 @@ -414,17 +372,22 @@ tlinelen(int y) 1870 { 1871 int i = term.col; 1872 1873 - if (term.line[y][i - 1].mode & ATTR_WRAP) 1874 + if (TLINE(y)[i - 1].mode & ATTR_WRAP) 1875 return i; 1876 1877 - while (i > 0 && term.line[y][i - 1].u == ' ') 1878 + while (i > 0 && TLINE(y)[i - 1].u == ' ') 1879 --i; 1880 1881 return i; 1882 } 1883 1884 void 1885 -selstart(int col, int row, int snap) 1886 +xselstart(int col, int row, int snap) { 1887 + selstart(col, row, term.scr, snap); 1888 +} 1889 + 1890 +void 1891 +selstart(int col, int row, int scroll, int snap) 1892 { 1893 selclear(); 1894 sel.mode = SEL_EMPTY; 1895 @@ -433,6 +396,7 @@ selstart(int col, int row, int snap) 1896 sel.snap = snap; 1897 sel.oe.x = sel.ob.x = col; 1898 sel.oe.y = sel.ob.y = row; 1899 + sel.oe.scroll = sel.ob.scroll = scroll; 1900 selnormalize(); 1901 1902 if (sel.snap != 0) 1903 @@ -441,10 +405,13 @@ selstart(int col, int row, int snap) 1904 } 1905 1906 void 1907 -selextend(int col, int row, int type, int done) 1908 -{ 1909 - int oldey, oldex, oldsby, oldsey, oldtype; 1910 +xselextend(int col, int row, int type, int done) { 1911 + selextend(col, row, term.scr, type, done); 1912 +} 1913 1914 +void 1915 +selextend(int col, int row, int scroll, int type, int done) 1916 +{ 1917 if (sel.mode == SEL_IDLE) 1918 return; 1919 if (done && sel.mode == SEL_EMPTY) { 1920 @@ -452,18 +419,22 @@ selextend(int col, int row, int type, int done) 1921 return; 1922 } 1923 1924 - oldey = sel.oe.y; 1925 - oldex = sel.oe.x; 1926 - oldsby = sel.nb.y; 1927 - oldsey = sel.ne.y; 1928 - oldtype = sel.type; 1929 + int const oldey = sel.oe.y; 1930 + int const oldex = sel.oe.x; 1931 + int const oldscroll = sel.oe.scroll; 1932 + int const oldsby = sel.nb.y; 1933 + int const oldsey = sel.ne.y; 1934 + int const oldtype = sel.type; 1935 1936 sel.oe.x = col; 1937 sel.oe.y = row; 1938 + sel.oe.scroll = scroll; 1939 + 1940 selnormalize(); 1941 sel.type = type; 1942 1943 - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 1944 + if (oldey != sel.oe.y || oldex != sel.oe.x || oldscroll != sel.oe.scroll 1945 + || oldtype != sel.type || sel.mode == SEL_EMPTY) 1946 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 1947 1948 sel.mode = done ? SEL_IDLE : SEL_READY; 1949 @@ -472,17 +443,21 @@ selextend(int col, int row, int type, int done) 1950 void 1951 selnormalize(void) 1952 { 1953 - int i; 1954 - 1955 - if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 1956 - sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 1957 - sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 1958 + sel.nb.y = INTERVAL(sel.ob.y + term.scr - sel.ob.scroll, 0, term.bot); 1959 + sel.ne.y = INTERVAL(sel.oe.y + term.scr - sel.oe.scroll, 0, term.bot); 1960 + if (sel.type == SEL_REGULAR && sel.nb.y != sel.ne.y) { 1961 + sel.nb.x = sel.nb.y < sel.ne.y ? sel.ob.x : sel.oe.x; 1962 + sel.ne.x = sel.nb.y < sel.ne.y ? sel.oe.x : sel.ob.x; 1963 } else { 1964 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 1965 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 1966 } 1967 - sel.nb.y = MIN(sel.ob.y, sel.oe.y); 1968 - sel.ne.y = MAX(sel.ob.y, sel.oe.y); 1969 + 1970 + if (sel.nb.y > sel.ne.y) { 1971 + int32_t const tmp = sel.nb.y; 1972 + sel.nb.y = sel.ne.y; 1973 + sel.ne.y = tmp; 1974 + } 1975 1976 selsnap(&sel.nb.x, &sel.nb.y, -1); 1977 selsnap(&sel.ne.x, &sel.ne.y, +1); 1978 @@ -490,7 +465,7 @@ selnormalize(void) 1979 /* expand selection over line breaks */ 1980 if (sel.type == SEL_RECTANGULAR) 1981 return; 1982 - i = tlinelen(sel.nb.y); 1983 + int i = tlinelen(sel.nb.y); 1984 if (i < sel.nb.x) 1985 sel.nb.x = i; 1986 if (tlinelen(sel.ne.y) <= sel.ne.x) 1987 @@ -526,7 +501,7 @@ selsnap(int *x, int *y, int direction) 1988 * Snap around if the word wraps around at the end or 1989 * beginning of a line. 1990 */ 1991 - prevgp = &term.line[*y][*x]; 1992 + prevgp = &TLINE(*y)[*x]; 1993 prevdelim = ISDELIM(prevgp->u); 1994 for (;;) { 1995 newx = *x + direction; 1996 @@ -541,14 +516,14 @@ selsnap(int *x, int *y, int direction) 1997 yt = *y, xt = *x; 1998 else 1999 yt = newy, xt = newx; 2000 - if (!(term.line[yt][xt].mode & ATTR_WRAP)) 2001 + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 2002 break; 2003 } 2004 2005 if (newx >= tlinelen(newy)) 2006 break; 2007 2008 - gp = &term.line[newy][newx]; 2009 + gp = &TLINE(newy)[newx]; 2010 delim = ISDELIM(gp->u); 2011 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 2012 || (delim && gp->u != prevgp->u))) 2013 @@ -569,14 +544,14 @@ selsnap(int *x, int *y, int direction) 2014 *x = (direction < 0) ? 0 : term.col - 1; 2015 if (direction < 0) { 2016 for (; *y > 0; *y += direction) { 2017 - if (!(term.line[*y-1][term.col-1].mode 2018 + if (!(TLINE(*y-1)[term.col-1].mode 2019 & ATTR_WRAP)) { 2020 break; 2021 } 2022 } 2023 } else if (direction > 0) { 2024 for (; *y < term.row-1; *y += direction) { 2025 - if (!(term.line[*y][term.col-1].mode 2026 + if (!(TLINE(*y)[term.col-1].mode 2027 & ATTR_WRAP)) { 2028 break; 2029 } 2030 @@ -596,24 +571,32 @@ getsel(void) 2031 if (sel.ob.x == -1) 2032 return NULL; 2033 2034 - bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 2035 + int32_t syb = sel.ob.y - sel.ob.scroll + term.scr; 2036 + int32_t sye = sel.oe.y - sel.oe.scroll + term.scr; 2037 + if (syb > sye) { 2038 + int32_t tmp = sye; 2039 + sye = syb; 2040 + syb = tmp; 2041 + } 2042 + 2043 + bufsize = (term.col+1) * (sye - syb + 1) * UTF_SIZ; 2044 ptr = str = xmalloc(bufsize); 2045 2046 /* append every set & selected glyph to the selection */ 2047 - for (y = sel.nb.y; y <= sel.ne.y; y++) { 2048 + for (y = syb; y <= sye; y++) { 2049 if ((linelen = tlinelen(y)) == 0) { 2050 *ptr++ = '\n'; 2051 continue; 2052 } 2053 2054 if (sel.type == SEL_RECTANGULAR) { 2055 - gp = &term.line[y][sel.nb.x]; 2056 + gp = &TLINE(y)[sel.nb.x]; 2057 lastx = sel.ne.x; 2058 } else { 2059 - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 2060 - lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 2061 + gp = &TLINE(y)[syb == y ? sel.nb.x : 0]; 2062 + lastx = (sye == y) ? sel.ne.x : term.col-1; 2063 } 2064 - last = &term.line[y][MIN(lastx, linelen-1)]; 2065 + last = &TLINE(y)[MIN(lastx, linelen-1)]; 2066 while (last >= gp && last->u == ' ') 2067 --last; 2068 2069 @@ -836,6 +819,9 @@ void 2070 ttywrite(const char *s, size_t n, int may_echo) 2071 { 2072 const char *next; 2073 + Arg arg = (Arg) { .i = term.scr }; 2074 + 2075 + kscrolldown(&arg); 2076 2077 if (may_echo && IS_SET(MODE_ECHO)) 2078 twrite(s, n, 1); 2079 @@ -1047,13 +1033,53 @@ tswapscreen(void) 2080 } 2081 2082 void 2083 -tscrolldown(int orig, int n) 2084 +kscrolldown(const Arg* a) 2085 +{ 2086 + int n = a->i; 2087 + 2088 + if (n < 0) 2089 + n = term.row + n; 2090 + 2091 + if (n > term.scr) 2092 + n = term.scr; 2093 + 2094 + if (term.scr > 0) { 2095 + term.scr -= n; 2096 + selscroll(0, -n); 2097 + tfulldirt(); 2098 + } 2099 +} 2100 + 2101 +void 2102 +kscrollup(const Arg* a) 2103 +{ 2104 + int n = a->i; 2105 + 2106 + if (n < 0) 2107 + n = term.row + n; 2108 + 2109 + if (term.scr <= HISTSIZE-n) { 2110 + term.scr += n; 2111 + selscroll(0, n); 2112 + tfulldirt(); 2113 + } 2114 +} 2115 + 2116 +void 2117 +tscrolldown(int orig, int n, int copyhist) 2118 { 2119 int i; 2120 Line temp; 2121 2122 LIMIT(n, 0, term.bot-orig+1); 2123 2124 + if (copyhist) { 2125 + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 2126 + temp = term.hist[term.histi]; 2127 + term.hist[term.histi] = term.line[term.bot]; 2128 + term.line[term.bot] = temp; 2129 + } 2130 + 2131 tsetdirt(orig, term.bot-n); 2132 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 2133 2134 @@ -1067,13 +1093,23 @@ tscrolldown(int orig, int n) 2135 } 2136 2137 void 2138 -tscrollup(int orig, int n) 2139 +tscrollup(int orig, int n, int copyhist) 2140 { 2141 int i; 2142 Line temp; 2143 2144 LIMIT(n, 0, term.bot-orig+1); 2145 2146 + if (copyhist) { 2147 + term.histi = (term.histi + 1) % HISTSIZE; 2148 + temp = term.hist[term.histi]; 2149 + term.hist[term.histi] = term.line[orig]; 2150 + term.line[orig] = temp; 2151 + } 2152 + 2153 + if (term.scr > 0 && term.scr < HISTSIZE) 2154 + term.scr = MIN(term.scr + n, HISTSIZE-1); 2155 + 2156 tclearregion(0, orig, term.col-1, orig+n-1); 2157 tsetdirt(orig+n, term.bot); 2158 2159 @@ -1093,6 +1129,7 @@ selscroll(int orig, int n) 2160 return; 2161 2162 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 2163 + sel.oe.scroll = sel.ob.scroll = term.scr; 2164 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 2165 selclear(); 2166 return; 2167 @@ -1122,13 +1159,19 @@ tnewline(int first_col) 2168 int y = term.c.y; 2169 2170 if (y == term.bot) { 2171 - tscrollup(term.top, 1); 2172 + tscrollup(term.top, 1, 1); 2173 } else { 2174 y++; 2175 } 2176 tmoveto(first_col ? 0 : term.c.x, y); 2177 } 2178 2179 +int 2180 +currentLine(int x, int y) 2181 +{ 2182 + return (x == term.c.x || y == term.c.y); 2183 +} 2184 + 2185 void 2186 csiparse(void) 2187 { 2188 @@ -1181,6 +1224,8 @@ tmoveto(int x, int y) 2189 term.c.state &= ~CURSOR_WRAPNEXT; 2190 term.c.x = LIMIT(x, 0, term.col-1); 2191 term.c.y = LIMIT(y, miny, maxy); 2192 + // Set the last position in order to restore after normal mode exits. 2193 + onMove(); 2194 } 2195 2196 void 2197 @@ -1287,14 +1332,14 @@ void 2198 tinsertblankline(int n) 2199 { 2200 if (BETWEEN(term.c.y, term.top, term.bot)) 2201 - tscrolldown(term.c.y, n); 2202 + tscrolldown(term.c.y, n, 0); 2203 } 2204 2205 void 2206 tdeleteline(int n) 2207 { 2208 if (BETWEEN(term.c.y, term.top, term.bot)) 2209 - tscrollup(term.c.y, n); 2210 + tscrollup(term.c.y, n, 0); 2211 } 2212 2213 int32_t 2214 @@ -1725,11 +1770,11 @@ csihandle(void) 2215 break; 2216 case 'S': /* SU -- Scroll <n> line up */ 2217 DEFAULT(csiescseq.arg[0], 1); 2218 - tscrollup(term.top, csiescseq.arg[0]); 2219 + tscrollup(term.top, csiescseq.arg[0], 0); 2220 break; 2221 case 'T': /* SD -- Scroll <n> line down */ 2222 DEFAULT(csiescseq.arg[0], 1); 2223 - tscrolldown(term.top, csiescseq.arg[0]); 2224 + tscrolldown(term.top, csiescseq.arg[0], 0); 2225 break; 2226 case 'L': /* IL -- Insert <n> blank lines */ 2227 DEFAULT(csiescseq.arg[0], 1); 2228 @@ -2235,7 +2280,7 @@ eschandle(uchar ascii) 2229 return 0; 2230 case 'D': /* IND -- Linefeed */ 2231 if (term.c.y == term.bot) { 2232 - tscrollup(term.top, 1); 2233 + tscrollup(term.top, 1, 1); 2234 } else { 2235 tmoveto(term.c.x, term.c.y+1); 2236 } 2237 @@ -2248,7 +2293,7 @@ eschandle(uchar ascii) 2238 break; 2239 case 'M': /* RI -- Reverse index */ 2240 if (term.c.y == term.top) { 2241 - tscrolldown(term.top, 1); 2242 + tscrolldown(term.top, 1, 1); 2243 } else { 2244 tmoveto(term.c.x, term.c.y-1); 2245 } 2246 @@ -2290,7 +2335,7 @@ tputc(Rune u) 2247 { 2248 char c[UTF_SIZ]; 2249 int control; 2250 - int width, len; 2251 + int width = 0, len; 2252 Glyph *gp; 2253 2254 control = ISCONTROL(u); 2255 @@ -2469,7 +2514,7 @@ twrite(const char *buf, int buflen, int show_ctrl) 2256 void 2257 tresize(int col, int row) 2258 { 2259 - int i; 2260 + int i, j; 2261 int minrow = MIN(row, term.row); 2262 int mincol = MIN(col, term.col); 2263 int *bp; 2264 @@ -2506,6 +2551,14 @@ tresize(int col, int row) 2265 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 2266 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 2267 2268 + for (i = 0; i < HISTSIZE; i++) { 2269 + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 2270 + for (j = mincol; j < col; j++) { 2271 + term.hist[i][j] = term.c.attr; 2272 + term.hist[i][j].u = ' '; 2273 + } 2274 + } 2275 + 2276 /* resize each row to new width, zero-pad if needed */ 2277 for (i = 0; i < minrow; i++) { 2278 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 2279 @@ -2563,7 +2616,7 @@ drawregion(int x1, int y1, int x2, int y2) 2280 continue; 2281 2282 term.dirty[y] = 0; 2283 - xdrawline(term.line[y], x1, y, x2); 2284 + xdrawline(TLINE(y), x1, y, x2); 2285 } 2286 } 2287 2288 @@ -2584,8 +2637,8 @@ draw(void) 2289 cx--; 2290 2291 drawregion(0, 0, term.col, term.row); 2292 - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 2293 - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 2294 + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], 2295 + term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); 2296 term.ocx = cx, term.ocy = term.c.y; 2297 xfinishdraw(); 2298 xximspot(term.ocx, term.ocy); 2299 diff --git a/st.h b/st.h 2300 index a1928ca..3aad9f3 100644 2301 --- a/st.h 2302 +++ b/st.h 2303 @@ -1,5 +1,8 @@ 2304 /* See LICENSE for license details. */ 2305 2306 +#include "glyph.h" 2307 +#include "normalMode.h" 2308 + 2309 #include <stdint.h> 2310 #include <sys/types.h> 2311 2312 @@ -33,6 +36,8 @@ enum glyph_attribute { 2313 ATTR_WRAP = 1 << 8, 2314 ATTR_WIDE = 1 << 9, 2315 ATTR_WDUMMY = 1 << 10, 2316 + ATTR_HIGHLIGHT = 1 << 12, 2317 + ATTR_CURRENT = 1 << 13, 2318 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 2319 }; 2320 2321 @@ -42,11 +47,6 @@ enum selection_mode { 2322 SEL_READY = 2 2323 }; 2324 2325 -enum selection_type { 2326 - SEL_REGULAR = 1, 2327 - SEL_RECTANGULAR = 2 2328 -}; 2329 - 2330 enum selection_snap { 2331 SNAP_WORD = 1, 2332 SNAP_LINE = 2 2333 @@ -57,18 +57,6 @@ typedef unsigned int uint; 2334 typedef unsigned long ulong; 2335 typedef unsigned short ushort; 2336 2337 -typedef uint_least32_t Rune; 2338 - 2339 -#define Glyph Glyph_ 2340 -typedef struct { 2341 - Rune u; /* character code */ 2342 - ushort mode; /* attribute flags */ 2343 - uint32_t fg; /* foreground */ 2344 - uint32_t bg; /* background */ 2345 -} Glyph; 2346 - 2347 -typedef Glyph *Line; 2348 - 2349 typedef union { 2350 int i; 2351 uint ui; 2352 @@ -81,6 +69,11 @@ void die(const char *, ...); 2353 void redraw(void); 2354 void draw(void); 2355 2356 +int currentLine(int, int); 2357 +void kscrolldown(const Arg *); 2358 +void kscrollup(const Arg *); 2359 +void normalMode(Arg const *); 2360 + 2361 void printscreen(const Arg *); 2362 void printsel(const Arg *); 2363 void sendbreak(const Arg *); 2364 @@ -90,6 +83,9 @@ int tattrset(int); 2365 void tnew(int, int); 2366 void tresize(int, int); 2367 void tsetdirtattr(int); 2368 +size_t utf8decode(const char *, Rune *, size_t); 2369 +Rune utf8decodebyte(char, size_t *); 2370 +void tsetdirt(int, int); 2371 void ttyhangup(void); 2372 int ttynew(char *, char *, char *, char **); 2373 size_t ttyread(void); 2374 @@ -100,8 +96,10 @@ void resettitle(void); 2375 2376 void selclear(void); 2377 void selinit(void); 2378 -void selstart(int, int, int); 2379 -void selextend(int, int, int, int); 2380 +void selstart(int, int, int, int); 2381 +void xselstart(int, int, int); 2382 +void selextend(int, int, int, int, int); 2383 +void xselextend(int, int, int, int); 2384 int selected(int, int); 2385 char *getsel(void); 2386 2387 diff --git a/term.h b/term.h 2388 new file mode 100644 2389 index 0000000..23adf0e 2390 --- /dev/null 2391 +++ b/term.h 2392 @@ -0,0 +1,73 @@ 2393 +#ifndef TERM_H 2394 +#define TERM_H 2395 + 2396 +// 2397 +// Internal terminal structs. 2398 +// 2399 + 2400 +#include "glyph.h" 2401 + 2402 +#include <stdint.h> 2403 + 2404 +#define HISTSIZE 2500 2405 + 2406 +typedef struct { 2407 + Glyph attr; /* current char attributes */ 2408 + int x; 2409 + int y; 2410 + char state; 2411 +} TCursor; 2412 + 2413 +typedef struct { 2414 + int mode; 2415 + int type; 2416 + int snap; 2417 + /// Selection variables: 2418 + /// ob – original coordinates of the beginning of the selection 2419 + /// oe – original coordinates of the end of the selection 2420 + struct { 2421 + int x, y, scroll; 2422 + } ob, oe; 2423 + /// Selection variables; currently displayed chunk. 2424 + /// nb – normalized coordinates of the beginning of the selection 2425 + /// ne – normalized coordinates of the end of the selection 2426 + struct { 2427 + int x, y; 2428 + } nb, ne; 2429 + 2430 + int alt; 2431 +} Selection; 2432 + 2433 +/* Internal representation of the screen */ 2434 +typedef struct { 2435 + int row; /* nb row */ 2436 + int col; /* nb col */ 2437 + Line *line; /* screen */ 2438 + Line *alt; /* alternate screen */ 2439 + Line hist[HISTSIZE]; /* history buffer */ 2440 + int histi; /* history index */ 2441 + int scr; /* scroll back */ 2442 + int *dirty; /* dirtyness of lines */ 2443 + TCursor c; /* cursor */ 2444 + int ocx; /* old cursor col */ 2445 + int ocy; /* old cursor row */ 2446 + int top; /* top scroll limit */ 2447 + int bot; /* bottom scroll limit */ 2448 + int mode; /* terminal mode flags */ 2449 + int esc; /* escape state flags */ 2450 + char trantbl[4]; /* charset table translation */ 2451 + int charset; /* current charset */ 2452 + int icharset; /* selected charset for sequence */ 2453 + int *tabs; 2454 +} Term; 2455 + 2456 +extern Term term; 2457 + 2458 +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 2459 + term.scr + HISTSIZE + 1) % HISTSIZE] : \ 2460 + term.line[(y) - term.scr]) 2461 + 2462 +extern Selection sel; 2463 + 2464 + 2465 +#endif // TERM_H 2466 diff --git a/win.h b/win.h 2467 index a6ef1b9..1a6fefe 100644 2468 --- a/win.h 2469 +++ b/win.h 2470 @@ -19,6 +19,7 @@ enum win_mode { 2471 MODE_MOUSEMANY = 1 << 15, 2472 MODE_BRCKTPASTE = 1 << 16, 2473 MODE_NUMLOCK = 1 << 17, 2474 + MODE_NORMAL = 1 << 18, 2475 MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 2476 |MODE_MOUSEMANY, 2477 }; 2478 @@ -27,6 +28,7 @@ void xbell(void); 2479 void xclipcopy(void); 2480 void xdrawcursor(int, int, Glyph, int, int, Glyph); 2481 void xdrawline(Line, int, int, int); 2482 +void xdrawglyph(Glyph, int, int); 2483 void xfinishdraw(void); 2484 void xloadcols(void); 2485 int xsetcolorname(int, const char *); 2486 diff --git a/x.c b/x.c 2487 index 1f62129..e297946 100644 2488 --- a/x.c 2489 +++ b/x.c 2490 @@ -143,7 +143,6 @@ typedef struct { 2491 static inline ushort sixd_to_16bit(int); 2492 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 2493 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 2494 -static void xdrawglyph(Glyph, int, int); 2495 static void xclear(int, int, int, int); 2496 static int xgeommasktogravity(int); 2497 static int ximopen(Display *); 2498 @@ -355,7 +354,7 @@ mousesel(XEvent *e, int done) 2499 break; 2500 } 2501 } 2502 - selextend(evcol(e), evrow(e), seltype, done); 2503 + xselextend(evcol(e), evrow(e), seltype, done); 2504 if (done) 2505 setsel(getsel(), e->xbutton.time); 2506 } 2507 @@ -471,7 +470,7 @@ bpress(XEvent *e) 2508 xsel.tclick2 = xsel.tclick1; 2509 xsel.tclick1 = now; 2510 2511 - selstart(evcol(e), evrow(e), snap); 2512 + xselstart(evcol(e), evrow(e), snap); 2513 } 2514 } 2515 2516 @@ -757,6 +756,13 @@ xloadcolor(int i, const char *name, Color *ncolor) 2517 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 2518 } 2519 2520 +void 2521 +normalMode(Arg const *_) { 2522 + (void) _; 2523 + win.mode ^= MODE_NORMAL; 2524 +} 2525 + 2526 + 2527 void 2528 xloadcols(void) 2529 { 2530 @@ -1344,6 +1350,14 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 2531 base.fg = defaultattr; 2532 } 2533 2534 + if (base.mode & ATTR_HIGHLIGHT) { 2535 + base.bg = highlightBg; 2536 + base.fg = highlightFg; 2537 + } else if ((base.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { 2538 + base.bg = currentBg; 2539 + base.fg = currentFg; 2540 + } 2541 + 2542 if (IS_TRUECOL(base.fg)) { 2543 colfg.alpha = 0xffff; 2544 colfg.red = TRUERED(base.fg); 2545 @@ -1433,7 +1447,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 2546 xclear(winx, winy + win.ch, winx + width, win.h); 2547 2548 /* Clean up the region we want to draw to. */ 2549 - XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 2550 + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 2551 2552 /* Set the clip region because Xft is sometimes dirty. */ 2553 r.x = 0; 2554 @@ -1476,8 +1490,9 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 2555 Color drawcol; 2556 2557 /* remove the old cursor */ 2558 - if (selected(ox, oy)) 2559 - og.mode ^= ATTR_REVERSE; 2560 + if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; 2561 + if (highlighted(ox, oy)) { og.mode ^= ATTR_HIGHLIGHT; } 2562 + if (currentLine(ox, oy)) { og.mode ^= ATTR_CURRENT; } 2563 xdrawglyph(og, ox, oy); 2564 2565 if (IS_SET(MODE_HIDE)) 2566 @@ -1509,6 +1524,11 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 2567 drawcol = dc.col[g.bg]; 2568 } 2569 2570 + if ((g.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { 2571 + g.bg = currentBg; 2572 + g.fg = currentFg; 2573 + } 2574 + 2575 /* draw the new one */ 2576 if (IS_SET(MODE_FOCUSED)) { 2577 switch (win.cursor) { 2578 @@ -1592,12 +1612,18 @@ xdrawline(Line line, int x1, int y1, int x2) 2579 2580 numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); 2581 i = ox = 0; 2582 - for (x = x1; x < x2 && i < numspecs; x++) { 2583 + for (x = x1; x < x2 && i < numspecs; ++x) { 2584 new = line[x]; 2585 if (new.mode == ATTR_WDUMMY) 2586 continue; 2587 if (selected(x, y1)) 2588 new.mode ^= ATTR_REVERSE; 2589 + if (highlighted(x, y1)) { 2590 + new.mode ^= ATTR_HIGHLIGHT; 2591 + } 2592 + if (currentLine(x, y1)) { 2593 + new.mode ^= ATTR_CURRENT; 2594 + } 2595 if (i > 0 && ATTRCMP(base, new)) { 2596 xdrawglyphfontspecs(specs, base, i, ox, y1); 2597 specs += i; 2598 @@ -1786,6 +1812,14 @@ kpress(XEvent *ev) 2599 len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); 2600 else 2601 len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 2602 + 2603 + if (IS_SET(MODE_NORMAL)) { 2604 + ExitState const es = kpressNormalMode(buf, len, // strlen(buf), 2605 + match(ControlMask, e->state), 2606 + &ksym); 2607 + if (es == finished) { normalMode(NULL); } 2608 + return; 2609 + } 2610 /* 1. shortcuts */ 2611 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 2612 if (ksym == bp->keysym && match(bp->mod, e->state)) { 2613 -- 2614 2.25.0 2615