st-vimBrowse-20200604-295a43f.diff (64935B)
1 diff -ruN st-default/config.def.h st1/config.def.h 2 --- st-default/config.def.h 2020-06-04 11:15:55.164135902 +0200 3 +++ st1/config.def.h 2020-06-04 11:15:28.476134951 +0200 4 @@ -56,6 +56,10 @@ 5 static double minlatency = 8; 6 static double maxlatency = 33; 7 8 +/* frames per second st should at maximum draw to the screen */ 9 +static unsigned int xfps = 120; 10 +static unsigned int actionfps = 30; 11 + 12 /* 13 * blinking timeout (set to 0 to disable blinking) for the terminal blinking 14 * attribute. 15 @@ -160,6 +164,14 @@ 16 * doesn't match the ones requested. 17 */ 18 static unsigned int defaultattr = 11; 19 +/// Colors for the entities that are 'highlighted' in normal mode (search 20 +/// results currently on screen) [Vim Browse]. 21 +static unsigned int highlightBg = 160; 22 +static unsigned int highlightFg = 15; 23 +/// Colors for highlighting the current cursor position (row + col) in normal 24 +/// mode [Vim Browse]. 25 +static unsigned int currentBg = 8; 26 +static unsigned int currentFg = 15; 27 28 /* 29 * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). 30 @@ -175,18 +187,18 @@ 31 static MouseShortcut mshortcuts[] = { 32 /* mask button function argument release */ 33 { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, 34 - { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, 35 { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, 36 - { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, 37 { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, 38 }; 39 40 /* Internal keyboard shortcuts. */ 41 #define MODKEY Mod1Mask 42 +#define AltMask Mod1Mask 43 #define TERMMOD (ControlMask|ShiftMask) 44 45 static Shortcut shortcuts[] = { 46 /* mask keysym function argument */ 47 + { AltMask, XK_c, normalMode, {.i = 0} }, 48 { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, 49 { ControlMask, XK_Print, toggleprinter, {.i = 0} }, 50 { ShiftMask, XK_Print, printscreen, {.i = 0} }, 51 @@ -199,6 +211,8 @@ 52 { TERMMOD, XK_Y, selpaste, {.i = 0} }, 53 { ShiftMask, XK_Insert, selpaste, {.i = 0} }, 54 { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, 55 + { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, 56 + { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, 57 }; 58 59 /* 60 @@ -470,3 +484,45 @@ 61 " !\"#$%&'()*+,-./0123456789:;<=>?" 62 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 63 "`abcdefghijklmnopqrstuvwxyz{|}~"; 64 + 65 + 66 +/// word sepearors normal mode 67 +/// [Vim Browse]. 68 +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; 69 +char wordDelimLarge[] = " \t"; /// <Word sepearors normal mode (capital W) 70 + 71 +/// Shortcusts executed in normal mode (which should not already be in use) 72 +/// [Vim Browse]. 73 +struct NormalModeShortcuts normalModeShortcuts [] = { 74 + { 'R', "?Building\n" }, 75 + { 'r', "/Building\n" }, 76 + { 'F', "?: error:\n" }, 77 + { 'f', "/: error:\n" }, 78 + { 'Q', "?[Leaving vim, starting execution]\n" }, 79 + { 'S', "Qf" }, 80 + { 'X', "?juli@machine\n" }, 81 + { 'x', "/juli@machine\n" }, 82 +}; 83 + 84 +size_t const amountNormalModeShortcuts = sizeof(normalModeShortcuts) / sizeof(*normalModeShortcuts); 85 + 86 +/// Style of the command string visualized in normal mode in the right corner 87 +/// [Vim Browse]. 88 +Glyph const styleCommand = {' ', ATTR_ITALIC | ATTR_FAINT, 7, 16}; 89 +/// Style of the search string visualized in normal mode in the right corner. 90 +/// [Vim Browse]. 91 +Glyph const styleSearch = {' ', ATTR_ITALIC | ATTR_BOLD_FAINT, 7, 16}; 92 + 93 +/// Colors used in normal mode in order to highlight different operations and 94 +/// empathise the current position on screen in the status area [Vim Browse]. 95 +unsigned int bgCommandYank = 11; 96 +unsigned int bgCommandVisual = 4; 97 +unsigned int bgCommandVisualLine = 12; 98 + 99 +unsigned int fgCommandYank = 232; 100 +unsigned int fgCommandVisual = 232; 101 +unsigned int fgCommandVisualLine = 232; 102 + 103 +unsigned int bgPos = 15; 104 +unsigned int fgPos = 16; 105 + 106 diff -ruN st-default/dynamicArray.h st1/dynamicArray.h 107 --- st-default/dynamicArray.h 1970-01-01 01:00:00.000000000 +0100 108 +++ st1/dynamicArray.h 2020-06-04 11:04:30.227111509 +0200 109 @@ -0,0 +1,175 @@ 110 +#ifndef DYNAMIC_ARRAY_H 111 +#define DYNAMIC_ARRAY_H 112 + 113 +#include "error.h" 114 + 115 +#include <stdint.h> 116 +#include <stdlib.h> 117 +#include <string.h> 118 +#include <stdbool.h> 119 + 120 +/// Struct for which this file offers functionality in order to expand the array 121 +/// and set / get its content. 122 +typedef struct DynamicArray { 123 + /// Size of the datatype contained in the array. 124 + uint8_t itemSize; 125 + /// Amount of bytes currently initialized 126 + uint32_t index; 127 + /// Amount of bytes currently reserved (not necessarily initialized) 128 + uint32_t allocated; 129 + /// Actual content. 130 + char* content; 131 +} DynamicArray; 132 + 133 +#define EXPAND_STEP 15 134 + 135 +/// Default initializers for the dynamic array. 136 +#define CHAR_ARRAY {1, 0, 0, NULL} 137 +#define WORD_ARRAY {2, 0, 0, NULL} 138 +#define DWORD_ARRAY {4, 0, 0, NULL} 139 +#define QWORD_ARRAY {8, 0, 0, NULL} 140 +/// (Wasteful) utf-8 array, that always used 4 bytes in order to display a 141 +/// character, even if the space is not required. 142 +#define UTF8_ARRAY DWORD_ARRAY 143 + 144 +/// Check that at least \p bytes are allocated, if true implying that 145 +/// \p s->content[\bytes - 1] is allocated. 146 +static inline bool 147 +isAllocated(DynamicArray const *s, uint32_t bytes) { 148 + return s != NULL && s->allocated >= bytes; 149 +} 150 + 151 +/// @see #isAllocated 152 +static inline bool 153 +isInitialized(DynamicArray const *s, uint32_t bytes) { 154 + return s != NULL && s->index >= bytes; 155 +} 156 + 157 +/// Return the next element in \p s and increment index without checking bounds. 158 +static inline char* 159 +gnext(DynamicArray *s) { 160 + ENSURE(s!=NULL, return NULL); 161 + ENSURE(s->index % s->itemSize == 0 && "(index not aligned)", 162 + s->index += s->itemSize - (s->index % s->itemSize)); 163 + ENSURE(isAllocated(s, s->index + 2 * s->itemSize), return NULL); 164 + return s->content + (s->index += s->itemSize); 165 +} 166 + 167 +/// View element \p i in \p s. 168 +static inline char* 169 +view(DynamicArray const * s, uint32_t i) { 170 + ENSURE((s != NULL) && isAllocated(s, (i+1) * s->itemSize), return NULL); 171 + return s->content + i*s->itemSize; 172 +} 173 + 174 +/// Inspect element content[size() - 1 - i]. 175 +static inline char * 176 +viewEnd(DynamicArray const *s, uint32_t i) { 177 + ENSURE((s != NULL) && isInitialized(s, i * s->itemSize), return NULL); 178 + ENSURE(s->index%s->itemSize == 0 && "(index not aligned)", return NULL); 179 + return s->content + s->index - (i + 1) * s->itemSize; 180 +} 181 + 182 +/// Set conent without applying 183 +static inline bool 184 +setValues(DynamicArray* s, char const *vals, uint32_t amount) { 185 + ENSURE(vals != NULL, return false); 186 + ENSURE((s != NULL) && isAllocated(s, s->index + amount), return false); 187 + memcpy(s->content + s->index, vals, amount); 188 + return true; 189 +} 190 + 191 +static inline bool 192 +snext(DynamicArray* s, char const *vals, uint32_t amount) { 193 + bool const success = setValues(s, vals, amount); 194 + ENSURE(success, return false); 195 + uint8_t const rest = amount % s->itemSize; 196 + uint32_t const newSize = s->index + amount + (rest ? s->itemSize : 0); 197 + ENSURE(isAllocated(s, newSize), return false); 198 + s->index = newSize; 199 + return true; 200 +} 201 + 202 +/// Empty \p s. 203 +static inline void 204 +empty(DynamicArray* s) { 205 + ENSURE((s != NULL), return); 206 + s->index = 0; 207 +} 208 + 209 +/// Check if \p s has initialized content (which can be the case even if memory 210 +/// is allocated). 211 +static inline bool 212 +isEmpty(DynamicArray const * s) { 213 + ENSURE((s != NULL), return true); 214 + return s->index == 0; 215 +} 216 + 217 +static inline int 218 +size(DynamicArray const * s) { 219 + ENSURE(s != NULL, return 0); 220 + ENSURE(s->itemSize != 0, return 0); 221 + return s->index / s->itemSize; 222 +} 223 + 224 +static inline void 225 +pop(DynamicArray* s) { 226 + ENSURE((s != NULL), return); 227 + ENSURE(s->index % s->itemSize == 0 && "(index not aligned)", 228 + s->index += s->itemSize - (s->index % s->itemSize)); 229 + ENSURE(isInitialized(s, s->itemSize), return); 230 + s->index -= s->itemSize; 231 +} 232 + 233 +static inline bool 234 +checkSetNext(DynamicArray *s, char const *c, uint32_t amount) { 235 + ENSURE(s != NULL && c != NULL, return false); 236 + if (s->allocated < s->index + s->itemSize * amount) { 237 + uint32_t const diff = s->index+s->itemSize*amount-s->allocated; 238 + uint32_t const newAlloSize = s->allocated + (diff > EXPAND_STEP 239 + ? diff : EXPAND_STEP) * s->itemSize; 240 + char* tmp = realloc(s->content, newAlloSize); 241 + if (tmp == NULL) { return false; } 242 + s->allocated = newAlloSize; 243 + s->content = tmp; 244 + assert(s->allocated >= s->index + s->itemSize * amount); 245 + } 246 + if (amount) { snext(s, c, amount); } 247 + return true; 248 +} 249 + 250 +static inline bool 251 +checkSetNextV(DynamicArray *s, char const c) { 252 + return checkSetNext(s, &c, 1); 253 +} 254 + 255 +static inline bool 256 +checkSetNextP(DynamicArray *s, char const *c) { 257 + ENSURE(c != NULL, return false); 258 + return checkSetNext(s, c, strlen(c)); 259 +} 260 + 261 +/// Expand the currently initialized content in \p s and the allocated chunk of 262 +/// memory if required. 263 +static char * 264 +expand(DynamicArray *s) { 265 + ENSURE(s != NULL, return NULL); 266 + if (s->allocated < s->index + s->itemSize) { 267 + uint32_t const diff = s->index + s->itemSize - s->allocated; 268 + uint32_t const newAlloSize = s->allocated + (diff > EXPAND_STEP 269 + ? diff : EXPAND_STEP) * s->itemSize; 270 + char* tmp = realloc(s->content, newAlloSize); 271 + if (tmp == NULL) { return NULL; } 272 + s->allocated = newAlloSize; 273 + s->content = tmp; 274 + assert(s->allocated >= s->index + s->itemSize); 275 + } 276 + s->index+=s->itemSize; 277 + return viewEnd(s, 0); 278 +} 279 + 280 +#define append(s, c) checkSetNext((s), (char const *) (c), (s)->itemSize) 281 +#define appendPartial(s, c, i) checkSetNext((s), (char const *) (c), (i)) 282 + 283 + 284 +#endif // DYNAMIC_ARRAY_H 285 diff -ruN st-default/error.h st1/error.h 286 --- st-default/error.h 1970-01-01 01:00:00.000000000 +0100 287 +++ st1/error.h 2020-06-04 11:04:30.227111509 +0200 288 @@ -0,0 +1,47 @@ 289 +#ifndef ERROR_H 290 +#define ERROR_H 291 + 292 +#include <assert.h> 293 + 294 +// Flag which determines whether to fail if a required condition is not met, or 295 +// to adapt the condition in order to work properly. 296 +// Attention: Be sure to perform a clean build after you alter preprocessor 297 +// directives / definitions. 298 +//#define FAIL_ON_ERROR 299 + 300 +#include <stdio.h> 301 + 302 +/// 303 +/// Function used in case the fail-on-error mode is disabled (via definition) 304 +/// to report errors. In debug production mode, alias st to st 2> error.log. 305 +static void reportError(char const * cond, char const * stt, char const * file, 306 + unsigned int line ) { 307 + unsigned int const maxErrorCount = 100; 308 + static unsigned int errorCount = 0; 309 + if (++errorCount == 1) { 310 + printf("Report the following bug to " 311 + "https://github.com/juliusHuelsmann/st.\n"); 312 + } 313 + if (errorCount < maxErrorCount) { 314 + printf("Bug:\tCondition '%s' evaluates to false.\n\tPerforming" 315 + " '%s' to counteract.\n\tFile:%s:%u\n", 316 + cond, stt, file, line); 317 + } else if (errorCount == maxErrorCount) { 318 + printf("Max amount of reported errors %u is reached. From here" 319 + "on, no additional errors will be reported.\n", 320 + maxErrorCount); 321 + } 322 +} 323 + 324 +/// Note that everyting condition checked / endforced with #ENSURE is 325 +/// considered an error, and behaves like an error depending on the flag. 326 +#ifdef FAIL_ON_ERROR 327 +#define ENSURE(cond, stt) assert(cond); 328 +#else // FAIL_ON_ERROR 329 +#define ENSURE(cond, stt) if (!(cond)) { \ 330 + reportError(#cond, #stt, __FILE__, __LINE__); \ 331 + stt; \ 332 + } 333 +#endif // FAIL_ON_ERROR 334 + 335 +#endif // ERROR_H 336 diff -ruN st-default/glyph.h st1/glyph.h 337 --- st-default/glyph.h 1970-01-01 01:00:00.000000000 +0100 338 +++ st1/glyph.h 2020-06-04 11:04:30.228111510 +0200 339 @@ -0,0 +1,30 @@ 340 +#ifndef LINE_H 341 +#define LINE_H 342 + 343 +// 344 +// Contains the representation of the entities in the buffer (Line, Gylph), that 345 +// is used by every part of the software implmeneting terminal logic. 346 +// 347 + 348 +#include <stdint.h> 349 + 350 +enum selection_type { 351 + SEL_REGULAR = 1, 352 + SEL_RECTANGULAR = 2 353 +}; 354 + 355 +typedef uint_least32_t Rune; 356 + 357 +#define Glyph Glyph_ 358 + 359 +typedef struct { 360 + Rune u; /* character code */ 361 + unsigned short mode; /* attribute flags */ 362 + uint32_t fg; /* foreground */ 363 + uint32_t bg; /* background */ 364 +} Glyph; 365 + 366 + 367 +typedef Glyph *Line; 368 + 369 +#endif // LINE_H 370 diff -ruN st-default/Makefile st1/Makefile 371 --- st-default/Makefile 2020-06-04 11:15:55.164135902 +0200 372 +++ st1/Makefile 2020-06-04 11:04:30.228111510 +0200 373 @@ -4,7 +4,7 @@ 374 375 include config.mk 376 377 -SRC = st.c x.c 378 +SRC = st.c x.c normalMode.c 379 OBJ = $(SRC:.c=.o) 380 381 all: options st 382 @@ -21,8 +21,8 @@ 383 .c.o: 384 $(CC) $(STCFLAGS) -c $< 385 386 -st.o: config.h st.h win.h 387 -x.o: arg.h config.h st.h win.h 388 +st.o: config.h st.h win.h dynamicArray.h normalMode.h term.h glyph.h error.h 389 +x.o: arg.h config.h st.h win.h dynamicArray.h normalMode.h term.h glyph.h error.h 390 391 $(OBJ): config.h config.mk 392 393 @@ -35,7 +35,8 @@ 394 dist: clean 395 mkdir -p st-$(VERSION) 396 cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ 397 - config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ 398 + config.def.h st.info st.1 arg.h st.h win.h dynamicArray.h\ 399 + normalMode.h term.h error.h $(SRC)\ 400 st-$(VERSION) 401 tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz 402 rm -rf st-$(VERSION) 403 diff -ruN st-default/normalMode.c st1/normalMode.c 404 --- st-default/normalMode.c 1970-01-01 01:00:00.000000000 +0100 405 +++ st1/normalMode.c 2020-06-04 11:04:30.229111510 +0200 406 @@ -0,0 +1,752 @@ 407 +/* See LICENSE for license details. */ 408 +#include "normalMode.h" 409 +#include "dynamicArray.h" 410 +#include "term.h" 411 +#include "win.h" 412 +#include "error.h" 413 + 414 +#include <X11/keysym.h> 415 +#include <X11/XKBlib.h> 416 + 417 +#include <ctype.h> 418 +#include <stdio.h> 419 +#include <limits.h> 420 +#include <math.h> 421 + 422 +#define LEN(a) (sizeof(a) / sizeof(a)[0]) 423 +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) 424 +//#define FALLTHROUGH __attribute__((fallthrough)); 425 +#define FALLTHROUGH 426 +#define SEC(var,ini,h,r) var = ini; if (!var) { h; return r; } 427 +#define EXPAND(v1,v2,r) char *SEC(v1, expand(v2), empty(v2), true) 428 +#define currentCommand (toggle ? &commandHist0 : &commandHist1) 429 +#define lastCommand (toggle ? &commandHist1 : &commandHist0) 430 + 431 +// 432 +// Interface to the terminal 433 +extern Glyph const styleCommand, styleSearch; 434 +extern NormalModeShortcuts normalModeShortcuts[]; 435 +extern size_t const amountNormalModeShortcuts; 436 +extern char wordDelimSmall[]; 437 +extern char wordDelimLarge[]; 438 +extern unsigned int fgCommandYank, fgCommandVisual, fgCommandVisualLine, 439 + bgCommandYank, bgCommandVisual, bgCommandVisualLine, bgPos, fgPos; 440 + 441 +extern void selclear(void); 442 +extern void tsetdirt(int, int); 443 +extern size_t utf8encode(Rune, char *); 444 +extern size_t utf8decode(const char *, Rune *, size_t); 445 +extern size_t utf8decodebyte(char c, size_t *i); 446 + 447 +extern void selextend(int, int, int, int, int); 448 +extern void selstart(int, int, int, int); 449 +extern char *getsel(void); 450 +extern void tfulldirt(void); 451 + 452 +// 453 +// `Private` structs 454 +typedef struct { uint32_t x; uint32_t y; uint32_t yScr; } Position; 455 + 456 +/// Entire normal mode state, consisting of an operation and a motion. 457 +typedef struct { 458 + Position initialPosition; 459 + struct OperationState { 460 + enum Operation { 461 + noop = ' ', visual='v', visualLine='V', yank = 'y' } op; 462 + Position startPosition; 463 + enum Infix { infix_none = 0, infix_i = 1, infix_a = 2, } infix; 464 + } command; 465 + struct MotionState { 466 + uint32_t amount; 467 + enum Search {none, forward, backward} search; 468 + Position searchPosition; 469 + bool finished; 470 + } motion; 471 +} NormalModeState; 472 + 473 +/// Default state if no operation is performed. 474 +NormalModeState defaultNormalMode = { 475 + {0,0,0}, {noop, {0, 0, 0}, false}, {0, none, {0, 0, 0}, true} 476 +}; 477 +NormalModeState stateVB = { 478 + {0,0,0}, {noop, {0, 0, 0}, false}, {0, none, {0, 0, 0}, true} 479 +}; 480 + 481 +DynamicArray searchString = UTF8_ARRAY; 482 +DynamicArray commandHist0 = UTF8_ARRAY; 483 +DynamicArray commandHist1 = UTF8_ARRAY; 484 +DynamicArray highlights = DWORD_ARRAY; 485 + 486 +/// History command toggle 487 +static bool toggle = false; 488 + 489 +// 490 +// Utility functions 491 +static inline int intervalDiff(int v, int a, int b) { 492 + return (v < a) ? (v - a) : ((v > b) ? (v - b) : 0); 493 +} 494 +static inline void swap(DynamicArray *const a, DynamicArray *const b) { 495 + DynamicArray tmp = *a; *a = *b; *b = tmp; 496 +} 497 +static inline int max(int a, int b) { return a > b ? a : b; } 498 +static inline int min(int a, int b) { return a < b ? a : b; } 499 +static inline int mod(int a, int b) { for (; a < 0; a += b); return a % b; } 500 +static inline bool contains (char c, char const * values, uint32_t memSize) { 501 + ENSURE(values != NULL, return false); 502 + for (uint32_t i = 0; i < memSize; ++i) if (c == values[i]) return true; 503 + return false; 504 +} 505 +static inline void applyPosition(Position const *pos) { 506 + ENSURE(pos != NULL, return); 507 + term.c.x = pos->x; 508 + term.c.y = pos->y; 509 + term.scr = pos->yScr; 510 +} 511 +static inline int getSearchDirection(void) { 512 + return stateVB.motion.search == forward ? 1 : -1; 513 +} 514 + 515 +// Utilities for working with the current version of the scrollback patch. 516 +static bool moveLine(int32_t const amount) { 517 + int32_t const reqShift = intervalDiff(term.c.y+=amount, 0, term.row-1); 518 + term.c.y -= reqShift; 519 + int32_t const sDiff = intervalDiff(term.scr-=reqShift, 0, HISTSIZE-1); 520 + term.scr -= sDiff; 521 + return sDiff == 0; 522 +} 523 + 524 +static void moveLetter(int32_t const amount) { 525 + int32_t value = (term.c.x += amount) / term.col; 526 + if (value -= (term.c.x < 0)) { 527 + term.c.x = moveLine(value) ? mod(term.c.x, term.col) 528 + : max(min(term.c.x,term.col - 1), 0); 529 + } 530 + assert(BETWEEN(term.c.x,0,term.col-1)&&BETWEEN(term.c.y,0,term.row-1)); 531 +} 532 + 533 +// 534 +// `Private` functions: 535 + 536 +// Functions: Temporarily display string on screen. 537 + 538 +/// Display string at end of a specified line without writing it into the buffer 539 +/// @param str string that is to be displayed 540 +/// @param g glyph 541 +/// @param yPos 542 +static void 543 +displayString(DynamicArray const *str, Glyph const *g, int yPos, bool prePos) { 544 + ENSURE((str != NULL) && (g != NULL) && (term.row > 0), return); 545 + ENSURE(yPos >= 0, yPos = 0); 546 + ENSURE(yPos < term.row, yPos = term.row - 1); 547 + // Arbritary limit to avoid withhelding too much info from user. 548 + int const maxFractionOverridden = 3; 549 + // Threshold: if there is no space to print, do not print, but transfer 550 + // repsonsibility for printing back to [st]. 551 + if (term.col < maxFractionOverridden) { // (0) 552 + term.dirty[yPos] = 1; 553 + return; 554 + } 555 + int32_t const botSz = prePos * 6; //< sz for position indication 556 + // Determine the dimensions of used chunk of screen. 557 + int32_t const overrideSize = min(size(str) + botSz, 558 + term.col / maxFractionOverridden); // (1) 559 + int32_t const overrideEnd = term.col - 2; 560 + // Has to follow trivially hence th assert: 561 + // overrideSize <(1)= term.col/3 <(0)= term.col = overrideEnd + 1. 562 + assert(overrideSize <= overrideEnd + 1); 563 + int32_t const overrideStart = 1 + overrideEnd - overrideSize; 564 + // display history[history.size() - (overrideSize - botSz)::-1] 565 + Glyph *SEC(line, malloc(sizeof(Glyph) * (overrideSize)),,) 566 + int32_t offset = (size(str) - overrideSize - 1 + botSz) * str->itemSize; 567 + for (uint32_t chr = 0; chr < overrideSize - botSz; ++chr) { 568 + line[chr] = *g; 569 + line[chr].u = *((Rune*) (str->content+(offset+=str->itemSize))); 570 + } 571 + if (prePos) { 572 + ENSURE(term.scr < HISTSIZE, term.scr = HISTSIZE - 1); 573 + int const p=(int)(0.5+(HISTSIZE-1-term.scr)*100./(HISTSIZE-1)); 574 + int const v = min(max(p, 0), 100); 575 + char prc [10]; 576 + switch (term.scr) { 577 + case HISTSIZE - 1: strcpy(prc, " [TOP]"); break; 578 + case 0: strcpy(prc, " [BOT]"); break; 579 + default: sprintf(prc, " % 3d%c ", v, '%'); 580 + } 581 + for (uint32_t chr = 0; chr < botSz; ++chr) { 582 + line[chr + overrideSize - botSz] =*g; 583 + line[chr + overrideSize - botSz].fg = fgPos; 584 + line[chr + overrideSize - botSz].bg = bgPos; 585 + utf8decode(&prc[chr],&line[chr+overrideSize-botSz].u,1); 586 + } 587 + line[overrideSize - botSz] =*g; 588 + } 589 + xdrawline(TLINE(yPos), 0, yPos, overrideStart); 590 + term.c.y -= term.row; term.c.x -= term.col; // not highlight hack 591 + xdrawline(line-overrideStart, overrideStart, yPos, overrideEnd + 1); 592 + term.c.y += term.row; term.c.x += term.col; 593 + free(line); 594 +} 595 + 596 +static inline void printCommandString(void) { 597 + Glyph g = styleCommand; 598 + switch(stateVB.command.op) { 599 + case yank: g.fg = fgCommandYank; g.bg = bgCommandYank; break; 600 + case visual: g.fg=fgCommandVisual; g.bg=bgCommandVisual; break; 601 + case visualLine: g.fg=fgCommandVisualLine; 602 + g.bg=bgCommandVisualLine; 603 + } 604 + displayString(isEmpty(currentCommand) ? lastCommand : currentCommand, 605 + &g, term.row - 1, true); 606 +} 607 + 608 +static inline void printSearchString(void) { 609 + displayString(&searchString, &styleSearch, term.row - 2, false); 610 +} 611 + 612 +// NormalMode Operation / Motion utilies. 613 + 614 +static inline bool isMotionFinished(void) { return stateVB.motion.finished; } 615 + 616 +static inline void finishMotion(void) { stateVB.motion.finished = true; } 617 + 618 +static inline bool isOperationFinished(void) { 619 + return stateVB.command.op==noop && stateVB.command.infix==infix_none; 620 +} 621 + 622 +/// Register that the current comamnd is finished and a new command is lgoged 623 +static inline void startNewCommand(bool abort) { 624 + if (!abort) { toggle = !toggle; } 625 + empty(currentCommand); 626 +} 627 + 628 +static inline void finishOperation(void) { 629 + stateVB.command = defaultNormalMode.command; 630 + assert(isOperationFinished()); 631 + // After an operation is finished, the selection has to be released and 632 + // no highlights are to be released. 633 + selclear(); 634 + empty(&highlights); 635 + // THe command string is reset for a new command. 636 + startNewCommand(true); 637 +} 638 + 639 +static inline void enableOperation(enum Operation o) { 640 + finishOperation(); 641 + stateVB.command.op = o; 642 + stateVB.command.infix = infix_none; 643 + stateVB.command.startPosition.x = term.c.x; 644 + stateVB.command.startPosition.y = term.c.y; 645 + stateVB.command.startPosition.yScr = term.scr; 646 +} 647 + 648 +/// @param abort: If enabled, the command exits without registering 649 +/// @return Whether the the application is ready to yield control back to 650 +//the normal command flow 651 +static bool terminateCommand(bool abort) { 652 + bool const exitOperation = isMotionFinished(); 653 + bool exitNormalMode = false; 654 + finishMotion(); 655 + 656 + if (exitOperation) { 657 + exitNormalMode = isOperationFinished(); 658 + finishOperation(); 659 + } 660 + printCommandString(); 661 + printSearchString(); 662 + return exitNormalMode; 663 +} 664 + 665 +static inline void exitCommand(void) { terminateCommand(false); } 666 + 667 +static inline void abortCommand(void) { terminateCommand(true); } 668 + 669 +/// Go to next occurrence of string relative to the current location 670 +/// conduct search, starting at start pos 671 +static bool gotoString(int8_t sign) { 672 + moveLetter(sign); 673 + uint32_t const searchStrSize = size(&searchString); 674 + uint32_t const maxIter = (HISTSIZE+term.row) * term.col + searchStrSize; 675 + uint32_t findIdx = 0; 676 + for (uint32_t cIteration = 0; findIdx < searchStrSize 677 + && ++cIteration <= maxIter; moveLetter(sign)) { 678 + char const * const SEC(next, sign==1 679 + ? view(&searchString, findIdx) 680 + : viewEnd(&searchString, findIdx), , false) 681 + uint32_t const searchChar = *((uint32_t*) next); 682 + 683 + if (TLINE(term.c.y)[term.c.x].u == searchChar) { ++findIdx; } 684 + else { findIdx = 0; } 685 + } 686 + bool const found = findIdx == searchStrSize; 687 + for (uint32_t i = 0; found && i < searchStrSize; ++i) moveLetter(-sign); 688 + return found; 689 +} 690 + 691 +/// Highlight all found strings on the current screen. 692 +static void highlightStringOnScreen(void) { 693 + if (isEmpty(&searchString)) { return; } 694 + empty(&highlights); 695 + uint32_t const searchStringSize = size(&searchString); 696 + uint32_t findIdx = 0; 697 + uint32_t xStart, yStart; 698 + bool success = true; 699 + for (int y = 0; y < term.row && success; y++) { 700 + for (int x = 0; x < term.col && success; x++) { 701 + char const* const SEC(next, 702 + view(&searchString,findIdx),,) 703 + if (TLINE(y)[x].u == (Rune) *((uint32_t*)(next))) { 704 + if (++findIdx == 1) { 705 + xStart = x; 706 + yStart = y; 707 + } 708 + if (findIdx == searchStringSize) { 709 + success = success 710 + && append(&highlights, &xStart) 711 + && append(&highlights, &yStart); 712 + findIdx = 0; //term.dirty[yStart] = 1; 713 + } 714 + } else { findIdx = 0; } 715 + } 716 + } 717 + if (!success) { empty(&highlights); } 718 +} 719 + 720 +static bool gotoStringAndHighlight(int8_t sign) { 721 + // Find hte next occurrence of the #searchString in direction #sign 722 + bool const found = gotoString(sign); 723 + if (!found) { applyPosition(&stateVB.motion.searchPosition); } 724 + highlightStringOnScreen(); 725 + //tsetdirt(0, term.row-3); //< everything except for the 'status bar' 726 + return found; 727 +} 728 + 729 +static bool pressKeys(char const* nullTerminatedString, size_t end) { 730 + bool sc = true; 731 + for (size_t i = 0; i < end && sc; ++i) { 732 + sc = kpressNormalMode(&nullTerminatedString[i], 1, false, NULL); 733 + } 734 + return sc; 735 +} 736 + 737 +static bool executeCommand(DynamicArray const *command) { 738 + size_t end=size(command); 739 + char decoded [32]; 740 + bool succ = true; 741 + size_t len; 742 + for (size_t i = 0; i < end && succ; ++i) { 743 + char const *const SEC(nextRune, view(command, i),,false) 744 + len = utf8encode(*((Rune *) nextRune), decoded); 745 + succ = kpressNormalMode(decoded, len, false, NULL); 746 + } 747 + return succ; 748 +} 749 + 750 +struct { char const first; char const second; } const Brackets [] = 751 +{ {'(', ')'}, {'<', '>'}, {'{', '}'}, {'[', ']'}, }; 752 + 753 + 754 +/// Emits Command prefix and suffix when i motion is performed (e.g. yiw). 755 +/// 756 +/// @param c: motion character 757 +/// @param expandMode: 1 for 'i', 2 for 'a' 758 +/// @param first, second: Dynamic arrays in which the prefix and postfix 759 +/// commands will be returned 760 +/// @return whether the command could be extracted successfully. 761 +static bool expandExpression(char const c, enum Infix expandMode, 762 + char operation, DynamicArray *cmd) { 763 + empty(cmd); 764 + bool s = true; //< used in order to detect memory allocation errors. 765 + char const lower = tolower(c); 766 + // Motions 767 + if (lower == 'w') { 768 + // translated into wb[command]e resp. WB[command]E, which works 769 + // file even when at the fist letter. Does not work for single 770 + // letter words though. 771 + int const diff = c - lower; 772 + s = s && checkSetNextV(cmd, c); 773 + s = s && checkSetNextV(cmd, (signed char)(((int)'b') + diff)); 774 + s = s && checkSetNextV(cmd, operation); 775 + s = s && checkSetNextV(cmd, (signed char)(((int)'e')+ diff)); 776 + return s; 777 + } 778 + // Symmetrical brackets (quotation marks) 779 + if (c == '\'' || c == '"') { 780 + // Local ambiguity -> do nothing. It cannot be determined if 781 + // the current char is the 1st or last char of the selection. 782 + // <---- search here? -- ['] -- or search here? ---> 783 + if (TLINE(term.c.y)[term.c.x].u == c) { 784 + return false; 785 + } 786 + // Prefix 787 + char res [] = {'?', c, '\n'}; 788 + s = s && checkSetNextP(cmd, res); 789 + // infix 790 + bool const iffy = expandMode == infix_i; 791 + if (iffy) { s = s && checkSetNextV(cmd, 'l'); } 792 + s = s && checkSetNextV(cmd, operation); 793 + if (!iffy) { s = s && checkSetNextV(cmd, 'l'); } 794 + // suffix 795 + res[0] = '/'; 796 + s = s && checkSetNextP(cmd, res); 797 + if (iffy) { s = s && checkSetNextV(cmd, 'h'); } 798 + return s; 799 + } 800 + // Brackets: Does not if in range / if the brackets belong togehter. 801 + for (size_t pid = 0; pid < sizeof(Brackets); ++pid) { 802 + if(Brackets[pid].first == c || Brackets[pid].second == c) { 803 + if (TLINE(term.c.y)[term.c.x].u!=Brackets[pid].first) { 804 + s = s && checkSetNextV(cmd, '?'); 805 + s = s && checkSetNextV(cmd, Brackets[pid].first); 806 + s = s && checkSetNextV(cmd, '\n'); 807 + } 808 + bool const iffy = expandMode == infix_i; 809 + if (iffy) { s = s && checkSetNextV(cmd, 'l'); } 810 + s = s && checkSetNextV(cmd, operation); 811 + if (!iffy) { s = s && checkSetNextV(cmd, 'l'); } 812 + s = s && checkSetNextV(cmd, '/'); 813 + s = s && checkSetNextV(cmd, Brackets[pid].second); 814 + s = s && checkSetNextV(cmd, '\n'); 815 + if (iffy) { s = s && checkSetNextV(cmd, 'h'); } 816 + return s; 817 + } 818 + } 819 + /**/ 820 + // search string 821 + // complicated search operation: <tag> 822 + if (c == 't') { 823 + // XXX: (Bug in vim: @vit ) 824 + // <tag_name attr="hier" a2="\<sch\>"> [current pos] </tag_name> 825 + 826 + // 1. Copy history ( tag := hist[?<\n:/ \n] ) 827 + // 2. Copy history ( first_find := hist[?<\n: next place in 828 + // history where count '>' > count '<' 829 + // (can be behind current pos) ) 830 + // 3. first := [?first_find][#first_ind]l 831 + // second:= [/tag">"]h 832 + //return true; // XXX: not implmented yet. 833 + } 834 + return false; 835 +} 836 + 837 +// 838 +// Public API 839 +// 840 + 841 +void onMove(void) { 842 + stateVB.initialPosition.x = term.c.x; 843 + stateVB.initialPosition.y = term.c.y; 844 + stateVB.initialPosition.yScr = term.scr; 845 +} 846 + 847 +int highlighted(int x, int y) { 848 + // Compute the legal bounds for a hit: 849 + int32_t const stringSize = size(&searchString); 850 + int32_t xMin = x - stringSize; 851 + int32_t yMin = y; 852 + while (xMin < 0 && yMin > 0) { 853 + xMin += term.col; 854 + --yMin; 855 + } 856 + if (xMin < 0) { xMin = 0; } 857 + 858 + uint32_t highSize = size(&highlights); 859 + ENSURE(highSize % 2 == 0, empty(&highlights); return false;); 860 + highSize /= 2; 861 + uint32_t *ptr = (uint32_t*) highlights.content; 862 + for (uint32_t i = 0; i < highSize; ++i) { 863 + int32_t const sx = (int32_t) *(ptr++); 864 + int32_t const sy = (int32_t) *(ptr++); 865 + if (BETWEEN(sy, yMin, y) && (sy != yMin || sx > xMin) 866 + && (sy != y || sx <= x)) { 867 + return true; 868 + } 869 + } 870 + return false; 871 +} 872 + 873 +ExitState kpressNormalMode(char const * cs, int len, bool ctrl, void const *v) { 874 + KeySym const * const ksym = (KeySym*) v; 875 + bool const esc = ksym && *ksym == XK_Escape; 876 + bool const enter = (ksym && *ksym==XK_Return) || (len==1 &&cs[0]=='\n'); 877 + bool const quantifier = len == 1 && (BETWEEN(cs[0], 49, 57) 878 + || (cs[0] == 48 && stateVB.motion.amount)); 879 + int const previousScroll = term.scr; 880 + // [ESC] or [ENTER] abort resp. finish the current level of operation. 881 + // Typing 'i' if no operation is currently performed behaves like ESC. 882 + if (esc || enter || (len == 1 && cs[0] == 'i' && isMotionFinished() 883 + && isOperationFinished())) { 884 + if (terminateCommand(!enter)) { 885 + applyPosition(&stateVB.initialPosition); 886 + Position const pc = stateVB.initialPosition; 887 + stateVB = defaultNormalMode; 888 + stateVB.initialPosition = pc; 889 + tfulldirt(); 890 + return finished; 891 + } 892 + len = 0; 893 + goto motionFinish; 894 + } 895 + // Backspace 896 + if (ksym && *ksym == XK_BackSpace) { 897 + bool s = stateVB.motion.search!=none&&!stateVB.motion.finished; 898 + bool q = stateVB.motion.amount != 0; 899 + if (!(s || q)) { return failed; } 900 + len = 0; 901 + 902 + if (!isEmpty(currentCommand)) { pop(currentCommand); } 903 + if (s) { 904 + if (!isEmpty(&searchString)) { pop(&searchString); } 905 + else if (isEmpty(&searchString)) { 906 + exitCommand(); 907 + return success; 908 + } 909 + } else if (q) { 910 + stateVB.motion.amount /= 10; 911 + goto finishNoAppend; 912 + } 913 + } 914 + 915 + // Search: append to search string, then search & highlight 916 + if (stateVB.motion.search != none && !stateVB.motion.finished) { 917 + if (len >= 1) { 918 + EXPAND(kSearch, &searchString, true) 919 + utf8decode(cs, (Rune*)(kSearch), len); 920 + } 921 + applyPosition(&stateVB.motion.searchPosition); 922 + gotoStringAndHighlight(getSearchDirection()); 923 + goto finish; 924 + } 925 + if (len == 0) { return failed; } 926 + // Quantifiers 927 + if (quantifier) { 928 + stateVB.motion.amount = min(SHRT_MAX, 929 + stateVB.motion.amount * 10 + cs[0] - 48); 930 + goto finish; 931 + } 932 + // 'i' mode enabled, hence the expression is to be expanded: 933 + // [start_expression(cs[0])] [operation] [stop_expression(cs[0])] 934 + if (stateVB.command.infix != infix_none && stateVB.command.op != noop) { 935 + DynamicArray cmd = CHAR_ARRAY; 936 + char const operation = stateVB.command.op; 937 + bool succ = expandExpression(cs[0], 938 + stateVB.command.infix, visual, &cmd); 939 + if (operation == yank) { 940 + succ = succ && checkSetNextV(&cmd, operation); 941 + } 942 + NormalModeState const st = stateVB; 943 + TCursor const tc = term.c; 944 + stateVB.command.infix = infix_none; 945 + if (succ) { 946 + stateVB.command.op = noop; 947 + for (int i = 0; i < size(&cmd) && succ; ++i) { 948 + succ = pressKeys(&cmd.content[i], 1); 949 + } 950 + if (!succ) { // go back to the old position, apply op 951 + stateVB = st; 952 + term.c = tc; 953 + } 954 + empty(currentCommand); 955 + for (uint32_t i = 0; i < size(&cmd); ++i) { 956 + EXPAND(kCommand, currentCommand, true) 957 + utf8decode(cmd.content+i, (Rune*)(kCommand),1); 958 + } 959 + } 960 + free(cmd.content); 961 + goto finishNoAppend; 962 + } 963 + // Commands (V / v or y) 964 + switch(cs[0]) { 965 + case '.': 966 + { 967 + if (isEmpty(currentCommand)) { toggle = !toggle; } 968 + DynamicArray cmd = UTF8_ARRAY; 969 + swap(&cmd, currentCommand); 970 + executeCommand(&cmd) ? success : failed; 971 + swap(&cmd, currentCommand); 972 + free(cmd.content); 973 + goto finishNoAppend; 974 + } 975 + case 'i': stateVB.command.infix = infix_i; goto finish; 976 + case 'a': stateVB.command.infix = infix_a; goto finish; 977 + case 'y': 978 + switch(stateVB.command.op) { 979 + case noop: //< Start yank mode & set #op 980 + enableOperation(yank); 981 + selstart(term.c.x, term.c.y,term.scr,0); 982 + goto finish; 983 + case yank: //< Complete yank [y#amount j] 984 + selstart(0, term.c.y, term.scr, 0); 985 + int const origY = term.c.y; 986 + moveLine(max(stateVB.motion.amount, 1)); 987 + selextend(term.col-1,term.c.y,term.scr, 988 + SEL_RECTANGULAR, 0); 989 + term.c.y = origY; 990 + FALLTHROUGH 991 + case visualLine: // Yank visual selection 992 + case visual: 993 + xsetsel(getsel()); 994 + xclipcopy(); 995 + exitCommand(); 996 + goto finish; 997 + default: 998 + return failed; 999 + } 1000 + case visual: 1001 + case visualLine: 1002 + if (stateVB.command.op == cs[0]) { 1003 + finishOperation(); 1004 + return true; 1005 + } else { 1006 + enableOperation(cs[0]); 1007 + selstart(cs[0] == visualLine ? 0 : term.c.x, 1008 + term.c.y, term.scr, 0); 1009 + goto finish; 1010 + } 1011 + } 1012 + // CTRL Motions 1013 + int32_t sign = -1; //< if command goes 'forward'(1) or 'backward'(-1) 1014 + if (ctrl) { 1015 + if (ksym == NULL) { return false; } 1016 + switch(*ksym) { 1017 + case XK_f: 1018 + term.scr = max(term.scr - max(term.row-2,1), 0); 1019 + term.c.y = 0; 1020 + goto finish; 1021 + case XK_b: 1022 + term.scr = min(term.scr + max(term.row - 2, 1), 1023 + HISTSIZE - 1); 1024 + term.c.y = term.bot; 1025 + goto finish; 1026 + case XK_u: 1027 + term.scr = min(term.scr+term.row/2, HISTSIZE-1); 1028 + goto finish; 1029 + case XK_d: 1030 + term.scr = max(term.scr - term.row / 2, 0); 1031 + goto finish; 1032 + default: return false; 1033 + } 1034 + } 1035 + // Motions 1036 + switch(cs[0]) { 1037 + case 'c': empty(&commandHist0); empty(&commandHist1); 1038 + goto finishNoAppend; 1039 + case 'j': sign = 1; FALLTHROUGH 1040 + case 'k': moveLine(max(stateVB.motion.amount,1) * sign); 1041 + goto motionFinish; 1042 + case 'H': term.c.y = 0; 1043 + goto motionFinish; 1044 + case 'M': term.c.y = term.bot / 2; 1045 + goto motionFinish; 1046 + case 'L': term.c.y = term.bot; 1047 + goto motionFinish; 1048 + case 'G': applyPosition(&stateVB.initialPosition); 1049 + goto motionFinish; 1050 + case 'l': sign = 1; FALLTHROUGH 1051 + case 'h': moveLetter(sign * max(stateVB.motion.amount,1)); 1052 + goto motionFinish; 1053 + case '0': term.c.x = 0; 1054 + goto motionFinish; 1055 + case '$': term.c.x = term.col-1; 1056 + goto motionFinish; 1057 + case 'w': FALLTHROUGH 1058 + case 'W': FALLTHROUGH 1059 + case 'e': FALLTHROUGH 1060 + case 'E': sign = 1; FALLTHROUGH 1061 + case 'B': FALLTHROUGH 1062 + case 'b': { 1063 + char const * const wDelim = 1064 + cs[0] <= 90 ? wordDelimLarge : wordDelimSmall; 1065 + uint32_t const wDelimLen = strlen(wDelim); 1066 + 1067 + bool const startSpaceIsSeparator = 1068 + !(cs[0] == 'w' || cs[0] == 'W'); 1069 + // Whether to start & end with offset: 1070 + bool const performOffset = startSpaceIsSeparator; 1071 + // Max iteration := One complete hist traversal. 1072 + uint32_t const maxIter = (HISTSIZE+term.row) * term.col; 1073 + // Doesn't work exactly as in vim: Linebreak is 1074 + // counted as 'normal' separator, hence a jump can 1075 + // span multiple lines here. 1076 + stateVB.motion.amount = max(stateVB.motion.amount, 1); 1077 + for (;stateVB.motion.amount>0;--stateVB.motion.amount) { 1078 + uint8_t state = 0; 1079 + if (performOffset) { moveLetter(sign); } 1080 + for (uint32_t cIt = 0; cIt ++ < maxIter; moveLetter(sign)) { 1081 + if (startSpaceIsSeparator == contains(TLINE(term.c.y)[term.c.x].u, wDelim, wDelimLen)) { 1082 + if (state == 1) { 1083 + if (performOffset) { 1084 + moveLetter(-sign); 1085 + } 1086 + break; 1087 + } 1088 + } else if (state == 0) { state = 1; } 1089 + } 1090 + } 1091 + goto motionFinish; 1092 + } 1093 + case '/': sign = 1; FALLTHROUGH 1094 + case '?': 1095 + empty(&searchString); 1096 + stateVB.motion.search = sign == 1 ? forward : backward; 1097 + stateVB.motion.searchPosition.x = term.c.x; 1098 + stateVB.motion.searchPosition.y = term.c.y; 1099 + stateVB.motion.searchPosition.yScr = term.scr; 1100 + stateVB.motion.finished = false; 1101 + goto finish; 1102 + case 'n': sign = 1; FALLTHROUGH 1103 + case 'N': { 1104 + if (stateVB.motion.search == none) return failed; 1105 + if (stateVB.motion.search == backward) { sign *= -1; } 1106 + bool b = true; int ox = term.c.x; 1107 + int oy = term.c.y ; int scr = term.scr; 1108 + int32_t i = max(stateVB.motion.amount, 1); 1109 + for (;i>0 && (b=gotoString(sign)); --i) { 1110 + oy = term.c.y; scr = term.scr; 1111 + } 1112 + if (!b) { term.c.x = ox; term.c.y = oy; term.scr = scr;} 1113 + goto motionFinish; 1114 + } 1115 + case 't': // Toggle selection mode and set dirt. 1116 + sel.type = sel.type == SEL_REGULAR 1117 + ? SEL_RECTANGULAR : SEL_REGULAR; 1118 + //tsetdirt(sel.nb.y, sel.ne.y); 1119 + goto motionFinish; 1120 + } 1121 + // Custom commands 1122 + for (size_t i = 0; i < amountNormalModeShortcuts; ++i) { 1123 + if (cs[0] == normalModeShortcuts[i].key) { 1124 + return pressKeys(normalModeShortcuts[i].value, 1125 + strlen(normalModeShortcuts[i].value)) 1126 + ? success : failed; 1127 + } 1128 + } 1129 + return failed; 1130 +motionFinish: 1131 + stateVB.motion.amount = 0; 1132 + //if (isMotionFinished() && stateVB.command.op == yank) { 1133 + if (stateVB.command.op == yank) { 1134 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); 1135 + xsetsel(getsel()); 1136 + xclipcopy(); 1137 + exitCommand(); 1138 + } 1139 +finish: 1140 + if (len == 1 && !ctrl) { // XXX: for now. 1141 + EXPAND(kCommand, currentCommand, true) 1142 + utf8decode(cs, (Rune*)(kCommand), len); 1143 + } 1144 +finishNoAppend: 1145 + if (stateVB.command.op == visual) { 1146 + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); 1147 + } else if (stateVB.command.op == visualLine) { 1148 + selextend(term.col-1, term.c.y, term.scr, sel.type, 0); 1149 + } 1150 + 1151 + if (previousScroll != term.scr && !isEmpty(&searchString)) { 1152 + highlightStringOnScreen(); 1153 + } 1154 + tsetdirt(0, term.row-3); //< Required because of the cursor cross. 1155 + printCommandString(); 1156 + printSearchString(); 1157 + return success; 1158 +} 1159 diff -ruN st-default/normalMode.h st1/normalMode.h 1160 --- st-default/normalMode.h 1970-01-01 01:00:00.000000000 +0100 1161 +++ st1/normalMode.h 2020-06-04 11:04:30.229111510 +0200 1162 @@ -0,0 +1,36 @@ 1163 +/* See LICENSE for license details. */ 1164 +#ifndef NORMAL_MODE_H 1165 +#define NORMAL_MODE_H 1166 + 1167 +#include <stdbool.h> 1168 +#include <stddef.h> 1169 +#include <stdint.h> 1170 + 1171 +/// Used in the configuration file to define custom shortcuts. 1172 +typedef struct NormalModeShortcuts { 1173 + char key; 1174 + char *value; 1175 +} NormalModeShortcuts; 1176 + 1177 +/// Holds the exit status of the #kpressNormalMode function, which informs the 1178 +/// caller when to exit normal mode. 1179 +typedef enum ExitState { 1180 + failed = 0, 1181 + success = 1, 1182 + finished = 2, 1183 +} ExitState; 1184 + 1185 +/// Called when curr position is altered. 1186 +void onMove(void); 1187 + 1188 +/// Function which returns whether the value at position provided as arguments 1189 +/// is to be highlighted. 1190 +int highlighted(int, int); 1191 + 1192 +/// Handles keys in normal mode. 1193 +ExitState kpressNormalMode(char const * decoded, int len, bool ctrlPressed, 1194 + void const * ksym); 1195 + //bool esc, bool enter, bool backspace, void* keysym); 1196 + 1197 + 1198 +#endif // NORMAL_MODE_H 1199 Binary files st-default/normalMode.o and st1/normalMode.o differ 1200 Binary files st-default/st and st1/st differ 1201 diff -ruN st-default/st.c st1/st.c 1202 --- st-default/st.c 2020-06-04 11:15:55.165135902 +0200 1203 +++ st1/st.c 2020-06-04 11:04:30.231111510 +0200 1204 @@ -1,8 +1,10 @@ 1205 /* See LICENSE for license details. */ 1206 +#include <assert.h> 1207 #include <ctype.h> 1208 #include <errno.h> 1209 #include <fcntl.h> 1210 #include <limits.h> 1211 +#include <math.h> 1212 #include <pwd.h> 1213 #include <stdarg.h> 1214 #include <stdio.h> 1215 @@ -17,6 +19,8 @@ 1216 #include <unistd.h> 1217 #include <wchar.h> 1218 1219 + 1220 +#include "term.h" 1221 #include "st.h" 1222 #include "win.h" 1223 1224 @@ -42,6 +46,7 @@ 1225 #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) 1226 #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) 1227 #define ISDELIM(u) (u && wcschr(worddelimiters, u)) 1228 +#define INTERVAL(x, a, b) (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 1229 1230 enum term_mode { 1231 MODE_WRAP = 1 << 0, 1232 @@ -86,17 +91,17 @@ 1233 ESC_DCS =128, 1234 }; 1235 1236 -typedef struct { 1237 - Glyph attr; /* current char attributes */ 1238 - int x; 1239 - int y; 1240 - char state; 1241 -} TCursor; 1242 - 1243 -typedef struct { 1244 - int mode; 1245 - int type; 1246 - int snap; 1247 +/*typedef struct {*/ 1248 + /*Glyph attr; [> current char attributes <]*/ 1249 + /*int x;*/ 1250 + /*int y;*/ 1251 + /*char state;*/ 1252 +/*} TCursor;*/ 1253 + 1254 +/*typedef struct {*/ 1255 + /*int mode;*/ 1256 + /*int type;*/ 1257 + /*int snap;*/ 1258 /* 1259 * Selection variables: 1260 * nb – normalized coordinates of the beginning of the selection 1261 @@ -104,33 +109,33 @@ 1262 * ob – original coordinates of the beginning of the selection 1263 * oe – original coordinates of the end of the selection 1264 */ 1265 - struct { 1266 - int x, y; 1267 - } nb, ne, ob, oe; 1268 - 1269 - int alt; 1270 -} Selection; 1271 - 1272 -/* Internal representation of the screen */ 1273 -typedef struct { 1274 - int row; /* nb row */ 1275 - int col; /* nb col */ 1276 - Line *line; /* screen */ 1277 - Line *alt; /* alternate screen */ 1278 - int *dirty; /* dirtyness of lines */ 1279 - TCursor c; /* cursor */ 1280 - int ocx; /* old cursor col */ 1281 - int ocy; /* old cursor row */ 1282 - int top; /* top scroll limit */ 1283 - int bot; /* bottom scroll limit */ 1284 - int mode; /* terminal mode flags */ 1285 - int esc; /* escape state flags */ 1286 - char trantbl[4]; /* charset table translation */ 1287 - int charset; /* current charset */ 1288 - int icharset; /* selected charset for sequence */ 1289 - int *tabs; 1290 - Rune lastc; /* last printed char outside of sequence, 0 if control */ 1291 -} Term; 1292 + /*struct {*/ 1293 + /*int x, y;*/ 1294 + /*} nb, ne, ob, oe;*/ 1295 + 1296 + /*int alt;*/ 1297 +/*} Selection;*/ 1298 + 1299 +/*[> Internal representation of the screen <]*/ 1300 +/*typedef struct {*/ 1301 + /*int row; [> nb row <]*/ 1302 + /*int col; [> nb col <]*/ 1303 + /*Line *line; [> screen <]*/ 1304 + /*Line *alt; [> alternate screen <]*/ 1305 + /*int *dirty; [> dirtyness of lines <]*/ 1306 + /*TCursor c; [> cursor <]*/ 1307 + /*int ocx; [> old cursor col <]*/ 1308 + /*int ocy; [> old cursor row <]*/ 1309 + /*int top; [> top scroll limit <]*/ 1310 + /*int bot; [> bottom scroll limit <]*/ 1311 + /*int mode; [> terminal mode flags <]*/ 1312 + /*int esc; [> escape state flags <]*/ 1313 + /*char trantbl[4]; [> charset table translation <]*/ 1314 + /*int charset; [> current charset <]*/ 1315 + /*int icharset; [> selected charset for sequence <]*/ 1316 + /*int *tabs;*/ 1317 + /*Rune lastc; [> last printed char outside of sequence, 0 if control <]*/ 1318 +/*} Term;*/ 1319 1320 /* CSI Escape sequence structs */ 1321 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 1322 @@ -154,6 +159,8 @@ 1323 int narg; /* nb of args */ 1324 } STREscape; 1325 1326 +void tfulldirt(void); 1327 + 1328 static void execsh(char *, char **); 1329 static void stty(char **); 1330 static void sigchld(int); 1331 @@ -186,16 +193,14 @@ 1332 static void tputtab(int); 1333 static void tputc(Rune); 1334 static void treset(void); 1335 -static void tscrollup(int, int); 1336 -static void tscrolldown(int, int); 1337 +static void tscrollup(int, int, int); 1338 +static void tscrolldown(int, int, int); 1339 static void tsetattr(int *, int); 1340 static void tsetchar(Rune, Glyph *, int, int); 1341 -static void tsetdirt(int, int); 1342 static void tsetscroll(int, int); 1343 static void tswapscreen(void); 1344 static void tsetmode(int, int, int *, int); 1345 static int twrite(const char *, int, int); 1346 -static void tfulldirt(void); 1347 static void tcontrolcode(uchar ); 1348 static void tdectest(char ); 1349 static void tdefutf8(char); 1350 @@ -209,8 +214,6 @@ 1351 static void selscroll(int, int); 1352 static void selsnap(int *, int *, int); 1353 1354 -static size_t utf8decode(const char *, Rune *, size_t); 1355 -static Rune utf8decodebyte(char, size_t *); 1356 static char utf8encodebyte(Rune, size_t); 1357 static size_t utf8validate(Rune *, size_t); 1358 1359 @@ -220,8 +223,8 @@ 1360 static ssize_t xwrite(int, const char *, size_t); 1361 1362 /* Globals */ 1363 -static Term term; 1364 -static Selection sel; 1365 +Term term; 1366 +Selection sel; 1367 static CSIEscape csiescseq; 1368 static STREscape strescseq; 1369 static int iofd = 1; 1370 @@ -416,17 +419,22 @@ 1371 { 1372 int i = term.col; 1373 1374 - if (term.line[y][i - 1].mode & ATTR_WRAP) 1375 + if (TLINE(y)[i - 1].mode & ATTR_WRAP) 1376 return i; 1377 1378 - while (i > 0 && term.line[y][i - 1].u == ' ') 1379 + while (i > 0 && TLINE(y)[i - 1].u == ' ') 1380 --i; 1381 1382 return i; 1383 } 1384 1385 void 1386 -selstart(int col, int row, int snap) 1387 +xselstart(int col, int row, int snap) { 1388 + selstart(col, row, term.scr, snap); 1389 +} 1390 + 1391 +void 1392 +selstart(int col, int row, int scroll, int snap) 1393 { 1394 selclear(); 1395 sel.mode = SEL_EMPTY; 1396 @@ -435,6 +443,7 @@ 1397 sel.snap = snap; 1398 sel.oe.x = sel.ob.x = col; 1399 sel.oe.y = sel.ob.y = row; 1400 + sel.oe.scroll = sel.ob.scroll = scroll; 1401 selnormalize(); 1402 1403 if (sel.snap != 0) 1404 @@ -443,10 +452,13 @@ 1405 } 1406 1407 void 1408 -selextend(int col, int row, int type, int done) 1409 -{ 1410 - int oldey, oldex, oldsby, oldsey, oldtype; 1411 +xselextend(int col, int row, int type, int done) { 1412 + selextend(col, row, term.scr, type, done); 1413 +} 1414 1415 +void 1416 +selextend(int col, int row, int scroll, int type, int done) 1417 +{ 1418 if (sel.mode == SEL_IDLE) 1419 return; 1420 if (done && sel.mode == SEL_EMPTY) { 1421 @@ -454,18 +466,22 @@ 1422 return; 1423 } 1424 1425 - oldey = sel.oe.y; 1426 - oldex = sel.oe.x; 1427 - oldsby = sel.nb.y; 1428 - oldsey = sel.ne.y; 1429 - oldtype = sel.type; 1430 + int const oldey = sel.oe.y; 1431 + int const oldex = sel.oe.x; 1432 + int const oldscroll = sel.oe.scroll; 1433 + int const oldsby = sel.nb.y; 1434 + int const oldsey = sel.ne.y; 1435 + int const oldtype = sel.type; 1436 1437 sel.oe.x = col; 1438 sel.oe.y = row; 1439 + sel.oe.scroll = scroll; 1440 + 1441 selnormalize(); 1442 sel.type = type; 1443 1444 - if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) 1445 + if (oldey != sel.oe.y || oldex != sel.oe.x || oldscroll != sel.oe.scroll 1446 + || oldtype != sel.type || sel.mode == SEL_EMPTY) 1447 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); 1448 1449 sel.mode = done ? SEL_IDLE : SEL_READY; 1450 @@ -474,17 +490,21 @@ 1451 void 1452 selnormalize(void) 1453 { 1454 - int i; 1455 - 1456 - if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { 1457 - sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; 1458 - sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; 1459 + sel.nb.y = INTERVAL(sel.ob.y + term.scr - sel.ob.scroll, 0, term.bot); 1460 + sel.ne.y = INTERVAL(sel.oe.y + term.scr - sel.oe.scroll, 0, term.bot); 1461 + if (sel.type == SEL_REGULAR && sel.nb.y != sel.ne.y) { 1462 + sel.nb.x = sel.nb.y < sel.ne.y ? sel.ob.x : sel.oe.x; 1463 + sel.ne.x = sel.nb.y < sel.ne.y ? sel.oe.x : sel.ob.x; 1464 } else { 1465 sel.nb.x = MIN(sel.ob.x, sel.oe.x); 1466 sel.ne.x = MAX(sel.ob.x, sel.oe.x); 1467 } 1468 - sel.nb.y = MIN(sel.ob.y, sel.oe.y); 1469 - sel.ne.y = MAX(sel.ob.y, sel.oe.y); 1470 + 1471 + if (sel.nb.y > sel.ne.y) { 1472 + int32_t const tmp = sel.nb.y; 1473 + sel.nb.y = sel.ne.y; 1474 + sel.ne.y = tmp; 1475 + } 1476 1477 selsnap(&sel.nb.x, &sel.nb.y, -1); 1478 selsnap(&sel.ne.x, &sel.ne.y, +1); 1479 @@ -492,7 +512,7 @@ 1480 /* expand selection over line breaks */ 1481 if (sel.type == SEL_RECTANGULAR) 1482 return; 1483 - i = tlinelen(sel.nb.y); 1484 + int i = tlinelen(sel.nb.y); 1485 if (i < sel.nb.x) 1486 sel.nb.x = i; 1487 if (tlinelen(sel.ne.y) <= sel.ne.x) 1488 @@ -528,7 +548,7 @@ 1489 * Snap around if the word wraps around at the end or 1490 * beginning of a line. 1491 */ 1492 - prevgp = &term.line[*y][*x]; 1493 + prevgp = &TLINE(*y)[*x]; 1494 prevdelim = ISDELIM(prevgp->u); 1495 for (;;) { 1496 newx = *x + direction; 1497 @@ -543,14 +563,14 @@ 1498 yt = *y, xt = *x; 1499 else 1500 yt = newy, xt = newx; 1501 - if (!(term.line[yt][xt].mode & ATTR_WRAP)) 1502 + if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) 1503 break; 1504 } 1505 1506 if (newx >= tlinelen(newy)) 1507 break; 1508 1509 - gp = &term.line[newy][newx]; 1510 + gp = &TLINE(newy)[newx]; 1511 delim = ISDELIM(gp->u); 1512 if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim 1513 || (delim && gp->u != prevgp->u))) 1514 @@ -571,14 +591,14 @@ 1515 *x = (direction < 0) ? 0 : term.col - 1; 1516 if (direction < 0) { 1517 for (; *y > 0; *y += direction) { 1518 - if (!(term.line[*y-1][term.col-1].mode 1519 + if (!(TLINE(*y-1)[term.col-1].mode 1520 & ATTR_WRAP)) { 1521 break; 1522 } 1523 } 1524 } else if (direction > 0) { 1525 for (; *y < term.row-1; *y += direction) { 1526 - if (!(term.line[*y][term.col-1].mode 1527 + if (!(TLINE(*y)[term.col-1].mode 1528 & ATTR_WRAP)) { 1529 break; 1530 } 1531 @@ -598,24 +618,32 @@ 1532 if (sel.ob.x == -1) 1533 return NULL; 1534 1535 - bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; 1536 + int32_t syb = sel.ob.y - sel.ob.scroll + term.scr; 1537 + int32_t sye = sel.oe.y - sel.oe.scroll + term.scr; 1538 + if (syb > sye) { 1539 + int32_t tmp = sye; 1540 + sye = syb; 1541 + syb = tmp; 1542 + } 1543 + 1544 + bufsize = (term.col+1) * (sye - syb + 1) * UTF_SIZ; 1545 ptr = str = xmalloc(bufsize); 1546 1547 /* append every set & selected glyph to the selection */ 1548 - for (y = sel.nb.y; y <= sel.ne.y; y++) { 1549 + for (y = syb; y <= sye; y++) { 1550 if ((linelen = tlinelen(y)) == 0) { 1551 *ptr++ = '\n'; 1552 continue; 1553 } 1554 1555 if (sel.type == SEL_RECTANGULAR) { 1556 - gp = &term.line[y][sel.nb.x]; 1557 + gp = &TLINE(y)[sel.nb.x]; 1558 lastx = sel.ne.x; 1559 } else { 1560 - gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; 1561 - lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; 1562 + gp = &TLINE(y)[syb == y ? sel.nb.x : 0]; 1563 + lastx = (sye == y) ? sel.ne.x : term.col-1; 1564 } 1565 - last = &term.line[y][MIN(lastx, linelen-1)]; 1566 + last = &TLINE(y)[MIN(lastx, linelen-1)]; 1567 while (last >= gp && last->u == ' ') 1568 --last; 1569 1570 @@ -850,6 +878,9 @@ 1571 ttywrite(const char *s, size_t n, int may_echo) 1572 { 1573 const char *next; 1574 + Arg arg = (Arg) { .i = term.scr }; 1575 + 1576 + kscrolldown(&arg); 1577 1578 if (may_echo && IS_SET(MODE_ECHO)) 1579 twrite(s, n, 1); 1580 @@ -1061,13 +1092,53 @@ 1581 } 1582 1583 void 1584 -tscrolldown(int orig, int n) 1585 +kscrolldown(const Arg* a) 1586 +{ 1587 + int n = a->i; 1588 + 1589 + if (n < 0) 1590 + n = term.row + n; 1591 + 1592 + if (n > term.scr) 1593 + n = term.scr; 1594 + 1595 + if (term.scr > 0) { 1596 + term.scr -= n; 1597 + selscroll(0, -n); 1598 + tfulldirt(); 1599 + } 1600 +} 1601 + 1602 +void 1603 +kscrollup(const Arg* a) 1604 +{ 1605 + int n = a->i; 1606 + 1607 + if (n < 0) 1608 + n = term.row + n; 1609 + 1610 + if (term.scr <= HISTSIZE-n) { 1611 + term.scr += n; 1612 + selscroll(0, n); 1613 + tfulldirt(); 1614 + } 1615 +} 1616 + 1617 +void 1618 +tscrolldown(int orig, int n, int copyhist) 1619 { 1620 int i; 1621 Line temp; 1622 1623 LIMIT(n, 0, term.bot-orig+1); 1624 1625 + if (copyhist) { 1626 + term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; 1627 + temp = term.hist[term.histi]; 1628 + term.hist[term.histi] = term.line[term.bot]; 1629 + term.line[term.bot] = temp; 1630 + } 1631 + 1632 tsetdirt(orig, term.bot-n); 1633 tclearregion(0, term.bot-n+1, term.col-1, term.bot); 1634 1635 @@ -1081,13 +1152,23 @@ 1636 } 1637 1638 void 1639 -tscrollup(int orig, int n) 1640 +tscrollup(int orig, int n, int copyhist) 1641 { 1642 int i; 1643 Line temp; 1644 1645 LIMIT(n, 0, term.bot-orig+1); 1646 1647 + if (copyhist) { 1648 + term.histi = (term.histi + 1) % HISTSIZE; 1649 + temp = term.hist[term.histi]; 1650 + term.hist[term.histi] = term.line[orig]; 1651 + term.line[orig] = temp; 1652 + } 1653 + 1654 + if (term.scr > 0 && term.scr < HISTSIZE) 1655 + term.scr = MIN(term.scr + n, HISTSIZE-1); 1656 + 1657 tclearregion(0, orig, term.col-1, orig+n-1); 1658 tsetdirt(orig+n, term.bot); 1659 1660 @@ -1109,6 +1190,7 @@ 1661 if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { 1662 selclear(); 1663 } else if (BETWEEN(sel.nb.y, orig, term.bot)) { 1664 + sel.oe.scroll = sel.ob.scroll = term.scr; 1665 sel.ob.y += n; 1666 sel.oe.y += n; 1667 if (sel.ob.y < term.top || sel.ob.y > term.bot || 1668 @@ -1126,13 +1208,19 @@ 1669 int y = term.c.y; 1670 1671 if (y == term.bot) { 1672 - tscrollup(term.top, 1); 1673 + tscrollup(term.top, 1, 1); 1674 } else { 1675 y++; 1676 } 1677 tmoveto(first_col ? 0 : term.c.x, y); 1678 } 1679 1680 +int 1681 +currentLine(int x, int y) 1682 +{ 1683 + return (x == term.c.x || y == term.c.y); 1684 +} 1685 + 1686 void 1687 csiparse(void) 1688 { 1689 @@ -1185,6 +1273,8 @@ 1690 term.c.state &= ~CURSOR_WRAPNEXT; 1691 term.c.x = LIMIT(x, 0, term.col-1); 1692 term.c.y = LIMIT(y, miny, maxy); 1693 + // Set the last position in order to restore after normal mode exits. 1694 + onMove(); 1695 } 1696 1697 void 1698 @@ -1291,14 +1381,14 @@ 1699 tinsertblankline(int n) 1700 { 1701 if (BETWEEN(term.c.y, term.top, term.bot)) 1702 - tscrolldown(term.c.y, n); 1703 + tscrolldown(term.c.y, n, 0); 1704 } 1705 1706 void 1707 tdeleteline(int n) 1708 { 1709 if (BETWEEN(term.c.y, term.top, term.bot)) 1710 - tscrollup(term.c.y, n); 1711 + tscrollup(term.c.y, n, 0); 1712 } 1713 1714 int32_t 1715 @@ -1735,11 +1825,11 @@ 1716 break; 1717 case 'S': /* SU -- Scroll <n> line up */ 1718 DEFAULT(csiescseq.arg[0], 1); 1719 - tscrollup(term.top, csiescseq.arg[0]); 1720 + tscrollup(term.top, csiescseq.arg[0], 0); 1721 break; 1722 case 'T': /* SD -- Scroll <n> line down */ 1723 DEFAULT(csiescseq.arg[0], 1); 1724 - tscrolldown(term.top, csiescseq.arg[0]); 1725 + tscrolldown(term.top, csiescseq.arg[0], 0); 1726 break; 1727 case 'L': /* IL -- Insert <n> blank lines */ 1728 DEFAULT(csiescseq.arg[0], 1); 1729 @@ -2246,7 +2336,7 @@ 1730 return 0; 1731 case 'D': /* IND -- Linefeed */ 1732 if (term.c.y == term.bot) { 1733 - tscrollup(term.top, 1); 1734 + tscrollup(term.top, 1, 1); 1735 } else { 1736 tmoveto(term.c.x, term.c.y+1); 1737 } 1738 @@ -2259,7 +2349,7 @@ 1739 break; 1740 case 'M': /* RI -- Reverse index */ 1741 if (term.c.y == term.top) { 1742 - tscrolldown(term.top, 1); 1743 + tscrolldown(term.top, 1, 1); 1744 } else { 1745 tmoveto(term.c.x, term.c.y-1); 1746 } 1747 @@ -2301,7 +2391,7 @@ 1748 { 1749 char c[UTF_SIZ]; 1750 int control; 1751 - int width, len; 1752 + int width = 0, len; 1753 Glyph *gp; 1754 1755 control = ISCONTROL(u); 1756 @@ -2481,7 +2571,7 @@ 1757 void 1758 tresize(int col, int row) 1759 { 1760 - int i; 1761 + int i, j; 1762 int minrow = MIN(row, term.row); 1763 int mincol = MIN(col, term.col); 1764 int *bp; 1765 @@ -2518,6 +2608,14 @@ 1766 term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); 1767 term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); 1768 1769 + for (i = 0; i < HISTSIZE; i++) { 1770 + term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); 1771 + for (j = mincol; j < col; j++) { 1772 + term.hist[i][j] = term.c.attr; 1773 + term.hist[i][j].u = ' '; 1774 + } 1775 + } 1776 + 1777 /* resize each row to new width, zero-pad if needed */ 1778 for (i = 0; i < minrow; i++) { 1779 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); 1780 @@ -2576,7 +2674,7 @@ 1781 continue; 1782 1783 term.dirty[y] = 0; 1784 - xdrawline(term.line[y], x1, y, x2); 1785 + xdrawline(TLINE(y), x1, y, x2); 1786 } 1787 } 1788 1789 @@ -2597,8 +2695,8 @@ 1790 cx--; 1791 1792 drawregion(0, 0, term.col, term.row); 1793 - xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 1794 - term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 1795 + xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], 1796 + term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); 1797 term.ocx = cx; 1798 term.ocy = term.c.y; 1799 xfinishdraw(); 1800 diff -ruN st-default/st.c.rej st1/st.c.rej 1801 --- st-default/st.c.rej 1970-01-01 01:00:00.000000000 +0100 1802 +++ st1/st.c.rej 2020-06-04 11:04:30.231111510 +0200 1803 @@ -0,0 +1,73 @@ 1804 +--- st.c 1805 ++++ st.c 1806 +@@ -91,51 +96,6 @@ enum escape_state { 1807 + ESC_DCS =128, 1808 + }; 1809 + 1810 +-typedef struct { 1811 +- Glyph attr; /* current char attributes */ 1812 +- int x; 1813 +- int y; 1814 +- char state; 1815 +-} TCursor; 1816 +- 1817 +-typedef struct { 1818 +- int mode; 1819 +- int type; 1820 +- int snap; 1821 +- /* 1822 +- * Selection variables: 1823 +- * nb – normalized coordinates of the beginning of the selection 1824 +- * ne – normalized coordinates of the end of the selection 1825 +- * ob – original coordinates of the beginning of the selection 1826 +- * oe – original coordinates of the end of the selection 1827 +- */ 1828 +- struct { 1829 +- int x, y; 1830 +- } nb, ne, ob, oe; 1831 +- 1832 +- int alt; 1833 +-} Selection; 1834 +- 1835 +-/* Internal representation of the screen */ 1836 +-typedef struct { 1837 +- int row; /* nb row */ 1838 +- int col; /* nb col */ 1839 +- Line *line; /* screen */ 1840 +- Line *alt; /* alternate screen */ 1841 +- int *dirty; /* dirtyness of lines */ 1842 +- TCursor c; /* cursor */ 1843 +- int ocx; /* old cursor col */ 1844 +- int ocy; /* old cursor row */ 1845 +- int top; /* top scroll limit */ 1846 +- int bot; /* bottom scroll limit */ 1847 +- int mode; /* terminal mode flags */ 1848 +- int esc; /* escape state flags */ 1849 +- char trantbl[4]; /* charset table translation */ 1850 +- int charset; /* current charset */ 1851 +- int icharset; /* selected charset for sequence */ 1852 +- int *tabs; 1853 +-} Term; 1854 +- 1855 + /* CSI Escape sequence structs */ 1856 + /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */ 1857 + typedef struct { 1858 +@@ -1174,6 +1210,7 @@ selscroll(int orig, int n) 1859 + return; 1860 + 1861 + if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { 1862 ++ sel.oe.scroll = sel.ob.scroll = term.scr; 1863 + if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { 1864 + selclear(); 1865 + return; 1866 +@@ -2681,8 +2734,8 @@ draw(void) 1867 + cx--; 1868 + 1869 + drawregion(0, 0, term.col, term.row); 1870 +- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], 1871 +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); 1872 ++ xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], 1873 ++ term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); 1874 + term.ocx = cx, term.ocy = term.c.y; 1875 + xfinishdraw(); 1876 + xximspot(term.ocx, term.ocy); 1877 diff -ruN st-default/st.h st1/st.h 1878 --- st-default/st.h 2020-06-04 11:15:55.165135902 +0200 1879 +++ st1/st.h 2020-06-04 11:04:30.232111510 +0200 1880 @@ -1,5 +1,8 @@ 1881 /* See LICENSE for license details. */ 1882 1883 +#include "glyph.h" 1884 +#include "normalMode.h" 1885 + 1886 #include <stdint.h> 1887 #include <sys/types.h> 1888 1889 @@ -33,6 +36,8 @@ 1890 ATTR_WRAP = 1 << 8, 1891 ATTR_WIDE = 1 << 9, 1892 ATTR_WDUMMY = 1 << 10, 1893 + ATTR_HIGHLIGHT = 1 << 12, 1894 + ATTR_CURRENT = 1 << 13, 1895 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 1896 }; 1897 1898 @@ -42,11 +47,6 @@ 1899 SEL_READY = 2 1900 }; 1901 1902 -enum selection_type { 1903 - SEL_REGULAR = 1, 1904 - SEL_RECTANGULAR = 2 1905 -}; 1906 - 1907 enum selection_snap { 1908 SNAP_WORD = 1, 1909 SNAP_LINE = 2 1910 @@ -57,18 +57,6 @@ 1911 typedef unsigned long ulong; 1912 typedef unsigned short ushort; 1913 1914 -typedef uint_least32_t Rune; 1915 - 1916 -#define Glyph Glyph_ 1917 -typedef struct { 1918 - Rune u; /* character code */ 1919 - ushort mode; /* attribute flags */ 1920 - uint32_t fg; /* foreground */ 1921 - uint32_t bg; /* background */ 1922 -} Glyph; 1923 - 1924 -typedef Glyph *Line; 1925 - 1926 typedef union { 1927 int i; 1928 uint ui; 1929 @@ -81,6 +69,11 @@ 1930 void redraw(void); 1931 void draw(void); 1932 1933 +int currentLine(int, int); 1934 +void kscrolldown(const Arg *); 1935 +void kscrollup(const Arg *); 1936 +void normalMode(Arg const *); 1937 + 1938 void printscreen(const Arg *); 1939 void printsel(const Arg *); 1940 void sendbreak(const Arg *); 1941 @@ -90,6 +83,9 @@ 1942 void tnew(int, int); 1943 void tresize(int, int); 1944 void tsetdirtattr(int); 1945 +size_t utf8decode(const char *, Rune *, size_t); 1946 +Rune utf8decodebyte(char, size_t *); 1947 +void tsetdirt(int, int); 1948 void ttyhangup(void); 1949 int ttynew(char *, char *, char *, char **); 1950 size_t ttyread(void); 1951 @@ -100,8 +96,10 @@ 1952 1953 void selclear(void); 1954 void selinit(void); 1955 -void selstart(int, int, int); 1956 -void selextend(int, int, int, int); 1957 +void selstart(int, int, int, int); 1958 +void xselstart(int, int, int); 1959 +void selextend(int, int, int, int, int); 1960 +void xselextend(int, int, int, int); 1961 int selected(int, int); 1962 char *getsel(void); 1963 1964 Binary files st-default/st.o and st1/st.o differ 1965 diff -ruN st-default/term.h st1/term.h 1966 --- st-default/term.h 1970-01-01 01:00:00.000000000 +0100 1967 +++ st1/term.h 2020-06-04 11:04:30.232111510 +0200 1968 @@ -0,0 +1,74 @@ 1969 +#ifndef TERM_H 1970 +#define TERM_H 1971 + 1972 +// 1973 +// Internal terminal structs. 1974 +// 1975 + 1976 +#include "glyph.h" 1977 + 1978 +#include <stdint.h> 1979 + 1980 +#define HISTSIZE 2500 1981 + 1982 +typedef struct { 1983 + Glyph attr; /* current char attributes */ 1984 + int x; 1985 + int y; 1986 + char state; 1987 +} TCursor; 1988 + 1989 +typedef struct { 1990 + int mode; 1991 + int type; 1992 + int snap; 1993 + /// Selection variables: 1994 + /// ob – original coordinates of the beginning of the selection 1995 + /// oe – original coordinates of the end of the selection 1996 + struct { 1997 + int x, y, scroll; 1998 + } ob, oe; 1999 + /// Selection variables; currently displayed chunk. 2000 + /// nb – normalized coordinates of the beginning of the selection 2001 + /// ne – normalized coordinates of the end of the selection 2002 + struct { 2003 + int x, y; 2004 + } nb, ne; 2005 + 2006 + int alt; 2007 +} Selection; 2008 + 2009 +/* Internal representation of the screen */ 2010 +typedef struct { 2011 + int row; /* nb row */ 2012 + int col; /* nb col */ 2013 + Line *line; /* screen */ 2014 + Line *alt; /* alternate screen */ 2015 + Line hist[HISTSIZE]; /* history buffer */ 2016 + int histi; /* history index */ 2017 + int scr; /* scroll back */ 2018 + int *dirty; /* dirtyness of lines */ 2019 + TCursor c; /* cursor */ 2020 + int ocx; /* old cursor col */ 2021 + int ocy; /* old cursor row */ 2022 + int top; /* top scroll limit */ 2023 + int bot; /* bottom scroll limit */ 2024 + int mode; /* terminal mode flags */ 2025 + int esc; /* escape state flags */ 2026 + char trantbl[4]; /* charset table translation */ 2027 + int charset; /* current charset */ 2028 + int icharset; /* selected charset for sequence */ 2029 + int *tabs; 2030 + Rune lastc; 2031 +} Term; 2032 + 2033 +extern Term term; 2034 + 2035 +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ 2036 + term.scr + HISTSIZE + 1) % HISTSIZE] : \ 2037 + term.line[(y) - term.scr]) 2038 + 2039 +extern Selection sel; 2040 + 2041 + 2042 +#endif // TERM_H 2043 diff -ruN st-default/win.h st1/win.h 2044 --- st-default/win.h 2020-06-04 11:15:55.166135902 +0200 2045 +++ st1/win.h 2020-06-04 11:04:30.232111510 +0200 2046 @@ -19,6 +19,7 @@ 2047 MODE_MOUSEMANY = 1 << 15, 2048 MODE_BRCKTPASTE = 1 << 16, 2049 MODE_NUMLOCK = 1 << 17, 2050 + MODE_NORMAL = 1 << 18, 2051 MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ 2052 |MODE_MOUSEMANY, 2053 }; 2054 @@ -27,6 +28,7 @@ 2055 void xclipcopy(void); 2056 void xdrawcursor(int, int, Glyph, int, int, Glyph); 2057 void xdrawline(Line, int, int, int); 2058 +void xdrawglyph(Glyph, int, int); 2059 void xfinishdraw(void); 2060 void xloadcols(void); 2061 int xsetcolorname(int, const char *); 2062 diff -ruN st-default/x.c st1/x.c 2063 --- st-default/x.c 2020-06-04 11:15:55.166135902 +0200 2064 +++ st1/x.c 2020-06-04 11:04:30.232111510 +0200 2065 @@ -143,7 +143,6 @@ 2066 static inline ushort sixd_to_16bit(int); 2067 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); 2068 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); 2069 -static void xdrawglyph(Glyph, int, int); 2070 static void xclear(int, int, int, int); 2071 static int xgeommasktogravity(int); 2072 static int ximopen(Display *); 2073 @@ -356,7 +355,7 @@ 2074 break; 2075 } 2076 } 2077 - selextend(evcol(e), evrow(e), seltype, done); 2078 + xselextend(evcol(e), evrow(e), seltype, done); 2079 if (done) 2080 setsel(getsel(), e->xbutton.time); 2081 } 2082 @@ -486,7 +485,7 @@ 2083 xsel.tclick2 = xsel.tclick1; 2084 xsel.tclick1 = now; 2085 2086 - selstart(evcol(e), evrow(e), snap); 2087 + xselstart(evcol(e), evrow(e), snap); 2088 } 2089 } 2090 2091 @@ -773,6 +772,13 @@ 2092 } 2093 2094 void 2095 +normalMode(Arg const *_) { 2096 + (void) _; 2097 + win.mode ^= MODE_NORMAL; 2098 +} 2099 + 2100 + 2101 +void 2102 xloadcols(void) 2103 { 2104 int i; 2105 @@ -1358,6 +1364,14 @@ 2106 base.fg = defaultattr; 2107 } 2108 2109 + if (base.mode & ATTR_HIGHLIGHT) { 2110 + base.bg = highlightBg; 2111 + base.fg = highlightFg; 2112 + } else if ((base.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { 2113 + base.bg = currentBg; 2114 + base.fg = currentFg; 2115 + } 2116 + 2117 if (IS_TRUECOL(base.fg)) { 2118 colfg.alpha = 0xffff; 2119 colfg.red = TRUERED(base.fg); 2120 @@ -1447,7 +1461,7 @@ 2121 xclear(winx, winy + win.ch, winx + width, win.h); 2122 2123 /* Clean up the region we want to draw to. */ 2124 - XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 2125 + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); 2126 2127 /* Set the clip region because Xft is sometimes dirty. */ 2128 r.x = 0; 2129 @@ -1490,8 +1504,9 @@ 2130 Color drawcol; 2131 2132 /* remove the old cursor */ 2133 - if (selected(ox, oy)) 2134 - og.mode ^= ATTR_REVERSE; 2135 + if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; 2136 + if (highlighted(ox, oy)) { og.mode ^= ATTR_HIGHLIGHT; } 2137 + if (currentLine(ox, oy)) { og.mode ^= ATTR_CURRENT; } 2138 xdrawglyph(og, ox, oy); 2139 2140 if (IS_SET(MODE_HIDE)) 2141 @@ -1523,6 +1538,11 @@ 2142 drawcol = dc.col[g.bg]; 2143 } 2144 2145 + if ((g.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { 2146 + g.bg = currentBg; 2147 + g.fg = currentFg; 2148 + } 2149 + 2150 /* draw the new one */ 2151 if (IS_SET(MODE_FOCUSED)) { 2152 switch (win.cursor) { 2153 @@ -1607,12 +1627,18 @@ 2154 2155 numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); 2156 i = ox = 0; 2157 - for (x = x1; x < x2 && i < numspecs; x++) { 2158 + for (x = x1; x < x2 && i < numspecs; ++x) { 2159 new = line[x]; 2160 if (new.mode == ATTR_WDUMMY) 2161 continue; 2162 if (selected(x, y1)) 2163 new.mode ^= ATTR_REVERSE; 2164 + if (highlighted(x, y1)) { 2165 + new.mode ^= ATTR_HIGHLIGHT; 2166 + } 2167 + if (currentLine(x, y1)) { 2168 + new.mode ^= ATTR_CURRENT; 2169 + } 2170 if (i > 0 && ATTRCMP(base, new)) { 2171 xdrawglyphfontspecs(specs, base, i, ox, y1); 2172 specs += i; 2173 @@ -1800,6 +1826,14 @@ 2174 len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); 2175 else 2176 len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 2177 + 2178 + if (IS_SET(MODE_NORMAL)) { 2179 + ExitState const es = kpressNormalMode(buf, len, // strlen(buf), 2180 + match(ControlMask, e->state), 2181 + &ksym); 2182 + if (es == finished) { normalMode(NULL); } 2183 + return; 2184 + } 2185 /* 1. shortcuts */ 2186 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 2187 if (ksym == bp->keysym && match(bp->mod, e->state)) { 2188 Binary files st-default/x.o and st1/x.o differ