diff options
authorTom Ryder <tom@sanctum.geek.nz>2019-06-23 01:24:37 +1200
committerTom Ryder <tom@sanctum.geek.nz>2019-06-23 01:24:37 +1200
commit17de75eb35838c3f588f1556f53dc574d78a2faf (patch)
parentMerge branch 'release/v0.2.0' (diff)
parentBump VERSION (diff)
Merge branch 'release/v0.3.0'v0.3.0
* release/v0.3.0: Revert "Clear event table only once so user can... Flesh out documentation a lot Flesh out comment a little Don't fill out .buffer property if already set Catch specific mapping errors Add lots of comments Clear event table only once so user can use it Clarify explanation of map settings a bit More sophisticated map caching Get normal mode map specifically for cancel Support mapping cancel keys, add Esc as default
3 files changed, 150 insertions, 28 deletions
diff --git a/VERSION b/VERSION
index 0ea3a94..0d91a54 100644
@@ -1 +1 @@
diff --git a/autoload/paste_insert.vim b/autoload/paste_insert.vim
index 277f333..fe9e482 100644
--- a/autoload/paste_insert.vim
+++ b/autoload/paste_insert.vim
@@ -43,29 +43,125 @@ function! paste_insert#() abort
-" Key that cancels the pending paste in normal mode, defaults to CTRL-C
-let s:cancel = get(g:, 'paste_insert_cancel', '<C-C>')
+" Keys that cancel the pending paste in normal mode, defaults to Ctrl-C and
+" Escape
+let s:cancel = get(g:, 'paste_insert_cancel', ['<C-C>', '<Esc>'])
-" Start the paste: save cancel key's prior function, remap it, set 'paste'
+" Cache for the prior functions of those keys, in case the user has already
+" mapped them
+let s:maps = {}
+" Function to abstract over shortcomings in Vim older than 7.3.032 in yielding
+" incomplete map information; return as much as we can in the same structure
+" as Vim after this patchlevel does with the {dict} parameter TRUE;
+" unfortunately, this is just the mapping's right hand side. We should be
+" able to figure out the <buffer> flag too, but that's it. The documentation
+" for this plugin points out this unfortunate caveat, and suggests
+" a workaround.
+function! s:MapArg(key, mode) abort
+ if v:version > 703 || v:version == 703 && has('patch-32')
+ return maparg(a:key, a:mode, 0, 1)
+ else
+ let rhs = maparg(a:key, a:mode)
+ if rhs ==# ''
+ return {}
+ else
+ return { 'rhs': rhs }
+ endif
+ endif
+" Start the paste: save each cancel key's prior function, remap it, set
+" 'paste'
function! s:Start() abort
- let s:maparg = maparg(s:cancel)
- let command = join([
- \ 'nnoremap'
- \,s:cancel
- \,':<C-U>doautocmd paste_insert User Cancel<CR>'
- \])
- execute command
+ " Collect and remove existing mappings for this key, replacing with a global
+ " map that cancels the pending paste operation
+ "
+ for key in s:cancel
+ let maps = []
+ " We might need to stop partway through this
+ try
+ " Collect first mapping, perhaps a buffer-local mapping, since maparg()
+ " yields those first
+ let map = s:MapArg(key, 'n')
+ if !empty(map)
+ call add(maps, s:MapArg(key, 'n'))
+ endif
+ execute 'nunmap <buffer> '.key
+ " Since we got here, the `:nunmap <buffer>` command must have worked,
+ " and there might be one more global map to collect
+ let map = s:MapArg(key, 'n')
+ if !empty(map)
+ call add(maps, s:MapArg(key, 'n'))
+ endif
+ execute 'nunmap '.key
+ catch /^Vim\%((\a\+)\)\=:E31:/ " No such mapping
+ " Ignore it
+ endtry
+ " If we collected two mappings, the first one must have been buffer-local,
+ " and Vim before 7.3.032 won't have told us that, so we'll flag it now
+ "
+ if len(maps) == 2 && !has_key(maps[0], 'buffer')
+ let maps[0]['buffer'] = 1
+ endif
+ " Add the collected maps to this key's cache
+ let s:maps[key] = maps
+ " Set up our map to cancel the pending paste
+ execute 'nnoremap '.key
+ \.' :<C-U>doautocmd paste_insert User Cancel<CR>'
+ endfor
+ " Let's go!
set paste
" Stop the paste: unset 'paste', restore prior function of cancel key
function! s:Stop() abort
+ " Let's stop!
set nopaste
- let command = join(
- \ s:maparg !=# ''
- \ ? ['nnoremap', s:cancel, s:maparg]
- \ : ['nunmap', s:cancel]
- \)
- execute command
- unlet s:maparg
+ " Now we need to remove our temporary maps, and restore the user's
+ for key in s:cancel
+ " Start by getting read of our map; there should now be none at all
+ execute 'nunmap '.key
+ " Iterate through any cached maps for this key
+ for map in reverse(s:maps[key])
+ " Start building the command to restore the mapping; assume this is
+ " a nonrecursive map unless flagged otherwise
+ let command = !has_key(map, 'noremap') || map['noremap']
+ \ ? ['nnoremap']
+ \ : ['nmap']
+ " Restore any flags for the mapping as we know them
+ for flag in ['buffer', 'expr', 'nowait', 'silent']
+ if has_key(map, flag) && map[flag]
+ call add(command, '<'.flag.'>')
+ endif
+ endfor
+ " Add the key and right hand
+ call extend(command, [key, map['rhs']])
+ execute join(command)
+ endfor
+ " Clear the map cache for this key, now that we've restored them
+ unlet s:maps[key]
+ endfor
diff --git a/doc/paste_insert.txt b/doc/paste_insert.txt
index 73036e4..d9b2bb2 100644
--- a/doc/paste_insert.txt
+++ b/doc/paste_insert.txt
@@ -1,4 +1,4 @@
-*paste_insert.txt* For Vim version 7.0 Last change: 2019 Jun 19
+*paste_insert.txt* For Vim version 7.0 Last change: 2019 Jun 23
DESCRIPTION *paste_insert*
@@ -8,28 +8,54 @@ after the insert ends, to avoid the annoyances caused by forgetting to do so.
It includes a timeout if insert mode isn't entered within 'updatetime'
seconds, or if the user navigates away from the buffer or window. It can also
-be cancelled with a key in normal mode, by default CTRL-C.
+be cancelled with keys in normal mode, by default CTRL-C or Escape.
REQUIREMENTS *paste_insert-requirements*
This plugin only loads if 'compatible' is not set.
+MAPPINGS *paste_insert-mappings*
+ *<Plug>(PasteInsert)*
+The `<Plug>(PasteInsert)` map in normal mode just does `:PasteInsert`. This
+is probably the way you want to use this plugin. To use `<Leader>p`, for
+example, you might do this in your |vimrc|:
+ nmap <Leader>p <Plug>(PasteInsert)
COMMANDS *paste_insert-commands*
Enter the `:PasteInsert` command just before entering insert mode to set up
-the relevant hooks. It takes neither range prefix nor arguments.
-MAPPINGS *paste_insert-mappings*
- *<Plug>(PasteInsert)*
-The `<Plug>(PasteInsert)` map in normal mode just does `:PasteInsert`.
+the relevant hooks. It takes neither range prefix nor arguments. It's made
+available for you if you want it, but most users probably just want the
+|<Plug>(PasteInsert)| mapping above.
OPTIONS *paste_insert-options*
-Set `g:paste_insert_cancel` to the key you want to cancel the pending paste in
-normal mode. This defaults to '<C-C>', for CTRL-C.
+Set `g:paste_insert_cancel` to a |List| of the special codes for keys you want
+to cancel the pending paste in normal mode. `['<C-C>', '<Esc>']` is the
+default, for CTRL-C and Escape.
+The plugin will overwrite any existing normal mode maps for these keys during
+the normal mode phase of the operation, but will try to restore them
+afterwards. If you don't want your mappings touched at all, set this to the
+empty list, and the plugin will leave them alone.
+CAVEATS *paste_insert-caveats*
+By design (Vim's), mappings don't work in paste mode. If you break the insert
+with CTRL-C, the |InsertLeave| event doesn't fire, and you'll still be in
+'paste' mode, just as you would be without this plugin. Leaving the buffer or
+window should fix it. Friends don't let friends use CTRL-C to break insert
+The map restoration for your chosen cancel keys works well in Vim v7.3.032 or
+newer, but before that patch, recursive maps and flags like <expr> or <silent>
+won't be restored correctly, although <buffer> should still work. If it's
+mangling your mappings and you don't want to change keys, your only option is
+to upgrade Vim.
AUTHOR *paste_insert-author*