diff --git a/st-6009e6e25bdff9548f085e9ae562b1ca305d3a0b-tavo.diff b/st-6009e6e25bdff9548f085e9ae562b1ca305d3a0b-tavo.diff new file mode 100644 index 0000000..f3d4b20 --- /dev/null +++ b/st-6009e6e25bdff9548f085e9ae562b1ca305d3a0b-tavo.diff @@ -0,0 +1,2331 @@ +diff '--color=auto' -Nu a/config.def.h b/config.def.h +--- a/config.def.h 2024-12-13 22:55:23.180773698 -0600 ++++ b/config.def.h 2024-12-13 23:03:13.068200348 -0600 +@@ -176,6 +176,8 @@ + */ + static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ ++ { ShiftMask, Button4, kscrollup, {.i = 1} }, ++ { ShiftMask, Button5, kscrolldown, {.i = 1} }, + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, +@@ -201,6 +203,8 @@ + { 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} }, + }; + + /* +diff '--color=auto' -Nu a/config.h b/config.h +--- a/config.h 1969-12-31 18:00:00.000000000 -0600 ++++ b/config.h 2024-12-13 23:13:56.012933216 -0600 +@@ -0,0 +1,480 @@ ++/* See LICENSE file for copyright and license details. */ ++ ++/* ++ * appearance ++ * ++ * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html ++ */ ++static char *font = "JetBrainsMono:size=10"; ++static int borderpx = 4; ++ ++/* ++ * What program is execed by st depends of these precedence rules: ++ * 1: program passed with -e ++ * 2: scroll and/or utmp ++ * 3: SHELL environment variable ++ * 4: value of shell in /etc/passwd ++ * 5: value of shell in config.h ++ */ ++static char *shell = "/bin/sh"; ++char *utmp = NULL; ++/* scroll program: to enable use a string like "scroll" */ ++char *scroll = NULL; ++char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; ++ ++/* identification sequence returned in DA and DECID */ ++char *vtiden = "\033[?6c"; ++ ++/* Kerning / character bounding-box multipliers */ ++static float cwscale = 1.0; ++static float chscale = 1.0; ++ ++/* ++ * word delimiter string ++ * ++ * More advanced example: L" `'\"()[]{}" ++ */ ++wchar_t *worddelimiters = L" "; ++ ++/* selection timeouts (in milliseconds) */ ++static unsigned int doubleclicktimeout = 300; ++static unsigned int tripleclicktimeout = 600; ++ ++/* alt screens */ ++int allowaltscreen = 1; ++ ++/* allow certain non-interactive (insecure) window operations such as: ++ setting the clipboard text */ ++int allowwindowops = 0; ++ ++/* ++ * draw latency range in ms - from new content/keypress/etc until drawing. ++ * within this range, st draws when content stops arriving (idle). mostly it's ++ * near minlatency, but it waits longer for slow updates to avoid partial draw. ++ * low minlatency will tear/flicker more, as it can "detect" idle too early. ++ */ ++static double minlatency = 2; ++static double maxlatency = 33; ++ ++/* ++ * blinking timeout (set to 0 to disable blinking) for the terminal blinking ++ * attribute. ++ */ ++static unsigned int blinktimeout = 800; ++ ++/* ++ * thickness of underline and bar cursors ++ */ ++static unsigned int cursorthickness = 2; ++ ++/* ++ * bell volume. It must be a value between -100 and 100. Use 0 for disabling ++ * it ++ */ ++static int bellvolume = 0; ++ ++/* default TERM value */ ++char *termname = "st-256color"; ++ ++/* ++ * spaces per tab ++ * ++ * When you are changing this value, don't forget to adapt the »it« value in ++ * the st.info and appropriately install the st.info in the environment where ++ * you use this st version. ++ * ++ * it#$tabspaces, ++ * ++ * Secondly make sure your kernel is not expanding tabs. When running `stty ++ * -a` »tab0« should appear. You can tell the terminal to not expand tabs by ++ * running following command: ++ * ++ * stty tabs ++ */ ++unsigned int tabspaces = 8; ++ ++/* Terminal colors (16 first used in escape sequence) */ ++static const char *colorname[] = { ++ /* 8 normal colors */ ++ [0] = "#414b50", /* black */ ++ [1] = "#e67e80", /* red */ ++ [2] = "#a7c080", /* green */ ++ [3] = "#dbbc7f", /* yellow */ ++ [4] = "#7fbbb3", /* blue */ ++ [5] = "#d699b6", /* magenta */ ++ [6] = "#83c092", /* cyan */ ++ [7] = "#d3c6aa", /* white */ ++ ++ /* 8 bright colors */ ++ [8] = "#475258", /* black */ ++ [9] = "#e67e80", /* red */ ++ [10] = "#a7c080", /* green */ ++ [11] = "#dbbc7f", /* yellow */ ++ [12] = "#7fbbb3", /* blue */ ++ [13] = "#d699b6", /* magenta */ ++ [14] = "#83c092", /* cyan */ ++ [15] = "#d3c6aa", /* white */ ++ ++ /* special colors */ ++ [256] = "#272e33", /* background */ ++ [257] = "#d3c6aa", /* foreground */ ++ ++ /* more colors can be added after 255 to use with DefaultXX */ ++ "#cccccc", ++ "#555555", ++ "gray90", /* default foreground colour */ ++ "black", /* default background colour */ ++}; ++ ++ ++/* ++ * Default colors (colorname index) ++ * foreground, background, cursor, reverse cursor ++ */ ++unsigned int defaultfg = 257; ++unsigned int defaultbg = 256; ++unsigned int defaultcs = 257; ++static unsigned int defaultrcs = 257; ++ ++/* ++ * Default shape of cursor ++ * 2: Block ("█") ++ * 4: Underline ("_") ++ * 6: Bar ("|") ++ * 7: Snowman ("☃") ++ */ ++static unsigned int cursorshape = 2; ++ ++/* ++ * Default columns and rows numbers ++ */ ++ ++static unsigned int cols = 80; ++static unsigned int rows = 24; ++ ++/* ++ * Default colour and shape of the mouse cursor ++ */ ++static unsigned int mouseshape = XC_xterm; ++static unsigned int mousefg = 7; ++static unsigned int mousebg = 0; ++ ++/* ++ * Color used to display font attributes when fontconfig selected a font which ++ * doesn't match the ones requested. ++ */ ++static unsigned int defaultattr = 11; ++ ++/* ++ * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). ++ * Note that if you want to use ShiftMask with selmasks, set this to an other ++ * modifier, set to 0 to not use it. ++ */ ++static uint forcemousemod = ShiftMask; ++ ++/* ++ * Internal mouse shortcuts. ++ * Beware that overloading Button1 will disable the selection. ++ */ ++static MouseShortcut mshortcuts[] = { ++ /* mask button function argument release */ ++ { ShiftMask, Button4, kscrollup, {.i = 1} }, ++ { ShiftMask, Button5, kscrolldown, {.i = 1} }, ++ { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, ++ { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, ++ { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, ++ { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, ++ { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, ++}; ++ ++/* Internal keyboard shortcuts. */ ++#define MODKEY Mod1Mask ++#define TERMMOD (ControlMask|ShiftMask) ++ ++static Shortcut shortcuts[] = { ++ /* mask keysym function argument */ ++ { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, ++ { ControlMask, XK_Print, toggleprinter, {.i = 0} }, ++ { ShiftMask, XK_Print, printscreen, {.i = 0} }, ++ { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, ++ { TERMMOD, XK_Prior, zoom, {.f = +1} }, ++ { TERMMOD, XK_Next, zoom, {.f = -1} }, ++ { TERMMOD, XK_Home, zoomreset, {.f = 0} }, ++ { TERMMOD, XK_C, clipcopy, {.i = 0} }, ++ { TERMMOD, XK_V, clippaste, {.i = 0} }, ++ { 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} }, ++}; ++ ++/* ++ * Special keys (change & recompile st.info accordingly) ++ * ++ * Mask value: ++ * * Use XK_ANY_MOD to match the key no matter modifiers state ++ * * Use XK_NO_MOD to match the key alone (no modifiers) ++ * appkey value: ++ * * 0: no value ++ * * > 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{|}~"; +Common subdirectories: a/.git and b/.git +diff '--color=auto' -Nu a/st.c b/st.c +--- a/st.c 2024-12-13 22:55:23.180773698 -0600 ++++ b/st.c 2024-12-13 23:07:51.886249098 -0600 +@@ -35,6 +35,8 @@ + #define ESC_ARG_SIZ 16 + #define STR_BUF_SIZ ESC_BUF_SIZ + #define STR_ARG_SIZ ESC_ARG_SIZ ++#define HISTSIZE 2000 ++#define RESIZEBUFFER 1000 + + /* macros */ + #define IS_SET(flag) ((term.mode & (flag)) != 0) +@@ -42,6 +44,26 @@ + #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[(term.histi + (y) - term.scr + 1 + HISTSIZE) % HISTSIZE] \ ++ : term.line[(y) - term.scr] \ ++) ++ ++#define TLINE_HIST(y) ( \ ++ (y) <= HISTSIZE-term.row+2 ? term.hist[(y)] \ ++ : term.line[(y-HISTSIZE+term.row-3)] \ ++) ++ ++#define TLINEABS(y) ( \ ++ (y) < 0 ? term.hist[(term.histi + (y) + 1 + HISTSIZE) % HISTSIZE] : term.line[(y)] \ ++) ++ ++#define UPDATEWRAPNEXT(alt, col) do { \ ++ if ((term.c.state & CURSOR_WRAPNEXT) && term.c.x + term.wrapcwidth[alt] < col) { \ ++ term.c.x += term.wrapcwidth[alt]; \ ++ term.c.state &= ~CURSOR_WRAPNEXT; \ ++ } \ ++} while (0); + + enum term_mode { + MODE_WRAP = 1 << 0, +@@ -53,6 +75,12 @@ + MODE_UTF8 = 1 << 6, + }; + ++enum scroll_mode { ++ SCROLL_RESIZE = -1, ++ SCROLL_NOSAVEHIST = 0, ++ SCROLL_SAVEHIST = 1 ++}; ++ + enum cursor_movement { + CURSOR_SAVE, + CURSOR_LOAD +@@ -114,7 +142,11 @@ + 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 histf; /* nb history available */ ++ int scr; /* scroll back */ ++ int wrapcwidth[2]; /* used in updating WRAPNEXT when resizing */ + int *dirty; /* dirtyness of lines */ + TCursor c; /* cursor */ + int ocx; /* old cursor col */ +@@ -172,26 +204,37 @@ + static void tdumpsel(void); + static void tdumpline(int); + static void tdump(void); +-static void tclearregion(int, int, int, int); ++static void tclearregion(int, int, int, int, int); + static void tcursor(int); ++static void tclearglyph(Glyph *, int); ++static void tresetcursor(void); + static void tdeletechar(int); + static void tdeleteline(int); + static void tinsertblank(int); + static void tinsertblankline(int); +-static int tlinelen(int); ++static int tlinelen(Line len); ++static int tiswrapped(Line line); ++static char *tgetglyphs(char *, const Glyph *, const Glyph *); ++static size_t tgetline(char *, const Glyph *); + static void tmoveto(int, int); + static void tmoveato(int, int); + static void tnewline(int); + static void tputtab(int); + static void tputc(Rune); + static void treset(void); +-static void tscrollup(int, int); ++static void tscrollup(int, int, int, int); + static void tscrolldown(int, int); ++static void treflow(int, int); ++static void rscrolldown(int); ++static void tresizedef(int, int); ++static void tresizealt(int, int); + static void tsetattr(const int *, int); + static void tsetchar(Rune, const Glyph *, int, int); + static void tsetdirt(int, int); + static void tsetscroll(int, int); + static void tswapscreen(void); ++static void tloaddefscreen(int, int); ++static void tloadaltscreen(int, int); + static void tsetmode(int, int, const int *, int); + static int twrite(const char *, int, int); + static void tfulldirt(void); +@@ -205,7 +248,10 @@ + static void drawregion(int, int, int, int); + + static void selnormalize(void); +-static void selscroll(int, int); ++static void selscroll(int, int, int); ++static void selmove(int); ++static void selremove(void); ++static int regionselected(int, int, int, int); + static void selsnap(int *, int *, int); + + static size_t utf8decode(const char *, Rune *, size_t); +@@ -405,14 +451,57 @@ + } + + int +-tlinelen(int y) ++tlinelen(Line line) ++{ ++ int i = term.col - 1; ++ ++ for (; i >= 0 && !(line[i].mode & (ATTR_SET | ATTR_WRAP)); i--); ++ return i + 1; ++} ++ ++int ++tiswrapped(Line line) ++{ ++ int len = tlinelen(line); ++ ++ return len > 0 && (line[len - 1].mode & ATTR_WRAP); ++} ++ ++char * ++tgetglyphs(char *buf, const Glyph *gp, const Glyph *lgp) ++{ ++ while (gp <= lgp) ++ if (gp->mode & ATTR_WDUMMY) { ++ gp++; ++ } else { ++ buf += utf8encode((gp++)->u, buf); ++ } ++ return buf; ++} ++ ++size_t ++tgetline(char *buf, const Glyph *fgp) ++{ ++ char *ptr; ++ const Glyph *lgp = &fgp[term.col - 1]; ++ ++ while (lgp > fgp && !(lgp->mode & (ATTR_SET | ATTR_WRAP))) ++ lgp--; ++ ptr = tgetglyphs(buf, fgp, lgp); ++ if (!(lgp->mode & ATTR_WRAP)) ++ *(ptr++) = '\n'; ++ return ptr - buf; ++} ++ ++int ++tlinehistlen(int y) + { + int i = term.col; + +- if (term.line[y][i - 1].mode & ATTR_WRAP) ++ if (TLINE_HIST(y)[i - 1].mode & ATTR_WRAP) + return i; + +- while (i > 0 && term.line[y][i - 1].u == ' ') ++ while (i > 0 && TLINE_HIST(y)[i - 1].u == ' ') + --i; + + return i; +@@ -455,10 +544,11 @@ + + sel.oe.x = col; + sel.oe.y = row; +- selnormalize(); + sel.type = type; ++ selnormalize(); + +- 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 || ++ 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,61 +557,69 @@ + void + selnormalize(void) + { +- int i; ++ 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; +- } 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.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; ++ } 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); + +- selsnap(&sel.nb.x, &sel.nb.y, -1); +- selsnap(&sel.ne.x, &sel.ne.y, +1); ++ selsnap(&sel.nb.x, &sel.nb.y, -1); ++ selsnap(&sel.ne.x, &sel.ne.y, +1); + +- /* expand selection over line breaks */ +- if (sel.type == SEL_RECTANGULAR) +- return; +- i = tlinelen(sel.nb.y); +- if (i < sel.nb.x) +- sel.nb.x = i; +- if (tlinelen(sel.ne.y) <= sel.ne.x) +- sel.ne.x = term.col - 1; ++ /* expand selection over line breaks */ ++ if (sel.type == SEL_RECTANGULAR) ++ return; ++ ++ i = tlinelen(TLINE(sel.nb.y)); ++ if (sel.nb.x > i) ++ sel.nb.x = i; ++ if (sel.ne.x >= tlinelen(TLINE(sel.ne.y))) ++ sel.ne.x = term.col - 1; + } + +-int +-selected(int x, int y) ++ int ++regionselected(int x1, int y1, int x2, int y2) + { +- if (sel.mode == SEL_EMPTY || sel.ob.x == -1 || +- sel.alt != IS_SET(MODE_ALTSCREEN)) +- return 0; ++ if (sel.ob.x == -1 || sel.mode == SEL_EMPTY || ++ sel.alt != IS_SET(MODE_ALTSCREEN) || sel.nb.y > y2 || sel.ne.y < y1) ++ return 0; + +- if (sel.type == SEL_RECTANGULAR) +- return BETWEEN(y, sel.nb.y, sel.ne.y) +- && BETWEEN(x, sel.nb.x, sel.ne.x); +- +- return BETWEEN(y, sel.nb.y, sel.ne.y) +- && (y != sel.nb.y || x >= sel.nb.x) +- && (y != sel.ne.y || x <= sel.ne.x); ++ return (sel.type == SEL_RECTANGULAR) ? sel.nb.x <= x2 && sel.ne.x >= x1 ++ : (sel.nb.y != y2 || sel.nb.x <= x2) && ++ (sel.ne.y != y1 || sel.ne.x >= x1); + } + ++ int ++selected(int x, int y) ++{ ++ return regionselected(x, y, x, y); ++} ++ ++ + void + selsnap(int *x, int *y, int direction) + { + int newx, newy, xt, yt; ++ int rtop = 0, rbot = term.row - 1; + int delim, prevdelim; + const Glyph *gp, *prevgp; + ++ if (!IS_SET(MODE_ALTSCREEN)) ++ rtop += -term.histf + term.scr, rbot += term.scr; ++ + switch (sel.snap) { + case SNAP_WORD: + /* + * 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; +@@ -529,24 +627,24 @@ + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; +- if (!BETWEEN(newy, 0, term.row - 1)) ++ if (!BETWEEN(newy, rtop, rbot)) + break; + + if (direction > 0) + 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)) ++ if (newx >= tlinelen(TLINE(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))) ++ if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim || ++ (delim && !(gp->u == ' ' && prevgp->u == ' ')))) + break; + + *x = newx; +@@ -561,20 +659,16 @@ + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ +- *x = (direction < 0) ? 0 : term.col - 1; +- if (direction < 0) { +- for (; *y > 0; *y += direction) { +- if (!(term.line[*y-1][term.col-1].mode +- & ATTR_WRAP)) { +- break; +- } ++ *x = (direction < 0) ? 0 : term.col - 1; ++ if (direction < 0) { ++ for (; *y > rtop; *y -= 1) { ++ if (!tiswrapped(TLINE(*y-1))) ++ break; + } + } else if (direction > 0) { +- for (; *y < term.row-1; *y += direction) { +- if (!(term.line[*y][term.col-1].mode +- & ATTR_WRAP)) { ++ for (; *y < rbot; *y += 1) { ++ if (!tiswrapped(TLINE(*y))) + break; +- } + } + } + break; +@@ -585,39 +679,34 @@ + getsel(void) + { + char *str, *ptr; +- int y, bufsize, lastx, linelen; +- const Glyph *gp, *last; ++ int y, lastx, linelen; ++ const Glyph *gp, *lgp; + +- if (sel.ob.x == -1) ++ if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) + return NULL; + +- bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; +- ptr = str = xmalloc(bufsize); ++ str = xmalloc((term.col + 1) * (sel.ne.y - sel.nb.y + 1) * UTF_SIZ); ++ ptr = str; + + /* append every set & selected glyph to the selection */ + for (y = sel.nb.y; y <= sel.ne.y; y++) { +- if ((linelen = tlinelen(y)) == 0) { ++ Line line = TLINE(y); ++ ++ if ((linelen = tlinelen(line)) == 0) { + *ptr++ = '\n'; + continue; + } + + if (sel.type == SEL_RECTANGULAR) { +- gp = &term.line[y][sel.nb.x]; ++ gp = &line[sel.nb.x]; + lastx = sel.ne.x; + } else { +- gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; ++ gp = &line[sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } +- last = &term.line[y][MIN(lastx, linelen-1)]; +- while (last >= gp && last->u == ' ') +- --last; ++ lgp = &line[MIN(lastx, linelen-1)]; + +- for ( ; gp <= last; ++gp) { +- if (gp->mode & ATTR_WDUMMY) +- continue; +- +- ptr += utf8encode(gp->u, ptr); +- } ++ ptr = tgetglyphs(ptr, gp, lgp); + + /* + * Copy and pasting of line endings is inconsistent +@@ -629,10 +718,10 @@ + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && +- (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) ++ (!(lgp->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) + *ptr++ = '\n'; + } +- *ptr = 0; ++ *ptr = '\0'; + return str; + } + +@@ -641,9 +730,15 @@ + { + if (sel.ob.x == -1) + return; ++ selremove(); ++ tsetdirt(sel.nb.y, sel.ne.y); ++} ++ ++void ++selremove(void) ++{ + sel.mode = SEL_IDLE; + sel.ob.x = -1; +- tsetdirt(sel.nb.y, sel.ne.y); + } + + void +@@ -718,8 +813,14 @@ + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + +- if (pid != p) ++ if (pid != p) { ++ if (p == 0 && wait(&stat) < 0) ++ die("wait: %s\n", strerror(errno)); ++ ++ /* reinstall sigchld handler */ ++ signal(SIGCHLD, sigchld); + return; ++ } + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + die("child exited with status %d\n", WEXITSTATUS(stat)); +@@ -803,7 +904,7 @@ + break; + default: + #ifdef __OpenBSD__ +- if (pledge("stdio rpath tty proc", NULL) == -1) ++ if (pledge("stdio rpath tty proc exec", NULL) == -1) + die("pledge\n"); + #endif + close(s); +@@ -845,6 +946,8 @@ + { + const char *next; + ++ kscrolldown(&((Arg){ .i = term.scr })); ++ + if (may_echo && IS_SET(MODE_ECHO)) + twrite(s, n, 1); + +@@ -980,7 +1083,7 @@ + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { +- tsetdirt(i, i); ++ term.dirty[i] = 1; + break; + } + } +@@ -990,7 +1093,8 @@ + void + tfulldirt(void) + { +- tsetdirt(0, term.row-1); ++ for (int i = 0; i < term.row; i++) ++ term.dirty[i] = 1; + } + + void +@@ -1008,109 +1112,260 @@ + } + + void ++tresetcursor(void) ++{ ++ term.c = (TCursor){ { .mode = ATTR_NULL, .fg = defaultfg, .bg = defaultbg }, ++ .x = 0, .y = 0, .state = CURSOR_DEFAULT }; ++} ++ ++void + treset(void) + { + uint i; ++ int x, y; + +- term.c = (TCursor){{ +- .mode = ATTR_NULL, +- .fg = defaultfg, +- .bg = defaultbg +- }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; ++ tresetcursor(); + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; ++ term.histf = 0; ++ term.scr = 0; + term.bot = term.row - 1; + term.mode = MODE_WRAP|MODE_UTF8; + memset(term.trantbl, CS_USA, sizeof(term.trantbl)); + term.charset = 0; + ++ selremove(); + for (i = 0; i < 2; i++) { +- tmoveto(0, 0); +- tcursor(CURSOR_SAVE); +- tclearregion(0, 0, term.col-1, term.row-1); +- tswapscreen(); ++ tcursor(CURSOR_SAVE); /* reset saved cursor */ ++ for (y = 0; y < term.row; y++) ++ for (x = 0; x < term.col; x++) ++ tclearglyph(&term.line[y][x], 0); ++ tswapscreen(); + } ++ tfulldirt(); + } + + void + tnew(int col, int row) + { +- term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; +- tresize(col, row); +- treset(); ++ int i, j; ++ ++ for (i = 0; i < 2; i++) { ++ term.line = xmalloc(row * sizeof(Line)); ++ for (j = 0; j < row; j++) ++ term.line[j] = xmalloc(col * sizeof(Glyph)); ++ term.col = col, term.row = row; ++ tswapscreen(); ++ } ++ term.dirty = xmalloc(row * sizeof(*term.dirty)); ++ term.tabs = xmalloc(col * sizeof(*term.tabs)); ++ for (i = 0; i < HISTSIZE; i++) ++ term.hist[i] = xmalloc(col * sizeof(Glyph)); ++ treset(); + } + ++/* handle it with care */ + void + tswapscreen(void) + { +- Line *tmp = term.line; +- +- term.line = term.alt; +- term.alt = tmp; ++ static Line *altline; ++ static int altcol, altrow; ++ Line *tmpline = term.line; ++ int tmpcol = term.col, tmprow = term.row; ++ ++ term.line = altline; ++ term.col = altcol, term.row = altrow; ++ altline = tmpline; ++ altcol = tmpcol, altrow = tmprow; + term.mode ^= MODE_ALTSCREEN; +- tfulldirt(); + } + + void +-tscrolldown(int orig, int n) ++tloaddefscreen(int clear, int loadcursor) + { +- int i; +- Line temp; ++ int col, row, alt = IS_SET(MODE_ALTSCREEN); + +- LIMIT(n, 0, term.bot-orig+1); ++ if (alt) { ++ if (clear) ++ tclearregion(0, 0, term.col-1, term.row-1, 1); ++ col = term.col, row = term.row; ++ tswapscreen(); ++ } ++ if (loadcursor) ++ tcursor(CURSOR_LOAD); ++ if (alt) ++ tresizedef(col, row); ++} + +- tsetdirt(orig, term.bot-n); +- tclearregion(0, term.bot-n+1, term.col-1, term.bot); ++void ++tloadaltscreen(int clear, int savecursor) ++{ ++ int col, row, def = !IS_SET(MODE_ALTSCREEN); + +- for (i = term.bot; i >= orig+n; i--) { +- temp = term.line[i]; +- term.line[i] = term.line[i-n]; +- term.line[i-n] = temp; ++ if (savecursor) ++ tcursor(CURSOR_SAVE); ++ if (def) { ++ col = term.col, row = term.row; ++ tswapscreen(); ++ term.scr = 0; ++ tresizealt(col, row); + } ++ if (clear) ++ tclearregion(0, 0, term.col-1, term.row-1, 1); ++} ++ ++int ++tisaltscreen(void) ++{ ++ return IS_SET(MODE_ALTSCREEN); ++} ++ ++ ++void ++kscrolldown(const Arg* a) ++{ ++ int n = a->i; ++ ++ if (!term.scr || IS_SET(MODE_ALTSCREEN)) ++ return; + +- selscroll(orig, n); ++ if (n < 0) ++ n = MAX(term.row / -n, 1); ++ ++ if (n <= term.scr) { ++ term.scr -= n; ++ } else { ++ n = term.scr; ++ term.scr = 0; ++ } ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(-n); /* negate change in term.scr */ ++ tfulldirt(); + } + ++ ++ + void +-tscrollup(int orig, int n) ++kscrollup(const Arg* a) + { +- int i; +- Line temp; ++ int n = a->i; + +- LIMIT(n, 0, term.bot-orig+1); ++ if (!term.histf || IS_SET(MODE_ALTSCREEN)) ++ return; + +- tclearregion(0, orig, term.col-1, orig+n-1); +- tsetdirt(orig+n, term.bot); ++ if (n < 0) ++ n = MAX(term.row / -n, 1); + +- for (i = orig; i <= term.bot-n; i++) { +- temp = term.line[i]; +- term.line[i] = term.line[i+n]; +- term.line[i+n] = temp; +- } ++ if (term.scr + n <= term.histf) { ++ term.scr += n; ++ } else { ++ n = term.histf - term.scr; ++ term.scr = term.histf; ++ } ++ ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(n); /* negate change in term.scr */ ++ tfulldirt(); + +- selscroll(orig, -n); + } + + void +-selscroll(int orig, int n) ++tscrolldown(int top, int n) + { +- if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN)) +- return; ++ int i, bot = term.bot; ++ Line temp; + +- if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { +- selclear(); +- } else if (BETWEEN(sel.nb.y, orig, term.bot)) { +- sel.ob.y += n; +- sel.oe.y += n; +- if (sel.ob.y < term.top || sel.ob.y > term.bot || +- sel.oe.y < term.top || sel.oe.y > term.bot) { +- selclear(); +- } else { +- selnormalize(); +- } ++ if (n <= 0) ++ return; ++ n = MIN(n, bot-top+1); ++ ++ tsetdirt(top, bot-n); ++ tclearregion(0, bot-n+1, term.col-1, bot, 1); ++ ++ for (i = bot; i >= top+n; i--) { ++ temp = term.line[i]; ++ term.line[i] = term.line[i-n]; ++ term.line[i-n] = temp; ++ } ++ ++ if (sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN)) ++ selscroll(top, bot, n); ++} ++ ++void ++tscrollup(int top, int bot, int n, int mode) ++{ ++ int i, j, s; ++ int alt = IS_SET(MODE_ALTSCREEN); ++ int savehist = !alt && top == 0 && mode != SCROLL_NOSAVEHIST; ++ Line temp; ++ ++ if (n <= 0) ++ return; ++ n = MIN(n, bot-top+1); ++ ++ if (savehist) { ++ for (i = 0; i < n; i++) { ++ term.histi = (term.histi + 1) % HISTSIZE; ++ temp = term.hist[term.histi]; ++ for (j = 0; j < term.col; j++) ++ tclearglyph(&temp[j], 1); ++ term.hist[term.histi] = term.line[i]; ++ term.line[i] = temp; ++ } ++ term.histf = MIN(term.histf + n, HISTSIZE); ++ s = n; ++ if (term.scr) { ++ j = term.scr; ++ term.scr = MIN(j + n, HISTSIZE); ++ s = j + n - term.scr; ++ } ++ if (mode != SCROLL_RESIZE) ++ tfulldirt(); ++ } else { ++ tclearregion(0, top, term.col-1, top+n-1, 1); ++ tsetdirt(top+n, bot); ++ } ++ ++ for (i = top; i <= bot-n; i++) { ++ temp = term.line[i]; ++ term.line[i] = term.line[i+n]; ++ term.line[i+n] = temp; ++ } ++ ++ if (sel.ob.x != -1 && sel.alt == alt) { ++ if (!savehist) { ++ selscroll(top, bot, -n); ++ } else if (s > 0) { ++ selmove(-s); ++ if (-term.scr + sel.nb.y < -term.histf) ++ selremove(); ++ } ++ } ++} ++ ++void ++selmove(int n) ++ { ++ sel.ob.y += n, sel.nb.y += n; ++ sel.oe.y += n, sel.ne.y += n; ++} ++ ++void ++selscroll(int top, int bot, int n) ++{ ++ /* turn absolute coordinates into relative */ ++ top += term.scr, bot += term.scr; ++ ++ if (BETWEEN(sel.nb.y, top, bot) != BETWEEN(sel.ne.y, top, bot)) { ++ selclear(); ++ } else if (BETWEEN(sel.nb.y, top, bot)) { ++ selmove(n); ++ if (sel.nb.y < top || sel.ne.y > bot) ++ selclear(); + } + } + +@@ -1120,7 +1375,7 @@ + int y = term.c.y; + + if (y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); + } else { + y++; + } +@@ -1187,101 +1442,112 @@ + void + tsetchar(Rune u, const Glyph *attr, int x, int y) + { +- static const char *vt100_0[62] = { /* 0x41 - 0x7e */ +- "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ +- 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ +- 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ +- 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ +- "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ +- "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ +- "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ +- "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ +- }; ++ static const char *vt100_0[62] = { /* 0x41 - 0x7e */ ++ "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ ++ 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ ++ 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ ++ 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ ++ "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ ++ "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ ++ "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ ++ "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ ++ }; ++ ++ /* ++ * The table is proudly stolen from rxvt. ++ */ ++ if (term.trantbl[term.charset] == CS_GRAPHIC0 && ++ BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) ++ utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); ++ ++ if (term.line[y][x].mode & ATTR_WIDE) { ++ if (x+1 < term.col) { ++ term.line[y][x+1].u = ' '; ++ term.line[y][x+1].mode &= ~ATTR_WDUMMY; ++ } ++ } else if (term.line[y][x].mode & ATTR_WDUMMY) { ++ term.line[y][x-1].u = ' '; ++ term.line[y][x-1].mode &= ~ATTR_WIDE; ++ } ++ ++ term.dirty[y] = 1; ++ term.line[y][x] = *attr; ++ term.line[y][x].u = u; ++ term.line[y][x].mode |= ATTR_SET; ++} + +- /* +- * The table is proudly stolen from rxvt. +- */ +- if (term.trantbl[term.charset] == CS_GRAPHIC0 && +- BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) +- utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); + +- if (term.line[y][x].mode & ATTR_WIDE) { +- if (x+1 < term.col) { +- term.line[y][x+1].u = ' '; +- term.line[y][x+1].mode &= ~ATTR_WDUMMY; +- } +- } else if (term.line[y][x].mode & ATTR_WDUMMY) { +- term.line[y][x-1].u = ' '; +- term.line[y][x-1].mode &= ~ATTR_WIDE; +- } + +- term.dirty[y] = 1; +- term.line[y][x] = *attr; +- term.line[y][x].u = u; ++void ++tclearglyph(Glyph *gp, int usecurattr) ++{ ++ if (usecurattr) { ++ gp->fg = term.c.attr.fg; ++ gp->bg = term.c.attr.bg; ++ } else { ++ gp->fg = defaultfg; ++ gp->bg = defaultbg; ++ } ++ gp->mode = ATTR_NULL; ++ gp->u = ' '; + } + ++ ++ + void +-tclearregion(int x1, int y1, int x2, int y2) ++tclearregion(int x1, int y1, int x2, int y2, int usecurattr) + { +- int x, y, temp; +- Glyph *gp; +- +- if (x1 > x2) +- temp = x1, x1 = x2, x2 = temp; +- if (y1 > y2) +- temp = y1, y1 = y2, y2 = temp; +- +- LIMIT(x1, 0, term.col-1); +- LIMIT(x2, 0, term.col-1); +- LIMIT(y1, 0, term.row-1); +- LIMIT(y2, 0, term.row-1); ++ int x, y; ++ /* regionselected() takes relative coordinates */ ++ if (regionselected(x1+term.scr, y1+term.scr, x2+term.scr, y2+term.scr)) ++ selremove(); + +- for (y = y1; y <= y2; y++) { ++ for (y = y1; y <= y2; y++) { + term.dirty[y] = 1; +- for (x = x1; x <= x2; x++) { +- gp = &term.line[y][x]; +- if (selected(x, y)) +- selclear(); +- gp->fg = term.c.attr.fg; +- gp->bg = term.c.attr.bg; +- gp->mode = 0; +- gp->u = ' '; +- } ++ for (x = x1; x <= x2; x++) ++ tclearglyph(&term.line[y][x], usecurattr); + } + } + + void + tdeletechar(int n) + { +- int dst, src, size; +- Glyph *line; ++ int src, dst, size; ++ Line line; + +- LIMIT(n, 0, term.col - term.c.x); ++ if (n <= 0) ++ return; + +- dst = term.c.x; +- src = term.c.x + n; +- size = term.col - src; +- line = term.line[term.c.y]; +- +- memmove(&line[dst], &line[src], size * sizeof(Glyph)); +- tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); ++ dst = term.c.x; ++ src = MIN(term.c.x + n, term.col); ++ size = term.col - src; ++ if (size > 0) { ++ /* ++ * otherwise src would point beyond the array ++ * https://stackoverflow.com/questions/29844298 ++ */ ++ line = term.line[term.c.y]; ++ memmove(&line[dst], &line[src], size * sizeof(Glyph)); ++ } ++ tclearregion(dst + size, term.c.y, term.col - 1, term.c.y, 1); + } + + void + tinsertblank(int n) + { +- int dst, src, size; +- Glyph *line; +- +- LIMIT(n, 0, term.col - term.c.x); ++ int src, dst, size; ++ Line line; + +- dst = term.c.x + n; +- src = term.c.x; +- size = term.col - dst; +- line = term.line[term.c.y]; +- +- memmove(&line[dst], &line[src], size * sizeof(Glyph)); +- tclearregion(src, term.c.y, dst - 1, term.c.y); ++ if (n <= 0) ++ return; ++ dst = MIN(term.c.x + n, term.col); ++ src = term.c.x; ++ size = term.col - dst; ++ if (size > 0) { /* otherwise dst would point beyond the array */ ++ line = term.line[term.c.y]; ++ memmove(&line[dst], &line[src], size * sizeof(Glyph)); ++ } ++ tclearregion(src, term.c.y, dst - 1, term.c.y, 1); + } + + void +@@ -1295,7 +1561,7 @@ + tdeleteline(int n) + { + if (BETWEEN(term.c.y, term.top, term.bot)) +- tscrollup(term.c.y, n); ++ tscrollup(term.c.y, term.bot, n, SCROLL_NOSAVEHIST); + } + + int32_t +@@ -1469,7 +1735,7 @@ + void + tsetmode(int priv, int set, const int *args, int narg) + { +- int alt; const int *lim; ++ const int *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { +@@ -1530,26 +1796,20 @@ + xsetmode(set, MODE_8BIT); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ +- if (!allowaltscreen) +- break; +- tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); +- /* FALLTHROUGH */ + case 47: /* swap screen */ +- case 1047: ++ case 1047: /*swap screen, clearing alternate screen */ + if (!allowaltscreen) + break; +- alt = IS_SET(MODE_ALTSCREEN); +- if (alt) { +- tclearregion(0, 0, term.col-1, +- term.row-1); +- } +- if (set ^ alt) /* set is always 1 or 0 */ +- tswapscreen(); +- if (*args != 1049) +- break; ++ if (set) ++ tloadaltscreen(*args == 1049, *args == 1049); ++ else ++ tloaddefscreen(*args == 1047, *args == 1049); ++ break; + /* FALLTHROUGH */ +- case 1048: +- tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); ++ case 1048: ++ if (!allowaltscreen) ++ break; ++ tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, MODE_BRCKTPASTE); +@@ -1600,7 +1860,7 @@ + csihandle(void) + { + char buf[40]; +- int len; ++ int n, x; + + switch (csiescseq.mode[0]) { + default: +@@ -1698,19 +1958,30 @@ + case 'J': /* ED -- Clear screen */ + switch (csiescseq.arg[0]) { + case 0: /* below */ +- tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); ++ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); + if (term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, +- term.row-1); ++ term.row-1, 1); + } + break; + case 1: /* above */ +- if (term.c.y > 0) +- tclearregion(0, 0, term.col-1, term.c.y-1); +- tclearregion(0, term.c.y, term.c.x, term.c.y); ++ if (term.c.y >= 0) ++ tclearregion(0, 0, term.col-1, term.c.y-1, 1); ++ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ +- tclearregion(0, 0, term.col-1, term.row-1); ++ if (IS_SET(MODE_ALTSCREEN)) { ++ tclearregion(0, 0, term.col-1, term.row-1, 1); ++ break; ++ } ++ /* vte does this: ++ tscrollup(0, term.row-1, term.row, SCROLL_SAVEHIST); */ ++ ++ /* alacritty does this: */ ++ for (n = term.row-1; n >= 0 && tlinelen(term.line[n]) == 0; n--); ++ if (n >= 0) ++ tscrollup(0, term.row-1, n+1, SCROLL_SAVEHIST); ++ tscrollup(0, term.row-1, term.row-n-1, SCROLL_NOSAVEHIST); + break; + default: + goto unknown; +@@ -1719,21 +1990,21 @@ + case 'K': /* EL -- Clear line */ + switch (csiescseq.arg[0]) { + case 0: /* right */ +- tclearregion(term.c.x, term.c.y, term.col-1, +- term.c.y); ++ tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); + break; + case 1: /* left */ +- tclearregion(0, term.c.y, term.c.x, term.c.y); ++ tclearregion(0, term.c.y, term.c.x, term.c.y, 1); + break; + case 2: /* all */ +- tclearregion(0, term.c.y, term.col-1, term.c.y); ++ tclearregion(0, term.c.y, term.col-1, term.c.y, 1); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + if (csiescseq.priv) break; + DEFAULT(csiescseq.arg[0], 1); +- tscrollup(term.top, csiescseq.arg[0]); ++ /* xterm, urxvt, alacritty save this in history */ ++ tscrollup(term.top, term.bot, csiescseq.arg[0], SCROLL_SAVEHIST); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); +@@ -1751,9 +2022,11 @@ + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ ++ if (csiescseq.arg[0] < 0) ++ return; + DEFAULT(csiescseq.arg[0], 1); +- tclearregion(term.c.x, term.c.y, +- term.c.x + csiescseq.arg[0] - 1, term.c.y); ++ x = MIN(term.c.x + csiescseq.arg[0], term.col) - 1; ++ tclearregion(term.c.x, term.c.y, x, term.c.y, 1); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); +@@ -1779,9 +2052,9 @@ + ttywrite("\033[0n", sizeof("\033[0n") - 1, 0); + break; + case 6: /* Report Cursor Position (CPR) ";R" */ +- len = snprintf(buf, sizeof(buf), "\033[%i;%iR", ++ n = snprintf(buf, sizeof(buf), "\033[%i;%iR", + term.c.y+1, term.c.x+1); +- ttywrite(buf, len, 0); ++ ttywrite(buf, n, 0); + break; + default: + goto unknown; +@@ -1995,6 +2268,61 @@ + } + + void ++externalpipe(const Arg *arg) ++{ ++ int to[2]; ++ char buf[UTF_SIZ]; ++ void (*oldsigpipe)(int); ++ Glyph *bp, *end; ++ int lastpos, n, newline; ++ ++ if (pipe(to) == -1) ++ return; ++ ++ switch (fork()) { ++ case -1: ++ close(to[0]); ++ close(to[1]); ++ return; ++ case 0: ++ dup2(to[0], STDIN_FILENO); ++ close(to[0]); ++ close(to[1]); ++ execvp(((char **)arg->v)[0], (char **)arg->v); ++ fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]); ++ perror("failed"); ++ exit(0); ++ } ++ ++ close(to[0]); ++ /* ignore sigpipe for now, in case child exists early */ ++ oldsigpipe = signal(SIGPIPE, SIG_IGN); ++ newline = 0; ++ for (n = 0; n <= HISTSIZE + 2; n++) { ++ bp = TLINE_HIST(n); ++ lastpos = MIN(tlinehistlen(n) + 1, term.col) - 1; ++ if (lastpos < 0) ++ break; ++ if (lastpos == 0) ++ continue; ++ end = &bp[lastpos + 1]; ++ for (; bp < end; ++bp) ++ if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0) ++ break; ++ if ((newline = TLINE_HIST(n)[lastpos].mode & ATTR_WRAP)) ++ continue; ++ if (xwrite(to[1], "\n", 1) < 0) ++ break; ++ newline = 0; ++ } ++ if (newline) ++ (void)xwrite(to[1], "\n", 1); ++ close(to[1]); ++ /* restore */ ++ signal(SIGPIPE, oldsigpipe); ++} ++ ++void + strdump(void) + { + size_t i; +@@ -2079,16 +2407,8 @@ + void + tdumpline(int n) + { +- char buf[UTF_SIZ]; +- const Glyph *bp, *end; +- +- bp = &term.line[n][0]; +- end = &bp[MIN(tlinelen(n), term.col) - 1]; +- if (bp != end || bp->u != ' ') { +- for ( ; bp <= end; ++bp) +- tprinter(buf, utf8encode(bp->u, buf)); +- } +- tprinter("\n", 1); ++ char str[(term.col + 1) * UTF_SIZ]; ++ tprinter(str, tgetline(str, &term.line[n][0])); + } + + void +@@ -2309,7 +2629,7 @@ + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { +- tscrollup(term.top, 1); ++ tscrollup(term.top, term.bot, 1, SCROLL_SAVEHIST); + } else { + tmoveto(term.c.x, term.c.y+1); + } +@@ -2466,7 +2786,9 @@ + */ + return; + } +- if (selected(term.c.x, term.c.y)) ++ ++ /* selected() takes relative coordinates */ ++ if (selected(term.c.x + term.scr, term.c.y + term.scr)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; +@@ -2506,6 +2828,7 @@ + if (term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + } else { ++ term.wrapcwidth[IS_SET(MODE_ALTSCREEN)] = width; + term.c.state |= CURSOR_WRAPNEXT; + } + } +@@ -2543,85 +2866,284 @@ + } + + void ++rscrolldown(int n) ++{ ++ int i; ++ Line temp; ++ ++ /* can never be true as of now ++ if (IS_SET(MODE_ALTSCREEN)) ++ return; */ ++ ++ if ((n = MIN(n, term.histf)) <= 0) ++ return; ++ ++ for (i = term.c.y + n; i >= n; i--) { ++ temp = term.line[i]; ++ term.line[i] = term.line[i-n]; ++ term.line[i-n] = temp; ++ } ++ for (/*i = n - 1 */; i >= 0; i--) { ++ temp = term.line[i]; ++ term.line[i] = term.hist[term.histi]; ++ term.hist[term.histi] = temp; ++ term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE; ++ } ++ term.c.y += n; ++ term.histf -= n; ++ if ((i = term.scr - n) >= 0) { ++ term.scr = i; ++ } else { ++ term.scr = 0; ++ if (sel.ob.x != -1 && !sel.alt) ++ selmove(-i); ++ } ++} ++ ++ ++ ++void + tresize(int col, int row) + { +- int i; +- int minrow = MIN(row, term.row); +- int mincol = MIN(col, term.col); + int *bp; +- TCursor c; + ++ /* col and row are always MAX(_, 1) + if (col < 1 || row < 1) { +- fprintf(stderr, +- "tresize: error resizing to %dx%d\n", col, row); ++ fprintf(stderr, "tresize: error resizing to %dx%d\n", col, row); + return; +- } ++ } */ + +- /* +- * slide screen to keep cursor where we expect it - +- * tscrollup would work here, but we can optimize to +- * memmove because we're freeing the earlier lines +- */ +- for (i = 0; i <= term.c.y - row; i++) { +- free(term.line[i]); +- free(term.alt[i]); +- } +- /* ensure that both src and dst are not NULL */ +- if (i > 0) { +- memmove(term.line, term.line + i, row * sizeof(Line)); +- memmove(term.alt, term.alt + i, row * sizeof(Line)); +- } +- for (i += row; i < term.row; i++) { +- free(term.line[i]); +- free(term.alt[i]); +- } +- +- /* resize to new height */ +- term.line = xrealloc(term.line, row * sizeof(Line)); +- term.alt = xrealloc(term.alt, row * sizeof(Line)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); ++ if (col > term.col) { ++ bp = term.tabs + term.col; ++ memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); ++ while (--bp > term.tabs && !*bp) ++ /* nothing */ ; ++ for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) ++ *bp = 1; ++ } ++ ++ if (IS_SET(MODE_ALTSCREEN)) ++ tresizealt(col, row); ++ else ++ tresizedef(col, row); ++} ++ ++ ++void ++tresizedef(int col, int row) ++{ ++ int i, j; + +- /* 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)); +- term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph)); +- } +- +- /* allocate any new rows */ +- for (/* i = minrow */; i < row; i++) { +- term.line[i] = xmalloc(col * sizeof(Glyph)); +- term.alt[i] = xmalloc(col * sizeof(Glyph)); +- } +- if (col > term.col) { +- bp = term.tabs + term.col; +- +- memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); +- while (--bp > term.tabs && !*bp) +- /* nothing */ ; +- for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) +- *bp = 1; ++ /* return if dimensions haven't changed */ ++ if (term.col == col && term.row == row) { ++ tfulldirt(); ++ return; ++ } ++ if (col != term.col) { ++ if (!sel.alt) ++ selremove(); ++ treflow(col, row); ++ } else { ++ /* slide screen up if otherwise cursor would get out of the screen */ ++ if (term.c.y >= row) { ++ tscrollup(0, term.row - 1, term.c.y - row + 1, SCROLL_RESIZE); ++ term.c.y = row - 1; ++ } ++ for (i = row; i < term.row; i++) ++ free(term.line[i]); ++ ++ /* resize to new height */ ++ term.line = xrealloc(term.line, row * sizeof(Line)); ++ /* allocate any new rows */ ++ for (i = term.row; i < row; i++) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ for (j = 0; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); ++ } ++ /* scroll down as much as height has increased */ ++ rscrolldown(row - term.row); + } + /* update terminal size */ +- term.col = col; +- term.row = row; ++ term.col = col, term.row = row; + /* reset scrolling region */ +- tsetscroll(0, row-1); +- /* make use of the LIMIT in tmoveto */ +- tmoveto(term.c.x, term.c.y); +- /* Clearing both screens (it makes dirty all lines) */ +- c = term.c; +- for (i = 0; i < 2; i++) { +- if (mincol < col && 0 < minrow) { +- tclearregion(mincol, 0, col - 1, minrow - 1); +- } +- if (0 < col && minrow < row) { +- tclearregion(0, minrow, col - 1, row - 1); +- } +- tswapscreen(); +- tcursor(CURSOR_LOAD); +- } +- term.c = c; ++ term.top = 0, term.bot = row - 1; ++ /* dirty all lines */ ++ tfulldirt(); ++} ++ ++ ++ ++void ++tresizealt(int col, int row) ++{ ++ int i, j; ++ ++ /* return if dimensions haven't changed */ ++ if (term.col == col && term.row == row) { ++ tfulldirt(); ++ return; ++ } ++ if (sel.alt) ++ selremove(); ++ /* slide screen up if otherwise cursor would get out of the screen */ ++ for (i = 0; i <= term.c.y - row; i++) ++ free(term.line[i]); ++ if (i > 0) { ++ /* ensure that both src and dst are not NULL */ ++ memmove(term.line, term.line + i, row * sizeof(Line)); ++ term.c.y = row - 1; ++ } ++ for (i += row; i < term.row; i++) ++ free(term.line[i]); ++ /* resize to new height */ ++ term.line = xrealloc(term.line, row * sizeof(Line)); ++ /* resize to new width */ ++ for (i = 0; i < MIN(row, term.row); i++) { ++ term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph)); ++ for (j = term.col; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); ++ } ++ /* allocate any new rows */ ++ for (/*i = MIN(row, term.row) */; i < row; i++) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ for (j = 0; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); ++ } ++ /* update cursor */ ++ if (term.c.x >= col) { ++ term.c.state &= ~CURSOR_WRAPNEXT; ++ term.c.x = col - 1; ++ } else { ++ UPDATEWRAPNEXT(1, col); ++ } ++ /* update terminal size */ ++ term.col = col, term.row = row; ++ /* reset scrolling region */ ++ term.top = 0, term.bot = row - 1; ++ /* dirty all lines */ ++ tfulldirt(); ++ } ++ ++ ++ ++ ++ ++void ++treflow(int col, int row) ++{ ++ int i, j; ++ int oce, nce, bot, scr; ++ int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; ++ int cy = -1; /* proxy for new y coordinate of cursor */ ++ int nlines; ++ Line *buf, line; ++ ++ /* y coordinate of cursor line end */ ++ for (oce = term.c.y; oce < term.row - 1 && ++ tiswrapped(term.line[oce]); oce++); ++ ++ nlines = term.histf + oce + 1; ++ if (col < term.col) { ++ /* each line can take this many lines after reflow */ ++ j = (term.col + col - 1) / col; ++ nlines = j * nlines; ++ if (nlines > HISTSIZE + RESIZEBUFFER + row) { ++ nlines = HISTSIZE + RESIZEBUFFER + row; ++ oy = -(nlines / j - oce - 1); ++ } ++ } ++ buf = xmalloc(nlines * sizeof(Line)); ++ do { ++ if (!nx) ++ buf[++ny] = xmalloc(col * sizeof(Glyph)); ++ if (!ox) { ++ line = TLINEABS(oy); ++ len = tlinelen(line); ++ } ++ if (oy == term.c.y) { ++ if (!ox) ++ len = MAX(len, term.c.x + 1); ++ /* update cursor */ ++ if (cy < 0 && term.c.x - ox < col - nx) { ++ term.c.x = nx + term.c.x - ox, cy = ny; ++ UPDATEWRAPNEXT(0, col); ++ } ++ } ++ /* get reflowed lines in buf */ ++ if (col - nx > len - ox) { ++ memcpy(&buf[ny][nx], &line[ox], (len-ox) * sizeof(Glyph)); ++ nx += len - ox; ++ if (len == 0 || !(line[len - 1].mode & ATTR_WRAP)) { ++ for (j = nx; j < col; j++) ++ tclearglyph(&buf[ny][j], 0); ++ nx = 0; ++ } else if (nx > 0) { ++ buf[ny][nx - 1].mode &= ~ATTR_WRAP; ++ } ++ ox = 0, oy++; ++ } else if (col - nx == len - ox) { ++ memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); ++ ox = 0, oy++, nx = 0; ++ } else/* if (col - nx < len - ox) */ { ++ memcpy(&buf[ny][nx], &line[ox], (col-nx) * sizeof(Glyph)); ++ ox += col - nx; ++ buf[ny][col - 1].mode |= ATTR_WRAP; ++ nx = 0; ++ } ++ } while (oy <= oce); ++ if (nx) ++ for (j = nx; j < col; j++) ++ tclearglyph(&buf[ny][j], 0); ++ ++ /* free extra lines */ ++ for (i = row; i < term.row; i++) ++ free(term.line[i]); ++ /* resize to new height */ ++ term.line = xrealloc(term.line, row * sizeof(Line)); ++ ++ bot = MIN(ny, row - 1); ++ scr = MAX(row - term.row, 0); ++ /* update y coordinate of cursor line end */ ++ nce = MIN(oce + scr, bot); ++ /* update cursor y coordinate */ ++ term.c.y = nce - (ny - cy); ++ if (term.c.y < 0) { ++ j = nce, nce = MIN(nce + -term.c.y, bot); ++ term.c.y += nce - j; ++ while (term.c.y < 0) { ++ free(buf[ny--]); ++ term.c.y++; ++ } ++ } ++ /* allocate new rows */ ++ for (i = row - 1; i > nce; i--) { ++ term.line[i] = xmalloc(col * sizeof(Glyph)); ++ for (j = 0; j < col; j++) ++ tclearglyph(&term.line[i][j], 0); ++ } ++ /* fill visible area */ ++ for (/*i = nce */; i >= term.row; i--, ny--) ++ term.line[i] = buf[ny]; ++ for (/*i = term.row - 1 */; i >= 0; i--, ny--) { ++ free(term.line[i]); ++ term.line[i] = buf[ny]; ++ } ++ /* fill lines in history buffer and update term.histf */ ++ for (/*i = -1 */; ny >= 0 && i >= -HISTSIZE; i--, ny--) { ++ j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; ++ free(term.hist[j]); ++ term.hist[j] = buf[ny]; ++ } ++ term.histf = -i - 1; ++ term.scr = MIN(term.scr, term.histf); ++ /* resize rest of the history lines */ ++ for (/*i = -term.histf - 1 */; i >= -HISTSIZE; i--) { ++ j = (term.histi + i + 1 + HISTSIZE) % HISTSIZE; ++ term.hist[j] = xrealloc(term.hist[j], col * sizeof(Glyph)); ++ } ++ free(buf); + } + + void +@@ -2640,7 +3162,7 @@ + continue; + + term.dirty[y] = 0; +- xdrawline(term.line[y], x1, y, x2); ++ xdrawline(TLINE(y), x1, y, x2); + } + } + +diff '--color=auto' -Nu a/st.h b/st.h +--- a/st.h 2024-12-13 22:55:23.180773698 -0600 ++++ b/st.h 2024-12-13 23:05:25.285170829 -0600 +@@ -22,17 +22,19 @@ + + enum glyph_attribute { + ATTR_NULL = 0, +- ATTR_BOLD = 1 << 0, +- ATTR_FAINT = 1 << 1, +- ATTR_ITALIC = 1 << 2, +- ATTR_UNDERLINE = 1 << 3, +- ATTR_BLINK = 1 << 4, +- ATTR_REVERSE = 1 << 5, +- ATTR_INVISIBLE = 1 << 6, +- ATTR_STRUCK = 1 << 7, +- ATTR_WRAP = 1 << 8, +- ATTR_WIDE = 1 << 9, +- ATTR_WDUMMY = 1 << 10, ++ ATTR_SET = 1 << 0, ++ ATTR_BOLD = 1 << 1, ++ ATTR_FAINT = 1 << 2, ++ ATTR_ITALIC = 1 << 3, ++ ATTR_UNDERLINE = 1 << 4, ++ ATTR_BLINK = 1 << 5, ++ ATTR_REVERSE = 1 << 6, ++ ATTR_INVISIBLE = 1 << 7, ++ ATTR_STRUCK = 1 << 8, ++ ATTR_WRAP = 1 << 9, ++ ATTR_WIDE = 1 << 10, ++ ATTR_WDUMMY = 1 << 11, ++ ATTR_SELECTED = 1 << 12, + ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, + }; + +@@ -81,6 +83,9 @@ + void redraw(void); + void draw(void); + ++void kscrolldown(const Arg *); ++void kscrollup(const Arg *); ++void externalpipe(const Arg *); + void printscreen(const Arg *); + void printsel(const Arg *); + void sendbreak(const Arg *); +@@ -88,6 +93,7 @@ + + int tattrset(int); + void tnew(int, int); ++int tisaltscreen(void); + void tresize(int, int); + void tsetdirtattr(int); + void ttyhangup(void); +diff '--color=auto' -Nu a/x.c b/x.c +--- a/x.c 2024-12-13 22:55:23.180773698 -0600 ++++ b/x.c 2024-12-13 23:08:16.910433371 -0600 +@@ -81,6 +81,7 @@ + typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ ++ int hborderpx, vborderpx; + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ +@@ -331,7 +332,7 @@ + int + evcol(XEvent *e) + { +- int x = e->xbutton.x - borderpx; ++ int x = e->xbutton.x - win.hborderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; + } +@@ -339,7 +340,7 @@ + int + evrow(XEvent *e) + { +- int y = e->xbutton.y - borderpx; ++ int y = e->xbutton.y - win.vborderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; + } +@@ -739,6 +740,9 @@ + col = MAX(1, col); + row = MAX(1, row); + ++ win.hborderpx = (win.w - col * win.cw) / 2; ++ win.vborderpx = (win.h - row * win.ch) / 2; ++ + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +@@ -869,8 +873,8 @@ + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h; + sizeh->width = win.w; +- sizeh->height_inc = win.ch; +- sizeh->width_inc = win.cw; ++ sizeh->height_inc = 1; ++ sizeh->width_inc = 1; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; +@@ -1152,8 +1156,8 @@ + xloadcols(); + + /* adjust fixed window geometry */ +- win.w = 2 * borderpx + cols * win.cw; +- win.h = 2 * borderpx + rows * win.ch; ++ win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; ++ win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) +@@ -1245,7 +1249,7 @@ + int + xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) + { +- float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; ++ float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; +@@ -1378,7 +1382,7 @@ + xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) + { + int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); +- int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, ++ int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; +@@ -1468,17 +1472,17 @@ + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { +- xclear(0, (y == 0)? 0 : winy, borderpx, ++ xclear(0, (y == 0)? 0 : winy, win.hborderpx, + winy + win.ch + +- ((winy + win.ch >= borderpx + win.th)? win.h : 0)); ++ ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); + } +- if (winx + width >= borderpx + win.tw) { ++ if (winx + width >= win.hborderpx + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, +- ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); ++ ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) +- xclear(winx, 0, winx + width, borderpx); +- if (winy + win.ch >= borderpx + win.th) ++ xclear(winx, 0, winx + width, win.vborderpx); ++ if (winy + win.ch >= win.vborderpx + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ +@@ -1572,35 +1576,35 @@ + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, +- borderpx + cx * win.cw, +- borderpx + (cy + 1) * win.ch - \ ++ win.hborderpx + cx * win.cw, ++ win.vborderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, +- borderpx + cx * win.cw, +- borderpx + cy * win.ch, ++ win.hborderpx + cx * win.cw, ++ win.vborderpx + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, +- borderpx + cx * win.cw, +- borderpx + cy * win.ch, ++ win.hborderpx + cx * win.cw, ++ win.vborderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, +- borderpx + cx * win.cw, +- borderpx + cy * win.ch, ++ win.hborderpx + cx * win.cw, ++ win.vborderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, +- borderpx + (cx + 1) * win.cw - 1, +- borderpx + cy * win.ch, ++ win.hborderpx + (cx + 1) * win.cw - 1, ++ win.vborderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, +- borderpx + cx * win.cw, +- borderpx + (cy + 1) * win.ch - 1, ++ win.hborderpx + cx * win.cw, ++ win.vborderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } + }