patch 9.0.1991: no cmdline completion for setting the font

Problem:  no cmdline completion for setting the font
Solution: enable it on Win32 and GTK builds

Add guifont cmdline completion (for Windows and GTK)

For Windows, auto-complete will only suggest monospace fonts as that's
the only types allowed. Will also suggest font options after the colon,
including suggesting the current font size for convenience, and misc
charset and quality options like `cANSI` and `qCLEARTYPE`.

For GTK, auto-complete will suggest only monospace fonts for `guifont`
but will include all fonts for `guifontwide`. The completion code
doesn't currently suggest the current font size, as the GTK guifont
format does not have a clear delimiter (':' for other platforms).

closes: #13264

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
This commit is contained in:
Yee Cheng Chin
2023-10-05 20:54:21 +02:00
committed by Christian Brabandt
parent ea746f9e86
commit 290b887e8c
9 changed files with 362 additions and 9 deletions

View File

@ -5315,6 +5315,62 @@ gui_mch_free_font(GuiFont font)
pango_font_description_free(font);
}
/*
* Cmdline expansion for setting 'guifont' / 'guifontwide'. Will enumerate
* through all fonts for completion. When setting 'guifont' it will only show
* monospace fonts as it's unlikely other fonts would be useful.
*/
void
gui_mch_expand_font(optexpand_T *args, void *param, int (*add_match)(char_u *val))
{
PangoFontFamily **font_families = NULL;
int n_families = 0;
int wide = *(int *)param;
if (args->oe_include_orig_val && *args->oe_opt_value == NUL && !wide)
{
// If guifont is empty, and we want to fill in the orig value, suggest
// the default so the user can modify it.
if (add_match((char_u *)DEFAULT_FONT) != OK)
return;
}
pango_context_list_families(
gui.text_context,
&font_families,
&n_families);
for (int i = 0; i < n_families; i++)
{
if (!wide && !pango_font_family_is_monospace(font_families[i]))
continue;
const char* fam_name = pango_font_family_get_name(font_families[i]);
if (input_conv.vc_type != CONV_NONE)
{
char_u *buf = string_convert(&input_conv, (char_u*)fam_name, NULL);
if (buf != NULL)
{
if (add_match(buf) != OK)
{
vim_free(buf);
break;
}
vim_free(buf);
}
else
break;
}
else
{
if (add_match((char_u *)fam_name) != OK)
break;
}
}
g_free(font_families);
}
/*
* Return the Pixel value (color) for the given color name.
*

View File

@ -1164,9 +1164,13 @@ static struct vimoption options[] =
{(char_u *)NULL, (char_u *)0L}
#endif
SCTX_INIT},
{"guifont", "gfn", P_STRING|P_VI_DEF|P_RCLR|P_ONECOMMA|P_NODUP,
{"guifont", "gfn", P_STRING|P_VI_DEF|P_RCLR|P_ONECOMMA|P_NODUP
#if !defined(FEAT_GUI_GTK)
|P_COLON
#endif
,
#ifdef FEAT_GUI
(char_u *)&p_guifont, PV_NONE, did_set_guifont, NULL,
(char_u *)&p_guifont, PV_NONE, did_set_guifont, expand_set_guifont,
{(char_u *)"", (char_u *)0L}
#else
(char_u *)NULL, PV_NONE, NULL, NULL,
@ -1183,10 +1187,14 @@ static struct vimoption options[] =
{(char_u *)NULL, (char_u *)0L}
#endif
SCTX_INIT},
{"guifontwide", "gfw", P_STRING|P_VI_DEF|P_RCLR|P_ONECOMMA|P_NODUP,
{"guifontwide", "gfw", P_STRING|P_VI_DEF|P_RCLR|P_ONECOMMA|P_NODUP
#if !defined(FEAT_GUI_GTK)
|P_COLON
#endif
,
#if defined(FEAT_GUI)
(char_u *)&p_guifontwide, PV_NONE,
did_set_guifontwide, NULL,
did_set_guifontwide, expand_set_guifont,
{(char_u *)"", (char_u *)0L}
#else
(char_u *)NULL, PV_NONE, NULL, NULL,

View File

@ -731,7 +731,8 @@ did_set_option_listflag(char_u *val, char_u *flags, char *errbuf)
}
/*
* Expand an option that accepts a list of string values.
* Expand an option that accepts a list of fixed string values with known
* number of items.
*/
static int
expand_set_opt_string(
@ -801,10 +802,11 @@ static char_u *set_opt_callback_orig_option = NULL;
static char_u *((*set_opt_callback_func)(expand_T *, int));
/*
* Callback used by expand_set_opt_generic to also include the original value.
* Callback used by expand_set_opt_generic to also include the original value
* as the first item.
*/
static char_u *
expand_set_opt_callback(expand_T *xp, int idx)
expand_set_opt_generic_cb(expand_T *xp, int idx)
{
if (idx == 0)
{
@ -817,7 +819,8 @@ expand_set_opt_callback(expand_T *xp, int idx)
}
/*
* Expand an option with a callback that iterates through a list of possible names.
* Expand an option with a callback that iterates through a list of possible
* names using an index.
*/
static int
expand_set_opt_generic(
@ -838,7 +841,7 @@ expand_set_opt_generic(
args->oe_regmatch,
matches,
numMatches,
expand_set_opt_callback,
expand_set_opt_generic_cb,
FALSE);
set_opt_callback_orig_option = NULL;
@ -846,6 +849,95 @@ expand_set_opt_generic(
return ret;
}
static garray_T *expand_cb_ga;
static optexpand_T *expand_cb_args;
/*
* Callback provided to a function in expand_set_opt_callback. Will perform
* regex matching against the value and add to the list.
*
* Returns OK usually. Returns FAIL if it failed to allocate memory, and the
* caller should terminate the enumeration.
*/
static int
expand_set_opt_callback_cb(char_u *val)
{
regmatch_T *regmatch = expand_cb_args->oe_regmatch;
expand_T *xp = expand_cb_args->oe_xp;
garray_T *ga = expand_cb_ga;
char_u *str;
if (val == NULL || *val == NUL)
return OK;
if (xp->xp_pattern[0] != NUL &&
!vim_regexec(regmatch, val, (colnr_T)0))
return OK;
str = vim_strsave_escaped(val, (char_u *)" \t\\");
if (str == NULL)
return FAIL;
if (ga_grow(ga, 1) == FAIL)
{
vim_free(str);
return FAIL;
}
((char_u **)ga->ga_data)[ga->ga_len] = str;
++ga->ga_len;
return OK;
}
/*
* Expand an option with a provided function that takes a callback. The
* function will enumerate through all options and call the callback to add it
* to the list.
*
* "func" is the enumerator function that will generate the list of options.
* "func_params" is a single parameter that will be passed to func.
*/
static int
expand_set_opt_callback(
optexpand_T *args,
void (*func)(optexpand_T *, void* params, int (*cb)(char_u *val)),
void *func_params,
int *numMatches,
char_u ***matches)
{
garray_T ga;
int include_orig_val = args->oe_include_orig_val;
char_u *option_val = args->oe_opt_value;
ga_init2(&ga, sizeof(char *), 30);
if (include_orig_val && *option_val != NUL)
{
char_u *p = vim_strsave(option_val);
if (p == NULL)
return FAIL;
if (ga_grow(&ga, 1) == FAIL)
{
vim_free(p);
return FAIL;
}
((char_u **)ga.ga_data)[ga.ga_len] = p;
++ga.ga_len;
}
expand_cb_ga = &ga;
expand_cb_args = args;
func(args, func_params, expand_set_opt_callback_cb);
expand_cb_ga = NULL;
expand_cb_args = NULL;
*matches = ga.ga_data;
*numMatches = ga.ga_len;
return OK;
}
/*
* Expand an option which is a list of flags.
@ -2237,6 +2329,27 @@ did_set_guifont(optset_T *args UNUSED)
return errmsg;
}
/*
* Expand the 'guifont' option. Only when GUI is being used. Each platform has
* specific behaviors.
*/
int
expand_set_guifont(optexpand_T *args, int *numMatches, char_u ***matches)
{
if (!gui.in_use)
return FAIL;
# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_GTK)
char_u **varp = (char_u **)args->oe_varp;
int wide = (varp == &p_guifontwide);
return expand_set_opt_callback(
args, gui_mch_expand_font, &wide, numMatches, matches);
# else
return FAIL;
# endif
}
# if defined(FEAT_XFONTSET) || defined(PROTO)
/*
* The 'guifontset' option is changed.

View File

@ -2819,6 +2819,32 @@ points_to_pixels(WCHAR *str, WCHAR **end, int vertical, long_i pprinter_dc)
return pixels;
}
/*
* Convert pixel into point size. This is a reverse of points_to_pixels.
*/
static double
pixels_to_points(int pixels, int vertical, long_i pprinter_dc)
{
double points = 0;
HWND hwnd = (HWND)0;
HDC hdc;
HDC printer_dc = (HDC)pprinter_dc;
if (printer_dc == NULL)
{
hwnd = GetDesktopWindow();
hdc = GetWindowDC(hwnd);
}
else
hdc = printer_dc;
points = pixels * 72.0 / GetDeviceCaps(hdc, vertical ? LOGPIXELSY : LOGPIXELSX);
if (printer_dc == NULL)
ReleaseDC(hwnd, hdc);
return points;
}
static int CALLBACK
font_enumproc(
ENUMLOGFONTW *elf,
@ -2889,6 +2915,100 @@ init_logfont(LOGFONTW *lf)
return OK;
}
/*
* Call back for EnumFontFamiliesW in expand_font_enumproc.
*
*/
static int CALLBACK
expand_font_enumproc(
ENUMLOGFONTW *elf,
NEWTEXTMETRICW *ntm UNUSED,
DWORD type UNUSED,
LPARAM lparam)
{
LOGFONTW *lf = (LOGFONTW*)elf;
# ifndef FEAT_PROPORTIONAL_FONTS
// Ignore non-monospace fonts without further ado
if ((ntm->tmPitchAndFamily & 1) != 0)
return 1;
# endif
// Filter only on ANSI. Otherwise will see a lot of random fonts that we
// usually don't want.
if (lf->lfCharSet != ANSI_CHARSET)
return 1;
int (*add_match)(char_u *) = (int (*)(char_u *))lparam;
WCHAR *faceNameW = lf->lfFaceName;
char_u *faceName = utf16_to_enc(faceNameW, NULL);
if (!faceName)
return 0;
add_match(faceName);
vim_free(faceName);
return 1;
}
/*
* Cmdline expansion for setting 'guifont'. Will enumerate through all
* monospace fonts for completion. If used after ':', will expand to possible
* font configuration options like font sizes.
*
* This function has "gui" in its name because in some platforms (GTK) font
* handling is done by the GUI code, whereas in Windows it's part of the
* platform code.
*/
void
gui_mch_expand_font(optexpand_T *args, void *param UNUSED, int (*add_match)(char_u *val))
{
expand_T *xp = args->oe_xp;
if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern-1) == ':')
{
char buf[30];
// Always fill in with the current font size as first option for
// convenience. We simply round to the closest integer for simplicity.
int font_height = (int)round(
pixels_to_points(-current_font_height, TRUE, (long_i)NULL));
vim_snprintf(buf, ARRAY_LENGTH(buf), "h%d", font_height);
add_match((char_u *)buf);
// Note: Keep this in sync with get_logfont(). Don't include 'c' and
// 'q' as we fill in all the values below.
static char *(p_gfn_win_opt_values[]) = {
"h" , "w" , "W" , "b" , "i" , "u" , "s"};
for (size_t i = 0; i < ARRAY_LENGTH(p_gfn_win_opt_values); i++)
add_match((char_u *)p_gfn_win_opt_values[i]);
struct charset_pair *cp;
for (cp = charset_pairs; cp->name != NULL; ++cp)
{
vim_snprintf(buf, ARRAY_LENGTH(buf), "c%s", cp->name);
add_match((char_u *)buf);
}
struct quality_pair *qp;
for (qp = quality_pairs; qp->name != NULL; ++qp)
{
vim_snprintf(buf, ARRAY_LENGTH(buf), "q%s", qp->name);
add_match((char_u *)buf);
}
return;
}
HWND hwnd = GetDesktopWindow();
HDC hdc = GetWindowDC(hwnd);
EnumFontFamiliesW(hdc,
NULL,
(FONTENUMPROCW)expand_font_enumproc,
(LPARAM)add_match);
ReleaseDC(hwnd, hdc);
}
/*
* Compare a UTF-16 string and an ASCII string literally.
* Only works all the code points are inside ASCII range.
@ -2995,6 +3115,7 @@ get_logfont(
{
switch (*p++)
{
// Note: Keep this in sync with gui_mch_expand_font().
case L'h':
lf->lfHeight = - points_to_pixels(p, &p, TRUE, (long_i)printer_dc);
break;

View File

@ -37,6 +37,7 @@ int gui_mch_init_font(char_u *font_name, int fontset);
GuiFont gui_mch_get_font(char_u *name, int report_error);
char_u *gui_mch_get_fontname(GuiFont font, char_u *name);
void gui_mch_free_font(GuiFont font);
void gui_mch_expand_font(optexpand_T *args, void *param, int (*cb)(char_u *val));
guicolor_T gui_mch_get_color(char_u *name);
guicolor_T gui_mch_get_rgb_color(int r, int g, int b);
void gui_mch_set_fg_color(guicolor_T color);

View File

@ -152,6 +152,7 @@ int expand_set_foldclose(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_foldmethod(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_foldopen(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_formatoptions(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_guifont(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_guioptions(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_highlight(optexpand_T *args, int *numMatches, char_u ***matches);
int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char_u ***matches);

View File

@ -51,5 +51,6 @@ void serverProcessPendingMessages(void);
char *charset_id2name(int id);
char *quality_id2name(DWORD id);
int get_logfont(LOGFONTW *lf, char_u *name, HDC printer_dc, int verbose);
void gui_mch_expand_font(optexpand_T *args, void *param, int (*cb)(char_u *val));
void channel_init_winsock(void);
/* vim: set ft=c : */

View File

@ -580,6 +580,56 @@ func Test_set_guifontwide()
endif
endfunc
func Test_expand_guifont()
if has('gui_win32')
let guifont_saved = &guifont
let guifontwide_saved = &guifontwide
" Test recalling existing option, and suggesting current font size
set guifont=Courier\ New:h11:cANSI
call assert_equal('Courier\ New:h11:cANSI', getcompletion('set guifont=', 'cmdline')[0])
call assert_equal('h11', getcompletion('set guifont=Lucida\ Console:', 'cmdline')[0])
" Test auto-completion working for font names
call assert_equal(['Courier\ New'], getcompletion('set guifont=Couri*ew$', 'cmdline'))
call assert_equal(['Courier\ New'], getcompletion('set guifontwide=Couri*ew$', 'cmdline'))
" Make sure non-monospace fonts are filtered out
call assert_equal([], getcompletion('set guifont=Arial', 'cmdline'))
call assert_equal([], getcompletion('set guifontwide=Arial', 'cmdline'))
" Test auto-completion working for font options
call assert_notequal(-1, index(getcompletion('set guifont=Courier\ New:', 'cmdline'), 'b'))
call assert_equal(['cDEFAULT'], getcompletion('set guifont=Courier\ New:cD*T', 'cmdline'))
call assert_equal(['qCLEARTYPE'], getcompletion('set guifont=Courier\ New:qC*TYPE', 'cmdline'))
let &guifontwide = guifontwide_saved
let &guifont = guifont_saved
elseif has('gui_gtk')
let guifont_saved = &guifont
let guifontwide_saved = &guifontwide
" Test recalling default and existing option
set guifont=
call assert_equal('Monospace\ 10', getcompletion('set guifont=', 'cmdline')[0])
set guifont=Monospace\ 9
call assert_equal('Monospace\ 9', getcompletion('set guifont=', 'cmdline')[0])
" Test auto-completion working for font names
call assert_equal(['Monospace'], getcompletion('set guifont=Mono*pace$', 'cmdline'))
call assert_equal(['Monospace'], getcompletion('set guifontwide=Mono*pace$', 'cmdline'))
" Make sure non-monospace fonts are filtered out only in 'guifont'
call assert_equal([], getcompletion('set guifont=Sans$', 'cmdline'))
call assert_equal(['Sans'], getcompletion('set guifontwide=Sans$', 'cmdline'))
let &guifontwide = guifontwide_saved
let &guifont = guifont_saved
else
call assert_equal([], getcompletion('set guifont=', 'cmdline'))
endif
endfunc
func Test_set_guiligatures()
CheckX11BasedGui

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1991,
/**/
1990,
/**/