sites

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

commit 0c257fe22da595deeab10aecd4dc74084a157959
parent 3cef6eb671b5ed25e67ac2af8ec5b689ed43a94f
Author: Julius Huelsmann <juliusHuelsmann@gmail.com>
Date:   Tue,  3 Dec 2019 11:09:07 +0100

[st][vimBrowse] update patch and add contributor

Add bugfix for underlined text that was highlighted prior to this fix.

Diffstat:
Mst.suckless.org/patches/vimbrowse/index.md | 13++++++++-----
Ast.suckless.org/patches/vimbrowse/st-vimBrowse-20191203-2b8333f.diff | 1559+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1567 insertions(+), 5 deletions(-)

diff --git a/st.suckless.org/patches/vimbrowse/index.md b/st.suckless.org/patches/vimbrowse/index.md @@ -64,7 +64,7 @@ direction). Screenshot ---------- -![Screenshot](https://user-images.githubusercontent.com/9212314/68340852-7d6d9380-00e7-11ea-9705-51ed098eba2a.gif =250x) +* [Gif which shows some of the features](https://user-images.githubusercontent.com/9212314/68340852-7d6d9380-00e7-11ea-9705-51ed098eba2a.gif) Contributions + Bug Reports --------------------------- @@ -91,12 +91,13 @@ Bugs Download -------- -**All versions**: -* [st-vimBrowse-20191107-2b8333f.diff (attached)](st-vimBrowse-20191107-2b8333f.diff ) -* [st-vimBrowse-20191107-2b8333f.diff (Github)](https://github.com/juliusHuelsmann/st/releases/download/patchesV1/st-vimBrowse-20191107-2b8333f.diff) +**All versions (from old to new) **: +* [st-vimBrowse-20191107-2b8333f.diff (attached)](st-vimBrowse-20191107-2b8333f.diff) +* [st-vimBrowse-20191203-2b8333f.diff (attached)](st-vimBrowse-20191203-2b8333f.diff) +* [st-vimBrowse-20191203-2b8333f.diff (Github)](https://github.com/juliusHuelsmann/st/releases/download/patchesV1/st-vimBrowse-20191203-2b8333f.diff) **Most Recent**: -* [st-vimBrowse-20191107-2b8333f.diff (Github)](https://github.com/juliusHuelsmann/st/releases/download/patchesV1/st-vimBrowse-20191107-2b8333f.diff) +* [st-vimBrowse-20191203-2b8333f.diff (Github)](https://github.com/juliusHuelsmann/st/releases/download/patchesV1/st-vimBrowse-20191203-2b8333f.diff) Authors of the [Scrollback patch](https://st.suckless.org/patches/scrollback/) @@ -119,3 +120,5 @@ Authors of the [Scrollback patch](https://st.suckless.org/patches/scrollback/) Authors of the Vim-Browse Patch -------------------------------- * Julius Hülsmann - <juliusHuelsmann [at] gmail [dot] com> +* [Kevin Velghe](https://github.com/paretje): Fix: Underline highlight + diff --git a/st.suckless.org/patches/vimbrowse/st-vimBrowse-20191203-2b8333f.diff b/st.suckless.org/patches/vimbrowse/st-vimBrowse-20191203-2b8333f.diff @@ -0,0 +1,1559 @@ +From de020f0c06440fd19a36e1b001ef9e0058f73369 Mon Sep 17 00:00:00 2001 +From: Julius Huelsmann <juliusHuelsmann@gmail.com> +Date: Thu, 7 Nov 2019 09:08:49 +0100 +Subject: [PATCH 1/2] [PATCH:VIM]: first version + +--- + Makefile | 6 +- + config.def.h | 27 ++ + dynamicArray.h | 90 ++++++ + st.c | 794 +++++++++++++++++++++++++++++++++++++++++++++---- + st.h | 31 +- + win.h | 2 + + x.c | 51 +++- + 7 files changed, 936 insertions(+), 65 deletions(-) + create mode 100644 dynamicArray.h + +diff --git a/Makefile b/Makefile +index 470ac86..7d93347 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,8 +21,8 @@ config.h: + .c.o: + $(CC) $(STCFLAGS) -c $< + +-st.o: config.h st.h win.h +-x.o: arg.h config.h st.h win.h ++st.o: config.h st.h win.h dynamicArray.h ++x.o: arg.h config.h st.h win.h dynamicArray.h + + $(OBJ): config.h config.mk + +@@ -35,7 +35,7 @@ clean: + dist: clean + mkdir -p st-$(VERSION) + cp -R FAQ LEGACY TODO LICENSE Makefile README config.mk\ +- config.def.h st.info st.1 arg.h st.h win.h $(SRC)\ ++ config.def.h st.info st.1 arg.h st.h win.h dynamicArray.h $(SRC)\ + st-$(VERSION) + tar -cf - st-$(VERSION) | gzip > st-$(VERSION).tar.gz + rm -rf st-$(VERSION) +diff --git a/config.def.h b/config.def.h +index 6ebea98..1b0e501 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -149,6 +149,12 @@ static unsigned int mousebg = 0; + * doesn't match the ones requested. + */ + static unsigned int defaultattr = 11; ++/// Colors for the entities that are highlighted in normal mode. ++static unsigned int highlightBg = 160; ++static unsigned int highlightFg = 15; ++/// Colors for the line and column that is marked 'current' in normal mode. ++static unsigned int currentBg = 0; ++static unsigned int currentFg = 15; + + /* + * Internal mouse shortcuts. +@@ -162,10 +168,12 @@ static MouseShortcut mshortcuts[] = { + + /* Internal keyboard shortcuts. */ + #define MODKEY Mod1Mask ++#define AltMask Mod1Mask + #define TERMMOD (ControlMask|ShiftMask) + + static Shortcut shortcuts[] = { + /* mask keysym function argument */ ++ { AltMask, XK_c, normalMode, {.i = 0} }, + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, +@@ -178,6 +186,8 @@ static Shortcut shortcuts[] = { + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, ++ { ShiftMask, XK_Page_Up, kscrollup, {.i = -1} }, ++ { ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} }, + }; + + /* +@@ -456,3 +466,20 @@ static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; ++ ++ ++/// word sepearors normal mode ++char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; ++char wordDelimLarge[] = " \t"; /// <Word sepearors normal mode (capital W) ++ ++/// Shortcusts executed in normal mode (which should not already be in use) ++struct NormalModeShortcuts normalModeShortcuts [] = { ++ { 'C', "?Building\n" }, ++ { 'c', "/Building\n" }, ++ { 'F', "?: error:\n" }, ++ { 'f', "/: error:\n" }, ++ { 'X', "?juli@machine\n" }, ++ { 'x', "/juli@machine\n" }, ++}; ++ ++size_t const amountNormalModeShortcuts = sizeof(normalModeShortcuts) / sizeof(*normalModeShortcuts); +diff --git a/dynamicArray.h b/dynamicArray.h +new file mode 100644 +index 0000000..c65fbef +--- /dev/null ++++ b/dynamicArray.h +@@ -0,0 +1,90 @@ ++#include <stdint.h> ++#include <assert.h> ++#include <stdlib.h> ++#include <string.h> ++#include <stdbool.h> ++ ++/// Struct for which this file offers functionality in order to expand the array ++/// and set / get its content. ++typedef struct DynamicArray { ++ uint8_t itemSize; ++ uint32_t index; ++ uint32_t allocated; ++ char* content; ++} DynamicArray; ++ ++#define EXPAND_STEP 15 ++ ++/// Default initializers for the dynamic array. ++#define CHAR_ARRAY {1, 0, 0, NULL} ++#define WORD_ARRAY {2, 0, 0, NULL} ++#define DWORD_ARRAY {4, 0, 0, NULL} ++#define QWORD_ARRAY {8, 0, 0, NULL} ++/// (Wasteful) utf-8 array, that always used 4 bytes in order to display a character, ++/// even if the space is not required. ++#define UTF8_ARRAY DWORD_ARRAY ++ ++ ++inline char* ++gnext(DynamicArray *s) { return &s->content[s->index+=s->itemSize]; } ++ ++inline char* ++get(DynamicArray const * s) { return &s->content[s->index]; } ++ ++inline char* ++view(DynamicArray const * s, uint32_t i) { ++ return s->content + i*s->itemSize; ++} ++ ++inline char * ++viewEnd(DynamicArray const *s, uint32_t i) { ++ return s->content + s->index - (i + 1) * s->itemSize; ++} ++ ++inline void ++set(DynamicArray* s, char const *vals, uint8_t amount) { ++ assert(amount <= s->itemSize); ++ memcpy(s->content + s->index, vals, amount); ++} ++ ++inline void ++snext(DynamicArray* s, char const *vals, uint8_t amount) { ++ set(s, vals, amount); ++ s->index+=s->itemSize; ++} ++ ++inline void ++empty(DynamicArray* s) { s->index = 0; } ++ ++inline bool ++isEmpty(DynamicArray* s) { return s->index == 0; } ++ ++inline uint32_t ++size(DynamicArray const * s) { return s->index / s->itemSize; } ++ ++inline void ++pop(DynamicArray* s) { s->index -= s->itemSize; } ++ ++inline void checkSetNext(DynamicArray *s, char const *c, uint8_t amount) { ++ if (s->index + s->itemSize >= s->allocated) { ++ if ((s->content = (char *)realloc( ++ s->content, s->allocated += EXPAND_STEP * s->itemSize)) == NULL) { ++ exit(1); ++ }; ++ } ++ if (amount) { snext(s, c, amount); } ++} ++ ++char *checkGetNext(DynamicArray *s) { ++ if (s->index + s->itemSize >= s->allocated) { ++ if ((s->content = (char *)realloc( ++ s->content, s->allocated += EXPAND_STEP * s->itemSize)) == NULL) { ++ exit(1); ++ }; ++ } ++ s->index+=s->itemSize; ++ return viewEnd(s, 0); ++} ++ ++#define append(s, c) checkSetNext((s), (char const *) (c), (s)->itemSize) ++#define appendPartial(s, c, i) checkSetNext((s), (char const *) (c), (i)) +diff --git a/st.c b/st.c +index ede7ae6..27bfca8 100644 +--- a/st.c ++++ b/st.c +@@ -1,8 +1,10 @@ + /* See LICENSE for license details. */ ++#include <assert.h> + #include <ctype.h> + #include <errno.h> + #include <fcntl.h> + #include <limits.h> ++#include <math.h> + #include <pwd.h> + #include <stdarg.h> + #include <stdio.h> +@@ -17,8 +19,10 @@ + #include <unistd.h> + #include <wchar.h> + ++ + #include "st.h" + #include "win.h" ++#include "dynamicArray.h" + + #if defined(__linux) + #include <pty.h> +@@ -35,6 +39,8 @@ + #define ESC_ARG_SIZ 16 + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ ++//#define HISTSIZE 100 ++#define HISTSIZE 2000 + + /* macros */ + #define IS_SET(flag) ((term.mode & (flag)) != 0) +@@ -42,6 +48,9 @@ + #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) + #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) + #define ISDELIM(u) (u && wcschr(worddelimiters, u)) ++#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ ++ term.scr + HISTSIZE + 1) % HISTSIZE] : \ ++ term.line[(y) - term.scr]) + + enum term_mode { + MODE_WRAP = 1 << 0, +@@ -97,16 +106,18 @@ typedef struct { + int mode; + int type; + int snap; +- /* +- * Selection variables: +- * nb – normalized coordinates of the beginning of the selection +- * ne – normalized coordinates of the end of the selection +- * ob – original coordinates of the beginning of the selection +- * oe – original coordinates of the end of the selection +- */ ++ /// Selection variables: ++ /// ob – original coordinates of the beginning of the selection ++ /// oe – original coordinates of the end of the selection ++ struct { ++ int x, y, scroll; ++ } ob, oe; ++ /// Selection variables; currently displayed chunk. ++ /// nb – normalized coordinates of the beginning of the selection ++ /// ne – normalized coordinates of the end of the selection + struct { + int x, y; +- } nb, ne, ob, oe; ++ } nb, ne; + + int alt; + } Selection; +@@ -117,6 +128,9 @@ typedef struct { + int col; /* nb col */ + Line *line; /* screen */ + Line *alt; /* alternate screen */ ++ Line hist[HISTSIZE]; /* history buffer */ ++ int histi; /* history index */ ++ int scr; /* scroll back */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ +@@ -152,6 +166,50 @@ typedef struct { + int narg; /* nb of args */ + } STREscape; + ++/// Position (x, y , and current scroll in the y dimension). ++typedef struct Position { ++ uint32_t x; ++ uint32_t y; ++ uint32_t yScr; ++} Position; ++ ++/// The entire normal mode state, consisting of an operation ++/// and a motion. ++struct NormalModeState { ++ Position initialPosition; ++ // Operation: ++ struct OperationState { ++ enum Operation { ++ noop, ++ visual, ++ visualLine, ++ yank ++ } op; ++ Position startPosition; ++ } command; ++ // Motions: ++ struct MotionState { ++ uint32_t amount; ++ enum Search { ++ none, ++ forward, ++ backward, ++ } search; ++ Position searchPosition; ++ bool finished; ++ } motion; ++} stateNormalMode; ++ ++ ++DynamicArray searchString = UTF8_ARRAY; ++DynamicArray commandHist0 = UTF8_ARRAY; ++DynamicArray commandHist1 = UTF8_ARRAY; ++DynamicArray highlights = QWORD_ARRAY; ++/// History command toggle ++bool toggle = false; ++#define currentCommand toggle ? &commandHist0 : &commandHist1 ++#define lastCommand toggle ? &commandHist1 : &commandHist0 ++ + static void execsh(char *, char **); + static void stty(char **); + static void sigchld(int); +@@ -184,8 +242,8 @@ static void tnewline(int); + static void tputtab(int); + static void tputc(Rune); + static void treset(void); +-static void tscrollup(int, int); +-static void tscrolldown(int, int); ++static void tscrollup(int, int, int); ++static void tscrolldown(int, int, int); + static void tsetattr(int *, int); + static void tsetchar(Rune, Glyph *, int, int); + static void tsetdirt(int, int); +@@ -231,6 +289,12 @@ static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; + static Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; + static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + ++void applyPosition(Position const *pos) { ++ term.c.x = pos->x; ++ term.c.y = pos->y; ++ term.scr = pos->yScr; ++} ++ + ssize_t + xwrite(int fd, const char *s, size_t len) + { +@@ -409,17 +473,22 @@ tlinelen(int y) + { + int i = term.col; + +- if (term.line[y][i - 1].mode & ATTR_WRAP) ++ if (TLINE(y)[i - 1].mode & ATTR_WRAP) + return i; + +- while (i > 0 && term.line[y][i - 1].u == ' ') ++ while (i > 0 && TLINE(y)[i - 1].u == ' ') + --i; + + return i; + } + + void +-selstart(int col, int row, int snap) ++xselstart(int col, int row, int snap) { ++ selstart(col, row, term.scr, snap); ++} ++ ++void ++selstart(int col, int row, int scroll, int snap) + { + selclear(); + sel.mode = SEL_EMPTY; +@@ -428,6 +497,7 @@ selstart(int col, int row, int snap) + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; ++ sel.oe.scroll = sel.ob.scroll = scroll; + selnormalize(); + + if (sel.snap != 0) +@@ -436,10 +506,13 @@ selstart(int col, int row, int snap) + } + + void +-selextend(int col, int row, int type, int done) +-{ +- int oldey, oldex, oldsby, oldsey, oldtype; ++xselextend(int col, int row, int type, int done) { ++ selextend(col, row, term.scr, type, done); ++} + ++void ++selextend(int col, int row, int scroll, int type, int done) ++{ + if (sel.mode == SEL_IDLE) + return; + if (done && sel.mode == SEL_EMPTY) { +@@ -447,18 +520,22 @@ selextend(int col, int row, int type, int done) + return; + } + +- oldey = sel.oe.y; +- oldex = sel.oe.x; +- oldsby = sel.nb.y; +- oldsey = sel.ne.y; +- oldtype = sel.type; ++ int const oldey = sel.oe.y; ++ int const oldex = sel.oe.x; ++ int const oldscroll = sel.oe.scroll; ++ int const oldsby = sel.nb.y; ++ int const oldsey = sel.ne.y; ++ int const oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; ++ sel.oe.scroll = scroll; ++ + selnormalize(); + sel.type = type; + +- if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY) ++ if (oldey != sel.oe.y || oldex != sel.oe.x || oldscroll != sel.oe.scroll ++ || oldtype != sel.type || sel.mode == SEL_EMPTY) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SEL_IDLE : SEL_READY; +@@ -467,17 +544,21 @@ selextend(int col, int row, int type, int done) + void + selnormalize(void) + { +- int i; +- +- if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) { +- sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; +- sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; ++ sel.nb.y = INTERVAL(sel.ob.y + term.scr - sel.ob.scroll, 0, term.bot); ++ sel.ne.y = INTERVAL(sel.oe.y + term.scr - sel.oe.scroll, 0, term.bot); ++ if (sel.type == SEL_REGULAR && sel.nb.y != sel.ne.y) { ++ sel.nb.x = sel.nb.y < sel.ne.y ? sel.ob.x : sel.oe.x; ++ sel.ne.x = sel.nb.y < sel.ne.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } +- sel.nb.y = MIN(sel.ob.y, sel.oe.y); +- sel.ne.y = MAX(sel.ob.y, sel.oe.y); ++ ++ if (sel.nb.y > sel.ne.y) { ++ int32_t const tmp = sel.nb.y; ++ sel.nb.y = sel.ne.y; ++ sel.ne.y = tmp; ++ } + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); +@@ -485,7 +566,7 @@ selnormalize(void) + /* expand selection over line breaks */ + if (sel.type == SEL_RECTANGULAR) + return; +- i = tlinelen(sel.nb.y); ++ int i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) +@@ -521,7 +602,7 @@ selsnap(int *x, int *y, int direction) + * Snap around if the word wraps around at the end or + * beginning of a line. + */ +- prevgp = &term.line[*y][*x]; ++ prevgp = &TLINE(*y)[*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; +@@ -536,14 +617,14 @@ selsnap(int *x, int *y, int direction) + yt = *y, xt = *x; + else + yt = newy, xt = newx; +- if (!(term.line[yt][xt].mode & ATTR_WRAP)) ++ if (!(TLINE(yt)[xt].mode & ATTR_WRAP)) + break; + } + + if (newx >= tlinelen(newy)) + break; + +- gp = &term.line[newy][newx]; ++ gp = &TLINE(newy)[newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) +@@ -564,14 +645,14 @@ selsnap(int *x, int *y, int direction) + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode ++ if (!(TLINE(*y-1)[term.col-1].mode + & ATTR_WRAP)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode ++ if (!(TLINE(*y)[term.col-1].mode + & ATTR_WRAP)) { + break; + } +@@ -591,24 +672,32 @@ getsel(void) + if (sel.ob.x == -1) + return NULL; + +- bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; ++ int32_t syb = sel.ob.y - sel.ob.scroll + term.scr; ++ int32_t sye = sel.oe.y - sel.oe.scroll + term.scr; ++ if (syb > sye) { ++ int32_t tmp = sye; ++ sye = syb; ++ syb = tmp; ++ } ++ ++ bufsize = (term.col+1) * (sye - syb + 1) * UTF_SIZ; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ +- for (y = sel.nb.y; y <= sel.ne.y; y++) { ++ for (y = syb; y <= sye; y++) { + if ((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &term.line[y][sel.nb.x]; ++ gp = &TLINE(y)[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; +- lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; ++ gp = &TLINE(y)[syb == y ? sel.nb.x : 0]; ++ lastx = (sye == y) ? sel.ne.x : term.col-1; + } +- last = &term.line[y][MIN(lastx, linelen-1)]; ++ last = &TLINE(y)[MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + +@@ -831,6 +920,9 @@ void + ttywrite(const char *s, size_t n, int may_echo) + { + const char *next; ++ Arg arg = (Arg) { .i = term.scr }; ++ ++ kscrolldown(&arg); + + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); +@@ -1042,13 +1134,53 @@ tswapscreen(void) + } + + void +-tscrolldown(int orig, int n) ++kscrolldown(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (n > term.scr) ++ n = term.scr; ++ ++ if (term.scr > 0) { ++ term.scr -= n; ++ selscroll(0, -n); ++ tfulldirt(); ++ } ++} ++ ++void ++kscrollup(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (n < 0) ++ n = term.row + n; ++ ++ if (term.scr <= HISTSIZE-n) { ++ term.scr += n; ++ selscroll(0, n); ++ tfulldirt(); ++ } ++} ++ ++void ++tscrolldown(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + ++ if (copyhist) { ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[term.bot]; ++ term.line[term.bot] = temp; ++ } ++ + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + +@@ -1062,13 +1194,23 @@ tscrolldown(int orig, int n) + } + + void +-tscrollup(int orig, int n) ++tscrollup(int orig, int n, int copyhist) + { + int i; + Line temp; + + LIMIT(n, 0, term.bot-orig+1); + ++ if (copyhist) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ term.hist[term.histi] = term.line[orig]; ++ term.line[orig] = temp; ++ } ++ ++ if (term.scr > 0 && term.scr < HISTSIZE) ++ term.scr = MIN(term.scr + n, HISTSIZE-1); ++ + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + +@@ -1088,6 +1230,7 @@ selscroll(int orig, int n) + return; + + if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) { ++ sel.oe.scroll = sel.ob.scroll = term.scr; + if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) { + selclear(); + return; +@@ -1117,13 +1260,544 @@ tnewline(int first_col) + int y = term.c.y; + + if (y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); + } + ++int ++currentLine(int x, int y) ++{ ++ return (x == term.c.x || y == term.c.y); ++} ++ ++int ++highlighted(int x, int y) ++{ ++ // Compute the legal bounds for a hit: ++ int32_t const stringSize = size(&searchString); ++ int32_t xMin = x - stringSize; ++ int32_t yMin = y; ++ while (xMin < 0 && yMin > 0) { //< I think this temds to be more efficient than ++ xMin += term.col; // division + modulo. ++ --yMin; ++ } ++ if (xMin < 0) { xMin = 0; } ++ ++ uint32_t highSize = size(&highlights); ++ uint32_t *ptr = (uint32_t*) highlights.content; ++ for (uint32_t i = 0; i < highSize; ++i) { ++ int32_t const sx = *(ptr++); ++ int32_t const sy = *(ptr++); ++ if (BETWEEN(sy, yMin, y) && (sy != yMin || sx > xMin) && (sy != y || sx <= x)) { ++ return true; ++ } ++ } ++ return false; ++} ++ ++int mod(int a, int b) { ++ while (a < 0) { ++ a+= b; ++ } ++ return a % b; ++} ++ ++void displayString(DynamicArray const *str, Glyph *g, int yPos) { ++ // Threshold: if there is nothing or no space to print, do not print. ++ if (term.col == 0 || str->index == 0) { ++ term.dirty[yPos] = 1; //< mark this line as 'dirty', because the line is not ++ // marked dirty when scrolling due to string display. ++ return; ++ } ++ ++ uint32_t lineSize = MIN(size(str), term.col / 3); ++ uint32_t xEnd = term.col - 1; ++ assert(lineSize <= 1 + xEnd); //< as lineSize <= term.col/3 <= term.col - 1 + 1 = xEnd + 1 ++ uint32_t xStart = 1 + xEnd - lineSize; ++ ++ Line line = malloc(sizeof(Glyph) * lineSize); ++ assert(str->index - 1 >= lineSize - 1); //< lineSize <= str->index -1 direct premise. ++ ++ for (uint32_t lineIdx = 0; lineIdx < lineSize; lineIdx++) { ++ line[lineIdx] = *g; ++ char* end = viewEnd(str, lineSize - lineIdx - 1); ++ memcpy(&line[lineIdx].u, end, str->itemSize); ++ } ++ xdrawline(TLINE(yPos), 0, yPos, xStart); ++ xdrawline(line -xStart, xStart, yPos, xEnd+1); ++ free(line); // that sucks. ++} ++ ++/// Print either the current command or the last comman din case the current command is empty. ++void printCommandString() { ++ Glyph g = {'c', ATTR_ITALIC | ATTR_FAINT , defaultfg, defaultbg}; ++ if (term.c.y == term.row-1) { g.mode ^= ATTR_CURRENT; } //< dont highlight ++ DynamicArray * cc = currentCommand; ++ displayString(isEmpty(cc) ? lastCommand : cc, &g, term.row - 1); ++ //displayString(lastCommand, &g, term.row - 2); ++} ++ ++void printSearchString() { ++ Glyph g = {'c', ATTR_ITALIC | ATTR_BOLD_FAINT, defaultfg, defaultbg}; ++ if (term.c.y == term.row-2) { g.mode ^= ATTR_CURRENT; } //< dont highlight ++ displayString(&searchString, &g, term.row - 2); ++} ++ ++/// Default state if no operation is performed. ++struct NormalModeState defaultNormalMode = {{0,0,0}, {noop, {0, 0, 0}}, {0, none, {0, 0, 0}, false}}; ++ ++void enableMode(enum Operation o) { ++ stateNormalMode.command.op = o; ++ stateNormalMode.command.startPosition.x = term.c.x; ++ stateNormalMode.command.startPosition.y = term.c.y; ++ stateNormalMode.command.startPosition.yScr = term.scr; ++} ++ ++bool normalModeEnabled = false; ++ ++void onNormalModeStart() { ++ normalModeEnabled = true; ++} ++ ++void onNormalModeStop() { //XXX breaks if resized ++ normalModeEnabled = false; ++ applyPosition(&stateNormalMode.initialPosition); ++} ++ ++void moveLine(int8_t sign) { ++ if (sign == -1) { ++ if (term.c.y-- == 0) { ++ if (++term.scr == HISTSIZE) { ++ term.c.y = term.row - 1; ++ term.scr = 0; ++ } else { ++ term.c.y = 0; ++ } ++ } ++ } else { ++ term.c.x = 0; ++ if (++term.c.y == term.row) { ++ if (term.scr-- == 0) { ++ term.c.y = 0; ++ term.scr = HISTSIZE - 1; ++ } else { ++ term.c.y = term.row - 1; ++ } ++ } ++ } ++} ++ ++void moveLetter(int8_t sign) { ++ term.c.x += sign; ++ if (!BETWEEN(term.c.x, 0, term.col-1)) { ++ if (term.c.x < 0) { ++ term.c.x = term.col - 1; ++ moveLine(sign); ++ } else { ++ term.c.x = 0; ++ moveLine(sign); ++ } ++ } ++} ++ ++bool contains (char ksym, char const * values, uint32_t amount) { ++ for (uint32_t i = 0; i < amount; i++) { if (ksym == values[i]) { return true; } } ++ return false; ++} ++ ++ ++void terminateCommand(bool abort) { ++ stateNormalMode.command = defaultNormalMode.command; //< clear command + motion ++ stateNormalMode.motion = defaultNormalMode.motion; ++ selclear(); //< clear selection if any ++ ++ if (!abort) { toggle = !toggle; } ++ empty(currentCommand); ++ ++ printCommandString(); ++ printSearchString(); ++ //tsetdirt(0, term.row-3); ++} ++inline void exitCommand() { terminateCommand(false); } ++inline void abortCommand() { terminateCommand(true); } ++ ++/// Go to next occurrence of string relative to the current location ++/// conduct search, starting at start pos ++bool ++gotoString(int8_t sign) { ++ uint32_t findIndex = 0; ++ uint32_t searchStringSize = size(&searchString); ++ uint32_t const maxIteration = (HISTSIZE + term.row) * term.col + searchStringSize; //< one complete traversal. ++ for (uint32_t cIteration = 0; findIndex < searchStringSize ++ && cIteration ++ < maxIteration; moveLetter(sign)) { ++ uint32_t const searchChar = *((uint32_t*)(sign == 1 ? view(&searchString, findIndex) ++ : viewEnd(&searchString, findIndex))); ++ ++ uint32_t const fu = TLINE(term.c.y)[term.c.x].u; ++ ++ if (fu == searchChar) findIndex++; ++ else findIndex = 0; ++ } ++ bool const found = findIndex == searchStringSize; ++ if (found) { for (uint32_t i = 0; i < searchStringSize; i++) { moveLetter(-sign); } } ++ return found; ++} ++ ++/// Find the next occurrence of a word ++bool ++gotoNextString(int8_t sign) { ++ moveLetter(sign); ++ return gotoString(sign); ++} ++ ++/// Highlight all found strings on the current screen. ++void ++highlightStringOnScreen() { ++ if (isEmpty(&searchString)) { return; } ++ uint32_t const searchStringSize = size(&searchString); ++ uint32_t findIndex = 0; ++ uint32_t xStart, yStart; ++ for (uint32_t y = 0; y < term.row; y++) { ++ for (uint32_t x = 0; x < term.col; x++) { ++ if (TLINE(y)[x].u == *((uint32_t*)(view(&searchString, findIndex)))) { ++ if (findIndex++ == 0) { ++ xStart = x; ++ yStart = y; ++ } ++ if (findIndex == searchStringSize) { ++ // mark selected ++ append(&highlights, &xStart); ++ append(&highlights, &yStart); ++ ++ findIndex = 0; ++ term.dirty[yStart] = 1; ++ } ++ } else { ++ findIndex = 0; ++ } ++ } ++ } ++} ++ ++void gotoStringAndHighlight(int8_t sign) { ++ bool const found = gotoString(sign); //< find the next string to the current position ++ empty(&highlights); //< remove previous highlights ++ if (found) { //< apply new highlights if found ++ //if (sign == -1) { moveLetter(-1); } ++ highlightStringOnScreen(sign); ++ } else { //< go to the position where the search started. ++ applyPosition(&stateNormalMode.motion.searchPosition); ++ } ++ tsetdirt(0, term.row-3); //< repaint everything except for the status bar, which ++ // is painted separately. ++} ++ ++void pressKeys(char const* nullTerminatedString) { ++ size_t end; ++ for (size_t i = 0, end=strlen(nullTerminatedString); i < end; ++i) { ++ if (nullTerminatedString[i] == '\n') { ++ kpressNormalMode(&nullTerminatedString[i], 0, false, true, false); ++ } else { ++ kpressNormalMode(&nullTerminatedString[i], 1, false, false, false); ++ } ++ } ++} ++ ++void executeCommand(DynamicArray const *command) { ++ size_t end; ++ char decoded [32]; ++ for (size_t i = 0, end=size(command); i < end; ++i) { ++ size_t len = utf8encode(*((Rune*)view(command, i)) , decoded); ++ kpressNormalMode(decoded, len, false, false, false); ++ } ++ //kpressNormalMode(NULL, 0, false, true, false); ++} ++ ++void kpressNormalMode(char const * ksym, uint32_t len, bool esc, bool enter, bool backspace) { ++ // [ESC] or [ENTER] abort resp. finish the current operation or ++ // the Normal Mode if no operation is currently executed. ++ if (esc || enter) { ++ if (stateNormalMode.command.op == noop ++ && stateNormalMode.motion.search == none ++ && stateNormalMode.motion.amount == 0) { ++ terminateCommand(!enter); ++ empty(&highlights); ++ tfulldirt(); // < this also removes the search string and the last command. ++ normalMode(NULL); ++ } else { ++ if (enter && stateNormalMode.motion.search != none && !isEmpty(&searchString)) { ++ exitCommand(); //stateNormalMode.motion.finished = true; ++ return; ++ } else { ++ abortCommand(); ++ } ++ } ++ return; ++ } //< ! (esc || enter) ++ // Search: append to search string & conduct search for best hit, starting at start pos, ++ // highlighting all other occurrences on the current page if one is found. ++ if (stateNormalMode.motion.search != none && !stateNormalMode.motion.finished) { ++ int8_t const sign = stateNormalMode.motion.search == forward ? 1 : -1; ++ // Apply start position. ++ if (backspace) { // XXX: if a quantifier is subject to removal, it is currently only removed ++ // from the command string. ++ if (!isEmpty(currentCommand) && !isEmpty(&searchString)) { ++ pop(currentCommand); ++ pop(&searchString); ++ } else if (isEmpty(currentCommand) || isEmpty(&searchString)) { ++ empty(&highlights); ++ stateNormalMode.motion = defaultNormalMode .motion; //< if typed once more than there are ++ selclear(); // letters, the search motion is ++ return; // terminated ++ } ++ applyPosition(&stateNormalMode.motion.searchPosition); ++ } else { ++ if (len > 0) { ++ char* kSearch = checkGetNext(&searchString); ++ utf8decode(ksym, (Rune*)(kSearch), len); ++ ++ char* kCommand = checkGetNext(currentCommand); ++ utf8decode(ksym, (Rune*)(kCommand), len); ++ } ++ } ++ if (sign == -1) { moveLetter(1); } ++ gotoStringAndHighlight(sign); //< go to the next occurrence of the string and highlight ++ // all occurrences currently on screen ++ ++ if (stateNormalMode.command.op == visual) { ++ selextend(term.c.x, term.c.y, term.scr, sel.type, 0); ++ } else if (stateNormalMode.command.op == visualLine) { ++ selextend(term.col-1, term.c.y, term.scr, sel.type, 0); ++ } ++ printCommandString(); ++ printSearchString(); ++ return; ++ } ++ ++ if (len == 0) { return; } ++ // V / v or y take precedence over movement commands. ++ switch(ksym[0]) { ++ case '.': ++ { ++ ++ if (!isEmpty(currentCommand)) { toggle = !toggle; empty(currentCommand); } ++ executeCommand(lastCommand); ++ } ++ return; ++ case 'y': //< Yank mode ++ { ++ char* kCommand = checkGetNext(currentCommand); ++ utf8decode(ksym, (Rune*)(kCommand), len); ++ switch(stateNormalMode.command.op) { ++ case noop: //< Start yank mode & set #op ++ enableMode(yank); ++ selstart(term.c.x, term.c.y, term.scr, 0); ++ empty(currentCommand); ++ break; ++ case visualLine: //< Complete yank operation ++ case visual: ++ xsetsel(getsel()); //< yank ++ xclipcopy(); ++ exitCommand(); //< reset command ++ break; ++ case yank: //< Complete yank operation as in y#amount j ++ selstart(0, term.c.y, term.scr, 0); ++ uint32_t const origY = term.c.y; ++ for (int32_t i = 0; i < MAX(stateNormalMode.motion.amount, 1) - 1; i ++) moveLine(1); ++ selextend(term.col-1, term.c.y, term.scr, SEL_RECTANGULAR, 0); ++ xsetsel(getsel()); ++ xclipcopy(); ++ term.c.y = origY; ++ exitCommand(); ++ } ++ } ++ printCommandString(); ++ printSearchString(); ++ return; ++ case 'v': //< Visual Mode: Toggle mode. ++ case 'V': ++ { ++ enum Operation mode = ksym[0] == 'v' ? visual : visualLine; ++ bool assign = stateNormalMode.command.op != mode; ++ abortCommand(); ++ if (assign) { ++ enableMode(mode); ++ char* kCommand = checkGetNext(currentCommand); ++ utf8decode(ksym, (Rune*)(kCommand), len); ++ if (mode == visualLine) { ++ selstart(0, term.c.y, term.scr, 0); ++ selextend(term.col-1, term.c.y, term.scr, SEL_RECTANGULAR, 0); ++ } else { ++ selstart(term.c.x, term.c.y, term.scr, 0); ++ } ++ } ++ } ++ return; ++ } ++ // Perform the movement. ++ int32_t sign = -1; //< whehter a command goes 'forward' (1) or 'backward' (-1) ++ bool discard = false; //< discard input, as it does not have a meaning. ++ switch(ksym[0]) { ++ case 'j': sign = 1; ++ case 'k': ++ term.c.y += sign * MAX(stateNormalMode.motion.amount, 1); ++ break; ++ case 'H': term.c.y = 0; break; //< [numer]H ~ L[number]j is not supported. ++ case 'M': term.c.y = term.bot / 2; break; ++ case 'L': term.c.y = term.bot; break; //< [numer]L ~ L[number]k is not supported. ++ case 'G': //< a little different from vim, but in this use case the most useful translation. ++ applyPosition(&stateNormalMode.initialPosition); ++ case 'l': sign = 1; ++ case 'h': ++ { ++ int32_t const amount = term.c.x + sign * MAX(stateNormalMode.motion.amount, 1); ++ term.c.x = amount % term.col; ++ while (term.c.x < 0) { term.c.x += term.col; } ++ term.c.y += floor(1.0 * amount / term.col); ++ break; ++ } ++ case '0': ++ if (stateNormalMode.motion.amount == 0) { term.c.x = 0; } ++ else { discard = true; } ++ break; ++ case '$': term.c.x = term.col-1; break; ++ case 'w': ++ case 'W': ++ case 'e': ++ case 'E': sign = 1; ++ case 'B': ++ case 'b': ++ { ++ bool const startSpaceIsSeparator = !(ksym[0] == 'w' || ksym[0] == 'W'); ++ bool const capital = ksym[0] <= 90; //< defines the word separators to use ++ char const * const wDelim = capital ? wordDelimLarge : wordDelimSmall; ++ uint32_t const wDelimLen = strlen(wDelim); ++ bool const performOffset = startSpaceIsSeparator; //< start & end with offset. ++ uint32_t const maxIteration = (HISTSIZE + term.row) * term.col; //< one complete traversal. ++ ++ // doesn't work exactly as in vim, but I think this version is better; ++ // Linebreak is counted as 'normal' separator; hence a jump can span multiple lines here. ++ stateNormalMode.motion.amount = MAX(stateNormalMode.motion.amount, 1); ++ for (; stateNormalMode.motion.amount > 0; stateNormalMode.motion.amount--) { ++ uint8_t state = 0; ++ if (performOffset) { moveLetter(sign); } ++ for (uint32_t cIteration = 0; cIteration ++ < maxIteration; moveLetter(sign)) { ++ if (startSpaceIsSeparator == contains(TLINE(term.c.y)[term.c.x].u, wDelim, wDelimLen)) { ++ if (state == 1) { ++ if (performOffset) { moveLetter(-sign); } ++ break; ++ } ++ } else if (state == 0) { state = 1; } ++ } ++ } ++ break; ++ } ++ case '/': sign = 1; ++ case '?': ++ empty(&searchString); ++ stateNormalMode.motion.search = sign == 1 ? forward : backward; ++ stateNormalMode.motion.searchPosition.x = term.c.x; ++ stateNormalMode.motion.searchPosition.y = term.c.y; ++ stateNormalMode.motion.searchPosition.yScr = term.scr; ++ stateNormalMode.motion.finished = false; ++ break; ++ case 'n': sign = 1; ++ case 'N': ++ toggle = !toggle; ++ empty(currentCommand); ++ if (stateNormalMode.motion.search == none) { ++ stateNormalMode.motion.search = forward; ++ stateNormalMode.motion.finished = true; ++ } ++ for (int32_t amount = MAX(stateNormalMode.motion.amount, 1); amount > 0; amount--) { ++ if (stateNormalMode.motion.search == backward) { sign *= -1; } ++ moveLetter(sign); ++ gotoStringAndHighlight(sign); ++ } ++ break; ++ case 't': ++ if (sel.type == SEL_REGULAR) { ++ sel.type = SEL_RECTANGULAR; ++ } else { ++ sel.type = SEL_REGULAR; ++ } ++ tsetdirt(sel.nb.y, sel.ne.y); ++ discard = true; ++ default: ++ discard = true; ++ } ++ bool const isNumber = len == 1 && BETWEEN(ksym[0], 48, 57); ++ if (isNumber) { //< record numbers ++ discard = false; ++ stateNormalMode.motion.amount = ++ MIN(SHRT_MAX, stateNormalMode.motion.amount * 10 + ksym[0] - 48); ++ } else if (!discard) { ++ stateNormalMode.motion.amount = 0; ++ } ++ ++ if (discard) { ++ for (size_t i = 0; i < amountNormalModeShortcuts; ++i) { ++ if (ksym[0] == normalModeShortcuts[i].key) { ++ pressKeys(normalModeShortcuts[i].value); ++ } ++ } ++ } else { ++ char* kCommand = checkGetNext(currentCommand); ++ utf8decode(ksym, (Rune*)(kCommand), len); ++ ++ int diff = 0; ++ if (term.c.y > 0) { ++ if (term.c.y > term.bot) { ++ diff = term.bot - term.c.y; ++ term.c.y = term.bot; ++ } ++ } else { ++ if (term.c.y < 0) { ++ diff = -term.c.y; ++ term.c.y = 0; ++ } ++ } ++ ++ int const _newScr = term.scr + diff; ++ term.c.y = _newScr < 0 ? 0 : (_newScr >= HISTSIZE ? term.bot : term.c.y); ++ term.scr = mod(_newScr, HISTSIZE); ++ ++ if (!isEmpty(&highlights)) { ++ empty(&highlights); ++ highlightStringOnScreen(); ++ } ++ ++ tsetdirt(0, term.row-3); ++ printCommandString(); ++ printSearchString(); ++ ++ if (stateNormalMode.command.op == visual) { ++ selextend(term.c.x, term.c.y, term.scr, sel.type, 0); ++ } else if (stateNormalMode.command.op == visualLine) { ++ selextend(term.col-1, term.c.y, term.scr, sel.type, 0); ++ } else { ++ if (!isNumber && (stateNormalMode.motion.search == none ++ || stateNormalMode.motion.finished)) { ++ toggle = !toggle; ++ empty(currentCommand); ++ } ++ if (stateNormalMode.command.op == yank) { ++ if (!isNumber && !discard) { ++ // copy ++ selextend(term.c.x, term.c.y, term.scr, sel.mode, 0); ++ xsetsel(getsel()); ++ xclipcopy(); ++ applyPosition(&stateNormalMode.command.startPosition); ++ exitCommand(); ++ } ++ } ++ } ++ } ++} ++ + void + csiparse(void) + { +@@ -1176,6 +1850,10 @@ tmoveto(int x, int y) + term.c.state &= ~CURSOR_WRAPNEXT; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); ++ // Set the last position in order to restore after normal mode exits. ++ stateNormalMode.initialPosition.x = term.c.x; ++ stateNormalMode.initialPosition.y = term.c.y; ++ stateNormalMode.initialPosition.yScr = term.scr; + } + + void +@@ -1282,14 +1960,14 @@ void + tinsertblankline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrolldown(term.c.y, n); ++ tscrolldown(term.c.y, n, 0); + } + + void + tdeleteline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrollup(term.c.y, n); ++ tscrollup(term.c.y, n, 0); + } + + int32_t +@@ -1720,11 +2398,11 @@ csihandle(void) + break; + case 'S': /* SU -- Scroll <n> line up */ + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0]); ++ tscrollup(term.top, csiescseq.arg[0], 0); + break; + case 'T': /* SD -- Scroll <n> line down */ + DEFAULT(csiescseq.arg[0], 1); +- tscrolldown(term.top, csiescseq.arg[0]); ++ tscrolldown(term.top, csiescseq.arg[0], 0); + break; + case 'L': /* IL -- Insert <n> blank lines */ + DEFAULT(csiescseq.arg[0], 1); +@@ -2227,7 +2905,7 @@ eschandle(uchar ascii) + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } +@@ -2240,7 +2918,7 @@ eschandle(uchar ascii) + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { +- tscrolldown(term.top, 1); ++ tscrolldown(term.top, 1, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } +@@ -2458,7 +3136,7 @@ twrite(const char *buf, int buflen, int show_ctrl) + void + tresize(int col, int row) + { +- int i; ++ int i, j; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; +@@ -2495,6 +3173,14 @@ tresize(int col, int row) + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + ++ for (i = 0; i < HISTSIZE; i++) { ++ term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph)); ++ for (j = mincol; j < col; j++) { ++ term.hist[i][j] = term.c.attr; ++ term.hist[i][j].u = ' '; ++ } ++ } ++ + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); +@@ -2552,7 +3238,7 @@ drawregion(int x1, int y1, int x2, int y2) + continue; + + term.dirty[y] = 0; +- xdrawline(term.line[y], x1, y, x2); ++ xdrawline(TLINE(y), x1, y, x2); + } + } + +@@ -2573,8 +3259,8 @@ draw(void) + cx--; + + drawregion(0, 0, term.col, term.row); +- xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], +- term.ocx, term.ocy, term.line[term.ocy][term.ocx]); ++ xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], ++ term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); + term.ocx = cx, term.ocy = term.c.y; + xfinishdraw(); + xximspot(term.ocx, term.ocy); +diff --git a/st.h b/st.h +index 4da3051..7bd8bba 100644 +--- a/st.h ++++ b/st.h +@@ -1,5 +1,6 @@ + /* See LICENSE for license details. */ + ++#include <stdbool.h> + #include <stdint.h> + #include <sys/types.h> + +@@ -10,6 +11,8 @@ + #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) + #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) + #define DEFAULT(a, b) (a) = (a) ? (a) : (b) ++#define INTERVAL(x, a, b) (x) < (a) ? (a) : (x) > (b) ? (b) : (x) ++#define INTERVAL_DIFF(x, a, b) (x) < (a) ? (x) - (a) : (x) > (b) ? (x) - (b) : 0 + #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) + #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ + (a).bg != (b).bg) +@@ -33,6 +36,8 @@ enum glyph_attribute { + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, ++ ATTR_HIGHLIGHT = 1 << 11 | ATTR_UNDERLINE, ++ ATTR_CURRENT = 1 << 12, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +@@ -80,6 +85,14 @@ void die(const char *, ...); + void redraw(void); + void draw(void); + ++int highlighted(int, int); ++int currentLine(int, int); ++void kscrolldown(const Arg *); ++void kscrollup(const Arg *); ++void kpressNormalMode(char const * ksym, uint32_t len, bool esc, bool enter, bool backspace); ++void normalMode(Arg const *); ++void onNormalModeStart(); ++void onNormalModeStop(); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); +@@ -99,8 +112,10 @@ void resettitle(void); + + void selclear(void); + void selinit(void); +-void selstart(int, int, int); +-void selextend(int, int, int, int); ++void selstart(int, int, int, int); ++void xselstart(int, int, int); ++void selextend(int, int, int, int, int); ++void xselextend(int, int, int, int); + int selected(int, int); + char *getsel(void); + +@@ -110,6 +125,8 @@ void *xmalloc(size_t); + void *xrealloc(void *, size_t); + char *xstrdup(char *); + ++ ++ + /* config.h globals */ + extern char *utmp; + extern char *stty_args; +@@ -120,3 +137,13 @@ extern char *termname; + extern unsigned int tabspaces; + extern unsigned int defaultfg; + extern unsigned int defaultbg; ++extern char wordDelimSmall[]; ++extern char wordDelimLarge[]; ++ ++typedef struct NormalModeShortcuts { ++ char key; ++ char *value; ++} NormalModeShortcuts; ++ ++extern NormalModeShortcuts normalModeShortcuts[]; ++extern size_t const amountNormalModeShortcuts; +diff --git a/win.h b/win.h +index a6ef1b9..1a6fefe 100644 +--- a/win.h ++++ b/win.h +@@ -19,6 +19,7 @@ enum win_mode { + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, ++ MODE_NORMAL = 1 << 18, + MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ + |MODE_MOUSEMANY, + }; +@@ -27,6 +28,7 @@ void xbell(void); + void xclipcopy(void); + void xdrawcursor(int, int, Glyph, int, int, Glyph); + void xdrawline(Line, int, int, int); ++void xdrawglyph(Glyph, int, int); + void xfinishdraw(void); + void xloadcols(void); + int xsetcolorname(int, const char *); +diff --git a/x.c b/x.c +index 5828a3b..ccf1751 100644 +--- a/x.c ++++ b/x.c +@@ -136,7 +136,6 @@ typedef struct { + static inline ushort sixd_to_16bit(int); + static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); + static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); +-static void xdrawglyph(Glyph, int, int); + static void xclear(int, int, int, int); + static int xgeommasktogravity(int); + static void ximopen(Display *); +@@ -340,7 +339,7 @@ mousesel(XEvent *e, int done) + break; + } + } +- selextend(evcol(e), evrow(e), seltype, done); ++ xselextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); + } +@@ -444,7 +443,7 @@ bpress(XEvent *e) + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + +- selstart(evcol(e), evrow(e), snap); ++ xselstart(evcol(e), evrow(e), snap); + } + } + +@@ -730,6 +729,19 @@ xloadcolor(int i, const char *name, Color *ncolor) + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); + } + ++void ++normalMode(Arg const *_) //< the argument is just for the sake of ++ // adhering to the function format. ++{ ++ win.mode ^= MODE_NORMAL; //< toggle normal mode via exclusive or. ++ if (win.mode & MODE_NORMAL) { ++ onNormalModeStart(); ++ } else { ++ onNormalModeStop(); ++ } ++} ++ ++ + void + xloadcols(void) + { +@@ -1296,6 +1308,14 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i + base.fg = defaultattr; + } + ++ if (base.mode & ATTR_HIGHLIGHT) { ++ base.bg = highlightBg; ++ base.fg = highlightFg; ++ } else if ((base.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { ++ base.bg = currentBg; ++ base.fg = currentFg; ++ } ++ + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); +@@ -1428,8 +1448,9 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) + Color drawcol; + + /* remove the old cursor */ +- if (selected(ox, oy)) +- og.mode ^= ATTR_REVERSE; ++ if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; ++ if (highlighted(ox, oy)) { og.mode ^= ATTR_HIGHLIGHT; } ++ if (currentLine(ox, oy)) { og.mode ^= ATTR_CURRENT; } + xdrawglyph(og, ox, oy); + + if (IS_SET(MODE_HIDE)) +@@ -1461,6 +1482,11 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) + drawcol = dc.col[g.bg]; + } + ++ if ((g.mode & ATTR_CURRENT) && (win.mode & MODE_NORMAL)) { ++ g.bg = currentBg; ++ g.fg = currentFg; ++ } ++ + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { +@@ -1550,6 +1576,12 @@ xdrawline(Line line, int x1, int y1, int x2) + continue; + if (selected(x, y1)) + new.mode ^= ATTR_REVERSE; ++ if (highlighted(x, y1)) { ++ new.mode ^= ATTR_HIGHLIGHT; ++ } ++ if (currentLine(x, y1)) { ++ new.mode ^= ATTR_CURRENT; ++ } + if (i > 0 && ATTRCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; +@@ -1731,6 +1763,12 @@ kpress(XEvent *ev) + return; + + len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); ++ if (IS_SET(MODE_NORMAL)) { ++ kpressNormalMode(buf, strlen(buf), ++ ksym == XK_Escape, ksym == XK_Return, ksym == XK_BackSpace); ++ return; ++ } ++ + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { +@@ -1870,8 +1908,9 @@ run(void) + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; +- if (handler[ev.type]) ++ if (handler[ev.type]) { + (handler[ev.type])(&ev); ++ } + } + + draw(); +-- +2.24.0 + + +From aae3d1c2e5437a3bd2c79ae0cb2ad6380b10adce Mon Sep 17 00:00:00 2001 +From: Kevin Velghe <kevin@paretje.be> +Date: Mon, 2 Dec 2019 23:25:52 +0100 +Subject: [PATCH 2/2] Merge contribution of paretje: fix underlined text + +https://github.com/juliusHuelsmann/st/issues/13 +--- + st.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/st.h b/st.h +index 7bd8bba..4a78440 100644 +--- a/st.h ++++ b/st.h +@@ -36,8 +36,8 @@ enum glyph_attribute { + ATTR_WRAP = 1 << 8, + ATTR_WIDE = 1 << 9, + ATTR_WDUMMY = 1 << 10, +- ATTR_HIGHLIGHT = 1 << 11 | ATTR_UNDERLINE, +- ATTR_CURRENT = 1 << 12, ++ ATTR_HIGHLIGHT = 1 << 12, ++ ATTR_CURRENT = 1 << 13, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +-- +2.24.0 +