Commit 71781c31 authored by Paul Eggert's avatar Paul Eggert

format-message now curves ` and '

That way, the caller doesn’t have to use curved quotes to
get diagnostics that match the text-quoting-style preferences.
Suggested by Dmitry Gutov in:
http://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00893.html
This means we no longer need %qs, so remove that format.
While we’re at it, fix an unlikely bug and lessen the pressure
on the garbage collector by processing the string once rather
than twice in the usual case.
* doc/lispref/strings.texi (Formatting Strings):
* etc/NEWS: Document this.
* lisp/subr.el (format-message): Remove; now done in C.
* src/callint.c (Fcall_interactively):
* src/editfns.c (Fmessage, Fmessage_box):
Use Fformat_message instead of Finternal__text_restyle
followed by Fformat.
* src/doc.c (LSQM, RSQM): Remove; all uses changed to use
uLSQM and uRSQM.
(Fsubstitute_command_keys): Prefer AUTO_STRING to build_string
when pure ASCII now suffices.  Fix unlikely bug when parsing
unibyte string containing non-ASCII bytes.  Use inline code
rather than memcpy, as it’s a tiny number of bytes.
(Finternal__text_restyle): Remove; no longer used.
(syms_of_doc): Don’t declare it.
* src/editfns.c (Fformat): Rewrite in terms of new function
‘styled_format’.
(Fformat_message): New function, moved here from subr.el.
(styled_format): New function, with the old guts of Fformat,
except it now optionally transliterates quotes, and it transliterates
traditional grave accent and apostrophe quoting as well.
Remove recently-added q flag; no longer needed or used.
(syms_of_editfns): Define format-message.
* src/lisp.h (uLSQM0, uLSQM1, uLSQM2, uRSQM0, uRSQM1, uRSQM2):
Remove; no longer need to be global symbols.
* src/xdisp.c (vadd_to_log): Use Fformat_message, not Fformat,
so that callers can use `%s'.
* src/image.c (image_size_error, xbm_load_image, xbm_load)
(xpm_load, pbm_load, png_load_body, jpeg_load_body, tiff_load)
(gif_load, imagemagick_load_image, imagemagick_load, svg_load)
(svg_load_image, gs_load, x_kill_gs_process):
* src/lread.c (load_warn_old_style_backquotes):
* src/xfaces.c (load_pixmap):
* src/xselect.c (x_clipboard_manager_error_1):
Use `%s' instead of %qs in formats.
parent ef4c2eac
......@@ -816,9 +816,13 @@ if any.
@end defun
@defun format-message string &rest objects
@cindex curved quotes
@cindex curly quotes
This function acts like @code{format}, except it also converts any
curved quotes in @var{string} as per the value of
@code{text-quoting-style}. @xref{Keys in Documentation}.
curved single quotes in @var{string} as per the value of
@code{text-quoting-style}, and treats grave accent (@t{`}) and
apostrophe (@t{'}) as if they were curved single quotes. @xref{Keys
in Documentation}.
@end defun
@cindex @samp{%} in format
......@@ -919,20 +923,23 @@ specification is unusual in that it does not use a value. For example,
Any other format character results in an @samp{Invalid format
operation} error.
Here are several examples:
Here are several examples, which assume the typical
@code{text-quoting-style} settings:
@example
@group
(format "The name of this buffer is %s." (buffer-name))
@result{} "The name of this buffer is strings.texi."
(format "The buffer object prints as %qs." (current-buffer))
@result{} "The buffer object prints as ‘strings.texi’."
(format "The octal value of %d is %o,
and the hex value is %x." 18 18 18)
@result{} "The octal value of 18 is 22,
and the hex value is 12."
(format-message
"The name of this buffer is ‘%s’." (buffer-name))
@result{} "The name of this buffer is ‘strings.texi’."
(format-message
"The buffer object prints as `%s'." (current-buffer))
@result{} "The buffer object prints as ‘strings.texi’."
@end group
@end example
......@@ -1001,20 +1008,13 @@ specifier, if any, to be inserted on the right rather than the left.
If both @samp{-} and @samp{0} are present, the @samp{0} flag is
ignored.
@cindex curved quotes
@cindex curly quotes
The flag @samp{q} quotes the printed representation as per the
variable @samp{text-quoting-style}. @xref{Keys in Documentation}.
Typically it uses curved single quotes @t{‘like this’} as in the
following example.
@example
@group
(format "%06d is padded on the left with zeros" 123)
@result{} "000123 is padded on the left with zeros"
(format "%q-6d is padded on the right" 123)
@result{} "‘123 ’ is padded on the right"
(format "'%-6d' is padded on the right" 123)
@result{} "'123 ' is padded on the right"
(format "The word '%-7s' actually has %d letters in it."
"foo" (length "foo"))
......
......@@ -272,7 +272,7 @@ successive char insertions.
. As before, you can type C-x 8 C-h to list shorthands.
** New minor mode electric-quote-mode for quoting like this and like this
as you type.
as you type. See also the new variable text-quoting-style.
** New minor mode global-eldoc-mode is enabled by default.
......@@ -916,20 +916,21 @@ Set it to ‘curve’ for curved single quotes ‘like this’, to ‘straight
for straight apostrophes 'like this', and to grave for grave accent
and apostrophe `like this'. The default value nil acts like ‘curve’
if curved single quotes are displayable, and like ‘grave’ otherwise.
Quotes in info files are not translated.
The new variable affects display of diagnostics and help, but not of info.
+++
** substitute-command-keys now replaces quotes.
That is, it converts documentation strings’ quoting style as per the
value of ‘text-quoting-style’. Doc strings in source code can use
either curved quotes or grave accent and apostrophe. As before,
characters preceded by \= are output as-is.
either curved single quotes or grave accents and apostrophes. As
before, characters preceded by \= are output as-is.
+++
** Message-issuing functions ‘error’, ‘message’, etc. now convert quotes.
They use the new ‘format-message’ function instead of plain ‘format’,
so that they now follow user preference as per ‘text-quoting-style’ if
their format argument contains curved quotes.
so that they now follow user preference as per ‘text-quoting-style’
when processing curved single quotes, grave accents, and apostrophes
in their format argument.
+++
** The character classes [:alpha:] and [:alnum:] in regular expressions
......@@ -1055,13 +1056,8 @@ quotes.
+++
** New function format-message is like format and also converts
curved quotes as per text-quoting-style.
+++
** New format flag q
The new q flag causes format to quote the output representation as
per the value of text-quoting-style. E.g., (format "%qs failed"
"foo") might return "‘foo’ failed".
curved single quotes, grave accents and apostrophes as per
text-quoting-style.
+++
** show-help-function's arg is converted via substitute-command-keys
......
......@@ -288,12 +288,6 @@ This function accepts any number of arguments, but ignores them."
(interactive)
nil)
(defun format-message (format-string &rest args)
"Format a string out of FORMAT-STRING and arguments.
This is like ‘format’, except it also converts curved quotes in
FORMAT-STRING as per ‘text-quoting-style’."
(apply #'format (internal--text-restyle format-string) args))
;; Signal a compile-error if the first arg is missing.
(defun error (&rest args)
"Signal an error, making error message by passing all args to `format'.
......
......@@ -511,9 +511,8 @@ invoke it. If KEYS is omitted or nil, the return value of
for (i = 2; *tem; i++)
{
visargs[1] = make_string (tem + 1, strcspn (tem + 1, "\n"));
visargs[1] = Finternal__text_restyle (visargs[1]);
if (strchr (SSDATA (visargs[1]), '%'))
callint_message = Fformat (i - 1, visargs + 1);
callint_message = Fformat_message (i - 1, visargs + 1);
else
callint_message = visargs[1];
......
......@@ -684,10 +684,7 @@ the same file name is found in the `doc-directory'. */)
return unbind_to (count, Qnil);
}
/* Curved quotation marks. */
static unsigned char const LSQM[] = { uLSQM0, uLSQM1, uLSQM2 };
static unsigned char const RSQM[] = { uRSQM0, uRSQM1, uRSQM2 };
/* Return true if text quoting style should default to quote `like this'. */
static bool
default_to_grave_quoting_style (void)
{
......@@ -925,14 +922,13 @@ Otherwise, return a new string. */)
if (NILP (tem))
{
name = Fsymbol_name (name);
insert1 (Fsubstitute_command_keys
(build_string ("\nUses keymap "uLSQM)));
AUTO_STRING (msg_prefix, "\nUses keymap `");
insert1 (Fsubstitute_command_keys (msg_prefix));
insert_from_string (name, 0, 0,
SCHARS (name),
SBYTES (name), 1);
insert1 (Fsubstitute_command_keys
(build_string
(uRSQM", which is not currently defined.\n")));
AUTO_STRING (msg_suffix, "', which is not currently defined.\n");
insert1 (Fsubstitute_command_keys (msg_suffix));
if (start[-1] == '<') keymap = Qnil;
}
else if (start[-1] == '<')
......@@ -972,9 +968,9 @@ Otherwise, return a new string. */)
else if ((strp[0] == '`' || strp[0] == '\'')
&& quoting_style == CURVE_QUOTING_STYLE)
{
start = strp[0] == '`' ? LSQM : RSQM;
start = (unsigned char const *) (strp[0] == '`' ? uLSQM : uRSQM);
length = 1;
length_byte = 3;
length_byte = sizeof uLSQM - 1;
idx = strp - SDATA (string) + 1;
goto subst;
}
......@@ -985,29 +981,28 @@ Otherwise, return a new string. */)
nchars++;
changed = true;
}
else if (strp[0] == uLSQM0 && strp[1] == uLSQM1
&& (strp[2] == uLSQM2 || strp[2] == uRSQM2)
&& quoting_style != CURVE_QUOTING_STYLE)
{
*bufp++ = (strp[2] == uLSQM2 && quoting_style == GRAVE_QUOTING_STYLE
? '`' : '\'');
strp += 3;
nchars++;
changed = true;
}
else if (! multibyte) /* just copy other chars */
else if (! multibyte)
*bufp++ = *strp++, nchars++;
else
{
int len;
STRING_CHAR_AND_LENGTH (strp, len);
if (len == 1)
*bufp = *strp;
int ch = STRING_CHAR_AND_LENGTH (strp, len);
if ((ch == LEFT_SINGLE_QUOTATION_MARK
|| ch == RIGHT_SINGLE_QUOTATION_MARK)
&& quoting_style != CURVE_QUOTING_STYLE)
{
*bufp++ = ((ch == LEFT_SINGLE_QUOTATION_MARK
&& quoting_style == GRAVE_QUOTING_STYLE)
? '`' : '\'');
strp += len;
changed = true;
}
else
memcpy (bufp, strp, len);
strp += len;
bufp += len;
{
do
*bufp++ = *strp++;
while (--len != 0);
}
nchars++;
}
}
......@@ -1019,67 +1014,6 @@ Otherwise, return a new string. */)
xfree (buf);
RETURN_UNGCPRO (tem);
}
DEFUN ("internal--text-restyle", Finternal__text_restyle,
Sinternal__text_restyle, 1, 1, 0,
doc: /* Return STRING, possibly substituting quote characters.
In the result, replace each curved single quote (\\=‘ and \\=’) by
left and right quote characters as specified by ‘text-quoting-style’.
Return the original STRING in the common case where no changes are needed.
Otherwise, return a new string. */)
(Lisp_Object string)
{
bool changed = false;
CHECK_STRING (string);
if (! STRING_MULTIBYTE (string))
return string;
enum text_quoting_style quoting_style = text_quoting_style ();
if (quoting_style == CURVE_QUOTING_STYLE)
return string;
ptrdiff_t bsize = SBYTES (string);
unsigned char const *strp = SDATA (string);
unsigned char const *strlim = strp + bsize;
USE_SAFE_ALLOCA;
char *buf = SAFE_ALLOCA (bsize);
char *bufp = buf;
ptrdiff_t nchars = 0;
while (strp < strlim)
{
unsigned char const *cp = strp;
switch (STRING_CHAR_ADVANCE (strp))
{
case LEFT_SINGLE_QUOTATION_MARK:
*bufp++ = quoting_style == GRAVE_QUOTING_STYLE ? '`': '\'';
changed = true;
break;
case RIGHT_SINGLE_QUOTATION_MARK:
*bufp++ = '\'';
changed = true;
break;
default:
do
*bufp++ = *cp++;
while (cp != strp);
break;
}
nchars++;
}
Lisp_Object result
= changed ? make_string_from_bytes (buf, nchars, bufp - buf) : string;
SAFE_FREE ();
return result;
}
void
syms_of_doc (void)
......@@ -1113,5 +1047,4 @@ displayable, and like ‘grave’ otherwise. */);
defsubr (&Sdocumentation_property);
defsubr (&Ssnarf_documentation);
defsubr (&Ssubstitute_command_keys);
defsubr (&Sinternal__text_restyle);
}
......@@ -72,6 +72,7 @@ static Lisp_Object format_time_string (char const *, ptrdiff_t, struct timespec,
static long int tm_gmtoff (struct tm *);
static int tm_diff (struct tm *, struct tm *);
static void update_buffer_properties (ptrdiff_t, ptrdiff_t);
static Lisp_Object styled_format (ptrdiff_t, Lisp_Object *, bool);
#ifndef HAVE_TM_GMTOFF
# define HAVE_TM_GMTOFF false
......@@ -3696,8 +3697,7 @@ usage: (message FORMAT-STRING &rest ARGS) */)
}
else
{
args[0] = Finternal__text_restyle (args[0]);
Lisp_Object val = Fformat (nargs, args);
Lisp_Object val = Fformat_message (nargs, args);
message3 (val);
return val;
}
......@@ -3722,8 +3722,7 @@ usage: (message-box FORMAT-STRING &rest ARGS) */)
}
else
{
args[0] = Finternal__text_restyle (args[0]);
Lisp_Object val = Fformat (nargs, args);
Lisp_Object val = Fformat_message (nargs, args);
Lisp_Object pane, menu;
struct gcpro gcpro1;
......@@ -3822,7 +3821,7 @@ specifiers, as follows:
%<flags><width><precision>character
where flags is [+ #-0q]+, width is [0-9]+, and precision is .[0-9]+
where flags is [+ #-0]+, width is [0-9]+, and precision is .[0-9]+
The + flag character inserts a + before any positive number, while a
space inserts a space before any positive number; these flags only
......@@ -3835,9 +3834,6 @@ The # flag means to use an alternate display form for %o, %x, %X, %e,
for %e, %f, and %g, it causes a decimal point to be included even if
the precision is zero.
The q flag means to quote the printed representation as per
‘text-quoting-style’. E.g., "%qs" is equivalent to "‘%s’".
The width specifier supplies a lower limit for the length of the
printed representation. The padding, if any, normally goes on the
left, but it goes on the right if the - flag is present. The padding
......@@ -3852,6 +3848,31 @@ specifier truncates the string to the given width.
usage: (format STRING &rest OBJECTS) */)
(ptrdiff_t nargs, Lisp_Object *args)
{
return styled_format (nargs, args, false);
}
DEFUN ("format-message", Fformat_message, Sformat_message, 1, MANY, 0,
doc: /* Format a string out of a format-string and arguments.
The first argument is a format control string.
The other arguments are substituted into it to make the result, a string.
This acts like ‘format’, except it also replaces each left single
quotation mark (\\=‘) and grave accent (\\=`) by a left quote, and each
right single quotation mark (\\=’) and apostrophe (\\=') by a right quote.
The left and right quote replacement characters are specified by
‘text-quoting-style’.
usage: (format-message STRING &rest OBJECTS) */)
(ptrdiff_t nargs, Lisp_Object *args)
{
return styled_format (nargs, args, true);
}
/* Implement ‘format-message’ if MESSAGE is true, ‘format’ otherwise. */
static Lisp_Object
styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
{
ptrdiff_t n; /* The number of the next arg to substitute. */
char initial_buffer[4000];
......@@ -3917,7 +3938,8 @@ usage: (format STRING &rest OBJECTS) */)
/* Try to determine whether the result should be multibyte.
This is not always right; sometimes the result needs to be multibyte
because of an object that we will pass through prin1,
because of an object that we will pass through prin1.
or because a grave accent or apostrophe is requoted,
and in that case, we won't know it here. */
multibyte_format = STRING_MULTIBYTE (args[0]);
multibyte = multibyte_format;
......@@ -3925,7 +3947,7 @@ usage: (format STRING &rest OBJECTS) */)
if (STRINGP (args[n]) && STRING_MULTIBYTE (args[n]))
multibyte = 1;
enum text_quoting_style quoting_style = text_quoting_style ();
int quoting_style = message ? text_quoting_style () : -1;
/* If we start out planning a unibyte result,
then discover it has to be multibyte, we jump back to retry. */
......@@ -3945,11 +3967,13 @@ usage: (format STRING &rest OBJECTS) */)
/* The values of N and FORMAT when the loop body is entered. */
ptrdiff_t n0 = n;
char *format0 = format;
char const *convsrc = format;
unsigned char format_char = *format++;
/* Bytes needed to represent the output of this conversion. */
ptrdiff_t convbytes;
ptrdiff_t convbytes = 1;
if (*format == '%')
if (format_char == '%')
{
/* General format specifications look like
......@@ -3974,23 +3998,21 @@ usage: (format STRING &rest OBJECTS) */)
bool space_flag = false;
bool sharp_flag = false;
bool zero_flag = false;
bool quote_flag = false;
ptrdiff_t field_width;
bool precision_given;
uintmax_t precision = UINTMAX_MAX;
char *num_end;
char conversion;
while (1)
for (; ; format++)
{
switch (*++format)
switch (*format)
{
case '-': minus_flag = true; continue;
case '+': plus_flag = true; continue;
case ' ': space_flag = true; continue;
case '#': sharp_flag = true; continue;
case '0': zero_flag = true; continue;
case 'q': quote_flag = true; continue;
}
break;
}
......@@ -4014,11 +4036,10 @@ usage: (format STRING &rest OBJECTS) */)
error ("Format string ends in middle of format specifier");
memset (&discarded[format0 - format_start], 1, format - format0);
conversion = *format;
conversion = *format++;
if (conversion == '%')
goto copy_char;
discarded[format - format_start] = 1;
format++;
++n;
if (! (n < nargs))
......@@ -4118,20 +4139,6 @@ usage: (format STRING &rest OBJECTS) */)
if (convbytes && multibyte && ! STRING_MULTIBYTE (args[n]))
convbytes = count_size_as_multibyte (SDATA (args[n]), nbytes);
if (quote_flag)
{
convbytes += 2;
if (quoting_style == CURVE_QUOTING_STYLE)
{
if (!multibyte)
{
multibyte = true;
goto retry;
}
convbytes += 4;
}
}
padding = width < field_width ? field_width - width : 0;
if (max_bufsize - padding <= convbytes)
......@@ -4139,27 +4146,6 @@ usage: (format STRING &rest OBJECTS) */)
convbytes += padding;
if (convbytes <= buf + bufsize - p)
{
if (quote_flag)
{
switch (quoting_style)
{
case CURVE_QUOTING_STYLE:
memcpy (p, uLSQM, 3);
p += 3;
break;
case GRAVE_QUOTING_STYLE:
*p++ = '`';
break;
case STRAIGHT_QUOTING_STYLE:
*p++ = '\'';
break;
}
nchars++;
}
if (! minus_flag)
{
memset (p, ' ', padding);
......@@ -4189,22 +4175,6 @@ usage: (format STRING &rest OBJECTS) */)
nchars += padding;
}
if (quote_flag)
{
switch (quoting_style)
{
case CURVE_QUOTING_STYLE:
memcpy (p, uRSQM, 3);
p += 3;
break;
default:
*p++ = '\'';
break;
}
nchars++;
}
/* If this argument has text properties, record where
in the result string it appears. */
if (string_intervals (args[n]))
......@@ -4464,44 +4434,72 @@ usage: (format STRING &rest OBJECTS) */)
}
}
else
copy_char:
{
/* Copy a single character from format to buf. */
/* Named constants for the UTF-8 encodings of U+2018 LEFT SINGLE
QUOTATION MARK and U+2019 RIGHT SINGLE QUOTATION MARK. */
enum
{
uLSQM0 = 0xE2, uLSQM1 = 0x80, uLSQM2 = 0x98,
/* uRSQM0 = 0xE2, uRSQM1 = 0x80, */ uRSQM2 = 0x99
};
char *src = format;
unsigned char str[MAX_MULTIBYTE_LENGTH];
if (multibyte_format)
if ((format_char == '`' || format_char == '\'')
&& quoting_style == CURVE_QUOTING_STYLE)
{
/* Copy a whole multibyte character. */
if (p > buf
&& !ASCII_CHAR_P (*((unsigned char *) p - 1))
&& !CHAR_HEAD_P (*format))
maybe_combine_byte = 1;
do
format++;
while (! CHAR_HEAD_P (*format));
convbytes = format - src;
memset (&discarded[src + 1 - format_start], 2, convbytes - 1);
if (! multibyte)
{
multibyte = true;
goto retry;
}
convsrc = format_char == '`' ? uLSQM : uRSQM;
convbytes = 3;
}
else if (format_char == '`' && quoting_style == STRAIGHT_QUOTING_STYLE)
convsrc = "'";
else if (format_char == uLSQM0 && CURVE_QUOTING_STYLE < quoting_style
&& multibyte_format
&& (unsigned char) format[0] == uLSQM1
&& ((unsigned char) format[1] == uLSQM2
|| (unsigned char) format[1] == uRSQM2))
{
convsrc = (((unsigned char) format[1] == uLSQM2
&& quoting_style == GRAVE_QUOTING_STYLE)
? "`" : "'");
format += 2;
memset (&discarded[format0 + 1 - format_start], 2, 2);
}
else
{
unsigned char uc = *format++;
if (! multibyte || ASCII_CHAR_P (uc))
convbytes = 1;
else
/* Copy a single character from format to buf. */
if (multibyte_format)
{
/* Copy a whole multibyte character. */
if (p > buf
&& !ASCII_CHAR_P (*((unsigned char *) p - 1))
&& !CHAR_HEAD_P (format_char))
maybe_combine_byte = 1;
while (! CHAR_HEAD_P (*format))
format++;
convbytes = format - format0;
memset (&discarded[format0 + 1 - format_start], 2,
convbytes - 1);
}
else if (multibyte && !ASCII_CHAR_P (format_char))
{
int c = BYTE8_TO_CHAR (uc);
int c = BYTE8_TO_CHAR (format_char);
convbytes = CHAR_STRING (c, str);
src = (char *) str;
convsrc = (char *) str;
}
}
copy_char:
if (convbytes <= buf + bufsize - p)
{
memcpy (p, src, convbytes);
memcpy (p, convsrc, convbytes);
p += convbytes;
nchars++;
continue;
......@@ -5213,6 +5211,7 @@ functions if all the text being accessed has this property. */);
defsubr (&Smessage_or_box);
defsubr (&Scurrent_message);
defsubr (&Sformat);
defsubr (&Sformat_message);
defsubr (&Sinsert_buffer_substring);
defsubr (&Scompare_buffer_substrings);
......
......@@ -647,7 +647,7 @@ image_error (const char *format, ...)
static void
image_size_error (void)
{
image_error ("Invalid image size (see %qs)", "max-image-size");
image_error ("Invalid image size (see `%s')", "max-image-size");
}
......@@ -2952,13 +2952,13 @@ xbm_load_image (struct frame *f, struct image *img, unsigned char *contents,
if (img->pixmap == NO_PIXMAP)
{
x_clear_image (f, img);
image_error ("Unable to create X pixmap for %qs", img->spec);
image_error ("Unable to create X pixmap for `%s'", img->spec);
}
else
success_p = 1;
}
else
image_error ("Error loading XBM image %qs", img->spec);
image_error ("Error loading XBM image `%s'", img->spec);
return success_p;
}
......@@ -2996,7 +2996,7 @@ xbm_load (struct frame *f, struct image *img)