20

I recently removed the YankRing plugin from my setup partly because it was causing some problems with macros, but mostly because I decided it would be better to train myself to use Vim's registers instead — in particular, the way that deleted text is stored initially in register "1 and then shifted up through "2, "3, etc. as further deletions are made is very useful.

However, when a deletion/change removes less than one line of text, it is instead stored in the "- small delete register (with a few exceptions for certain movement commands). If subsequent small deletions are made, that bit of text is lost.

Is it possible to get Vim to store all deletions in register "1?

nnoremap d "1d almost works, but you end up with the text in both registers "1 and "2.

I don't mind whether or not the functionality of the small delete register is affected. Suggesting I change my behaviour to save small deletions to a named register would definitely not be a helpful answer — that's what I'm currently doing.

4 Answers 4

12

This used to work in older Vim 7.3 versions when you :set clipboard=unnamed (see this Reddit discussion, as a result of a bug, which I've reported here.

That thread contains a patch by Aryeh Leib Taurog which introduces a 'regone' option. This has made it into the (overly long) todo list, but it's unlikely to be incorporated soon. So, you currently only have the option to downgrade to an outdated Vim, or build Vim yourself with that patch (and push for adoption on the vim_dev mailing list).

1
  • 1
    Interesting! (Especially your idea about the threshold.) Thanks for the info. Gonna hold off on the upvote/accept for the time being in an attempt to encourage someone to come up with some crazy VimScript to do this in the interim.
    – Rich
    Mar 11, 2015 at 13:35
6

I've got a proof-of-concept solution that works for the d command with motions and in visual mode. It's not a fully robust solution yet (for example, d[count]d doesn't work, and neither does [count]D), but it very nearly covers all my actual use cases.

It works by setting a custom operator function that:

  1. stores the contents of the registers 1–8 in a dictionary,
  2. performs the deletion into register 1,
  3. sets registers 2–9 to the previously saved contents of registers 1–8.

See :help map-operator for an explanation of how operator functions work.

function! ShiftingDeleteOperator(type)
  let reg_dict = {}
  for k in range(1, 8)
    execute printf("let reg_dict[%d]=getreg('%d', 1)", k, k)
  endfor

  if a:type ==# 'v'
    execute 'normal! `<v`>d'
  elseif a:type ==# 'V'
    execute 'normal! `<V`>d'
  elseif a:type ==# "\<C-V>"
    execute "normal! `<\<C-V>`>d"
  elseif a:type ==# 'char'
    execute 'normal! `[v`]d'
  elseif a:type ==# 'line'
    execute "normal! '[V']d"
  else
    return
  endif

  let deleted = getreg('"', 1)
  call setreg(1, deleted)

  for [k, v] in items(reg_dict)
    execute printf("call setreg(%d, v)", k + 1)
  endfor
endfunction

" Call the function for d{motion} via operatorfunc
nnoremap <silent> d :set operatorfunc=ShiftingDeleteOperator<CR>g@
" Call the function when d or x are hit in visual mode
vnoremap d :<C-U>call ShiftingDeleteOperator(visualmode())<CR>
vnoremap x :<C-U>call ShiftingDeleteOperator(visualmode())<CR>
" Use the d{motion} as defined above to add limited support for `D` command
nmap D d$
" Use the standard dd command
nnoremap dd dd 
1
  • Sorry for commenting on an old answer. I adopted this solution to y and c but for c my nvim doesn't enter insert mode after the action... Currently I have to execute l and startinsert which isn't a proper fix (like c$ would put the cursor in the wrong position). Any ideas?
    – Sunny Pun
    Jun 28, 2018 at 4:53
4

Check out the Pull Request into the neovim editor:

[RFC] Add an option to use numbered registers for smaller deletions #8169

I've tried to add the smalldel option, which allows you to configure the minimum number of deleted characters to be pushed into the numbered registers "1.."9

The next neovim release after this PR is merged (if this happens) will contain this option.

4

As of version 8.0.1394, you can implement this as an autocommand that hooks into the new TextYankPost autocommand. This is somewhat cleaner than my previous map-based answer, and works regardless of what keystrokes you use for deleting:

if exists("##TextYankPost")
  function! SmallDeleteRing(event) abort
    if a:event['operator'] == 'y'
      " Don't care about actual yanks
      return
    endif
    if a:event['regtype'] ==# 'V'
      " Vim already handles linewise deletions
      return
    endif
    
    let regcontents = a:event['regcontents']
    if len(regcontents) > 1
      " Vim already handles deletions spanning multiple lines
      return
    endif

    let deleted = regcontents[0]

    if len(deleted) == 1
      " Don't want to catch single-character deletions (in particular, x)
      return
    endif

    " Grab registers 1-8
    let one_through_eight = mapnew(range(1, 8), {k, v -> getreg(v)})

    " Set register "1
    call setreg(1, deleted)

    " Set registers 2-9
    for i in range(1, 8)
      call setreg(i + 1, one_through_eight[i - 1])
    endfor
  endfunction

  augroup small_delete_ring
    autocmd!
    autocmd TextYankPost * call SmallDeleteRing(v:event)
  augroup END
endif
5
  • I get an E117: Unknown function: mapnew when running this (currently using neovim nightly) Aug 10, 2021 at 12:37
  • 2
    @LuizMartins Hmm. I guess mapnew isn't in newvim. Maybe it's new? Try replacing that line with let one_through_eight = range(1, 8) | call map(one_through_eight, {k, v -> getreg(v)})
    – Rich
    Aug 11, 2021 at 16:30
  • Yep, that worked! Thank you. Aug 11, 2021 at 21:34
  • I'm not sure if it is something specific to me, but deleting only one character (e.g. dl) doesn't seem to save it in the delete registers. Aug 18, 2021 at 18:59
  • 1
    @LuizMartins That's deliberate. If you don't like that behaviour, delete the following if block from the code: if len(deleted) == 1 ... " Don't want to catch single-character deletions (in particular, x) ... endif (about halfway down the function definition)
    – Rich
    Aug 19, 2021 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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