Duplicate tip
This tip is very similar to the following:
These tips need to be merged – see the merge guidelines.
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