A new option narrow_symbols to turn off opportunistic wide rendering of private use codepoints
This commit is contained in:
parent
b2317e0f12
commit
01b4654461
@ -88,6 +88,8 @@ Detailed list of changes
|
||||
|
||||
- Improve CWD detection when there are multiple foreground processes in the TTY process group
|
||||
|
||||
- A new option :opt:`narrow_symbols` to turn off opportunistic wide rendering of private use codepoints
|
||||
|
||||
- ssh kitten: Fix location of generated terminfo files on NetBSD (:iss:`4622`)
|
||||
|
||||
- A new action to clear the screen up to the line containing the cursor, see
|
||||
|
||||
15
docs/faq.rst
15
docs/faq.rst
@ -6,21 +6,22 @@ Frequently Asked Questions
|
||||
Some special symbols are rendered small/truncated in kitty?
|
||||
-----------------------------------------------------------
|
||||
|
||||
The number of cells a unicode character takes up are controlled by the unicode
|
||||
standard. All characters are rendered in a single cell unless the unicode
|
||||
The number of cells a Unicode character takes up are controlled by the Unicode
|
||||
standard. All characters are rendered in a single cell unless the Unicode
|
||||
standard says they should be rendered in two cells. When a symbol does not fit,
|
||||
it will either be rescaled to be smaller or truncated (depending on how much
|
||||
extra space it needs). This is often different from other terminals which just
|
||||
let the character overflow into neighboring cells, which is fine if the
|
||||
neighboring cell is empty, but looks terrible if it is not.
|
||||
|
||||
Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||
misuse unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are square and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the unicode
|
||||
Some programs, like Powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||
use Unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are wide and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the Unicode
|
||||
standard, |kitty| renders them either smaller or truncated. The exception is if
|
||||
these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells.
|
||||
makes use of the extra cell to render them in two cells. This behavior can be
|
||||
turned off for specific symbols using :opt:`narrow_symbols`.
|
||||
|
||||
|
||||
Using a color theme with a background color does not work well in vim?
|
||||
|
||||
@ -967,7 +967,8 @@ def set_font_data(
|
||||
descriptor_for_idx: Callable[[int], Tuple[FontObject, bool, bool]],
|
||||
bold: int, italic: int, bold_italic: int, num_symbol_fonts: int,
|
||||
symbol_maps: Tuple[Tuple[int, int, int], ...], font_sz_in_pts: float,
|
||||
font_feature_settings: Dict[str, Tuple[FontFeature, ...]]
|
||||
font_feature_settings: Dict[str, Tuple[FontFeature, ...]],
|
||||
narrow_symbols: Tuple[Tuple[int, int, int], ...],
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@ -50,8 +50,8 @@ typedef struct {
|
||||
size_t font_idx;
|
||||
} SymbolMap;
|
||||
|
||||
static SymbolMap *symbol_maps = NULL;
|
||||
static size_t num_symbol_maps = 0;
|
||||
static SymbolMap *symbol_maps = NULL, *narrow_symbols = NULL;
|
||||
static size_t num_symbol_maps = 0, num_narrow_symbols = 0;
|
||||
|
||||
typedef enum { SPACER_STRATEGY_UNKNOWN, SPACERS_BEFORE, SPACERS_AFTER, SPACERS_IOSEVKA } SpacerStrategy;
|
||||
|
||||
@ -1219,6 +1219,17 @@ is_non_emoji_dingbat(char_type ch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
cell_cap_for_codepoint(const char_type cp) {
|
||||
unsigned int ans = UINT_MAX;
|
||||
for (size_t i = 0; i < num_narrow_symbols; i++) {
|
||||
SymbolMap *sm = narrow_symbols + i;
|
||||
if (sm->left <= cp && cp <= sm->right) ans = sm->font_idx;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \
|
||||
@ -1251,6 +1262,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
|
||||
int width = get_glyph_width(font->face, glyph_id);
|
||||
desired_cells = (unsigned int)ceilf((float)width / fg->cell_width);
|
||||
}
|
||||
desired_cells = MIN(desired_cells, cell_cap_for_codepoint(cpu_cell->ch));
|
||||
|
||||
unsigned int num_spaces = 0;
|
||||
while (
|
||||
@ -1303,6 +1315,7 @@ render_simple_text(FONTS_DATA_HANDLE fg_, const char *text) {
|
||||
static void
|
||||
clear_symbol_maps(void) {
|
||||
if (symbol_maps) { free(symbol_maps); symbol_maps = NULL; num_symbol_maps = 0; }
|
||||
if (narrow_symbols) { free(narrow_symbols); narrow_symbols = NULL; num_narrow_symbols = 0; }
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@ -1311,26 +1324,33 @@ typedef struct {
|
||||
|
||||
DescriptorIndices descriptor_indices = {0};
|
||||
|
||||
static PyObject*
|
||||
set_font_data(PyObject UNUSED *m, PyObject *args) {
|
||||
PyObject *sm;
|
||||
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx); Py_CLEAR(font_feature_settings);
|
||||
if (!PyArg_ParseTuple(args, "OOOIIIIO!dO",
|
||||
&box_drawing_function, &prerender_function, &descriptor_for_idx,
|
||||
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
|
||||
&PyTuple_Type, &sm, &OPT(font_size), &font_feature_settings)) return NULL;
|
||||
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx); Py_INCREF(font_feature_settings);
|
||||
free_font_groups();
|
||||
clear_symbol_maps();
|
||||
num_symbol_maps = PyTuple_GET_SIZE(sm);
|
||||
symbol_maps = calloc(num_symbol_maps, sizeof(SymbolMap));
|
||||
if (symbol_maps == NULL) return PyErr_NoMemory();
|
||||
for (size_t s = 0; s < num_symbol_maps; s++) {
|
||||
static bool
|
||||
set_symbol_maps(SymbolMap **symbol_maps, size_t *num_symbol_maps, const PyObject *sm) {
|
||||
*num_symbol_maps = PyTuple_GET_SIZE(sm);
|
||||
*symbol_maps = calloc(*num_symbol_maps, sizeof(SymbolMap));
|
||||
if (*symbol_maps == NULL) { PyErr_NoMemory(); return false; }
|
||||
for (size_t s = 0; s < *num_symbol_maps; s++) {
|
||||
unsigned int left, right, font_idx;
|
||||
SymbolMap *x = symbol_maps + s;
|
||||
SymbolMap *x = *symbol_maps + s;
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, s), "III", &left, &right, &font_idx)) return NULL;
|
||||
x->left = left; x->right = right; x->font_idx = font_idx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_font_data(PyObject UNUSED *m, PyObject *args) {
|
||||
PyObject *sm, *ns;
|
||||
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx); Py_CLEAR(font_feature_settings);
|
||||
if (!PyArg_ParseTuple(args, "OOOIIIIO!dOO!",
|
||||
&box_drawing_function, &prerender_function, &descriptor_for_idx,
|
||||
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
|
||||
&PyTuple_Type, &sm, &OPT(font_size), &font_feature_settings, &PyTuple_Type, &ns)) return NULL;
|
||||
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx); Py_INCREF(font_feature_settings);
|
||||
free_font_groups();
|
||||
clear_symbol_maps();
|
||||
set_symbol_maps(&symbol_maps, &num_symbol_maps, sm);
|
||||
set_symbol_maps(&narrow_symbols, &num_narrow_symbols, ns);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ from kitty.fonts.box_drawing import (
|
||||
)
|
||||
from kitty.options.types import Options, defaults
|
||||
from kitty.typing import CoreTextFont, FontConfigPattern
|
||||
from kitty.types import _T
|
||||
from kitty.utils import log_error
|
||||
|
||||
if is_macos:
|
||||
@ -50,10 +51,9 @@ def font_for_family(family: str) -> Tuple[FontObject, bool, bool]:
|
||||
return font_for_family_fontconfig(family)
|
||||
|
||||
|
||||
Range = Tuple[Tuple[int, int], str]
|
||||
|
||||
|
||||
def merge_ranges(a: Range, b: Range, priority_map: Dict[Tuple[int, int], int]) -> Generator[Range, None, None]:
|
||||
def merge_ranges(
|
||||
a: Tuple[Tuple[int, int], _T], b: Tuple[Tuple[int, int], _T], priority_map: Dict[Tuple[int, int], int]
|
||||
) -> Generator[Tuple[Tuple[int, int], _T], None, None]:
|
||||
a_start, a_end = a[0]
|
||||
b_start, b_end = b[0]
|
||||
a_val, b_val = a[1], b[1]
|
||||
@ -95,7 +95,7 @@ def merge_ranges(a: Range, b: Range, priority_map: Dict[Tuple[int, int], int]) -
|
||||
after_range = ((b_end + 1, a_end), a_val)
|
||||
after_range_prio = a_prio
|
||||
# check if the before, mid and after ranges can be coalesced
|
||||
ranges: List[Range] = []
|
||||
ranges: List[Tuple[Tuple[int, int], _T]] = []
|
||||
priorities: List[int] = []
|
||||
for rq, prio in ((before_range, before_range_prio), (mid_range, mid_range_prio), (after_range, after_range_prio)):
|
||||
if rq is None:
|
||||
@ -117,7 +117,7 @@ def merge_ranges(a: Range, b: Range, priority_map: Dict[Tuple[int, int], int]) -
|
||||
yield from ranges
|
||||
|
||||
|
||||
def coalesce_symbol_maps(maps: Dict[Tuple[int, int], str]) -> Dict[Tuple[int, int], str]:
|
||||
def coalesce_symbol_maps(maps: Dict[Tuple[int, int], _T]) -> Dict[Tuple[int, int], _T]:
|
||||
if not maps:
|
||||
return maps
|
||||
priority_map = {r: i for i, r in enumerate(maps.keys())}
|
||||
@ -155,6 +155,10 @@ def create_symbol_map(opts: Options) -> Tuple[Tuple[int, int, int], ...]:
|
||||
return sm
|
||||
|
||||
|
||||
def create_narrow_symbols(opts: Options) -> Tuple[Tuple[int, int, int], ...]:
|
||||
return tuple((a, b, v) for (a, b), v in coalesce_symbol_maps(opts.narrow_symbols).items())
|
||||
|
||||
|
||||
def descriptor_for_idx(idx: int) -> Tuple[FontObject, bool, bool]:
|
||||
return current_faces[idx]
|
||||
|
||||
@ -193,6 +197,7 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional
|
||||
current_faces.append((font_map[k], 'b' in k, 'i' in k))
|
||||
before = len(current_faces)
|
||||
sm = create_symbol_map(opts)
|
||||
ns = create_narrow_symbols(opts)
|
||||
num_symbol_fonts = len(current_faces) - before
|
||||
font_features = {}
|
||||
for face, _, _ in current_faces:
|
||||
@ -203,7 +208,7 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional
|
||||
set_font_data(
|
||||
render_box_drawing, prerender_function, descriptor_for_idx,
|
||||
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
|
||||
sm, sz, font_features
|
||||
sm, sz, font_features, ns
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -124,6 +124,22 @@ Syntax is::
|
||||
'''
|
||||
)
|
||||
|
||||
opt('+narrow_symbols', 'U+E0A0-U+E0A3,U+E0C0-U+E0C7 1',
|
||||
option_type='narrow_symbols',
|
||||
add_to_default=False,
|
||||
long_text='''
|
||||
Usually, for Private Use Unicode characters and some symbol/dingbat characters,
|
||||
if the character is followed by one or more spaces, kitty will use those extra cells
|
||||
to render the character larger, if the character in the font has a wide aspect ratio.
|
||||
Using this setting you can force kitty to restrict the specified code points to render in
|
||||
the specified number of cells (defaulting to one cell).
|
||||
Syntax is::
|
||||
|
||||
narrow_symbols codepoints Optionally the number of cells
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
opt('disable_ligatures', 'never',
|
||||
option_type='disable_ligatures', ctype='int',
|
||||
long_text='''
|
||||
|
||||
17
kitty/options/parse.py
generated
17
kitty/options/parse.py
generated
@ -11,12 +11,12 @@ from kitty.options.utils import (
|
||||
clipboard_control, config_or_absolute_path, copy_on_select, cursor_text_color,
|
||||
deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias,
|
||||
deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations,
|
||||
macos_option_as_alt, macos_titlebar_color, optional_edge_width, parse_map, parse_mouse_map,
|
||||
resize_draw_strategy, scrollback_lines, scrollback_pager_history_size, 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_font_size, to_layout_names, to_modifiers, url_prefixes, url_style,
|
||||
visual_window_select_characters, window_border_width, window_size
|
||||
macos_option_as_alt, macos_titlebar_color, narrow_symbols, optional_edge_width, parse_map,
|
||||
parse_mouse_map, resize_draw_strategy, scrollback_lines, scrollback_pager_history_size,
|
||||
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_font_size, to_layout_names, to_modifiers,
|
||||
url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size
|
||||
)
|
||||
|
||||
|
||||
@ -1079,6 +1079,10 @@ class Parser:
|
||||
def mouse_hide_wait(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['mouse_hide_wait'] = float(val)
|
||||
|
||||
def narrow_symbols(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
for k, v in narrow_symbols(val):
|
||||
ans["narrow_symbols"][k] = v
|
||||
|
||||
def open_url_with(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['open_url_with'] = to_cmdline(val)
|
||||
|
||||
@ -1331,6 +1335,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]:
|
||||
'exe_search_path': {},
|
||||
'font_features': {},
|
||||
'kitten_alias': {},
|
||||
'narrow_symbols': {},
|
||||
'symbol_map': {},
|
||||
'watcher': {},
|
||||
'map': [],
|
||||
|
||||
3
kitty/options/types.py
generated
3
kitty/options/types.py
generated
@ -387,6 +387,7 @@ option_names = ( # {{{
|
||||
'mark3_foreground',
|
||||
'mouse_hide_wait',
|
||||
'mouse_map',
|
||||
'narrow_symbols',
|
||||
'open_url_with',
|
||||
'placement_strategy',
|
||||
'pointer_shape_when_dragging',
|
||||
@ -594,6 +595,7 @@ class Options:
|
||||
exe_search_path: typing.Dict[str, str] = {}
|
||||
font_features: typing.Dict[str, typing.Tuple[kitty.fonts.FontFeature, ...]] = {}
|
||||
kitten_alias: typing.Dict[str, str] = {}
|
||||
narrow_symbols: typing.Dict[typing.Tuple[int, int], int] = {}
|
||||
symbol_map: typing.Dict[typing.Tuple[int, int], str] = {}
|
||||
watcher: typing.Dict[str, str] = {}
|
||||
map: typing.List[kitty.options.utils.KeyDefinition] = []
|
||||
@ -712,6 +714,7 @@ defaults.env = {}
|
||||
defaults.exe_search_path = {}
|
||||
defaults.font_features = {}
|
||||
defaults.kitten_alias = {}
|
||||
defaults.narrow_symbols = {}
|
||||
defaults.symbol_map = {}
|
||||
defaults.watcher = {}
|
||||
defaults.map = [
|
||||
|
||||
@ -818,19 +818,19 @@ def action_alias(val: str) -> Iterable[Tuple[str, str]]:
|
||||
kitten_alias = action_alias
|
||||
|
||||
|
||||
def symbol_map(val: str) -> Iterable[Tuple[Tuple[int, int], str]]:
|
||||
def symbol_map(val: str, min_size: int = 2) -> Iterable[Tuple[Tuple[int, int], str]]:
|
||||
parts = val.split()
|
||||
|
||||
def abort() -> None:
|
||||
log_error(f'Symbol map: {val} is invalid, ignoring')
|
||||
|
||||
if len(parts) < 2:
|
||||
if len(parts) < min_size:
|
||||
return abort()
|
||||
family = ' '.join(parts[1:])
|
||||
|
||||
def to_chr(x: str) -> int:
|
||||
if not x.startswith('U+'):
|
||||
raise ValueError()
|
||||
raise ValueError(f'{x} is not a unicode codepoint of the form U+number')
|
||||
return int(x[2:], 16)
|
||||
|
||||
for x in parts[0].split(','):
|
||||
@ -845,6 +845,11 @@ def symbol_map(val: str) -> Iterable[Tuple[Tuple[int, int], str]]:
|
||||
yield (a, b), family
|
||||
|
||||
|
||||
def narrow_symbols(val: str) -> Iterable[Tuple[Tuple[int, int], int]]:
|
||||
for x, y in symbol_map(val, min_size=1):
|
||||
yield x, int(y or 1)
|
||||
|
||||
|
||||
def parse_key_action(action: str, action_type: str = 'map') -> KeyAction:
|
||||
parts = action.strip().split(maxsplit=1)
|
||||
func = parts[0]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user