jlr / rainbow-delimiters

Emacs rainbow delimiters mode
http://www.emacswiki.org/emacs/RainbowDelimiters
112 stars 12 forks source link

fails with temp buffers #5

Closed renard closed 10 years ago

renard commented 12 years ago

Hi Jeremy,

I guess I found a bug (or something goes wrong) when using rainbow-delimiter-mode in temp buffers.

I do use temp buffer for exporting colorized source to html.

If you do run following snippets you don't get the same results:

(let ((rtn "(defun org-xor (a b)\n \"Exclusive or.\"\n (if a (not b) b))\n") (mode 'emacs-lisp-mode) (emacs-lisp-mode-hook nil)) (with-temp-buffer (insert rtn) (if (functionp mode) (funcall mode) (fundamental-mode)) (rainbow-delimiters-mode 1) (font-lock-fontify-buffer) (htmlize-region-for-paste (point-min) (point-max))))

This returns

"

(<span style=\"color: #729fcf; font-weight: bold;\">defun <span style=\"color: #edd400; font-weight: bold;\">org-xor (a b)
<span style=\"color: #888a85;\">\"Exclusive or.\"
(<span style=\"color: #729fcf; font-weight: bold;\">if a (not b) b))
"

It seems that the rainbow-delimiters-mode is not effective

But when using real buffer:

(let ((rtn "(defun org-xor (a b)\n \"Exclusive or.\"\n (if a (not b) b))\n") (mode 'emacs-lisp-mode) (emacs-lisp-mode-hook nil)) (with-current-buffer (get-buffer-create "Just for test") (insert rtn) (if (functionp mode) (funcall mode) (fundamental-mode)) (rainbow-delimiters-mode 1) (font-lock-fontify-buffer) (setq rtn (htmlize-region-for-paste (point-min) (point-max))) (kill-buffer)) rtn)

It returns

"

<span style=\"color: #fce94f; font-weight: bold;\">(<span style=\"color: #729fcf; font-weight: bold;\">defun <span style=\"color: #edd400; font-weight: bold;\">org-xor <span style=\"color: #729fcf; font-weight: bold;\">(a b<span style=\"color: #729fcf; font-weight: bold;\">)
<span style=\"color: #888a85;\">\"Exclusive or.\"
<span style=\"color: #729fcf; font-weight: bold;\">(<span style=\"color: #729fcf; font-weight: bold;\">if a <span style=\"color: #8ae234; font-weight: bold;\">(not b<span style=\"color: #8ae234; font-weight: bold;\">) b<span style=\"color: #729fcf; font-weight: bold;\">)<span style=\"color: #fce94f; font-weight: bold;\">)
"

As expected.

I do run rainbow-delimiters-mode Version: 1.3.3 and las emacs-snapshot GNU Emacs 24.0.92.1 (x86_64-pc-linux-gnu, GTK+ Version 2.20.1) of 2012-01-05 on cw-bkp0, modified by Debian

Thanks you for your help and great work on rainbow-delimiters-mode.

Cheers

jlr commented 12 years ago

Hi - this is an interesting bug. 'with-temp-buffer' macroexpands to something quite similar to the second example - it creates a new buffer using (generate-new-buffer " temp") and goes there using (set-buffer ...). To clean up/delete the buffer when done, 'with-temp-buffer' runs everything inside an unwind-protect. Even though it's quite similar to your second example there's something 'with-temp-buffer' is doing differently that rainbow-delimiters isn't accounting for, because I do see the behaviour you're describing. I use Emacs 24 as well.

The presence of jit-lock made me wonder if that was affecting things, but inserting a (jit-lock-fontify-now) doesn't help.

I'll keep at it till we figure out what's going on. Even though 'with-temp-buffer' uses a similar technique to your working example it uses different functions (generate-new-buffer instead of get-buffer-create; set-buffer instead of with-current-buffer; etc). Something there could be creating a problem for rainbow-delimiters; one thought that comes to mind is if the temp-buffer isn't initializing something present in most ordinary buffers, rainbow-delimiters could be failing when started. Regardless some further exploration will turn up the reason.

Thank you for taking the time to put together a bug report, I appreciate it. I'll work on figuring out what's happening here and get it corrected for you.

renard commented 12 years ago

jlr reply@reply.github.com writes:

Hi,

I investigated a while And I noticed that if the buffer is a hidden buffer starting with a space character (such as " temp" instead of "temp") rainbow-delimiters-mode fails.

For example :

(let ((rtn "(defun org-xor (a b)\n \"Exclusive or.\"\n (if a (not b) b))\n") (mode 'emacs-lisp-mode) (emacs-lisp-mode-hook nil)) (with-current-buffer (get-buffer-create " Just for test") (insert rtn) (if (functionp mode) (funcall mode) (fundamental-mode)) (rainbow-delimiters-mode 1) (font-lock-fontify-buffer) (setq rtn (htmlize-region-for-paste (point-min) (point-max))) (kill-buffer)) rtn)

Fails,

but :

(let ((rtn "(defun org-xor (a b)\n \"Exclusive or.\"\n (if a (not b) b))\n") (mode 'emacs-lisp-mode) (emacs-lisp-mode-hook nil)) (with-current-buffer (get-buffer-create "Just for test") (insert rtn) (if (functionp mode) (funcall mode) (fundamental-mode)) (rainbow-delimiters-mode 1) (font-lock-fontify-buffer) (setq rtn (htmlize-region-for-paste (point-min) (point-max))) (kill-buffer)) rtn)

Works.

I didn't figured out why yet.

Thanks

Cheers

Sébastien Gross

jlr commented 12 years ago

That narrows it down wonderfully. Good catch.

Looking into it further, this goes all the way to underlying Emacs behaviour. If you create a buffer named " Just a test" and another one named "Just a test", use M-x emacs-lisp-mode on both, you'll find that the buffer with a space in front has no highlighting at all.

M-x font-lock-mode always comes back with "Font-Lock mode disabled" in the buffer with a space in front, where the buffer with an ordinary name is able to enable/disable font lock normally.

You can't even apply a text property to a buffer with a space in front of the name because of the absence of font-lock. Running: (set-text-properties (point) (1+ (point)) '(font-lock-face "region" rear-nonsticky t)) Works fine in "Just a test" but not the buffer with a space in front of the name. Setting a text property like this is how a delimiter would be highlighted in rainbow-delimiters.

It turns out this may be intentional. From the Emacs info on Undo: Not all buffers record undo information. Buffers whose names start with spaces don't; these buffers are used internally by Emacs and its extensions to hold text that users don't normally look at or edit.

So it's certainly possible that from a speed perspective, you want to turn off font-lock and a lot of other things in buffers you don't intend for users to see. It still surprises me a little you can't turn it back on manually, and in your first example you did turn on font-lock but it still didn't listen to the text properties set by rainbow-delimiters.

Evaling the (set-text-properties ...) line given above returns 't' in both buffers (no error message, same return value), but it doesn't change anything in the buffer with a space in front of the name. It highlights the character as expected in the buffer without a space in front.

I'm not sure whether to call this a problem or an intentional design choice in Emacs. On the one hand, I would expect to be able to use 'with-temp-buffer' for the purpose you're using it for; on the other, 'with-temp-buffer' is part of a family of functions including 'with-output-to-string' and 'with-temp-file' where you would not expect highlighting.

I'd also be curious what Emacs 23.x series does and may check that later today.

As far as rainbow-delimiters is concerned this is definitely not something I can change. It may be worth looking into the rationale for 'with-temp-buffer' using a buffer named " temp" and seeing whether or not users expect highlighting to be available when using that macro. It's an edge case for with-temp-buffer; most uses of it aren't specifically for highlighting so I can see why they wrote it the way they did. You may just want to go with code like the second example you gave.

At this point we know it's not an issue with rainbow-delimiters. It may or may not be worth taking it up with Emacs proper; my guess is that using 'with-temp-buffer' for highlighting is enough of an edge case that they'd decide to keep it the way it's currently written.

(tl;dr: Not a bug in rainbow-delimiters.)

Thanks,

renard commented 12 years ago

Thank for that explanations.

I meanwhile found a (I guess UGLY) workaround using macrolet and change the " temp" to "temp":

(let ((rtn "(defun org-xor (a b)\n \"Exclusive or.\"\n (if a (not b) b))\n")
      (mode 'emacs-lisp-mode)
      (emacs-lisp-mode-hook nil))
  (macrolet ((with-temp-buffer
           (&rest body)
           (let ((temp-buffer (make-symbol "temp-buffer")))
         `(let ((,temp-buffer (generate-new-buffer "*temp*")))
            (with-current-buffer ,temp-buffer
              (unwind-protect
              (progn ,@body)
              (and (buffer-name ,temp-buffer)
               (kill-buffer ,temp-buffer))))))))
    (with-temp-buffer
      (insert rtn)
      (if (functionp mode)
      (funcall mode)
    (fundamental-mode))
      (rainbow-delimiters-mode 1)
      (font-lock-fontify-buffer)
      (htmlize-region-for-paste (point-min) (point-max)))))

thus the behavior of the with-temp-buffer macro is only changed for the htmlization process.

Use it sparingly, wisely and with care. You have been warned.

Hope this might help.

Cheers

jlr commented 12 years ago

Haha, OK so you're just making a temp buffer without a space in front? shrug That's the best I could come up with :)

What are you using it with/for, anyway? I want to be supportive of making this work with generating html. Rainbow parens are really helpful when looking at code on a webpage.

I'd like to add a utility fn that doesn't use jit-lock at all and instead just goes through and highlights all the delims in a single pass specifically for htmlize. Would you want that to just be a function you can call, or an interactive command, or something else? Should it take some options? I haven't looked at htmlize so I don't know. Probably won't get that written for a couple months, but definitely want it to be there for people. Glad you found a temp workaround for now. Cheers

renard commented 12 years ago

Hi,

I am using it for o-blog (https://github.com/renard/o-blog) in the exact use case you just described.

Unfortunately I never managed to make the macrolet work when exporting a webpage. It looks like the first with-temp-buffer uses the function like in the macrolet defines it ("*temp*" with no space), but next with-temp-buffer calls uses a " *temp*" named buffer (with a space in front of it). IOW the with-temp-buffer called within with-temp-buffer use original piece of code.

Fanael commented 10 years ago

Works for me with current code. Closing.