13 Commits

Author SHA1 Message Date
5d6d2c98c7 st-meta-vim-full: change currentBg, introduce currentFg 2026-01-22 09:19:59 +01:00
Justinas Grigas
02739346a8 cyclefonts: keybind to cycle fonts (agree with st-xresources-signal-reloading)
This patch is an update to the 20210604, which fixes zoomreset.

Because the cyclefonts function doesn't change the defaultfontsize
variable, zoomreset function resets all fonts to the size of the first
one loaded.

With this patch, zoomreset will reset the font to the specified fontsize
2026-01-22 08:22:36 +01:00
wael
f37ea1f018 update to 0.8.5 (apply patch st-csi_22_23-0.8.5.diff) 2026-01-22 08:02:55 +01:00
01873515e4 agree st-clickurl with st-externalpipe 2026-01-22 07:53:25 +01:00
Jishnu Sen
c639b09920 Highlight URLs with control and follow with click 2026-01-22 07:50:51 +01:00
3c61f404b9 apply patch st-expected-anysize-0.9.diff 2026-01-22 07:43:32 +01:00
1d15a6b4d5 add dmenu dark and light variants 2026-01-22 07:42:58 +01:00
0336421c97 add copy/paste dmenu prompt and remove empty values from completion list 2026-01-22 07:42:49 +01:00
7959aba0bf add key to insert text selected with dmenu as terminal input 2026-01-22 07:42:34 +01:00
278f7b1869 add keys to edit screen content with editor and copy WORD using externalpipe 2026-01-22 07:42:04 +01:00
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
12 changed files with 1060 additions and 1004 deletions

View File

@@ -15,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
@@ -38,8 +38,6 @@ install: st
mkdir -p $(DESTDIR)$(PREFIX)/bin mkdir -p $(DESTDIR)$(PREFIX)/bin
cp -f st $(DESTDIR)$(PREFIX)/bin cp -f st $(DESTDIR)$(PREFIX)/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/st chmod 755 $(DESTDIR)$(PREFIX)/bin/st
cp -f st-autocomplete $(DESTDIR)$(PREFIX)/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/st-autocomplete
mkdir -p $(DESTDIR)$(MANPREFIX)/man1 mkdir -p $(DESTDIR)$(MANPREFIX)/man1
sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1 sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/man1/st.1
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
@@ -48,7 +46,6 @@ install: st
uninstall: uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st rm -f $(DESTDIR)$(PREFIX)/bin/st
rm -f $(DESTDIR)$(PREFIX)/bin/st-autocomplete
rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1 rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
.PHONY: all clean dist install uninstall .PHONY: all clean dist install uninstall

View File

@@ -1,16 +0,0 @@
# ifndef __ST_AUTOCOMPLETE_H
# define __ST_AUTOCOMPLETE_H
enum {
ACMPL_DEACTIVATE,
ACMPL_WORD,
ACMPL_WWORD,
ACMPL_FUZZY_WORD,
ACMPL_FUZZY_WWORD,
ACMPL_FUZZY,
ACMPL_SUFFIX,
ACMPL_SURROUND,
ACMPL_UNDO,
};
# endif // __ST_AUTOCOMPLETE_H

View File

@@ -5,9 +5,51 @@
* *
* font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
*/ */
static char *font = "FiraCode Nerd Font:size=10"; static char *fonts[] = {
"FiraCode Nerd Font:size=10",
"Monaspace Neon:size=10",
"Monaspace Argon:size=10",
"Monaspace Xenon:size=10",
"Monaspace Radon:size=10",
"Monaspace Krypton:size=10",
};
static size_t currentfont = 0;
static int borderpx = 2; static int borderpx = 2;
static char *externaleditcmd[] = { "/bin/sh", "-c",
"tmpfile=$(mktemp /tmp/st-edit.XXXXXX)\n"
"trap 'rm \"$tmpfile\"' 0 1 15\n"
"cat > \"$tmpfile\"\n"
"st -e vis \"$tmpfile\"\n",
NULL};
int lightmode = 0;
#define COL_GRAY1 "'#222222'"
#define COL_GRAY2 "'#444444'"
#define COL_GRAY3 "'#bbbbbb'"
#define COL_GRAY4 "'#eeeeee'"
#define COL_CYAN "'#005577'"
#define DMENU_DARK "-l 10 -fn 'FiraCode Nerd Font:size=10' -nb " COL_GRAY1 " -nf " COL_GRAY3 " -sb " COL_CYAN " -sf " COL_GRAY4
#define DMENU_LIGHT "-l 10 -fn 'FiraCode Nerd Font:size=10' -nb " COL_GRAY3 " -nf " COL_GRAY1 " -sb " COL_CYAN " -sf " COL_GRAY4
static char *externalcopydarkcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_DARK " -w $WINDOWID -p Copy | tr -d '\\n' | vis-clipboard --copy",
NULL};
static char *externalpastedarkcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_DARK " -w $WINDOWID -p Paste | tr -d '\\n'",
NULL};
static char *externalcopylightcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_LIGHT " -w $WINDOWID -p Copy | tr -d '\\n' | vis-clipboard --copy",
NULL};
static char *externalpastelightcmd[] = { "/bin/sh", "-c",
"sed 's/[ \t][ \t]*/\\n/g' | tac | awk 'BEGIN{x[\"\"]++} !x[$0]++' | dmenu " DMENU_LIGHT " -w $WINDOWID -p Paste | tr -d '\\n'",
NULL};
/* /*
* What program is execed by st depends of these precedence rules: * What program is execed by st depends of these precedence rules:
* 1: program passed with -e * 1: program passed with -e
@@ -133,6 +175,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 = 179, currentFg = 0, 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
@@ -170,8 +227,6 @@ static unsigned int defaultattr = 11;
*/ */
static uint forcemousemod = ShiftMask; static uint forcemousemod = ShiftMask;
#include "autocomplete.h"
/* /*
* Internal mouse shortcuts. * Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection. * Beware that overloading Button1 will disable the selection.
@@ -189,8 +244,6 @@ static MouseShortcut mshortcuts[] = {
#define MODKEY Mod1Mask #define MODKEY Mod1Mask
#define TERMMOD (ControlMask|ShiftMask) #define TERMMOD (ControlMask|ShiftMask)
#define ACMPL_MOD ControlMask|Mod1Mask
static Shortcut shortcuts[] = { static Shortcut shortcuts[] = {
/* mask keysym function argument */ /* mask keysym function argument */
{ XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} },
@@ -203,18 +256,13 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_C, clipcopy, {.i = 0} }, { TERMMOD, XK_C, clipcopy, {.i = 0} },
{ TERMMOD, XK_V, clippaste, {.i = 0} }, { TERMMOD, XK_V, clippaste, {.i = 0} },
{ TERMMOD, XK_Y, selpaste, {.i = 0} }, { TERMMOD, XK_Y, selpaste, {.i = 0} },
{ TERMMOD, XK_E, externalpipe, { .v = externaleditcmd } },
{ TERMMOD, XK_K, externalpipecopy, { 0 } },
{ TERMMOD, XK_I, externalpipepaste, { 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} },
{ ACMPL_MOD, XK_slash, autocomplete, { .i = ACMPL_WORD } }, { MODKEY, XK_c, normalMode, {.i = 0} },
{ ACMPL_MOD, XK_period, autocomplete, { .i = ACMPL_FUZZY_WORD } }, { TERMMOD, XK_S, cyclefonts, {} },
{ ACMPL_MOD, XK_comma, autocomplete, { .i = ACMPL_FUZZY } },
{ ACMPL_MOD, XK_apostrophe, autocomplete, { .i = ACMPL_SUFFIX } },
{ ACMPL_MOD, XK_semicolon, autocomplete, { .i = ACMPL_SURROUND } },
{ ACMPL_MOD, XK_bracketright,autocomplete, { .i = ACMPL_WWORD } },
{ ACMPL_MOD, XK_bracketleft, autocomplete, { .i = ACMPL_FUZZY_WWORD } },
{ ACMPL_MOD, XK_equal, autocomplete, { .i = ACMPL_UNDO } },
{ ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
{ ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
}; };
/* /*
@@ -486,3 +534,14 @@ static char ascii_printable[] =
" !\"#$%&'()*+,-./0123456789:;<=>?" " !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~"; "`abcdefghijklmnopqrstuvwxyz{|}~";
/*
* Open urls starting with urlprefixes, contatining urlchars
* by passing as ARG1 to urlhandler.
*/
char* urlhandler = "xdg-open";
char urlchars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-._~:/?#@!$&'*+,;=%";
char* urlprefixes[] = {"http://", "https://", NULL};

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, currentFg, 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; g->fg = currentFg; }
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);

