25 Commits

Author SHA1 Message Date
66bd98c533 apply patch st-externalpipe-0.8.5.diff 2026-01-22 07:41:30 +01:00
wael
3e4d5728d3 fix xresources with signal reloading removing arg.h and st.h & remove unneccesary xresources variables(?) 2026-01-22 07:30:47 +01:00
6d00916036 apply patch st-meta-vim-full-20210425-43a395a-8.4.patch 2026-01-22 07:28:42 +01:00
Hiltjo Posthuma
6e97047474 bump version to 0.9.3 2025-08-09 14:35:14 +02:00
Hiltjo Posthuma
5a4666c19e add a few comments 2025-08-09 14:22:28 +02:00
Ayman Bagabas
d6c431859c Support OSC 110, 111, and 112 for resetting colors
This adds support for OSC 110, 111, and 112 escape sequences to reset
the foreground, background, and cursor colors in the terminal. The
changes include handling these sequences in the `strhandle` function of
`st.c`, allowing applications to reset colors to their default values.

The OSC sequences originated from Xterm control sequences and are now
widely used in terminal applications and supported by many terminal
emulators. For applications, this allows them to reset colors to
default values without needing to know the colors beforehand.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
2025-08-09 12:34:01 +02:00
sasha
f114bcedd1 Eat up "CSI 58" sequences
This is used in the wild by systemd systemctl for example and st
misinterpreted it as "blink", because it didn't know "58", then saw "5"
as "blink", and then didn't know "245".

This should print "foo" as normal text:

    printf '\e[58:5:245mfoo\n'
    printf '\e[58:2:50:100:200mfoo\n'
2025-07-27 20:06:54 +02:00
Johannes Altmanninger
98610fcd37 Do not interpret CSI ? u as DECRC
The kitty keyboard protocol docs recommend CSI ? u to query support for
that protocol, see https://sw.kovidgoyal.net/kitty/keyboard-protocol/

For better or worse, fish shell uses this query to work around bugs
in other terminals triggered by requesting that protocol via CSI = 5 u.

Unfortunately, st interprets CSI ? u as DECRC (restore cursor
position). reproduce with 'printf "\x1b[?u"; cat'.

fish could work around this by switching to the alternate screen
before running this query; but that might cause tearing on terminals
that don't support Synchronized Output. I'm not sure.

In the meantime, let's correct our parser.

