Vimscript Solution for Maintaining Comment Boxes

November 18, 2018 ยท 4 minute read  

Even though I know it’s not normally considered a good practice, I still like to use comment boxes from time to time. These are especially handy in bash scripts to write comments about the script at the top.

The reason they are generally considered a bad practice is that they are difficult to change and thus encourage stale documentation. I still advice that you don’t use comment boxes in the real world since not everyone on your team will have read this amazing article. But for small scripts on my own system, I still use comment boxes, and you can too.

The Code

In the below code snippet, you’ll find a definition for the MakeBox vimscript function. This function can be used to help create and maintain comment boxes in any language that has a single line comment specification (e.g. // not /* ... */).

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Close comment box.                                                          "
"                                                                             "
" Beginning comment bar and ending comment bar must both be defined already   "
" and the cursor needs to be between the bars when 'MakeBox' is called.       "
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! MakeBox()
    " Mark starting position
    normal! mm

    if index(['sh', 'python', 'ruby'], &ft) >= 0
        let g:comment_char = '#'
    elseif index(['c', 'cpp', 'java', 'javascript', 'php'], &ft) >= 0
        let g:comment_char = '/'
    elseif index(['haskell', 'sql'], &ft) >= 0
        let g:comment_char = '-'
    elseif index(['tex'], &ft) >= 0
        let g:comment_char = '%'
    elseif index(['scheme'], &ft) >= 0
        let g:comment_char = ';'
    elseif index(['vim'], &ft) >= 0
        let g:comment_char = '"'
    endif

    let triple_comment = g:comment_char . g:comment_char . g:comment_char

    let curr_line = getline('.')
    while curr_line[0:2] != triple_comment
        normal! k
        let curr_line = getline('.')
    endw

    normal! $
    let max_line = col('.')

    call MakeBoxBar(max_line)

    normal! j
    let curr_line = getline('.')
    let first_line = 1
    while curr_line[0:2] != triple_comment
        if first_line == 1
            let first_line = 0
        else
            normal! j
        endif

        let curr_line = getline('.')
        call MakeBoxLine(max_line)
    endw

    call MakeBoxBar(max_line)

    " Return cursor to starting position
    normal! `mh
endfunction

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Construct one of the bars that goes at the beginning and end of a           "
" comment-box.                                                                "
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! MakeBoxBar(max_line)
    let _ = cursor(line('.'), a:max_line)
    let column_number = col('.')
    while column_number != a:max_line
        execute "normal! a" . g:comment_char
        let column_number = col('.')
    endw
endf

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Format a single line inside of a comment box                                "
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! MakeBoxLine(max_line)
    normal! 0
    let current_ch = matchstr(getline('.'), '\%' . col('.') . 'c.')

    if current_ch == ' ' || current_ch == ''
        execute "normal! xi" . g:comment_char . " "
    elseif current_ch != g:comment_char
        execute "normal! i" . g:comment_char . " "
    endif

    let do_double = index(['/', '-'], g:comment_char) >= 0

    if do_double
        normal! 0l
        let ch = matchstr(getline('.'), '\%' . col('.') . 'c.')
        let column_number = col('.')
        if ch != g:comment_char
            execute "normal! i" . g:comment_char
        endif
    endif

    let _ = cursor(line('.'), a:max_line)
    let column_number = col('.')
    let current_ch = matchstr(getline('.'), '\%' . col('.') . 'c.')
    if column_number != 1 && (do_double != 1 || column_number != 2)
        if current_ch == g:comment_char || column_number == a:max_line
            normal! D
            let column_number = col('.')
        endif
    endif
    while column_number < (a:max_line - 2)
        let column_number = col('.')
        execute "normal! a "
    endwhile
    execute "normal! a" . g:comment_char

    if do_double
        execute "normal! hr" . g:comment_char
    endif
endfunction

Now, you should know that I’m new to the language so you probably shouldn’t use the above code as an example of proper vimscript. In fact, if you spot an error or know of better practices that I should be following, please let me know in the comments.

I have hard-coded the comment characters for various languages at the top of the MakeBox function. If your language is not listed there, you will have to make sure to add it manually.

The Setup

Just copy the above code into you vimrc file. I also have the following mapping defined in my vimrc which you can use as is or customize to your liking (or just call MakeBox directly):

nnoremap <Leader># :call MakeBox()<CR>

The Demo

Here’s a look at the MakeBox function in action:

Demonstration GIF for MakeBox Function


CHANGELOG

  • Made several improvements to the original source code of the MakeBox function in response to advice given by statox42 in this Reddit discussion.

Thanks for Reading!

Before you go, take a look at some of my more popular open source projects. These tools make my job as a programmer easier. They can do the same for you! 😄

Trending Projects

  • cookie: A template-based file generator. Like cookiecutter but works with file templates instead of project templates.
  • funky: Takes shell functions to the next level by making them easier to define, more flexible, and more interactive.
Did you enjoy the article? Follow me on Twitter so you'll be notified when I publish the next one: