summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-02-06 02:14:51 +0100
committersalmonik <raisersoft@proton.me>2026-02-06 02:14:51 +0100
commit931e5010d51d1640c61034ada767715a544fa40b (patch)
tree46bd0b5c046a4aaf14c7a571425a745d2d12be2b
parentscrollback (diff)
downloadst-931e5010d51d1640c61034ada767715a544fa40b.tar.gz
st-931e5010d51d1640c61034ada767715a544fa40b.zip
apply scrollback patch and urlclick patch
-rw-r--r--config.def.h11
-rw-r--r--st.c92
-rw-r--r--st.h9
-rw-r--r--x.c24
4 files changed, 135 insertions, 1 deletions
diff --git a/config.def.h b/config.def.h
index 4f0b27e..375c248 100644
--- a/config.def.h
+++ b/config.def.h
@@ -481,3 +481,14 @@ static char ascii_printable[] =
481 " !\"#$%&'()*+,-./0123456789:;<=>?" 481 " !\"#$%&'()*+,-./0123456789:;<=>?"
482 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" 482 "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
483 "`abcdefghijklmnopqrstuvwxyz{|}~"; 483 "`abcdefghijklmnopqrstuvwxyz{|}~";
484
485/*
486 * Open urls starting with urlprefixes, contatining urlchars
487 * by passing as ARG1 to urlhandler.
488 */
489char* urlhandler = "xdg-open";
490char urlchars[] =
491 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
492 "abcdefghijklmnopqrstuvwxyz"
493 "0123456789-._~:/?#@!$&'*+,;=%";
494char* urlprefixes[] = {"http://", "https://", NULL};
diff --git a/st.c b/st.c
index 17fc9fb..170a8a8 100644
--- a/st.c
+++ b/st.c
@@ -652,6 +652,98 @@ getsel(void)
652 return str; 652 return str;
653} 653}
654 654
655char *
656strstrany(char* s, char** strs) {
657 char *match;
658 for (int i = 0; strs[i]; i++) {
659 if ((match = strstr(s, strs[i]))) {
660 return match;
661 }
662 }
663 return NULL;
664}
665
666void
667highlighturls(void)
668{
669 char *match;
670 char *linestr = calloc(sizeof(char), term.col+1); /* assume ascii */
671 for (int i = 0; i < term.row; i++) {
672 Line line = TLINE(i);
673 int url_start = -1;
674 for (int j = 0; j < term.col; j++) {
675 if (line[j].u < 127) {
676 linestr[j] = line[j].u;
677 }
678 linestr[term.col] = '\0';
679 }
680 while ((match = strstrany(linestr + url_start + 1, urlprefixes))) {
681 url_start = match - linestr;
682 for (int c = url_start; c < term.col && strchr(urlchars, linestr[c]); c++) {
683 line[c].mode |= ATTR_URL;
684 tsetdirt(i, c);
685 }
686 }
687 }
688 free(linestr);
689}
690
691void
692unhighlighturls(void)
693{
694 for (int i = 0; i < term.row; i++) {
695 Line line = TLINE(i);
696 for (int j = 0; j < term.col; j++) {
697 Glyph* g = &line[j];
698 if (g->mode & ATTR_URL) {
699 g->mode &= ~ATTR_URL;
700 tsetdirt(i, j);
701 }
702 }
703 }
704 return;
705}
706
707void
708followurl(int x, int y) {
709 char *linestr = calloc(sizeof(char), term.col+1); /* assume ascii */
710 char *match;
711 Line line = TLINE(x);
712 for (int i = 0; i < term.col; i++) {
713 if (line[i].u < 127) {
714 linestr[i] = line[i].u;
715 }
716 linestr[term.col] = '\0';
717 }
718 int url_start = -1;
719 while ((match = strstrany(linestr + url_start + 1, urlprefixes))) {
720 url_start = match - linestr;
721 int url_end = url_start;
722 for (int c = url_start; c < term.col && strchr(urlchars, linestr[c]); c++) {
723 url_end++;
724 }
725 if (url_start <= y && y < url_end) {
726 linestr[url_end] = '\0';
727 break;
728 }
729 }
730 if (url_start == -1) {
731 free(linestr);
732 return;
733 }
734
735 pid_t chpid;
736 if ((chpid = fork()) == 0) {
737 if (fork() == 0)
738 execlp(urlhandler, urlhandler, linestr + url_start, NULL);
739 exit(1);
740 }
741 if (chpid > 0)
742 waitpid(chpid, NULL, 0);
743 free(linestr);
744 unhighlighturls();
745}
746
655void 747void
656selclear(void) 748selclear(void)
657{ 749{
diff --git a/st.h b/st.h
index 3cea73b..edd5ea7 100644
--- a/st.h
+++ b/st.h
@@ -35,6 +35,7 @@ enum glyph_attribute {
35 ATTR_WIDE = 1 << 9, 35 ATTR_WIDE = 1 << 9,
36 ATTR_WDUMMY = 1 << 10, 36 ATTR_WDUMMY = 1 << 10,
37 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, 37 ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
38 ATTR_URL = 1 << 14,
38}; 39};
39 40
40enum selection_mode { 41enum selection_mode {
@@ -106,6 +107,10 @@ void selextend(int, int, int, int);
106int selected(int, int); 107int selected(int, int);
107char *getsel(void); 108char *getsel(void);
108 109
110void highlighturls(void);
111void unhighlighturls(void);
112void followurl(int, int);
113
109size_t utf8encode(Rune, char *); 114size_t utf8encode(Rune, char *);
110 115
111void *xmalloc(size_t); 116void *xmalloc(size_t);
@@ -125,3 +130,7 @@ extern unsigned int tabspaces;
125extern unsigned int defaultfg; 130extern unsigned int defaultfg;
126extern unsigned int defaultbg; 131extern unsigned int defaultbg;
127extern unsigned int defaultcs; 132extern unsigned int defaultcs;
133extern char *urlhandler;
134extern char urlchars[];
135extern char *urlprefixes[];
136extern int nurlprefixes;
diff --git a/x.c b/x.c
index d50c0c4..fbb7891 100644
--- a/x.c
+++ b/x.c
@@ -193,6 +193,7 @@ static void usage(void);
193 193
194static void (*handler[LASTEvent])(XEvent *) = { 194static void (*handler[LASTEvent])(XEvent *) = {
195 [KeyPress] = kpress, 195 [KeyPress] = kpress,
196 [KeyRelease] = kpress,
196 [ClientMessage] = cmessage, 197 [ClientMessage] = cmessage,
197 [ConfigureNotify] = resize, 198 [ConfigureNotify] = resize,
198 [VisibilityNotify] = visibility, 199 [VisibilityNotify] = visibility,
@@ -455,6 +456,15 @@ mouseaction(XEvent *e, uint release)
455 /* ignore Button<N>mask for Button<N> - it's set on release */ 456 /* ignore Button<N>mask for Button<N> - it's set on release */
456 uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); 457 uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
457 458
459 if (release == 0 &&
460 e->xbutton.button == Button1 &&
461 (match(ControlMask, state) ||
462 match(ControlMask, state & ~forcemousemod))) {
463 followurl(evrow(e), evcol(e));
464 return 1;
465 }
466
467
458 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { 468 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
459 if (ms->release == release && 469 if (ms->release == release &&
460 ms->button == e->xbutton.button && 470 ms->button == e->xbutton.button &&
@@ -1498,7 +1508,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
1498 XftDrawGlyphFontSpec(xw.draw, fg, specs, len); 1508 XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
1499 1509
1500 /* Render underline and strikethrough. */ 1510 /* Render underline and strikethrough. */
1501 if (base.mode & ATTR_UNDERLINE) { 1511 if (base.mode & ATTR_UNDERLINE || base.mode & ATTR_URL) {
1502 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1, 1512 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
1503 width, 1); 1513 width, 1);
1504 } 1514 }
@@ -1880,6 +1890,18 @@ kpress(XEvent *ev)
1880 } else { 1890 } else {
1881 len = XLookupString(e, buf, sizeof buf, &ksym, NULL); 1891 len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
1882 } 1892 }
1893
1894 /* 0. highlight URLs when control held */
1895 if (ksym == XK_Control_L) {
1896 highlighturls();
1897 } else if (ev->type == KeyRelease && e->keycode == XKeysymToKeycode(e->display, XK_Control_L)) {
1898 unhighlighturls();
1899 }
1900
1901 /* KeyRelease not relevant to shortcuts */
1902 if (ev->type == KeyRelease)
1903 return;
1904
1883 /* 1. shortcuts */ 1905 /* 1. shortcuts */
1884 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { 1906 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
1885 if (ksym == bp->keysym && match(bp->mod, e->state)) { 1907 if (ksym == bp->keysym && match(bp->mod, e->state)) {