This adds a redundant else-after-return, for consistency with the
surrounding code.
2025-01-30 17:50:37 +01:00
Markus Rinne
6009e6e25b Clear screen: Fix edge case
With sequence \e[1J, if cursor is on second line, clear the first line.
2024-12-06 13:42:50 +01:00
Lucas de Sena
a0274bc20e fix BadMatch error when embedding on some windows
When embedded, st fails with BadMatch error if the embedder's window has
non-default colormap/depth/visual.  This commit fixes that by creating
st's window inside root and then reparent it into embedder.

The reference window for dc.gc is also changed to match root's visuals.

A similar commit had been made for dmenu[1].
See this issue[2] on github for context.

[1]: https://git.suckless.org/dmenu/commit/0fe460dbd469a1d5b6a7140d0e1801935e4a923b.html
[2]: https://github.com/phillbush/xfiles/issues/47
2024-08-09 13:34:56 +02:00
Hiltjo Posthuma
5dbcca4926 support colons in SGR character attributes
Patch by Mikhail Kot <to@myrrc.dev>
With some modifications to behave more like xterm (see note below).

Example:

	printf '\033[48;2;255:0:0mtest\n'

https://invisible-island.net/xterm/ctlseqs/ctlseqs.html

Some notes:

"CSI Pm m  Character Attributes (SGR).
[...]
o   xterm allows either colons (standard) or semicolons
(legacy) to separate the subparameters (but after the
first colon, colons must be used).
2024-05-01 20:45:39 +02:00
Hiltjo Posthuma
d63b9eb902 bump version to 0.9.2 2024-04-05 12:18:41 +02:00
DOGMAN
497a756382 Reset title when an empty title string is given
With this patch, st will reset its window title when an empty string is
given as the terminal title. For example:
	printf "\033]0;\007"

Some applications, like termdown, expect this functionality. xterm
implements it, but it seems that most other terminal emulators don't.
In any case, I don't see why there should ever be a case where the st
window doesn't have a title property.
2024-04-03 19:49:05 +02:00
Hiltjo Posthuma
8c68ec5241 Revert "Fix cursor move with wide glyphs"
This reverts commit 7473a8d1a5.

This patch needs some more work. It caused regressions with programs that use
GNU readline, etc.

Original test-case example from Tim Culverhouse <tim@timculverhouse.com>:

	printf " 😀" && sleep 2 && printf "\e[D" && sleep 2 && printf "\e[D" && sleep 2

After the patch it caused regressions, example test-case:

	printf "A字\bB\n"
2024-03-30 12:37:06 +01:00
Hiltjo Posthuma
5ce9716281 bump version to 0.9.1 2024-03-19 12:13:42 +01:00
Hiltjo Posthuma
f20e169a20 config.def.h: improve latency for the default configuration 2024-03-17 14:42:44 +01:00
Tommi Hirvola
95f22c5305 set upper limit for REP escape sequence argument
Previously, printf 'L\033[2147483647b' would call tputc('L') 2^31 times,
making st unresponsive. This commit allows repeating the last character
at most 65535 times in order to prevent freezing and DoS attacks.
2024-03-04 23:50:58 +01:00
Quentin Rameau
7473a8d1a5 Fix cursor move with wide glyphs
st would always move back 1 column,
even with wide glyhps (using more than a single column).

The glyph rune is set on its first column,
and the other ones are to 0,
so loop until we detect the start of the previous glyph.
2024-02-25 11:56:43 +01:00
Tim Culverhouse
a3f7420310 csi: check for private marker in 'S' case
The handler for 'S' final character does not check for a private
marker. This can cause a conflict with a sequence called 'XTSMGRAPHICS'
which also has an 'S' final character, but uses the private marker '?'.
Without checking for a private marker, st will perform a scroll up
operation when XTSMGRAPHICS is seen, which can cause unexpected display
artifacts.
2024-02-18 16:14:26 +01:00
Peter Hofmann
9846a56bd7 Add terminfo entries for bracketed paste mode
Helps Vim (and hopefully others) to discover that this feature exists
without further user configuration.
2023-10-07 12:16:59 +02:00
Peter Hofmann
559fdc2786 Unhide cursor on RIS (\033c)
It is unclear if it's "required" to do this on RIS, but it's useful when
calling reset(1) after interactive programs have crashed and garbled up
the screen.

FWIW, other terminals do it as well (tested with XTerm, VTE, Kitty,
Alacritty, Linux VT).
2023-10-07 12:16:59 +02:00
Peter Hofmann
8abe4bcb41 Fix wide glyphs breaking "nowrap" mode
Consider the following example:

    printf '\e[?7l';\
    for i in $(seq $(($(tput cols) - 1))); do printf a; done;\
    printf '🙈\n';\
    printf '\e[?7h'

Even though MODE_WRAP has been disabled, the emoji appeared on the next
line. This patch keeps wide glyphs on the same line and moves them to
the right-most possible position.
2023-10-07 12:16:59 +02:00
Peter Hofmann
2fc7e532b2 Don't scroll selection on the other screen
Fixes garbage selections when switching to/from the alternate screen.

How to reproduce:

-   Be in primary screen.
-   Select something.
-   Run this (switches to alternate screen, positions the cursor at the
    bottom, triggers selscroll(), and then goes back to primary screen):

        tput smcup; tput cup $(tput lines) 0; echo foo; tput rmcup

-   Notice how the (visual) selection now covers a different line.

The reason is that selscroll() calls selnormalize() and that cannot find
the original range anymore. It's all empty lines now, so it snaps to
"select the whole line".
2023-10-07 12:16:59 +02:00
Peter Hofmann
a6bbc0c96b Fix bounds checks of dc.col
dc.collen is the length of dc.col, not the maximum index, hence if x is
equal to dc.collen, then it's an error.

With config.def.h, the last valid index is 259, so this correctly
reports "black":

    $ printf '\033]4;259;?\e\\'

260 is an invalid index and this reports garbage instead of printing an
error:

    $ printf '\033]4;260;?\e\\'
2023-10-07 12:16:59 +02:00
Hiltjo Posthuma
eb3b894f40 Makefile: remove the options target
The Makefile used to suppress output (by using @), so this target made sense at
the time.

But the Makefile should be simple and make debugging with less abstractions or
fancy printing.  The Makefile was made verbose and doesn't hide the build
output, so remove this target.

Prompted by a question on the mailing list about the options target.
2023-09-22 15:16:52 +02:00
11 changed files with 806 additions and 213 deletions

View File

@@ -7,13 +7,7 @@ include config.mk
SRC = st.c x.c SRC = st.c x.c
OBJ = $(SRC:.c=.o) OBJ = $(SRC:.c=.o)
all: options st all: st
options:
@echo st build options:
@echo "CFLAGS = $(STCFLAGS)"
@echo "LDFLAGS = $(STLDFLAGS)"
@echo "CC = $(CC)"
config.h: config.h:
cp config.def.h config.h cp config.def.h config.h
@@ -21,7 +15,7 @@ config.h:
.c.o: .c.o:
$(CC) $(STCFLAGS) -c $< $(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h st.o: config.h st.h win.h normalMode.h normalMode.c utils.h
x.o: arg.h config.h st.h win.h x.o: arg.h config.h st.h win.h
$(OBJ): config.h config.mk $(OBJ): config.h config.mk
@@ -54,4 +48,4 @@ uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st rm -f $(DESTDIR)$(PREFIX)/bin/st
rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
.PHONY: all options clean dist install uninstall .PHONY: all clean dist install uninstall

View File

@@ -53,7 +53,7 @@ int allowwindowops = 0;
* near minlatency, but it waits longer for slow updates to avoid partial draw. * 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. * low minlatency will tear/flicker more, as it can "detect" idle too early.
*/ */
static double minlatency = 8; static double minlatency = 2;
static double maxlatency = 33; static double maxlatency = 33;
/* /*
@@ -133,6 +133,21 @@ unsigned int defaultfg = 258;
unsigned int defaultbg = 259; unsigned int defaultbg = 259;
unsigned int defaultcs = 256; unsigned int defaultcs = 256;
static unsigned int defaultrcs = 257; static unsigned int defaultrcs = 257;
unsigned int const currentBg = 6, buffSize = 2048;
/// Enable double / triple click yanking / selection of word / line.
int const mouseYank = 1, mouseSelect = 0;
/// [Vim Browse] Colors for search results currently on screen.
unsigned int const highlightBg = 160, highlightFg = 15;
char const wDelS[] = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", wDelL[] = " \t";
char *nmKeys [] = { ///< Shortcusts executed in normal mode
"R/Building\nN", "r/Building\n", "X/juli@machine\nN", "x/juli@machine\n",
"Q?[Leaving vim, starting execution]\n","F/: error:\nN", "f/: error:\n", "DQf"
};
unsigned int const amountNmKeys = sizeof(nmKeys) / sizeof(*nmKeys);
/// Style of the {command, search} string shown in the right corner (y,v,V,/)
Glyph styleSearch = {' ', ATTR_ITALIC | ATTR_BOLD_FAINT, 7, 16};
Glyph style[] = {{' ',ATTR_ITALIC|ATTR_FAINT,15,16}, {' ',ATTR_ITALIC,232,11},
{' ', ATTR_ITALIC, 232, 4}, {' ', ATTR_ITALIC, 232, 12}};
/* /*
* Default shape of cursor * Default shape of cursor
@@ -201,6 +216,7 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_Y, selpaste, {.i = 0} }, { TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
{ MODKEY, XK_c, normalMode, {.i = 0} },
}; };
/* /*

View File

@@ -1,5 +1,5 @@
# st version # st version
VERSION = 0.9 VERSION = 0.9.3
# Customize below to fit your system # Customize below to fit your system

284
normalMode.c Normal file
View File

@@ -0,0 +1,284 @@
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include "normalMode.h"
#include "utils.h"
extern Glyph const styleSearch, style[];
extern char const wDelS[], wDelL[], *nmKeys[];
extern unsigned int bg[], fg, currentBg, highlightBg, highlightFg, amountNmKeys;
typedef struct { int p[3]; } Pos;
typedef enum {visual='v', visualLine='V', yank = 'y'} Op;
typedef enum {infix_none=0, infix_i='i', infix_a='a'} Infix;
typedef enum {fw='/', bw='?'} Search;
struct NormalModeState {
struct OperationState { Op op; Infix infix; } cmd;
struct MotionState { uint32_t c; int active; Pos searchPos; Search search; } m;
} defaultNormalMode, state;
DynamicArray searchStr=UTF8_ARRAY, cCmd=UTF8_ARRAY, lCmd=UTF8_ARRAY;
Glyph styleCmd;
char posBuffer[10], braces[6][3] = { {"()"}, {"<>"}, {"{}"}, {"[]"}, {"\"\""}, {"''"}};
int exited=1, overlay=1;
static inline Rune cChar() { return term.line[term.c.y][term.c.x].u; }
static inline int pos(int p, int h) {return IS_SET(MODE_ALTSCREEN)?p:rangeY(p+h*histOff-insertOff);}
static inline int contains(Rune l, char const * values, size_t const memSize) {
for (uint32_t i = 0; i < memSize; ++i) if (l == values[i]) return 1;
return 0;
}
static inline void decodeTo(char const *cs, size_t len, DynamicArray *arr) {
char *var = expand(arr);
if (!var) empty(arr); else utf8decode(cs, (Rune*)(var), len);
}
static inline void applyPos(Pos p) {
term.c.x = p.p[0], term.c.y = p.p[1];
if (!IS_SET(MODE_ALTSCREEN) && histOp) term.line = &buf[histOff = p.p[2]];
}
/// Find string in history buffer, and provide string-match-lookup for highlighting matches
static int highlighted(int x, int y) {
int const s=term.row*term.col, i=y*term.col+x, sz=size(&searchStr);
return sz && i<s && mark[i]!=sz && i+mark[i]<s && !mark[i+mark[i]];
}
static void markSearchMatches(int all) {
int sz = size(&searchStr), ox = 0, oy = 0, oi=0;
for (int y=0; sz && all && y<term.row; ++y)
for (int x=0; x<term.col; ++x) term.dirty[y] |= highlighted(x, y);
for (int y = 0, wi=0, owi=0, i=0; sz && y < term.row; ++y)
for (int x=0; x<term.col; ++x, wi%=sz, ++i, owi=wi)
if (all || term.dirty[y]) {
mark[i]=sz-(wi=(getU32(&searchStr,wi,1)==term.line[y][x].u?wi+1:0));
if (wi==1) ox=x, oy=y, oi=i; else if (!wi && owi) x=ox, y=oy, i=oi;
}
for (int y=0; sz &&all &&y<term.row; ++y)
for (int x=0; x<term.col; ++x) term.dirty[y] |= highlighted(x, y);
}
static int findString(int s, int all) {
Pos p = (Pos) {.p={term.c.x, term.c.y, IS_SET(MODE_ALTSCREEN) ? 0 : histOff}};
historyMove(s, 0, 0);
uint32_t strSz=size(&searchStr), maxIter=rows()*term.col+strSz, wIdx=0;
for (uint32_t i=0, wi = 0; wIdx<strSz && ++i<=maxIter; historyMove(s, 0, 0), wi=wIdx) {
wIdx = (getU32(&searchStr, wIdx, s>0)==cChar())?wIdx+1:0;
if (wi && !wIdx) historyMove(-(int)(s*wi), 0, 0);
}
if (wIdx == strSz && wIdx) historyMove(-(int)(s*strSz), 0, 0);
else applyPos(p);
markSearchMatches(all);
return wIdx == strSz;
}
/// Execute series of normal-mode commands from char array / decoded from dynamic array
ExitState pressKeys(char const* s, size_t e) {
ExitState x=success;
for (size_t i=0; i<e && (x=(!s[i] ? x : kPressHist(&s[i], 1, 0, NULL))); ++i);
return x;
}
static ExitState executeCommand(uint32_t *cs, size_t z) {
ExitState x=success;
char dc [32];
for (size_t i=0; i<z && (x=kPressHist(dc, utf8encode(cs[i],dc),0,NULL));++i);
return x;
}
/// Get character for overlay, if the overlay (st) has something to show, else normal char.
static void getChar(DynamicArray *st, Glyph *glyphChange, int y, int xEnd, int width, int x) {
if (x < xEnd - min(min(width,xEnd), size(st))) *glyphChange = term.line[y][x];
else if (x<xEnd) glyphChange->u = *((Rune*)(st->content + (size(st)+x-xEnd)*st->elSize));
}
/// Expand "infix" expression: for instance (w =>) l b | | v e | | y
static ExitState expandExpression(char l) { // ({ =>) l ? { \n | l | v / } \n | h | y
int a=state.cmd.infix==infix_a, yank=state.cmd.op=='y', lc=tolower(l), found=1;
state.cmd.infix = infix_none;
if(!yank && state.cmd.op!=visual && state.cmd.op!=visualLine) return failed;
char mot[11] = {'l', 0, 'b', 0, 0, 'v', 0, 'e', 0, 0, (char)(yank ? 'y' : 0)};
if (lc == 'w') mot[2] = (char) ('b' - lc + l), mot[7] = (char) ((a ? 'w' : 'e') - lc + l), mot[9]=(char)(a?'h':0);
else {
mot[1]='?', mot[3]=mot[8]='\n', mot[6]='/', mot[4]=(char)(a?0:'l'), mot[9]=(char)(a?0:'h');
for (int i=found=0; !found && i < 6; ++i)
if ((found=contains(l,braces[i],2))) mot[2]=braces[i][0], mot[7]=braces[i][1];
}
if (!found) return failed;
assign(&lCmd, &cCmd);
empty(&cCmd);
state.cmd = defaultNormalMode.cmd;
return pressKeys(mot, 11);
}
ExitState executeMotion(char const cs, KeySym const *const ks) {
state.m.c = state.m.c < 1u ? 1u : state.m.c;
if (ks && *ks == XK_d) historyMove(0, 0, term.row / 2);
else if (ks && *ks == XK_u) historyMove(0, 0, -term.row / 2);
else if (ks && *ks == XK_f) historyMove(0, 0, term.row-1+(term.c.y=0));
else if (ks && *ks == XK_b) historyMove(0, 0, -(term.c.y=term.row-1));
else if (ks && *ks == XK_h) overlay = !overlay;
else if (cs == 'K') historyMove(0, 0, -(int)state.m.c);
else if (cs == 'J') historyMove(0, 0, (int)state.m.c);
else if (cs == 'k') historyMove(0, -(int)state.m.c, 0);
else if (cs == 'j') historyMove(0, (int)state.m.c, 0);
else if (cs == 'h') historyMove(-(int)state.m.c, 0, 0);
else if (cs == 'l') historyMove( (int)state.m.c, 0, 0);
else if (cs == 'H') term.c.y = 0;
else if (cs == 'M') term.c.y = term.bot / 2;
else if (cs == 'L') term.c.y = term.bot;
else if (cs == 's' || cs == 'S') altToggle = cs == 's' ? !altToggle : 1;
else if (cs == 'G' || cs == 'g') {
if (cs == 'G') term.c = c[0] = c[IS_SET(MODE_ALTSCREEN)+1];
if (!IS_SET(MODE_ALTSCREEN)) term.line = &buf[histOff=insertOff];
} else if (cs == '0') term.c.x = 0;
else if (cs == '$') term.c.x = term.col-1;
else if (cs == 't') sel.type = sel.type==SEL_REGULAR ? SEL_RECTANGULAR : SEL_REGULAR;
else if (cs == 'n' || cs == 'N') {
int const d = ((cs=='N')!=(state.m.search==bw))?-1:1;
for (uint32_t i = state.m.c; i && findString(d, 0); --i);
} else if (contains(cs, "wWeEbB", 6)) {
int const low=cs<=90, off=tolower(cs)!='w', sgn=(tolower(cs)=='b')?-1:1;
size_t const l=strlen(wDelL), s=strlen(wDelS), maxIt=rows()*term.col;
for (int it=0, on=0; state.m.c > 0 && it < maxIt; ++it) {
// If an offset is to be performed in beginning or not in beginning, move in history.
if ((off || it) && historyMove(sgn, 0, 0)) break;
// Determine if the category of the current letter changed since last iteration.
int n = 1<<(contains(cChar(),wDelS,s) ?(2-low) :!contains(cChar(),wDelL,l)),
found = (on|=n)^n && ((off ?on^n :n)!=1);
// If a reverse offset is to be performed and this is the last letter:
if (found && off) historyMove(-sgn, 0, 0);
// Terminate iteration: reset #it and old n value #on and decrease operation count:
if (found) it=-1, on=0, --state.m.c;
}
} else return failed;
state.m.c = 0;
return state.cmd.op == yank ? exitMotion : success;
}
ExitState kPressHist(char const *cs, size_t len, int ctrl, KeySym const *kSym) {
historyOpToggle(1, 1);
int const prevYOff=IS_SET(MODE_ALTSCREEN)?0:histOff, search=state.m.search&&state.m.active,
prevAltToggle=altToggle, prevOverlay=overlay;
int const noOp=!state.cmd.op&&!state.cmd.infix, num=len==1&&BETWEEN(cs[0],48,57),
esc=kSym&&*kSym==XK_Escape, ret=(kSym&&*kSym==XK_Return)||(len==1&&cs[0]=='\n'),
quantifier=num&&(cs[0]!='0'||state.m.c), ins=!search &&noOp &&len &&cs[0]=='i';
exited = 0;
ExitState result = success;
if (esc || ret || ins) { result = exitMotion, len = 0;
} else if (kSym && *kSym == XK_BackSpace) {
if ((search || state.m.c) && size(&cCmd)) pop(&cCmd);
if (search) {
if (size(&searchStr)) pop(&searchStr);
else result = exitMotion;
if (!size(&searchStr)) tfulldirt();
applyPos(state.m.searchPos);
findString(state.m.search==fw ? 1 : -1, 1);
} else if (state.m.c) state.m.c /= 10;
len = 0;
} else if (search) {
if (len >= 1) decodeTo(cs, len, &searchStr);
applyPos(state.m.searchPos);
findString(state.m.search==fw ? 1 : -1, 1);
} else if (len == 0) { result = failed;
} else if (quantifier) { state.m.c = min(SHRT_MAX, (int)state.m.c*10+cs[0]-48);
} else if (state.cmd.infix && state.cmd.op && (result = expandExpression(cs[0]), len=0)) {
} else if (cs[0] == 'd') { state = defaultNormalMode; result = exitMotion; state.m.active = 1;
} else if (cs[0] == '.') {
if (size(&cCmd)) assign(&lCmd, &cCmd);
empty(&cCmd);
executeCommand((uint32_t*) lCmd.content, size(&lCmd));
empty(&cCmd);
len = 0;
} else if (cs[0] == 'r') { tfulldirt();
} else if (cs[0] == 'c') {
empty(&lCmd);
empty(&cCmd);
empty(&searchStr);
tfulldirt();
len = 0;
} else if (cs[0] == fw || cs[0] == bw) {
empty(&searchStr);
state.m.search = (Search) cs[0];
state.m.searchPos = (Pos){.p={term.c.x, term.c.y, prevYOff}};
state.m.active = 1;
} else if (cs[0]==infix_i || cs[0]==infix_a) { state.cmd.infix=(Infix) cs[0];
} else if (cs[0] == 'y') {
if (state.cmd.op) {
result = (state.cmd.op == yank || state.cmd.op == visualLine) ? exitOp : exitMotion;
if (state.cmd.op == yank) selstart(0, term.c.y, 0);
} else selstart(term.c.x, term.c.y, 0);
state.cmd.op = yank;
} else if (cs[0] == visual || cs[0] == visualLine) {
if (state.cmd.op != (Op) cs[0]) {
state.cmd = defaultNormalMode.cmd;
state.cmd.op = (Op) cs[0];
selstart(cs[0] == visualLine ?0 :term.c.x, term.c.y, 0);
} else result = exitOp;
} else if (!(result =executeMotion((char) (len?cs[0]:0), ctrl?kSym:NULL))) {
result=failed;
for (size_t i = 0; !ctrl && i < amountNmKeys; ++i)
if (cs[0]==nmKeys[i][0] &&
failed!=(result=pressKeys(&nmKeys[i][1], strlen(nmKeys[i])-1))) goto end;
} // Operation/Motion finished if valid: update cmd string, extend selection, update search
if (result != failed) {
if (len == 1 && !ctrl) decodeTo(cs, len, &cCmd);
if ((state.cmd.op == visualLine) || ((state.cmd.op == yank) && (result == exitOp))) {
int const off = term.c.y + (IS_SET(MODE_ALTSCREEN) ? 0 : histOff) < sel.ob.y; //< Selection start below end.
sel.ob.x = off ? term.col - 1 : 0;
selextend(off ? 0 : term.col-1, term.c.y, sel.type, 0);
} else if (sel.oe.x != -1) {
selextend(term.c.x, term.c.y, sel.type, 0);
}
} // Set repaint for motion or status bar
if (!IS_SET(MODE_ALTSCREEN) && prevYOff != histOff) tfulldirt();
// Terminate Motion / operation if thus indicated
if (result == exitMotion) {
if (!state.m.active) result = (exited=noOp) ? finish : exitOp;
state.m.active = (int) (state.m.c = 0u);
}
if (result == exitOp || result == finish) {
if (state.cmd.op == yank) {
xsetsel(getsel());
xclipcopy();
}
state = defaultNormalMode;
selclear();
if (!esc) assign(&lCmd, &cCmd);
empty(&cCmd);
} // Update the content displayed in the history overlay
styleCmd = style[state.cmd.op==yank ? 1 : (state.cmd.op==visual ? 2 :
(state.cmd.op==visualLine ? 3 :0))];
int const posLin = !IS_SET(MODE_ALTSCREEN) ? rangeY(insertOff-histOff):0, h=rows()-term.row;
if (!posLin || posLin==h || !h) strcpy(posBuffer, posLin ? " [BOT] " : " [TOP] ");
else sprintf(posBuffer, " % 3d%c ", min(100, max(0, (int)(.5 + posLin * 100. / h))),'%');
if ((overlay || overlay!=prevOverlay) && term.col>9 && term.row>4) {
if (!term.dirty[term.row-1]) xdrawline(term.line[term.row-1], term.col*2/3, term.row-1, term.col-1);
if (!term.dirty[term.row-2]) xdrawline(term.line[term.row-2], term.col*2/3, term.row-2, term.col-1);
}
if (result==finish) altToggle = 0;
if (altToggle != prevAltToggle) tswapscreen();
end:
historyOpToggle(-1, 1);
return result;
}
void historyOverlay(int x, int y, Glyph* g) {
if (!histMode) return;
TCursor const *cHist = histOp ? &term.c : &c[0];
if(overlay && term.col > 9 && term.row > 4 && (x > (2*term.col/3)) && (y >= (term.row-2))) {
*g = (y == term.row - 2) ? styleSearch : styleCmd;
if (y == term.row-2) getChar(&searchStr, g, term.row-2, term.col-2, term.col/3, x);
else if (x > term.col - 7) g->u = (Rune)(posBuffer[x - term.col + 7]);
else getChar(size(&cCmd) ?&cCmd :&lCmd, g, term.row-1, term.col-7, term.col/3-6, x);
} else if (highlighted(x, y)) g->bg = highlightBg, g->fg = highlightFg;
else if ((x==cHist->x) ^ (y==cHist->y)) g->bg = currentBg;
else if (x==cHist->x) g->mode^=ATTR_REVERSE;
}
void historyPreDraw() {
static Pos op = {.p={0, 0, 0}};
historyOpToggle(1, 0);
// Draw the cursor cross if changed
if (term.c.y >= term.row || op.p[1] >= term.row) tfulldirt();
else if (exited || (op.p[1] != term.c.y)) term.dirty[term.c.y] = term.dirty[op.p[1]] = 1;
for (int i=0; (exited || term.c.x != op.p[0]) && i<term.row; ++i) if (!term.dirty[i]) {
xdrawline(term.line[i], term.c.x, i, term.c.x + 1);
xdrawline(term.line[i], op.p[0], i, op.p[0] + 1);
}
// Update search results either only for lines with new content or all results if exiting
markSearchMatches(exited);
op = (Pos){.p = {term.c.x, term.c.y, 0}};
historyOpToggle(-1, 0);
}

8
normalMode.h Normal file
View File

@@ -0,0 +1,8 @@
void normalMode();
void historyPreDraw();
void historyOverlay(int x, int y, Glyph* g);
void historyModeToggle(int start);
void historyOpToggle(int, int);
typedef enum {failed=0, success=1, exitMotion=2, exitOp=3, finish=4} ExitState;
ExitState kPressHist(char const *txt, size_t len, int ctrl, KeySym const *kSym);
ExitState pressKeys(char const* s, size_t e);

477
st.c
View File

@@ -1,4 +1,5 @@
/* See LICENSE for license details. */ /* See LICENSE for license details. */
#include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
@@ -42,6 +43,8 @@
#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
#define ISDELIM(u) (u && wcschr(worddelimiters, u)) #define ISDELIM(u) (u && wcschr(worddelimiters, u))
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; }
enum term_mode { enum term_mode {
MODE_WRAP = 1 << 0, MODE_WRAP = 1 << 0,
@@ -95,6 +98,7 @@ typedef struct {
int mode; int mode;
int type; int type;
int snap; int snap;
int swap;
/* /*
* Selection variables: * Selection variables:
* nb normalized coordinates of the beginning of the selection * nb normalized coordinates of the beginning of the selection
@@ -179,7 +183,6 @@ static void tdeleteline(int);
static void tinsertblank(int); static void tinsertblank(int);
static void tinsertblankline(int); static void tinsertblankline(int);
static int tlinelen(int); static int tlinelen(int);
static void tmoveto(int, int);
static void tmoveato(int, int); static void tmoveato(int, int);
static void tnewline(int); static void tnewline(int);
static void tputtab(int); static void tputtab(int);
@@ -206,7 +209,6 @@ static void drawregion(int, int, int, int);
static void selnormalize(void); static void selnormalize(void);
static void selscroll(int, int); static void selscroll(int, int);
static void selsnap(int *, int *, int);
static size_t utf8decode(const char *, Rune *, size_t); static size_t utf8decode(const char *, Rune *, size_t);
static Rune utf8decodebyte(char, size_t *); static Rune utf8decodebyte(char, size_t *);
@@ -232,6 +234,14 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
int buffCols;
extern int const buffSize;
int histOp, histMode, histOff, insertOff, altToggle, *mark;
Line *buf = NULL;
static TCursor c[3];
static inline int rows() { return IS_SET(MODE_ALTSCREEN) ? term.row : buffSize;}
static inline int rangeY(int i) { while (i < 0) i += rows(); return i % rows();}
ssize_t ssize_t
xwrite(int fd, const char *s, size_t len) xwrite(int fd, const char *s, size_t len)
{ {
@@ -418,6 +428,118 @@ tlinelen(int y)
return i; return i;
} }
void historyOpToggle(int start, int paint) {
if ((!histOp == !(histOp + start)) && ((histOp += start) || 1)) return;
if (histMode && paint && (!IS_SET(MODE_ALTSCREEN) || altToggle)) draw();
tcursor(CURSOR_SAVE);
histOp += start;
if (histMode && altToggle) {
tswapscreen();
memset(term.dirty,0,sizeof(*term.dirty)*term.row);
}
tcursor(CURSOR_LOAD);
*(!IS_SET(MODE_ALTSCREEN)?&term.line:&term.alt)=&buf[histOp?histOff:insertOff];
}
void historyModeToggle(int start) {
if (!(histMode = (histOp = !!start))) {
selnormalize();
tfulldirt();
} else {
tcursor(CURSOR_SAVE);
histOp = 0;
histOff = insertOff;
}
}
int historyBufferScroll(int n) {
if (IS_SET(MODE_ALTSCREEN) || !n) return histOp;
int p=abs(n=(n<0) ? max(n,-term.row) : min(n,term.row)), r=term.row-p,
s=sizeof(*term.dirty), *ptr=histOp?&histOff:&insertOff;
if (!histMode || histOp) tfulldirt(); else {
memmove(&term.dirty[-min(n,0)], &term.dirty[max(n,0)], s*r);
memset(&term.dirty[n>0 ? r : 0], 0, s * p);
}
term.line = &buf[*ptr = (buffSize+*ptr+n) % buffSize];
// Cut part of selection removed from buffer, and update sel.ne/b.
int const prevOffBuf = sel.alt ? 0 : insertOff + term.row;
if (sel.ob.x != -1 && !histOp && n) {
int const offBuf = sel.alt ? 0 : insertOff + term.row,
pb = rangeY(sel.ob.y - prevOffBuf),
pe = rangeY(sel.oe.y - prevOffBuf);
int const b = rangeY(sel.ob.y - offBuf), nln = n < 0,
e = rangeY(sel.oe.y - offBuf), last = offBuf - nln;
if (pb != b && ((pb < b) != nln)) sel.ob.y = last;
if (pe != e && ((pe < e) != nln)) sel.oe.y = last;
if (sel.oe.y == last && sel.ob.y == last) selclear();
}
selnormalize();
// Clear the new region exposed by the shift.
if (!histOp) tclearregion(0, n>0?r+1:0, buffCols-1, n>0?term.row:p-1);
return 1;
}
int historyMove(int x, int y, int ly) {
historyOpToggle(1, 1);
y += ((term.c.x += x) < 0 ?term.c.x-term.col :term.c.x) / term.col;//< x
if ((term.c.x %= term.col) < 0) term.c.x += term.col;
if ((term.c.y += y) >= term.row) ly += term.c.y - term.row + 1; //< y
else if (term.c.y < 0) ly += term.c.y;
term.c.y = MIN(MAX(term.c.y, 0), term.row - 1);
// Check if scroll is necessary / arrived at top / bottom of terminal history
int t = 0, b = 0, finTop = ly < 0, finBot = ly > 0;
if (!IS_SET(MODE_ALTSCREEN)) {
b=rangeY(insertOff-histOff), t=-rangeY(-term.row-(insertOff-histOff));
finBot = ly > b, finTop=histMode&&((-ly>-t));
}
if ((finTop || finBot) && (x||y)) term.c.x = finBot ? term.col-1 : 0;
historyBufferScroll(finBot ? b : (finTop ? t : ly));
historyOpToggle(-1, 1);
return finTop || finBot;
}
#include "normalMode.c"
void selnormalize(void) {
historyOpToggle(1, 1);
int const oldb = sel.nb.y, olde = sel.ne.y;
if (sel.ob.x == -1) {
sel.ne.y = sel.nb.y = -1;
} else {
int const offsetBuffer = sel.alt ? 0 : insertOff + term.row;
int const off = sel.alt ? 0 : (histMode ? histOff : insertOff);
int const nby = rangeY(sel.ob.y - off),
ney = rangeY(sel.oe.y - off);
sel.swap = rangeY(sel.ob.y - offsetBuffer)
> rangeY(sel.oe.y - offsetBuffer);
sel.nb.y = sel.swap ? ney : nby;
sel.ne.y = !sel.swap ? ney : nby;
int const cnb = sel.nb.y < term.row, cne = sel.ne.y < term.row;
if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
if (cnb) sel.nb.x = (!sel.swap) ? sel.ob.x : sel.oe.x;
if (cne) sel.ne.x = (!sel.swap) ? sel.oe.x : sel.ob.x;
} else {
if (cnb) sel.nb.x = MIN(sel.ob.x, sel.oe.x);
if (cne) sel.ne.x = MAX(sel.ob.x, sel.oe.x);
}
}
int const nBet=sel.nb.y<=sel.ne.y, oBet=oldb<=olde;
for (int i = 0; i < term.row; ++i) {
int const n = nBet ? BETWEEN(i, sel.nb.y, sel.ne.y)
: OUT(i, sel.nb.y, sel.ne.y);
term.dirty[i] |= (sel.type == SEL_RECTANGULAR && n) ||
(n != (oBet ? BETWEEN(i,oldb,olde) : OUT(i,oldb,olde)));
}
if (BETWEEN(oldb, 0, term.row - 1)) term.dirty[oldb] = 1;
if (BETWEEN(olde, 0, term.row - 1)) term.dirty[olde] = 1;
if (BETWEEN(sel.nb.y, 0, term.row - 1)) term.dirty[sel.nb.y] = 1;
if (BETWEEN(sel.ne.y, 0, term.row - 1)) term.dirty[sel.ne.y] = 1;
historyOpToggle(-1, 1);
}
void void
selstart(int col, int row, int snap) selstart(int col, int row, int snap)
{ {
@@ -427,19 +549,14 @@ selstart(int col, int row, int snap)
sel.alt = IS_SET(MODE_ALTSCREEN); sel.alt = IS_SET(MODE_ALTSCREEN);
sel.snap = snap; sel.snap = snap;
sel.oe.x = sel.ob.x = col; sel.oe.x = sel.ob.x = col;
sel.oe.y = sel.ob.y = row; sel.oe.y = sel.ob.y = row + !sel.alt * (histMode ? histOff : insertOff);
if (sel.snap != 0) sel.mode = SEL_READY;
selnormalize(); selnormalize();
if (sel.snap != 0)
sel.mode = SEL_READY;
tsetdirt(sel.nb.y, sel.ne.y);
} }
void void
selextend(int col, int row, int type, int done) selextend(int col, int row, int type, int done)
{ {
int oldey, oldex, oldsby, oldsey, oldtype;
if (sel.mode == SEL_IDLE) if (sel.mode == SEL_IDLE)
return; return;
if (done && sel.mode == SEL_EMPTY) { if (done && sel.mode == SEL_EMPTY) {
@@ -447,51 +564,13 @@ selextend(int col, int row, int type, int done)
return; return;
} }
oldey = sel.oe.y;
oldex = sel.oe.x;
oldsby = sel.nb.y;
oldsey = sel.ne.y;
oldtype = sel.type;
sel.oe.x = col; sel.oe.x = col;
sel.oe.y = row; sel.oe.y = row + (sel.alt ? 0 : (histMode ? histOff : insertOff));
selnormalize(); selnormalize();
sel.type = type; sel.type = type;
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; sel.mode = done ? SEL_IDLE : SEL_READY;
} }
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;
} 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);
/* 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;
}
int int
selected(int x, int y) selected(int x, int y)
{ {
@@ -503,119 +582,47 @@ selected(int x, int y)
return BETWEEN(y, sel.nb.y, sel.ne.y) return BETWEEN(y, sel.nb.y, sel.ne.y)
&& BETWEEN(x, sel.nb.x, sel.ne.x); && BETWEEN(x, sel.nb.x, sel.ne.x);
return BETWEEN(y, sel.nb.y, sel.ne.y) return ((sel.nb.y > sel.ne.y) ? OUT(y, sel.nb.y, sel.ne.y)
&& (y != sel.nb.y || x >= sel.nb.x) : BETWEEN(y, sel.nb.y, sel.ne.y)) &&
&& (y != sel.ne.y || x <= sel.ne.x); (y != sel.nb.y || x >= sel.nb.x) &&
} (y != sel.ne.y || x <= sel.ne.x);
void
selsnap(int *x, int *y, int direction)
{
int newx, newy, xt, yt;
int delim, prevdelim;
const Glyph *gp, *prevgp;
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];
prevdelim = ISDELIM(prevgp->u);
for (;;) {
newx = *x + direction;
newy = *y;
if (!BETWEEN(newx, 0, term.col - 1)) {
newy += direction;
newx = (newx + term.col) % term.col;
if (!BETWEEN(newy, 0, term.row - 1))
break;
if (direction > 0)
yt = *y, xt = *x;
else
yt = newy, xt = newx;
if (!(term.line[yt][xt].mode & ATTR_WRAP))
break;
}
if (newx >= tlinelen(newy))
break;
gp = &term.line[newy][newx];
delim = ISDELIM(gp->u);
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|| (delim && gp->u != prevgp->u)))
break;
*x = newx;
*y = newy;
prevgp = gp;
prevdelim = delim;
}
break;
case SNAP_LINE:
/*
* Snap around if the the previous line or the current one
* 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;
}
}
} else if (direction > 0) {
for (; *y < term.row-1; *y += direction) {
if (!(term.line[*y][term.col-1].mode
& ATTR_WRAP)) {
break;
}
}
}
break;
}
} }
char * char *
getsel(void) getsel(void)
{ {
char *str, *ptr; char *str, *ptr;
int y, bufsize, lastx, linelen; int y, yy, bufsize, lastx;
const Glyph *gp, *last; Glyph *gp, *last;
if (sel.ob.x == -1) if (sel.ob.x == -1)
return NULL; return NULL;
bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ; int const start = sel.swap ? sel.oe.y : sel.ob.y, h = rows();
int endy = (sel.swap ? sel.ob.y : sel.oe.y);
for (; endy < start; endy += h);
Line * const cbuf = IS_SET(MODE_ALTSCREEN) ? term.line : buf;
bufsize = (term.col+1) * (endy-start+1 ) * UTF_SIZ;
assert(bufsize > 0);
ptr = str = xmalloc(bufsize); ptr = str = xmalloc(bufsize);
/* append every set & selected glyph to the selection */ /* append every set & selected glyph to the selection */
for (y = sel.nb.y; y <= sel.ne.y; y++) { for (y = start; y <= endy; y++) {
if ((linelen = tlinelen(y)) == 0) { yy = y % h;
*ptr++ = '\n';
continue;
}
if (sel.type == SEL_RECTANGULAR) { if (sel.type == SEL_RECTANGULAR) {
gp = &term.line[y][sel.nb.x]; gp = &cbuf[yy][sel.nb.x];
lastx = sel.ne.x; lastx = sel.ne.x;
} else { } else {
gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; gp = &cbuf[yy][start == y ? sel.nb.x : 0];
lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; lastx = (endy == y) ? sel.ne.x : term.col-1;
} }
last = &term.line[y][MIN(lastx, linelen-1)]; last = &cbuf[yy][lastx];
while (last >= gp && last->u == ' ') if (!(cbuf[yy][term.col - 1].mode & ATTR_WRAP))
--last; while (last > gp && last->u == ' ') --last;
for ( ; gp <= last; ++gp) { for ( ; gp <= last; ++gp) {
if (gp->mode & ATTR_WDUMMY) if (gp->mode & ATTR_WDUMMY) continue;
continue;
ptr += utf8encode(gp->u, ptr); ptr += utf8encode(gp->u, ptr);
} }
@@ -628,7 +635,7 @@ getsel(void)
* st. * st.
* FIXME: Fix the computer world. * FIXME: Fix the computer world.
*/ */
if ((y < sel.ne.y || lastx >= linelen) && if ((y < endy || lastx == term.col - 1) &&
(!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR)) (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
*ptr++ = '\n'; *ptr++ = '\n';
} }
@@ -643,7 +650,7 @@ selclear(void)
return; return;
sel.mode = SEL_IDLE; sel.mode = SEL_IDLE;
sel.ob.x = -1; sel.ob.x = -1;
tsetdirt(sel.nb.y, sel.ne.y); selnormalize();
} }
void void
@@ -718,8 +725,14 @@ sigchld(int a)
if ((p = waitpid(pid, &stat, WNOHANG)) < 0) if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
die("waiting for pid %hd failed: %s\n", pid, strerror(errno)); 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; return;
}
if (WIFEXITED(stat) && WEXITSTATUS(stat)) if (WIFEXITED(stat) && WEXITSTATUS(stat))
die("child exited with status %d\n", WEXITSTATUS(stat)); die("child exited with status %d\n", WEXITSTATUS(stat));
@@ -803,7 +816,7 @@ ttynew(const char *line, char *cmd, const char *out, char **args)
break; break;
default: default:
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge("stdio rpath tty proc", NULL) == -1) if (pledge("stdio rpath tty proc exec", NULL) == -1)
die("pledge\n"); die("pledge\n");
#endif #endif
close(s); close(s);
@@ -996,8 +1009,7 @@ tfulldirt(void)
void void
tcursor(int mode) tcursor(int mode)
{ {
static TCursor c[2]; int alt = (histOp) ? 0 : (IS_SET(MODE_ALTSCREEN) + 1);
int alt = IS_SET(MODE_ALTSCREEN);
if (mode == CURSOR_SAVE) { if (mode == CURSOR_SAVE) {
c[alt] = term.c; c[alt] = term.c;
@@ -1057,6 +1069,7 @@ tswapscreen(void)
void void
tscrolldown(int orig, int n) tscrolldown(int orig, int n)
{ {
if (!orig && historyBufferScroll(-n)) return;
int i; int i;
Line temp; Line temp;
@@ -1077,6 +1090,7 @@ tscrolldown(int orig, int n)
void void
tscrollup(int orig, int n) tscrollup(int orig, int n)
{ {
if (!orig && historyBufferScroll(n)) return;
int i; int i;
Line temp; Line temp;
@@ -1097,7 +1111,7 @@ tscrollup(int orig, int n)
void void
selscroll(int orig, int n) selscroll(int orig, int n)
{ {
if (sel.ob.x == -1) if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
return; return;
if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
@@ -1132,6 +1146,7 @@ csiparse(void)
{ {
char *p = csiescseq.buf, *np; char *p = csiescseq.buf, *np;
long int v; long int v;
int sep = ';'; /* colon or semi-colon, but not both */
csiescseq.narg = 0; csiescseq.narg = 0;
if (*p == '?') { if (*p == '?') {
@@ -1149,7 +1164,9 @@ csiparse(void)
v = -1; v = -1;
csiescseq.arg[csiescseq.narg++] = v; csiescseq.arg[csiescseq.narg++] = v;
p = np; p = np;
if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) if (sep == ';' && *p == ':')
sep = ':'; /* allow override to colon once */
if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
break; break;
p++; p++;
} }
@@ -1228,8 +1245,8 @@ tclearregion(int x1, int y1, int x2, int y2)
if (y1 > y2) if (y1 > y2)
temp = y1, y1 = y2, y2 = temp; temp = y1, y1 = y2, y2 = temp;
LIMIT(x1, 0, term.col-1); LIMIT(x1, 0, buffCols-1);
LIMIT(x2, 0, term.col-1); LIMIT(x2, 0, buffCols-1);
LIMIT(y1, 0, term.row-1); LIMIT(y1, 0, term.row-1);
LIMIT(y2, 0, term.row-1); LIMIT(y2, 0, term.row-1);
@@ -1417,16 +1434,22 @@ tsetattr(const int *attr, int l)
if ((idx = tdefcolor(attr, &i, l)) >= 0) if ((idx = tdefcolor(attr, &i, l)) >= 0)
term.c.attr.fg = idx; term.c.attr.fg = idx;
break; break;
case 39: case 39: /* set foreground color to default */
term.c.attr.fg = defaultfg; term.c.attr.fg = defaultfg;
break; break;
case 48: case 48:
if ((idx = tdefcolor(attr, &i, l)) >= 0) if ((idx = tdefcolor(attr, &i, l)) >= 0)
term.c.attr.bg = idx; term.c.attr.bg = idx;
break; break;
case 49: case 49: /* set background color to default */
term.c.attr.bg = defaultbg; term.c.attr.bg = defaultbg;
break; break;
case 58:
/* This starts a sequence to change the color of
* "underline" pixels. We don't support that and
* instead eat up a following "5;n" or "2;r;g;b". */
tdefcolor(attr, &i, l);
break;
default: default:
if (BETWEEN(attr[i], 30, 37)) { if (BETWEEN(attr[i], 30, 37)) {
term.c.attr.fg = attr[i] - 30; term.c.attr.fg = attr[i] - 30;
@@ -1523,7 +1546,7 @@ tsetmode(int priv, int set, const int *args, int narg)
case 1006: /* 1006: extended reporting mode */ case 1006: /* 1006: extended reporting mode */
xsetmode(set, MODE_MOUSESGR); xsetmode(set, MODE_MOUSESGR);
break; break;
case 1034: case 1034: /* 1034: enable 8-bit mode for keyboard input */
xsetmode(set, MODE_8BIT); xsetmode(set, MODE_8BIT);
break; break;
case 1049: /* swap screen & set/restore cursor as xterm */ case 1049: /* swap screen & set/restore cursor as xterm */
@@ -1531,8 +1554,8 @@ tsetmode(int priv, int set, const int *args, int narg)
break; break;
tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
/* FALLTHROUGH */ /* FALLTHROUGH */
case 47: /* swap screen */ case 47: /* swap screen buffer */
case 1047: case 1047: /* swap screen buffer */
if (!allowaltscreen) if (!allowaltscreen)
break; break;
alt = IS_SET(MODE_ALTSCREEN); alt = IS_SET(MODE_ALTSCREEN);
@@ -1545,7 +1568,7 @@ tsetmode(int priv, int set, const int *args, int narg)
if (*args != 1049) if (*args != 1049)
break; break;
/* FALLTHROUGH */ /* FALLTHROUGH */
case 1048: case 1048: /* save/restore cursor (like DECSC/DECRC) */
tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
break; break;
case 2004: /* 2004: bracketed paste mode */ case 2004: /* 2004: bracketed paste mode */
@@ -1643,7 +1666,7 @@ csihandle(void)
ttywrite(vtiden, strlen(vtiden), 0); ttywrite(vtiden, strlen(vtiden), 0);
break; break;
case 'b': /* REP -- if last char is printable print it <n> more times */ case 'b': /* REP -- if last char is printable print it <n> more times */
DEFAULT(csiescseq.arg[0], 1); LIMIT(csiescseq.arg[0], 1, 65535);
if (term.lastc) if (term.lastc)
while (csiescseq.arg[0]-- > 0) while (csiescseq.arg[0]-- > 0)
tputc(term.lastc); tputc(term.lastc);
@@ -1702,7 +1725,7 @@ csihandle(void)
} }
break; break;
case 1: /* above */ case 1: /* above */
if (term.c.y > 1) if (term.c.y > 0)
tclearregion(0, 0, term.col-1, term.c.y-1); tclearregion(0, 0, term.col-1, term.c.y-1);
tclearregion(0, term.c.y, term.c.x, term.c.y); tclearregion(0, term.c.y, term.c.x, term.c.y);
break; break;
@@ -1728,6 +1751,7 @@ csihandle(void)
} }
break; break;
case 'S': /* SU -- Scroll <n> line up */ case 'S': /* SU -- Scroll <n> line up */
if (csiescseq.priv) break;
DEFAULT(csiescseq.arg[0], 1); DEFAULT(csiescseq.arg[0], 1);
tscrollup(term.top, csiescseq.arg[0]); tscrollup(term.top, csiescseq.arg[0]);
break; break;
@@ -1797,7 +1821,11 @@ csihandle(void)
tcursor(CURSOR_SAVE); tcursor(CURSOR_SAVE);
break; break;
case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
tcursor(CURSOR_LOAD); if (csiescseq.priv) {
goto unknown;
} else {
tcursor(CURSOR_LOAD);
}
break; break;
case ' ': case ' ':
switch (csiescseq.mode[1]) { switch (csiescseq.mode[1]) {
@@ -1899,7 +1927,7 @@ strhandle(void)
if (narg > 1) if (narg > 1)
xsettitle(strescseq.args[1]); xsettitle(strescseq.args[1]);
return; return;
case 52: case 52: /* manipulate selection data */
if (narg > 2 && allowwindowops) { if (narg > 2 && allowwindowops) {
dec = base64dec(strescseq.args[2]); dec = base64dec(strescseq.args[2]);
if (dec) { if (dec) {
@@ -1910,9 +1938,9 @@ strhandle(void)
} }
} }
return; return;
case 10: case 10: /* set dynamic VT100 text foreground color */
case 11: case 11: /* set dynamic VT100 text background color */
case 12: case 12: /* set dynamic text cursor color */
if (narg < 2) if (narg < 2)
break; break;
p = strescseq.args[1]; p = strescseq.args[1];
@@ -1953,6 +1981,19 @@ strhandle(void)
tfulldirt(); tfulldirt();
} }
return; return;
case 110: /* reset dynamic VT100 text foreground color */
case 111: /* reset dynamic VT100 text background color */
case 112: /* reset dynamic text cursor color */
if (narg != 1)
break;
if ((j = par - 110) < 0 || j >= LEN(osc_table))
break; /* shouldn't be possible */
if (xsetcolorname(osc_table[j].idx, NULL)) {
fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str);
} else {
tfulldirt();
}
return;
} }
break; break;
case 'k': /* old title set compatibility */ case 'k': /* old title set compatibility */
@@ -1990,6 +2031,59 @@ strparse(void)
} }
} }
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 < term.row; n++) {
bp = term.line[n];
lastpos = MIN(tlinelen(n) + 1, term.col) - 1;
if (lastpos < 0)
break;
end = &bp[lastpos + 1];
for (; bp < end; ++bp)
if (xwrite(to[1], buf, utf8encode(bp->u, buf)) < 0)
break;
if ((newline = term.line[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 void
strdump(void) strdump(void)
{ {
@@ -2330,6 +2424,7 @@ eschandle(uchar ascii)
treset(); treset();
resettitle(); resettitle();
xloadcols(); xloadcols();
xsetmode(0, MODE_HIDE);
break; break;
case '=': /* DECPAM -- Application keypad */ case '=': /* DECPAM -- Application keypad */
xsetmode(1, MODE_APPKEYPAD); xsetmode(1, MODE_APPKEYPAD);
@@ -2461,8 +2556,6 @@ check_control_code:
*/ */
return; return;
} }
if (selected(term.c.x, term.c.y))
selclear();
gp = &term.line[term.c.y][term.c.x]; gp = &term.line[term.c.y][term.c.x];
if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
@@ -2477,7 +2570,10 @@ check_control_code:
} }
if (term.c.x+width > term.col) { if (term.c.x+width > term.col) {
tnewline(1); if (IS_SET(MODE_WRAP))
tnewline(1);
else
tmoveto(term.col - width, term.c.y);
gp = &term.line[term.c.y][term.c.x]; gp = &term.line[term.c.y][term.c.x];
} }
@@ -2538,8 +2634,10 @@ void
tresize(int col, int row) tresize(int col, int row)
{ {
int i; int i;
int minrow = MIN(row, term.row); int const colSet = col, alt = IS_SET(MODE_ALTSCREEN), ini = buf == NULL;
int mincol = MIN(col, term.col); col = MAX(col, buffCols);
row = MIN(row, buffSize);
int const minrow = MIN(row, term.row), mincol = MIN(col, buffCols);
int *bp; int *bp;
TCursor c; TCursor c;
@@ -2548,6 +2646,7 @@ tresize(int col, int row)
"tresize: error resizing to %dx%d\n", col, row); "tresize: error resizing to %dx%d\n", col, row);
return; return;
} }
if (alt) tswapscreen();
/* /*
* slide screen to keep cursor where we expect it - * slide screen to keep cursor where we expect it -
@@ -2555,48 +2654,54 @@ tresize(int col, int row)
* memmove because we're freeing the earlier lines * memmove because we're freeing the earlier lines
*/ */
for (i = 0; i <= term.c.y - row; i++) { for (i = 0; i <= term.c.y - row; i++) {
free(term.line[i]);
free(term.alt[i]); free(term.alt[i]);
} }
/* ensure that both src and dst are not NULL */ /* ensure that both src and dst are not NULL */
if (i > 0) { if (i > 0) {
memmove(term.line, term.line + i, row * sizeof(Line));
memmove(term.alt, term.alt + i, row * sizeof(Line)); memmove(term.alt, term.alt + i, row * sizeof(Line));
} }
for (i += row; i < term.row; i++) { for (i += row; i < term.row; i++) {
free(term.line[i]);
free(term.alt[i]); free(term.alt[i]);
} }
/* resize to new height */ /* resize to new height */
term.line = xrealloc(term.line, row * sizeof(Line)); buf = xrealloc(buf, (buffSize + row) * sizeof(Line));
term.alt = xrealloc(term.alt, row * sizeof(Line)); term.alt = xrealloc(term.alt, row * sizeof(Line));
term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
mark = xrealloc(mark, col * row * sizeof(*mark));
term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
/* resize each row to new width, zero-pad if needed */ /* resize each row to new width, zero-pad if needed */
for (i = 0; i < minrow; i++) { 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)); term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
} }
/* allocate any new rows */ /* allocate any new rows */
for (/* i = minrow */; i < row; i++) { for (/* i = minrow */; i < row; i++) {
term.line[i] = xmalloc(col * sizeof(Glyph));
term.alt[i] = xmalloc(col * sizeof(Glyph)); term.alt[i] = xmalloc(col * sizeof(Glyph));
} }
if (col > term.col) { if (col > buffCols) {
bp = term.tabs + term.col; bp = term.tabs + buffCols;
memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); memset(bp, 0, sizeof(*term.tabs) * (col - buffCols));
while (--bp > term.tabs && !*bp) while (--bp > term.tabs && !*bp)
/* nothing */ ; /* nothing */ ;
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
*bp = 1; *bp = 1;
} }
Glyph g=(Glyph){.bg=term.c.attr.bg, .fg=term.c.attr.fg, .u=' ', .mode=0};
for (i = 0; i < buffSize; ++i) {
buf[i] = xrealloc(ini ? NULL : buf[i], col*sizeof(Glyph));
for (int j = ini ? 0 : buffCols; j < col; ++j) buf[i][j] = g;
}
for (i = 0; i < row; ++i) buf[buffSize + i] = buf[i];
term.line = &buf[*(histOp?&histOff:&insertOff) +=MAX(term.c.y-row+1,0)];
memset(mark, 0, col * row * sizeof(*mark));
/* update terminal size */ /* update terminal size */
term.col = col; term.col = colSet;
buffCols = col;
term.row = row; term.row = row;
if (alt) tswapscreen();
/* reset scrolling region */ /* reset scrolling region */
tsetscroll(0, row-1); tsetscroll(0, row-1);
/* make use of the LIMIT in tmoveto */ /* make use of the LIMIT in tmoveto */
@@ -2625,15 +2730,17 @@ resettitle(void)
void void
drawregion(int x1, int y1, int x2, int y2) drawregion(int x1, int y1, int x2, int y2)
{ {
if (altToggle && histMode && !histOp)
memset(term.dirty, 0, sizeof(*term.dirty) * term.row);
int const o = !IS_SET(MODE_ALTSCREEN) && histMode && !histOp, h =rows();
int y; int y;
for (y = y1; y < y2; y++) { for (y = y1; y < y2; y++) {
if (!term.dirty[y]) int const oy = o ? (y + insertOff - histOff + h) % h : y;
continue; if (!BETWEEN(oy, 0, term.row-1) || !term.dirty[y]) continue;
xdrawline(term.line[y], x1, oy, x2);
term.dirty[y] = 0;
xdrawline(term.line[y], x1, y, x2);
} }
memset(&term.dirty[y1], 0, sizeof(*term.dirty) * (y2 - y1));
} }
void void
@@ -2652,7 +2759,9 @@ draw(void)
if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
cx--; cx--;
if (histMode) historyPreDraw();
drawregion(0, 0, term.col, term.row); drawregion(0, 0, term.col, term.row);
if (!histMode)
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
term.ocx, term.ocy, term.line[term.ocy][term.ocx]); term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
term.ocx = cx; term.ocx = cx;

