terminology/src/bin/termiolink.c

602 lines
15 KiB
C
Raw Normal View History

#include "private.h"
#include <Elementary.h>
#include "termpty.h"
#include "backlog.h"
2018-09-26 10:59:33 -07:00
#include "termiolink.h"
#include "termio.h"
#include "sb.h"
#include "utf8.h"
#include "utils.h"
static Eina_Bool
_isspace_unicode(const int codepoint)
{
switch (codepoint)
{
case 9: // character tabulation
EINA_FALLTHROUGH;
case 10: // line feed
EINA_FALLTHROUGH;
case 11: // line tabulation
EINA_FALLTHROUGH;
case 12: // form feed
EINA_FALLTHROUGH;
case 13: // carriage return
EINA_FALLTHROUGH;
case 32: // space
EINA_FALLTHROUGH;
case 133: // next line
EINA_FALLTHROUGH;
case 160: // no-break space
EINA_FALLTHROUGH;
case 5760: // ogham space mark
EINA_FALLTHROUGH;
case 6158: // mongolian vowel separator
EINA_FALLTHROUGH;
case 8192: // en quad
EINA_FALLTHROUGH;
case 8193: // em quad
EINA_FALLTHROUGH;
case 8194: // en space
EINA_FALLTHROUGH;
case 8195: // em space
EINA_FALLTHROUGH;
case 8196: // three-per-em space
EINA_FALLTHROUGH;
case 8197: // four-per-em space
EINA_FALLTHROUGH;
case 8198: // six-per-em space
EINA_FALLTHROUGH;
case 8199: // figure space
EINA_FALLTHROUGH;
case 8200: // puncturation space
EINA_FALLTHROUGH;
case 8201: // thin space
EINA_FALLTHROUGH;
case 8202: // hair space
EINA_FALLTHROUGH;
case 8203: // zero width space
EINA_FALLTHROUGH;
case 8204: // zero width non-joiner
EINA_FALLTHROUGH;
case 8205: // zero width joiner
EINA_FALLTHROUGH;
case 8232: // line separator
EINA_FALLTHROUGH;
case 8233: // paragraph separator
EINA_FALLTHROUGH;
case 8239: // narrow no-break space
EINA_FALLTHROUGH;
case 8287: // medium mathematical space
EINA_FALLTHROUGH;
case 8288: // word joiner
EINA_FALLTHROUGH;
case 12288: // ideographic space
EINA_FALLTHROUGH;
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);
}
2019-09-15 08:47:07 -07:00
Eina_Bool
link_is_protocol(const char *str)
{
const char *p = str;
int 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 (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 = 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)
{
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 *
2017-07-06 11:55:02 -07:00
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;
2013-06-04 14:08:05 -07:00
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;
2016-11-08 12:13:21 -08:00
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;
2016-11-11 03:38:54 -08:00
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;
}
2013-06-06 13:51:32 -07:00
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))
{
2013-06-06 13:51:32 -07:00
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 '"':
case '\'':
case '`':
case '<':
case '>':
case '[':
case ']':
case '{':
case '}':
case '|':
case 0xab:
case 0xbb:
case 0x2018:
case 0x2019:
case 0x201b:
case 0x201c:
case 0x201d:
case 0x201e:
case 0x2039:
case 0x203a:
case 0x2308:
case 0x2309:
case 0x230a:
case 0x230b:
case 0x231c:
case 0x231d:
case 0x231e:
case 0x231f:
case 0x2329:
case 0x232a:
case 0x27e6:
case 0x27e7:
case 0x27e8:
case 0x27e9:
goto out;
}
2016-11-08 12:13:21 -08:00
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)
{
2019-09-15 08:47:07 -07:00
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;
2013-06-15 14:37:10 -07:00
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;
}