Vim Tips Wiki
Register
Advertisement

Duplicate tip

This tip is very similar to the following:

These tips need to be merged – see the merge guidelines.

Tip 332 Printable Monobook Previous Next

created September 23, 2002 · complexity basic · author Emmanuel Touzery · version 6.0


Here is a macro to insert footnotes. It requires two differents shortcuts, one for entering the first footnote, the other one for all subsequent footnotes.

When you hit "K0" (first footnote) or "KK" (all other footnotes) in normal mode, your cursor is positioned at the end of the document, in the footnote & in insert mode. The "a" bookmark is set to the place where you entered the footnote in the text. so a "`a" will bring you back to the location of the footnote in the text.

" for now requires entering K0 for the first footnote and then KK nmap K0 i[0]<Esc>maG$i<End><CR>[0]
nmap KK maG$?\[[0-9]*\]<CR>yt]G$i<End><CR><Esc>p<C-a>i<End>]<Esc>`aP<C-a><Right>i]<Esc>maG$i<End><End>

Comments[]

For information I just added a worthwhile special case: we do not add the footnotes at the end of the document, but on the line before a "--".

this is useful if you have a signature at the end of your mails, eg, do:

hello[0]!

[0] footnote
--
sig

and not:
hello[0]!
--
sig

[0] footnote

here is the updated code:

nmap K0 i[0]<Esc>maG?--<CR><Up>$i<End><CR>[0]
nmap KK maG$?\[[0-9]*\]<CR>yt]G?--<CR><Up>$i<End><CR><Esc>p<C-a>i<End>]<Esc>`aP<C-a><Right>i]<Esc>maG?--<CR><Up>$i<End><End>

ok, learned some more vim. now it's not anymore a macro, but a function. you don't have to enter the text of the footnote separately, you are prompted for it, so it's faster to use. also, there is no more separation for the first footnote case: you all the time enter KK. it also supports the case when there is a sig or not.

here is the whole code:

nmap KK :call InsertFootNote()<LF>

function! InsertFootNote()
. " mark the position of the cursor
. execute "normal ma"
. " ask for footnote text
. let footNoteText = input("enter text for footnote: ")
. " was there already a footnote?
. if search("\[[0-9]*]", "w")
. . " yes => get the number, copy it, increase
. . " it, put it at the footnote position, put
. . " the footnote text and position the cursor back.
. . execute "normal G$?\[[0-9]*\]\<CR>yt]:call GotoFootNoteLocation()\<LF>$i\<End>\<CR>\<Esc>p\<C-a>i\<End>] " . footNoteText . "\<Esc>`aP\<C-a>\<Right>i]\<Esc>"
. else
. . " no => put [0], add at the end [0] + footnote text
. . " and position cursor back
. . execute "normal i[0]\<Esc>:call GotoFootNoteLocation()\<LF>$i\<End>
\<CR>\<CR>[0] " . footNoteText . "\<Esc>`a"
. endif
endfunction

" if there is a signature, the footnote
" should be positioned ontop of it, eg
" mail text
" [0] footnote 0
" --
" sig
" and not:
" mail text
" --
" sig
" [0] footnote 0
" otherwise it's at the end of the text.
function! GotoFootNoteLocation()
. " the signature is found by the "--"
. " pattern.
. " i don't search from the end because
. " a fwd will also match this and i don't want
. " that footnotes are too far off, say after 5-6
. " old forwarded emails.
. if search("^--", "w")
. . " ok, there's a sig.
. . " just go on top of it.
. . execute "normal \<Up>"
. else
. . " no sig: we go at the end of the
. . " document.
. . execute "normal G$"
. endif
endfunction

This is easier:

inoremap ,f <Esc>:call VimFootnotes()<CR>
inoremap ,r <Esc>:exe b:pos<CR>

function! VimFootnotes()
  if exists("b:vimfootnotenumber")
    let b:vimfootnotenumber = b:vimfootnotenumber + 1
    let cr = ""
  else
    let b:vimfootnotenumber = 0
    let cr = "\<CR>"
  endif
  let b:pos = line('.').' | normal! '.virtcol('.').'|'.'4l'
  exe "normal a[".b:vimfootnotenumber."]\<Esc>G"
  if search("-- $", "b")
    exe "normal O".cr."[".b:vimfootnotenumber."] "
  else
    exe "normal o".cr."[".b:vimfootnotenumber."] "
  endif
  startinsert!
endfunction

This works just fine -- and is a pretty cool idea -- except that it would be nice if ,r would restart insert-mode when done. Replacing the beginning of the rhs with <c-o> instead of <Esc> works in all cases except when the footnote was added to the end of the line (more likely than not, actually, since footnotes might be added during the initial text entry).


i really like your 100% function implementation (as opposed to my half-macro), but i have some comments:

  • detail: you don't use an input text to ask the text of the footnote, which i found nicer than providing a goto footnote (see my latest version). it's trivial to change though:
function! VimFootnotes()
  execute "normal ma"
  let footNoteText = input("enter text for footnote: ")
  if exists("b:vimfootnotenumber")
    let b:vimfootnotenumber = b:vimfootnotenumber + 1
    let cr = ""
  else
    let b:vimfootnotenumber = 0
    let cr = "\<CR>"
  endif
  let b:pos = line('.').' | normal! '.virtcol('.').'|'.'4l'
  exe "normal a[".b:vimfootnotenumber."]\<Esc>G"
  if search("-- $", "b")
    exe "normal O".cr."[".b:vimfootnotenumber."] " . footNoteText
  else
    exe "normal o".cr."[".b:vimfootnotenumber."] " . footNoteText
  endif
  execute "normal `a"
endfunction
  • bigger problem: if you insert a footnote, and undo it, and insert a footnote again, the number is incremented once too much. i guess it's impossible to catch undo events for vim functions? :O(

Input field doesn't give possibility to format text


And here is another example how nice (although sophisticated) map can become terrible beast :) Split of the window is a good compromise between input field and going down Play with b:vimfootnotetype :) Alpha, alpha, arabic - roman for footnotes is rare

inoremap ,f <C-O>:call VimFootnotes()<CR>
inoremap ,r <C-O>:q<CR><Right>

let b:vimfootnotetype = "alpha"
function! VimFootnoteType(footnumber)
  if !exists("b:vimfootnotetype")
    let b:vimfootnotetype = "arabic"
  endif
  if (b:vimfootnotetype =~ "^alpha\\|^Alpha")
    if (b:vimfootnotetype =~ "^alpha")
      let upper = "0"
    else
      let upper = "-32"
    endif
    if (a:footnumber <= 26)
      let ftnumber = nr2char(a:footnumber+96+upper)
    elseif (a:footnumber <= 52)
      let ftnumber = nr2char(a:footnumber+70+upper).nr2char(a:footnumber+70+upper)
    else
      let b:vimfootnotenumber = 1
      let ftnumber = nr2char(97+upper)
    endif
  else
    let ftnumber = a:footnumber
  endif
  return ftnumber
endfunction

function! VimFootnotes()
  if exists("b:vimfootnotenumber")
    let b:vimfootnotenumber = b:vimfootnotenumber + 1
    let b:vimfootnotemark = VimFootnoteType(b:vimfootnotenumber)
    let cr = ""
  else
    let b:vimfootnotenumber = 1
    let b:vimfootnotemark = VimFootnoteType(b:vimfootnotenumber)
    let cr = "\<CR>"
  endif
  "let b:pos = line('.').' | normal! '.virtcol('.').'|'.'4l'
  exe "normal a[".b:vimfootnotemark."]\<Esc>"
  let splitposition = &splitbelow
  set splitbelow
  :5 split
  let &splitbelow = splitposition
  normal G
  if search("^-- $", "bW")
    exe "normal O".cr."[".b:vimfootnotemark."] "
  else
    exe "normal o".cr."[".b:vimfootnotemark."] "
  endif
  startinsert!
endfunction

Instead of hard-coding numbers like 97, suggest doing something like char2nr( 'a' ) -- hard-coded numbers frighten me.


I do also vote for the window-splitting approach. I think it is one of the best and cleanest choice we can have.

Otherwise, some minor other improvments can be done like "pluginizing" the script :

It would start with something like :

" ----
if exists("g:loaded_footnote_vim") | finish | endif
let g:loaded_footnote_vim = 1

let s:first_footnote = exists('g:first_footnote') : g:first_footnote ? 1
" Because I don't like to start the footnotes with [0]

if !hasmapto('<Plug>AddVimFootnote', 'i')
 imap <C-X>f <Plug>AddVimFootnote
endif
if !hasmapto('<Plug>AddVimFootnote', 'n')
 nmap <Leader>af <Plug>AddVimFootnote
endif
nnoremap <Plug>AddVimFootnote :call <SID>VimFootnotes('a')<CR>
inoremap <Plug>AddVimFootnote <c-o>:call <SID>VimFootnotes('i')<CR>
"Note: be sure there is *NO* space after the '<CR>' when you copy-paste.

" The previous paragraph enables anyone to remap the functions calls
" to anything else that the developper's default bindings. To do so, add into
" your .vimrc something like :
" nmap ,f <Plug>AddVimFootNote
function! s:VimFootnotes(appendcmd)
 ....
 :below 3sp
 " note that you don't need change the value of 'splitbelow'
 exe "normal ".a:appendcmd."[".b:vimfootnotenumber."]\<Esc>G"
 ...

You could technically parse the line just above the place where the next footnote is going to be placed: something like substitute( getline( '.' - 1 ), '^\[\(\w\+\)\]', '\1', ) should give you the footnote number/letter. Then, either increment it as a number or do a char2nr on it (depending on the footnote style) for the processing. That way, you don't have to worry about not being able to handle decrementing the footnote value upon an undo operation.


There is now a script improving on this tip. You probably want it instead: script#431


Advertisement