Option to control underline exclusion

This commit is contained in:
Kovid Goyal
2024-12-16 08:56:37 +05:30
parent 1707e603f3
commit 81a5f29979
8 changed files with 68 additions and 5 deletions

View File

@@ -218,6 +218,14 @@ the curl will peak once per character, with dense twice.
'''
)
opt('underline_exclusion', '1', option_type='underline_exclusion', ctype='!underline_exclusion', long_text='''
By default kitty renders gaps in underlines when they overlap with descenders
(the parts of letters below the baseline, such as for y, q, p etc.). This option
controls the thickness of the gaps. It can be either a unitless number in which
case it is a fraction of the underline thickness as specified in the font or
it can have a suffix of :code:`px` for pixels or :code:`pt` for points.
''')
opt('text_composition_strategy', 'platform',
ctype='!text_composition_strategy',
long_text='''

View File

@@ -19,9 +19,9 @@ from kitty.options.utils import (
shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge,
tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size,
to_layout_names, to_modifiers, transparent_background_colors, url_prefixes, url_style,
visual_bell_duration, visual_window_select_characters, window_border_width, window_logo_scale,
window_size
to_layout_names, to_modifiers, transparent_background_colors, underline_exclusion, url_prefixes,
url_style, visual_bell_duration, visual_window_select_characters, window_border_width,
window_logo_scale, window_size
)
@@ -1344,6 +1344,9 @@ class Parser:
choices_for_undercurl_style = frozenset(('thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense'))
def underline_exclusion(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['underline_exclusion'] = underline_exclusion(val)
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:

View File

@@ -83,6 +83,19 @@ convert_from_opts_undercurl_style(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_underline_exclusion(PyObject *val, Options *opts) {
underline_exclusion(val, opts);
}
static void
convert_from_opts_underline_exclusion(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "underline_exclusion");
if (ret == NULL) return;
convert_from_python_underline_exclusion(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_text_composition_strategy(PyObject *val, Options *opts) {
text_composition_strategy(val, opts);
@@ -1163,6 +1176,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_undercurl_style(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_underline_exclusion(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_text_composition_strategy(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_cursor_shape(py_opts, opts);

View File

@@ -380,6 +380,16 @@ menu_map(PyObject *entry_dict, Options *opts) {
}
}
static inline void
underline_exclusion(PyObject *val, Options *opts) {
if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "underline_exclusion must be a tuple"); return; }
opts->underline_exclusion.thickness = PyFloat_AsFloat(PyTuple_GET_ITEM(val, 0));
if (PyUnicode_CompareWithASCIIString(PyTuple_GET_ITEM(val, 1), "%")) opts->underline_exclusion.unit = 0;
else if (PyUnicode_CompareWithASCIIString(PyTuple_GET_ITEM(val, 1), "px")) opts->underline_exclusion.unit = 1;
else if (PyUnicode_CompareWithASCIIString(PyTuple_GET_ITEM(val, 1), "pt")) opts->underline_exclusion.unit = 2;
else opts->underline_exclusion.unit = 0;
}
static inline void
text_composition_strategy(PyObject *val, Options *opts) {
if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_rendering_strategy must be a string"); return; }

View File

@@ -447,6 +447,7 @@ option_names = (
'touch_scroll_multiplier',
'transparent_background_colors',
'undercurl_style',
'underline_exclusion',
'underline_hyperlinks',
'update_check_interval',
'url_color',
@@ -615,6 +616,7 @@ class Options:
touch_scroll_multiplier: float = 1.0
transparent_background_colors: typing.Tuple[typing.Tuple[kitty.fast_data_types.Color, float], ...] = ()
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
underline_exclusion: tuple[float, typing.Literal['%', 'px', 'pt']] = (1.0, '%')
underline_hyperlinks: choices_for_underline_hyperlinks = 'hover'
update_check_interval: float = 24.0
url_color: Color = Color(0, 135, 189)

View File

@@ -1017,6 +1017,20 @@ def shell_integration(x: str) -> FrozenSet[str]:
return q
def underline_exclusion(x: str) -> tuple[float, Literal['%', 'px', 'pt']]:
try:
return float(x), '%'
except Exception:
unit: Literal['%', 'pt', 'px'] = x[-2:] # type: ignore
if unit not in ('px', 'pt', '%'):
raise ValueError(f'Invalid underline_exclusion with unrecognized unit: {x}')
try:
val = float(x[:-2])
except Exception:
raise ValueError(f'Invalid underline_exclusion with non numberic value: {x}')
return val, unit
def paste_actions(x: str) -> FrozenSet[str]:
s = frozenset({'quote-urls-at-prompt', 'confirm', 'filter', 'confirm-if-large', 'replace-dangerous-control-codes', 'replace-newline', 'no-op'})
q = frozenset(x.lower().split(','))