Re: [PATCH] emoji on TUI emacs

Eli Zaretskii <[email protected]> Sun, 09 Aug 2026 08:07:17 +0300
Newsgroups gmane.emacs.devel
Message-ID <[email protected]>
> From: Kai Ma <[email protected]>
> Cc: [email protected]
> Date: Sat, 08 Aug 2026 20:48:33 +0200
> 
> string-width returns 2 after the patch for fully-matched composed emoji
> sequences on tty terminals:
> 
>     (string-width (string #x2665))         ;; ==> 1
>     (string-width (string #x2665 #xfe0f))  ;; ==> 2
>     (string-width (string #x1f3f4 #x200d #x2620 #xfe0f))
>                                            ;; ==> 2
> 
> And I can confirm org-table alignment works properly now.
> 
> However, the column number change due to C-f, C-b, etc. is still the
> total sum of CHARACTER_WIDTH (not necessarily 2).  Do we want to change
> it as well?

Yes, definitely.  current-column and move-to-column should both work
correctly in these cases.

> --- a/lisp/composite.el
> +++ b/lisp/composite.el
> @@ -899,11 +899,18 @@ auto-compose-chars
>  
>  This function is the default value of `auto-composition-function' (which see)."
>    (let ((gstring (composition-get-gstring from to font-object string)))
> -    (if (lgstring-shaped-p gstring)
> -	gstring
> -      (or (fontp font-object 'font-object)
> -	  (setq func 'compose-gstring-for-terminal))
> -      (funcall func gstring direction))))
> +    (cond
> +     ((lgstring-shaped-p gstring)
> +      gstring)
> +     ((and (eq func #'compose-gstring-and-emoji)
> +           (fontp font-object 'font-object))
> +      (compose-gstring-for-graphic gstring direction))
> +     ((eq func #'compose-gstring-and-emoji)
> +      (compose-gstring-and-emoji gstring direction))
> +     ((fontp font-object 'font-object)
> +      (funcall func gstring direction))
> +     (t
> +      (compose-gstring-for-terminal gstring direction)))))

I think the above logic could benefit from comments explaining why we
dispatch the different cases as we do.

> +  DEFVAR_LISP ("auto-composition-emoji-tty-eligible-codepoints", Vauto_composition_emoji_tty_eligible_codepoints,
> +	       doc: /* List of codepoints for which auto-composition will check for an emoji font on tty display.

This line is too long.  Our conventions are not to exceed 79
characters.  I suggest to shorten it as follows:

  List of characters displayed as Emoji on TTY frames when followed by VS-16.

> +These are codepoints which have Emoji_Presentation = No, and thus by
> +default are not displayed as emoji.  When followed by U+FE0F (VS-16),
                                ^^^^^
> +they may be considered as the beginning of an emoji sequence instead.
                                                 ^^^^^
"Emoji", capitalized.

> +This list is auto-generated, you should not need to modify it.  */);
> +  Vauto_composition_emoji_tty_eligible_codepoints = Qnil;
> +
> +  DEFVAR_BOOL ("tty-display-emoji-force-wide", tty_display_emoji_force_wide,
> +    doc: /* Whether Emoji sequences on TTY frames should always be considered wide.
> +
> +When nil, emoji sequences whose first character's width is 1 may be
             ^^^^^
"Emoji"

> +considered as a narrow glyph to be compatible with some non-compliant
> +terminal emulators.  */);
> +  tty_display_emoji_force_wide = true;

I think these two variables are related (basically, the first one
controls which sequences will be considered to be wide, right?).  So I
think they should each mention the other one.

> @@ -379,9 +377,8 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to,
>  	  chars = end - i;
>  	  bytes = string_char_to_byte (string, end) - i_byte;
>  	}
> -#ifdef HAVE_WINDOW_SYSTEM
>        else if (auto_comp
> -	       && f && FRAME_WINDOW_P (f)
> +	       && f
>  	       && multibyte
>  	       && find_automatic_composition (i, -1, i, &ignore,
>  					      &end, &val, string)
> @@ -396,7 +393,7 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to,
>  
>  	  /* The below is somewhat expensive, so compute it only once
>  	     for the entire loop, and only if needed.  */
> -	  if (font_width < 0)
> +	  if (FRAME_WINDOW_P (f) && font_width < 0)
>  	    {
>  	      font_width = FRAME_COLUMN_WIDTH (f);
>  	      default_font = Fface_font (Qdefault, Qnil, Qnil);
> @@ -417,11 +414,13 @@ lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to,
>  		    }
>  		}
>  	    }
> -	  thiswidth = (double) pixelwidth / font_width + 0.5;
> +
> +	  thiswidth = FRAME_WINDOW_P (f)
> +	    ? ((double) pixelwidth / font_width + 0.5)
> +	    : pixelwidth;
>  	  chars = end - i;
>  	  bytes = string_char_to_byte (string, end) - i_byte;
>  	}
> -#endif	/* HAVE_WINDOW_SYSTEM */
>        else
>  	{
>  	  int c;

This is not quite right: in an Emacs build --without-x some of the
functions mentioned in the code fragment which was previously under
HAVE_WINDOW_SYSTEM do not exist, so this will fail to link.  What you
need is to move the HAVE_WINDOW_SYSTEM condition inside this fragment,
such that it is still in effect when functions like Ffont_info are
called.  I think the entire block which starts with

	  if (font_width < 0)

should be under the HAVE_WINDOW_SYSTEM condition, since it makes no
sense on a TTY frame.

Finally, I think these changes and the new variables warrant a NEWS
entry.

Thanks.