patch 9.1.0030: Cannot use terminal alternate font

Problem:  Cannot use terminal alternate fonts (PMunch)
Solution: Support terminal alternate fonts using
          CSI SGR 10-20 and t_CF code (PMunch)

Add support for alternate font highlighting

This adds support for alternate font highlighting using CSI SGR 10-20.
Few terminals currently support this, but with added tool support this
should improve over time. The change here is more or less taken from how
colors are configured and applied, but there might be some parts I
missed while implementing it. Changing fonts is done through the new
`:hi ctermfont` attribute which takes a number, 0 is the normal font, and
the numbers 1-9 select an "alternative" font. Which fonts are in use is
up to the terminal.

fixes: #13513
closes: #13537

Signed-off-by: PMunch <peterme@peterme.net>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/highlight.c b/src/highlight.c
index 318564c..3381208 100644
--- a/src/highlight.c
+++ b/src/highlight.c
@@ -59,6 +59,7 @@
     int		sg_cterm_bg;	// terminal bg color number + 1
     int		sg_cterm_ul;	// terminal ul color number + 1
     int		sg_cterm_attr;	// Screen attr for color term mode
+    int		sg_cterm_font;	// terminal alternative font (0 for normal)
 // for when using the GUI
 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
     guicolor_T	sg_gui_fg;	// GUI foreground color handle
