简体   繁体   中英

Could Emacs fontify elisp string constants?

The Dilemma: readability or maintainability?

Let's look at the following function. It doesn't really matter what it does, the important part is that it's using twice the string "(let\\\\*?[ \\t]*" :

(defun setq-expression-or-sexp ()
  "Return the smallest list that contains point.
If inside VARLIST part of `let' form,
return the corresponding `setq' expression."
  (interactive)
  (ignore-errors
    (save-excursion
      (up-list)
      (let ((sexp (preceding-sexp)))
        (backward-list 1)
        (cond
         ((looking-back "(let\\*?[ \t]*")
          (cons 'setq
                (if (= (length sexp) 1)
                    (car sexp)
                  (cl-mapcan
                   (lambda (x) (unless (listp x) (list x nil)))
                   sexp))))
         ((progn
            (up-list)
            (backward-list 1)
            (looking-back "(let\\*?[ \t]*"))
          (cons 'setq sexp))
         (t
          sexp))))))

Since it's a headache having to update the string in two (or more) locations, I'd have to defconst it like so:

(defconst regex-let-form "(let\\*?[ \t]*")

Although the code became more maintainable, it became less readable as well, because it's hard to see at a glance what regex-let-form really is:

(defun setq-expression-or-sexp ()
  "Return the smallest list that contains point.
If inside VARLIST part of `let' form,
return the corresponding `setq' expression."
  (interactive)
  (ignore-errors
    (save-excursion
      (up-list)
      (let ((sexp (preceding-sexp)))
        (backward-list 1)
        (cond
         ((looking-back regex-let-form)
          (cons 'setq
                (if (= (length sexp) 1)
                    (car sexp)
                  (cl-mapcan
                   (lambda (x) (unless (listp x) (list x nil)))
                   sexp))))
         ((progn
            (up-list)
            (backward-list 1)
            (looking-back regex-let-form))
          (cons 'setq sexp))
         (t
          sexp))))))

The idea: why not both?

Since it's a constant anyway, why not font-lock it and make regex-let-form appear as if it's "(let\\\\*?[ \\t]*" ? It's a feasable job, since:

  1. It's possible to font-lock identifiers like so: http://www.emacswiki.org/emacs/PrettyLambda , or even so: rainbow-mode .

  2. And it's possible to font-lock constants. It's already done for c++-mode, but not yet for emacs-lisp-mode, as far as I know.

Then it remains only to connect the two. Unfortunately, I don't know enough of font-lock innards to do it, but maybe someone else does? Or is there already a package that does this?

Tweaking the code from this answer , I've solved the problem:

(font-lock-add-keywords 
 'emacs-lisp-mode
 '((fl-string-constant . 'font-lock-constant-face)) 'append)

(defun fl-string-constant (_limit)
  (while (not 
          (ignore-errors
            (save-excursion
              (skip-chars-forward "'")
              (let ((opoint (point))
                    (obj (read (current-buffer)))
                    obj-val)
                (and (symbolp obj)
                     (risky-local-variable-p obj)
                     (special-variable-p obj)
                     (stringp (setq obj-val (eval obj)))
                     (progn
                       (put-text-property 
                        (1- (point)) (point) 'display
                        (format "%c\"%s\"" (char-before) obj-val))
                       (set-match-data (list opoint (point))) 
                       t))))))
    (if (looking-at "\\(\\sw\\|\\s_\\)")
        (forward-sexp 1)
      (forward-char 1)))
  t)

This displays the value of a string constant right after the constant name. It works quite nicely with fontified string constants as well. Speed is a bit of an issue - suggestions to improve are welcome.

Also, I couldn't find anything better than risky-local-variable-p to determine that it's a constant. The doc says that defconst marks the variable as special and risky, but nothing else.

hl-defined.el (updated today, 2013-10-20) can highlight constant Emacs-Lisp symbols as such, that is, variables whose current value is the symbol itself. If your defconst has been evaluated then this will do what you are requesting.

This seems to work (source: http://www.emacswiki.org/emacs/PrettyLambda ):

(font-lock-add-keywords 'emacs-lisp-mode
  `(("\\<\\(regex-let-form\\)\\>" (0 (prog1 nil
                                       (compose-region (match-beginning 1)
                                                       (match-end 1)
                                                       "\"(let\\\\*?[ \\t]*\""))))))

Although I think adding regex-let-form into the existing let block would be a cleaner solution:

(let ((sexp (preceding-sexp))
      (regex-let-form "(let\\*?[ \t]*"))
    ...

Perhaps your example is not indicative of the real problem, and you really do want to do some display replacement or font-locking, as you say.

But I will answer wrt your example and the problem as posed, regarding maintainability vs readability: Just let -bind your regexp. The binding, unlike a defconst will be nearby and clearly related to the occurrences of the bound variable.

This is typically what people do. Again, you might have had another use case in mind --- I am responding only to the problem as posed narrowly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM