A new option to control when hyperlinks are underlined

While kitty is never going to underline detected URLs as the performance
of that is absurd, underlining hyperlinks specifically is acceptable,
since they dont require detection.

See #6766
This commit is contained in:
Kovid Goyal 2023-11-03 08:51:58 +05:30
parent 954b7f87a5
commit b4f88b4f81
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
10 changed files with 65 additions and 7 deletions

View file

@ -58,6 +58,8 @@ Detailed list of changes
- A new mouse action ``mouse_selection word_and_line_from_point`` to select the current word under the mouse cursor and extend to end of line (:pull:`6663`)
- A new option :opt:`underline_hyperlinks` to control when hyperlinks are underlined (:iss:`6766`)
- Allow using the full range of standard mouse cursor shapes when customizing the mouse cursor
- macOS: When running the default shell with the login program fix :file:`~/.hushlogin` not being respected when opening windows not in the home directory (:iss:`6689`)

View file

@ -660,6 +660,10 @@ line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Curs
}
self->cpu_cells[at].ch = ch;
self->cpu_cells[at].hyperlink_id = hyperlink_id;
if (OPT(underline_hyperlinks) == UNDERLINE_ALWAYS && hyperlink_id) {
g->decoration_fg = ((OPT(url_color) & COL_MASK) << 8) | 2;
g->attrs.decoration = OPT(url_style);
}
memset(self->cpu_cells[at].cc_idx, 0, sizeof(self->cpu_cells[at].cc_idx));
}

View file

@ -486,7 +486,8 @@ opt('detect_urls', 'yes',
long_text='''
Detect URLs under the mouse. Detected URLs are highlighted with an underline and
the mouse cursor becomes a hand over them. Even if this option is disabled, URLs
are still clickable.
are still clickable. See also the :opt:`underline_hyperlinks` option to control
how hyperlinks (as opposed to plain text URLs) are displayed.
'''
)
@ -510,6 +511,16 @@ When the mouse hovers over a terminal hyperlink, show the actual URL that will
be activated when the hyperlink is clicked.
''')
opt('underline_hyperlinks', 'hover', choices=('hover', 'always', 'never'),
ctype='underline_hyperlinks', long_text='''
Control how hyperlinks are underlined. They can either be underlined on mouse
``hover``, ``always`` (i.e. permanently underlined) or ``never`` which means
that kitty will not apply any underline styling to hyperlinks.
Uses the :opt:`url_style` and :opt:`url_color` settings for the underline style.
''')
opt('copy_on_select', 'no',
option_type='copy_on_select',
long_text='''

View file

@ -1308,6 +1308,14 @@ class Parser:
choices_for_undercurl_style = frozenset(('thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense'))
def underline_hyperlinks(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
val = val.lower()
if val not in self.choices_for_underline_hyperlinks:
raise ValueError(f"The value {val} is not a valid choice for underline_hyperlinks")
ans["underline_hyperlinks"] = val
choices_for_underline_hyperlinks = frozenset(('hover', 'always', 'never'))
def update_check_interval(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['update_check_interval'] = float(val)

View file

@ -291,6 +291,19 @@ convert_from_opts_show_hyperlink_targets(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_underline_hyperlinks(PyObject *val, Options *opts) {
opts->underline_hyperlinks = underline_hyperlinks(val);
}
static void
convert_from_opts_underline_hyperlinks(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "underline_hyperlinks");
if (ret == NULL) return;
convert_from_python_underline_hyperlinks(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_select_by_word_characters(PyObject *val, Options *opts) {
select_by_word_characters(val, opts);
@ -1143,6 +1156,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_show_hyperlink_targets(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_underline_hyperlinks(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_select_by_word_characters(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_select_by_word_characters_forward(py_opts, opts);

View file

@ -57,6 +57,16 @@ window_title_in(PyObject *title_in) {
return ALL;
}
static UnderlineHyperlinks
underline_hyperlinks(PyObject *x) {
const char *in = PyUnicode_AsUTF8(x);
switch(in[0]) {
case 'a': return UNDERLINE_ALWAYS;
case 'n': return UNDERLINE_NEVER;
default : return UNDERLINE_ON_HOVER;
}
}
static BackgroundImageLayout
bglayout(PyObject *layout_name) {
const char *name = PyUnicode_AsUTF8(layout_name);

View file

@ -29,6 +29,7 @@ choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separ
choices_for_tab_powerline_style = typing.Literal['angled', 'round', 'slanted']
choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right']
choices_for_undercurl_style = typing.Literal['thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense']
choices_for_underline_hyperlinks = typing.Literal['hover', 'always', 'never']
choices_for_window_logo_position = typing.Literal['top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right']
option_names = ( # {{{
@ -432,6 +433,7 @@ option_names = ( # {{{
'text_fg_override_threshold',
'touch_scroll_multiplier',
'undercurl_style',
'underline_hyperlinks',
'update_check_interval',
'url_color',
'url_excluded_characters',
@ -588,6 +590,7 @@ class Options:
text_fg_override_threshold: float = 0.0
touch_scroll_multiplier: float = 1.0
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
underline_hyperlinks: choices_for_underline_hyperlinks = 'hover'
update_check_interval: float = 24.0
url_color: Color = Color(0, 135, 189)
url_excluded_characters: str = ''

View file

@ -2809,7 +2809,9 @@ screen_apply_selection(Screen *self, void *address, size_t size) {
}
self->selections.last_rendered_count = self->selections.count;
for (size_t i = 0; i < self->url_ranges.count; i++) {
apply_selection(self, address, self->url_ranges.items + i, 2);
Selection *s = self->url_ranges.items + i;
if (OPT(underline_hyperlinks) == UNDERLINE_NEVER && s->is_hyperlink) continue;
apply_selection(self, address, s, 2);
}
self->url_ranges.last_rendered_count = self->url_ranges.count;
}
@ -3945,12 +3947,13 @@ screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_ha
}
static void
add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) {
add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y, bool is_hyperlink) {
#define A(attr, val) r->attr = val;
ensure_space_for(&self->url_ranges, items, Selection, self->url_ranges.count + 8, capacity, 8, false);
Selection *r = self->url_ranges.items + self->url_ranges.count++;
memset(r, 0, sizeof(Selection));
r->last_rendered.y = INT_MAX;
r->is_hyperlink = is_hyperlink;
A(start.x, start_x); A(end.x, end_x); A(start.y, start_y); A(end.y, end_y);
A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by);
A(start.in_left_half_of_cell, true);
@ -3960,7 +3963,7 @@ add_url_range(Screen *self, index_type start_x, index_type start_y, index_type e
void
screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) {
self->url_ranges.count = 0;
if (start_x || start_y || end_x || end_y) add_url_range(self, start_x, start_y, end_x, end_y);
if (start_x || start_y || end_x || end_y) add_url_range(self, start_x, start_y, end_x, end_y, false);
}
static bool
@ -3972,7 +3975,7 @@ mark_hyperlinks_in_line(Screen *self, Line *line, hyperlink_id_type id, index_ty
bool has_hyperlink = line->cpu_cells[x].hyperlink_id == id;
if (in_range) {
if (!has_hyperlink) {
add_url_range(self, start, y, x - 1, y);
add_url_range(self, start, y, x - 1, y, true);
in_range = false;
start = 0;
}
@ -3983,7 +3986,7 @@ mark_hyperlinks_in_line(Screen *self, Line *line, hyperlink_id_type id, index_ty
}
}
}
if (in_range) add_url_range(self, start, y, self->columns - 1, y);
if (in_range) add_url_range(self, start, y, self->columns - 1, y, true);
return found;
}

View file

@ -39,7 +39,7 @@ typedef struct {
typedef struct {
SelectionBoundary start, end, input_start, input_current;
unsigned int start_scrolled_by, end_scrolled_by;
bool rectangle_select, adjusting_start;
bool rectangle_select, adjusting_start, is_hyperlink;
IterationData last_rendered;
int sort_y, sort_x;
struct {

View file

@ -22,6 +22,7 @@ typedef struct {
} UrlPrefix;
typedef enum AdjustmentUnit { POINT = 0, PERCENT = 1, PIXEL = 2 } AdjustmentUnit;
typedef enum UnderlineHyperlinks { UNDERLINE_ON_HOVER = 0, UNDERLINE_ALWAYS = 1, UNDERLINE_NEVER = 2 } UnderlineHyperlinks;
struct MenuItem {
const char* *location;
@ -95,6 +96,7 @@ typedef struct {
float val; AdjustmentUnit unit;
} underline_position, underline_thickness, strikethrough_position, strikethrough_thickness, cell_width, cell_height, baseline;
bool show_hyperlink_targets;
UnderlineHyperlinks underline_hyperlinks;
int background_blur;
long macos_titlebar_color;
unsigned long wayland_titlebar_color;