Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax highlighting for #_ reader macro #60

Closed
metasoarous opened this issue May 8, 2015 · 8 comments
Closed

Syntax highlighting for #_ reader macro #60

metasoarous opened this issue May 8, 2015 · 8 comments

Comments

@metasoarous
Copy link

This reader macro effectively comments out the following form; it would be nice if there was syntax highlighting to reflect this. I realize this might be a bit challenging, since a form might span multiple lines, but it would still be helpful.

@guns
Copy link
Owner

guns commented Jul 18, 2016

Hello @metasoarous 1year+ later!

The #_ reader macro and comment macro used to be treated as comments, but a side effect of this was that the rich structural information from the syntax engine was unavailable. This information can be used by S-expression and REPL plugins to select and evaluate forms, so the behavior was reverted.

Thanks for the suggestion!

@guns guns closed this as completed Jul 18, 2016
@SevereOverfl0w
Copy link

When I use the reader macro, It's unusual for me to want to evaluate that.

It's common to see this kind of usage of the reader macro:

(defn foo [a b])

(foo a #_(+ 1 1) b)

Where having the element + macro makes it significantly more readable. If I want to evaluate a region as I am repl-ing along, I tend to use the (comment) macro.

I looked through the commit history, and couldn't find a syntax highlighting which would grey out both the prefix and the element it was attached to. I was hoping you might be able to assist me with a line I could add to my vimrc that would allow me to match for that case.

@guns
Copy link
Owner

guns commented Sep 30, 2016

Hi @SevereOverfl0w,

I looked through the commit history, and couldn't find a syntax highlighting which would grey out both the prefix and the element it was attached to. I was hoping you might be able to assist me with a line I could add to my vimrc that would allow me to match for that case.

Looks like I was mistaken. The original VimClojure syntax file simply matched #_ as a comment without handling the form that follows.

Matching #_-commented forms is a tall order for vim's syntax engine. For instance, how do you properly highlight the following without a proper parser?

> (list #_#_ 1 2 3)
(3)

Regardless, I sympathize with your desire to highlight your code to taste.

To handle the common case of a single #_, you will need to match #_ + atoms and define at least four syntax regions to deal with compound forms + strings:

" WARNING: The following code is incorrect
syntax match  clojureComment "\v#_[ \t\r\n]*[^()\[\]{} \t\r\n]+"
syntax region clojureComment start="\v#_[ \t\r\n]*\(" end=")"
syntax region clojureComment start="\v#_[ \t\r\n]*\[" end="]"
syntax region clojureComment start="\v#_[ \t\r\n]*\{" end="}"
syntax region clojureComment start="\v#_[ \t\r\n]*\"" end="\""

I don't think this is production quality, but you might find it satisfactory.

@SevereOverfl0w
Copy link

First of all, thank you for taking the time to put that code together for me. That's really great. I really appreciate it. Sorry to stretch you further, but it doesn't seem to match the parens though, so:

#_(defn foo
   []
   (anything))

Unfortunately breaks it, the final parens aren't considered a comment. This might be a better demo, as one of the parts of the body highlights, and the other doesn't.

#_(defn foo
  []
  (a-comment)
  (not-acomment))

It's not clear to me how I should fix this problem though. I'm happy to try figure it out and report back if you can point me in the right direction?

I just did a quick scope of highlighting with emacs, and it seems to do nothing extra in the #_#_ case. I didn't even know that was possible before! Seems dangerous. Not a case I'd be trying to catch, but I understand your reluctance.

@guns
Copy link
Owner

guns commented Sep 30, 2016

Unfortunately breaks it, the final parens aren't considered a comment. This might be a better demo, as one of the parts of the body highlights, and the other doesn't.

Yes, I forgot to add recursive matching. :)

Here is a somewhat more robust implementation:

" WARNING: This code has the following KNOWN deficiencies:
"
"   · Consecutive #_ macros are not matched correctly:
"       (list #_#_ 1 2 3) ;; => (3)
"   · Bracket character literals within #_ comment regions break syntax:
"       #_[\a \[ \] \b] ;; broken
"   · Compound forms preceded by reader metacharacters are unmatched:
"       #_'(α β γ) ;; broken
"   · Atomic forms preceded by reader metacharacters + whitespace are unmatched:
"       #_' foo ;; broken
"
syntax match  clojureCommentAtom            /\v#_[ \t\r\n]*[^()\[\]{} \t\r\n]+/
syntax region clojureCommentListContained   start=/(/  end=/)/ contains=clojureCommentListContained,clojureCommentVectorContained,clojureCommentMapContained,clojureCommentStringContained contained
syntax region clojureCommentVectorContained start=/\[/ end=/]/ contains=clojureCommentListContained,clojureCommentVectorContained,clojureCommentMapContained,clojureCommentStringContained contained
syntax region clojureCommentMapContained    start=/{/  end=/}/ contains=clojureCommentListContained,clojureCommentVectorContained,clojureCommentMapContained,clojureCommentStringContained contained
syntax region clojureCommentStringContained start=/"/  skip=/\v\\\\|\\"/ end=/"/ contained
syntax region clojureCommentList            matchgroup=clojureCommentDelimiter start=/\v#_[ \t\r\n]*\(/ end=/)/ contains=clojureCommentListContained,clojureCommentVectorContained,clojureCommentMapContained,clojureCommentStringContained
syntax region clojureCommentVector          matchgroup=clojureCommentDelimiter start=/\v#_[ \t\r\n]*\[/ end=/]/ contains=clojureCommentListContained,clojureCommentVectorContained,clojureCommentMapContained,clojureCommentStringContained
syntax region clojureCommentMap             matchgroup=clojureCommentDelimiter start=/\v#_[ \t\r\n]*\{/ end=/}/ contains=clojureCommentListContained,clojureCommentVectorContained,clojureCommentMapContained,clojureCommentStringContained
syntax region clojureCommentString          matchgroup=clojureCommentDelimiter start=/\v#_[ \t\r\n]*"/  skip=/\v\\\\|\\"/ end=/"/
highlight link clojureCommentDelimiter       clojureComment
highlight link clojureCommentAtom            clojureComment
highlight link clojureCommentListContained   clojureComment
highlight link clojureCommentVectorContained clojureComment
highlight link clojureCommentMapContained    clojureComment
highlight link clojureCommentStringContained clojureComment
highlight link clojureCommentList            clojureComment
highlight link clojureCommentVector          clojureComment
highlight link clojureCommentMap             clojureComment
highlight link clojureCommentString          clojureComment

This should be fairly easy to understand after reading the syn-region help topic (:help syn-region). The contains=, contained, and matchgroup= modifiers are particularly important to apprehend.

If you would like to tackle any of the problems mentioned in the comment header, I would suggest creating the following command:

" http://vimcasts.org/episodes/creating-colorschemes-for-vim/
command! -bar SynStack call <SID>SynStack() "{{{1
function! s:SynStack()
    if exists("*synstack")
        echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
    endif
endfunction

This command shows the syntax stack at the cursor position and is invaluable for debugging syntax definitions. Bind this to a normal mapping and have fun!

… the #_#_ case. I didn't even know that was possible before! Seems dangerous. Not a case I'd be trying to catch, but I understand your reluctance.

I believe this trick is more commonly known than a reasonable person might first expect. A quick reading of LispReader.java reveals that the form ignored by #_ is consumed by a recursive call to the same top-level read method used to read any Clojure code. This means, of course, that #_#_ pushes two read calls onto the stack, effectively discarding the next two forms.

Also, I've seen it posed as a koan on IRC channels for understanding the Clojure reader.

@SevereOverfl0w
Copy link

Woah, you've gone absolutely above and beyond any expectations here. Thank you so much for your help. This is absolutely brilliant. I really appreciate this.

I'm going to take some time to read through those help files and make sure I understand as much of this as possible. There's no way I would have got this on my own.

@zendevil
Copy link

zendevil commented Jul 27, 2021

I copied all the commands from #60 (comment) to nvim init.vim but don't see the #_ comment "commented out"

@axvr
Copy link

axvr commented Jul 27, 2021

@zendevil, I recommend switching to https://github.com/clojure-vim/clojure.vim. It is a fork/continuation of Clojure-vim-static which syntax highlights the comment reader macro (#_) amongst other improvements.

The only downside is that the comment reader macro highlighting in Clojure.vim only highlights the first form as a comment. For example this is highlighted correctly:

#_(defn foo [x]
    (println x))

But this is not (stacked #_#_):

{:foo 1
 #_#_:bar 2}

(If anyone knows how to implement the second case, please open a PR as it'd be greatly appreciated. The related issue can be found here: clojure-vim/clojure.vim#17)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants