mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-09-21 19:47:21 +03:00
Make the set of URL prefixes that are recognized while hovering with the mouse configurable
Fixes #2416
This commit is contained in:
parent
cc1336a616
commit
645890ccc8
@ -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`)
|
||||
|
||||
|
@ -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=_('''
|
||||
|
21
kitty/line.c
21
kitty/line.c
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user