@@ -1035,6 +1036,39 @@
 }
 
 /*
+ * Set the cterm font for the highlight group at 'idx'.
+ * 'arg' is the color name or the numeric value as a string.
+ * 'init' is set to TRUE when initializing highlighting.
+ * Called for the ":highlight" command and the "hlset()" function.
+ *
+ * Returns TRUE if the font is set.
+ */
+    static int
+highlight_set_cterm_font(
+	int	idx,
+	char_u	*arg,
+	int	init)
+{
+    int		font;
+
+    if (init && (HL_TABLE()[idx].sg_set & SG_CTERM))
+	return FALSE;
+
+    if (!init)
+	HL_TABLE()[idx].sg_set |= SG_CTERM;
+
+    if (VIM_ISDIGIT(*arg))
+	font = atoi((char *)arg);
+    else if (STRICMP(arg, "NONE") == 0)
+	font = -1;
+    else
+	return FALSE;
+
+    HL_TABLE()[idx].sg_cterm_font = font + 1;
+    return TRUE;
+}
+
+/*
  * Set the cterm fg/bg/ul color for the highlight group at 'idx'.
  * 'key' is one of 'CTERMFG' or 'CTERMBG' or 'CTERMUL'.
  * 'keystart' is the color name/value.
@@ -1679,6 +1713,14 @@
 		    break;
 		}
 	    }
+	    else if (STRCMP(key, "CTERMFONT") == 0)
+	    {
+		if (!highlight_set_cterm_font(idx, arg, init))
+		{
+		    error = TRUE;
+		    break;
+		}
+	    }
 	    else if (STRCMP(key, "GUIFG") == 0)
 	    {
 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
@@ -1865,6 +1907,7 @@
 	    || HL_TABLE()[idx].sg_cterm_attr != 0
 	    || HL_TABLE()[idx].sg_cterm_fg != 0
 	    || HL_TABLE()[idx].sg_cterm_bg != 0
+	    || HL_TABLE()[idx].sg_cterm_font != 0
 #ifdef FEAT_GUI
 	    || HL_TABLE()[idx].sg_gui_attr != 0
 	    || HL_TABLE()[idx].sg_gui_fg_name != NULL
@@ -1892,6 +1935,7 @@
     HL_TABLE()[idx].sg_cterm_fg = 0;
     HL_TABLE()[idx].sg_cterm_bg = 0;
     HL_TABLE()[idx].sg_cterm_attr = 0;
+    HL_TABLE()[idx].sg_cterm_font = 0;
 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
     HL_TABLE()[idx].sg_gui = 0;
     VIM_CLEAR(HL_TABLE()[idx].sg_gui_fg_name);
@@ -2539,6 +2583,8 @@
 						  == taep->ae_u.cterm.bg_color
 			    && aep->ae_u.cterm.ul_color
 						  == taep->ae_u.cterm.ul_color
+			    && aep->ae_u.cterm.font
+						  == taep->ae_u.cterm.font
 #ifdef FEAT_TERMGUICOLORS
 			    && aep->ae_u.cterm.fg_rgb
 						    == taep->ae_u.cterm.fg_rgb
@@ -2609,6 +2655,7 @@
 	taep->ae_u.cterm.fg_color = aep->ae_u.cterm.fg_color;
 	taep->ae_u.cterm.bg_color = aep->ae_u.cterm.bg_color;
 	taep->ae_u.cterm.ul_color = aep->ae_u.cterm.ul_color;
+	taep->ae_u.cterm.font = aep->ae_u.cterm.font;
 #ifdef FEAT_TERMGUICOLORS
 	taep->ae_u.cterm.fg_rgb = aep->ae_u.cterm.fg_rgb;
 	taep->ae_u.cterm.bg_rgb = aep->ae_u.cterm.bg_rgb;
@@ -2639,6 +2686,7 @@
     at_en.ae_u.cterm.fg_color = fg;
     at_en.ae_u.cterm.bg_color = bg;
     at_en.ae_u.cterm.ul_color = 0;
+    at_en.ae_u.cterm.font = 0;
     return get_attr_entry(&cterm_attr_table, &at_en);
 }
 #endif
@@ -2809,6 +2857,8 @@
 		    new_en.ae_u.cterm.bg_color = prim_aep->ae_u.cterm.bg_color;
 		if (prim_aep->ae_u.cterm.ul_color > 0)
 		    new_en.ae_u.cterm.ul_color = prim_aep->ae_u.cterm.ul_color;
+		if (prim_aep->ae_u.cterm.font > 0)
+		    new_en.ae_u.cterm.font = prim_aep->ae_u.cterm.font;
 #ifdef FEAT_TERMGUICOLORS
 		// If both fg and bg are not set fall back to cterm colors.
 		// Helps for SpellBad which uses undercurl in the GUI.
@@ -2948,6 +2998,8 @@
 				    sgp->sg_cterm_bg, NULL, "ctermbg");
     didh = highlight_list_arg(id, didh, LIST_INT,
 				    sgp->sg_cterm_ul, NULL, "ctermul");
+    didh = highlight_list_arg(id, didh, LIST_INT,
+				    sgp->sg_cterm_font, NULL, "ctermfont");
 
 #if defined(FEAT_GUI) || defined(FEAT_EVAL)
     didh = highlight_list_arg(id, didh, LIST_ATTR,
@@ -3138,7 +3190,7 @@
 	    return (HL_TABLE()[id - 1].sg_gui_sp_name);
 	return (HL_TABLE()[id - 1].sg_gui_bg_name);
     }
-    if (font || sp)
+    if (sp)
 	return NULL;
     if (modec == 'c')
     {
@@ -3146,6 +3198,8 @@
 	    n = HL_TABLE()[id - 1].sg_cterm_fg - 1;
 	else if (ul)
 	    n = HL_TABLE()[id - 1].sg_cterm_ul - 1;
+	else if (font)
+	    n = HL_TABLE()[id - 1].sg_cterm_font - 1;
 	else
 	    n = HL_TABLE()[id - 1].sg_cterm_bg - 1;
 	if (n < 0)
@@ -3296,7 +3350,8 @@
 
     // For the color term mode: If there are other than "normal"
     // highlighting attributes, need to allocate an attr number.
-    if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0 && sgp->sg_cterm_ul == 0
+    if (sgp->sg_cterm_fg == 0 && sgp->sg_cterm_bg == 0 &&
+	sgp->sg_cterm_ul == 0 && sgp->sg_cterm_font == 0
 # ifdef FEAT_TERMGUICOLORS
 	    && sgp->sg_gui_fg == INVALCOLOR
 	    && sgp->sg_gui_bg == INVALCOLOR
@@ -3310,6 +3365,7 @@
 	at_en.ae_u.cterm.fg_color = sgp->sg_cterm_fg;
 	at_en.ae_u.cterm.bg_color = sgp->sg_cterm_bg;
 	at_en.ae_u.cterm.ul_color = sgp->sg_cterm_ul;
+	at_en.ae_u.cterm.font = sgp->sg_cterm_font;
 # ifdef FEAT_TERMGUICOLORS
 	at_en.ae_u.cterm.fg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_fg);
 	at_en.ae_u.cterm.bg_rgb = GUI_MCH_GET_RGB2(sgp->sg_gui_bg);
@@ -3717,6 +3773,8 @@
 	hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg;
     if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg)
 	hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg;
+    if (hlt[id - 1].sg_cterm_font != hlt[id_S - 1].sg_cterm_font)
+	hlt[hlcnt + i].sg_cterm_font = hlt[id - 1].sg_cterm_font;
 #  if defined(FEAT_GUI) || defined(FEAT_EVAL)
     hlt[hlcnt + i].sg_gui ^=
 	hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui;
@@ -4180,6 +4238,10 @@
 	if (dict_add_string(dict, "ctermul",
 			highlight_color(hlgid, (char_u *)"ul", 'c')) == FAIL)
 	    goto error;
+    if (sgp->sg_cterm_font != 0)
+	if (dict_add_string(dict, "ctermfont",
+			highlight_color(hlgid, (char_u *)"font", 'c')) == FAIL)
+	    goto error;
     if (sgp->sg_gui != 0)
     {
 	attr_dict = highlight_get_attr_dict(sgp->sg_gui);
@@ -4408,6 +4470,7 @@
     char_u	*ctermfg;
     char_u	*ctermbg;
     char_u	*ctermul;
+    char_u	*ctermfont;
     char_u	*guifg;
     char_u	*guibg;
     char_u	*guisp;
@@ -4492,6 +4555,10 @@
     if (error)
 	return FALSE;
 
+    ctermfont = hldict_get_string(dict, (char_u *)"ctermfont", &error);
+    if (error)
+	return FALSE;
+
     if (!hldict_attr_to_str(dict, (char_u *)"gui", gui_attr, sizeof(gui_attr)))
 	return FALSE;
 
@@ -4516,7 +4583,7 @@
     // If none of the attributes are specified, then do nothing.
     if (term_attr[0] == NUL && start == NULL && stop == NULL
 	    && cterm_attr[0] == NUL && ctermfg == NULL && ctermbg == NULL
-	    && ctermul == NULL && gui_attr[0] == NUL
+	    && ctermul == NULL && ctermfont == NULL && gui_attr[0] == NUL
 # ifdef FEAT_GUI
 	    && font == NULL
 # endif
@@ -4536,6 +4603,7 @@
     p = add_attr_and_value(p, (char_u *)" ctermfg=", 9, ctermfg);
     p = add_attr_and_value(p, (char_u *)" ctermbg=", 9, ctermbg);
     p = add_attr_and_value(p, (char_u *)" ctermul=", 9, ctermul);
+    p = add_attr_and_value(p, (char_u *)" ctermfont=", 9, ctermfont);
     p = add_attr_and_value(p, (char_u *)" gui=", 5, gui_attr);
 # ifdef FEAT_GUI
     p = add_attr_and_value(p, (char_u *)" font=", 6, font);
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 783b851..5fa8f37 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -2931,6 +2931,7 @@
     p_term("t_cd", T_CD)
     p_term("t_ce", T_CE)
     p_term("t_Ce", T_UCE)
+    p_term("t_CF", T_CFO)
     p_term("t_cl", T_CL)
     p_term("t_cm", T_CM)
     p_term("t_Co", T_CCO)
diff --git a/src/proto/term.pro b/src/proto/term.pro
index b83df86..d82f906 100644
--- a/src/proto/term.pro
+++ b/src/proto/term.pro
@@ -33,6 +33,7 @@
 void term_fg_color(int n);
 void term_bg_color(int n);
 void term_ul_color(int n);
+void term_font(int n);
 char_u *term_bg_default(void);
 void term_fg_rgb_color(guicolor_T rgb);
 void term_bg_rgb_color(guicolor_T rgb);
diff --git a/src/screen.c b/src/screen.c
index ac24a16..ef43cfc 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -1667,6 +1667,8 @@
      */
     if (aep != NULL)
     {
+	if (aep->ae_u.cterm.font > 0 && aep->ae_u.cterm.font < 12)
+		term_font(aep->ae_u.cterm.font);
 #ifdef FEAT_TERMGUICOLORS
 	// When 'termguicolors' is set but fg or bg is unset,
 	// fall back to the cterm colors.   This helps for SpellBad,
diff --git a/src/structs.h b/src/structs.h
index 85f440d..da8c2e2 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1186,6 +1186,7 @@
 	    short_u	    fg_color;	// foreground color number
 	    short_u	    bg_color;	// background color number
 	    short_u	    ul_color;	// underline color number
+	    short_u	    font;	// font number
 # ifdef FEAT_TERMGUICOLORS
 	    guicolor_T	    fg_rgb;	// foreground color RGB
 	    guicolor_T	    bg_rgb;	// background color RGB
diff --git a/src/term.c b/src/term.c
index 734b759..2e2c6df 100644
--- a/src/term.c
+++ b/src/term.c
@@ -625,7 +625,7 @@
 
 #ifdef FEAT_TERMGUICOLORS
 /*
- * Additions for using the RGB colors
+ * Additions for using the RGB colors and terminal font
  */
 static tcap_entry_T builtin_rgb[] = {
     // These are printf strings, not terminal codes.
@@ -637,6 +637,12 @@
 };
 #endif
 
+static tcap_entry_T special_term[] = {
+    // These are printf strings, not terminal codes.
+    {(int)KS_CF,	"\033[%dm"},
+    {(int)KS_NAME,	NULL}  // end marker
+};
+
 /*
  * iris-ansi for Silicon Graphics machines.
  */
@@ -1235,6 +1241,7 @@
     {(int)KS_U7,	"[U7]"},
     {(int)KS_RFG,	"[RFG]"},
     {(int)KS_RBG,	"[RBG]"},
+    {(int)KS_CF,	"[CF%d]"},
     {K_UP,		"[KU]"},
     {K_DOWN,		"[KD]"},
     {K_LEFT,		"[KL]"},
@@ -1754,6 +1761,7 @@
 			{KS_CBE, "BE"}, {KS_CBD, "BD"},
 			{KS_CST, "ST"}, {KS_CRT, "RT"},
 			{KS_SSI, "Si"}, {KS_SRI, "Ri"},
+			{KS_CF, "CF"},
 			{(enum SpecialKey)0, NULL}
 		    };
     int		    i;
@@ -2113,6 +2121,8 @@
 		&& term_strings_not_set(KS_8U))
 	    apply_builtin_tcap(term, builtin_rgb, TRUE);
 #endif
+	if (term_strings_not_set(KS_CF))
+	    apply_builtin_tcap(term, special_term, TRUE);
     }
 
 /*
@@ -3116,6 +3126,17 @@
 }
 #endif
 
+    void
+term_font(int n)
+{
+    if (*T_CFO)
+    {
+	char buf[20];
+	sprintf(buf, (char *)T_CFO, 9 + n);
+	OUT_STR(buf);
+    }
+}
+
     static void
 term_color(char_u *s, int n)
 {
diff --git a/src/termdefs.h b/src/termdefs.h
index 9f72dc1..f28fd19 100644
--- a/src/termdefs.h
+++ b/src/termdefs.h
@@ -114,10 +114,11 @@
     KS_SSI,	// save icon text
     KS_SRI,	// restore icon text
     KS_FD,	// disable focus event tracking
-    KS_FE	// enable focus event tracking
+    KS_FE,	// enable focus event tracking
+    KS_CF	// set terminal alternate font
 };
 
-#define KS_LAST	    KS_FE
+#define KS_LAST	    KS_CF
 
 /*
  * the terminal capabilities are stored in this array
@@ -191,6 +192,7 @@
 #define T_CAF	(TERM_STR(KS_CAF))	// set foreground color (ANSI)
 #define T_CAB	(TERM_STR(KS_CAB))	// set background color (ANSI)
 #define T_CAU	(TERM_STR(KS_CAU))	// set underline color (ANSI)
+#define T_CFO	(TERM_STR(KS_CF))	// set alternate font
 #define T_LE	(TERM_STR(KS_LE))	// cursor left
 #define T_ND	(TERM_STR(KS_ND))	// cursor right
 #define T_CIS	(TERM_STR(KS_CIS))	// set icon text start
diff --git a/src/testdir/test_highlight.vim b/src/testdir/test_highlight.vim
index af60503..b831878 100644
--- a/src/testdir/test_highlight.vim
+++ b/src/testdir/test_highlight.vim
@@ -888,6 +888,16 @@
   highlight Normal ctermul=NONE
 endfunc
 
+" Test for 'ctermfont' in a highlight group
+func Test_highlight_ctermfont()
+  CheckNotGui
+  call assert_notmatch('ctermfont=', HighlightArgs('Normal'))
+  highlight Normal ctermfont=3
+  call assert_match('ctermfont=3', HighlightArgs('Normal'))
+  call assert_equal('3', synIDattr(synIDtrans(hlID('Normal')), 'font'))
+  highlight Normal ctermfont=NONE
+endfunc
+
 " Test for specifying 'start' and 'stop' in a highlight group
 func Test_highlight_start_stop()
   hi HlGrp1 start=<Esc>[27h;<Esc>[<Space>r;
@@ -1314,6 +1324,7 @@
   call hlset([{'name': 'hlg11', 'ctermfg': ''}])
   call hlset([{'name': 'hlg11', 'ctermbg': ''}])
   call hlset([{'name': 'hlg11', 'ctermul': ''}])
+  call hlset([{'name': 'hlg11', 'ctermfont': ''}])
   call hlset([{'name': 'hlg11', 'font': ''}])
   call hlset([{'name': 'hlg11', 'gui': {}}])
   call hlset([{'name': 'hlg11', 'guifg': ''}])
diff --git a/src/version.c b/src/version.c
index b44385a..8b19d81 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    30,
+/**/
     29,
 /**/
     28,