3
st.h
View File

@@ -8,6 +8,7 @@
#define MAX(a, b) ((a) < (b) ? (b) : (a)) #define MAX(a, b) ((a) < (b) ? (b) : (a))
#define LEN(a) (sizeof(a) / sizeof(a)[0]) #define LEN(a) (sizeof(a) / sizeof(a)[0])
#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
#define OUT(x, a, b) ((a) <= (x) || (x) <= (b))
#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b) #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
@@ -81,6 +82,7 @@ void die(const char *, ...);
void redraw(void); void redraw(void);
void draw(void); void draw(void);
void externalpipe(const Arg *);
void printscreen(const Arg *); void printscreen(const Arg *);
void printsel(const Arg *); void printsel(const Arg *);
void sendbreak(const Arg *); void sendbreak(const Arg *);
@@ -89,6 +91,7 @@ void toggleprinter(const Arg *);
int tattrset(int); int tattrset(int);
void tnew(int, int); void tnew(int, int);
void tresize(int, int); void tresize(int, int);
void tmoveto(int x, int y);
void tsetdirtattr(int); void tsetdirtattr(int);
void ttyhangup(void); void ttyhangup(void);
int ttynew(const char *, char *, const char *, char **); int ttynew(const char *, char *, const char *, char **);

View File

