If you ever used
ielm, or other
comint-mode derivatives, you will notice
that the text you input is not highlighted according to the major-mode.
If I type
(setq foo bar) into ielm, the setq won't be highlighted.
Why is this? And how do we change this?
font-lock-keywords in ielm and it is suspiciously near-empty. We could
copy over emacs-lisp's keywords:
(setq-local font-lock-keywords `(,@lisp-el-font-lock-keywords-2 ,@lisp-cl-font-lock-keywords-2))
But what if I type in
(princ "(setq foo bar)")? The output will inherit the
Naively enabling font locking in comint buffers can lead to a mess of syntax highlighting in the output. While the example above is contrived, it is in general not a trivial problem.
I wrote and support hy-mode, a lisp embedded in Python. When the interpreter is given "–spy", the translation of the Hy code to Python is given in the output before the result of the Hy code. This translation would inherit Hy's syntax highlighting and look like a mess.
python-mode actually implements fontification of shell input. How do they do it?
They add a
post-command-hook that essentially extracts the current input being
entered, fontifies it according to python, then reinserts it into the prompt.
There is quite a bit going on to make this work in practice - check out
python-shell-font-lock-post-command-hook if you are interested.
I had success using this approach for
hy-mode but always thought it was a
kludge and difficult to understand and work with. Can't I just use
I came up with a hookless, pure font-lock-mode solution that should work for arbitrary modes.
I convert every
font-lock-keyword MATCHER component to check that we are
within a prompt before calling the MATCHER if it is a function or matching on it
if it is a regex.
(require 'dash) (defun kwd->comint-kwd (kwd) "Converts a `font-lock-keywords' KWD for `comint-mode' input fontification." (-let (((matcher . match-highlights) kwd)) ;; below is ` quoted but breaks my blogs syntax higlighting, so removing it! ;; make sure to capture first paren in a ` if copying! ((lambda (limit) ;; Matcher can be a function or a regex (when ,(if (symbolp matcher) `(,matcher limit) `(re-search-forward ,matcher limit t)) ;; While the SUBEXP can be anything, this search always can use zero (-let ((start (match-beginning 0)) ((comint-last-start . comint-last-end) comint-last-prompt) (state (syntax-ppss))) (and (> start comint-last-start) ;; Make sure not in comment or string ;; have to manually do this in custom MATCHERs (not (or (nth 3 state) (nth 4 state))))))) ,@match-highlights))) (setq my-ielm-font-lock-kwds `(,@(-map #'kwd->comint-kwd lisp-el-font-lock-keywords-2) ,@(-map #'kwd->comint-kwd lisp-cl-font-lock-keywords-2))) (defun set-my-ielm-kwds () (interactive) (setq-local font-lock-keywords my-ielm-font-lock-kwds))
ielm, my own
hy-mode, etc. highlights shell input without messing with
the output if I call
set-my-ielm-kwds in an ielm buffer.