2

I used to have some simple map macros that implemented a "pager mode" for vi.

When I was reading through a very long file, I would activate pager mode. This would make two key maps:

  • <Space> would be mapped to: ^F^G
  • <Backspace> would be mapped to: ^B^G

Not that weird. Space would go to the next whole screenful, and then display the filename status line (that also includes line number and percent progress through the file). Backspace went to previous screen and displayed the status line.

But I didn't want these active all the time; I wanted to be able to activate and deactivate them. So I bound a pair of key sequences:

]p would "activate pager mode" (i.e. would make the bindings above) ]P would "deactivate pager mode" (i.e. would unmap those two keys)

This is tricky because mapping the space bar can be tricky, and because I was trying to write a macro that would run two map commands.

Way back in the day, I'm pretty sure this used to work:

"this was in .exrc
"note: all "^" sequences were actual control characters
map ]p :map ^V^V^V  ^F^G^M:map ^V^V^? ^B^G^M
map ]P :unmap ^V^V^V ^M:unmap ^V^V^?^M

All ^ sequences were actual control characters. ^V was <Ctrl+V> and so on.

^V quotes something, and I think I had to double it to get a single ^V in the macro that would run.

So, the ]p macro, when run, would map the two keys, and ]P would unmap them.

I don't use these every day and I'm not sure when they broke. But at some point I tried them, and they were broken. Recently I decided to invest the time to get these working. I have a kludge working but it's not elegant, and it only flips pages; the part where it should display the status line simply doesn't work. I tried both including ^G and explicitly saying :file^M and while I don't have any errors, it also doesn't display anything. It does nothing.

All I ever run these days is Vim so I'm specifically looking for a solution that will work in Vim and I don't care if it's Vim-specific.

Because this is a map that is supposed to do a map the quoting issues are tedious, and because we are binding space bar quoting is even trickier. It seems like there ought to be an elegant way to do this by making Vimscript functions that would map or unmap the keys, and then binding ]p and ]P to call the functions.

If anyone really wants I'll post my kludge here, but I'm hoping someone with more Vim knowledge can make something better so I'm not posting it now.

In summary:

  • ]p should map the keys
  • ]P should unmap the keys
  • When mapped, <Space> should do the same thing as: ^F^G
  • When mapped, <Backspace> should do the same thing as: ^B^G
1

This is how you would do it with expression mappings:

nnoremap <expr> <space> get(g:, 'pager_mode', 0) ? "\<c-f>:redraw\<cr>\<c-g>" : "\<space>"
nnoremap <expr> <bs> get(g:, 'pager_mode', 0) ? "\<c-b>:redraw\<cr>\<c-g>" : "\<bs>" 
nnoremap ]p :<c-u>let g:pager_mode = 1<cr>:echo "Pager mode"<cr>
nnoremap ]P :<c-u>let g:pager_mode = 0<cr>:echo "Nopager mode"<cr>

Note: ]p & ]P can be useful. I would personally suggest a different key and maybe make it toggle instead. Maybe something like:

nnoremap <leader>p :<c-u>let g:pager_mode = !get(g:, 'pager_mode', 0)<cr>echo get(['Nopager', 'Pager'], g:pager_mode) . ' mode'<cr>
  • Thank you! I like how you implemented this and echoing the mode is a nice feature. I learned vi a number of years ago, and I barely use the vim-specific features. Back when I first implemented this it was real vi and ]p and ]P were unused; only ]] was used, so I bound lots of macros that started with ] (]v to edit my .vimrc file, or back then my .exrc file; ]s to "source" the settings file to pick up new edited changes; etc.) – steveha Mar 20 at 8:12
1

There lots of variations, including <expr> mappings, but I'm just going to outline a pair of functions which you can drop in ~/.vim/autoload/pager.vim:

function! pager#map() abort
  if get(b:, 'pager_maps', v:false)
    return
  endif

  let b:pager_maps = v:true
  nnoremap <buffer> <silent> <Space> <C-f><C-g>
  nnoremap <buffer> <silent> <BS> <C-b><C-g>
endfunction

function! pager#unmap() abort
  if ! get(b:, 'pager_maps', v:false)
    return
  endif

  let b:pager_maps = v:false
  silent! nunmap <buffer> <Space>
  silent! nunmap <buffer> <BS>
endfunction

Then, in your ~/.vim/vimrc, you can do

nnoremap ]p :call pager#map()<CR>
nnoremap ]P :call pager#unmap()<CR>
" I'm including [p because I think it's easier to remember, just as an example
nnoremap [p :call pager#unmap()<CR>

I've included the <buffer> modifiers to ensure the mappings only happen in the the desired buffer; they can be removed to make the mappings global.

I've also silence the unmapping commands, as they can error if the mappings aren't present (e.g., because you unmapped them manually). Because of the abort attribute, these errors would cause the function to terminate.

  • There are probably more elegant solutions (such as only creating the ]P mapping in pager mode, &c.) but this is a simple and readable start. – D. Ben Knoble Mar 6 at 22:48
  • I think you should be using b:pager_mode if you are going to use buffer local mappings. You can use get() to safely query the value, e.g. get(b:, 'pager_mode', 0) – Peter Rincker Mar 6 at 22:56
  • Good point; forgot to switch that when editing. @PeterRincker – D. Ben Knoble Mar 6 at 22:56
  • 1
    I just tried this and it works perfectly! I like how you saved the function in its own file. – steveha Mar 20 at 8:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.