Skip to content

Commit 08aac3c

Browse files
committed
patch 8.2.1535: it is not possible to specify cell widths of characters
Problem: It is not possible to specify cell widths of characters. Solution: Add setcellwidths().
1 parent ee8580e commit 08aac3c

File tree

9 files changed

+265
-4
lines changed

9 files changed

+265
-4
lines changed

runtime/doc/eval.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2768,6 +2768,7 @@ setbufline({expr}, {lnum}, {text})
27682768
{expr}
27692769
setbufvar({expr}, {varname}, {val})
27702770
none set {varname} in buffer {expr} to {val}
2771+
setcellwidths({list}) none set character cell width overrides
27712772
setcharsearch({dict}) Dict set character search from {dict}
27722773
setcmdpos({pos}) Number set cursor position in command-line
27732774
setenv({name}, {val}) none set environment variable
@@ -8937,6 +8938,29 @@ setbufvar({expr}, {varname}, {val}) *setbufvar()*
89378938
third argument: >
89388939
GetValue()->setbufvar(buf, varname)
89398940

8941+
8942+
setcellwidths({list}) *setcellwidths()*
8943+
Specify overrides for cell widths of character ranges. This
8944+
tells Vim how wide characters are, counted in screen cells.
8945+
This overrides 'ambiwidth'. Example: >
8946+
setcellwidths([[0xad, 0xad, 1],
8947+
\ [0x2194, 0x2199, 2]]
8948+
8949+
< *E1109* *E1110* *E1111* *E1112* *E1113*
8950+
The {list} argument is a list of lists with each three
8951+
numbers. These three numbers are [low, high, width]. "low"
8952+
and "high" can be the same, in which case this refers to one
8953+
character. Otherwise it is the range of characters from "low"
8954+
to "high" (inclusive). "width" is either 1 or 2, indicating
8955+
the character width in screen cells.
8956+
An error is given if the argument is invalid, also when a
8957+
range overlaps with another.
8958+
Only characters with value 0x100 and higher can be used.
8959+
8960+
To clear the overrides pass an empty list: >
8961+
setcellwidths([]);
8962+
8963+
89408964
setcharsearch({dict}) *setcharsearch()*
89418965
Set the current character search information to {dict},
89428966
which contains one or more of the following entries:

runtime/doc/options.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,9 @@ A jump table for the options with a short description can be found at |Q_op|.
701701
"double": Use twice the width of ASCII characters.
702702
*E834* *E835*
703703
The value "double" cannot be used if 'listchars' or 'fillchars'
704-
contains a character that would be double width.
704+
705+
The values are overruled for characters specified with
706+
|setcellwidths()|.
705707

706708
There are a number of CJK fonts for which the width of glyphs for
707709
those characters are solely based on how many octets they take in

runtime/doc/usr_41.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ String manipulation: *string-functions*
611611
strchars() length of a string in characters
612612
strwidth() size of string when displayed
613613
strdisplaywidth() size of string when displayed, deals with tabs
614+
setcellwidths() set character cell width overrides
614615
substitute() substitute a pattern match with a string
615616
submatch() get a specific match in ":s" and substitute()
616617
strpart() get part of a string using byte index

src/errors.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,16 @@ EXTERN char e_string_list_dict_or_blob_required[]
238238
INIT(= N_("E1107: String, List, Dict or Blob required"));
239239
EXTERN char e_item_not_found_str[]
240240
INIT(= N_("E1108: Item not found: %s"));
241+
EXTERN char e_list_item_nr_is_not_list[]
242+
INIT(= N_("E1109: List item %d is not a List"));
243+
EXTERN char e_list_item_nr_does_not_contain_3_numbers[]
244+
INIT(= N_("E1110: List item %d does not contain 3 numbers"));
245+
EXTERN char e_list_item_nr_range_invalid[]
246+
INIT(= N_("E1111: List item %d range invalid"));
247+
EXTERN char e_list_item_nr_cell_width_invalid[]
248+
INIT(= N_("E1112: List item %d cell width invalid"));
249+
EXTERN char e_overlapping_ranges_for_nr[]
250+
INIT(= N_("E1113: Overlapping ranges for %lx"));
251+
EXTERN char e_only_values_of_0x100_and_higher_supported[]
252+
INIT(= N_("E1114: Only values of 0x100 and higher supported"));
241253
#endif

src/evalfunc.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@ static funcentry_T global_functions[] =
886886
{"serverlist", 0, 0, 0, ret_string, f_serverlist},
887887
{"setbufline", 3, 3, FEARG_3, ret_number, f_setbufline},
888888
{"setbufvar", 3, 3, FEARG_3, ret_void, f_setbufvar},
889+
{"setcellwidths", 1, 1, FEARG_1, ret_void, f_setcellwidths},
889890
{"setcharsearch", 1, 1, FEARG_1, ret_void, f_setcharsearch},
890891
{"setcmdpos", 1, 1, FEARG_1, ret_number, f_setcmdpos},
891892
{"setenv", 2, 2, FEARG_2, ret_void, f_setenv},

src/mbyte.c

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ static int dbcs_char2cells(int c);
132132
static int dbcs_ptr2cells_len(char_u *p, int size);
133133
static int dbcs_ptr2char(char_u *p);
134134
static int dbcs_head_off(char_u *base, char_u *p);
135+
static int cw_value(int c);
135136

136137
/*
137138
* Lookup table to quickly get the length in bytes of a UTF-8 character from
@@ -1487,7 +1488,7 @@ utf_char2cells(int c)
14871488
// Sorted list of non-overlapping intervals of Emoji characters that don't
14881489
// have ambiguous or double width,
14891490
// based on http://unicode.org/emoji/charts/emoji-list.html
1490-
static struct interval emoji_width[] =
1491+
static struct interval emoji_wide[] =
14911492
{
14921493
{0x1f1e6, 0x1f1ff},
14931494
{0x1f321, 0x1f321},
@@ -1532,12 +1533,18 @@ utf_char2cells(int c)
15321533

15331534
if (c >= 0x100)
15341535
{
1536+
int n;
1537+
1538+
n = cw_value(c);
1539+
if (n != 0)
1540+
return n;
1541+
15351542
#ifdef USE_WCHAR_FUNCTIONS
15361543
/*
15371544
* Assume the library function wcwidth() works better than our own
15381545
* stuff. It should return 1 for ambiguous width chars!
15391546
*/
1540-
int n = wcwidth(c);
1547+
n = wcwidth(c);
15411548

15421549
if (n < 0)
15431550
return 6; // unprintable, displays <xxxx>
@@ -1549,7 +1556,7 @@ utf_char2cells(int c)
15491556
if (intable(doublewidth, sizeof(doublewidth), c))
15501557
return 2;
15511558
#endif
1552-
if (p_emoji && intable(emoji_width, sizeof(emoji_width), c))
1559+
if (p_emoji && intable(emoji_wide, sizeof(emoji_wide), c))
15531560
return 2;
15541561
}
15551562

@@ -2570,6 +2577,8 @@ utf_printable(int c)
25702577

25712578
// Sorted list of non-overlapping intervals of all Emoji characters,
25722579
// based on http://unicode.org/emoji/charts/emoji-list.html
2580+
// Generated by ../runtime/tools/unicode.vim.
2581+
// Excludes 0x00a9 and 0x00ae because they are considered latin1.
25732582
static struct interval emoji_all[] =
25742583
{
25752584
{0x203c, 0x203c},
@@ -5342,3 +5351,177 @@ string_convert_ext(
53425351

53435352
return retval;
53445353
}
5354+
5355+
/*
5356+
* Table set by setcellwidths().
5357+
*/
5358+
typedef struct
5359+
{
5360+
long first;
5361+
long last;
5362+
char width;
5363+
} cw_interval_T;
5364+
5365+
static cw_interval_T *cw_table = NULL;
5366+
static size_t cw_table_size = 0;
5367+
5368+
/*
5369+
* Return 1 or 2 when "c" is in the cellwidth table.
5370+
* Return 0 if not.
5371+
*/
5372+
static int
5373+
cw_value(int c)
5374+
{
5375+
int mid, bot, top;
5376+
5377+
if (cw_table == NULL)
5378+
return 0;
5379+
5380+
// first quick check for Latin1 etc. characters
5381+
if (c < cw_table[0].first)
5382+
return 0;
5383+
5384+
// binary search in table
5385+
bot = 0;
5386+
top = (int)cw_table_size - 1;
5387+
while (top >= bot)
5388+
{
5389+
mid = (bot + top) / 2;
5390+
if (cw_table[mid].last < c)
5391+
bot = mid + 1;
5392+
else if (cw_table[mid].first > c)
5393+
top = mid - 1;
5394+
else
5395+
return cw_table[mid].width;
5396+
}
5397+
return 0;
5398+
}
5399+
5400+
static int
5401+
tv_nr_compare(const void *a1, const void *a2)
5402+
{
5403+
listitem_T *li1 = (listitem_T *)a1;
5404+
listitem_T *li2 = (listitem_T *)a2;
5405+
5406+
return li1->li_tv.vval.v_number - li2->li_tv.vval.v_number;
5407+
}
5408+
5409+
void
5410+
f_setcellwidths(typval_T *argvars, typval_T *rettv UNUSED)
5411+
{
5412+
list_T *l;
5413+
listitem_T *li;
5414+
int item;
5415+
int i;
5416+
listitem_T **ptrs;
5417+
cw_interval_T *table;
5418+
5419+
if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
5420+
{
5421+
emsg(_(e_listreq));
5422+
return;
5423+
}
5424+
l = argvars[0].vval.v_list;
5425+
if (l->lv_len == 0)
5426+
{
5427+
// Clearing the table.
5428+
vim_free(cw_table);
5429+
cw_table = NULL;
5430+
cw_table_size = 0;
5431+
return;
5432+
}
5433+
5434+
ptrs = ALLOC_MULT(listitem_T *, l->lv_len);
5435+
if (ptrs == NULL)
5436+
return;
5437+
5438+
// Check that all entries are a list with three numbers, the range is
5439+
// valid and the cell width is valid.
5440+
item = 0;
5441+
for (li = l->lv_first; li != NULL; li = li->li_next)
5442+
{
5443+
listitem_T *lili;
5444+
varnumber_T n1;
5445+
5446+
if (li->li_tv.v_type != VAR_LIST || li->li_tv.vval.v_list == NULL)
5447+
{
5448+
semsg(_(e_list_item_nr_is_not_list), item);
5449+
vim_free(ptrs);
5450+
return;
5451+
}
5452+
for (lili = li->li_tv.vval.v_list->lv_first, i = 0; lili != NULL;
5453+
lili = lili->li_next, ++i)
5454+
{
5455+
if (lili->li_tv.v_type != VAR_NUMBER)
5456+
break;
5457+
if (i == 0)
5458+
{
5459+
n1 = lili->li_tv.vval.v_number;
5460+
if (n1 < 0x100)
5461+
{
5462+
emsg(_(e_only_values_of_0x100_and_higher_supported));
5463+
vim_free(ptrs);
5464+
return;
5465+
}
5466+
}
5467+
else if (i == 1 && lili->li_tv.vval.v_number < n1)
5468+
{
5469+
semsg(_(e_list_item_nr_range_invalid), item);
5470+
vim_free(ptrs);
5471+
return;
5472+
}
5473+
else if (i == 2 && (lili->li_tv.vval.v_number < 1
5474+
|| lili->li_tv.vval.v_number > 2))
5475+
{
5476+
semsg(_(e_list_item_nr_cell_width_invalid), item);
5477+
vim_free(ptrs);
5478+
return;
5479+
}
5480+
}
5481+
if (i != 3)
5482+
{
5483+
semsg(_(e_list_item_nr_does_not_contain_3_numbers), item);
5484+
vim_free(ptrs);
5485+
return;
5486+
}
5487+
ptrs[item++] = lili;
5488+
}
5489+
5490+
// Sort the list on the first number.
5491+
qsort((void *)ptrs, (size_t)l->lv_len, sizeof(listitem_T *), tv_nr_compare);
5492+
5493+
table = ALLOC_MULT(cw_interval_T, l->lv_len);
5494+
if (table == NULL)
5495+
{
5496+
vim_free(ptrs);
5497+
return;
5498+
}
5499+
5500+
// Store the items in the new table.
5501+
item = 0;
5502+
for (li = l->lv_first; li != NULL; li = li->li_next)
5503+
{
5504+
listitem_T *lili = li->li_tv.vval.v_list->lv_first;
5505+
varnumber_T n1;
5506+
5507+
n1 = lili->li_tv.vval.v_number;
5508+
if (item > 0 && n1 <= table[item - 1].last)
5509+
{
5510+
semsg(_(e_overlapping_ranges_for_nr), (long)n1);
5511+
vim_free(ptrs);
5512+
vim_free(table);
5513+
return;
5514+
}
5515+
table[item].first = n1;
5516+
lili = lili->li_next;
5517+
table[item].last = lili->li_tv.vval.v_number;
5518+
lili = lili->li_next;
5519+
table[item].width = lili->li_tv.vval.v_number;
5520+
++item;
5521+
}
5522+
5523+
vim_free(ptrs);
5524+
vim_free(cw_table);
5525+
cw_table = table;
5526+
cw_table_size = l->lv_len;
5527+
}

src/proto/mbyte.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,5 @@ int convert_input(char_u *ptr, int len, int maxlen);
8484
int convert_input_safe(char_u *ptr, int len, int maxlen, char_u **restp, int *restlenp);
8585
char_u *string_convert(vimconv_T *vcp, char_u *ptr, int *lenp);
8686
char_u *string_convert_ext(vimconv_T *vcp, char_u *ptr, int *lenp, int *unconvlenp);
87+
void f_setcellwidths(typval_T *argvars, typval_T *rettv);
8788
/* vim: set ft=c : */

src/testdir/test_utf8.vim

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,39 @@ func Test_screenchar_utf8()
145145
bwipe!
146146
endfunc
147147

148+
func Test_setcellwidths()
149+
call setcellwidths([
150+
\ [0x1330, 0x1330, 2],
151+
\ [0x1337, 0x1339, 2],
152+
\ [9999, 10000, 1],
153+
\])
154+
155+
call assert_equal(2, strwidth("\u1330"))
156+
call assert_equal(1, strwidth("\u1336"))
157+
call assert_equal(2, strwidth("\u1337"))
158+
call assert_equal(2, strwidth("\u1339"))
159+
call assert_equal(1, strwidth("\u133a"))
160+
161+
call setcellwidths([])
162+
163+
call assert_fails('call setcellwidths(1)', 'E714:')
164+
165+
call assert_fails('call setcellwidths([1, 2, 0])', 'E1109:')
166+
167+
call assert_fails('call setcellwidths([[0x101]])', 'E1110:')
168+
call assert_fails('call setcellwidths([[0x101, 0x102]])', 'E1110:')
169+
call assert_fails('call setcellwidths([[0x101, 0x102, 1, 4]])', 'E1110:')
170+
call assert_fails('call setcellwidths([["a"]])', 'E1110:')
171+
172+
call assert_fails('call setcellwidths([[0x102, 0x101, 1]])', 'E1111:')
173+
174+
call assert_fails('call setcellwidths([[0x101, 0x102, 0]])', 'E1112:')
175+
call assert_fails('call setcellwidths([[0x101, 0x102, 3]])', 'E1112:')
176+
177+
call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x115, 0x116, 2]])', 'E1113:')
178+
call assert_fails('call setcellwidths([[0x111, 0x122, 1], [0x122, 0x123, 2]])', 'E1113:')
179+
180+
call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:')
181+
endfunc
182+
148183
" vim: shiftwidth=2 sts=2 expandtab

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,8 @@ static char *(features[]) =
754754

755755
static int included_patches[] =
756756
{ /* Add new patch number below this line */
757+
/**/
758+
1535,
757759
/**/
758760
1534,
759761
/**/

0 commit comments

Comments
 (0)