View File

@@ -1,310 +0,0 @@
#!/usr/bin/perl
#########################################################################
# Copyright (C) 2012-2017 Wojciech Siewierski #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
#########################################################################
my ($cmd, $cursor_row, $cursor_column) = @ARGV;
my $lines = [];
my $lines1 = [];
my $last_line = -1;
my $lines_before_cursor = 0;
while (<stdin>)
{
$last_line++;
s/[^[:print:]]/?/g;
if ($last_line < $cursor_row)
{
unshift @{$lines1}, $_;
$lines_before_cursor++;
}
else
{
unshift @{$lines}, $_;
}
}
foreach (@{$lines1})
{
unshift @{$lines}, $_;
}
my $cursor_row_in = $cursor_row;
$cursor_row = $last_line;
$self = {};
# A reference to a function that transforms the completed word
# into a regex matching the completions. Usually generated by
# generate_matcher().
#
# For example
# $fun = generate_matcher(".*");
# $fun->("foo");
# would return "f.*o.*o"
#
# In other words, indirectly decides which characters can
# appear in the completion.
my $matcher;
# A regular expression matching a character before each match.
# For example, it you want to match the text after a
# whitespace, set it to "\s".
my $char_class_before;
# A regular expression matching every character in the entered
# text that will be used to find matching completions. Usually
# "\w" or similar.
my $char_class_to_complete;
# A regular expression matching every allowed last character
# of the completion (uses greedy matching).
my $char_class_at_end;
if ($cmd eq 'word-complete') {
# Basic word completion. Completes the current word
# without any special matching.
$char_class_before = '[^-\w]';
$matcher = sub { quotemeta shift }; # identity
$char_class_at_end = '[-\w]';
$char_class_to_complete = '[-\w]';
} elsif ($cmd eq 'WORD-complete') {
# The same as above but in the Vim meaning of a "WORD" --
# whitespace delimited.
$char_class_before = '\s';
$matcher = sub { quotemeta shift };
$char_class_at_end = '\S';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'fuzzy-word-complete' ||
$cmd eq 'skeleton-word-complete') {
# Fuzzy completion of the current word.
$char_class_before = '[^-\w]';
$matcher = generate_matcher('[-\w]*');
$char_class_at_end = '[-\w]';
$char_class_to_complete = '[-\w]';
} elsif ($cmd eq 'fuzzy-WORD-complete') {
# Fuzzy completion of the current WORD.
$char_class_before = '\s';
$matcher = generate_matcher('\S*');
$char_class_at_end = '\S';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'fuzzy-complete' ||
$cmd eq 'skeleton-complete') {
# Fuzzy completion of an arbitrary text.
$char_class_before = '\W';
$matcher = generate_matcher('.*?');
$char_class_at_end = '\w';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'suffix-complete') {
# Fuzzy completion of an completing suffixes, like
# completing test=hello from /blah/hello.
$char_class_before = '\S';
$matcher = generate_matcher('\S*');
$char_class_at_end = '\S';
$char_class_to_complete = '\S';
} elsif ($cmd eq 'surround-complete') {
# Completing contents of quotes and braces.
# Here we are using three named groups: s, b, p for quotes, braces
# and parenthesis.
$char_class_before = '((?<q>["\'`])|(?<b>\[)|(?<p>\())';
$matcher = generate_matcher('.*?');
# Here we match text till enclosing pair, using perl conditionals in
# regexps (?(condition)yes-expression|no-expression).
# \0 is used to hack concatenation with '*' later in the code.
$char_class_at_end = '.*?(.(?=(?(<b>)\]|((?(<p>)\)|\g{q})))))\0';
$char_class_to_complete = '\S';
}
# use the last used word or read the word behind the cursor
my $word_to_complete = read_word_at_coord($self, $cursor_row, $cursor_column,
$char_class_to_complete);
print stdout "$word_to_complete\n";
if ($word_to_complete) {
while (1) {
# ignore the completed word itself
$self->{already_completed}{$word_to_complete} = 1;
# continue the last search or start from the current row
my $completion = find_match($self,
$word_to_complete,
$self->{next_row} // $cursor_row,
$matcher->($word_to_complete),
$char_class_before,
$char_class_at_end);
if ($completion) {
print stdout $completion."\n".join ("\n", @{$self->{highlight}})."\n";
}
else {
last;
}
}
}
######################################################################
sub highlight_match {
my ($self, $linenum, $completion) = @_;
# clear_highlight($self);
my $line = @{$lines}[$linenum];
my $re = quotemeta $completion;
$line =~ /$re/;
my $beg = $-[0];
my $end = $+[0];
if ($linenum >= $lines_before_cursor)
{
$lline = $last_line - $lines_before_cursor;
$linenum -= $lines_before_cursor;
$linenum = $lline - $linenum;
$linenum += $lines_before_cursor;
}
$self->{highlight} = [$linenum, $beg, $end];
}
######################################################################
sub read_word_at_coord {
my ($self, $row, $col, $char_class) = @_;
$_ = substr(@{$lines} [$row], 0, $col); # get the current line up to the cursor...
s/.*?($char_class*)$/$1/; # ...and read the last word from it
return $_;
}
######################################################################
# Returns a function that takes a string and returns that string with
# this function's argument inserted between its every two characters.
# The resulting string is used as a regular expression matching the
# completion candidates.
sub generate_matcher {
my $regex_between = shift;
sub {
$_ = shift;
# sorry for this lispy code, I couldn't resist ;)
(join "$regex_between",
(map quotemeta,
(split //)))
}
}
######################################################################
# Checks whether the completion found by find_match() was already
# found and if it was, calls find_match() again to find the next
# completion.
#
# Takes all the arguments that find_match() would take, to make a
# mutually recursive call.
sub skip_duplicates {
my ($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end) = @_;
my $completion;
if ($current_row <= $lines_before_cursor)
{
$completion = shift @{$self->{matches_in_row}}; # get the leftmost one
}
else
{
$completion = pop @{$self->{matches_in_row}}; # get the leftmost one
}
# check for duplicates
if (exists $self->{already_completed}{$completion}) {
# skip this completion
return find_match(@_);
} else {
$self->{already_completed}{$completion} = 1;
highlight_match($self,
$self->{next_row}+1,
$completion);
return $completion;
}
}
######################################################################
# Finds the next matching completion in the row current row or above
# while skipping duplicates using skip_duplicates().
sub find_match {
my ($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end) = @_;
$self->{matches_in_row} //= [];
# cycle through all the matches in the current row if not starting a new search
if (@{$self->{matches_in_row}}) {
return skip_duplicates($self, $word_to_match, $current_row, $regexp, $char_class_before, $char_class_at_end);
}
my $i;
# search through all the rows starting with current one or one above the last checked
for ($i = $current_row; $i >= 0; --$i) {
my $line = @{$lines}[$i]; # get the line of text from the row
# if ($i == $cursor_row) {
# $line = substr $line, 0, $cursor_column;
# }
$_ = $line;
# find all the matches in the current line
my $match;
push @{$self->{matches_in_row}}, $+{match} while ($_, $match) = /
(.*${char_class_before})
(?<match>
${regexp}
${char_class_at_end}*
)
/ix;
# corner case: match at the very beginning of line
push @{$self->{matches_in_row}}, $+{match} if $line =~ /^(${char_class_before}){0}(?<match>$regexp$char_class_at_end*)/i;
if (@{$self->{matches_in_row}}) {
# remember which row should be searched next
$self->{next_row} = --$i;
# arguments needed for find_match() mutual recursion
return skip_duplicates($self, $word_to_match, $i, $regexp, $char_class_before, $char_class_at_end);
}
}
# # no more possible completions, revert to the original word
# undo_completion($self) if $i < 0;
return undef;
}

1137
st.c

File diff suppressed because it is too large Load Diff

21
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)
@@ -19,7 +20,6 @@
#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
#define IS_TRUECOL(x) (1 << 24 & (x)) #define IS_TRUECOL(x) (1 << 24 & (x))
#define HISTSIZE 2000
enum glyph_attribute { enum glyph_attribute {
ATTR_NULL = 0, ATTR_NULL = 0,
@@ -35,6 +35,7 @@ enum glyph_attribute {
ATTR_WIDE = 1 << 9, ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10, ATTR_WDUMMY = 1 << 10,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
ATTR_URL = 1 << 14,
}; };
enum selection_mode { enum selection_mode {
@@ -78,12 +79,13 @@ typedef union {
const char *s; const char *s;
} Arg; } Arg;
void autocomplete (const Arg *);
void die(const char *, ...); void die(const char *, ...);
void redraw(void); void redraw(void);
void draw(void); void draw(void);
void externalpipe(const Arg *);
void externalpipecopy(const Arg *);
void externalpipepaste(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 *);
@@ -92,6 +94,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 **);
@@ -108,6 +111,10 @@ void selextend(int, int, int, int);
int selected(int, int); int selected(int, int);
char *getsel(void); char *getsel(void);
void highlighturls(void);
void unhighlighturls(void);
void followurl(int, int);
size_t utf8encode(Rune, char *); size_t utf8encode(Rune, char *);
void *xmalloc(size_t); void *xmalloc(size_t);
@@ -122,8 +129,16 @@ extern char *vtiden;
extern wchar_t *worddelimiters; extern wchar_t *worddelimiters;
extern int allowaltscreen; extern int allowaltscreen;
extern int allowwindowops; extern int allowwindowops;
extern int lightmode;
extern char *termname; extern char *termname;
extern unsigned int tabspaces; extern unsigned int tabspaces;
extern unsigned int defaultfg; extern unsigned int defaultfg;
extern unsigned int defaultbg; extern unsigned int defaultbg;
extern unsigned int defaultcs; extern unsigned int defaultcs;
extern char *urlhandler;
extern char urlchars[];
extern char *urlprefixes[];
extern int nurlprefixes;
char **externalcopycmd();
char **externalpastecmd();

View File

@@ -161,7 +161,7 @@ st-mono| simpleterm monocolor,
rin=\E[%p1%dT, rin=\E[%p1%dT,
ritm=\E[23m, ritm=\E[23m,
rmacs=\E(B, rmacs=\E(B,
rmcup=\E[?1049l, rmcup=\E[?1049l\E[23;0;0t,
rmir=\E[4l, rmir=\E[4l,
rmkx=\E[?1l\E>, rmkx=\E[?1l\E>,
rmso=\E[27m, rmso=\E[27m,
@@ -172,7 +172,7 @@ st-mono| simpleterm monocolor,
sitm=\E[3m, sitm=\E[3m,
sgr0=\E[0m, sgr0=\E[0m,
smacs=\E(0, smacs=\E(0,
smcup=\E[?1049h, smcup=\E[?1049h\E[22;0;0t,
smir=\E[4h, smir=\E[4h,
smkx=\E[?1h\E=, smkx=\E[?1h\E=,
smso=\E[7m, smso=\E[7m,

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));
}

5
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,
}; };
@@ -32,7 +33,9 @@ void xloadcols(void);
int xsetcolorname(int, const char *); int xsetcolorname(int, const char *);
int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *); int xgetcolor(int, unsigned char *, unsigned char *, unsigned char *);
void xseticontitle(char *); void xseticontitle(char *);
void xsettitle(char *); void xfreetitlestack(void);
void xsettitle(char *, int);
void xpushtitle(void);
int xsetcursor(int); int xsetcursor(int);
void xsetmode(int, unsigned int); void xsetmode(int, unsigned int);
void xsetpointermotion(int); void xsetpointermotion(int);

160
x.c
View File

@@ -20,6 +20,7 @@ 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 {
@@ -60,12 +61,24 @@ static void zoom(const Arg *);
static void zoomabs(const Arg *); static void zoomabs(const Arg *);
static void zoomreset(const Arg *); static void zoomreset(const Arg *);
static void ttysend(const Arg *); static void ttysend(const Arg *);
void kscrollup(const Arg *); static void cyclefonts(const Arg *);
void kscrolldown(const Arg *);
/* config.h for applying patches and the configuration. */ /* config.h for applying patches and the configuration. */
#include "config.h" #include "config.h"
char **externalcopycmd()
{
return (lightmode) ? externalcopylightcmd : externalcopydarkcmd;
}
char **externalpastecmd()
{
return (lightmode) ? externalpastelightcmd : externalpastedarkcmd;
}
/* size of title stack */
#define TITLESTACKSIZE 8
/* XEMBED messages */ /* XEMBED messages */
#define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5 #define XEMBED_FOCUS_OUT 5
@@ -194,6 +207,7 @@ static void usage(void);
static void (*handler[LASTEvent])(XEvent *) = { static void (*handler[LASTEvent])(XEvent *) = {
[KeyPress] = kpress, [KeyPress] = kpress,
[KeyRelease] = kpress,
[ClientMessage] = cmessage, [ClientMessage] = cmessage,
[ConfigureNotify] = resize, [ConfigureNotify] = resize,
[VisibilityNotify] = visibility, [VisibilityNotify] = visibility,
@@ -223,6 +237,8 @@ static DC dc;
static XWindow xw; static XWindow xw;
static XSelection xsel; static XSelection xsel;
static TermWindow win; static TermWindow win;
static int tstki; /* title stack index */
static char *titlestack[TITLESTACKSIZE]; /* title stack */
/* Font Ring Cache */ /* Font Ring Cache */
enum { enum {
@@ -264,6 +280,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);
@@ -318,12 +335,8 @@ void
zoomreset(const Arg *arg) zoomreset(const Arg *arg)
{ {
Arg larg; Arg larg;
if (defaultfontsize > 0) {
larg.f = defaultfontsize;
zoomabs(&larg); zoomabs(&larg);
} }
}
void void
ttysend(const Arg *arg) ttysend(const Arg *arg)
@@ -347,6 +360,15 @@ evrow(XEvent *e)
return y / win.ch; return y / win.ch;
} }
void cyclefonts(const Arg *arg) {
currentfont++;
currentfont %= (sizeof fonts / sizeof fonts[0]);
usedfont = fonts[currentfont];
Arg larg;
larg.f = usedfontsize;
zoomabs(&larg);
}
void void
mousesel(XEvent *e, int done) mousesel(XEvent *e, int done)
{ {
@@ -455,6 +477,15 @@ mouseaction(XEvent *e, uint release)
/* ignore Button<N>mask for Button<N> - it's set on release */ /* ignore Button<N>mask for Button<N> - it's set on release */
uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
if (release == 0 &&
e->xbutton.button == Button1 &&
(match(ControlMask, state) ||
match(ControlMask, state & ~forcemousemod))) {
followurl(evrow(e), evcol(e));
return 1;
}
for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
if (ms->release == release && if (ms->release == release &&
ms->button == e->xbutton.button && ms->button == e->xbutton.button &&
@@ -473,7 +504,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);
@@ -492,17 +522,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);
} }
} }
@@ -712,8 +759,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
@@ -793,6 +839,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)
{ {
@@ -872,8 +920,8 @@ xhints(void)
sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
sizeh->height = win.h; sizeh->height = win.h;
sizeh->width = win.w; sizeh->width = win.w;
sizeh->height_inc = win.ch; sizeh->height_inc = 1;
sizeh->width_inc = win.cw; sizeh->width_inc = 1;
sizeh->base_height = 2 * borderpx; sizeh->base_height = 2 * borderpx;
sizeh->base_width = 2 * borderpx; sizeh->base_width = 2 * borderpx;
sizeh->min_height = win.ch + 2 * borderpx; sizeh->min_height = win.ch + 2 * borderpx;
@@ -1147,7 +1195,7 @@ xinit(int cols, int rows)
if (!FcInit()) if (!FcInit())
die("could not init fontconfig.\n"); die("could not init fontconfig.\n");
usedfont = (opt_font == NULL)? font : opt_font; usedfont = (opt_font == NULL) ? fonts[currentfont] : opt_font;
xloadfonts(usedfont, 0); xloadfonts(usedfont, 0);
/* colors */ /* colors */
@@ -1263,8 +1311,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)
@@ -1498,7 +1548,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
XftDrawGlyphFontSpec(xw.draw, fg, specs, len); XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
/* Render underline and strikethrough. */ /* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) { if (base.mode & ATTR_UNDERLINE || base.mode & ATTR_URL) {
XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
width, 1); width, 1);
} }
@@ -1635,10 +1685,30 @@ xseticontitle(char *p)
} }
void void
xsettitle(char *p) xfreetitlestack(void)
{
for (int i = 0; i < LEN(titlestack); i++) {
free(titlestack[i]);
titlestack[i] = NULL;
}
}
void
xsettitle(char *p, int pop)
{ {
XTextProperty prop; XTextProperty prop;
DEFAULT(p, opt_title);
free(titlestack[tstki]);
if (pop) {
titlestack[tstki] = NULL;
tstki = (tstki - 1 + TITLESTACKSIZE) % TITLESTACKSIZE;
p = titlestack[tstki] ? titlestack[tstki] : opt_title;
} else if (p) {
titlestack[tstki] = xstrdup(p);
} else {
titlestack[tstki] = NULL;
p = opt_title;
}
if (p[0] == '\0') if (p[0] == '\0')
p = opt_title; p = opt_title;
@@ -1651,6 +1721,16 @@ xsettitle(char *p)
XFree(prop.value); XFree(prop.value);
} }
void
xpushtitle(void)
{
int tstkin = (tstki + 1) % TITLESTACKSIZE;
free(titlestack[tstkin]);
titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL;
tstki = tstkin;
}
int int
xstartdraw(void) xstartdraw(void)
{ {
@@ -1668,6 +1748,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))
@@ -1862,23 +1943,31 @@ 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;
}
/* 0. highlight URLs when control held */
if (ksym == XK_Control_L) {
highlighturls();
} else if (ev->type == KeyRelease && e->keycode == XKeysymToKeycode(e->display, XK_Control_L)) {
unhighlighturls();
}
/* KeyRelease not relevant to shortcuts */
if (ev->type == KeyRelease)
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)) {
if (bp -> func != autocomplete)
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
bp->func(&(bp->arg)); bp->func(&(bp->arg));
return; return;
} }
} }
if (!(
len == 0 &&
e -> state & ~ignoremod // ACMPL_ISSUE: I'm not sure that this is the right way
| ACMPL_MOD == ACMPL_MOD
))
autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
/* 2. custom keys from config.h */ /* 2. custom keys from config.h */
if ((customkey = kmap(ksym, e->state))) { if ((customkey = kmap(ksym, e->state))) {
ttywrite(customkey, strlen(customkey), 1); ttywrite(customkey, strlen(customkey), 1);
@@ -2111,13 +2200,14 @@ xrdb_load(void)
defaultrcs = defaultbg; defaultrcs = defaultbg;
} }
XRESOURCE_LOAD_STRING("font", font); XRESOURCE_LOAD_STRING("font", fonts[0]);
XRESOURCE_LOAD_STRING("termname", termname); XRESOURCE_LOAD_STRING("termname", termname);
XRESOURCE_LOAD_INTEGER("blinktimeout", blinktimeout); XRESOURCE_LOAD_INTEGER("blinktimeout", blinktimeout);
XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume); XRESOURCE_LOAD_INTEGER("bellvolume", bellvolume);
XRESOURCE_LOAD_INTEGER("borderpx", borderpx); XRESOURCE_LOAD_INTEGER("borderpx", borderpx);
XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape); XRESOURCE_LOAD_INTEGER("cursorshape", cursorshape);
XRESOURCE_LOAD_INTEGER("lightmode", lightmode);
XRESOURCE_LOAD_FLOAT("cwscale", cwscale); XRESOURCE_LOAD_FLOAT("cwscale", cwscale);
XRESOURCE_LOAD_FLOAT("chscale", chscale); XRESOURCE_LOAD_FLOAT("chscale", chscale);
@@ -2133,7 +2223,7 @@ reload(int sig)
/* colors, fonts */ /* colors, fonts */
xloadcols(); xloadcols();
xunloadfonts(); xunloadfonts();
xloadfonts(font, 0); xloadfonts(fonts[currentfont], 0);
/* pretend the window just got resized */ /* pretend the window just got resized */
cresize(win.w, win.h); cresize(win.w, win.h);