Make the set of URL prefixes that are recognized while hovering with the mouse configurable

Fixes #2416
This commit is contained in:
Kovid Goyal 2020-03-08 13:16:34 +05:30
parent cc1336a616
commit 645890ccc8
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 72 additions and 18 deletions

View File

@ -40,6 +40,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- Fix a bug that prevented using custom functions with the new marks feature
(:iss:`2344`)
- Make the set of URL prefixes that are recognized while hovering with the
mouse configurable (:iss:`2416`)
- Fix border/margin/padding sizes not being recalculated on DPI change
(:iss:`2346`)

View File

@ -6,7 +6,7 @@
import os
from gettext import gettext as _
from typing import (
Any, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar, Union
Any, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar, Union, cast
)
from . import fast_data_types as defines
@ -502,6 +502,14 @@ def url_style(x: str) -> int:
operating system's default URL handler.'''))
def url_prefixes(x: str) -> Tuple[str, ...]:
return tuple(a.lower() for a in x.replace(',', ' ').split())
o('url_prefixes', 'http https file ftp', option_type=url_prefixes, long_text=_('''
The set of URL prefixes to look for when detecting a URL under the mouse cursor.'''))
def copy_on_select(raw: str) -> str:
q = raw.lower()
# boolean values special cased for backwards compat
@ -784,7 +792,7 @@ def tab_font_style(x: str) -> Tuple[bool, bool]:
def tab_bar_min_tabs(x: str) -> int:
return max(1, positive_int(x))
return cast(int, max(1, positive_int(x)))
o('tab_bar_min_tabs', 2, option_type=tab_bar_min_tabs, long_text=_('''
@ -1068,7 +1076,7 @@ def macos_titlebar_color(x: str) -> int:
return 0
if x == 'background':
return 1
return (color_as_int(to_color(x)) << 8) | 2
return cast(int, (color_as_int(to_color(x)) << 8) | 2)
o('macos_titlebar_color', 'system', option_type=macos_titlebar_color, long_text=_('''

View File

@ -5,7 +5,7 @@
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include "state.h"
#include "unicode-data.h"
#include "lineops.h"
#include "charsets.h"
@ -49,9 +49,6 @@ cell_text(CPUCell *cell) {
// URL detection {{{
static const char* url_prefixes[4] = {"https", "http", "file", "ftp"};
static size_t url_prefix_lengths[arraysz(url_prefixes)] = {0};
static inline index_type
find_colon_slash(Line *self, index_type x, index_type limit) {
// Find :// at or before x
@ -88,29 +85,25 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
}
static inline bool
prefix_matches(Line *self, index_type at, const char* prefix, index_type prefix_len) {
prefix_matches(Line *self, index_type at, const char_type* prefix, index_type prefix_len) {
if (prefix_len > at) return false;
index_type p, i;
for (p = at - prefix_len, i = 0; i < prefix_len && p < self->xnum; i++, p++) {
if ((self->cpu_cells[p].ch) != (unsigned char)prefix[i]) return false;
if ((self->cpu_cells[p].ch) != prefix[i]) return false;
}
return i == prefix_len;
}
static inline bool
has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_type *ans) {
if (UNLIKELY(!url_prefix_lengths[0])) {
for (index_type i = 0; i < arraysz(url_prefixes); i++) url_prefix_lengths[i] = strlen(url_prefixes[i]);
}
for (index_type i = 0; i < arraysz(url_prefixes); i++) {
index_type prefix_len = url_prefix_lengths[i];
for (size_t i = 0; i < OPT(url_prefixes.num); i++) {
index_type prefix_len = OPT(url_prefixes.values[i].len);
if (at < prefix_len || prefix_len < min_prefix_len) continue;
if (prefix_matches(self, at, url_prefixes[i], prefix_len)) { *ans = at - prefix_len; return true; }
if (prefix_matches(self, at, OPT(url_prefixes.values[i].string), prefix_len)) { *ans = at - prefix_len; return true; }
}
return false;
}
#define MAX_URL_SCHEME_LEN 5
#define MIN_URL_LEN 5
static inline bool
@ -129,7 +122,7 @@ line_url_start_at(Line *self, index_type x) {
if (x >= self->xnum || self->xnum <= MIN_URL_LEN + 3) return self->xnum;
index_type ds_pos = 0, t;
// First look for :// ahead of x
if (self->xnum - x > MAX_URL_SCHEME_LEN + 3) ds_pos = find_colon_slash(self, x + MAX_URL_SCHEME_LEN + 3, x < 2 ? 0 : x - 2);
if (self->xnum - x > OPT(url_prefixes).max_prefix_len + 3) ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2);
if (ds_pos != 0 && has_url_beyond(self, ds_pos)) {
if (has_url_prefix_at(self, ds_pos, ds_pos > x ? ds_pos - x: 0, &t)) return t;
}

View File

@ -513,6 +513,34 @@ pointer_shape(PyObject *shape_name) {
return BEAM;
}
static inline void
free_url_prefixes(void) {
OPT(url_prefixes).num = 0;
OPT(url_prefixes).max_prefix_len = 0;
if (OPT(url_prefixes).values) {
free(OPT(url_prefixes.values));
OPT(url_prefixes).values = NULL;
}
}
static void
set_url_prefixes(PyObject *up) {
free_url_prefixes();
OPT(url_prefixes).values = calloc(PyTuple_GET_SIZE(up), sizeof(UrlPrefix));
if (!OPT(url_prefixes).values) { PyErr_NoMemory(); return; }
OPT(url_prefixes).num = PyTuple_GET_SIZE(up);
for (size_t i = 0; i < OPT(url_prefixes).num; i++) {
PyObject *t = PyTuple_GET_ITEM(up, i);
if (!PyUnicode_Check(t)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be strings"); return; }
OPT(url_prefixes).values[i].len = MIN(arraysz(OPT(url_prefixes).values[i].string) - 1, (size_t)PyUnicode_GET_LENGTH(t));
int kind = PyUnicode_KIND(t);
OPT(url_prefixes).max_prefix_len = MAX(OPT(url_prefixes).max_prefix_len, OPT(url_prefixes).values[i].len);
for (size_t x = 0; x < OPT(url_prefixes).values[i].len; x++) {
OPT(url_prefixes).values[i].string[x] = PyUnicode_READ(kind, PyUnicode_DATA(t), x);
}
}
}
#define dict_iter(d) { \
PyObject *key, *value; Py_ssize_t pos = 0; \
while (PyDict_Next(d, &pos, &key, &value))
@ -628,6 +656,13 @@ PYWRAP1(set_options) {
Py_CLEAR(ret);
if (PyErr_Occurred()) return NULL;
PyObject *up = PyObject_GetAttrString(opts, "url_prefixes");
if (up == NULL) return NULL;
if (!PyTuple_Check(up)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be a tuple"); return NULL; }
set_url_prefixes(up);
Py_DECREF(up);
if (PyErr_Occurred()) return NULL;
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
if (chars == NULL) return NULL;
for (size_t i = 0; i < MIN((size_t)PyUnicode_GET_LENGTH(chars), sizeof(OPT(select_by_word_characters))/sizeof(OPT(select_by_word_characters[0]))); i++) {
@ -1081,6 +1116,7 @@ finalize(void) {
// destroyed.
free_bgimage(&global_state.bgimage, false);
global_state.bgimage = NULL;
free_url_prefixes();
}
bool

View File

@ -15,6 +15,11 @@ typedef enum { LEFT_EDGE, TOP_EDGE, RIGHT_EDGE, BOTTOM_EDGE } Edge;
typedef enum { RESIZE_DRAW_STATIC, RESIZE_DRAW_SCALED, RESIZE_DRAW_BLANK, RESIZE_DRAW_SIZE } ResizeDrawStrategy;
typedef enum { REPEAT_MIRROR, REPEAT_CLAMP, REPEAT_DEFAULT } RepeatStrategy;
typedef struct {
char_type string[16];
size_t len;
} UrlPrefix;
typedef struct {
monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval;
double wheel_scroll_multiplier, touch_scroll_multiplier;
@ -61,6 +66,10 @@ typedef struct {
bool debug_keyboard;
monotonic_t resize_debounce_time;
MouseShape pointer_shape_when_grabbed;
struct {
UrlPrefix *values;
size_t num, max_prefix_len;
} url_prefixes;
} Options;
typedef struct {

View File

@ -74,12 +74,15 @@ class BaseTest(TestCase):
ae = TestCase.assertEqual
maxDiff = 2000
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None):
def set_options(self, options=None):
final_options = {'scrollback_pager_history_size': 1024, 'click_interval': 0.5}
if options:
final_options.update(options)
options = Options(merge_configs(defaults._asdict(), final_options))
set_options(options)
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None):
self.set_options(options)
c = Callbacks()
return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)

View File

@ -232,6 +232,8 @@ def test_line(self):
self.assertEqualAttributes(l3.cursor_from(0), q)
def test_url_at(self):
self.set_options()
def create(t):
lb = create.lb = LineBuf(1, len(t))
lf = lb.line(0)