Vim Recipes Navigation Bookmarking Lines with Visible Markers (Signs)

Bookmarking Lines with Visible Markers (Signs)

Problem

You want to assign visible marks to the margins of certain lines.

For example, you're using Vim as an IDE and want breakpoints to be clearly marked. Or, you want to label lines in need of editing with a question mark icon.

Solution

Vim allows you to define a sign and then associate it with one or more lines of a file. It is displayed in the right-hand margin as a two-character string in the terminal, and an icon in Gvim.

Before you use a sign you must define it. For example:

:sign define fixme text=!! linehl=Todo texthl=Error icon=/path/to/todo.xpm

Let's break this down. We name the sign fixme, which is how we'll refer to it later. We specify that in the terminal the sign should be displayed as !!, and that in the GUI the icon stored at /path/to/todo.xpm should be used instead. The linehl argument defines the highlight group used for the entire line the sign is attached to; texthl defines the highlight group for the sign itself.

Now the sign is defined, presumably in vimrc, you can use it in any file. To attach the sign to a specific line you use:

:sign place id line=line name=name
file=file-path

For example:

:sign place 22 line=200 name=fixme file=/home/user/novel.txt

The id is arbitrary, but must be unique and numeric. The name is the same name you used when you defined the sign. The value of the line argument is the number of the line on which the sign should be attached. The file argument is the full path (no expansion is done) to a currently loaded file to which the sign should be attached. So, in the above example, two exclamation marks are inserted in the margin of the 200th line of /home/user/novel.txt.

Discussion

You only need to define signs once, so that's easy enough, but the syntax for placing signs is particularly unwieldy. Let's look at some alternative approaches.

You could place the following stanza in your vimrc so <F5> places the previously defined fixme sign on the current line of the current file:

function! SignFixme()
  execute(":sign place ".line(".")." line=".line(".")." name=fixme file=".expand("%:p"))
endfunction
map <F5> :call SignFixme()<CR>

Rather than placing signs manually, you may prefer to have them automatically placed on lines satisfying some criteria. The following stanza attaches the fixme sign to lines containing notes like TODO: check for race conditions. It operates on the current line or selection. So, you can select a range of lines, press <F6> then have your to-do list items flagged in the margin.

function! SignLines() range
  let n = a:firstline
  execute(":sign define fixme text=!! texthl=Todo")
  while n <= a:lastline
    if getline(n) =~ '\(TODO\|FIXME\)'
      execute(":sign place ".n." line=".n." name=fixme file=".expand("%:p"))
    endif
    let n = n + 1
  endwhile  
endfunction
map <F6> :call SignLines()<CR>