From de020f0c06440fd19a36e1b001ef9e0058f73369 Mon Sep 17 00:00:00 2001 From: Julius Huelsmann 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"; /// +#include +#include +#include +#include + +/// 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 #include #include #include #include +#include #include #include #include @@ -17,8 +19,10 @@ #include #include + #include "st.h" #include "win.h" +#include "dynamicArray.h" #if defined(__linux) #include @@ -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 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 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 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 #include #include @@ -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 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