module patch.openurlonclick; import core.stdc.string : strchr, strncmp; import core.sys.posix.unistd : pid_t; import std.algorithm : min, max; import std.string : toStringz, fromStringz; import patches : isPatchEnabled; import st : term, Rune, Line, ATTR_WRAP; import st : TLINE; // Define ATTR_SET if not already defined enum ATTR_SET = 1 << 15; enum MODE_ALTSCREEN = 1 << 2; // Define IS_SET helper bool IS_SET(uint flag) { import st : win; return (win.mode & flag) != 0; } static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { static if (!isPatchEnabled!"REFLOW_PATCH") { static if (isPatchEnabled!"SCROLLBACK_PATCH") { alias TLINEURL = TLINE; } else { auto TLINEURL(int y) { return term.line[y]; } } } extern(C) export __gshared int url_x1, url_y1, url_x2, url_y2 = -1; extern(C) export __gshared int url_draw, url_click, url_maxcol; private int isvalidurlchar(Rune u) { static immutable string urlchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ~ "abcdefghijklmnopqrstuvwxyz" ~ "0123456789-._~:/?#@!$&'*+,;=%"; return u < 128 && strchr(urlchars.ptr, cast(int)u) != null; } static if (isPatchEnabled!"REFLOW_PATCH") { private int findeowl(Line line) { int i = term.col - 1; do { if (line[i].mode & ATTR_WRAP) return i; } while (!(line[i].mode & ATTR_SET) && --i >= 0); return -1; } } else { private int findeowl(int row) { static if (isPatchEnabled!"COLUMNS_PATCH") { int col = term.maxcol - 1; } else { int col = term.col - 1; } do { if (TLINEURL(row)[col].mode & ATTR_WRAP) return col; } while (TLINEURL(row)[col].u == ' ' && --col >= 0); return -1; } } void clearurl() { while (url_y1 <= url_y2 && url_y1 < term.row) term.dirty[url_y1++] = 1; url_y2 = -1; } static if (isPatchEnabled!"REFLOW_PATCH") { char* detecturl(int col, int row, int draw) { static char[2048] url; Line line; int x1, y1, x2, y2; int i = url.length/2+1, j = cast(int)(url.length/2); int row_start = row, col_start = col; int minrow = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr - term.histf; int maxrow = IS_SET(MODE_ALTSCREEN) ? term.row - 1 : term.scr + term.row - 1; if (draw) clearurl(); url_maxcol = 0; line = TLINE(row); if (!isvalidurlchar(line[col].u)) return null; do { x1 = col_start; y1 = row_start; url_maxcol = max(url_maxcol, x1); url[--i] = cast(char)line[col_start].u; if (--col_start < 0) { if (--row_start < minrow || (col_start = findeowl(TLINE(row_start))) < 0) break; line = TLINE(row_start); } } while (isvalidurlchar(line[col_start].u) && i > 0); if (url[i] != 'h') return null; line = TLINE(row); do { x2 = col; y2 = row; url_maxcol = max(url_maxcol, x2); url[j++] = cast(char)line[col].u; if (line[col++].mode & ATTR_WRAP) { if (++row > maxrow) break; col = 0; line = TLINE(row); } } while (col < term.col && isvalidurlchar(line[col].u) && j < url.length-1); url[j] = 0; if (strncmp("https://".ptr, &url[i], 8) && strncmp("http://".ptr, &url[i], 7)) return null; if (strchr(",.;:?!".ptr, cast(int)(url[j-1])) != null) { x2 = max(x2-1, 0); url[j-1] = 0; } if (draw) { url_x1 = (y1 >= 0) ? x1 : 0; url_x2 = (y2 < term.row) ? x2 : url_maxcol; url_y1 = max(y1, 0); url_y2 = min(y2, term.row-1); url_draw = 1; for (y1 = url_y1; y1 <= url_y2; y1++) term.dirty[y1] = 1; } return &url[i]; } } else { char* detecturl(int col, int row, int draw) { static char[2048] url; int x1, y1, x2, y2, wrapped; int row_start = row; int col_start = col; int i = url.length/2+1, j = cast(int)(url.length/2); static if (isPatchEnabled!"SCROLLBACK_PATCH") { int minrow = term.scr - term.histn, maxrow = term.scr + term.row - 1; if ((term.mode & (1 << 2)) != 0) minrow = 0, maxrow = term.row - 1; } else { int minrow = 0, maxrow = term.row - 1; } url_maxcol = 0; if (draw) clearurl(); if (!isvalidurlchar(TLINEURL(row)[col].u)) return null; do { x1 = col_start; y1 = row_start; url_maxcol = max(url_maxcol, x1); url[--i] = cast(char)TLINEURL(row_start)[col_start].u; if (--col_start < 0) { if (--row_start < minrow || (col_start = findeowl(row_start)) < 0) break; } } while (i > 0 && isvalidurlchar(TLINEURL(row_start)[col_start].u)); if (url[i] != 'h') return null; do { x2 = col; y2 = row; url_maxcol = max(url_maxcol, x2); url[j++] = cast(char)TLINEURL(row)[col].u; wrapped = TLINEURL(row)[col].mode & ATTR_WRAP; static if (isPatchEnabled!"COLUMNS_PATCH") { if (++col >= term.maxcol || wrapped) { col = 0; if (++row > maxrow || !wrapped) break; } } else { if (++col >= term.col || wrapped) { col = 0; if (++row > maxrow || !wrapped) break; } } } while (j < url.length-1 && isvalidurlchar(TLINEURL(row)[col].u)); url[j] = 0; if (strncmp("https://".ptr, &url[i], 8) && strncmp("http://".ptr, &url[i], 7)) return null; if (draw) { url_x1 = (y1 >= 0) ? x1 : 0; url_x2 = (y2 < term.row) ? x2 : url_maxcol; url_y1 = max(y1, 0); url_y2 = min(y2, term.row-1); url_draw = 1; for (y1 = url_y1; y1 <= url_y2; y1++) term.dirty[y1] = 1; } return &url[i]; } } void openUrlOnClick(int col, int row, char* url_opener) { char* url = detecturl(col, row, 1); if (url) { import core.stdc.stdlib : system; import std.format : format; import std.process : spawnProcess; // Use spawnProcess instead of posix_spawnp string[] args = [cast(string)fromStringz(url_opener), cast(string)fromStringz(url)]; try { spawnProcess(args); } catch (Exception e) { // Ignore errors } } } }