More work on multicell URL detection

This commit is contained in:
Kovid Goyal 2025-01-13 11:11:16 +05:30
parent cf89dba5af
commit 6a19918687
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 65 additions and 46 deletions

View file

@ -296,29 +296,60 @@ is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_ho
}
index_type
line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos) {
line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos, ListOfChars *lc) {
index_type ans = x;
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0;
RAII_ListOfChars(lc);
#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos, &lc)
if (sentinel) { while (ans < self->xnum && !cell_is_char(self->cpu_cells + ans, sentinel) && pos_ok(ans)) ans++; }
else { while (ans < self->xnum && pos_ok(ans)) ans++; }
if (ans) ans--;
if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) {
while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) ans--;
#define is_not_ok(n) ((sentinel && cell_is_char(self->cpu_cells + n, sentinel)) || !is_pos_ok_for_url(self, n, in_hostname, last_hostname_char_pos, lc))
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3) || is_not_ok(x)) return 0;
index_type n = ans;
while ((n = next_char_pos(self, ans, 1)) < self->xnum) {
if (is_not_ok(n)) break;
ans = n;
}
#undef is_not_ok
if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) {
while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) {
n = prev_char_pos(self, ans, 1);
if (n >= self->xnum || n < x) break;
ans = n;
}
}
#undef pos_ok
return ans;
}
bool
line_startswith_url_chars(Line *self, bool in_hostname) {
RAII_ListOfChars(lc);
text_in_cell(self->cpu_cells, self->text_cache, &lc);
if (in_hostname) return is_hostname_lc(&lc);
return is_url_lc(&lc);
line_startswith_url_chars(Line *self, bool in_hostname, ListOfChars *lc) {
text_in_cell(self->cpu_cells, self->text_cache, lc);
if (in_hostname) return is_hostname_lc(lc);
return is_url_lc(lc);
}
char_type
get_url_sentinel(Line *line, index_type url_start) {
char_type before = 0, sentinel;
if (url_start > 0 && url_start < line->xnum) {
index_type n = prev_char_pos(line, url_start, 1);
if (n < line->xnum) before = cell_first_char(line->cpu_cells + n, line->text_cache);
}
switch(before) {
case '"':
case '\'':
case '*':
sentinel = before; break;
case '(':
sentinel = ')'; break;
case '[':
sentinel = ']'; break;
case '{':
sentinel = '}'; break;
case '<':
sentinel = '>'; break;
default:
sentinel = 0; break;
}
return sentinel;
}
static PyObject*
url_start_at(Line *self, PyObject *x) {
@ -333,7 +364,8 @@ url_end_at(Line *self, PyObject *args) {
unsigned int x, sentinel = 0;
int next_line_starts_with_url_chars = 0;
if (!PyArg_ParseTuple(args, "I|Ip", &x, &sentinel, &next_line_starts_with_url_chars)) return NULL;
return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars, false, self->xnum));
RAII_ListOfChars(lc);
return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars, false, self->xnum, &lc));
}
// }}}

View file

@ -70,8 +70,9 @@ void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch
void line_apply_cursor(Line *self, const Cursor *cursor, unsigned int at, unsigned int num, bool clear_char);
char_type line_get_char(Line *self, index_type at);
index_type line_url_start_at(Line *self, index_type x, ListOfChars *lc);
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type);
bool line_startswith_url_chars(Line*, bool);
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type, ListOfChars*);
bool line_startswith_url_chars(Line*, bool, ListOfChars*);
char_type get_url_sentinel(Line *line, index_type url_start);
bool line_as_ansi(Line *self, ANSILineState *s, index_type start_at, index_type stop_before, char_type prefix_char, bool skip_multiline_non_zero_lines) __attribute__((nonnull));
unsigned int line_length(Line *self);
size_t cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf);

View file

@ -3706,7 +3706,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
bool next_line_starts_with_url_chars = false;
line = screen_visual_line(screen, *y + 2);
if (line) {
next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname);
next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname, screen->lc);
has_newline = !line->attrs.is_continued;
if (next_line_starts_with_url_chars && has_newline && !newlines_allowed) next_line_starts_with_url_chars = false;
if (sentinel && next_line_starts_with_url_chars && cell_is_char(line->cpu_cells, sentinel)) next_line_starts_with_url_chars = false;
@ -3722,8 +3722,8 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
}
}
}
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars, in_hostname, last_hostname_char_pos);
if (!new_x && !line_startswith_url_chars(line, in_hostname)) break;
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars, in_hostname, last_hostname_char_pos, screen->lc);
if (!new_x && !line_startswith_url_chars(line, in_hostname, screen->lc)) break;
*y += 1; *x = new_x;
}
if (sentinel && *x == 0 && *y > orig_y) {
@ -3734,29 +3734,6 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
}
}
static char_type
get_url_sentinel(Line *line, index_type url_start) {
char_type before = 0, sentinel;
if (url_start > 0 && url_start < line->xnum) before = cell_first_char(line->cpu_cells + url_start - 1, line->text_cache);
switch(before) {
case '"':
case '\'':
case '*':
sentinel = before; break;
case '(':
sentinel = ')'; break;
case '[':
sentinel = ']'; break;
case '{':
sentinel = '}'; break;
case '<':
sentinel = '>'; break;
default:
sentinel = 0; break;
}
return sentinel;
}
int
screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
bool has_url = false;
@ -3783,7 +3760,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
bool next_line_starts_with_url_chars = false;
if (y < screen->lines - 1) {
visual_line(screen, y + 1, &scratch);
next_line_starts_with_url_chars = line_startswith_url_chars(&scratch, last_hostname_char_pos >= line->xnum);
next_line_starts_with_url_chars = line_startswith_url_chars(&scratch, last_hostname_char_pos >= line->xnum, screen->lc);
if (next_line_starts_with_url_chars && !newlines_allowed && !scratch.attrs.is_continued) next_line_starts_with_url_chars = false;
}
sentinel = get_url_sentinel(line, url_start);
@ -3791,7 +3768,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
for (index_type i = url_start; i < line->xnum; i++) {
if (cell_is_char(line->cpu_cells + i, '/') && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; }
}
url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos);
url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos, screen->lc);
}
has_url = url_end > url_start;
if (has_url) {

View file

@ -679,3 +679,12 @@ def set_link(url=None, id=None):
self.ae('url-a', s.hyperlink_at(x, y))
asu((0, 0, 3), (1, 0, 3))
self.ae(s.current_url_text(), 'ab')
# URL detection
s = self.create_screen(cols=40)
s.reset()
url = 'http://moo.com'
multicell(s, url, scale=2)
s.detect_url(0, 0)
asl((0, 0, len(url)*2), (1, 0, len(url)*2))