From c4151ebe15479de4c2e511b068cdf9af6a4576cf Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Fri, 19 Jun 2015 00:35:43 -0700 Subject: [PATCH] Improve the optional translation of quotes MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Fix several problems with the recently-added custom variable help-quote-translation where the code would quote inconsistently in help buffers. Add support for quoting 'like this', which is common in other GNU programs in ASCII environments. Change help-quote-translation to use more mnemonic values: values are now the initial quoting char, e.g., (setq help-quote-translation ?`) gets the traditional Emacs help-buffer quoting style `like this'. Change the default behavior of substitute-command-keys to match what's done in set-locale-environment, i.e., quote ‘like this’ if displayable, 'like this' otherwise. * doc/lispref/help.texi (Keys in Documentation): Document new behavior of substitute-command-keys, and document help-quote-translation. * doc/lispref/tips.texi (Documentation Tips): Mention the effect of help-quote-translation. * etc/NEWS: Mention new behavior of substitute-command-keys, and merge help-quote-translation news into it. When talking about doc strings, mention new ways to type quotes. * lisp/cedet/mode-local.el (overload-docstring-extension): Revert my recent change to this function, which shouldn't be needed as the result is a doc string. * lisp/cedet/mode-local.el (mode-local-print-binding) (mode-local-describe-bindings-2): * lisp/cedet/srecode/srt-mode.el (srecode-macro-help): * lisp/cus-theme.el (describe-theme-1): * lisp/descr-text.el (describe-text-properties-1, describe-char): * lisp/emacs-lisp/cl-generic.el (cl--generic-describe): * lisp/emacs-lisp/eieio-opt.el (eieio-help-class) (eieio-help-constructor): * lisp/emacs-lisp/package.el (describe-package-1): * lisp/faces.el (describe-face): * lisp/help-fns.el (help-fns--key-bindings) (help-fns--compiler-macro, help-fns--parent-mode) (help-fns--obsolete, help-fns--interactive-only) (describe-function-1, describe-variable): * lisp/help.el (describe-mode): Use substitute-command-keys to ensure a more-consistent quoting style in help buffers. * lisp/cus-start.el (standard): Document new help-quote-translation behavior. * lisp/emacs-lisp/lisp-mode.el (lisp-fdefs): * lisp/help-mode.el (help-xref-symbol-regexp, help-xref-info-regexp) (help-xref-url-regexp): * lisp/international/mule-cmds.el (help-xref-mule-regexp-template): * lisp/wid-edit.el (widget-documentation-link-regexp): Also match 'foo', in case we're in a help buffer generated when help-quote-translation is ?'. * src/doc.c: Include disptab.h, for DISP_CHAR_VECTOR. (LEFT_SINGLE_QUOTATION_MARK, uLSQM0, uLSQM1, uLSQM2, uRSQM0) (uRSQM1, uRSQM2, LSQM, RSQM): New constants. (Fsubstitute_command_keys): Document and implement new behavior. (Vhelp_quote_translation): Document new behavior. --- doc/lispref/help.texi | 29 ++++++++++- doc/lispref/tips.texi | 8 +-- etc/NEWS | 23 +++++---- lisp/cedet/mode-local.el | 18 +++---- lisp/cedet/srecode/srt-mode.el | 4 +- lisp/cus-start.el | 7 +-- lisp/cus-theme.el | 4 +- lisp/descr-text.el | 12 +++-- lisp/emacs-lisp/cl-generic.el | 4 +- lisp/emacs-lisp/eieio-opt.el | 20 ++++---- lisp/emacs-lisp/lisp-mode.el | 8 +-- lisp/emacs-lisp/package.el | 7 +-- lisp/faces.el | 13 +++-- lisp/help-fns.el | 71 ++++++++++++++++---------- lisp/help-mode.el | 6 +-- lisp/help.el | 8 +-- lisp/international/mule-cmds.el | 2 +- lisp/wid-edit.el | 2 +- src/doc.c | 89 ++++++++++++++++++++++----------- 19 files changed, 213 insertions(+), 122 deletions(-) diff --git a/doc/lispref/help.texi b/doc/lispref/help.texi index ce29f3f5bc3..44a680c4a22 100644 --- a/doc/lispref/help.texi +++ b/doc/lispref/help.texi @@ -319,10 +319,22 @@ specifies @var{mapvar}'s value as the keymap for any following @samp{\[@var{command}]} sequences in this documentation string. @item ` -(grave accent) stands for a left single quotation mark (@samp{‘}). +(grave accent) stands for a left quote, and alters the interpretation +of the next unmatched apostrophe. @item ' -(apostrophe) stands for a right single quotation mark (@samp{’}) if +(apostrophe) stands for a right quote if preceded by grave accent and +there are no intervening apostrophes. Otherwise, apostrophe stands +for itself. + +@item ‘ +(left single quotation mark) stands for a left quote. + +@item ’ +(right single quotation mark) stands for a right quote. + +@item ' +(apostrophe) stands for a right quote if preceded by grave accent and there are no intervening apostrophes. Otherwise, apostrophe stands for itself. @@ -335,6 +347,19 @@ and @samp{\=\=} puts @samp{\=} into the output. @strong{Please note:} Each @samp{\} must be doubled when written in a string in Emacs Lisp. +@defvar help-quote-translation +@cindex curved quotes +The value of this variable specifies the style +@code{substitute-command-keys} uses when generating left and right +quotes. If the variable's value is @code{?‘} (U+2018 LEFT SINGLE +QUOTATION MARK), the style is @t{‘like this’} with curved single +quotes. If the value is @code{?'} (apostrophe), the style is @t{'like +this'} with apostrophes. If the value is @code{?`} (grave accent), +the style is @t{`like this'} with grave accent and apostrophe. The +default value @code{nil} means to use curved single quotes if +displayable and apostrophes otherwise. +@end defvar + @defun substitute-command-keys string This function scans @var{string} for the above special sequences and replaces them by what they stand for, returning the result as a string. diff --git a/doc/lispref/tips.texi b/doc/lispref/tips.texi index 9144497d5f1..7107bb45872 100644 --- a/doc/lispref/tips.texi +++ b/doc/lispref/tips.texi @@ -671,9 +671,11 @@ Documentation strings can also use an older single-quoting convention, which quotes symbols with grave accent @t{`} and apostrophe @t{'}: @t{`like-this'} rather than @t{‘like-this’}. This older convention was designed for now-obsolete displays in which grave -accent and apostrophe were mirror images. Documentation in this older -convention is converted to the standard convention when it is copied -into a help buffer. @xref{Keys in Documentation}. +accent and apostrophe were mirror images. + +Documentation using either convention is converted to the user's +preferred format when it is copied into a help buffer. @xref{Keys in +Documentation}. @cindex hyperlinks in documentation strings Help mode automatically creates a hyperlink when a documentation string diff --git a/etc/NEWS b/etc/NEWS index 1611c7a7926..bab1b41e759 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -845,11 +845,16 @@ when signaling a file error. For example, it now reports "Permission denied" instead of "permission denied". The old behavior was problematic in languages like German where downcasing rules depend on grammar. -** (substitute-command-keys "`foo'") now returns "‘foo’". -That is, it replaces grave accents by left single quotation marks, and -apostrophes that match grave accents by right single quotation marks. -As before, isolated apostrophes and characters preceded by \= are -output as-is. +** substitute-command-keys now replaces quotes. +That is, it replaces left single quotation marks (‘) by left quotes +and right single quotation marks (’) by right quotes. It also +replaces grave accents by left quotes, and apostrophes that match +grave accents by right quotes. As before, isolated apostrophes and +characters preceded by \= are output as-is. Left and right quotes are +determined by new custom variable ‘help-quote-translation’. ?‘ means +quote ‘like this’, ?' means quote 'like this', ?` means quote `like +this', and nil (default) means quote ‘like this’ if displayable and +'like this' otherwise. +++ ** The character classes [:alpha:] and [:alnum:] in regular expressions @@ -956,10 +961,10 @@ directory at point. ** Documentation strings now support quoting with curved single quotes ‘like-this’ in addition to the old style with grave accent and apostrophe `like-this'. The new style looks better on today's displays. -When an old-style string is copied to a help buffer it is converted to -the new style. - -** New option `help-quote-translation'. +In the new Electric Quote mode, you can enter curved single quotes +into documentation by typing ` and '. Outside Electric Quote mode, +you can enter them by typing ‘C-x 8 [’ and ‘C-x 8 ]’, or (if your Alt +key works) by typing ‘A-[’ and ‘A-]’. +++ ** Time-related changes: diff --git a/lisp/cedet/mode-local.el b/lisp/cedet/mode-local.el index 35363337aee..3bdc3ea6155 100644 --- a/lisp/cedet/mode-local.el +++ b/lisp/cedet/mode-local.el @@ -598,16 +598,15 @@ PROMPT, INITIAL, HIST, and DEFAULT are the same as for `completing-read'." (defun overload-docstring-extension (overload) "Return the doc string that augments the description of OVERLOAD." (let ((doc "\n\This function can be overloaded\ - with ‘define-mode-local-override’.") + with `define-mode-local-override'.") (sym (overload-obsoleted-by overload))) (when sym - (setq doc (format "%s\nIt has made the overload ‘%s’ obsolete since %s." + (setq doc (format "%s\nIt has made the overload `%s' obsolete since %s." doc sym (get sym 'overload-obsoleted-since)))) (setq sym (overload-that-obsolete overload)) (when sym - (setq doc (format - "%s\nThis overload is obsolete since %s;\nuse ‘%s’ instead." - doc (get overload 'overload-obsoleted-since) sym))) + (setq doc (format "%s\nThis overload is obsolete since %s;\nUse `%s' instead." + doc (get overload 'overload-obsoleted-since) sym))) doc)) (defun mode-local-augment-function-help (symbol) @@ -630,9 +629,10 @@ SYMBOL is a function that can be overridden." (defun mode-local-print-binding (symbol) "Print the SYMBOL binding." (let ((value (symbol-value symbol))) - (princ (format "\n ‘%s’ value is\n " symbol)) + (princ (format (substitute-command-keys "\n ‘%s’ value is\n ") + symbol)) (if (and value (symbolp value)) - (princ (format "‘%s’" value)) + (princ (format (substitute-command-keys "‘%s’") value)) (let ((pt (point))) (pp value) (save-excursion @@ -690,7 +690,7 @@ SYMBOL is a function that can be overridden." ) ((symbolp buffer-or-mode) (setq mode buffer-or-mode) - (princ (format "‘%s’\n" buffer-or-mode)) + (princ (format (substitute-command-keys "‘%s’\n") buffer-or-mode)) ) ((signal 'wrong-type-argument (list 'buffer-or-mode buffer-or-mode)))) @@ -700,7 +700,7 @@ SYMBOL is a function that can be overridden." (while mode (setq table (get mode 'mode-local-symbol-table)) (when table - (princ (format "\n- From ‘%s’\n" mode)) + (princ (format (substitute-command-keys "\n- From ‘%s’\n") mode)) (mode-local-print-bindings table)) (setq mode (get-mode-local-parent mode))))) diff --git a/lisp/cedet/srecode/srt-mode.el b/lisp/cedet/srecode/srt-mode.el index fbc56357b1c..48f055561ea 100644 --- a/lisp/cedet/srecode/srt-mode.el +++ b/lisp/cedet/srecode/srt-mode.el @@ -258,9 +258,9 @@ we can tell font lock about them.") (when (class-abstract-p C) (throw 'skip nil)) - (princ "‘") + (princ (substitute-command-keys "‘")) (princ name) - (princ "’") + (princ (substitute-command-keys "’")) (when (slot-exists-p C 'key) (when key (princ " - Character Key: ") diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 12def4c4148..7cf5ce736bd 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -223,9 +223,10 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of ;; doc.c (help-quote-translation help (choice - (const :tag "No translation" nil) - (const :tag "Translate curly single quotes to ASCII" traditional) - (const :tag "Translate ASCII single quotes to curly" prefer-unicode)) + (character :tag "Quote ‘like this’" :value ?‘) + (character :tag "Quote 'like this'" :value ?\') + (character :tag "Quote `like this'" :value ?\`) + (const :tag "Quote ‘like this’ if displyable, 'like this' otherwise" nil)) "25.1") ;; dosfns.c (dos-display-scancodes display boolean) diff --git a/lisp/cus-theme.el b/lisp/cus-theme.el index 1321fbc4b7e..bc221e17c4b 100644 --- a/lisp/cus-theme.el +++ b/lisp/cus-theme.el @@ -492,10 +492,10 @@ It includes all faces in list FACES." '("" "c"))) doc) (when fn - (princ " in ‘") + (princ (substitute-command-keys " in ‘")) (help-insert-xref-button (file-name-nondirectory fn) 'help-theme-def fn) - (princ "’")) + (princ (substitute-command-keys "’"))) (princ ".\n") (if (custom-theme-p theme) (progn diff --git a/lisp/descr-text.el b/lisp/descr-text.el index c8641aed8bb..a0b9ddfe2c9 100644 --- a/lisp/descr-text.el +++ b/lisp/descr-text.el @@ -161,8 +161,11 @@ otherwise." ;; Buttons (when (and button (not (widgetp wid-button))) (newline) - (insert "Here is a ‘" (format "%S" button-type) - "’ button labeled ‘" button-label "’.\n\n")) + (insert (substitute-command-keys "Here is a ‘") + (format "%S" button-type) + (substitute-command-keys "’ button labeled ‘") + button-label + (substitute-command-keys "’.\n\n"))) ;; Overlays (when overlays (newline) @@ -738,8 +741,9 @@ relevant to POS." (when face (insert (propertize " " 'display '(space :align-to 5)) "face: ") - (insert (concat "‘" (symbol-name face) "’")) - (insert "\n"))))) + (insert (substitute-command-keys "‘") + (symbol-name face) + (substitute-command-keys "’\n")))))) (insert "these terminal codes:\n") (dotimes (i (length disp-vector)) (insert (car (aref disp-vector i)) diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el index 96b86aa21cc..5923e4db996 100644 --- a/lisp/emacs-lisp/cl-generic.el +++ b/lisp/emacs-lisp/cl-generic.el @@ -876,11 +876,11 @@ Can only be used from within the lexical body of a primary or around method." (cl--generic-method-specializers method))) (file (find-lisp-object-file-name met-name 'cl-defmethod))) (when file - (insert " in ‘") + (insert (substitute-command-keys " in ‘")) (help-insert-xref-button (help-fns-short-filename file) 'help-function-def met-name file 'cl-defmethod) - (insert "’.\n"))) + (insert (substitute-command-keys "’.\n")))) (insert "\n" (or (nth 2 info) "Undocumented") "\n\n"))))))) ;;; Support for (head ) specializers. diff --git a/lisp/emacs-lisp/eieio-opt.el b/lisp/emacs-lisp/eieio-opt.el index 11d99849a97..6cd6813956a 100644 --- a/lisp/emacs-lisp/eieio-opt.el +++ b/lisp/emacs-lisp/eieio-opt.el @@ -90,11 +90,11 @@ If CLASS is actually an object, then also display current values of that object. " class") (let ((location (find-lisp-object-file-name class 'eieio-defclass))) (when location - (insert " in ‘") + (insert (substitute-command-keys " in ‘")) (help-insert-xref-button (help-fns-short-filename location) 'eieio-class-def class location 'eieio-defclass) - (insert "’"))) + (insert (substitute-command-keys "’")))) (insert ".\n") ;; Parents (let ((pl (eieio-class-parents class)) @@ -103,10 +103,10 @@ If CLASS is actually an object, then also display current values of that object. (insert " Inherits from ") (while (setq cur (pop pl)) (setq cur (eieio--class-name cur)) - (insert "‘") + (insert (substitute-command-keys "‘")) (help-insert-xref-button (symbol-name cur) 'help-function cur) - (insert (if pl "’, " "’"))) + (insert (substitute-command-keys (if pl "’, " "’")))) (insert ".\n"))) ;; Children (let ((ch (eieio-class-children class)) @@ -114,10 +114,10 @@ If CLASS is actually an object, then also display current values of that object. (when ch (insert " Children ") (while (setq cur (pop ch)) - (insert "‘") + (insert (substitute-command-keys "‘")) (help-insert-xref-button (symbol-name cur) 'help-function cur) - (insert (if ch "’, " "’"))) + (insert (substitute-command-keys (if ch "’, " "’")))) (insert ".\n"))) ;; System documentation (let ((doc (documentation-property class 'variable-documentation))) @@ -130,9 +130,9 @@ If CLASS is actually an object, then also display current values of that object. (when generics (insert (propertize "Specialized Methods:\n\n" 'face 'bold)) (dolist (generic generics) - (insert "‘") + (insert (substitute-command-keys "‘")) (help-insert-xref-button (symbol-name generic) 'help-function generic) - (insert "’") + (insert (substitute-command-keys "’")) (pcase-dolist (`(,qualifiers ,args ,doc) (eieio-method-documentation generic class)) (insert (format " %s%S\n" qualifiers args) @@ -245,11 +245,11 @@ are not abstract." (setq location (find-lisp-object-file-name ctr def))) (when location - (insert " in ‘") + (insert (substitute-command-keys " in ‘")) (help-insert-xref-button (help-fns-short-filename location) 'eieio-class-def ctr location 'eieio-defclass) - (insert "’")) + (insert (substitute-command-keys "’"))) (insert ".\nCreates an object of class " (symbol-name ctr) ".") (goto-char (point-max)) (if (autoloadp def) diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index ab01a109b7a..72a23cfdfc6 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el @@ -403,8 +403,8 @@ ;; Words inside \\[] tend to be for `substitute-command-keys'. ("\\\\\\\\\\[\\(\\(?:\\sw\\|\\s_\\)+\\)\\]" (1 font-lock-constant-face prepend)) - ;; Words inside ‘’ and `' tend to be symbol names. - ("[`‘]\\(\\(?:\\sw\\|\\s_\\)\\(?:\\sw\\|\\s_\\)+\\)['’]" + ;; Words inside ‘’ and '' and `' tend to be symbol names. + ("['`‘]\\(\\(?:\\sw\\|\\s_\\)\\(?:\\sw\\|\\s_\\)+\\)['’]" (1 font-lock-constant-face prepend)) ;; Constant values. ("\\_<:\\(?:\\sw\\|\\s_\\)+\\_>" 0 font-lock-builtin-face) @@ -452,8 +452,8 @@ ;; Erroneous structures. (,(concat "(" cl-errs-re "\\_>") (1 font-lock-warning-face)) - ;; Words inside ‘’ and `' tend to be symbol names. - ("[`‘]\\(\\(?:\\sw\\|\\s_\\)\\(?:\\sw\\|\\s_\\)+\\)['’]" + ;; Words inside ‘’ and '' and `' tend to be symbol names. + ("['`‘]\\(\\(?:\\sw\\|\\s_\\)\\(?:\\sw\\|\\s_\\)+\\)['’]" (1 font-lock-constant-face prepend)) ;; Constant values. ("\\_<:\\(?:\\sw\\|\\s_\\)+\\_>" 0 font-lock-builtin-face) diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el index 074d3e8dda3..62900e08360 100644 --- a/lisp/emacs-lisp/package.el +++ b/lisp/emacs-lisp/package.el @@ -2173,17 +2173,18 @@ will be deleted." "Installed" (capitalize status)) ;FIXME: Why comment-face? 'font-lock-face 'font-lock-comment-face)) - (insert " in ‘") + (insert (substitute-command-keys " in ‘")) ;; Todo: Add button for uninstalling. (help-insert-xref-button (abbreviate-file-name (file-name-as-directory pkg-dir)) 'help-package-def pkg-dir) (if (and (package-built-in-p name) (not (package-built-in-p name version))) - (insert "’,\n shadowing a " + (insert (substitute-command-keys + "’,\n shadowing a ") (propertize "built-in package" 'font-lock-face 'font-lock-builtin-face)) - (insert "’")) + (insert (substitute-command-keys "’"))) (if signed (insert ".") (insert " (unsigned).")) diff --git a/lisp/faces.el b/lisp/faces.el index 4366c0b9f23..ac6486ee46c 100644 --- a/lisp/faces.el +++ b/lisp/faces.el @@ -1428,10 +1428,12 @@ If FRAME is omitted or nil, use the selected frame." (when alias (setq face alias) (insert - (format "\n %s is an alias for the face ‘%s’.\n%s" + (format (substitute-command-keys + "\n %s is an alias for the face ‘%s’.\n%s") f alias (if (setq obsolete (get f 'obsolete-face)) - (format " This face is obsolete%s; use ‘%s’ instead.\n" + (format (substitute-command-keys + " This face is obsolete%s; use ‘%s’ instead.\n") (if (stringp obsolete) (format " since %s" obsolete) "") @@ -1449,12 +1451,13 @@ If FRAME is omitted or nil, use the selected frame." (help-xref-button 1 'help-customize-face f))) (setq file-name (find-lisp-object-file-name f 'defface)) (when file-name - (princ "Defined in ‘") + (princ (substitute-command-keys "Defined in ‘")) (princ (file-name-nondirectory file-name)) - (princ "’") + (princ (substitute-command-keys "’")) ;; Make a hyperlink to the library. (save-excursion - (re-search-backward "‘\\([^‘’]+\\)’" nil t) + (re-search-backward + (substitute-command-keys "‘\\([^‘’]+\\)’") nil t) (help-xref-button 1 'help-face-def f file-name)) (princ ".") (terpri) diff --git a/lisp/help-fns.el b/lisp/help-fns.el index 80f30e88fa4..9541d4797b4 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -306,7 +306,9 @@ suitable file is found, return nil." (when remapped (princ "Its keys are remapped to ") (princ (if (symbolp remapped) - (concat "‘" (symbol-name remapped) "’") + (concat (substitute-command-keys "‘") + (symbol-name remapped) + (substitute-command-keys "’")) "an anonymous command")) (princ ".\n")) @@ -340,16 +342,18 @@ suitable file is found, return nil." (insert "\nThis function has a compiler macro") (if (symbolp handler) (progn - (insert (format " ‘%s’" handler)) + (insert (format (substitute-command-keys " ‘%s’") handler)) (save-excursion - (re-search-backward "‘\\([^‘’]+\\)’" nil t) + (re-search-backward (substitute-command-keys "‘\\([^‘’]+\\)’") + nil t) (help-xref-button 1 'help-function handler))) ;; FIXME: Obsolete since 24.4. (let ((lib (get function 'compiler-macro-file))) (when (stringp lib) - (insert (format " in ‘%s’" lib)) + (insert (format (substitute-command-keys " in ‘%s’") lib)) (save-excursion - (re-search-backward "‘\\([^‘’]+\\)’" nil t) + (re-search-backward (substitute-command-keys "‘\\([^‘’]+\\)’") + nil t) (help-xref-button 1 'help-function-cmacro function lib))))) (insert ".\n")))) @@ -404,13 +408,13 @@ suitable file is found, return nil." (get function 'derived-mode-parent)))) (when parent-mode - (insert "\nParent mode: ‘") + (insert (substitute-command-keys "\nParent mode: ‘")) (let ((beg (point))) (insert (format "%s" parent-mode)) (make-text-button beg (point) 'type 'help-function 'help-args (list parent-mode))) - (insert "’.\n")))) + (insert (substitute-command-keys "’.\n"))))) (defun help-fns--obsolete (function) ;; Ignore lambda constructs, keyboard macros, etc. @@ -426,7 +430,9 @@ suitable file is found, return nil." (when (nth 2 obsolete) (insert (format " since %s" (nth 2 obsolete)))) (insert (cond ((stringp use) (concat ";\n" use)) - (use (format ";\nuse ‘%s’ instead." use)) + (use (format (substitute-command-keys + ";\nuse ‘%s’ instead.") + use)) (t ".")) "\n")))) @@ -462,7 +468,8 @@ FILE is the file where FUNCTION was probably defined." (format ";\nin Lisp code %s" interactive-only)) ((and (symbolp 'interactive-only) (not (eq interactive-only t))) - (format ";\nin Lisp code use ‘%s’ instead." + (format (substitute-command-keys + ";\nin Lisp code use ‘%s’ instead.") interactive-only)) (t ".")) "\n"))))) @@ -531,7 +538,8 @@ FILE is the file where FUNCTION was probably defined." ;; Aliases are Lisp functions, so we need to check ;; aliases before functions. (aliased - (format "an alias for ‘%s’" real-def)) + (format (substitute-command-keys "an alias for ‘%s’") + real-def)) ((autoloadp def) (format "%s autoloaded %s" (if (commandp def) "an interactive" "an") @@ -565,21 +573,24 @@ FILE is the file where FUNCTION was probably defined." (with-current-buffer standard-output (save-excursion (save-match-data - (when (re-search-backward "alias for ‘\\([^‘’]+\\)’" nil t) + (when (re-search-backward (substitute-command-keys + "alias for ‘\\([^‘’]+\\)’") + nil t) (help-xref-button 1 'help-function real-def))))) (when file-name - (princ " in ‘") + (princ (substitute-command-keys " in ‘")) ;; We used to add .el to the file name, ;; but that's completely wrong when the user used load-file. (princ (if (eq file-name 'C-source) "C source code" (help-fns-short-filename file-name))) - (princ "’") + (princ (substitute-command-keys "’")) ;; Make a hyperlink to the library. (with-current-buffer standard-output (save-excursion - (re-search-backward "‘\\([^‘’]+\\)’" nil t) + (re-search-backward (substitute-command-keys "‘\\([^‘’]+\\)’") + nil t) (help-xref-button 1 'help-function-def function file-name)))) (princ ".") (with-current-buffer (help-buffer) @@ -712,14 +723,17 @@ it is displayed along with the global value." (if file-name (progn - (princ " is a variable defined in ‘") + (princ (substitute-command-keys + " is a variable defined in ‘")) (princ (if (eq file-name 'C-source) "C source code" (file-name-nondirectory file-name))) - (princ "’.\n") + (princ (substitute-command-keys "’.\n")) (with-current-buffer standard-output (save-excursion - (re-search-backward "‘\\([^‘’]+\\)’" nil t) + (re-search-backward (substitute-command-keys + "‘\\([^‘’]+\\)’") + nil t) (help-xref-button 1 'help-variable-def variable file-name))) (if valvoid @@ -849,7 +863,8 @@ if it is given a local binding.\n"))) ;; Mention if it's an alias. (unless (eq alias variable) (setq extra-line t) - (princ (format " This variable is an alias for ‘%s’.\n" + (princ (format (substitute-command-keys + " This variable is an alias for ‘%s’.\n") alias))) (when obsolete @@ -858,7 +873,8 @@ if it is given a local binding.\n"))) (if (nth 2 obsolete) (princ (format " since %s" (nth 2 obsolete)))) (princ (cond ((stringp use) (concat ";\n " use)) - (use (format ";\n use ‘%s’ instead." + (use (format (substitute-command-keys + ";\n use ‘%s’ instead.") (car obsolete))) (t "."))) (terpri)) @@ -889,14 +905,15 @@ if it is given a local binding.\n"))) ;; Otherwise, assume it was set directly. (setq file (car file) dir-file nil))) - (princ (if dir-file - "by the file\n ‘" - "for the directory\n ‘")) + (princ (substitute-command-keys + (if dir-file + "by the file\n ‘" + "for the directory\n ‘"))) (with-current-buffer standard-output (insert-text-button file 'type 'help-dir-local-var-def 'help-args (list variable file))) - (princ "’.\n"))) + (princ (substitute-command-keys "’.\n")))) (princ " This variable's value is file-local.\n"))) (when (memq variable ignored-local-variables) @@ -910,8 +927,9 @@ variable.\n")) (princ " This variable may be risky if used as a \ file-local variable.\n") (when (assq variable safe-local-variable-values) - (princ " However, you have added it to \ -‘safe-local-variable-values’.\n"))) + (princ (substitute-command-keys + " However, you have added it to \ +‘safe-local-variable-values’.\n")))) (when safe-var (setq extra-line t) @@ -919,7 +937,8 @@ file-local variable.\n") (princ "if its value\n satisfies the predicate ") (princ (if (byte-code-function-p safe-var) "which is a byte-compiled expression.\n" - (format "‘%s’.\n" safe-var)))) + (format (substitute-command-keys "‘%s’.\n") + safe-var)))) (if extra-line (terpri)) (princ "Documentation:\n") diff --git a/lisp/help-mode.el b/lisp/help-mode.el index 3fc0ad2b403..6454eed27bd 100644 --- a/lisp/help-mode.el +++ b/lisp/help-mode.el @@ -322,7 +322,7 @@ Commands: "\\(source \\(?:code \\)?\\(?:of\\|for\\)\\)\\)" "[ \t\n]+\\)?" ;; Note starting with word-syntax character: - "[`‘]\\(\\sw\\(\\sw\\|\\s_\\)+\\)['’]")) + "['`‘]\\(\\sw\\(\\sw\\|\\s_\\)+\\)['’]")) "Regexp matching doc string references to symbols. The words preceding the quoted symbol can be used in doc strings to @@ -338,11 +338,11 @@ when help commands related to multilingual environment (e.g., (defconst help-xref-info-regexp (purecopy - "\\<[Ii]nfo[ \t\n]+\\(node\\|anchor\\)[ \t\n]+[`‘]\\([^'’]+\\)['’]") + "\\<[Ii]nfo[ \t\n]+\\(node\\|anchor\\)[ \t\n]+['`‘]\\([^'’]+\\)['’]") "Regexp matching doc string references to an Info node.") (defconst help-xref-url-regexp - (purecopy "\\<[Uu][Rr][Ll][ \t\n]+[`‘]\\([^'’]+\\)['’]") + (purecopy "\\<[Uu][Rr][Ll][ \t\n]+['`‘]\\([^'’]+\\)['’]") "Regexp matching doc string references to a URL.") ;;;###autoload diff --git a/lisp/help.el b/lisp/help.el index 2bf53c09b05..7a3460c1b3d 100644 --- a/lisp/help.el +++ b/lisp/help.el @@ -964,12 +964,14 @@ documentation for the major and minor modes of that buffer." (let* ((mode major-mode) (file-name (find-lisp-object-file-name mode nil))) (when file-name - (princ (concat " defined in ‘" (file-name-nondirectory file-name) - "’")) + (princ (concat (substitute-command-keys " defined in ‘") + (file-name-nondirectory file-name) + (substitute-command-keys "’"))) ;; Make a hyperlink to the library. (with-current-buffer standard-output (save-excursion - (re-search-backward "‘\\([^‘’]+\\)’" nil t) + (re-search-backward (substitute-command-keys "‘\\([^‘’]+\\)’") + nil t) (help-xref-button 1 'help-function-def mode file-name))))) (princ ":\n") (princ (documentation major-mode))))) diff --git a/lisp/international/mule-cmds.el b/lisp/international/mule-cmds.el index e56fcebfc26..16c1003960a 100644 --- a/lisp/international/mule-cmds.el +++ b/lisp/international/mule-cmds.el @@ -177,7 +177,7 @@ "\\(charset\\)" "\\)\\s-+\\)?" ;; Note starting with word-syntax character: - "[`‘]\\(\\sw\\(\\sw\\|\\s_\\)+\\)['’]"))) + "['`‘]\\(\\sw\\(\\sw\\|\\s_\\)+\\)['’]"))) (defun coding-system-change-eol-conversion (coding-system eol-type) "Return a coding system which differs from CODING-SYSTEM in EOL conversion. diff --git a/lisp/wid-edit.el b/lisp/wid-edit.el index 295e2aada2a..f7d8964c9fc 100644 --- a/lisp/wid-edit.el +++ b/lisp/wid-edit.el @@ -2863,7 +2863,7 @@ The following properties have special meanings for this widget: :type 'boolean :group 'widget-documentation) -(defcustom widget-documentation-link-regexp "[`‘]\\([^\n `'‘’]+\\)['’]" +(defcustom widget-documentation-link-regexp "['`‘]\\([^\n `'‘’]+\\)['’]" "Regexp for matching potential links in documentation strings. The first group should be the link itself." :type 'regexp diff --git a/src/doc.c b/src/doc.c index 81b1354668f..2ea416f3a2b 100644 --- a/src/doc.c +++ b/src/doc.c @@ -32,6 +32,7 @@ along with GNU Emacs. If not, see . */ #include "lisp.h" #include "character.h" #include "buffer.h" +#include "disptab.h" #include "keyboard.h" #include "keymap.h" @@ -683,6 +684,18 @@ the same file name is found in the `doc-directory'. */) return unbind_to (count, Qnil); } +/* Declare named constants for U+2018 LEFT SINGLE QUOTATION MARK and + U+2019 RIGHT SINGLE QUOTATION MARK, which have UTF-8 encodings + "\xE2\x80\x98" and "\xE2\x80\x99", respectively. */ +enum + { + LEFT_SINGLE_QUOTATION_MARK = 0x2018, + uLSQM0 = 0xE2, uLSQM1 = 0x80, uLSQM2 = 0x98, + uRSQM0 = 0xE2, uRSQM1 = 0x80, uRSQM2 = 0x99, + }; +static unsigned char const LSQM[] = { uLSQM0, uLSQM1, uLSQM2 }; +static unsigned char const RSQM[] = { uRSQM0, uRSQM1, uRSQM2 }; + DEFUN ("substitute-command-keys", Fsubstitute_command_keys, Ssubstitute_command_keys, 1, 1, 0, doc: /* Substitute key descriptions for command names in STRING. @@ -699,8 +712,10 @@ summary). Each substring of the form \\=\\ specifies the use of MAPVAR as the keymap for future \\=\\[COMMAND] substrings. -Each \\=` is replaced by ‘. Each ' preceded by \\=` and without -intervening ' is replaced by ’. +Each \\=‘ and \\=’ are replaced by left and right quote. Each \\=` is +replaced by left quote, and each ' preceded by \\=` and without +intervening ' is replaced by right quote. Left and right quote +characters are specified by ‘help-quote-translation’. \\=\\= quotes the following character and is discarded; thus, \\=\\=\\=\\= puts \\=\\= into the output, \\=\\=\\=\\[ puts \\=\\[ into the output, and @@ -719,7 +734,7 @@ Otherwise, return a new string. */) ptrdiff_t bsize; Lisp_Object tem; Lisp_Object keymap; - unsigned char *start; + unsigned char const *start; ptrdiff_t length, length_byte; Lisp_Object name; struct gcpro gcpro1, gcpro2, gcpro3, gcpro4; @@ -735,6 +750,21 @@ Otherwise, return a new string. */) name = Qnil; GCPRO4 (string, tem, keymap, name); + enum { unicode, grave_accent, apostrophe } quote_translation = unicode; + if (EQ (Vhelp_quote_translation, make_number ('`'))) + quote_translation = grave_accent; + else if (EQ (Vhelp_quote_translation, make_number ('\''))) + quote_translation = apostrophe; + else if (NILP (Vhelp_quote_translation) + && DISP_TABLE_P (Vstandard_display_table)) + { + Lisp_Object dv = DISP_CHAR_VECTOR (XCHAR_TABLE (Vstandard_display_table), + LEFT_SINGLE_QUOTATION_MARK); + if (VECTORP (dv) && ASIZE (dv) == 1 + && EQ (AREF (dv, 0), make_number ('\''))) + quote_translation = apostrophe; + } + multibyte = STRING_MULTIBYTE (string); nchars = 0; @@ -932,38 +962,39 @@ Otherwise, return a new string. */) strp = SDATA (string) + idx; } } - else if (EQ (Vhelp_quote_translation, Qprefer_unicode) - && (strp[0] == '`')) + else if (strp[0] == '`' && quote_translation == unicode) { in_quote = true; - start = (unsigned char *) "\xE2\x80\x98"; /* ‘ */ + start = LSQM; subst_quote: length = 1; length_byte = 3; idx = strp - SDATA (string) + 1; goto subst; } - else if (EQ (Vhelp_quote_translation, Qprefer_unicode) - && (strp[0] == '\'' && in_quote)) + else if (strp[0] == '`' && quote_translation == apostrophe) + { + *bufp++ = '\''; + strp++; + nchars++; + changed = true; + } + else if (strp[0] == '\'' && in_quote) { in_quote = false; - start = (unsigned char *) "\xE2\x80\x99"; /* ’ */ + start = RSQM; goto subst_quote; } - - else if (EQ (Vhelp_quote_translation, Qtraditional) - && (strp[0] == 0xE2) - && (strp[1] == 0x80) - && ((strp[2] == 0x98) /* curly opening quote */ - || (strp[2] == 0x99))) /* curly closing quote */ + else if (strp[0] == uLSQM0 && strp[1] == uLSQM1 + && (strp[2] == uLSQM2 || strp[2] == uRSQM2) + && quote_translation != unicode) { - start = (strp[2] == 0x98) ? "`" : "'"; - length = 1; - length_byte = 1; - idx = strp - SDATA (string) + 3; - goto subst; + *bufp++ = (strp[2] == uLSQM2 && quote_translation == grave_accent + ? '`' : '\''); + strp += 3; + nchars++; + changed = true; } - else if (! multibyte) /* just copy other chars */ *bufp++ = *strp++, nchars++; else @@ -1005,15 +1036,13 @@ syms_of_doc (void) Vbuild_files = Qnil; DEFVAR_LISP ("help-quote-translation", Vhelp_quote_translation, - doc: /* How to translate quotes for display in *Help*. -If the value is nil (default), no translation is done. -If it's the symbol `traditional', any occurrences of the curly quotes -are translated to their ASCII "equivalents", GRAVE and APOSTROPHE. -If it's the symbol `prefer-unicode', any matched pairs of GRAVE and -APOSTROPHE will get translated into the "equivalent" curly quotes. - -Note that any translation done is done in a fresh copy of the doc -string, and doesn't overwrite the original characters. */); + doc: /* Style to use for single quotes in help. +The value is a left single quote character of some style. +Quote \\=‘like this\\=’ if the value is ?\\=‘ (left single quotation mark). +Quote 'like this' if the value is ?' (apostrophe). +Quote \\=`like this' if the value is ?\\=` (grave accent). +The default value is nil, which means quote with left single quotation mark +if displayable, and with apostrophe otherwise. */); Vhelp_quote_translation = Qnil; defsubr (&Sdocumentation); -- 2.39.2