st-vimBrowse-20191107-2b8333f.diff (45073B)
1 From de020f0c06440fd19a36e1b001ef9e0058f73369 Mon Sep 17 00:00:00 2001 2 From: Julius Huelsmann <juliusHuelsmann@gmail.com> 3 Date: Thu, 7 Nov 2019 09:08:49 +0100 4 Subject: [PATCH] [PATCH:VIM]: first version 5 6 --- 7 Makefile | 6 +- 8 config.def.h | 27 ++ 9 dynamicArray.h | 90 ++++++ 10 st.c | 794 +++++++++++++++++++++++++++++++++++++++++++++---- 11 st.h | 31 +- 12 win.h | 2 + 13 x.c | 51 +++- 14 7 files changed, 936 insertions(+), 65 deletions(-) 15 create mode 100644 dynamicArray.h 16 17 diff --git a/Makefile b/Makefile 18 index 470ac86..7d93347 100644 19 --- a/Makefile 20 +++ b/Makefile 21 @@ -21,8 +21,8 @@ config.h: 22 .c.o: 23 $(CC) $(STCFLAGS) -c $< 24 25 -st.o: config.h st.h win.h 26 -x.o: arg.h config.h st.h win.h 27 +st.o: config.h st.h win.h dynamicArray.h 28 +x.o: arg.h config.h st.h win.h dynamicArray.h 29 30 $(OBJ): config.h config.mk 31 32 @@ -35,7 +35,7 @@ clean: 33 dist: clean 34 mkdir -p st-$(VERSION) 35 cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ 36 - config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ 37 + config.def.h st.info st.1 arg.h st.h win.h dynamicArray.h $(SRC)\ 38 st-$(VERSION) 39 tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz 40 rm -rf st-$(VERSION) 41 diff --git a/config.def.h b/config.def.h 42 index 6ebea98..1b0e501 100644 43 --- a/config.def.h 44 +++ b/config.def.h 45 @@ -149,6 +149,12 @@ static unsigned int mousebg = 0; 46 * doesn't match the ones requested. 47 */ 48 static unsigned int defaultattr = 11; 49 +/// Colors for the entities that are highlighted in normal mode. 50 +static unsigned int highlightBg = 160; 51 +static unsigned int highlightFg = 15; 52 +/// Colors for the line and column that is marked 'current' in normal mode. 53 +static unsigned int currentBg = 0; 54 +static unsigned int currentFg = 15; 55 56 /* 57 * Internal mouse shortcuts. 58 @@ -162,10 +168,12 @@ static MouseShortcut mshortcuts[] = { 59 60 /* Internal keyboard shortcuts. */ 61 #define MODKEY Mod1Mask 62 +#define AltMask Mod1Mask 63 #define TERMMOD (ControlMask|ShiftMask) 64 65 static Shortcut shortcuts[] = { 66 /* mask keysym function argument */ 67 + { AltMask, XK_c, normalMode, {.i = 0} }, 68 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 69 { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 70 { ShiftMask, XK_Print, printscreen, {.i = 0} }, 71 @@ -178,6 +186,8 @@ static Shortcut shortcuts[] = { 72 { TERMMOD, XK_Y, selpaste, {.i = 0} }, 73 { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 74 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 75 + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 76 + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 77 }; 78 79 /* 80 @@ -456,3 +466,20 @@ static char ascii_printable[] = 81 " !\"#$%&'()*+,-./0123456789:;<=>?" 82 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 83 "`abcdefghijklmnopqrstuvwxyz{|}~"; 84 + 85 + 86 +/// word sepearors normal mode 87 +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; 88 +char wordDelimLarge[] = " \t"; /// <Word sepearors normal mode (capital W) 89 + 90 +/// Shortcusts executed in normal mode (which should not already be in use) 91 +struct NormalModeShortcuts normalModeShortcuts [] = { 92 + { 'C', "?Building\n" }, 93 + { 'c', "/Building\n" }, 94 + { 'F', "?: error:\n" }, 95 + { 'f', "/: error:\n" }, 96 + { 'X', "?juli@machine\n" }, 97 + { 'x', "/juli@machine\n" }, 98 +}; 99 + 100 +size_t const amountNormalModeShortcuts = sizeof(normalModeShortcuts) / sizeof(*normalModeShortcuts); 101 diff --git a/dynamicArray.h b/dynamicArray.h 102 new file mode 100644 103 index 0000000..c65fbef 104 --- /dev/null 105 +++ b/dynamicArray.h 106 @@ -0,0 +1,90 @@ 107 +#include <stdint.h> 108 +#include <assert.h> 109 +#include <stdlib.h> 110 +#include <string.h> 111 +#include <stdbool.h> 112 + 113 +/// Struct for which this file offers functionality in order to expand the array 114 +/// and set / get its content. 115 +typedef struct DynamicArray { 116 + uint8_t itemSize; 117 + uint32_t index; 118 + uint32_t allocated; 119 + char* content; 120 +} DynamicArray; 121 + 122 +#define EXPAND_STEP 15 123 + 124 +/// Default initializers for the dynamic array. 125 +#define CHAR_ARRAY {1, 0, 0, NULL} 126 +#define WORD_ARRAY {2, 0, 0, NULL} 127 +#define DWORD_ARRAY {4, 0, 0, NULL} 128 +#define QWORD_ARRAY {8, 0, 0, NULL} 129 +/// (Wasteful) utf-8 array, that always used 4 bytes in order to display a character, 130 +/// even if the space is not required. 131 +#define UTF8_ARRAY DWORD_ARRAY 132 + 133 + 134 +inline char* 135 +gnext(DynamicArray *s) { return &s->content[s->index+=s->itemSize]; } 136 + 137 +inline char* 138 +get(DynamicArray const * s) { return &s->content[s->index]; } 139 + 140 +inline char* 141 +view(DynamicArray const * s, uint32_t i) { 142 + return s->content + i*s->itemSize; 143 +} 144 + 145 +inline char * 146 +viewEnd(DynamicArray const *s, uint32_t i) { 147 + return s->content + s->index - (i + 1) * s->itemSize; 148 +} 149 + 150 +inline void 151 +set(DynamicArray* s, char const *vals, uint8_t amount) { 152 + assert(amount <= s->itemSize); 153 + memcpy(s->content + s->index, vals, amount); 154 +} 155 + 156 +inline void 157 +snext(DynamicArray* s, char const *vals, uint8_t amount) { 158 + set(s, vals, amount); 159 + s->index+=s->itemSize; 160 +} 161 + 162 +inline void 163 +empty(DynamicArray* s) { s->index = 0; } 164 + 165 +inline bool 166 +isEmpty(DynamicArray* s) { return s->index == 0; } 167 + 168 +inline uint32_t 169 +size(DynamicArray const * s) { return s->index / s->itemSize; } 170 + 171 +inline void 172 +pop(DynamicArray* s) { s->index -= s->itemSize; } 173 + 174 +inline void checkSetNext(DynamicArray *s, char const *c, uint8_t amount) { 175 + if (s->index + s->itemSize >= s->allocated) { 176 + if ((s->content = (char *)realloc( 177 + s->content, s->allocated += EXPAND_STEP * s->itemSize)) == NULL) { 178 + exit(1); 179 + }; 180 + } 181 + if (amount) { snext(s, c, amount); } 182 +} 183 + 184 +char *checkGetNext(DynamicArray *s) { 185 + if (s->index + s->itemSize >= s->allocated) { 186 + if ((s->content = (char *)realloc( 187 + s->content, s->allocated += EXPAND_STEP * s->itemSize)) == NULL) { 188 + exit(1); 189 + }; 190 + } 191 + s->index+=s->itemSize; 192 + return viewEnd(s, 0); 193 +} 194 + 195 +#define append(s, c) checkSetNext((s), (char const *) (c), (s)->itemSize) 196 +#define appendPartial(s, c, i) checkSetNext((s), (char const *) (c), (i)) 197 diff --git a/st.c b/st.c 198 index ede7ae6..27bfca8 100644 199 --- a/st.c 200 +++ b/st.c 201 @@ -1,8 +1,10 @@ 202 /* See LICENSE for license details. */ 203 +#include <assert.h> 204 #include <ctype.h> 205 #include <errno.h> 206 #include <fcntl.h> 207 #include <limits.h> 208 +#include <math.h> 209 #include <pwd.h> 210 #include <stdarg.h> 211 #include <stdio.h> 212 @@ -17,8 +19,10 @@ 213 #include <unistd.h> 214 #include <wchar.h> 215 216 + 217 #include "st.h" 218 #include "win.h" 219 +#include "dynamicArray.h" 220 221 #if defined(__linux) 222 #include <pty.h> 223 @@ -35,6 +39,8 @@ 224 #define ESC_ARG_SIZ 16 225 #define STR_BUF_SIZ ESC_BUF_SIZ 226 #define STR_ARG_SIZ ESC_ARG_SIZ 227 +//#define HISTSIZE 100 228 +#define HISTSIZE 2000 229 230 /* macros */ 231 #define IS_SET(flag) ((term.mode & (flag)) != 0) 232 @@ -42,6 +48,9 @@ 233 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 234 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 235 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 236 +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 237 + term.scr + HISTSIZE + 1) % HISTSIZE] : \ 238 + term.line[(y) - term.scr]) 239 240 enum term_mode { 241 MODE_WRAP = 1 << 0, 242 @@ -97,16 +106,18 @@ typedef struct { 243 int mode; 244 int type; 245 int snap; 246 - /* 247 - * Selection variables: 248 - * nb – normalized coordinates of the beginning of the selection 249 - * ne – normalized coordinates of the end of the selection 250 - * ob – original coordinates of the beginning of the selection 251 - * oe – original coordinates of the end of the selection 252 - */ 253 + /// Selection variables: 254 + /// ob – original coordinates of the beginning of the selection 255 + /// oe – original coordinates of the end of the selection 256 + struct { 257 + int x, y, scroll; 258 + } ob, oe; 259 + /// Selection variables; currently displayed chunk. 260 + /// nb – normalized coordinates of the beginning of the selection 261 + /// ne – normalized coordinates of the end of the selection 262 struct { 263 int x, y; 264 - } nb, ne, ob, oe; 265 + } nb, ne; 266 267 int alt; 268 } Selection; 269 @@ -117,6 +128,9 @@ typedef struct { 270 int col; /* nb col */ 271 Line *line; /* screen */ 272 Line *alt; /* alternate screen */ 273 + Line hist[HISTSIZE]; /* history buffer */ 274 + int histi; /* history index */ 275 + int scr; /* scroll back */ 276 int *dirty; /* dirtyness of lines */ 277 TCursor c; /* cursor */ 278 int ocx; /* old cursor col */ 279 @@ -152,6 +166,50 @@ typedef struct { 280 int narg; /* nb of args */ 281 } STREscape; 282 283 +/// Position (x, y , and current scroll in the y dimension). 284 +typedef struct Position { 285 + uint32_t x; 286 + uint32_t y; 287 + uint32_t yScr; 288 +} Position; 289 + 290 +/// The entire normal mode state, consisting of an operation 291 +/// and a motion. 292 +struct NormalModeState { 293 + Position initialPosition; 294 + // Operation: 295 + struct OperationState { 296 + enum Operation { 297 + noop, 298 + visual, 299 + visualLine, 300 + yank 301 + } op; 302 + Position startPosition; 303 + } command; 304 + // Motions: 305 + struct MotionState { 306 + uint32_t amount; 307 + enum Search { 308 + none, 309 + forward, 310 + backward, 311 + } search; 312 + Position searchPosition; 313 + bool finished; 314 + } motion; 315 +} stateNormalMode; 316 + 317 + 318 +DynamicArray searchString = UTF8_ARRAY; 319 +DynamicArray commandHist0 = UTF8_ARRAY; 320 +DynamicArray commandHist1 = UTF8_ARRAY; 321 +DynamicArray highlights = QWORD_ARRAY; 322 +/// History command toggle 323 +bool toggle = false; 324 +#define currentCommand toggle ? &commandHist0 : &commandHist1 325 +#define lastCommand toggle ? &commandHist1 : &commandHist0 326 + 327 static void execsh(char *, char **); 328 static void stty(char **); 329 static void sigchld(int); 330 @@ -184,8 +242,8 @@ static void tnewline(int); 331 static void tputtab(int); 332 static void tputc(Rune); 333 static void treset(void); 334 -static void tscrollup(int, int); 335 -static void tscrolldown(int, int); 336 +static void tscrollup(int, int, int); 337 +static void tscrolldown(int, int, int); 338 static void tsetattr(int *, int); 339 static void tsetchar(Rune, Glyph *, int, int); 340 static void tsetdirt(int, int); 341 @@ -231,6 +289,12 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 342 static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 343 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 344 345 +void applyPosition(Position const *pos) { 346 + term.c.x = pos->x; 347 + term.c.y = pos->y; 348 + term.scr = pos->yScr; 349 +} 350 + 351 ssize_t 352 xwrite(int fd, const char *s, size_t len) 353 { 354 @@ -409,17 +473,22 @@ tlinelen(int y) 355 { 356 int i = term.col; 357 358 - if (term.line[y][i - 1].mode & ATTR_WRAP) 359 + if (TLINE(y)[i - 1].mode & ATTR_WRAP) 360 return i; 361 362 - while (i > 0 && term.line[y][i - 1].u == ' ') 363 + while (i > 0 && TLINE(y)[i - 1].u == ' ') 364 --i; 365 366 return i; 367 } 368 369 void 370 -selstart(int col, int row, int snap) 371 +xselstart(int col, int row, int snap) { 372 + selstart(col, row, term.scr, snap); 373 +} 374 + 375 +void 376 +selstart(int col, int row, int scroll, int snap) 377 { 378 selclear(); 379 sel.mode = SEL_EMPTY; 380 @@ -428,6 +497,7 @@ selstart(int col, int row, int snap) 381 sel.snap = snap; 382 sel.oe.x = sel.ob.x = col; 383 sel.oe.y = sel.ob.y = row; 384 + sel.oe.scroll = sel.ob.scroll = scroll; 385 selnormalize(); 386 387 if (sel.snap != 0) 388 @@ -436,10 +506,13 @@ selstart(int col, int row, int snap) 389 } 390 391 void 392 -selextend(int col, int row, int type, int done) 393 -{ 394 - int oldey, oldex, oldsby, oldsey, oldtype; 395 +xselextend(int col, int row, int type, int done) { 396 + selextend(col, row, term.scr, type, done); 397 +} 398 399 +void 400 +selextend(int col, int row, int scroll, int type, int done) 401 +{ 402 if (sel.mode == SEL_IDLE) 403 return; 404 if (done && sel.mode == SEL_EMPTY) { 405 @@ -447,18 +520,22 @@ selextend(int col, int row, int type, int done) 406 return; 407 } 408 409 - oldey = sel.oe.y; 410 - oldex = sel.oe.x; 411 - oldsby = sel.nb.y; 412 - oldsey = sel.ne.y; 413 - oldtype = sel.type; 414 + int const oldey = sel.oe.y; 415 + int const oldex = sel.oe.x; 416 + int const oldscroll = sel.oe.scroll; 417 + int const oldsby = sel.nb.y; 418 + int const oldsey = sel.ne.y; 419 + int const oldtype = sel.type; 420 421 sel.oe.x = col; 422 sel.oe.y = row; 423 + sel.oe.scroll = scroll; 424 + 425 selnormalize(); 426 sel.type = type; 427 428 - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 429 + if (oldey != sel.oe.y || oldex != sel.oe.x || oldscroll != sel.oe.scroll 430 + || oldtype != sel.type || sel.mode == SEL_EMPTY) 431 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 432 433 sel.mode = done ? SEL_IDLE : SEL_READY; 434 @@ -467,17 +544,21 @@ selextend(int col, int row, int type, int done) 435 void 436 selnormalize(void) 437 { 438 - int i; 439 - 440 - if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 441 - sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 442 - sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 443 + sel.nb.y = INTERVAL(sel.ob.y + term.scr - sel.ob.scroll, 0, term.bot); 444 + sel.ne.y = INTERVAL(sel.oe.y + term.scr - sel.oe.scroll, 0, term.bot); 445 + if (sel.type == SEL_REGULAR && sel.nb.y != sel.ne.y) { 446 + sel.nb.x = sel.nb.y < sel.ne.y ? sel.ob.x : sel.oe.x; 447 + sel.ne.x = sel.nb.y < sel.ne.y ? sel.oe.x : sel.ob.x; 448 } else { 449 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 450 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 451 } 452 - sel.nb.y = MIN(sel.ob.y, sel.oe.y); 453 - sel.ne.y = MAX(sel.ob.y, sel.oe.y); 454 + 455 + if (sel.nb.y > sel.ne.y) { 456 + int32_t const tmp = sel.nb.y; 457 + sel.nb.y = sel.ne.y; 458 + sel.ne.y = tmp; 459 + } 460 461 selsnap(&sel.nb.x, &sel.nb.y, -1); 462 selsnap(&sel.ne.x, &sel.ne.y, +1); 463 @@ -485,7 +566,7 @@ selnormalize(void) 464 /* expand selection over line breaks */ 465 if (sel.type == SEL_RECTANGULAR) 466 return; 467 - i = tlinelen(sel.nb.y); 468 + int i = tlinelen(sel.nb.y); 469 if (i < sel.nb.x) 470 sel.nb.x = i; 471 if (tlinelen(sel.ne.y) <= sel.ne.x) 472 @@ -521,7 +602,7 @@ selsnap(int *x, int *y, int direction) 473 * Snap around if the word wraps around at the end or 474 * beginning of a line. 475 */ 476 - prevgp = &term.line[*y][*x]; 477 + prevgp = &TLINE(*y)[*x]; 478 prevdelim = ISDELIM(prevgp->u); 479 for (;;) { 480 newx = *x + direction; 481 @@ -536,14 +617,14 @@ selsnap(int *x, int *y, int direction) 482 yt = *y, xt = *x; 483 else 484 yt = newy, xt = newx; 485 - if (!(term.line[yt][xt].mode & ATTR_WRAP)) 486 + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 487 break; 488 } 489 490 if (newx >= tlinelen(newy)) 491 break; 492 493 - gp = &term.line[newy][newx]; 494 + gp = &TLINE(newy)[newx]; 495 delim = ISDELIM(gp->u); 496 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 497 || (delim && gp->u != prevgp->u))) 498 @@ -564,14 +645,14 @@ selsnap(int *x, int *y, int direction) 499 *x = (direction < 0) ? 0 : term.col - 1; 500 if (direction < 0) { 501 for (; *y > 0; *y += direction) { 502 - if (!(term.line[*y-1][term.col-1].mode 503 + if (!(TLINE(*y-1)[term.col-1].mode 504 & ATTR_WRAP)) { 505 break; 506 } 507 } 508 } else if (direction > 0) { 509 for (; *y < term.row-1; *y += direction) { 510 - if (!(term.line[*y][term.col-1].mode 511 + if (!(TLINE(*y)[term.col-1].mode 512 & ATTR_WRAP)) { 513 break; 514 } 515 @@ -591,24 +672,32 @@ getsel(void) 516 if (sel.ob.x == -1) 517 return NULL; 518 519 - bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 520 + int32_t syb = sel.ob.y - sel.ob.scroll + term.scr; 521 + int32_t sye = sel.oe.y - sel.oe.scroll + term.scr; 522 + if (syb > sye) { 523 + int32_t tmp = sye; 524 + sye = syb; 525 + syb = tmp; 526 + } 527 + 528 + bufsize = (term.col+1) * (sye - syb + 1) * UTF_SIZ; 529 ptr = str = xmalloc(bufsize); 530 531 /* append every set & selected glyph to the selection */ 532 - for (y = sel.nb.y; y <= sel.ne.y; y++) { 533 + for (y = syb; y <= sye; y++) { 534 if ((linelen = tlinelen(y)) == 0) { 535 *ptr++ = '\n'; 536 continue; 537 } 538 539 if (sel.type == SEL_RECTANGULAR) { 540 - gp = &term.line[y][sel.nb.x]; 541 + gp = &TLINE(y)[sel.nb.x]; 542 lastx = sel.ne.x; 543 } else { 544 - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 545 - lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 546 + gp = &TLINE(y)[syb == y ? sel.nb.x : 0]; 547 + lastx = (sye == y) ? sel.ne.x : term.col-1; 548 } 549 - last = &term.line[y][MIN(lastx, linelen-1)]; 550 + last = &TLINE(y)[MIN(lastx, linelen-1)]; 551 while (last >= gp && last->u == ' ') 552 --last; 553 554 @@ -831,6 +920,9 @@ void 555 ttywrite(const char *s, size_t n, int may_echo) 556 { 557 const char *next; 558 + Arg arg = (Arg) { .i = term.scr }; 559 + 560 + kscrolldown(&arg); 561 562 if (may_echo && IS_SET(MODE_ECHO)) 563 twrite(s, n, 1); 564 @@ -1042,13 +1134,53 @@ tswapscreen(void) 565 } 566 567 void 568 -tscrolldown(int orig, int n) 569 +kscrolldown(const Arg* a) 570 +{ 571 + int n = a->i; 572 + 573 + if (n < 0) 574 + n = term.row + n; 575 + 576 + if (n > term.scr) 577 + n = term.scr; 578 + 579 + if (term.scr > 0) { 580 + term.scr -= n; 581 + selscroll(0, -n); 582 + tfulldirt(); 583 + } 584 +} 585 + 586 +void 587 +kscrollup(const Arg* a) 588 +{ 589 + int n = a->i; 590 + 591 + if (n < 0) 592 + n = term.row + n; 593 + 594 + if (term.scr <= HISTSIZE-n) { 595 + term.scr += n; 596 + selscroll(0, n); 597 + tfulldirt(); 598 + } 599 +} 600 + 601 +void 602 +tscrolldown(int orig, int n, int copyhist) 603 { 604 int i; 605 Line temp; 606 607 LIMIT(n, 0, term.bot-orig+1); 608 609 + if (copyhist) { 610 + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 611 + temp = term.hist[term.histi]; 612 + term.hist[term.histi] = term.line[term.bot]; 613 + term.line[term.bot] = temp; 614 + } 615 + 616 tsetdirt(orig, term.bot-n); 617 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 618 619 @@ -1062,13 +1194,23 @@ tscrolldown(int orig, int n) 620 } 621 622 void 623 -tscrollup(int orig, int n) 624 +tscrollup(int orig, int n, int copyhist) 625 { 626 int i; 627 Line temp; 628 629 LIMIT(n, 0, term.bot-orig+1); 630 631 + if (copyhist) { 632 + term.histi = (term.histi + 1) % HISTSIZE; 633 + temp = term.hist[term.histi]; 634 + term.hist[term.histi] = term.line[orig]; 635 + term.line[orig] = temp; 636 + } 637 + 638 + if (term.scr > 0 && term.scr < HISTSIZE) 639 + term.scr = MIN(term.scr + n, HISTSIZE-1); 640 + 641 tclearregion(0, orig, term.col-1, orig+n-1); 642 tsetdirt(orig+n, term.bot); 643 644 @@ -1088,6 +1230,7 @@ selscroll(int orig, int n) 645 return; 646 647 if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 648 + sel.oe.scroll = sel.ob.scroll = term.scr; 649 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 650 selclear(); 651 return; 652 @@ -1117,13 +1260,544 @@ tnewline(int first_col) 653 int y = term.c.y; 654 655 if (y == term.bot) { 656 - tscrollup(term.top, 1); 657 + tscrollup(term.top, 1, 1); 658 } else { 659 y++; 660 } 661 tmoveto(first_col ? 0 : term.c.x, y); 662 } 663 664 +int 665 +currentLine(int x, int y) 666 +{ 667 + return (x == term.c.x || y == term.c.y); 668 +} 669 + 670 +int 671 +highlighted(int x, int y) 672 +{ 673 + // Compute the legal bounds for a hit: 674 + int32_t const stringSize = size(&searchString); 675 + int32_t xMin = x - stringSize; 676 + int32_t yMin = y; 677 + while (xMin < 0 && yMin > 0) { //< I think this temds to be more efficient than 678 + xMin += term.col; // division + modulo. 679 + --yMin; 680 + } 681 + if (xMin < 0) { xMin = 0; } 682 + 683 + uint32_t highSize = size(&highlights); 684 + uint32_t *ptr = (uint32_t*) highlights.content; 685 + for (uint32_t i = 0; i < highSize; ++i) { 686 + int32_t const sx = *(ptr++); 687 + int32_t const sy = *(ptr++); 688 + if (BETWEEN(sy, yMin, y) && (sy != yMin || sx > xMin) && (sy != y || sx <= x)) { 689 + return true; 690 + } 691 + } 692 + return false; 693 +} 694 + 695 +int mod(int a, int b) { 696 + while (a < 0) { 697 + a+= b; 698 + } 699 + return a % b; 700 +} 701 + 702 +void displayString(DynamicArray const *str, Glyph *g, int yPos) { 703 + // Threshold: if there is nothing or no space to print, do not print. 704 + if (term.col == 0 || str->index == 0) { 705 + term.dirty[yPos] = 1; //< mark this line as 'dirty', because the line is not 706 + // marked dirty when scrolling due to string display. 707 + return; 708 + } 709 + 710 + uint32_t lineSize = MIN(size(str), term.col / 3); 711 + uint32_t xEnd = term.col - 1; 712 + assert(lineSize <= 1 + xEnd); //< as lineSize <= term.col/3 <= term.col - 1 + 1 = xEnd + 1 713 + uint32_t xStart = 1 + xEnd - lineSize; 714 + 715 + Line line = malloc(sizeof(Glyph) * lineSize); 716 + assert(str->index - 1 >= lineSize - 1); //< lineSize <= str->index -1 direct premise. 717 + 718 + for (uint32_t lineIdx = 0; lineIdx < lineSize; lineIdx++) { 719 + line[lineIdx] = *g; 720 + char* end = viewEnd(str, lineSize - lineIdx - 1); 721 + memcpy(&line[lineIdx].u, end, str->itemSize); 722 + } 723 + xdrawline(TLINE(yPos), 0, yPos, xStart); 724 + xdrawline(line -xStart, xStart, yPos, xEnd+1); 725 + free(line); // that sucks. 726 +} 727 + 728 +/// Print either the current command or the last comman din case the current command is empty. 729 +void printCommandString() { 730 + Glyph g = {'c', ATTR_ITALIC | ATTR_FAINT , defaultfg, defaultbg}; 731 + if (term.c.y == term.row-1) { g.mode ^= ATTR_CURRENT; } //< dont highlight 732 + DynamicArray * cc = currentCommand; 733 + displayString(isEmpty(cc) ? lastCommand : cc, &g, term.row - 1); 734 + //displayString(lastCommand, &g, term.row - 2); 735 +} 736 + 737 +void printSearchString() { 738 + Glyph g = {'c', ATTR_ITALIC | ATTR_BOLD_FAINT, defaultfg, defaultbg}; 739 + if (term.c.y == term.row-2) { g.mode ^= ATTR_CURRENT; } //< dont highlight 740 + displayString(&searchString, &g, term.row - 2); 741 +} 742 + 743 +/// Default state if no operation is performed. 744 +struct NormalModeState defaultNormalMode = {{0,0,0}, {noop, {0, 0, 0}}, {0, none, {0, 0, 0}, false}}; 745 + 746 +void enableMode(enum Operation o) { 747 + stateNormalMode.command.op = o; 748 + stateNormalMode.command.startPosition.x = term.c.x; 749 + stateNormalMode.command.startPosition.y = term.c.y; 750 + stateNormalMode.command.startPosition.yScr = term.scr; 751 +} 752 + 753 +bool normalModeEnabled = false; 754 + 755 +void onNormalModeStart() { 756 + normalModeEnabled = true; 757 +} 758 + 759 +void onNormalModeStop() { //XXX breaks if resized 760 + normalModeEnabled = false; 761 + applyPosition(&stateNormalMode.initialPosition); 762 +} 763 + 764 +void moveLine(int8_t sign) { 765 + if (sign == -1) { 766 + if (term.c.y-- == 0) { 767 + if (++term.scr == HISTSIZE) { 768 + term.c.y = term.row - 1; 769 + term.scr = 0; 770 + } else { 771 + term.c.y = 0; 772 + } 773 + } 774 + } else { 775 + term.c.x = 0; 776 + if (++term.c.y == term.row) { 777 + if (term.scr-- == 0) { 778 + term.c.y = 0; 779 + term.scr = HISTSIZE - 1; 780 + } else { 781 + term.c.y = term.row - 1; 782 + } 783 + } 784 + } 785 +} 786 + 787 +void moveLetter(int8_t sign) { 788 + term.c.x += sign; 789 + if (!BETWEEN(term.c.x, 0, term.col-1)) { 790 + if (term.c.x < 0) { 791 + term.c.x = term.col - 1; 792 + moveLine(sign); 793 + } else { 794 + term.c.x = 0; 795 + moveLine(sign); 796 + } 797 + } 798 +} 799 + 800 +bool contains (char ksym, char const * values, uint32_t amount) { 801 + for (uint32_t i = 0; i < amount; i++) { if (ksym == values[i]) { return true; } } 802 + return false; 803 +} 804 + 805 + 806 +void terminateCommand(bool abort) { 807 + stateNormalMode.command = defaultNormalMode.command; //< clear command + motion 808 + stateNormalMode.motion = defaultNormalMode.motion; 809 + selclear(); //< clear selection if any 810 + 811 + if (!abort) { toggle = !toggle; } 812 + empty(currentCommand); 813 + 814 + printCommandString(); 815 + printSearchString(); 816 + //tsetdirt(0, term.row-3); 817 +} 818 +inline void exitCommand() { terminateCommand(false); } 819 +inline void abortCommand() { terminateCommand(true); } 820 + 821 +/// Go to next occurrence of string relative to the current location 822 +/// conduct search, starting at start pos 823 +bool 824 +gotoString(int8_t sign) { 825 + uint32_t findIndex = 0; 826 + uint32_t searchStringSize = size(&searchString); 827 + uint32_t const maxIteration = (HISTSIZE + term.row) * term.col + searchStringSize; //< one complete traversal. 828 + for (uint32_t cIteration = 0; findIndex < searchStringSize 829 + && cIteration ++ < maxIteration; moveLetter(sign)) { 830 + uint32_t const searchChar = *((uint32_t*)(sign == 1 ? view(&searchString, findIndex) 831 + : viewEnd(&searchString, findIndex))); 832 + 833 + uint32_t const fu = TLINE(term.c.y)[term.c.x].u; 834 + 835 + if (fu == searchChar) findIndex++; 836 + else findIndex = 0; 837 + } 838 + bool const found = findIndex == searchStringSize; 839 + if (found) { for (uint32_t i = 0; i < searchStringSize; i++) { moveLetter(-sign); } } 840 + return found; 841 +} 842 + 843 +/// Find the next occurrence of a word 844 +bool 845 +gotoNextString(int8_t sign) { 846 + moveLetter(sign); 847 + return gotoString(sign); 848 +} 849 + 850 +/// Highlight all found strings on the current screen. 851 +void 852 +highlightStringOnScreen() { 853 + if (isEmpty(&searchString)) { return; } 854 + uint32_t const searchStringSize = size(&searchString); 855 + uint32_t findIndex = 0; 856 + uint32_t xStart, yStart; 857 + for (uint32_t y = 0; y < term.row; y++) { 858 + for (uint32_t x = 0; x < term.col; x++) { 859 + if (TLINE(y)[x].u == *((uint32_t*)(view(&searchString, findIndex)))) { 860 + if (findIndex++ == 0) { 861 + xStart = x; 862 + yStart = y; 863 + } 864 + if (findIndex == searchStringSize) { 865 + // mark selected 866 + append(&highlights, &xStart); 867 + append(&highlights, &yStart); 868 + 869 + findIndex = 0; 870 + term.dirty[yStart] = 1; 871 + } 872 + } else { 873 + findIndex = 0; 874 + } 875 + } 876 + } 877 +} 878 + 879 +void gotoStringAndHighlight(int8_t sign) { 880 + bool const found = gotoString(sign); //< find the next string to the current position 881 + empty(&highlights); //< remove previous highlights 882 + if (found) { //< apply new highlights if found 883 + //if (sign == -1) { moveLetter(-1); } 884 + highlightStringOnScreen(sign); 885 + } else { //< go to the position where the search started. 886 + applyPosition(&stateNormalMode.motion.searchPosition); 887 + } 888 + tsetdirt(0, term.row-3); //< repaint everything except for the status bar, which 889 + // is painted separately. 890 +} 891 + 892 +void pressKeys(char const* nullTerminatedString) { 893 + size_t end; 894 + for (size_t i = 0, end=strlen(nullTerminatedString); i < end; ++i) { 895 + if (nullTerminatedString[i] == '\n') { 896 + kpressNormalMode(&nullTerminatedString[i], 0, false, true, false); 897 + } else { 898 + kpressNormalMode(&nullTerminatedString[i], 1, false, false, false); 899 + } 900 + } 901 +} 902 + 903 +void executeCommand(DynamicArray const *command) { 904 + size_t end; 905 + char decoded [32]; 906 + for (size_t i = 0, end=size(command); i < end; ++i) { 907 + size_t len = utf8encode(*((Rune*)view(command, i)) , decoded); 908 + kpressNormalMode(decoded, len, false, false, false); 909 + } 910 + //kpressNormalMode(NULL, 0, false, true, false); 911 +} 912 + 913 +void kpressNormalMode(char const * ksym, uint32_t len, bool esc, bool enter, bool backspace) { 914 + // [ESC] or [ENTER] abort resp. finish the current operation or 915 + // the Normal Mode if no operation is currently executed. 916 + if (esc || enter) { 917 + if (stateNormalMode.command.op == noop 918 + && stateNormalMode.motion.search == none 919 + && stateNormalMode.motion.amount == 0) { 920 + terminateCommand(!enter); 921 + empty(&highlights); 922 + tfulldirt(); // < this also removes the search string and the last command. 923 + normalMode(NULL); 924 + } else { 925 + if (enter && stateNormalMode.motion.search != none && !isEmpty(&searchString)) { 926 + exitCommand(); //stateNormalMode.motion.finished = true; 927 + return; 928 + } else { 929 + abortCommand(); 930 + } 931 + } 932 + return; 933 + } //< ! (esc || enter) 934 + // Search: append to search string & conduct search for best hit, starting at start pos, 935 + // highlighting all other occurrences on the current page if one is found. 936 + if (stateNormalMode.motion.search != none && !stateNormalMode.motion.finished) { 937 + int8_t const sign = stateNormalMode.motion.search == forward ? 1 : -1; 938 + // Apply start position. 939 + if (backspace) { // XXX: if a quantifier is subject to removal, it is currently only removed 940 + // from the command string. 941 + if (!isEmpty(currentCommand) && !isEmpty(&searchString)) { 942 + pop(currentCommand); 943 + pop(&searchString); 944 + } else if (isEmpty(currentCommand) || isEmpty(&searchString)) { 945 + empty(&highlights); 946 + stateNormalMode.motion = defaultNormalMode .motion; //< if typed once more than there are 947 + selclear(); // letters, the search motion is 948 + return; // terminated 949 + } 950 + applyPosition(&stateNormalMode.motion.searchPosition); 951 + } else { 952 + if (len > 0) { 953 + char* kSearch = checkGetNext(&searchString); 954 + utf8decode(ksym, (Rune*)(kSearch), len); 955 + 956 + char* kCommand = checkGetNext(currentCommand); 957 + utf8decode(ksym, (Rune*)(kCommand), len); 958 + } 959 + } 960 + if (sign == -1) { moveLetter(1); } 961 + gotoStringAndHighlight(sign); //< go to the next occurrence of the string and highlight 962 + // all occurrences currently on screen 963 + 964 + if (stateNormalMode.command.op == visual) { 965 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); 966 + } else if (stateNormalMode.command.op == visualLine) { 967 + selextend(term.col-1, term.c.y, term.scr, sel.type, 0); 968 + } 969 + printCommandString(); 970 + printSearchString(); 971 + return; 972 + } 973 + 974 + if (len == 0) { return; } 975 + // V / v or y take precedence over movement commands. 976 + switch(ksym[0]) { 977 + case '.': 978 + { 979 + 980 + if (!isEmpty(currentCommand)) { toggle = !toggle; empty(currentCommand); } 981 + executeCommand(lastCommand); 982 + } 983 + return; 984 + case 'y': //< Yank mode 985 + { 986 + char* kCommand = checkGetNext(currentCommand); 987 + utf8decode(ksym, (Rune*)(kCommand), len); 988 + switch(stateNormalMode.command.op) { 989 + case noop: //< Start yank mode & set #op 990 + enableMode(yank); 991 + selstart(term.c.x, term.c.y, term.scr, 0); 992 + empty(currentCommand); 993 + break; 994 + case visualLine: //< Complete yank operation 995 + case visual: 996 + xsetsel(getsel()); //< yank 997 + xclipcopy(); 998 + exitCommand(); //< reset command 999 + break; 1000 + case yank: //< Complete yank operation as in y#amount j 1001 + selstart(0, term.c.y, term.scr, 0); 1002 + uint32_t const origY = term.c.y; 1003 + for (int32_t i = 0; i < MAX(stateNormalMode.motion.amount, 1) - 1; i ++) moveLine(1); 1004 + selextend(term.col-1, term.c.y, term.scr, SEL_RECTANGULAR, 0); 1005 + xsetsel(getsel()); 1006 + xclipcopy(); 1007 + term.c.y = origY; 1008 + exitCommand(); 1009 + } 1010 + } 1011 + printCommandString(); 1012 + printSearchString(); 1013 + return; 1014 + case 'v': //< Visual Mode: Toggle mode. 1015 + case 'V': 1016 + { 1017 + enum Operation mode = ksym[0] == 'v' ? visual : visualLine; 1018 + bool assign = stateNormalMode.command.op != mode; 1019 + abortCommand(); 1020 + if (assign) { 1021 + enableMode(mode); 1022 + char* kCommand = checkGetNext(currentCommand); 1023 + utf8decode(ksym, (Rune*)(kCommand), len); 1024 + if (mode == visualLine) { 1025 + selstart(0, term.c.y, term.scr, 0); 1026 + selextend(term.col-1, term.c.y, term.scr, SEL_RECTANGULAR, 0); 1027 + } else { 1028 + selstart(term.c.x, term.c.y, term.scr, 0); 1029 + } 1030 + } 1031 + } 1032 + return; 1033 + } 1034 + // Perform the movement. 1035 + int32_t sign = -1; //< whehter a command goes 'forward' (1) or 'backward' (-1) 1036 + bool discard = false; //< discard input, as it does not have a meaning. 1037 + switch(ksym[0]) { 1038 + case 'j': sign = 1; 1039 + case 'k': 1040 + term.c.y += sign * MAX(stateNormalMode.motion.amount, 1); 1041 + break; 1042 + case 'H': term.c.y = 0; break; //< [numer]H ~ L[number]j is not supported. 1043 + case 'M': term.c.y = term.bot / 2; break; 1044 + case 'L': term.c.y = term.bot; break; //< [numer]L ~ L[number]k is not supported. 1045 + case 'G': //< a little different from vim, but in this use case the most useful translation. 1046 + applyPosition(&stateNormalMode.initialPosition); 1047 + case 'l': sign = 1; 1048 + case 'h': 1049 + { 1050 + int32_t const amount = term.c.x + sign * MAX(stateNormalMode.motion.amount, 1); 1051 + term.c.x = amount % term.col; 1052 + while (term.c.x < 0) { term.c.x += term.col; } 1053 + term.c.y += floor(1.0 * amount / term.col); 1054 + break; 1055 + } 1056 + case '0': 1057 + if (stateNormalMode.motion.amount == 0) { term.c.x = 0; } 1058 + else { discard = true; } 1059 + break; 1060 + case '$': term.c.x = term.col-1; break; 1061 + case 'w': 1062 + case 'W': 1063 + case 'e': 1064 + case 'E': sign = 1; 1065 + case 'B': 1066 + case 'b': 1067 + { 1068 + bool const startSpaceIsSeparator = !(ksym[0] == 'w' || ksym[0] == 'W'); 1069 + bool const capital = ksym[0] <= 90; //< defines the word separators to use 1070 + char const * const wDelim = capital ? wordDelimLarge : wordDelimSmall; 1071 + uint32_t const wDelimLen = strlen(wDelim); 1072 + bool const performOffset = startSpaceIsSeparator; //< start & end with offset. 1073 + uint32_t const maxIteration = (HISTSIZE + term.row) * term.col; //< one complete traversal. 1074 + 1075 + // doesn't work exactly as in vim, but I think this version is better; 1076 + // Linebreak is counted as 'normal' separator; hence a jump can span multiple lines here. 1077 + stateNormalMode.motion.amount = MAX(stateNormalMode.motion.amount, 1); 1078 + for (; stateNormalMode.motion.amount > 0; stateNormalMode.motion.amount--) { 1079 + uint8_t state = 0; 1080 + if (performOffset) { moveLetter(sign); } 1081 + for (uint32_t cIteration = 0; cIteration ++ < maxIteration; moveLetter(sign)) { 1082 + if (startSpaceIsSeparator == contains(TLINE(term.c.y)[term.c.x].u, wDelim, wDelimLen)) { 1083 + if (state == 1) { 1084 + if (performOffset) { moveLetter(-sign); } 1085 + break; 1086 + } 1087 + } else if (state == 0) { state = 1; } 1088 + } 1089 + } 1090 + break; 1091 + } 1092 + case '/': sign = 1; 1093 + case '?': 1094 + empty(&searchString); 1095 + stateNormalMode.motion.search = sign == 1 ? forward : backward; 1096 + stateNormalMode.motion.searchPosition.x = term.c.x; 1097 + stateNormalMode.motion.searchPosition.y = term.c.y; 1098 + stateNormalMode.motion.searchPosition.yScr = term.scr; 1099 + stateNormalMode.motion.finished = false; 1100 + break; 1101 + case 'n': sign = 1; 1102 + case 'N': 1103 + toggle = !toggle; 1104 + empty(currentCommand); 1105 + if (stateNormalMode.motion.search == none) { 1106 + stateNormalMode.motion.search = forward; 1107 + stateNormalMode.motion.finished = true; 1108 + } 1109 + for (int32_t amount = MAX(stateNormalMode.motion.amount, 1); amount > 0; amount--) { 1110 + if (stateNormalMode.motion.search == backward) { sign *= -1; } 1111 + moveLetter(sign); 1112 + gotoStringAndHighlight(sign); 1113 + } 1114 + break; 1115 + case 't': 1116 + if (sel.type == SEL_REGULAR) { 1117 + sel.type = SEL_RECTANGULAR; 1118 + } else { 1119 + sel.type = SEL_REGULAR; 1120 + } 1121 + tsetdirt(sel.nb.y, sel.ne.y); 1122 + discard = true; 1123 + default: 1124 + discard = true; 1125 + } 1126 + bool const isNumber = len == 1 && BETWEEN(ksym[0], 48, 57); 1127 + if (isNumber) { //< record numbers 1128 + discard = false; 1129 + stateNormalMode.motion.amount = 1130 + MIN(SHRT_MAX, stateNormalMode.motion.amount * 10 + ksym[0] - 48); 1131 + } else if (!discard) { 1132 + stateNormalMode.motion.amount = 0; 1133 + } 1134 + 1135 + if (discard) { 1136 + for (size_t i = 0; i < amountNormalModeShortcuts; ++i) { 1137 + if (ksym[0] == normalModeShortcuts[i].key) { 1138 + pressKeys(normalModeShortcuts[i].value); 1139 + } 1140 + } 1141 + } else { 1142 + char* kCommand = checkGetNext(currentCommand); 1143 + utf8decode(ksym, (Rune*)(kCommand), len); 1144 + 1145 + int diff = 0; 1146 + if (term.c.y > 0) { 1147 + if (term.c.y > term.bot) { 1148 + diff = term.bot - term.c.y; 1149 + term.c.y = term.bot; 1150 + } 1151 + } else { 1152 + if (term.c.y < 0) { 1153 + diff = -term.c.y; 1154 + term.c.y = 0; 1155 + } 1156 + } 1157 + 1158 + int const _newScr = term.scr + diff; 1159 + term.c.y = _newScr < 0 ? 0 : (_newScr >= HISTSIZE ? term.bot : term.c.y); 1160 + term.scr = mod(_newScr, HISTSIZE); 1161 + 1162 + if (!isEmpty(&highlights)) { 1163 + empty(&highlights); 1164 + highlightStringOnScreen(); 1165 + } 1166 + 1167 + tsetdirt(0, term.row-3); 1168 + printCommandString(); 1169 + printSearchString(); 1170 + 1171 + if (stateNormalMode.command.op == visual) { 1172 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); 1173 + } else if (stateNormalMode.command.op == visualLine) { 1174 + selextend(term.col-1, term.c.y, term.scr, sel.type, 0); 1175 + } else { 1176 + if (!isNumber && (stateNormalMode.motion.search == none 1177 + || stateNormalMode.motion.finished)) { 1178 + toggle = !toggle; 1179 + empty(currentCommand); 1180 + } 1181 + if (stateNormalMode.command.op == yank) { 1182 + if (!isNumber && !discard) { 1183 + // copy 1184 + selextend(term.c.x, term.c.y, term.scr, sel.mode, 0); 1185 + xsetsel(getsel()); 1186 + xclipcopy(); 1187 + applyPosition(&stateNormalMode.command.startPosition); 1188 + exitCommand(); 1189 + } 1190 + } 1191 + } 1192 + } 1193 +} 1194 + 1195 void 1196 csiparse(void) 1197 { 1198 @@ -1176,6 +1850,10 @@ tmoveto(int x, int y) 1199 term.c.state &= ~CURSOR_WRAPNEXT; 1200 term.c.x = LIMIT(x, 0, term.col-1); 1201 term.c.y = LIMIT(y, miny, maxy); 1202 + // Set the last position in order to restore after normal mode exits. 1203 + stateNormalMode.initialPosition.x = term.c.x; 1204 + stateNormalMode.initialPosition.y = term.c.y; 1205 + stateNormalMode.initialPosition.yScr = term.scr; 1206 } 1207 1208 void 1209 @@ -1282,14 +1960,14 @@ void 1210 tinsertblankline(int n) 1211 { 1212 if (BETWEEN(term.c.y, term.top, term.bot)) 1213 - tscrolldown(term.c.y, n); 1214 + tscrolldown(term.c.y, n, 0); 1215 } 1216 1217 void 1218 tdeleteline(int n) 1219 { 1220 if (BETWEEN(term.c.y, term.top, term.bot)) 1221 - tscrollup(term.c.y, n); 1222 + tscrollup(term.c.y, n, 0); 1223 } 1224 1225 int32_t 1226 @@ -1720,11 +2398,11 @@ csihandle(void) 1227 break; 1228 case 'S': /* SU -- Scroll <n> line up */ 1229 DEFAULT(csiescseq.arg[0], 1); 1230 - tscrollup(term.top, csiescseq.arg[0]); 1231 + tscrollup(term.top, csiescseq.arg[0], 0); 1232 break; 1233 case 'T': /* SD -- Scroll <n> line down */ 1234 DEFAULT(csiescseq.arg[0], 1); 1235 - tscrolldown(term.top, csiescseq.arg[0]); 1236 + tscrolldown(term.top, csiescseq.arg[0], 0); 1237 break; 1238 case 'L': /* IL -- Insert <n> blank lines */ 1239 DEFAULT(csiescseq.arg[0], 1); 1240 @@ -2227,7 +2905,7 @@ eschandle(uchar ascii) 1241 return 0; 1242 case 'D': /* IND -- Linefeed */ 1243 if (term.c.y == term.bot) { 1244 - tscrollup(term.top, 1); 1245 + tscrollup(term.top, 1, 1); 1246 } else { 1247 tmoveto(term.c.x, term.c.y+1); 1248 } 1249 @@ -2240,7 +2918,7 @@ eschandle(uchar ascii) 1250 break; 1251 case 'M': /* RI -- Reverse index */ 1252 if (term.c.y == term.top) { 1253 - tscrolldown(term.top, 1); 1254 + tscrolldown(term.top, 1, 1); 1255 } else { 1256 tmoveto(term.c.x, term.c.y-1); 1257 } 1258 @@ -2458,7 +3136,7 @@ twrite(const char *buf, int buflen, int show_ctrl) 1259 void 1260 tresize(int col, int row) 1261 { 1262 - int i; 1263 + int i, j; 1264 int minrow = MIN(row, term.row); 1265 int mincol = MIN(col, term.col); 1266 int *bp; 1267 @@ -2495,6 +3173,14 @@ tresize(int col, int row) 1268 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 1269 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 1270 1271 + for (i = 0; i < HISTSIZE; i++) { 1272 + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 1273 + for (j = mincol; j < col; j++) { 1274 + term.hist[i][j] = term.c.attr; 1275 + term.hist[i][j].u = ' '; 1276 + } 1277 + } 1278 + 1279 /* resize each row to new width, zero-pad if needed */ 1280 for (i = 0; i < minrow; i++) { 1281 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 1282 @@ -2552,7 +3238,7 @@ drawregion(int x1, int y1, int x2, int y2) 1283 continue; 1284 1285 term.dirty[y] = 0; 1286 - xdrawline(term.line[y], x1, y, x2); 1287 + xdrawline(TLINE(y), x1, y, x2); 1288 } 1289 } 1290 1291 @@ -2573,8 +3259,8 @@ draw(void) 1292 cx--; 1293 1294 drawregion(0, 0, term.col, term.row); 1295 - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 1296 - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 1297 + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], 1298 + term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); 1299 term.ocx = cx, term.ocy = term.c.y; 1300 xfinishdraw(); 1301 xximspot(term.ocx, term.ocy); 1302 diff --git a/st.h b/st.h 1303 index 4da3051..7bd8bba 100644 1304 --- a/st.h 1305 +++ b/st.h 1306 @@ -1,5 +1,6 @@ 1307 /* See LICENSE for license details. */ 1308 1309 +#include <stdbool.h> 1310 #include <stdint.h> 1311 #include <sys/types.h> 1312 1313 @@ -10,6 +11,8 @@ 1314 #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 1315 #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) 1316 #define DEFAULT(a, b) (a) = (a) ? (a) : (b) 1317 +#define INTERVAL(x, a, b) (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 1318 +#define INTERVAL_DIFF(x, a, b) (x) < (a) ? (x) - (a) : (x) > (b) ? (x) - (b) : 0 1319 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 1320 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ 1321 (a).bg != (b).bg) 1322 @@ -33,6 +36,8 @@ enum glyph_attribute { 1323 ATTR_WRAP = 1 << 8, 1324 ATTR_WIDE = 1 << 9, 1325 ATTR_WDUMMY = 1 << 10, 1326 + ATTR_HIGHLIGHT = 1 << 11 | ATTR_UNDERLINE, 1327 + ATTR_CURRENT = 1 << 12, 1328 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 1329 }; 1330 1331 @@ -80,6 +85,14 @@ void die(const char *, ...); 1332 void redraw(void); 1333 void draw(void); 1334 1335 +int highlighted(int, int); 1336 +int currentLine(int, int); 1337 +void kscrolldown(const Arg *); 1338 +void kscrollup(const Arg *); 1339 +void kpressNormalMode(char const * ksym, uint32_t len, bool esc, bool enter, bool backspace); 1340 +void normalMode(Arg const *); 1341 +void onNormalModeStart(); 1342 +void onNormalModeStop(); 1343 void printscreen(const Arg *); 1344 void printsel(const Arg *); 1345 void sendbreak(const Arg *); 1346 @@ -99,8 +112,10 @@ void resettitle(void); 1347 1348 void selclear(void); 1349 void selinit(void); 1350 -void selstart(int, int, int); 1351 -void selextend(int, int, int, int); 1352 +void selstart(int, int, int, int); 1353 +void xselstart(int, int, int); 1354 +void selextend(int, int, int, int, int); 1355 +void xselextend(int, int, int, int); 1356 int selected(int, int); 1357 char *getsel(void); 1358 1359 @@ -110,6 +125,8 @@ void *xmalloc(size_t); 1360 void *xrealloc(void *, size_t); 1361 char *xstrdup(char *); 1362 1363 + 1364 + 1365 /* config.h globals */ 1366 extern char *utmp; 1367 extern char *stty_args; 1368 @@ -120,3 +137,13 @@ extern char *termname; 1369 extern unsigned int tabspaces; 1370 extern unsigned int defaultfg; 1371 extern unsigned int defaultbg; 1372 +extern char wordDelimSmall[]; 1373 +extern char wordDelimLarge[]; 1374 + 1375 +typedef struct NormalModeShortcuts { 1376 + char key; 1377 + char *value; 1378 +} NormalModeShortcuts; 1379 + 1380 +extern NormalModeShortcuts normalModeShortcuts[]; 1381 +extern size_t const amountNormalModeShortcuts; 1382 diff --git a/win.h b/win.h 1383 index a6ef1b9..1a6fefe 100644 1384 --- a/win.h 1385 +++ b/win.h 1386 @@ -19,6 +19,7 @@ enum win_mode { 1387 MODE_MOUSEMANY = 1 << 15, 1388 MODE_BRCKTPASTE = 1 << 16, 1389 MODE_NUMLOCK = 1 << 17, 1390 + MODE_NORMAL = 1 << 18, 1391 MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 1392 |MODE_MOUSEMANY, 1393 }; 1394 @@ -27,6 +28,7 @@ void xbell(void); 1395 void xclipcopy(void); 1396 void xdrawcursor(int, int, Glyph, int, int, Glyph); 1397 void xdrawline(Line, int, int, int); 1398 +void xdrawglyph(Glyph, int, int); 1399 void xfinishdraw(void); 1400 void xloadcols(void); 1401 int xsetcolorname(int, const char *); 1402 diff --git a/x.c b/x.c 1403 index 5828a3b..ccf1751 100644 1404 --- a/x.c 1405 +++ b/x.c 1406 @@ -136,7 +136,6 @@ typedef struct { 1407 static inline ushort sixd_to_16bit(int); 1408 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 1409 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 1410 -static void xdrawglyph(Glyph, int, int); 1411 static void xclear(int, int, int, int); 1412 static int xgeommasktogravity(int); 1413 static void ximopen(Display *); 1414 @@ -340,7 +339,7 @@ mousesel(XEvent *e, int done) 1415 break; 1416 } 1417 } 1418 - selextend(evcol(e), evrow(e), seltype, done); 1419 + xselextend(evcol(e), evrow(e), seltype, done); 1420 if (done) 1421 setsel(getsel(), e->xbutton.time); 1422 } 1423 @@ -444,7 +443,7 @@ bpress(XEvent *e) 1424 xsel.tclick2 = xsel.tclick1; 1425 xsel.tclick1 = now; 1426 1427 - selstart(evcol(e), evrow(e), snap); 1428 + xselstart(evcol(e), evrow(e), snap); 1429 } 1430 } 1431 1432 @@ -730,6 +729,19 @@ xloadcolor(int i, const char *name, Color *ncolor) 1433 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); 1434 } 1435 1436 +void 1437 +normalMode(Arg const *_) //< the argument is just for the sake of 1438 + // adhering to the function format. 1439 +{ 1440 + win.mode ^= MODE_NORMAL; //< toggle normal mode via exclusive or. 1441 + if (win.mode & MODE_NORMAL) { 1442 + onNormalModeStart(); 1443 + } else { 1444 + onNormalModeStop(); 1445 + } 1446 +} 1447 + 1448 + 1449 void 1450 xloadcols(void) 1451 { 1452 @@ -1296,6 +1308,14 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i 1453 base.fg = defaultattr; 1454 } 1455 1456 + if (base.mode & ATTR_HIGHLIGHT) { 1457 + base.bg = highlightBg; 1458 + base.fg = highlightFg; 1459 + } else if ((base.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { 1460 + base.bg = currentBg; 1461 + base.fg = currentFg; 1462 + } 1463 + 1464 if (IS_TRUECOL(base.fg)) { 1465 colfg.alpha = 0xffff; 1466 colfg.red = TRUERED(base.fg); 1467 @@ -1428,8 +1448,9 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 1468 Color drawcol; 1469 1470 /* remove the old cursor */ 1471 - if (selected(ox, oy)) 1472 - og.mode ^= ATTR_REVERSE; 1473 + if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; 1474 + if (highlighted(ox, oy)) { og.mode ^= ATTR_HIGHLIGHT; } 1475 + if (currentLine(ox, oy)) { og.mode ^= ATTR_CURRENT; } 1476 xdrawglyph(og, ox, oy); 1477 1478 if (IS_SET(MODE_HIDE)) 1479 @@ -1461,6 +1482,11 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) 1480 drawcol = dc.col[g.bg]; 1481 } 1482 1483 + if ((g.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { 1484 + g.bg = currentBg; 1485 + g.fg = currentFg; 1486 + } 1487 + 1488 /* draw the new one */ 1489 if (IS_SET(MODE_FOCUSED)) { 1490 switch (win.cursor) { 1491 @@ -1550,6 +1576,12 @@ xdrawline(Line line, int x1, int y1, int x2) 1492 continue; 1493 if (selected(x, y1)) 1494 new.mode ^= ATTR_REVERSE; 1495 + if (highlighted(x, y1)) { 1496 + new.mode ^= ATTR_HIGHLIGHT; 1497 + } 1498 + if (currentLine(x, y1)) { 1499 + new.mode ^= ATTR_CURRENT; 1500 + } 1501 if (i > 0 && ATTRCMP(base, new)) { 1502 xdrawglyphfontspecs(specs, base, i, ox, y1); 1503 specs += i; 1504 @@ -1731,6 +1763,12 @@ kpress(XEvent *ev) 1505 return; 1506 1507 len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); 1508 + if (IS_SET(MODE_NORMAL)) { 1509 + kpressNormalMode(buf, strlen(buf), 1510 + ksym == XK_Escape, ksym == XK_Return, ksym == XK_BackSpace); 1511 + return; 1512 + } 1513 + 1514 /* 1. shortcuts */ 1515 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 1516 if (ksym == bp->keysym && match(bp->mod, e->state)) { 1517 @@ -1870,8 +1908,9 @@ run(void) 1518 XNextEvent(xw.dpy, &ev); 1519 if (XFilterEvent(&ev, None)) 1520 continue; 1521 - if (handler[ev.type]) 1522 + if (handler[ev.type]) { 1523 (handler[ev.type])(&ev); 1524 + } 1525 } 1526 1527 draw(); 1528 -- 1529 2.24.0 1530