Get rid of the option to use the system wcwidth

The system wcwidth() is often wrong. Not to mention that if you SSH into
a different machine, then you have a potentially different wcwidth. The
only sane way to deal with this is to use the unicode standard.
This commit is contained in:
Kovid Goyal 2018-02-04 21:02:30 +05:30
parent 452ff02b15
commit fc7ec1d3f7
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
16 changed files with 48 additions and 82 deletions

View File

@ -407,25 +407,21 @@ brew or MacPorts as well.
=== Some special symbols are rendered small/truncated in kitty? === Some special symbols are rendered small/truncated in kitty?
The number of cells a unicode character takes up are controlled by the The number of cells a unicode character takes up are controlled by the unicode
`wcwidth()` system function. If wcwidth() returns 2 then kitty will render the standard. All characters are rendered in a single cell unless the unicode
character in two cells, otherwise it will render it in one cell. Often the standard says they should be rendered in two cells. When a symbol does not fit,
system `wcwidth()` is old/outdated. You can use the `use_system_wcwidth=no` it will either be rescaled to be smaller or truncated (depending on how much
setting in your kitty.conf to workaround this. But note that it might cause extra space it needs). This is often different from other terminals which just
other issues, since now kitty and the programs running inside it may not agree let the character overflow into neighboring cells, which is fine if the
on how wide characters should be. When a symbol does not fit, it will either be neighboring cell is empty, but looks terrible if it is not.
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, leading to ugly artifacts.
In addition to the problem with `wcwidth()` above, some programs, like Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
powerline, vim with fancy gutter symbols/status-bar, etc. use unicode use unicode characters from the private use area to represent symbols. Often
characters from the private use area to represent symbols. Often these symbols these symbols are square and should be rendered in two cells. However, since
are square and should be rendered in two cells. However, since private use private use area symbols all have their width set to one in the unicode
area symbols all have `wcwdith() == 1` kitty renders them either smaller or standard, kitty renders them either smaller or truncated. The correct solution
truncated. The correct solution for this is to use either use different symbols for this is to use either use different symbols that are not square, or to use
that are not square, or to use a font that defines ligatures with the space a font that defines ligatures with the space character for these symbols. See
character for these symbols. See
link:https://github.com/kovidgoyal/kitty/issues/182[#182] for a discussion of link:https://github.com/kovidgoyal/kitty/issues/182[#182] for a discussion of
the approach using ligatures. the approach using ligatures.

View File

@ -221,6 +221,7 @@ def gen_wcwidth():
p('\tswitch(code) {') p('\tswitch(code) {')
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs'] non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
add(p, 'Null', {0}, 0)
add(p, 'Non-printing characters', non_printing, -1) add(p, 'Non-printing characters', non_printing, -1)
add(p, 'Marks', marks, -1) add(p, 'Marks', marks, -1)
add(p, 'Private use', class_maps['Co'], -3) add(p, 'Private use', class_maps['Co'], -3)

View File

