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