From d5b0eb78731df41a9f20ebf4f3c2d15e8a3d22d8 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Sat, 16 Mar 2019 16:39:24 +0000 Subject: Don't interpret "`foo'" as a symbol reference This requires `foo' to be handled as part of the loop in `helpful--format-command-keys' rather than a separate function. Helps with #197 --- helpful.el | 139 +++++++++++++++++++++++++--------------------- test/helpful-unit-test.el | 16 ++++-- 2 files changed, 87 insertions(+), 68 deletions(-) diff --git a/helpful.el b/helpful.el index 64200df..3907861 100644 --- a/helpful.el +++ b/helpful.el @@ -757,51 +757,34 @@ blank line afterwards." (-cons* first-line "" (cdr lines))) docstring))) -(defun helpful--propertize-keywords (docstring) - "Propertize quoted keywords in docstrings." - (replace-regexp-in-string - ;; Replace all text of the form `foo'. - (rx "`" - (group ":" - symbol-start - (+? (or (syntax word) (syntax symbol))) - symbol-end) - "'") - (lambda (it) - (propertize (match-string 1 it) - 'face 'font-lock-builtin-face)) - docstring - t t)) - -(defun helpful--propertize-quoted (docstring) - "Convert `foo' in docstrings to buttons (if bound) or else highlight." - (replace-regexp-in-string - ;; Replace all text of the form `foo'. - (rx (? "\\=") "`" (+? (not (any "`" "'"))) "'") - (lambda (it) - (let* ((sym-name - (s-chop-prefix "`" (s-chop-suffix "'" it))) - (sym (intern sym-name))) - (cond - ;; If the quote is escaped, don't modify it. - ((s-starts-with-p "\\=" it) - it) - ;; Only create a link if this is a symbol that is bound as a - ;; variable or callable. - ((or (boundp sym) (fboundp sym)) - (helpful--button - sym-name - 'helpful-describe-button - 'symbol sym)) - ;; If this is already a button, don't modify it. - ((get-text-property 0 'button sym-name) - sym-name) - ;; Highlight the quoted string. - (t - (propertize sym-name - 'face 'font-lock-constant-face))))) - docstring - t t)) +(defun helpful--propertize-sym-ref (sym-name) + "Given a symbol name from a docstring, convert to a button (if +bound) or else highlight." + (let* ((sym (intern sym-name))) + (cond + ;; Highlight keywords. + ((s-matches-p + (rx ":" + symbol-start + (+? (or (syntax word) (syntax symbol))) + symbol-end) + sym-name) + (propertize sym-name + 'face 'font-lock-builtin-face)) + ;; Only create a link if this is a symbol that is bound as a + ;; variable or callable. + ((or (boundp sym) (fboundp sym)) + (helpful--button + sym-name + 'helpful-describe-button + 'symbol sym)) + ;; If this is already a button, don't modify it. + ((get-text-property 0 'button sym-name) + sym-name) + ;; Highlight the quoted string. + (t + (propertize sym-name + 'face 'font-lock-constant-face))))) (defun helpful--propertize-info (docstring) "Convert info references in docstrings to buttons." @@ -949,13 +932,35 @@ vector suitable for `key-description', and COMMAND is a smbol." ;; inherited bindings last. Sort so that we group by prefix. (s-join "\n" (-sort #'string< lines)))) +(defun helpful--format-commands (str keymap) + "Replace all the \\[ references in STR with buttons." + (replace-regexp-in-string + ;; Text of the form \\[foo-command] + (rx "\\[" (group (+ (not (in "]")))) "]") + (lambda (it) + (let* ((symbol-name (match-string 1 it)) + (symbol (intern symbol-name)) + (key (where-is-internal symbol keymap t)) + (key-description + (if key + (key-description key) + (format "M-x %s" symbol-name)))) + (helpful--button + key-description + 'helpful-describe-exactly-button + 'symbol symbol + 'callable-p t))) + str + t + t)) + (defun helpful--format-command-keys (docstring) "Convert command key references and keymap references in DOCSTRING to buttons. Emacs uses \\= to escape \\[ references, so replace that unescaping too." - ;; Based on `substitute-command-keys', but converts command + ;; Loosely based on `substitute-command-keys', but converts ;; references to buttons. (let ((keymap nil)) (with-temp-buffer @@ -981,6 +986,29 @@ unescaping too." (delete-region (point) (+ (point) 2)) (forward-char 1)) ((looking-at + ;; Text of the form `foo' + (rx "`")) + (let* ((start-pos (point)) + (end-pos (search-forward "'" nil t)) + (contents + (when end-pos + (buffer-substring (1+ start-pos) (1- end-pos))))) + (cond + ((null contents) + ;; If there's no closing ' to match the opening `, just + ;; leave it. + (goto-char (1+ start-pos))) + ((s-contains-p "`" contents) + ;; If we have repeated backticks `foo `bar', leave the + ;; first one. + (goto-char (1+ start-pos))) + ((s-contains-p "\\[" contents) + (delete-region start-pos end-pos) + (insert (helpful--format-commands contents keymap))) + (t + (delete-region start-pos end-pos) + (insert (helpful--propertize-sym-ref contents)))))) + ((looking-at ;; Text of the form \\ (rx "\\<" (group (+ (not (in ">")))) ">" (? "\n"))) @@ -1016,18 +1044,7 @@ unescaping too." (delete-region (point) (+ (point) (length symbol-with-parens))) ;; Add a button. - (let* ((symbol (intern symbol-name)) - (key (where-is-internal symbol keymap t)) - (key-description - (if key - (key-description key) - (format "M-x %s" symbol-name)))) - (insert - (helpful--button - key-description - 'helpful-describe-exactly-button - 'symbol symbol - 'callable-p t))))) + (insert (helpful--format-commands symbol-with-parens keymap)))) ;; Don't modify other characters. (t (forward-char 1)))) @@ -1042,10 +1059,6 @@ unescaping too." (helpful--propertize-info) (helpful--propertize-links) (helpful--propertize-bare-links) - (helpful--propertize-keywords) - (helpful--propertize-quoted) - ;; This needs to happen after we've replaced quoted chars, so we - ;; don't confuse \\=` with `. (helpful--format-command-keys) (s-trim))) @@ -2307,7 +2320,7 @@ For example, \"(some-func FOO &optional BAR)\"." (defun helpful--format-obsolete-info (sym callable-p) (-let [(use _ date) (helpful--obsolete-info sym callable-p)] - (helpful--propertize-quoted + (helpful--format-docstring (s-word-wrap 70 (format "This %s is obsolete%s%s" diff --git a/test/helpful-unit-test.el b/test/helpful-unit-test.el index 6d1e42d..3010708 100644 --- a/test/helpful-unit-test.el +++ b/test/helpful-unit-test.el @@ -56,7 +56,11 @@ ;; We should handle stray backquotes. (let* ((formatted-docstring (helpful--format-docstring "`foo `message'"))) (should - (equal formatted-docstring "`foo message")))) + (equal formatted-docstring "`foo message"))) + ;; Handle a missing closing '. + (let* ((formatted-docstring (helpful--format-docstring "`foo"))) + (should + (equal formatted-docstring "`foo")))) (ert-deftest helpful--docstring-unescape () "Discard \\=\\= in docstrings." @@ -266,13 +270,15 @@ symbol (not a form)." (should (eq (get-text-property 0 'face formatted) - 'button))) - ;; Propertize mode maps. - (-let [formatted (helpful--format-docstring "`\\{python-mode-map}'")] + 'button)))) + +(ert-deftest helpful--format-docstring-mode-maps () + "Ensure we propertize references to keymaps." + (-let [formatted (helpful--format-docstring "\\{python-mode-map}")] (should (s-contains-p "run-python" formatted))) ;; Handle non-existent mode maps gracefully. - (-let [formatted (helpful--format-docstring "`\\{no-such-mode-map}'")] + (-let [formatted (helpful--format-docstring "\\{no-such-mode-map}")] (should (s-contains-p "not currently defined" formatted)))) -- cgit v1.0