sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

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