aboutsummaryrefslogtreecommitdiff
path: root/plugin/insert.vim
blob: 722c0b5adeca4c608565f469b86aedb6816a05c5 (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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
if !exists('g:field_marshal_insert_include_bindings')
  let g:field_marshal_insert_include_bindings = 1
endif

if g:field_marshal_insert_include_bindings
  " Like 'I', but skip past the first v:count1 WORDs. Useful for when one wants
  " the 'I' behavior to respect comments.
  "
  " If there are fewer than v:count1 WORDs in the line, then append to the end of
  " the line.
  "
  " Have to cheese this a little bit to get the redo (.) operator to work with it.
  " This is done by making a "sort-of" 0-width text object that exists where the
  " change should happen.
  "
  " If the 'g' is present, that acts similar to gI -- it does not skip
  " whitespace and starts insertion directly after the v:count1'th word.
  noremap cI <Plug>(insert-after-comment)
  noremap cgI <Plug>(insert-after-comment-g)

  " Insert before/after a motion.
  "
  "  Zi - takes a text object, moves to the beginning of that text object and
  "       places the user in INSERT mode.
  "
  "  Za - takes a text object, moves to the end of that text object and places
  "       the user in INSERT mode.
  "
  "  Some of this functionality can be replicated using <C-R><C-P> in insert
  "  mode. For example, suffixing the current word with 'ed' can be
  "  accomplished with either:
  "
  "    Zaeed<esc>
  "
  "    or
  "
  "    ce^R^P"ed<esc>
  "
  "  or Prefixing with sub can be accomplished with
  "
  "    Zibsub<esc>
  "
  "  or
  "
  "    cbsub^R^P"<esc>
  "
  "  However the traditional ^R^P" solution is not very pretty and prone to
  "  typeos.
  "
  "  This also means that certain motions are now redundant and can be
  "  emulated. For example:
  "
  "  I  === Zi^
  "  gI === Zi0
  "  A  === Za$
  "
  "
  noremap Zi <Plug>(insert-before-motion)
  noremap Za <Plug>(append-after-motion)
  noremap Zo <Plug>(open-after-motion)
  noremap ZO <Plug>(OPEN-after-motion)

  noremap Z[ <Plug>(to-object-start)
  noremap Z] <Plug>(to-object-end)

  " Zgi -- start another insert using the same parameters from the last Z[ia]
  noremap Zgi c<Plug>(insert-about-obj)

  vnoremap <expr> Zi "\<esc>'<" . (visualmode() == "V" ? "0" : "") . "i"
  vnoremap <expr> Za "\<esc>'>" . (visualmode() == "V" ? "$" : "") . "a"
endif

noremap <Plug>(insert-after-comment) <cmd>call <sid>insert_comment_count(v:count1)<cr>1c<Plug><sid>(insert-comment-obj-nog)
noremap <Plug>(insert-after-comment-g)> <cmd>call <sid>insert_comment_count(v:count1)<cr>1c<Plug><sid>(insert-comment-obj-g)

onoremap <Plug><sid>(insert-comment-obj-nog) <cmd>call <sid>insert_comment_obj(0)<cr>
onoremap <Plug><sid>(insert-comment-obj-g) <cmd>call <sid>insert_comment_obj(1)<cr>

" Set the count for the insert comment command.
function! s:insert_comment_count(count) abort
  let s:count = a:count
endfunction

" The function which will set the insert comment.
function! s:insert_comment_obj(g, ...) abort
  if v:operator == 'c'
    let end = 0
    normal! 0
    let i = 0
    call search('^\s*\zs\S', '', line('.'))
    while i < s:count
      if col('.') == col('$') - 1
        let end = 1
        break
      endif

      if a:g
        let pattern = '\S*\zs\ze\s\+'
      else
        let pattern = '\S*\s\+\zs'
      endif

      if ! search(pattern, '', line('.'))
        let end = 1
        break
      endif

      let i += 1
    endwhile

    " Cheese because 0-width visual selections aren't a thing, I don't think, so
    " instead insert an ephemeral space and VIsual highlight that space, the c
    " command will then remove that ephemeral space, all with the user being
    " none-the-wiser.
    if end
      exec "normal! A \<esc>v"
    else
      exec "normal! i \<esc>v"
    endif
  else
  endif
endfunction

noremap <silent> <Plug>(insert-before-motion)
      \ <cmd>call <sid>prepare_operation('i', "c\<Plug>(insert-about-obj)")
      \<bar>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@

noremap <silent> <Plug>(append-after-motion)
      \ <cmd>call <sid>prepare_operation('a', "c\<Plug>(insert-about-obj)")
      \<bar>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@

noremap <silent> <Plug>(open-after-motion)
      \ <cmd>call <sid>prepare_operation('o', "c\<Plug>(insert-about-obj)")
      \<bar>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@

noremap <silent> <Plug>(OPEN-after-motion)
      \ <cmd>call <sid>prepare_operation('O', "c\<Plug>(insert-about-obj)")
      \<bar>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@

nnoremap <silent> <Plug>(to-object-start)
      \ <cmd>call <sid>prepare_operation('[', "\<Plug>(to-object-pos-post)")
      \<bar>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@
nnoremap <silent> <Plug>(to-object-end)
      \ <cmd>call <sid>prepare_operation(']', "\<Plug>(to-object-pos-post)")
      \<bar>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@

" The Z[ and Z] commands as text motions. These are done in a similar hacky way as other plugin
" mappings in this file
"
" What they do, is:
"
"   1. prepare the operation, and prepare to send <operator>\<plug>(to-object-pos-post)
"   2. escape operator-pending mode. This mapping is not the actual mapping we want to use for the
"      operation, we just use this as a dummy to set up all the variables needed for the actual
"      operation.
"   3. start the recording macro.
"   4. enter operating-pending mode again. This will record the macro. Once the user has entered the
"      operator, it will then call the <operator>\<plug>(to-object-pos-post). This is the command
"      that we really want to be able repeat with the dot (.) operator.
onoremap <silent> <Plug>(to-object-start)
      \ <cmd>call <sid>prepare_operation('[', printf("%s\<Plug>(to-object-pos-post)", v:operator))<cr>
      \<esc>
      \<cmd>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@
onoremap <silent> <Plug>(to-object-end)
      \ <cmd>call <sid>prepare_operation(']', printf("%s\<Plug>(to-object-pos-post)", v:operator))<cr>
      \<esc>
      \<cmd>call <sid>start_recording()
      \<bar>set operatorfunc=<sid>recordop<cr>g@

onoremap <silent> <Plug>(to-object-pos-post) <cmd>call <sid>to_object_start()<cr>
nnoremap <silent> <Plug>(to-object-pos-post) <cmd>call <sid>to_object_start()<cr>

onoremap <silent> <Plug>(insert-about-obj) <cmd>call <sid>insert_before_recorded()<cr>
nnoremap <silent> <Plug>(insert-about-obj) <cmd>call <sid>insert_before_recorded()<cr>

" Save the 'l' register. This is the register used for recording the text
" motion.
let s:savereg = []
let s:savepos = []
let s:instype = ''

" Not many people use the 'z' register, right?
let s:reg_to_clobber = 'z'
" if exists('&urf')
"   " Neovim has Rahm's multibyte register extension patch. Pick an arbitrary
"   " unicode character which is probably not used very often to clobber.
"   let s:reg_to_clobber = '∫'
" endif

function! s:start_recording()
  exec "normal! q" . s:reg_to_clobber
endfunction

let s:operate_keys=''
function! s:prepare_operation(instype, operate_keys)
  echom "operate_keys: " . a:operate_keys
  " Unfortunately macros kinda break this feature. While I think I might be able
  " to patch together a fix to make them work, I figure that I'll see if there's
  " any real need to implement it.
  if reg_recording() != ''
    normal! q
  endif

  let s:savereg = [getreg(s:reg_to_clobber, 1, 1), getregtype(s:reg_to_clobber)]
  let s:savepos = getpos('.')
  let s:instype = a:instype
  let s:operate_keys = a:operate_keys
endfunction

noremap <plug>(ñóþ) <nop>

" Save the motion postions, for use with g@.
function s:save_object(t) abort
  let s:object = {
        \ "type": a:t,
        \ "start": getpos("'["),
        \ "end": getpos("']")
        \ }
endfunction

function! s:to_object_start() abort
  set operatorfunc=s:save_object
  exec "normal g@" . s:recorded

  let pos = s:instype == '[' ? s:object.start : s:object.end

  echom printf("Jump To [%s] (%s) (%s)", string(pos), s:recorded, v:operator)
  call setpos('.', pos)
endfunction

function! s:insert_before_recorded() abort
  let l:instype = s:instype
  " Something of a hack. If the motion starts with i or a, it is probably a
  " text object.
  "
  " I think there's probably a better way to handle this, but 
  if s:recorded =~ '^[ia]' || s:recorded =~ 'g[nN]' || s:recorded =~ '[_]'
    " Without Rahm's patched Neovim, custom text objects will not work. This is
    " because while the redo buffer is saved and restored when calling a user
    " function, repeat_cmdline is not, and thus the g@ command clobbers the
    " repeat_cmdline which breaks the redobuff. Ergo the first Zi will work,
    " but subsequent repititions with the dot operator will not work.
    set operatorfunc=s:save_object
    exec "normal g@" . s:recorded

    if l:instype == 'a'
      call setpos(".", s:object.end)
      if s:object.type == 'line'
        normal! $
      endif
    elseif l:instype == 'i'
      call setpos(".", s:object.start)
      if s:object.type == 'line'
        normal! 0
      endif
      if s:object.start[2] > col(".")
        " Trying to insert at the $. Needs to be append.
        let l:instype = 'a'
      endif
    elseif l:instype == 'o'
      call setpos(".", s:object.end)
    elseif l:instype == 'O'
      call setpos(".", s:object.start)
    endif
  else
    exec "normal " . s:recorded
  endif

  if l:instype == 'a' 
    exec "normal! \<esc>a \<esc>v"
  elseif l:instype == 'i'
    exec "normal! \<esc>i \<esc>v"
  elseif l:instype == 'o'
    exec "normal! \<esc>o \<esc>v"
  elseif l:instype == 'O'
    exec "normal! \<esc>O \<esc>v"
  endif
endfunction

" Record the operator 
function! s:recordop(...) abort
  " Stop recording
  normal! q
  " Save the recorded amount
  let s:recorded=getreg(s:reg_to_clobber)
  " A limitation of normal! is that it can't start with a space, so use a NOP
  " instead.
  if s:recorded =~ '^\s'
    let s:recorded = "\<plug>(ñóþ)" . s:recorded
  endif
  " Restore the register
  call setreg(s:reg_to_clobber, s:savereg[0], s:savereg[1])
  " Restore the position
  call setpos('.', s:savepos)

  " Called <Plug>(insert-about-obj). This is the part that can be repeated.
  call feedkeys(s:operate_keys)
endfunction