patch 9.0.1991: no cmdline completion for setting the font

Commit: 
https://github.com/vim/vim/commit/290b887e8cc2c0d3dfc7f315b2052472c7c589cc
Author: Yee Cheng Chin <ychin....@gmail.com>
Date:   Thu Oct 5 20:54:21 2023 +0200

    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 <c...@256bit.org>
    Co-authored-by: Yee Cheng Chin <ychin....@gmail.com>

diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c
index 3fd6f3a90..4b3f53ef9 100644
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -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.
  *
diff --git a/src/optiondefs.h b/src/optiondefs.h
index f850d19b1..c4810e7d5 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -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,
diff --git a/src/optionstr.c b/src/optionstr.c
index 135cb9af6..6b40dcd70 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -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 *)"         \");
+
+    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.
diff --git a/src/os_mswin.c b/src/os_mswin.c
index aa5fe5243..46f73752f 100644
--- a/src/os_mswin.c
+++ b/src/os_mswin.c
@@ -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;
diff --git a/src/proto/gui_gtk_x11.pro b/src/proto/gui_gtk_x11.pro
index 3fa2ac96d..459cdb953 100644
--- a/src/proto/gui_gtk_x11.pro
+++ b/src/proto/gui_gtk_x11.pro
@@ -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);
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index 48cccea8c..88034fcec 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -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);
diff --git a/src/proto/os_mswin.pro b/src/proto/os_mswin.pro
index d9172ee01..9f534e09d 100644
--- a/src/proto/os_mswin.pro
+++ b/src/proto/os_mswin.pro
@@ -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 : */
diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim
index acc72f839..e7f7c6d83 100644
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -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
 
diff --git a/src/version.c b/src/version.c
index 0f73dc25a..9b1c0b4e9 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1991,
 /**/
     1990,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1qoTZv-009OVU-6N%40256bit.org.

Raspunde prin e-mail lui