@@ -184,6 +184,10 @@ st-mono| simpleterm monocolor,
# XTerm extensions # XTerm extensions
rmxx=\E[29m, rmxx=\E[29m,
smxx=\E[9m, smxx=\E[9m,
BE=\E[?2004h,
BD=\E[?2004l,
PS=\E[200~,
PE=\E[201~,
# disabled rep for now: causes some issues with older ncurses versions. # disabled rep for now: causes some issues with older ncurses versions.
# rep=%p1%c\E[%p2%{1}%-%db, # rep=%p1%c\E[%p2%{1}%-%db,
# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) # tmux extensions, see TERMINFO EXTENSIONS in tmux(1)

23
utils.h Normal file
View File

@@ -0,0 +1,23 @@
/// Dynamic memory-chunk, with (1) datatype size, (2/3) initialized / allocated chunk, (4) content
typedef struct { uint8_t const elSize; uint32_t init, alloc; char* content; } DynamicArray;
#define UTF8_ARRAY {4, 0, 0, NULL}
static inline int p_alloc(DynamicArray *s, uint32_t amount) {
uint32_t const diff=s->init+s->elSize*amount-s->alloc, nas=s->alloc+max(diff,15)*s->elSize;
if (s->alloc < s->init + s->elSize * amount) {
char* tmp = realloc(s->content, nas);
if (!tmp) return 0;
s->alloc = nas, s->content = tmp;
}
return 1;
}
static inline char *view(DynamicArray * s, uint32_t i) { return s->content + i*s->elSize; }
static inline char *end(DynamicArray *s, uint32_t i) { return s->content +s->init-(i+1)*s->elSize; }
static inline uint32_t getU32(DynamicArray* s, uint32_t i, int b) { return *((uint32_t*) (b ?view(s,i) :end(s,i))); }
static char *expand(DynamicArray *s) { if (!p_alloc(s, 1)) return NULL; s->init += s->elSize; return end(s, 0); }
static inline void pop(DynamicArray* s) { s->init -= s->elSize; }
static inline void empty(DynamicArray* s) { s->init = 0; }
static inline int size(DynamicArray const * s) { return s->init / s->elSize; }
static inline void assign(DynamicArray* s, DynamicArray const *o) {
if (p_alloc(s, size(o))) memcpy(s->content, o->content, (s->init=o->init));
}

1
win.h
View File

@@ -19,6 +19,7 @@ enum win_mode {
MODE_MOUSEMANY = 1 << 15, MODE_MOUSEMANY = 1 << 15,
MODE_BRCKTPASTE = 1 << 16, MODE_BRCKTPASTE = 1 << 16,
MODE_NUMLOCK = 1 << 17, MODE_NUMLOCK = 1 << 17,
MODE_NORMAL = 1 << 18,
MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\
|MODE_MOUSEMANY, |MODE_MOUSEMANY,
}; };

187
x.c
View File

@@ -14,11 +14,13 @@
#include <X11/keysym.h> #include <X11/keysym.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#include <X11/Xresource.h>
char *argv0; char *argv0;
#include "arg.h" #include "arg.h"
#include "st.h" #include "st.h"
#include "win.h" #include "win.h"
#include "normalMode.h"
/* types used in config.h */ /* types used in config.h */
typedef struct { typedef struct {
@@ -261,6 +263,7 @@ clipcopy(const Arg *dummy)
free(xsel.clipboard); free(xsel.clipboard);
xsel.clipboard = NULL; xsel.clipboard = NULL;
xsetsel(getsel());
if (xsel.primary != NULL) { if (xsel.primary != NULL) {
xsel.clipboard = xstrdup(xsel.primary); xsel.clipboard = xstrdup(xsel.primary);
@@ -470,7 +473,6 @@ bpress(XEvent *e)
{ {
int btn = e->xbutton.button; int btn = e->xbutton.button;
struct timespec now; struct timespec now;
int snap;
if (1 <= btn && btn <= 11) if (1 <= btn && btn <= 11)
buttons |= 1 << (btn-1); buttons |= 1 << (btn-1);
@@ -489,17 +491,34 @@ bpress(XEvent *e)
* snapping behaviour is exposed. * snapping behaviour is exposed.
*/ */
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { int const tripleClick = TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout,
snap = SNAP_LINE; doubleClick = TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout;
} else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { if ((mouseYank || mouseSelect) && (tripleClick || doubleClick)) {
snap = SNAP_WORD; if (!IS_SET(MODE_NORMAL)) normalMode();
historyOpToggle(1, 1);
tmoveto(evcol(e), evrow(e));
if (tripleClick) {
if (mouseYank) pressKeys("dVy", 3);
if (mouseSelect) pressKeys("dV", 2);
} else if (doubleClick) {
if (mouseYank) pressKeys("dyiW", 4);
if (mouseSelect) {
tmoveto(evcol(e), evrow(e));
pressKeys("viW", 3);
}
}
historyOpToggle(-1, 1);
} else { } else {
snap = 0; if (!IS_SET(MODE_NORMAL)) selstart(evcol(e), evrow(e), 0);
else {
historyOpToggle(1, 1);
tmoveto(evcol(e), evrow(e));
pressKeys("v", 1);
historyOpToggle(-1, 1);
}
} }
xsel.tclick2 = xsel.tclick1; xsel.tclick2 = xsel.tclick1;
xsel.tclick1 = now; xsel.tclick1 = now;
selstart(evcol(e), evrow(e), snap);
} }
} }
@@ -709,8 +728,7 @@ brelease(XEvent *e)
if (mouseaction(e, 1)) if (mouseaction(e, 1))
return; return;
if (btn == Button1) if (btn == Button1 && !IS_SET(MODE_NORMAL)) mousesel(e, 1);
mousesel(e, 1);
} }
void void
@@ -790,6 +808,8 @@ xloadcolor(int i, const char *name, Color *ncolor)
return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
} }
void normalMode() { historyModeToggle((win.mode ^=MODE_NORMAL) & MODE_NORMAL); }
void void
xloadcols(void) xloadcols(void)
{ {
@@ -818,7 +838,7 @@ xloadcols(void)
int int
xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
{ {
if (!BETWEEN(x, 0, dc.collen)) if (!BETWEEN(x, 0, dc.collen - 1))
return 1; return 1;
*r = dc.col[x].color.red >> 8; *r = dc.col[x].color.red >> 8;
@@ -833,7 +853,7 @@ xsetcolorname(int x, const char *name)
{ {
Color ncolor; Color ncolor;
if (!BETWEEN(x, 0, dc.collen)) if (!BETWEEN(x, 0, dc.collen - 1))
return 1; return 1;
if (!xloadcolor(x, name, &ncolor)) if (!xloadcolor(x, name, &ncolor))
@@ -1131,7 +1151,7 @@ xinit(int cols, int rows)
{ {
XGCValues gcvalues; XGCValues gcvalues;
Cursor cursor; Cursor cursor;
Window parent; Window parent, root;
pid_t thispid = getpid(); pid_t thispid = getpid();
XColor xmousefg, xmousebg; XColor xmousefg, xmousebg;
@@ -1168,16 +1188,19 @@ xinit(int cols, int rows)
| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
xw.attrs.colormap = xw.cmap; xw.attrs.colormap = xw.cmap;
root = XRootWindow(xw.dpy, xw.scr);
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
parent = XRootWindow(xw.dpy, xw.scr); parent = root;
xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t,
win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
| CWEventMask | CWColormap, &xw.attrs); | CWEventMask | CWColormap, &xw.attrs);
if (parent != root)
XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t);
memset(&gcvalues, 0, sizeof(gcvalues)); memset(&gcvalues, 0, sizeof(gcvalues));
gcvalues.graphics_exposures = False; gcvalues.graphics_exposures = False;
dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures,
&gcvalues); &gcvalues);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
DefaultDepth(xw.dpy, xw.scr)); DefaultDepth(xw.dpy, xw.scr));
@@ -1257,8 +1280,10 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
/* Fetch rune and mode for current glyph. */ /* Fetch rune and mode for current glyph. */
rune = glyphs[i].u; Glyph g = glyphs[i];
mode = glyphs[i].mode; historyOverlay(x+i, y, &g);
rune = g.u;
mode = g.mode;
/* Skip dummy wide-character spacing. */ /* Skip dummy wide-character spacing. */
if (mode == ATTR_WDUMMY) if (mode == ATTR_WDUMMY)
@@ -1617,6 +1642,9 @@ xseticontitle(char *p)
XTextProperty prop; XTextProperty prop;
DEFAULT(p, opt_title); DEFAULT(p, opt_title);
if (p[0] == '\0')
p = opt_title;
if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
&prop) != Success) &prop) != Success)
return; return;
@@ -1631,6 +1659,9 @@ xsettitle(char *p)
XTextProperty prop; XTextProperty prop;
DEFAULT(p, opt_title); DEFAULT(p, opt_title);
if (p[0] == '\0')
p = opt_title;
if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
&prop) != Success) &prop) != Success)
return; return;
@@ -1656,6 +1687,7 @@ xdrawline(Line line, int x1, int y1, int x2)
i = ox = 0; i = ox = 0;
for (x = x1; x < x2 && i < numspecs; x++) { for (x = x1; x < x2 && i < numspecs; x++) {
new = line[x]; new = line[x];
historyOverlay(x, y1, &new);
if (new.mode == ATTR_WDUMMY) if (new.mode == ATTR_WDUMMY)
continue; continue;
if (selected(x, y1)) if (selected(x, y1))
@@ -1850,6 +1882,11 @@ kpress(XEvent *ev)
} else { } else {
len = XLookupString(e, buf, sizeof buf, &ksym, NULL); len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
} }
if (IS_SET(MODE_NORMAL)) {
if (kPressHist(buf, len, match(ControlMask, e->state), &ksym)
== finish) normalMode();
return;
}
/* 1. shortcuts */ /* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state)) { if (ksym == bp->keysym && match(bp->mod, e->state)) {
@@ -2014,6 +2051,118 @@ run(void)
} }
} }
#define XRESOURCE_LOAD_META(NAME) \
if(!XrmGetResource(xrdb, "st." NAME, "st." NAME, &type, &ret)) \
XrmGetResource(xrdb, "*." NAME, "*." NAME, &type, &ret); \
if (ret.addr != NULL && !strncmp("String", type, 64))
#define XRESOURCE_LOAD_STRING(NAME, DST) \
XRESOURCE_LOAD_META(NAME) \
DST = ret.addr;
#define XRESOURCE_LOAD_CHAR(NAME, DST) \
XRESOURCE_LOAD_META(NAME) \
DST = ret.addr[0];
#define XRESOURCE_LOAD_INTEGER(NAME, DST) \
XRESOURCE_LOAD_META(NAME) \
DST = strtoul(ret.addr, NULL, 10);
#define XRESOURCE_LOAD_FLOAT(NAME, DST) \
XRESOURCE_LOAD_META(NAME) \
DST = strtof(ret.addr, NULL);
void
xrdb_load(void)
{
/* XXX */
char *xrm;
char *type;
XrmDatabase xrdb;
XrmValue ret;
Display *dpy;
if(!(dpy = XOpenDisplay(NULL)))
die("Can't open display\n");
XrmInitialize();
xrm = XResourceManagerString(dpy);
if (xrm != NULL) {
xrdb = XrmGetStringDatabase(xrm);
/* handling colors here without macros to do via loop. */
int i = 0;
char loadValue[12] = "";
for (i = 0; i < 256; i++)
{
sprintf(loadValue, "%s%d", "st.color", i);
if(!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret))
{
sprintf(loadValue, "%s%d", "*.color", i);
if (!XrmGetResource(xrdb, loadValue, loadValue, &type, &ret))
/* reset if not found (unless in range for defaults). */
if (i > 15)
colorname[i] = NULL;
}
if (ret.addr != NULL && !strncmp("String", type, 64))
colorname[i] = ret.addr;
}
XRESOURCE_LOAD_STRING("foreground", colorname[defaultfg]);
XRESOURCE_LOAD_STRING("background", colorname[defaultbg]);
XRESOURCE_LOAD_STRING("cursorColor", colorname[defaultcs])
else {
// this looks confusing because we are chaining off of the if
// in the macro. probably we should be wrapping everything blocks
// so this isn't possible...
defaultcs = defaultfg;
}
XRESOURCE_LOAD_STRING("reverse-cursor", colorname[defaultrcs])
else {
// see above.
defaultrcs = defaultbg;
}
XRESOURCE_LOAD_STRING("font", font);
XRESOURCE_LOAD_STRING("termname", termname);
XRESOURCE_LOAD_INTEGER("blinktimeout", blinktimeout);
XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume);
XRESOURCE_LOAD_INTEGER("borderpx", borderpx);
XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape);
XRESOURCE_LOAD_FLOAT("cwscale", cwscale);
XRESOURCE_LOAD_FLOAT("chscale", chscale);
}
XFlush(dpy);
}
void
reload(int sig)
{
xrdb_load();
/* colors, fonts */
xloadcols();
xunloadfonts();
xloadfonts(font, 0);
/* pretend the window just got resized */
cresize(win.w, win.h);
redraw();
/* triggers re-render if we're visible. */
ttywrite("\033[O", 3, 1);
signal(SIGUSR1, reload);
}
void void
usage(void) usage(void)
{ {
@@ -2087,6 +2236,8 @@ run:
setlocale(LC_CTYPE, ""); setlocale(LC_CTYPE, "");
XSetLocaleModifiers(""); XSetLocaleModifiers("");
xrdb_load();
signal(SIGUSR1, reload);
cols = MAX(cols, 1); cols = MAX(cols, 1);
rows = MAX(rows, 1); rows = MAX(rows, 1);
tnew(cols, rows); tnew(cols, rows);