terminology/src/bin/termiolink.c

856 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "private.h"
#include <Elementary.h>
#include "termpty.h"
#include "backlog.h"
#include "termiolink.h"
#include "termio.h"
#include "sb.h"
#include "utf8.h"
#include "utils.h"
__attribute__((const))
static Eina_Bool
_isspace_unicode(const int codepoint)
{
switch (codepoint)
{
case 9: // character tabulation
return EINA_TRUE;
case 10: // line feed
return EINA_TRUE;
case 11: // line tabulation
return EINA_TRUE;
case 12: // form feed
return EINA_TRUE;
case 13: // carriage return
return EINA_TRUE;
case 32: // space
return EINA_TRUE;
case 133: // next line
return EINA_TRUE;
case 160: // no-break space
return EINA_TRUE;
case 5760: // ogham space mark
return EINA_TRUE;
case 6158: // mongolian vowel separator
return EINA_TRUE;
case 8192: // en quad
return EINA_TRUE;
case 8193: // em quad
return EINA_TRUE;
case 8194: // en space
return EINA_TRUE;
case 8195: // em space
return EINA_TRUE;
case 8196: // three-per-em space
return EINA_TRUE;
case 8197: // four-per-em space
return EINA_TRUE;
case 8198: // six-per-em space
return EINA_TRUE;
case 8199: // figure space
return EINA_TRUE;
case 8200: // puncturation space
return EINA_TRUE;
case 8201: // thin space
return EINA_TRUE;
case 8202: // hair space
return EINA_TRUE;
case 8203: // zero width space
return EINA_TRUE;
case 8204: // zero width non-joiner
return EINA_TRUE;
case 8205: // zero width joiner
return EINA_TRUE;
case 8232: // line separator
return EINA_TRUE;
case 8233: // paragraph separator
return EINA_TRUE;
case 8239: // narrow no-break space
return EINA_TRUE;
case 8287: // medium mathematical space
return EINA_TRUE;
case 8288: // word joiner
return EINA_TRUE;
case 12288: // ideographic space
return EINA_TRUE;
case 65279: // zero width non-breaking space
return EINA_TRUE;
}
return EINA_FALSE;
}
static char *
_cwd_path_get(const Evas_Object *obj, const char *relpath)
{
char cwdpath[PATH_MAX], tmppath[PATH_MAX];
if (!termio_cwd_get(obj, cwdpath, sizeof(cwdpath)))
return NULL;
eina_str_join(tmppath, sizeof(tmppath), '/', cwdpath, relpath);
return strdup(tmppath);
}
static char *
_home_path_get(const Evas_Object *_obj EINA_UNUSED,
const char *relpath)
{
char tmppath[PATH_MAX], homepath[PATH_MAX];
if (!homedir_get(homepath, sizeof(homepath)))
return NULL;
eina_str_join(tmppath, sizeof(tmppath), '/', homepath, relpath);
return strdup(tmppath);
}
static char *
_local_path_get(const Evas_Object *obj, const char *relpath)
{
if (relpath[0] == '/')
return strdup(relpath);
else if (eina_str_has_prefix(relpath, "~/"))
return _home_path_get(obj, relpath + 2);
else
return _cwd_path_get(obj, relpath);
}
/* isalpha() may produce unsigned-integer-overflow
* runtime error: unsigned integer overflow: 32 - 97 cannot be represented in
* type 'unsigned int'
* int isalpha(int c)
* {
* return ((unsigned)c|32)-'a' < 26;
* }
*/
#if defined(__clang__)
__attribute__((no_sanitize("unsigned-integer-overflow")))
#endif
Eina_Bool
link_is_protocol(const char *str)
{
const char *p = str;
int c;
if (!str)
return EINA_FALSE;
c = *p;
if (!isalpha(c))
return EINA_FALSE;
/* Try to follow RFC3986 a bit
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
* hier-part = "//" authority path-abempty
* [...] other stuff not taken into account
* scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
*/
do
{
p++;
c = *p;
}
while (isalpha(c) || (c == '.') || (c == '-') || (c == '+'));
return (p[0] == ':') && (p[1] == '/') && (p[2] == '/');
}
Eina_Bool
link_is_url(const char *str)
{
if (!str)
return EINA_FALSE;
if (link_is_protocol(str) ||
casestartswith(str, "www.") ||
casestartswith(str, "ftp."))
return EINA_TRUE;
return EINA_FALSE;
}
Eina_Bool
link_is_email(const char *str)
{
const char *at;
if (!str)
return EINA_FALSE;
at = strchr(str, '@');
if (at && strchr(at + 1, '.'))
return EINA_TRUE;
if (casestartswith(str, "mailto:"))
return EINA_TRUE;
return EINA_FALSE;
}
Eina_Bool
link_is_file(const char *str)
{
if (!str)
return EINA_FALSE;
switch (str[0])
{
case '/':
return EINA_TRUE;
case '~':
if (str[1] == '/')
return EINA_TRUE;
return EINA_FALSE;
case '.':
if (str[1] == '/')
return EINA_TRUE;
else if ((str[1] == '.') && (str[2] == '/'))
return EINA_TRUE;
return EINA_FALSE;
default:
return EINA_FALSE;
}
}
static int
_txt_at(Termpty *ty, int *x, int *y, char *txt, int *txtlenp, int *codepointp)
{
Termcell *cells = NULL;
Termcell cell;
ssize_t w;
cells = termpty_cellrow_get(ty, *y, &w);
if (!cells || !w)
goto bad;
if ((*x >= w))
goto empty;
cell = cells[*x];
if ((cell.codepoint == 0) && (cell.att.dblwidth))
{
(*x)--;
if (*x < 0)
goto bad;
cell = cells[*x];
}
if ((cell.codepoint == 0) || (cell.att.link_id))
goto empty;
*txtlenp = codepoint_to_utf8(cell.codepoint, txt);
*codepointp = cell.codepoint;
return 0;
empty:
txt[0] = '\0';
*txtlenp = 0;
*codepointp = 0;
return 0;
bad:
*txtlenp = 0;
txt[0] = '\0';
return -1;
}
static int
_txt_prev_at(Termpty *ty, int *x, int *y, char *txt, int *txtlenp,
int *codepointp)
{
Termcell *cells = NULL;
Termcell cell;
ssize_t w;
(*x)--;
if ((*x) < 0)
{
(*y)--;
*x = ty->w-1;
cells = termpty_cellrow_get(ty, *y, &w);
if (!cells || !w)
goto bad;
if ((*x) >= w)
goto empty;
cell = cells[*x];
/* Either the cell is in the normal screen and needs to have
* autowrapped flag or is in the backlog and its length is larger than
* the screen, spanning multiple lines */
if (((!cell.att.autowrapped) && (*y) >= 0)
|| (w < ty->w))
goto empty;
}
else
{
cells = termpty_cellrow_get(ty, *y, &w);
if (!cells || !w)
goto bad;
if ((*x) >= w)
goto empty;
cell = cells[*x];
}
if ((cell.codepoint == 0) && (cell.att.dblwidth))
{
(*x)--;
if (*x < 0)
goto bad;
cell = cells[*x];
}
if ((cell.codepoint == 0) || (cell.att.link_id))
goto empty;
*txtlenp = codepoint_to_utf8(cell.codepoint, txt);
*codepointp = cell.codepoint;
return 0;
empty:
txt[0] = '\0';
*txtlenp = 0;
*codepointp = 0;
return 0;
bad:
*txtlenp = 0;
txt[0] = '\0';
return -1;
}
static int
_txt_next_at(Termpty *ty, int *x, int *y, char *txt, int *txtlenp,
int *codepointp)
{
Termcell *cells = NULL;
Termcell cell;
ssize_t w;
(*x)++;
cells = termpty_cellrow_get(ty, *y, &w);
if (!cells || !w)
goto bad;
if ((*x) >= w)
{
(*y)++;
if ((*x) <= ty->w)
{
cell = cells[w-1];
if (!cell.att.autowrapped)
goto empty;
}
*x = 0;
cells = termpty_cellrow_get(ty, *y, &w);
if (!cells || !w)
goto bad;
}
cell = cells[*x];
if ((cell.codepoint == 0) && (cell.att.dblwidth))
{
(*x)++;
if (*x >= w)
{
cell = cells[w-1];
if (!cell.att.autowrapped && w == ty->w)
goto empty;
(*y)++;
*x = 0;
cells = termpty_cellrow_get(ty, *y, &w);
if (!cells || !w)
goto bad;
}
goto bad;
}
cell = cells[*x];
if ((cell.codepoint == 0) || (cell.att.link_id))
goto empty;
*txtlenp = codepoint_to_utf8(cell.codepoint, txt);
*codepointp = cell.codepoint;
return 0;
empty:
txt[0] = '\0';
*txtlenp = 0;
*codepointp = 0;
return 0;
bad:
*txtlenp = 0;
txt[0] = '\0';
return -1;
}
/* returned string must be freed */
char *
termio_link_find(const Evas_Object *obj, int cx, int cy,
int *x1r, int *y1r, int *x2r, int *y2r)
{
char *s = NULL;
int endmatch1 = 0, endmatch2 = 0;
int x1, x2, y1, y2, w = 0, h = 0, sc;
Eina_Bool goback = EINA_TRUE,
goforward = EINA_FALSE,
escaped = EINA_FALSE;
struct ty_sb sb = {.buf = NULL, .gap = 0, .len = 0, .alloc = 0};
Termpty *ty = termio_pty_get(obj);
int res;
char txt[8];
int txtlen = 0;
int codepoint = 0;
Eina_Bool was_protocol = EINA_FALSE;
EINA_SAFETY_ON_NULL_RETURN_VAL(ty, NULL);
x1 = x2 = cx;
y1 = y2 = cy;
termio_size_get(obj, &w, &h);
if ((w <= 0) || (h <= 0)) goto end;
sc = termio_scroll_get(obj);
termpty_backlog_lock();
y1 -= sc;
y2 -= sc;
res = _txt_at(ty, &x1, &y1, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0)) goto end;
if (_isspace_unicode(codepoint))
goto end;
res = ty_sb_add(&sb, txt, txtlen);
if (res < 0) goto end;
while (goback)
{
int new_x1 = x1, new_y1 = y1;
res = _txt_prev_at(ty, &new_x1, &new_y1, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0))
{
goback = EINA_FALSE;
goforward = EINA_TRUE;
break;
}
res = ty_sb_prepend(&sb, txt, txtlen);
if (res < 0) goto end;
if (_isspace_unicode(codepoint))
{
int old_txtlen = txtlen;
res = _txt_prev_at(ty, &new_x1, &new_y1, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0) || (codepoint != '\\'))
{
ty_sb_lskip(&sb, old_txtlen);
goback = EINA_FALSE;
goforward = EINA_TRUE;
break;
}
}
switch (codepoint)
{
case '"': endmatch1 = endmatch2 = '"'; break;
case '\'': endmatch1 = endmatch2 = '\''; break;
case '`': endmatch1 = endmatch2 = '`'; break;
case '<': endmatch1 = endmatch2 = '>'; break;
case '[': endmatch1 = endmatch2 = ']'; break;
case ']': endmatch1 = endmatch2 = '['; break;
case '{': endmatch1 = endmatch2 = '}'; break;
case '(': endmatch1 = endmatch2 = ')'; break;
case '|': endmatch1 = endmatch2 = '|'; break;
case 0xab: endmatch1 = endmatch2 = 0xbb; break; // french « »
case 0xbb: endmatch1 = endmatch2 = 0xab; break; // swedish » «
case 0x2018: endmatch1 = endmatch2 = 0x2019; break; //
case 0x201b: endmatch1 = endmatch2 = 0x2019; break; //
case 0x201c: endmatch1 = endmatch2 = 0x201d; break; // “ ”
case 0x201e: endmatch1 = 0x201c; endmatch2 = 0x201d; break; // „ “”
case 0x2039: endmatch1 = endmatch2 = 0x203a; break; //
case 0x27e6: endmatch1 = endmatch2 = 0x27e7; break; // ⟦ ⟧
case 0x27e8: endmatch1 = endmatch2 = 0x27e9; break; // ⟨ ⟩
case 0x2329: endmatch1 = endmatch2 = 0x232a; break; // 〈 〉
case 0x231c: endmatch1 = 0x231d; endmatch2 = 0x231f; break; // ⌜⌝⌟
case 0x231e: endmatch1 = 0x231d; endmatch2 = 0x231f; break; // ⌞⌝⌟
case 0x2308: endmatch1 = 0x2309; endmatch2 = 0x230b; break; // ⌈⌉⌋
case 0x230a: endmatch1 = 0x2309; endmatch2 = 0x230b; break; // ⌊⌉⌋
}
if (endmatch1)
{
ty_sb_lskip(&sb, txtlen);
goback = EINA_FALSE;
goforward = EINA_TRUE;
break;
}
if (!link_is_protocol(sb.buf))
{
if (was_protocol)
{
if (!_isspace_unicode(codepoint))
endmatch1 = endmatch2 = codepoint;
ty_sb_lskip(&sb, txtlen);
goback = EINA_FALSE;
goforward = EINA_TRUE;
break;
}
}
else
{
was_protocol = EINA_TRUE;
}
x1 = new_x1;
y1 = new_y1;
}
while (goforward)
{
int new_x2 = x2, new_y2 = y2;
/* Check if the previous char is a delimiter */
res = _txt_next_at(ty, &new_x2, &new_y2, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0))
{
goforward = EINA_FALSE;
break;
}
/* escaping */
if (codepoint == '\\')
{
x2 = new_x2;
y2 = new_y2;
escaped = EINA_TRUE;
continue;
}
if (escaped)
{
x2 = new_x2;
y2 = new_y2;
escaped = EINA_FALSE;
}
if (_isspace_unicode(codepoint) || (codepoint == endmatch1)
|| (codepoint == endmatch2))
{
goforward = EINA_FALSE;
break;
}
switch (codepoint)
{
case '"': goto out;
case '\'': goto out;
case '`': goto out;
case '<': goto out;
case '>': goto out;
case '[': goto out;
case ']': goto out;
case '{': goto out;
case '}': goto out;
case '|': goto out;
case 0xab: goto out;
case 0xbb: goto out;
case 0x2018: goto out;
case 0x2019: goto out;
case 0x201b: goto out;
case 0x201c: goto out;
case 0x201d: goto out;
case 0x201e: goto out;
case 0x2039: goto out;
case 0x203a: goto out;
case 0x2308: goto out;
case 0x2309: goto out;
case 0x230a: goto out;
case 0x230b: goto out;
case 0x231c: goto out;
case 0x231d: goto out;
case 0x231e: goto out;
case 0x231f: goto out;
case 0x2329: goto out;
case 0x232a: goto out;
case 0x27e6: goto out;
case 0x27e7: goto out;
case 0x27e8: goto out;
case 0x27e9: goto out;
}
res = ty_sb_add(&sb, txt, txtlen);
if (res < 0) goto end;
if (!link_is_protocol(sb.buf))
{
if (was_protocol)
{
ty_sb_rskip(&sb, txtlen);
goback = EINA_FALSE;
}
}
else
{
was_protocol = EINA_TRUE;
}
x2 = new_x2;
y2 = new_y2;
}
out:
if (sb.len)
{
Eina_Bool is_file = link_is_file(sb.buf);
if (is_file ||
link_is_email(sb.buf) ||
link_is_url(sb.buf))
{
if (x1r) *x1r = x1;
if (y1r) *y1r = y1 + sc;
if (x2r) *x2r = x2;
if (y2r) *y2r = y2 + sc;
if (is_file && (sb.buf[0] != '/'))
{
char *local= _local_path_get(obj, (const char*)sb.buf);
ty_sb_free(&sb);
s = local;
goto end;
}
else
{
s = ty_sb_steal_buf(&sb);
}
goto end;
}
}
end:
termpty_backlog_unlock();
ty_sb_free(&sb);
return s;
}
static Eina_Bool
_is_authorized_in_color(const int codepoint)
{
switch (codepoint)
{
case '#': EINA_FALLTHROUGH;
case '0': EINA_FALLTHROUGH;
case '1': EINA_FALLTHROUGH;
case '2': EINA_FALLTHROUGH;
case '3': EINA_FALLTHROUGH;
case '4': EINA_FALLTHROUGH;
case '5': EINA_FALLTHROUGH;
case '6': EINA_FALLTHROUGH;
case '7': EINA_FALLTHROUGH;
case '8': EINA_FALLTHROUGH;
case '9': EINA_FALLTHROUGH;
case 'a': EINA_FALLTHROUGH;
case 'A': EINA_FALLTHROUGH;
case 'b': EINA_FALLTHROUGH;
case 'B': EINA_FALLTHROUGH;
case 'c': EINA_FALLTHROUGH;
case 'C': EINA_FALLTHROUGH;
case 'd': EINA_FALLTHROUGH;
case 'D': EINA_FALLTHROUGH;
case 'e': EINA_FALLTHROUGH;
case 'E': EINA_FALLTHROUGH;
case 'f': EINA_FALLTHROUGH;
case 'F':
return EINA_TRUE;
}
return EINA_FALSE;
}
static Eina_Bool
_parse_hex(const char c, uint8_t *v)
{
if (c < '0')
return EINA_FALSE;
if (c <= '9')
{
*v = c - '0';
return EINA_TRUE;
}
if (c < 'A')
return EINA_FALSE;
if (c <= 'F')
{
*v = 10 + c - 'A';
return EINA_TRUE;
}
if (c < 'a')
return EINA_FALSE;
if (c <= 'f')
{
*v = 10 + c - 'a';
return EINA_TRUE;
}
return EINA_FALSE;
}
static Eina_Bool
_parse_2hex(const char *s, uint8_t *v)
{
uint8_t v0, v1;
if (!_parse_hex(s[0], &v0))
return EINA_FALSE;
if (!_parse_hex(s[1], &v1))
return EINA_FALSE;
*v = v0 << 4 | v1;
return EINA_TRUE;
}
Eina_Bool
termio_color_find(const Evas_Object *obj, int cx, int cy,
int *x1r, int *y1r, int *x2r, int *y2r,
uint8_t *rp, uint8_t *gp, uint8_t *bp, uint8_t *ap)
{
int x1, x2, y1, y2, w = 0, h = 0, sc;
//const char authorized[] = "#0123456789aAbBcCdDeEfFrghsoltun() ,+/";
Eina_Bool goback = EINA_TRUE,
goforward = EINA_FALSE;
struct ty_sb sb = {.buf = NULL, .gap = 0, .len = 0, .alloc = 0};
Termpty *ty = termio_pty_get(obj);
int res;
char txt[8];
int txtlen = 0;
Eina_Bool found = EINA_FALSE;
int codepoint;
uint8_t r, g, b, a = 255;
EINA_SAFETY_ON_NULL_RETURN_VAL(ty, EINA_FALSE);
x1 = x2 = cx;
y1 = y2 = cy;
termio_size_get(obj, &w, &h);
if ((w <= 0) || (h <= 0)) goto end;
sc = termio_scroll_get(obj);
termpty_backlog_lock();
y1 -= sc;
y2 -= sc;
/* TODO: boris */
res = _txt_at(ty, &x1, &y1, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0))
goto end;
if (!_is_authorized_in_color(codepoint))
goto end;
res = ty_sb_add(&sb, txt, txtlen);
if (res < 0) goto end;
while (goback)
{
int new_x1 = x1, new_y1 = y1;
res = _txt_prev_at(ty, &new_x1, &new_y1, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0))
{
goback = EINA_FALSE;
goforward = EINA_TRUE;
break;
}
res = ty_sb_prepend(&sb, txt, txtlen);
if (res < 0) goto end;
if (!_is_authorized_in_color(codepoint))
{
ty_sb_lskip(&sb, txtlen);
goback = EINA_FALSE;
goforward = EINA_TRUE;
break;
}
x1 = new_x1;
y1 = new_y1;
}
while (goforward)
{
int new_x2 = x2, new_y2 = y2;
/* Check if the previous char is a delimiter */
res = _txt_next_at(ty, &new_x2, &new_y2, txt, &txtlen, &codepoint);
if ((res != 0) || (txtlen == 0))
{
goforward = EINA_FALSE;
break;
}
if (!_is_authorized_in_color(codepoint))
{
goforward = EINA_FALSE;
break;
}
res = ty_sb_add(&sb, txt, txtlen);
if (res < 0) goto end;
x2 = new_x2;
y2 = new_y2;
}
if (!sb.len)
goto end;
if (sb.buf[0] == '#')
{
ty_sb_lskip(&sb, 1);
switch (sb.len)
{
case 3:
if ((!_parse_hex(sb.buf[0], &r)) ||
(!_parse_hex(sb.buf[1], &g)) ||
(!_parse_hex(sb.buf[2], &b)))
goto end;
r <<= 4;
g <<= 4;
b <<= 4;
break;
case 4:
if ((!_parse_hex(sb.buf[0], &r)) ||
(!_parse_hex(sb.buf[1], &g)) ||
(!_parse_hex(sb.buf[2], &b)) ||
(!_parse_hex(sb.buf[3], &a)))
goto end;
r <<= 4;
g <<= 4;
b <<= 4;
a <<= 4;
break;
case 6:
if ((!_parse_2hex(&sb.buf[0], &r)) ||
(!_parse_2hex(&sb.buf[2], &g)) ||
(!_parse_2hex(&sb.buf[4], &b)))
goto end;
break;
case 8:
if ((!_parse_2hex(&sb.buf[0], &r)) ||
(!_parse_2hex(&sb.buf[2], &g)) ||
(!_parse_2hex(&sb.buf[4], &b)) ||
(!_parse_2hex(&sb.buf[6], &a)))
goto end;
break;
default:
goto end;
}
found = EINA_TRUE;
}
end:
termpty_backlog_unlock();
ty_sb_free(&sb);
if (found)
{
if (rp) *rp = r;
if (gp) *gp = g;
if (bp) *bp = b;
if (ap) *ap = a;
if (x1r) *x1r = x1;
if (y1r) *y1r = y1 + sc;
if (x2r) *x2r = x2;
if (y2r) *y2r = y2 + sc;
}
return found;
}