From 22471790fcaf1505890bcb53752a6df87cfd3592 Mon Sep 17 00:00:00 2001 From: Julius Huelsmann Date: Wed, 12 Feb 2020 19:50:06 +0100 Subject: [PATCH] port: vim patch 2020-02 to current HEAD of st --- Makefile | 9 +- config.def.h | 54 ++++ config.h | 513 +++++++++++++++++++++++++++++++++ dynamicArray.h | 175 ++++++++++++ error.h | 47 ++++ glyph.h | 30 ++ normalMode.c | 750 +++++++++++++++++++++++++++++++++++++++++++++++++ normalMode.h | 36 +++ st.c | 249 +++++++++------- st.h | 36 ++- term.h | 73 +++++ win.h | 2 + x.c | 48 +++- 13 files changed, 1894 insertions(+), 128 deletions(-) create mode 100644 config.h create mode 100644 dynamicArray.h create mode 100644 error.h create mode 100644 glyph.h create mode 100644 normalMode.c create mode 100644 normalMode.h create mode 100644 term.h diff --git a/Makefile b/Makefile index 470ac86..af8f0be 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ include config.mk -SRC = st.c x.c +SRC = st.c x.c normalMode.c OBJ = $(SRC:.c=.o) all: options st @@ -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 normalMode.h term.h glyph.h error.h +x.o: arg.h config.h st.h win.h dynamicArray.h normalMode.h term.h glyph.h error.h $(OBJ): config.h config.mk @@ -35,7 +35,8 @@ 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\ + normalMode.h term.h error.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 546edda..92b541d 100644 --- a/config.def.h +++ b/config.def.h @@ -149,6 +149,14 @@ 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 (search +/// results currently on screen) [Vim Browse]. +static unsigned int highlightBg = 160; +static unsigned int highlightFg = 15; +/// Colors for highlighting the current cursor position (row + col) in normal +/// mode [Vim Browse]. +static unsigned int currentBg = 8; +static unsigned int currentFg = 15; /* * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). @@ -170,10 +178,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} }, @@ -186,6 +196,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} }, }; /* @@ -457,3 +469,45 @@ static char ascii_printable[] = " !\"#$%&'()*+,-./0123456789:;<=>?" "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" "`abcdefghijklmnopqrstuvwxyz{|}~"; + + +/// word sepearors normal mode +/// [Vim Browse]. +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; +char wordDelimLarge[] = " \t"; /// 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SEL_RECTANGULAR] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + + +/// word sepearors normal mode +/// [Vim Browse]. +char wordDelimSmall[] = " \t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; +char wordDelimLarge[] = " \t"; /// +#include +#include +#include + +/// Struct for which this file offers functionality in order to expand the array +/// and set / get its content. +typedef struct DynamicArray { + /// Size of the datatype contained in the array. + uint8_t itemSize; + /// Amount of bytes currently initialized + uint32_t index; + /// Amount of bytes currently reserved (not necessarily initialized) + uint32_t allocated; + /// Actual content. + 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 + +/// Check that at least \p bytes are allocated, if true implying that +/// \p s->content[\bytes - 1] is allocated. +static inline bool +isAllocated(DynamicArray const *s, uint32_t bytes) { + return s != NULL && s->allocated >= bytes; +} + +/// @see #isAllocated +static inline bool +isInitialized(DynamicArray const *s, uint32_t bytes) { + return s != NULL && s->index >= bytes; +} + +/// Return the next element in \p s and increment index without checking bounds. +static inline char* +gnext(DynamicArray *s) { + ENSURE(s!=NULL, return NULL); + ENSURE(s->index % s->itemSize == 0 && "(index not aligned)", + s->index += s->itemSize - (s->index % s->itemSize)); + ENSURE(isAllocated(s, s->index + 2 * s->itemSize), return NULL); + return s->content + (s->index += s->itemSize); +} + +/// View element \p i in \p s. +static inline char* +view(DynamicArray const * s, uint32_t i) { + ENSURE((s != NULL) && isAllocated(s, (i+1) * s->itemSize), return NULL); + return s->content + i*s->itemSize; +} + +/// Inspect element content[size() - 1 - i]. +static inline char * +viewEnd(DynamicArray const *s, uint32_t i) { + ENSURE((s != NULL) && isInitialized(s, i * s->itemSize), return NULL); + ENSURE(s->index%s->itemSize == 0 && "(index not aligned)", return NULL); + return s->content + s->index - (i + 1) * s->itemSize; +} + +/// Set conent without applying +static inline bool +setValues(DynamicArray* s, char const *vals, uint32_t amount) { + ENSURE(vals != NULL, return false); + ENSURE((s != NULL) && isAllocated(s, s->index + amount), return false); + memcpy(s->content + s->index, vals, amount); + return true; +} + +static inline bool +snext(DynamicArray* s, char const *vals, uint32_t amount) { + bool const success = setValues(s, vals, amount); + ENSURE(success, return false); + uint8_t const rest = amount % s->itemSize; + uint32_t const newSize = s->index + amount + (rest ? s->itemSize : 0); + ENSURE(isAllocated(s, newSize), return false); + s->index = newSize; + return true; +} + +/// Empty \p s. +static inline void +empty(DynamicArray* s) { + ENSURE((s != NULL), return); + s->index = 0; +} + +/// Check if \p s has initialized content (which can be the case even if memory +/// is allocated). +static inline bool +isEmpty(DynamicArray const * s) { + ENSURE((s != NULL), return true); + return s->index == 0; +} + +static inline int +size(DynamicArray const * s) { + ENSURE(s != NULL, return 0); + ENSURE(s->itemSize != 0, return 0); + return s->index / s->itemSize; +} + +static inline void +pop(DynamicArray* s) { + ENSURE((s != NULL), return); + ENSURE(s->index % s->itemSize == 0 && "(index not aligned)", + s->index += s->itemSize - (s->index % s->itemSize)); + ENSURE(isInitialized(s, s->itemSize), return); + s->index -= s->itemSize; +} + +static inline bool +checkSetNext(DynamicArray *s, char const *c, uint32_t amount) { + ENSURE(s != NULL && c != NULL, return false); + if (s->allocated < s->index + s->itemSize * amount) { + uint32_t const diff = s->index+s->itemSize*amount-s->allocated; + uint32_t const newAlloSize = s->allocated + (diff > EXPAND_STEP + ? diff : EXPAND_STEP) * s->itemSize; + char* tmp = realloc(s->content, newAlloSize); + if (tmp == NULL) { return false; } + s->allocated = newAlloSize; + s->content = tmp; + assert(s->allocated >= s->index + s->itemSize * amount); + } + if (amount) { snext(s, c, amount); } + return true; +} + +static inline bool +checkSetNextV(DynamicArray *s, char const c) { + return checkSetNext(s, &c, 1); +} + +static inline bool +checkSetNextP(DynamicArray *s, char const *c) { + ENSURE(c != NULL, return false); + return checkSetNext(s, c, strlen(c)); +} + +/// Expand the currently initialized content in \p s and the allocated chunk of +/// memory if required. +static char * +expand(DynamicArray *s) { + ENSURE(s != NULL, return NULL); + if (s->allocated < s->index + s->itemSize) { + uint32_t const diff = s->index + s->itemSize - s->allocated; + uint32_t const newAlloSize = s->allocated + (diff > EXPAND_STEP + ? diff : EXPAND_STEP) * s->itemSize; + char* tmp = realloc(s->content, newAlloSize); + if (tmp == NULL) { return NULL; } + s->allocated = newAlloSize; + s->content = tmp; + assert(s->allocated >= s->index + s->itemSize); + } + 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)) + + +#endif // DYNAMIC_ARRAY_H diff --git a/error.h b/error.h new file mode 100644 index 0000000..91c621f --- /dev/null +++ b/error.h @@ -0,0 +1,47 @@ +#ifndef ERROR_H +#define ERROR_H + +#include + +// Flag which determines whether to fail if a required condition is not met, or +// to adapt the condition in order to work properly. +// Attention: Be sure to perform a clean build after you alter preprocessor +// directives / definitions. +//#define FAIL_ON_ERROR + +#include + +/// +/// Function used in case the fail-on-error mode is disabled (via definition) +/// to report errors. In debug production mode, alias st to st 2> error.log. +static void reportError(char const * cond, char const * stt, char const * file, + unsigned int line ) { + unsigned int const maxErrorCount = 100; + static unsigned int errorCount = 0; + if (++errorCount == 1) { + printf("Report the following bug to " + "https://github.com/juliusHuelsmann/st.\n"); + } + if (errorCount < maxErrorCount) { + printf("Bug:\tCondition '%s' evaluates to false.\n\tPerforming" + " '%s' to counteract.\n\tFile:%s:%u\n", + cond, stt, file, line); + } else if (errorCount == maxErrorCount) { + printf("Max amount of reported errors %u is reached. From here" + "on, no additional errors will be reported.\n", + maxErrorCount); + } +} + +/// Note that everyting condition checked / endforced with #ENSURE is +/// considered an error, and behaves like an error depending on the flag. +#ifdef FAIL_ON_ERROR +#define ENSURE(cond, stt) assert(cond); +#else // FAIL_ON_ERROR +#define ENSURE(cond, stt) if (!(cond)) { \ + reportError(#cond, #stt, __FILE__, __LINE__); \ + stt; \ + } +#endif // FAIL_ON_ERROR + +#endif // ERROR_H diff --git a/glyph.h b/glyph.h new file mode 100644 index 0000000..84aa252 --- /dev/null +++ b/glyph.h @@ -0,0 +1,30 @@ +#ifndef LINE_H +#define LINE_H + +// +// Contains the representation of the entities in the buffer (Line, Gylph), that +// is used by every part of the software implmeneting terminal logic. +// + +#include + +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + +typedef uint_least32_t Rune; + +#define Glyph Glyph_ + +typedef struct { + Rune u; /* character code */ + unsigned short mode; /* attribute flags */ + uint32_t fg; /* foreground */ + uint32_t bg; /* background */ +} Glyph; + + +typedef Glyph *Line; + +#endif // LINE_H diff --git a/normalMode.c b/normalMode.c new file mode 100644 index 0000000..59a5a89 --- /dev/null +++ b/normalMode.c @@ -0,0 +1,750 @@ +/* See LICENSE for license details. */ +#include "normalMode.h" +#include "dynamicArray.h" +#include "term.h" +#include "win.h" +#include "error.h" + +#include +#include + +#include +#include +#include +#include + +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define FALLTHROUGH __attribute__((fallthrough)); +#define SEC(var,ini,h,r) var = ini; if (!var) { h; return r; } +#define EXPAND(v1,v2,r) char *SEC(v1, expand(v2), empty(v2), true) +#define currentCommand (toggle ? &commandHist0 : &commandHist1) +#define lastCommand (toggle ? &commandHist1 : &commandHist0) + +// +// Interface to the terminal +extern Glyph const styleCommand, styleSearch; +extern NormalModeShortcuts normalModeShortcuts[]; +extern size_t const amountNormalModeShortcuts; +extern char wordDelimSmall[]; +extern char wordDelimLarge[]; +extern unsigned int fgCommandYank, fgCommandVisual, fgCommandVisualLine, + bgCommandYank, bgCommandVisual, bgCommandVisualLine, bgPos, fgPos; + +extern void selclear(void); +extern void tsetdirt(int, int); +extern size_t utf8encode(Rune, char *); +extern size_t utf8decode(const char *, Rune *, size_t); +extern size_t utf8decodebyte(char c, size_t *i); + +extern void selextend(int, int, int, int, int); +extern void selstart(int, int, int, int); +extern char *getsel(void); +extern void tfulldirt(void); + +// +// `Private` structs +typedef struct { uint32_t x; uint32_t y; uint32_t yScr; } Position; + +/// Entire normal mode state, consisting of an operation and a motion. +typedef struct { + Position initialPosition; + struct OperationState { + enum Operation { + noop = ' ', visual='v', visualLine='V', yank = 'y' } op; + Position startPosition; + enum Infix { infix_none = 0, infix_i = 1, infix_a = 2, } infix; + } command; + struct MotionState { + uint32_t amount; + enum Search {none, forward, backward} search; + Position searchPosition; + bool finished; + } motion; +} NormalModeState; + +/// Default state if no operation is performed. +NormalModeState defaultNormalMode = { + {0,0,0}, {noop, {0, 0, 0}, false}, {0, none, {0, 0, 0}, true} +}; +NormalModeState stateVB = { + {0,0,0}, {noop, {0, 0, 0}, false}, {0, none, {0, 0, 0}, true} +}; + +DynamicArray searchString = UTF8_ARRAY; +DynamicArray commandHist0 = UTF8_ARRAY; +DynamicArray commandHist1 = UTF8_ARRAY; +DynamicArray highlights = DWORD_ARRAY; + +/// History command toggle +static bool toggle = false; + +// +// Utility functions +static inline int intervalDiff(int v, int a, int b) { + return (v < a) ? (v - a) : ((v > b) ? (v - b) : 0); +} +static inline void swap(DynamicArray *const a, DynamicArray *const b) { + DynamicArray tmp = *a; *a = *b; *b = tmp; +} +static inline int max(int a, int b) { return a > b ? a : b; } +static inline int min(int a, int b) { return a < b ? a : b; } +static inline int mod(int a, int b) { for (; a < 0; a += b); return a % b; } +static inline bool contains (char c, char const * values, uint32_t memSize) { + ENSURE(values != NULL, return false); + for (uint32_t i = 0; i < memSize; ++i) if (c == values[i]) return true; + return false; +} +static inline void applyPosition(Position const *pos) { + ENSURE(pos != NULL, return); + term.c.x = pos->x; + term.c.y = pos->y; + term.scr = pos->yScr; +} +static inline int getSearchDirection(void) { + return stateVB.motion.search == forward ? 1 : -1; +} + +// Utilities for working with the current version of the scrollback patch. +static bool moveLine(int32_t const amount) { + int32_t const reqShift = intervalDiff(term.c.y+=amount, 0, term.row-1); + term.c.y -= reqShift; + int32_t const sDiff = intervalDiff(term.scr-=reqShift, 0, HISTSIZE-1); + term.scr -= sDiff; + return sDiff == 0; +} + +static void moveLetter(int32_t const amount) { + int32_t value = (term.c.x += amount) / term.col; + if (value -= (term.c.x < 0)) { + term.c.x = moveLine(value) ? mod(term.c.x, term.col) + : max(min(term.c.x,term.col - 1), 0); + } + assert(BETWEEN(term.c.x,0,term.col-1)&&BETWEEN(term.c.y,0,term.row-1)); +} + +// +// `Private` functions: + +// Functions: Temporarily display string on screen. + +/// Display string at end of a specified line without writing it into the buffer +/// @param str string that is to be displayed +/// @param g glyph +/// @param yPos +static void +displayString(DynamicArray const *str, Glyph const *g, int yPos, bool prePos) { + ENSURE((str != NULL) && (g != NULL) && (term.row > 0), return); + ENSURE(yPos >= 0, yPos = 0); + ENSURE(yPos < term.row, yPos = term.row - 1); + // Arbritary limit to avoid withhelding too much info from user. + int const maxFractionOverridden = 3; + // Threshold: if there is no space to print, do not print, but transfer + // repsonsibility for printing back to [st]. + if (term.col < maxFractionOverridden) { // (0) + term.dirty[yPos] = 1; + return; + } + int32_t const botSz = prePos * 6; //< sz for position indication + // Determine the dimensions of used chunk of screen. + int32_t const overrideSize = min(size(str) + botSz, + term.col / maxFractionOverridden); // (1) + int32_t const overrideEnd = term.col - 2; + // Has to follow trivially hence th assert: + // overrideSize <(1)= term.col/3 <(0)= term.col = overrideEnd + 1. + assert(overrideSize <= overrideEnd + 1); + int32_t const overrideStart = 1 + overrideEnd - overrideSize; + // display history[history.size() - (overrideSize - botSz)::-1] + Glyph *SEC(line, malloc(sizeof(Glyph) * (overrideSize)),,) + int32_t offset = (size(str) - overrideSize - 1 + botSz) * str->itemSize; + for (uint32_t chr = 0; chr < overrideSize - botSz; ++chr) { + line[chr] = *g; + line[chr].u = *((Rune*) (str->content+(offset+=str->itemSize))); + } + if (prePos) { + ENSURE(term.scr < HISTSIZE, term.scr = HISTSIZE - 1); + int32_t const p=round((HISTSIZE-1-term.scr)*100./(HISTSIZE-1)); + char prc [8]; + switch (term.scr) { + case HISTSIZE - 1: strcpy(prc, " [TOP]"); break; + case 0: strcpy(prc, " [BOT]"); break; + default: sprintf(prc, " % 3d%c ", p, '%'); + } + for (uint32_t chr = 0; chr < botSz; ++chr) { + line[chr + overrideSize - botSz] =*g; + line[chr + overrideSize - botSz].fg = fgPos; + line[chr + overrideSize - botSz].bg = bgPos; + utf8decode(&prc[chr],&line[chr+overrideSize-botSz].u,1); + } + line[overrideSize - botSz] =*g; + } + xdrawline(TLINE(yPos), 0, yPos, overrideStart); + term.c.y -= term.row; term.c.x -= term.col; // not highlight hack + xdrawline(line-overrideStart, overrideStart, yPos, overrideEnd + 1); + term.c.y += term.row; term.c.x += term.col; + free(line); +} + +static inline void printCommandString(void) { + Glyph g = styleCommand; + switch(stateVB.command.op) { + case yank: g.fg = fgCommandYank; g.bg = bgCommandYank; break; + case visual: g.fg=fgCommandVisual; g.bg=bgCommandVisual; break; + case visualLine: g.fg=fgCommandVisualLine; + g.bg=bgCommandVisualLine; + } + displayString(isEmpty(currentCommand) ? lastCommand : currentCommand, + &g, term.row - 1, true); +} + +static inline void printSearchString(void) { + displayString(&searchString, &styleSearch, term.row - 2, false); +} + +// NormalMode Operation / Motion utilies. + +static inline bool isMotionFinished(void) { return stateVB.motion.finished; } + +static inline void finishMotion(void) { stateVB.motion.finished = true; } + +static inline bool isOperationFinished(void) { + return stateVB.command.op==noop && stateVB.command.infix==infix_none; +} + +/// Register that the current comamnd is finished and a new command is lgoged +static inline void startNewCommand(bool abort) { + if (!abort) { toggle = !toggle; } + empty(currentCommand); +} + +static inline void finishOperation(void) { + stateVB.command = defaultNormalMode.command; + assert(isOperationFinished()); + // After an operation is finished, the selection has to be released and + // no highlights are to be released. + selclear(); + empty(&highlights); + // THe command string is reset for a new command. + startNewCommand(true); +} + +static inline void enableOperation(enum Operation o) { + finishOperation(); + stateVB.command.op = o; + stateVB.command.infix = infix_none; + stateVB.command.startPosition.x = term.c.x; + stateVB.command.startPosition.y = term.c.y; + stateVB.command.startPosition.yScr = term.scr; +} + +/// @param abort: If enabled, the command exits without registering +/// @return Whether the the application is ready to yield control back to +//the normal command flow +static bool terminateCommand(bool abort) { + bool const exitOperation = isMotionFinished(); + bool exitNormalMode = false; + finishMotion(); + + if (exitOperation) { + exitNormalMode = isOperationFinished(); + finishOperation(); + } + printCommandString(); + printSearchString(); + return exitNormalMode; +} + +static inline void exitCommand(void) { terminateCommand(false); } + +static inline void abortCommand(void) { terminateCommand(true); } + +/// Go to next occurrence of string relative to the current location +/// conduct search, starting at start pos +static bool gotoString(int8_t sign) { + moveLetter(sign); + uint32_t const searchStrSize = size(&searchString); + uint32_t const maxIter = (HISTSIZE+term.row) * term.col + searchStrSize; + uint32_t findIdx = 0; + for (uint32_t cIteration = 0; findIdx < searchStrSize + && ++cIteration <= maxIter; moveLetter(sign)) { + char const * const SEC(next, sign==1 + ? view(&searchString, findIdx) + : viewEnd(&searchString, findIdx), , false) + uint32_t const searchChar = *((uint32_t*) next); + + if (TLINE(term.c.y)[term.c.x].u == searchChar) { ++findIdx; } + else { findIdx = 0; } + } + bool const found = findIdx == searchStrSize; + for (uint32_t i = 0; found && i < searchStrSize; ++i) moveLetter(-sign); + return found; +} + +/// Highlight all found strings on the current screen. +static void highlightStringOnScreen(void) { + if (isEmpty(&searchString)) { return; } + empty(&highlights); + uint32_t const searchStringSize = size(&searchString); + uint32_t findIdx = 0; + uint32_t xStart, yStart; + bool success = true; + for (int y = 0; y < term.row && success; y++) { + for (int x = 0; x < term.col && success; x++) { + char const* const SEC(next, + view(&searchString,findIdx),,) + if (TLINE(y)[x].u == (Rune) *((uint32_t*)(next))) { + if (++findIdx == 1) { + xStart = x; + yStart = y; + } + if (findIdx == searchStringSize) { + success = success + && append(&highlights, &xStart) + && append(&highlights, &yStart); + findIdx = 0; //term.dirty[yStart] = 1; + } + } else { findIdx = 0; } + } + } + if (!success) { empty(&highlights); } +} + +static bool gotoStringAndHighlight(int8_t sign) { + // Find hte next occurrence of the #searchString in direction #sign + bool const found = gotoString(sign); + if (!found) { applyPosition(&stateVB.motion.searchPosition); } + highlightStringOnScreen(); + //tsetdirt(0, term.row-3); //< everything except for the 'status bar' + return found; +} + +static bool pressKeys(char const* nullTerminatedString, size_t end) { + bool sc = true; + for (size_t i = 0; i < end && sc; ++i) { + sc = kpressNormalMode(&nullTerminatedString[i], 1, false, NULL); + } + return sc; +} + +static bool executeCommand(DynamicArray const *command) { + size_t end=size(command); + char decoded [32]; + bool succ = true; + size_t len; + for (size_t i = 0; i < end && succ; ++i) { + char const *const SEC(nextRune, view(command, i),,false) + len = utf8encode(*((Rune *) nextRune), decoded); + succ = kpressNormalMode(decoded, len, false, NULL); + } + return succ; +} + +struct { char const first; char const second; } const Brackets [] = +{ {'(', ')'}, {'<', '>'}, {'{', '}'}, {'[', ']'}, }; + + +/// Emits Command prefix and suffix when i motion is performed (e.g. yiw). +/// +/// @param c: motion character +/// @param expandMode: 1 for 'i', 2 for 'a' +/// @param first, second: Dynamic arrays in which the prefix and postfix +/// commands will be returned +/// @return whether the command could be extracted successfully. +static bool expandExpression(char const c, enum Infix expandMode, + char operation, DynamicArray *cmd) { + empty(cmd); + bool s = true; //< used in order to detect memory allocation errors. + char const lower = tolower(c); + // Motions + if (lower == 'w') { + // translated into wb[command]e resp. WB[command]E, which works + // file even when at the fist letter. Does not work for single + // letter words though. + int const diff = c - lower; + s = s && checkSetNextV(cmd, c); + s = s && checkSetNextV(cmd, (signed char)(((int)'b') + diff)); + s = s && checkSetNextV(cmd, operation); + s = s && checkSetNextV(cmd, (signed char)(((int)'e')+ diff)); + return s; + } + // Symmetrical brackets (quotation marks) + if (c == '\'' || c == '"') { + // Local ambiguity -> do nothing. It cannot be determined if + // the current char is the 1st or last char of the selection. + // <---- search here? -- ['] -- or search here? ---> + if (TLINE(term.c.y)[term.c.x].u == c) { + return false; + } + // Prefix + char res [] = {'?', c, '\n'}; + s = s && checkSetNextP(cmd, res); + // infix + bool const iffy = expandMode == infix_i; + if (iffy) { s = s && checkSetNextV(cmd, 'l'); } + s = s && checkSetNextV(cmd, operation); + if (!iffy) { s = s && checkSetNextV(cmd, 'l'); } + // suffix + res[0] = '/'; + s = s && checkSetNextP(cmd, res); + if (iffy) { s = s && checkSetNextV(cmd, 'h'); } + return s; + } + // Brackets: Does not if in range / if the brackets belong togehter. + for (size_t pid = 0; pid < sizeof(Brackets); ++pid) { + if(Brackets[pid].first == c || Brackets[pid].second == c) { + if (TLINE(term.c.y)[term.c.x].u!=Brackets[pid].first) { + s = s && checkSetNextV(cmd, '?'); + s = s && checkSetNextV(cmd, Brackets[pid].first); + s = s && checkSetNextV(cmd, '\n'); + } + bool const iffy = expandMode == infix_i; + if (iffy) { s = s && checkSetNextV(cmd, 'l'); } + s = s && checkSetNextV(cmd, operation); + if (!iffy) { s = s && checkSetNextV(cmd, 'l'); } + s = s && checkSetNextV(cmd, '/'); + s = s && checkSetNextV(cmd, Brackets[pid].second); + s = s && checkSetNextV(cmd, '\n'); + if (iffy) { s = s && checkSetNextV(cmd, 'h'); } + return s; + } + } + /**/ + // search string + // complicated search operation: + if (c == 't') { + // XXX: (Bug in vim: @vit ) + // [current pos] + + // 1. Copy history ( tag := hist[?<\n:/ \n] ) + // 2. Copy history ( first_find := hist[?<\n: next place in + // history where count '>' > count '<' + // (can be behind current pos) ) + // 3. first := [?first_find][#first_ind]l + // second:= [/tag">"]h + //return true; // XXX: not implmented yet. + } + return false; +} + +// +// Public API +// + +void onMove(void) { + stateVB.initialPosition.x = term.c.x; + stateVB.initialPosition.y = term.c.y; + stateVB.initialPosition.yScr = term.scr; +} + +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) { + xMin += term.col; + --yMin; + } + if (xMin < 0) { xMin = 0; } + + uint32_t highSize = size(&highlights); + ENSURE(highSize % 2 == 0, empty(&highlights); return false;); + highSize /= 2; + uint32_t *ptr = (uint32_t*) highlights.content; + for (uint32_t i = 0; i < highSize; ++i) { + int32_t const sx = (int32_t) *(ptr++); + int32_t const sy = (int32_t) *(ptr++); + if (BETWEEN(sy, yMin, y) && (sy != yMin || sx > xMin) + && (sy != y || sx <= x)) { + return true; + } + } + return false; +} + +ExitState kpressNormalMode(char const * cs, int len, bool ctrl, void const *v) { + KeySym const * const ksym = (KeySym*) v; + bool const esc = ksym && *ksym == XK_Escape; + bool const enter = (ksym && *ksym==XK_Return) || (len==1 &&cs[0]=='\n'); + bool const quantifier = len == 1 && (BETWEEN(cs[0], 49, 57) + || (cs[0] == 48 && stateVB.motion.amount)); + int const previousScroll = term.scr; + // [ESC] or [ENTER] abort resp. finish the current level of operation. + // Typing 'i' if no operation is currently performed behaves like ESC. + if (esc || enter || (len == 1 && cs[0] == 'i' && isMotionFinished() + && isOperationFinished())) { + if (terminateCommand(!enter)) { + applyPosition(&stateVB.initialPosition); + Position const pc = stateVB.initialPosition; + stateVB = defaultNormalMode; + stateVB.initialPosition = pc; + tfulldirt(); + return finished; + } + len = 0; + goto motionFinish; + } + // Backspace + if (ksym && *ksym == XK_BackSpace) { + bool s = stateVB.motion.search!=none&&!stateVB.motion.finished; + bool q = stateVB.motion.amount != 0; + if (!(s || q)) { return failed; } + len = 0; + + if (!isEmpty(currentCommand)) { pop(currentCommand); } + if (s) { + if (!isEmpty(&searchString)) { pop(&searchString); } + else if (isEmpty(&searchString)) { + exitCommand(); + return success; + } + } else if (q) { + stateVB.motion.amount /= 10; + goto finishNoAppend; + } + } + + // Search: append to search string, then search & highlight + if (stateVB.motion.search != none && !stateVB.motion.finished) { + if (len >= 1) { + EXPAND(kSearch, &searchString, true) + utf8decode(cs, (Rune*)(kSearch), len); + } + applyPosition(&stateVB.motion.searchPosition); + gotoStringAndHighlight(getSearchDirection()); + goto finish; + } + if (len == 0) { return failed; } + // Quantifiers + if (quantifier) { + stateVB.motion.amount = min(SHRT_MAX, + stateVB.motion.amount * 10 + cs[0] - 48); + goto finish; + } + // 'i' mode enabled, hence the expression is to be expanded: + // [start_expression(cs[0])] [operation] [stop_expression(cs[0])] + if (stateVB.command.infix != infix_none && stateVB.command.op != noop) { + DynamicArray cmd = CHAR_ARRAY; + char const operation = stateVB.command.op; + bool succ = expandExpression(cs[0], + stateVB.command.infix, visual, &cmd); + if (operation == yank) { + succ = succ && checkSetNextV(&cmd, operation); + } + NormalModeState const st = stateVB; + TCursor const tc = term.c; + stateVB.command.infix = infix_none; + if (succ) { + stateVB.command.op = noop; + for (int i = 0; i < size(&cmd) && succ; ++i) { + succ = pressKeys(&cmd.content[i], 1); + } + if (!succ) { // go back to the old position, apply op + stateVB = st; + term.c = tc; + } + empty(currentCommand); + for (uint32_t i = 0; i < size(&cmd); ++i) { + EXPAND(kCommand, currentCommand, true) + utf8decode(cmd.content+i, (Rune*)(kCommand),1); + } + } + free(cmd.content); + goto finishNoAppend; + } + // Commands (V / v or y) + switch(cs[0]) { + case '.': + { + if (isEmpty(currentCommand)) { toggle = !toggle; } + DynamicArray cmd = UTF8_ARRAY; + swap(&cmd, currentCommand); + executeCommand(&cmd) ? success : failed; + swap(&cmd, currentCommand); + free(cmd.content); + goto finishNoAppend; + } + case 'i': stateVB.command.infix = infix_i; goto finish; + case 'a': stateVB.command.infix = infix_a; goto finish; + case 'y': + switch(stateVB.command.op) { + case noop: //< Start yank mode & set #op + enableOperation(yank); + selstart(term.c.x, term.c.y,term.scr,0); + goto finish; + case yank: //< Complete yank [y#amount j] + selstart(0, term.c.y, term.scr, 0); + int const origY = term.c.y; + moveLine(max(stateVB.motion.amount, 1)); + selextend(term.col-1,term.c.y,term.scr, + SEL_RECTANGULAR, 0); + term.c.y = origY; + FALLTHROUGH + case visualLine: // Yank visual selection + case visual: + xsetsel(getsel()); + xclipcopy(); + exitCommand(); + goto finish; + default: + return failed; + } + case visual: + case visualLine: + if (stateVB.command.op == cs[0]) { + finishOperation(); + return true; + } else { + enableOperation(cs[0]); + selstart(cs[0] == visualLine ? 0 : term.c.x, + term.c.y, term.scr, 0); + goto finish; + } + } + // CTRL Motions + int32_t sign = -1; //< if command goes 'forward'(1) or 'backward'(-1) + if (ctrl) { + if (ksym == NULL) { return false; } + switch(*ksym) { + case XK_f: + term.scr = max(term.scr - max(term.row-2,1), 0); + term.c.y = 0; + goto finish; + case XK_b: + term.scr = min(term.scr + max(term.row - 2, 1), + HISTSIZE - 1); + term.c.y = term.bot; + goto finish; + case XK_u: + term.scr = min(term.scr+term.row/2, HISTSIZE-1); + goto finish; + case XK_d: + term.scr = max(term.scr - term.row / 2, 0); + goto finish; + default: return false; + } + } + // Motions + switch(cs[0]) { + case 'c': empty(&commandHist0); empty(&commandHist1); + goto finishNoAppend; + case 'j': sign = 1; FALLTHROUGH + case 'k': moveLine(max(stateVB.motion.amount,1) * sign); + goto motionFinish; + case 'H': term.c.y = 0; + goto motionFinish; + case 'M': term.c.y = term.bot / 2; + goto motionFinish; + case 'L': term.c.y = term.bot; + goto motionFinish; + case 'G': applyPosition(&stateVB.initialPosition); + goto motionFinish; + case 'l': sign = 1; FALLTHROUGH + case 'h': moveLetter(sign * max(stateVB.motion.amount,1)); + goto motionFinish; + case '0': term.c.x = 0; + goto motionFinish; + case '$': term.c.x = term.col-1; + goto motionFinish; + case 'w': FALLTHROUGH + case 'W': FALLTHROUGH + case 'e': FALLTHROUGH + case 'E': sign = 1; FALLTHROUGH + case 'B': FALLTHROUGH + case 'b': { + char const * const wDelim = + cs[0] <= 90 ? wordDelimLarge : wordDelimSmall; + uint32_t const wDelimLen = strlen(wDelim); + + bool const startSpaceIsSeparator = + !(cs[0] == 'w' || cs[0] == 'W'); + // Whether to start & end with offset: + bool const performOffset = startSpaceIsSeparator; + // Max iteration := One complete hist traversal. + uint32_t const maxIter = (HISTSIZE+term.row) * term.col; + // Doesn't work exactly as in vim: Linebreak is + // counted as 'normal' separator, hence a jump can + // span multiple lines here. + stateVB.motion.amount = max(stateVB.motion.amount, 1); + for (;stateVB.motion.amount>0;--stateVB.motion.amount) { + uint8_t state = 0; + if (performOffset) { moveLetter(sign); } + for (uint32_t cIt = 0; cIt ++ < maxIter; 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; } + } + } + goto motionFinish; + } + case '/': sign = 1; FALLTHROUGH + case '?': + empty(&searchString); + stateVB.motion.search = sign == 1 ? forward : backward; + stateVB.motion.searchPosition.x = term.c.x; + stateVB.motion.searchPosition.y = term.c.y; + stateVB.motion.searchPosition.yScr = term.scr; + stateVB.motion.finished = false; + goto finish; + case 'n': sign = 1; FALLTHROUGH + case 'N': { + if (stateVB.motion.search == none) return failed; + if (stateVB.motion.search == backward) { sign *= -1; } + bool b = true; int ox = term.c.x; + int oy = term.c.y ; int scr = term.scr; + int32_t i = max(stateVB.motion.amount, 1); + for (;i>0 && (b=gotoString(sign)); --i) { + oy = term.c.y; scr = term.scr; + } + if (!b) { term.c.x = ox; term.c.y = oy; term.scr = scr;} + goto motionFinish; + } + case 't': // Toggle selection mode and set dirt. + sel.type = sel.type == SEL_REGULAR + ? SEL_RECTANGULAR : SEL_REGULAR; + //tsetdirt(sel.nb.y, sel.ne.y); + goto motionFinish; + } + // Custom commands + for (size_t i = 0; i < amountNormalModeShortcuts; ++i) { + if (cs[0] == normalModeShortcuts[i].key) { + return pressKeys(normalModeShortcuts[i].value, + strlen(normalModeShortcuts[i].value)) + ? success : failed; + } + } + return failed; +motionFinish: + stateVB.motion.amount = 0; + //if (isMotionFinished() && stateVB.command.op == yank) { + if (stateVB.command.op == yank) { + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); + xsetsel(getsel()); + xclipcopy(); + exitCommand(); + } +finish: + if (len == 1 && !ctrl) { // XXX: for now. + EXPAND(kCommand, currentCommand, true) + utf8decode(cs, (Rune*)(kCommand), len); + } +finishNoAppend: + if (stateVB.command.op == visual) { + selextend(term.c.x, term.c.y, term.scr, sel.type, 0); + } else if (stateVB.command.op == visualLine) { + selextend(term.col-1, term.c.y, term.scr, sel.type, 0); + } + + if (previousScroll != term.scr && !isEmpty(&searchString)) { + highlightStringOnScreen(); + } + tsetdirt(0, term.row-3); //< Required because of the cursor cross. + printCommandString(); + printSearchString(); + return success; +} diff --git a/normalMode.h b/normalMode.h new file mode 100644 index 0000000..7d88259 --- /dev/null +++ b/normalMode.h @@ -0,0 +1,36 @@ +/* See LICENSE for license details. */ +#ifndef NORMAL_MODE_H +#define NORMAL_MODE_H + +#include +#include +#include + +/// Used in the configuration file to define custom shortcuts. +typedef struct NormalModeShortcuts { + char key; + char *value; +} NormalModeShortcuts; + +/// Holds the exit status of the #kpressNormalMode function, which informs the +/// caller when to exit normal mode. +typedef enum ExitState { + failed = 0, + success = 1, + finished = 2, +} ExitState; + +/// Called when curr position is altered. +void onMove(void); + +/// Function which returns whether the value at position provided as arguments +/// is to be highlighted. +int highlighted(int, int); + +/// Handles keys in normal mode. +ExitState kpressNormalMode(char const * decoded, int len, bool ctrlPressed, + void const * ksym); + //bool esc, bool enter, bool backspace, void* keysym); + + +#endif // NORMAL_MODE_H diff --git a/st.c b/st.c index 3e48410..d8bf7ab 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,6 +19,8 @@ #include #include + +#include "term.h" #include "st.h" #include "win.h" @@ -42,6 +46,7 @@ #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISDELIM(u) (u && wcschr(worddelimiters, u)) +#define INTERVAL(x, a, b) (x) < (a) ? (a) : (x) > (b) ? (b) : (x) enum term_mode { MODE_WRAP = 1 << 0, @@ -86,51 +91,6 @@ enum escape_state { ESC_DCS =128, }; -typedef struct { - Glyph attr; /* current char attributes */ - int x; - int y; - char state; -} TCursor; - -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 - */ - struct { - int x, y; - } nb, ne, ob, oe; - - int alt; -} Selection; - -/* Internal representation of the screen */ -typedef struct { - int row; /* nb row */ - int col; /* nb col */ - Line *line; /* screen */ - Line *alt; /* alternate screen */ - int *dirty; /* dirtyness of lines */ - TCursor c; /* cursor */ - int ocx; /* old cursor col */ - int ocy; /* old cursor row */ - int top; /* top scroll limit */ - int bot; /* bottom scroll limit */ - int mode; /* terminal mode flags */ - int esc; /* escape state flags */ - char trantbl[4]; /* charset table translation */ - int charset; /* current charset */ - int icharset; /* selected charset for sequence */ - int *tabs; -} Term; - /* CSI Escape sequence structs */ /* ESC '[' [[ [] [;]] []] */ typedef struct { @@ -153,6 +113,8 @@ typedef struct { int narg; /* nb of args */ } STREscape; +void tfulldirt(void); + static void execsh(char *, char **); static void stty(char **); static void sigchld(int); @@ -185,16 +147,14 @@ 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); static void tsetscroll(int, int); static void tswapscreen(void); static void tsetmode(int, int, int *, int); static int twrite(const char *, int, int); -static void tfulldirt(void); static void tcontrolcode(uchar ); static void tdectest(char ); static void tdefutf8(char); @@ -208,8 +168,6 @@ static void selnormalize(void); static void selscroll(int, int); static void selsnap(int *, int *, int); -static size_t utf8decode(const char *, Rune *, size_t); -static Rune utf8decodebyte(char, size_t *); static char utf8encodebyte(Rune, size_t); static size_t utf8validate(Rune *, size_t); @@ -219,8 +177,8 @@ static char base64dec_getc(const char **); static ssize_t xwrite(int, const char *, size_t); /* Globals */ -static Term term; -static Selection sel; +Term term; +Selection sel; static CSIEscape csiescseq; static STREscape strescseq; static int iofd = 1; @@ -414,17 +372,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; @@ -433,6 +396,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) @@ -441,10 +405,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) { @@ -452,18 +419,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; @@ -472,17 +443,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); @@ -490,7 +465,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) @@ -526,7 +501,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; @@ -541,14 +516,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))) @@ -569,14 +544,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; } @@ -596,24 +571,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; @@ -836,6 +819,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); @@ -1047,13 +1033,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); @@ -1067,13 +1093,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); @@ -1093,6 +1129,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; @@ -1122,13 +1159,19 @@ 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); +} + void csiparse(void) { @@ -1181,6 +1224,8 @@ 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. + onMove(); } void @@ -1287,14 +1332,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 @@ -1725,11 +1770,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); @@ -2235,7 +2280,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); } @@ -2248,7 +2293,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); } @@ -2290,7 +2335,7 @@ tputc(Rune u) { char c[UTF_SIZ]; int control; - int width, len; + int width = 0, len; Glyph *gp; control = ISCONTROL(u); @@ -2469,7 +2514,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; @@ -2506,6 +2551,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)); @@ -2563,7 +2616,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); } } @@ -2584,8 +2637,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 a1928ca..3aad9f3 100644 --- a/st.h +++ b/st.h @@ -1,5 +1,8 @@ /* See LICENSE for license details. */ +#include "glyph.h" +#include "normalMode.h" + #include #include @@ -33,6 +36,8 @@ enum glyph_attribute { ATTR_WRAP = 1 << 8, ATTR_WIDE = 1 << 9, ATTR_WDUMMY = 1 << 10, + ATTR_HIGHLIGHT = 1 << 12, + ATTR_CURRENT = 1 << 13, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, }; @@ -42,11 +47,6 @@ enum selection_mode { SEL_READY = 2 }; -enum selection_type { - SEL_REGULAR = 1, - SEL_RECTANGULAR = 2 -}; - enum selection_snap { SNAP_WORD = 1, SNAP_LINE = 2 @@ -57,18 +57,6 @@ typedef unsigned int uint; typedef unsigned long ulong; typedef unsigned short ushort; -typedef uint_least32_t Rune; - -#define Glyph Glyph_ -typedef struct { - Rune u; /* character code */ - ushort mode; /* attribute flags */ - uint32_t fg; /* foreground */ - uint32_t bg; /* background */ -} Glyph; - -typedef Glyph *Line; - typedef union { int i; uint ui; @@ -81,6 +69,11 @@ void die(const char *, ...); void redraw(void); void draw(void); +int currentLine(int, int); +void kscrolldown(const Arg *); +void kscrollup(const Arg *); +void normalMode(Arg const *); + void printscreen(const Arg *); void printsel(const Arg *); void sendbreak(const Arg *); @@ -90,6 +83,9 @@ int tattrset(int); void tnew(int, int); void tresize(int, int); void tsetdirtattr(int); +size_t utf8decode(const char *, Rune *, size_t); +Rune utf8decodebyte(char, size_t *); +void tsetdirt(int, int); void ttyhangup(void); int ttynew(char *, char *, char *, char **); size_t ttyread(void); @@ -100,8 +96,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); diff --git a/term.h b/term.h new file mode 100644 index 0000000..23adf0e --- /dev/null +++ b/term.h @@ -0,0 +1,73 @@ +#ifndef TERM_H +#define TERM_H + +// +// Internal terminal structs. +// + +#include "glyph.h" + +#include + +#define HISTSIZE 2500 + +typedef struct { + Glyph attr; /* current char attributes */ + int x; + int y; + char state; +} TCursor; + +typedef struct { + int mode; + int type; + int snap; + /// 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; + + int alt; +} Selection; + +/* Internal representation of the screen */ +typedef struct { + int row; /* nb row */ + 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 */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4]; /* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; +} Term; + +extern Term term; + +#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \ + term.scr + HISTSIZE + 1) % HISTSIZE] : \ + term.line[(y) - term.scr]) + +extern Selection sel; + + +#endif // TERM_H 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 1f62129..e297946 100644 --- a/x.c +++ b/x.c @@ -143,7 +143,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 int ximopen(Display *); @@ -355,7 +354,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); } @@ -471,7 +470,7 @@ bpress(XEvent *e) xsel.tclick2 = xsel.tclick1; xsel.tclick1 = now; - selstart(evcol(e), evrow(e), snap); + xselstart(evcol(e), evrow(e), snap); } } @@ -757,6 +756,13 @@ xloadcolor(int i, const char *name, Color *ncolor) return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); } +void +normalMode(Arg const *_) { + (void) _; + win.mode ^= MODE_NORMAL; +} + + void xloadcols(void) { @@ -1344,6 +1350,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); @@ -1433,7 +1447,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i xclear(winx, winy + win.ch, winx + width, win.h); /* Clean up the region we want to draw to. */ - XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); /* Set the clip region because Xft is sometimes dirty. */ r.x = 0; @@ -1476,8 +1490,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)) @@ -1509,6 +1524,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) { @@ -1592,12 +1612,18 @@ xdrawline(Line line, int x1, int y1, int x2) numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); i = ox = 0; - for (x = x1; x < x2 && i < numspecs; x++) { + for (x = x1; x < x2 && i < numspecs; ++x) { new = line[x]; if (new.mode == ATTR_WDUMMY) 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; @@ -1786,6 +1812,14 @@ kpress(XEvent *ev) len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); else len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + + if (IS_SET(MODE_NORMAL)) { + ExitState const es = kpressNormalMode(buf, len, // strlen(buf), + match(ControlMask, e->state), + &ksym); + if (es == finished) { normalMode(NULL); } + return; + } /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { if (ksym == bp->keysym && match(bp->mod, e->state)) { -- 2.25.0