sites

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

st-vimBrowse-20191107-2b8333f.diff (45073B)


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