terminology/src/bin/termiolink.c

602 lines
15 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"
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);
}
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 *
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 '"':
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;
}
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;
}