st-vimBrowse-20191107-2b8333f.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) 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