@ -273,7 +273,6 @@ type_map = {
'remember_window_size': to_bool, 'remember_window_size': to_bool,
'initial_window_width': positive_int, 'initial_window_width': positive_int,
'initial_window_height': positive_int, 'initial_window_height': positive_int,
'use_system_wcwidth': to_bool,
'macos_hide_titlebar': to_bool, 'macos_hide_titlebar': to_bool,
'macos_option_as_alt': to_bool, 'macos_option_as_alt': to_bool,
'box_drawing_scale': box_drawing_scale, 'box_drawing_scale': box_drawing_scale,

View File

@ -75,12 +75,6 @@ wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromUnsignedLong(safe_wcwidth(PyLong_AsLong(chr))); return PyLong_FromUnsignedLong(safe_wcwidth(PyLong_AsLong(chr)));
} }
static PyObject*
change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
change_wcwidth(PyObject_IsTrue(use9));
Py_RETURN_NONE;
}
static PyObject* static PyObject*
redirect_std_streams(PyObject UNUSED *self, PyObject *args) { redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
char *devnull = NULL; char *devnull = NULL;
@ -147,7 +141,6 @@ static PyMethodDef module_methods[] = {
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""}, {"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
#ifdef __APPLE__ #ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS), METHODB(user_cache_dir, METH_NOARGS),

View File

@ -256,7 +256,6 @@ color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_typ
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride); void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
unsigned int safe_wcwidth(uint32_t ch); unsigned int safe_wcwidth(uint32_t ch);
void change_wcwidth(bool use9);
void set_mouse_cursor(MouseShape); void set_mouse_cursor(MouseShape);
void mouse_event(int, int); void mouse_event(int, int);
void focus_in_event(); void focus_in_event();

2
kitty/emoji.h generated
View File

@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2018-01-18 // unicode data, built from the unicode standard on: 2018-02-04
// see gen-wcwidth.py // see gen-wcwidth.py
#pragma once #pragma once
#include "data-types.h" #include "data-types.h"

View File

@ -10,7 +10,7 @@ from math import ceil, floor, pi, sin, sqrt
from kitty.config import defaults from kitty.config import defaults
from kitty.constants import is_macos from kitty.constants import is_macos
from kitty.fast_data_types import ( from kitty.fast_data_types import (
Screen, change_wcwidth, get_fallback_font, send_prerendered_sprites, Screen, get_fallback_font, send_prerendered_sprites,
set_font, set_font_size, set_logical_dpi, set_options, set_font, set_font_size, set_logical_dpi, set_options,
set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line, set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line,
test_shape test_shape
@ -260,7 +260,6 @@ def test_fallback_font(qtext=None, bold=False, italic=False):
def showcase(): def showcase():
change_wcwidth(True)
f = 'monospace' if is_macos else 'Liberation Mono' f = 'monospace' if is_macos else 'Liberation Mono'
test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f) test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f)
test_render_string('你好,世界', family=f) test_render_string('你好,世界', family=f)

View File

@ -162,15 +162,6 @@ rectangle_select_modifiers ctrl+alt
# Note that this even works over ssh connections. # Note that this even works over ssh connections.
allow_remote_control no allow_remote_control no
# Choose whether to use the system implementation of wcwidth() (used to
# control how many cells a character is rendered in). If you use the system
# implementation, then kitty and any programs running in it will agree. The
# problem is that system implementations often are based on outdated unicode
# standards and get the width of many characters, such as emoji, wrong. So if
# you are using kitty with programs that have their own up-to-date wcwidth()
# implementation, set this option to no, otherwise set it to yes.
use_system_wcwidth no
# The value of the TERM environment variable to set # The value of the TERM environment variable to set
term xterm-kitty term xterm-kitty

View File

@ -14,7 +14,7 @@ from .cli import create_opts, parse_args
from .config import initial_window_size, load_cached_values, save_cached_values from .config import initial_window_size, load_cached_values, save_cached_values
from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file
from .fast_data_types import ( from .fast_data_types import (
change_wcwidth, create_os_window, glfw_init, glfw_terminate, create_os_window, glfw_init, glfw_terminate,
install_sigchld_handler, set_default_window_icon, set_options, show_window install_sigchld_handler, set_default_window_icon, set_options, show_window
) )
from .fonts.box_drawing import set_scale from .fonts.box_drawing import set_scale
@ -146,7 +146,6 @@ def main():
single_instance.socket.sendall(data) single_instance.socket.sendall(data)
return return
opts = create_opts(args) opts = create_opts(args)
change_wcwidth(not opts.use_system_wcwidth)
init_graphics() init_graphics()
try: try:
with setup_profiling(args): with setup_profiling(args):

View File

@ -271,20 +271,13 @@ screen_designate_charset(Screen *self, uint32_t which, uint32_t as) {
} }
} }
static int (*wcwidth_impl)(wchar_t) = wcwidth;
unsigned int unsigned int
safe_wcwidth(uint32_t ch) { safe_wcwidth(uint32_t ch) {
int ans = wcwidth_impl(ch); int ans = wcwidth_std(ch);
if (ans < 0) ans = 1; if (ans < 0) ans = 1;
return MIN(2, ans); return MIN(2, ans);
} }
void
change_wcwidth(bool use_std) {
wcwidth_impl = use_std ? wcwidth_std : wcwidth;
}
void void
screen_draw(Screen *self, uint32_t och) { screen_draw(Screen *self, uint32_t och) {
@ -1729,6 +1722,12 @@ COUNT_WRAP(cursor_down)
COUNT_WRAP(cursor_down1) COUNT_WRAP(cursor_down1)
COUNT_WRAP(cursor_forward) COUNT_WRAP(cursor_forward)
static PyObject*
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromUnsignedLong(wcwidth_std(PyLong_AsLong(chr)));
}
#define MND(name, args) {#name, (PyCFunction)name, args, #name}, #define MND(name, args) {#name, (PyCFunction)name, args, #name},
#define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O) #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O)
@ -1757,6 +1756,7 @@ static PyMethodDef methods[] = {
MND(cursor_down, METH_VARARGS) MND(cursor_down, METH_VARARGS)
MND(cursor_down1, METH_VARARGS) MND(cursor_down1, METH_VARARGS)
MND(cursor_forward, METH_VARARGS) MND(cursor_forward, METH_VARARGS)
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""}, {"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""},
{"index", (PyCFunction)xxx_index, METH_VARARGS, ""}, {"index", (PyCFunction)xxx_index, METH_VARARGS, ""},
MND(refresh_sprite_positions, METH_NOARGS) MND(refresh_sprite_positions, METH_NOARGS)

View File

@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2018-01-18 // unicode data, built from the unicode standard on: 2018-02-04
// see gen-wcwidth.py // see gen-wcwidth.py
#include "data-types.h" #include "data-types.h"

11
kitty/wcwidth-std.h generated
View File

@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2018-01-18 // unicode data, built from the unicode standard on: 2018-02-04
// see gen-wcwidth.py // see gen-wcwidth.py
#pragma once #pragma once
#include "data-types.h" #include "data-types.h"
@ -8,8 +8,13 @@ START_ALLOW_CASE_RANGE
static int static int
wcwidth_std(int32_t code) { wcwidth_std(int32_t code) {
switch(code) { switch(code) {
// Non-printing characters (2264 codepoints) {{{ // Null (1 codepoints) {{{
case 0x0 ... 0x1f: case 0x0:
return 0;
// }}}
// Non-printing characters (2263 codepoints) {{{
case 0x1 ... 0x1f:
return -1; return -1;
case 0x7f ... 0x9f: case 0x7f ... 0x9f:
return -1; return -1;

View File

@ -2,9 +2,6 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
from unittest import skipIf
from kitty.config import build_ansi_color_table, defaults from kitty.config import build_ansi_color_table, defaults
from kitty.fast_data_types import ( from kitty.fast_data_types import (
REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf
@ -334,7 +331,6 @@ class TestDataTypes(BaseTest):
lb2 = self.line_comparison_rewrap(lb, '123', ' a', 'bcd', 'e') lb2 = self.line_comparison_rewrap(lb, '123', ' a', 'bcd', 'e')
self.assertContinued(lb2, False, True, True, True) self.assertContinued(lb2, False, True, True, True)
@skipIf('ANCIENT_WCWIDTH' in os.environ, 'wcwidth() is too old')
def test_utils(self): def test_utils(self):
self.ae(tuple(map(wcwidth, 'a1\0コニチ ')), (1, 1, 0, 2, 2, 2, 1)) self.ae(tuple(map(wcwidth, 'a1\0コニチ ')), (1, 1, 0, 2, 2, 2, 1))
self.assertEqual(sanitize_title('a\0\01 \t\n\f\rb'), 'a b') self.assertEqual(sanitize_title('a\0\01 \t\n\f\rb'), 'a b')

View File

@ -6,7 +6,7 @@ from collections import OrderedDict
from kitty.constants import is_macos from kitty.constants import is_macos
from kitty.fast_data_types import ( from kitty.fast_data_types import (
change_wcwidth, set_logical_dpi, set_send_sprite_to_gpu, set_logical_dpi, set_send_sprite_to_gpu,
sprite_map_set_layout, sprite_map_set_limits, test_render_line, sprite_map_set_layout, sprite_map_set_limits, test_render_line,
test_sprite_position_for, wcwidth test_sprite_position_for, wcwidth
) )
@ -72,8 +72,6 @@ class Rendering(BaseTest):
self.ae(len(cells), sz) self.ae(len(cells), sz)
def test_shaping(self): def test_shaping(self):
change_wcwidth(True)
try:
def groups(text, path=None): def groups(text, path=None):
return [x[:2] for x in shape_string(text, path=path)] return [x[:2] for x in shape_string(text, path=path)]
@ -88,5 +86,3 @@ class Rendering(BaseTest):
self.ae(groups('|\U0001F601|\U0001F64f|\U0001F63a|'), [(1, 1), (2, 1), (1, 1), (2, 1), (1, 1), (2, 1), (1, 1)]) self.ae(groups('|\U0001F601|\U0001F64f|\U0001F63a|'), [(1, 1), (2, 1), (1, 1), (2, 1), (1, 1), (2, 1), (1, 1)])
self.ae(groups('He\u0347\u0305llo\u0337,', path='kitty_tests/LiberationMono-Regular.ttf'), self.ae(groups('He\u0347\u0305llo\u0337,', path='kitty_tests/LiberationMono-Regular.ttf'),
[(1, 1), (1, 3), (1, 1), (1, 1), (1, 2), (1, 1)]) [(1, 1), (1, 3), (1, 1), (1, 1), (1, 2), (1, 1)])
finally:
change_wcwidth(False)

View File

@ -2,10 +2,8 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
from binascii import hexlify from binascii import hexlify
from functools import partial from functools import partial
from unittest import skipIf
from kitty.fast_data_types import CURSOR_BLOCK, parse_bytes, parse_bytes_dump from kitty.fast_data_types import CURSOR_BLOCK, parse_bytes, parse_bytes_dump
@ -41,7 +39,6 @@ class TestParser(BaseTest):
q.append(('draw', current)) q.append(('draw', current))
self.ae(tuple(q), cmds) self.ae(tuple(q), cmds)
@skipIf('ANCIENT_WCWIDTH' in os.environ, 'wcwidth() is too old')
def test_simple_parsing(self): def test_simple_parsing(self):
s = self.create_screen() s = self.create_screen()
pb = partial(self.parse_bytes_dump, s) pb = partial(self.parse_bytes_dump, s)

View File

@ -2,9 +2,6 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
from unittest import skipIf
from . import BaseTest from . import BaseTest
from kitty.fast_data_types import DECAWM, IRM, Cursor, DECCOLM, DECOM from kitty.fast_data_types import DECAWM, IRM, Cursor, DECCOLM, DECOM
@ -50,7 +47,6 @@ class TestScreen(BaseTest):
self.ae(str(s.line(4)), 'ab123') self.ae(str(s.line(4)), 'ab123')
self.ae((s.cursor.x, s.cursor.y), (2, 4)) self.ae((s.cursor.x, s.cursor.y), (2, 4))
@skipIf('ANCIENT_WCWIDTH' in os.environ, 'wcwidth() is too old')
def test_draw_char(self): def test_draw_char(self):
# Test in line-wrap, non-insert mode # Test in line-wrap, non-insert mode
s = self.create_screen() s = self.create_screen()
@ -93,7 +89,6 @@ class TestScreen(BaseTest):
self.ae(str(s.line(4)), 'a\u0306b1\u030623') self.ae(str(s.line(4)), 'a\u0306b1\u030623')
self.ae((s.cursor.x, s.cursor.y), (2, 4)) self.ae((s.cursor.x, s.cursor.y), (2, 4))
@skipIf('ANCIENT_WCWIDTH' in os.environ, 'wcwidth() is too old')
def test_char_manipulation(self): def test_char_manipulation(self):
s = self.create_screen() s = self.create_screen()