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
|
function! s:sfile() abort
return expand('<sfile>')
endfunction
let s:state_proto = {}
function! snipmate#jumping#state() abort
return copy(s:state_proto)
endfunction
function! s:listize_mirror(mirrors) abort
return map(copy(a:mirrors), '[v:val.line, v:val.col]')
endfunction
" Removes snippet state info
function! s:state_remove() dict abort
" Remove all autocmds in group snipmate_changes in the current buffer
unlet! b:snip_state
silent! au! snipmate_changes * <buffer>
endfunction
function! s:state_find_next_stop(backwards) dict abort
let self.stop_no += a:backwards? -1 : 1
while !has_key(self.stops, self.stop_no)
if self.stop_no == self.stop_count
let self.stop_no = 0
endif
if self.stop_no <= 0 && a:backwards
let self.stop_no = self.stop_count - 1
endif
let self.stop_no += a:backwards? -1 : 1
endwhile
endfunction
" Update state information to correspond to the given tab stop
function! s:state_set_stop(backwards) dict abort
call self.find_next_stop(a:backwards)
let self.cur_stop = self.stops[self.stop_no]
let self.stop_len = (type(self.cur_stop.placeholder) == type(0))
\ ? self.cur_stop.placeholder
\ : len(snipMate#placeholder_str(self.stop_no, self.stops))
let self.start_col = self.cur_stop.col
let self.end_col = self.start_col + self.stop_len
let self.mirrors = get(self.cur_stop, 'mirrors', [])
let self.old_mirrors = deepcopy(self.mirrors)
call cursor(self.cur_stop.line, self.cur_stop.col)
let self.prev_len = col('$')
let self.changed = 0
let ret = self.select_word()
if (self.stop_no == 0 || self.stop_no == self.stop_count - 1) && !a:backwards
call self.remove()
endif
return ret
endfunction
" Jump to the next/previous tab stop
function! s:state_jump_stop(backwards) dict abort
" Update changes just in case
" This seems to be only needed because insert completion does not trigger
" the CursorMovedI event
call self.update_changes()
" Store placeholder/location changes
let self.cur_stop.col = self.start_col
if self.changed
call self.remove_nested()
unlet! self.cur_stop.placeholder " avoid type error for old parsing version
let self.cur_stop.placeholder = [strpart(getline('.'),
\ self.start_col - 1, self.end_col - self.start_col)]
endif
return self.set_stop(a:backwards)
endfunction
function! s:state_remove_nested(...) dict abort
let id = a:0 ? a:1 : self.stop_no
if type(self.stops[id].placeholder) == type([])
for i in self.stops[id].placeholder
if type(i) == type([])
if type(i[1]) != type({})
call self.remove_nested(i[0])
call remove(self.stops, i[0])
else
call filter(self.stops[i[0]].mirrors, 'v:val isnot i[1]')
endif
endif
unlet i " Avoid E706
endfor
endif
endfunction
" Select the placeholder for the current tab stop
function! s:state_select_word() dict abort
let len = self.stop_len
if !len | return '' | endif
let l = col('.') != 1 ? 'l' : ''
if &sel == 'exclusive'
return "\<esc>".l.'v'.len."l\<c-g>"
endif
return len == 1 ? "\<esc>".l.'gh' : "\<esc>".l.'v'.(len - 1)."l\<c-g>"
endfunction
" Update the snippet as text is typed. The self.update_mirrors() function does
" the actual work.
" If the cursor moves outside of a placeholder, call self.remove()
function! s:state_update_changes() dict abort
let change_len = col('$') - self.prev_len
let self.changed = self.changed || change_len != 0
let self.end_col += change_len
let col = col('.')
if line('.') != self.cur_stop.line || col < self.start_col || col > self.end_col
return self.remove()
endif
call self.update(self.cur_stop, change_len, change_len)
if !empty(self.mirrors)
call self.update_mirrors(change_len)
endif
let self.prev_len = col('$')
endfunction
" Actually update the mirrors for any changed text
function! s:state_update_mirrors(change) dict abort
let newWordLen = self.end_col - self.start_col
let newWord = strpart(getline('.'), self.start_col - 1, newWordLen)
let changeLen = a:change
let curLine = line('.')
let curCol = col('.')
let oldStartSnip = self.start_col
let i = 0
for mirror in self.mirrors
for stop in values(filter(copy(self.stops), 'v:key != 0'))
if type(stop.placeholder) == type(0)
if mirror.line == stop.line && mirror.col > stop.col
\ && mirror.col < stop.col + stop.placeholder
let stop.placeholder += changeLen
endif
endif
endfor
if has_key(mirror, 'oldSize')
" recover the old size deduce the endline
let oldSize = mirror.oldSize
else
" first time, we use the intitial size
let oldSize = strlen(newWord)
endif
" Split the line into three parts: the mirror, what's before it, and
" what's after it. Then combine them using the new mirror string.
" Subtract one to go from column index to byte index
let theline = getline(mirror.line)
" part before the current mirror
let beginline = strpart(theline, 0, mirror.col - 1)
" current mirror transformation, and save size
let wordMirror= substitute(newWord, get(mirror, 'pat', ''), get(mirror, 'sub', ''), get(mirror, 'flags', ''))
let mirror.oldSize = strlen(wordMirror)
" end of the line, use the oldSize because with the transformation,
" the size of the mirror can be different from those of the snippet
let endline = strpart(theline, mirror.col + oldSize -1)
" Update other object on the line
call self.update(mirror, changeLen, mirror.oldSize - oldSize)
" reconstruct the line
let update = beginline.wordMirror.endline
call setline(mirror.line, update)
endfor
" Reposition the cursor in case a var updates on the same line but before
" the current tabstop
if oldStartSnip != self.start_col || mode() == 'i'
call cursor(0, curCol + self.start_col - oldStartSnip)
endif
endfunction
function! s:state_find_update_objects(item) dict abort
let item = a:item
let item.update_objects = []
" Filter the zeroth stop because it's duplicated as the last
for stop in values(filter(copy(self.stops), 'v:key != 0'))
if stop.line == item.line && stop.col > item.col
call add(item.update_objects, stop)
endif
for mirror in get(stop, 'mirrors', [])
if mirror.line == item.line && mirror.col > item.col
call add(item.update_objects, mirror)
endif
endfor
endfor
return item.update_objects
endfunction
function! s:state_update(item, change_len, mirror_change) dict abort
let item = a:item
if !exists('item.update_objects')
let item.update_objects = self.find_update_objects(a:item)
endif
let to_update = item.update_objects
for obj in to_update
" object does not necessarly have the same decalage
" than mirrors if mirrors use regexp
let obj.col += a:mirror_change
if obj is self.cur_stop
let self.start_col += a:change_len
let self.end_col += a:change_len
endif
endfor
endfunction
call extend(s:state_proto, snipmate#util#add_methods(s:sfile(), 'state',
\ [ 'remove', 'set_stop', 'jump_stop', 'remove_nested',
\ 'select_word', 'update_changes', 'update_mirrors',
\ 'find_next_stop', 'find_update_objects', 'update' ]), 'error')
" vim:noet:sw=4:ts=4:ft=vim
|