aboutsummaryrefslogtreecommitdiff
path: root/autoload/paste_insert.vim
blob: fe9e482fce162378741a3dce4cfc61ec0812594a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
" Autoloaded entry point function
function! paste_insert#() abort

  " Set up an event table
  augroup paste_insert
    autocmd!

    " Set up the paste and tell the user
    autocmd User Start
          \ call s:Start() | echo 'Paste ready'

    " When starting insert mode, add completion hook for when we leave
    autocmd InsertEnter *
          \ autocmd paste_insert InsertLeave *
                \ doautocmd paste_insert User Complete

    " User waits too long in normal mode, timeout
    autocmd CursorHold *
          \ doautocmd paste_insert User Timeout

    " User leaves the buffer or window, abort
    autocmd BufLeave,WinLeave *
          \ doautocmd paste_insert User Abort

    " Exit condition reporting
    autocmd User Abort
          \ echo 'Paste aborted'
    autocmd User Cancel
          \ echo 'Paste cancelled'
    autocmd User Complete
          \ echo 'Paste completed'
    autocmd User Timeout
          \ echo 'Paste timeout'

    " End the paste and clear the events table
    autocmd User Abort,Cancel,Complete,Timeout
          \ call s:Stop() | autocmd! paste_insert

  augroup END

  " Trigger the starting actions
  doautocmd paste_insert User Start

endfunction

" 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>'])

" 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
endfunction

" Start the paste: save each cancel key's prior function, remap it, set
" 'paste'
function! s:Start() abort

  " 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

endfunction

" Stop the paste: unset 'paste', restore prior function of cancel key
function! s:Stop() abort

  " Let's stop!
  set nopaste

  " 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

endfunction