Emacs: Change IPython traceback background color from yellow to black
I use elpy
minor-mode for my local Python development work and it works great for me. Quickly dropping into a ipython
shell after evaluating the current buffer or a particular region (using elpy-shell-send-region-or-buffer
) is a great way to play around without even needing to invoke a debugger as the local and global namespaces are readily available there. There's a very nagging issue with ipython
though -- whenever it shows the full traceback for an exception, it sets the background of some name identifying texts to yellow. With the characters/text on my terminal being white in color, this makes those identifiers completely unreadable. The image below shows an example:
The word foobar
(in yellow background) is absolutely unreadable. This might have worked if I had a light background with dark-colored characters.
elpy-shell-send-region-or-buffer
invokes inferior-python-mode
(uses comint
internally) and I've put the following definitions in my Emacs init file to use ipython
as the interpreter in that mode:
(setq python-shell-interpreter "ipython") (setq python-shell-interpreter-args "--profile=default --simple-prompt")
I tried running ipython
like above in the terminal directly and got the exact same coloring on exception, so conclusively, this definitely is coming from ipython
.
Now to see how inferior-python-mode
is handling the coloring, I've started looking into the source code of inferior-python-mode
and found something interesting:
(setq-local comint-output-filter-functions '(ansi-color-process-output python-shell-comint-watch-for-first-prompt-output-filter comint-watch-for-password-prompt))
inferior-python-mode
major-mode inherits from the comint-mode
and comint
(COMmand INTerpreter) works kind of like a terminal to take one command, execute, and return the response. There are many more bells and whistles around those steps but that's the higher level picture.
In the chunk above, we can see ansi-color-process-output
(from ansi-color.el
package) is being set as the first element of the comint-output-filter-functions
buffer-local variable. Having some idea about these packages, ansi-color-process-output
seems to me like an obvious candidate where the coloring indicated by ipython
would be handled.
(Given ansi-color-process-output
is invoked, we can safely assume the ANSI SGR control sequences are used by ipython
(like most terminal programs running in capable terminals).)
Next, I've taken a peek into the ansi-color-process-output
function and found that it's eventually calling ansi-color-apply-on-region
function to do the actual translation of SGR control sequences into Emacs overlays of appropriate colors.
The solution I've used to handle this is to use a :filter-args
advice on ansi-color-apply-on-region
to replace the SGR escape sequences of color yellow to black (later made these two as buffer-local variables so that the code is reusable) before it's translated into overlays by ansi-color-apply-on-region
.
Here's how it looks eventually:
(make-variable-buffer-local 'local-ansi-color-apply-on-region-ipython-from-color-code) (setq-default local-ansi-color-apply-on-region-ipython-from-color-code 43) ;; yellow (make-variable-buffer-local 'local-ansi-color-apply-on-region-ipython-to-color-code) (setq-default local-ansi-color-apply-on-region-ipython-to-color-code 40) ;; black (defun local-ansi-color-apply-on-region-ipython-filter-args-advice (args) "Change background color from yellow (default) to black in `ipython` traceback. The from and to colors are set using the buffer-local variables `local-ansi-color-apply-on-region-ipython-from-color-code' and `local-ansi-color-apply-on-region-ipython-to-color-code', respectively. This is targeted for the `ipython` in `inferior-python-mode` (`comint`). " (when (and (derived-mode-p 'inferior-python-mode) (boundp 'python-shell-interpreter) (string= python-shell-interpreter "ipython")) (let ((marker-beginning (nth 0 args)) (marker-end (nth 1 args))) (replace-regexp-in-region (format "\\([\\[;]\\)%s\\([m;]\\)" local-ansi-color-apply-on-region-ipython-from-color-code) (format "\\1%s\\2" local-ansi-color-apply-on-region-ipython-to-color-code) (marker-position marker-beginning) (marker-position marker-end)) args))) (advice-add #'ansi-color-apply-on-region :filter-args #'local-ansi-color-apply-on-region-ipython-filter-args-advice)
Alternate solution:
An alternate solution to the problem is to use the ipython
startup file ~/.ipython/profile_default/ipython_config.py
to monkey-patch IPython.core.ultratb.VerboseTB.tb_highlight = 'bg:ansiblack'
.
But as monkey-patching is never future-proof especially when it's someone else's code, using a little elisp
seems like a better idea to me.
References:
elpy
elpy-shell-send-region-or-buffer
ipython
~/.ipython/profile_default/ipython_config.py
IPython.core.ultratb.VerboseTB.tb_highlight = 'bg:ansiblack'
inferior-python-mode
comint.el
comint-output-filter-functions
ansi-color.el
ansi-color-process-output
ansi-color-apply-on-region
- ANSI SGR Escape sequences
- Emacs overlays
- Emacs init file
:filter-args
Advice
Comments
Comments powered by Disqus