FocusedWolf

Vim: Windows-style copy paste search replace

Aug 7th, 2026
58
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
VIM 17.82 KB | None | 0 0
  1. " Windows common keyboard shortcuts and pasting behavior {{{
  2.  
  3. " Recommended vimrc settings:
  4. "     Set clipboard like this:
  5. "         NOTE: Use ^= to prepend a value to a comma-separated list. See :h set^=
  6. "         set clipboard^=unnamedplus            " Works on Linux but has issues yanking on Windows.
  7. "         set clipboard^=unnamed,unnamedplus    " Works on Linux and Windows (and probably MacVim which needed the unnamed option the last time i used it).
  8. "
  9. "     Set mouse like this:
  10. "         set mouse=a    " Ensures mouse binds work in all modes.
  11. "
  12. "     Disable autoselect:
  13. "         NOTE: This forces MiddleMouse clicks in other programs to paste the contents of Vim's "+ unnamedplus register instead of the "* unnamed selection register. See :h go-a
  14. "         set guioptions-=a
  15. "         set guioptions-=A
  16. "
  17. " NOTE: On Linux you need to keep the source-window of a copy-operation open for a paste to occur inside another window.
  18.  
  19. " Functions {{{
  20.  
  21. " Uncomment to enable debugging.
  22. " Check debug output with :messages
  23. " let s:debug_smart_cut = 1
  24. " let s:debug_smart_copy = 1
  25. " let s:debug_smart_paste = 1
  26.  
  27. function! SmartCut() " {{{
  28.     if !has('clipboard')
  29.         echoerr 'Clipboard support is required to use this function.'
  30.         return
  31.     endif
  32.  
  33.     let clipboard_register = '+'
  34.  
  35.     " Restore the visual selection with 'gv' and delete the selection into the clipboard.
  36.     " NOTE: 'd' (delete) is better than 'c' (copy) because it moves the caret to the right for us.
  37.     execute 'normal! gv"' . clipboard_register . 'd'
  38.  
  39.     " Enter insert mode.
  40.     startinsert
  41.  
  42.     " Gets the value if it exists, else returns 0.
  43.     if get(s:, 'debug_smart_cut')
  44.         echomsg "SmartCut"
  45.         echomsg "    clipboard contents: " . getreg(clipboard_register)
  46.     endif
  47. endfunction " }}}
  48.  
  49. function! SmartCopy() " {{{
  50.     if !has('clipboard')
  51.         echoerr 'Clipboard support is required to use this function.'
  52.         return
  53.     endif
  54.  
  55.     let clipboard_register = '+'
  56.  
  57.     " Restore the visual selection with 'gv' and yank the selection into the clipboard.
  58.     execute 'normal! gv"' . clipboard_register . 'y'
  59.  
  60.     " Gets the value if it exists, else returns 0.
  61.     if get(s:, 'debug_smart_copy')
  62.         echomsg "SmartCopy"
  63.         echomsg "    clipboard contents: " . getreg(clipboard_register)
  64.     endif
  65. endfunction " }}}
  66.  
  67. function! SmartPaste() " {{{
  68.     if !has('clipboard')
  69.         echoerr 'Clipboard support is required to use this function.'
  70.         return
  71.     endif
  72.  
  73.     if !&modifiable
  74.         return
  75.     endif
  76.  
  77.     " See :help '> for more information. Hint: if you select some text and press ':' you will see :'<,'>
  78.     " SOURCE: http://superuser.com/questions/723621/how-can-i-check-if-the-cursor-is-at-the-end-of-a-line
  79.     " SOURCE: http://stackoverflow.com/questions/7262536/vim-count-lines-in-selected-range
  80.     " SOURCE: https://git.zug.fr/config/vim/blob/master/init.vim
  81.     " SOURCE: https://git.zug.fr/config/vim/blob/master/after/plugin/zzzmappings.vim
  82.     let currentColumn = col(".")
  83.     let currentLine = line(".")
  84.     let lastVisibleLetterColumn = col("$") - 1
  85.     let lastLineOfBuffer = line("$")
  86.     let selectionEndLine = line("'>")
  87.     let selectionEndLineLength = strchars(getline(selectionEndLine))
  88.     let nextLineLength = strchars(getline(currentLine + 1))
  89.     let selectionStartColumn = col("'<")
  90.     let selectionEndColumn = col("'>")
  91.  
  92.     " If selection does not include or go beyond the last visible character of the line (by also selecting the invisible EOL character).
  93.     if selectionEndColumn < selectionEndLineLength
  94.         let pee = 'P'
  95.  
  96.         " Gets the value if it exists, else returns 0.
  97.         if get(s:, 'debug_smart_paste')
  98.             echomsg "SmartPaste special case #1"
  99.         endif
  100.  
  101.     " If attempting to paste on a blank last line.
  102.     elseif selectionEndLineLength == 0 && selectionEndLine == lastLineOfBuffer
  103.         let pee = 'P'
  104.  
  105.         " Gets the value if it exists, else returns 0.
  106.         if get(s:, 'debug_smart_paste')
  107.             echomsg "SmartPaste special case #2"
  108.         endif
  109.  
  110.     " If selection ends after the last visible character of the line (by also selecting the invisible EOL character),
  111.     " or the line is visually selected (Shift + V),
  112.     " and the next line is not blank,
  113.     " and selection does not end on the last line.
  114.     elseif selectionEndColumn > selectionEndLineLength && nextLineLength > 0 && selectionEndLine != lastLineOfBuffer
  115.         let pee = 'P'
  116.  
  117.         " Gets the value if it exists, else returns 0.
  118.         if get(s:, 'debug_smart_paste')
  119.             echomsg "SmartPaste special case #3"
  120.         endif
  121.  
  122.     else
  123.         let pee = 'p'
  124.  
  125.         " Gets the value if it exists, else returns 0.
  126.         if get(s:, 'debug_smart_paste')
  127.             echomsg "SmartPaste default case"
  128.         endif
  129.     endif
  130.  
  131.     let clipboard_register = '+'
  132.  
  133.     " Gets the value if it exists, else returns 0.
  134.     if get(s:, 'debug_smart_paste')
  135.         echomsg "SmartPaste"
  136.         echomsg "    clipboard contents: " . getreg(clipboard_register)
  137.         echomsg "    currentColumn: " . currentColumn
  138.         echomsg "    currentLine: " . currentLine
  139.         echomsg "    lastVisibleLetterColumn: " . lastVisibleLetterColumn
  140.         echomsg "    lastLineOfBuffer: " . lastLineOfBuffer
  141.         echomsg "    selectionEndLine: " . selectionEndLine
  142.         echomsg "    selectionEndLineLength: " . selectionEndLineLength
  143.         echomsg "    nextLineLength: " . nextLineLength
  144.         echomsg "    selectionStartColumn: " . selectionStartColumn
  145.         echomsg "    selectionEndColumn: " . selectionEndColumn
  146.         echomsg "    pee: " . pee
  147.         echomsg "    selection bounds" . string([getpos("'<")[1:2], getpos("'>")[1:2]])
  148.         echomsg "    visualmode(): " . visualmode()
  149.         echomsg "    mode(): " . mode()
  150.         echomsg "    getpos('.'): " . string(getpos('.'))
  151.     endif
  152.  
  153.     try
  154.         " Enter paste mode.
  155.         " NOTE: This prevents InsertCharPre autocommands from running repeatedly for each character in the pasted string.
  156.         set paste
  157.  
  158.         " Backup clipboard.
  159.         let clipboard_contents = getreg(clipboard_register)
  160.         let clipboard_type = getregtype(clipboard_register)
  161.  
  162.         " If clipboard type is linewise.
  163.         if clipboard_type[0] == 'V'
  164.             " Change clipboard register to characterwise mode.
  165.             call setreg(clipboard_register, clipboard_contents, 'v')
  166.         endif
  167.  
  168.         " Restore the visual selection with 'gv' and paste the clipboard contents there.
  169.         execute 'normal! gv"' . clipboard_register . pee
  170.  
  171.         " Restore clipboard.
  172.         call setreg(clipboard_register, clipboard_contents, clipboard_type)
  173.     catch /E353:\ Nothing\ in\ register/
  174.     finally
  175.         " Exit paste mode.
  176.         set nopaste
  177.     endtry
  178. endfunction " }}}
  179.  
  180. function! SmartFind(...) " {{{
  181.     let l:mode = get(a:000, 0, mode())
  182.     if s:IsNormalMode(l:mode)
  183.         let wordUnderCursor = expand('<cword>')
  184.         if wordUnderCursor == ''
  185.             execute 'promptfind'
  186.         else
  187.             execute 'promptfind' wordUnderCursor
  188.         endif
  189.     else
  190.         let [line_start, column_start] = getpos("'<")[1:2]
  191.         let [line_end, column_end] = getpos("'>")[1:2]
  192.         " TODO: added this but idk if its bad to do because function doesn't strip :'<,'> for certain modes.
  193.         if (line2byte(line_start) + column_start) > (line2byte(line_end) + column_end)
  194.             let [line_start, column_start, line_end, column_end] = [line_end, column_end, line_start, column_start]
  195.         endif
  196.         let lines = getline(line_start, line_end)
  197.         if empty(lines)
  198.             execute 'promptfind'
  199.             return
  200.         endif
  201.         let lines[-1] = lines[-1][: column_end - (&selection ==# 'inclusive' ? 1 : 2)]
  202.         let lines[0] = lines[0][column_start - 1:]
  203.         execute 'promptfind' lines[0]
  204.     endif
  205. endfunction " }}}
  206.  
  207. function! SmartReplace(...) " {{{
  208.     let l:mode = get(a:000, 0, mode())
  209.     if s:IsNormalMode(l:mode)
  210.         let wordUnderCursor = expand('<cword>')
  211.         if wordUnderCursor == ''
  212.             execute 'promptrepl'
  213.         else
  214.             execute 'promptrepl' wordUnderCursor
  215.         endif
  216.     else
  217.         let [line_start, column_start] = getpos("'<")[1:2]
  218.         let [line_end, column_end] = getpos("'>")[1:2]
  219.         " TODO: added this but idk if its bad to do because function doesn't strip :'<,'> for certain modes.
  220.         if (line2byte(line_start) + column_start) > (line2byte(line_end) + column_end)
  221.             let [line_start, column_start, line_end, column_end] = [line_end, column_end, line_start, column_start]
  222.         endif
  223.         let lines = getline(line_start, line_end)
  224.         if empty(lines)
  225.             execute 'promptrepl'
  226.             return
  227.         endif
  228.         let lines[-1] = lines[-1][: column_end - (&selection ==# 'inclusive' ? 1 : 2)]
  229.         let lines[0] = lines[0][column_start - 1:]
  230.         execute 'promptrepl' lines[0]
  231.     endif
  232. endfunction " }}}
  233.  
  234. function! SmartSaveFile() " {{{
  235.     " SOURCE: http://vim.wikia.com/wiki/Map_Ctrl-S_to_save_current_or_new_files
  236.  
  237.     " If file is saved.
  238.     if !&modified
  239.         return
  240.     endif
  241.  
  242.     try
  243.         " If the current buffer has no name.
  244.         if empty(bufname('%'))
  245.             " Display a save dialog so the user can specify a file name.
  246.             browse confirm write
  247.         else
  248.             " Force a write even if a file exists with that name.
  249.             confirm write
  250.         endif
  251.     catch /E212:\ Can't\ open\ file\ for\ writing/
  252.        if has("win32") || has("win64")
  253.            echomsg "ERROR: You lack write permission for this directory or the file name is not valid."
  254.        else
  255.            " NOTE: The [browse confirm write] call above will establish the filename in the case of an empty buffer.
  256.            " NOTE: Try-catch needed because [redraw!] may erase an error message.
  257.            try
  258.                " A hack that uses tee to output the buffer to the % filename with sudo permissions.
  259.                " SOURCE: https://stackoverflow.com/questions/2600783/how-does-the-vim-write-with-sudo-trick-work
  260.                execute("w !sudo tee %")
  261.  
  262.                if v:shell_error
  263.                    throw 'Failed to save file.'
  264.                else
  265.                    " Reload the file, otherwise vim will detect the file changed and ask you for permission to reload the file.
  266.                    silent edit!
  267.  
  268.                    " Clears the command-line and removes 'Press ENTER or type command to continue' prompt.
  269.                    " SOURCE: https://vim.fandom.com/wiki/Avoiding_the_%22Hit_ENTER_to_continue%22_prompts
  270.                    redraw!
  271.                endif
  272.            catch /.*/
  273.                echoerr 'SmartSaveFile() Unhandled Exception: ' . v:exception
  274.            endtry
  275.        endif
  276.    endtry
  277. endfunction " }}}
  278.  
  279. function! SmartCopyMessageHistory() " {{{
  280.    redir @+
  281.    silent execute(':messages')
  282.    redir END
  283. endfunction " }}}
  284.  
  285. " }}} Functions
  286.  
  287. " Binds {{{
  288.  
  289. " Paste for MiddleMouse in terminal, normal, visual, and insert mode {{{
  290.  
  291. tnoremap <MiddleMouse> <C-w>"+
  292.  
  293. " NOTE: <C-u> removes the '<,'> visual-selection from the command-line. See :h c_CTRL-u
  294. nnoremap <silent> <MiddleMouse> <C-v>:<C-u>call SmartPaste()<CR>
  295.  
  296. " NOTE: <C-u> removes the '<,'> visual-selection from the command-line. See :h c_CTRL-u
  297. vnoremap <silent> <MiddleMouse> :<C-u>call SmartPaste()<CR>
  298.  
  299. " Characterwise paste for insert mode.
  300. " SOURCE: https://vim.fandom.com/wiki/Pasting_registers
  301. " NOTE: <C-o> executes a normal-mode command without leaving insert mode. See :help ins-special-special
  302. " NOTE: <C-r>+ inserts the contents of the unnamedplus register during insert mode. See :h i_CTRL-R
  303. " NOTE: <C-r>+ doesn't work for Ctrl-v yanked and pasted columns of text. It forces characterwise selections to be linewise pasted. <C-r><C-o>+ works though.
  304. " [bad, triggers InsertCharPre for each pasted letter] inoremap <MiddleMouse> <C-r><C-r>+
  305. " [works] inoremap <MiddleMouse> <C-o>"+<MiddleMouse>
  306. " [works, but no need for paste mode i think because it doesn't trigger InsertCharPre events for each pasted letter] inoremap <MiddleMouse> <C-o>:set paste<CR><C-r><C-o>+<C-o>:set nopaste<CR>
  307. inoremap <MiddleMouse> <C-r><C-o>+
  308.  
  309. " Disable weird multi-click things you can do with middle mouse button.
  310. " SOURCE: http://vim.wikia.com/wiki/Mouse_wheel_for_scroll_only_-_disable_middle_button_paste
  311. tnoremap <2-MiddleMouse> <Nop>
  312. nnoremap <2-MiddleMouse> <Nop>
  313. inoremap <2-MiddleMouse> <Nop>
  314. vnoremap <2-MiddleMouse> <Nop>
  315. tnoremap <3-MiddleMouse> <Nop>
  316. nnoremap <3-MiddleMouse> <Nop>
  317. inoremap <3-MiddleMouse> <Nop>
  318. vnoremap <3-MiddleMouse> <Nop>
  319. tnoremap <4-MiddleMouse> <Nop>
  320. nnoremap <4-MiddleMouse> <Nop>
  321. inoremap <4-MiddleMouse> <Nop>
  322. vnoremap <4-MiddleMouse> <Nop>
  323.  
  324. " }}} Paste for MiddleMouse in terminal, normal, visual, and insert mode
  325.  
  326. " If OS is not mac.
  327. if system('uname') !~ 'Darwin' " NOTE: MacVim provides Command+C|X|V|A|S and undo/redo functionality and also can Command+C|V to the command-line by default.
  328.     " Paste functionality for terminal mode {{{
  329.  
  330.     tnoremap <C-v> <C-w>"+
  331.  
  332.     " }}} Paste functionality for terminal mode
  333.  
  334.     " Copy, paste, and select-all functionality for command-line {{{
  335.  
  336.     " SOURCE: https://opensource.apple.com/source/vim/vim-62.41.2/runtime/macmap.vim.auto.html
  337.     " NOTE: Only copy and paste are possible in the command-line from what i can tell.
  338.     "       Their is no undo for text typed in the command-line and you cannot paste text onto a selection of text to replace it.
  339.     cnoremap <C-c> <C-y>
  340.     cnoremap <C-v> <C-r>+
  341.  
  342.     " Everyone seems to bind this. No way to select-all, but we can move the caret all the way to the left of the command-line. Yay.
  343.     cnoremap <C-A> <Home>
  344.  
  345.     " Copy message history to the clipboard.
  346.     " NOTE: This is a workaround for not being able to select all.
  347.     nnoremap <silent> <Leader>mh :<C-u>call SmartCopyMessageHistory()<CR>
  348.  
  349.     " }}} Copy, paste, select-all functionality for command-line
  350.  
  351.     " Cut, copy, paste functionality for visual and insert mode {{{
  352.  
  353.     " Cut, copy, and paste functionality for visual mode.
  354.     " SOURCE: http://superuser.com/questions/10588/how-to-make-cut-copy-paste-in-gvim-on-ubuntu-work-with-ctrlx-ctrlc-ctrlv
  355.     " NOTE: <C-u> removes the '<,'> visual-selection from the command-line. See :h c_CTRL-u
  356.     vnoremap <silent> <C-x> :<C-u>call SmartCut()<CR>
  357.     vnoremap <silent> <C-c> :<C-u>call SmartCopy()<CR>
  358.     vnoremap <silent> <C-v> :<C-u>call SmartPaste()<CR>
  359.  
  360.     " Characterwise paste for insert mode.
  361.     " SOURCE: https://vim.fandom.com/wiki/Pasting_registers
  362.     " NOTE: <C-o> executes a normal-mode command without leaving insert mode. See :help ins-special-special
  363.     " NOTE: <C-r>+ inserts the contents of the unnamedplus register during insert mode. See :h i_CTRL-R
  364.     " NOTE: <C-r>+ doesn't work for Ctrl-v yanked and pasted columns of text. It forces characterwise selections to be linewise pasted. <C-r><C-o>+ works though.
  365.     " [works, but no need for paste mode i think because it doesn't trigger InsertCharPre events for each pasted letter] inoremap <C-v> <C-o>:set paste<CR><C-r><C-o>+<C-o>:set nopaste<CR>
  366.     inoremap <C-v> <C-r><C-o>+
  367.  
  368.     " }}} Cut, copy, paste functionality for visual and insert mode
  369.  
  370.     " Select-all functionality for normal, visual, and insert mode {{{
  371.  
  372.     " SOURCE: http://vim.wikia.com/wiki/Using_standard_editor_shortcuts_in_Vim
  373.     nnoremap <C-a> ggVG
  374.     " <ESC> needed to prevent the existing characterwise selection from getting converted into a linewise selection (all the way home for the current line).
  375.     vnoremap <C-a> <Esc>ggVG
  376.     inoremap <C-a> <Esc>ggVG
  377.  
  378.     " }}} Select-all functionality for normal, visual, and insert mode
  379.  
  380.     " Save functionality for normal, visual, and insert mode {{{
  381.  
  382.     nnoremap <silent> <C-s> :call SmartSaveFile()<CR>
  383.  
  384.     " NOTE: <C-u> removes the '<,'> visual-selection from the command-line. See :h c_CTRL-u
  385.     vnoremap <silent> <C-s> :<C-u>call SmartSaveFile()<CR>gv
  386.  
  387.     " NOTE: <C-o> executes a normal-mode command without leaving insert mode. See :help ins-special-special
  388.     inoremap <silent> <C-s> <C-o>:call SmartSaveFile()<CR>
  389.  
  390.     " }}} Save functionality for normal, visual, and insert mode
  391.  
  392.     " Undo and redo functionality for normal, visual, and insert mode {{{
  393.  
  394.     nnoremap <C-z> <Esc>u
  395.     nnoremap <C-y> <Esc><C-r>
  396.  
  397.     " NOTE: <C-u> removes the '<,'> visual-selection from the command-line. See :h c_CTRL-u
  398.     vnoremap <C-z> :<C-u>uV
  399.     vnoremap <C-y> :<C-u><C-r>V
  400.  
  401.     inoremap <C-z> <Esc>uI
  402.     inoremap <C-y> <Esc><C-r>I
  403.  
  404.     " Disable Vim normal-mode undo/redo keys.
  405.     " Having a single key for undo is an accident waiting to happen, especially when 'u' and 'U' are also used as visual-mode character case toggles.
  406.     " SOURCE: https://stackoverflow.com/questions/57714401/vi-vim-remap-or-unmap-built-in-command-the-u-key-in-visual-mode
  407.     nnoremap u <NOP>
  408.     nnoremap <C-r> <NOP>
  409.  
  410.     " }}} Undo and redo functionality for normal, visual, and insert mode
  411.  
  412.     " Find and Replace functionality for normal, visual, and insert mode {{{
  413.  
  414.     nnoremap <silent> <C-f> :call SmartFind('n')<CR>
  415.     nnoremap <silent> <C-h> :call SmartReplace('n')<CR>
  416.  
  417.     " NOTE: <C-u> removes the '<,'> visual-selection from the command-line. See :h c_CTRL-u
  418.     vnoremap <silent> <C-f> :<C-u>call SmartFind('v')<CR>
  419.     vnoremap <silent> <C-h> :<C-u>call SmartReplace('v')<CR>
  420.  
  421.     " NOTE: <C-o> executes a normal-mode command without leaving insert mode. See :help ins-special-special
  422.     inoremap <silent> <C-f> <C-o>:call SmartFind('i')<CR>
  423.     inoremap <silent> <C-h> <C-o>:call SmartReplace('i')<CR>
  424.  
  425.     " }}} Find and Replace functionality for normal, visual, and insert mode
  426. endif
  427.  
  428. " }}} Binds
  429.  
  430. " }}} Windows common keyboard shortcuts and pasting behavior
Advertisement
Add Comment
Please, Sign In to add comment