aboutsummaryrefslogtreecommitdiffstats
path: root/vim/plugin/textobj.vim
blob: 01345bcb757bae7bee17b04ac23b523cdc569c79 (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
" Text object creation {{{
" XXX: use virtualedit here, it should greatly simplify things
let s:text_object_number = 0
function Textobj(char, callback, ...)
    let s:text_object_number += 1
    function s:textobj_{s:text_object_number}(inner, operator, count, callback, ...)
        try
            let pos = getpos('.')
            sandbox let [startline, startcol, endline, endcol] = call(a:callback, [a:inner, a:count] + a:000)
        catch /no-match/
            return
        finally
            call setpos('.', pos)
        endtry
        if startline == endline
            let objlength = endcol - startcol + 1
        else
            let objlength = 0
            if endline - startline > 1
                exe 'let objlength += '.join(map(getline(startline + 1, endline - 1), 'strlen(v:val) + 1'), '+')
            endif
            let objlength += endcol + strlen(getline(startline)) - startcol + 2
        endif
        let whichwrap = &whichwrap
        set whichwrap+=s
        let virtualedit = &virtualedit
        if startcol > strlen(getline(startline))
            let startcol = 1
            let startline += 1
            let objlength -= 1
        endif
        if endcol == 0
            set virtualedit=onemore
        endif
        if a:operator == 'v'
            let objlength -= 1
        endif
        call cursor(startline, startcol)
        if a:operator == 'c'
            let operator = 'd'
        else
            let operator = a:operator
        end
        exe 'normal! '.operator.objlength.' '

        if a:operator == 'c'
            startinsert
        endif
        let &whichwrap = whichwrap
        let &virtualedit = virtualedit
    endfunction

    exe 'onoremap <silent>a'.a:char.' <Esc>:call call("<SID>textobj_'.s:text_object_number.'", [0, v:operator, v:prevcount, "'.a:callback.'"] + '.string(a:000).')<CR>'
    exe 'onoremap <silent>i'.a:char.' <Esc>:call call("<SID>textobj_'.s:text_object_number.'", [1, v:operator, v:prevcount, "'.a:callback.'"] + '.string(a:000).')<CR>'
    exe 'xnoremap <silent>a'.a:char.' <Esc>:call call("<SID>textobj_'.s:text_object_number.'", [0, "v", v:prevcount, "'.a:callback.'"] + '.string(a:000).')<CR>'
    exe 'xnoremap <silent>i'.a:char.' <Esc>:call call("<SID>textobj_'.s:text_object_number.'", [1, "v", v:prevcount, "'.a:callback.'"] + '.string(a:000).')<CR>'
endfunction
" }}}
" Text object definitions {{{
" arbitrary paired symbols (/ for regex, etc) {{{
function Textobj_paired(inner, count, char)
    let pos = getpos('.')

    let line = strpart(getline(pos[1]), 0, pos[2])
    let lines = getline(1, pos[1] - 1) + [line]
    let linenum = pos[1]
    for line in reverse(lines)
        let objstart = match(line, '.*\zs\\\@<!'.a:char) + 1
        if objstart != 0
            break
        endif
        let linenum -= 1
    endfor
    if objstart == 0
        throw 'no-match'
    endif
    let objstart += a:inner
    let objstartline = linenum

    let line = strpart(getline(pos[1]), pos[2] - 1)
    let lines = [line] + getline(pos[1] + 1, line('$'))
    let linenum = pos[1]
    for line in lines
        let objend = match(line, '\\\@<!'.a:char) + 1
        if objend != 0
            if linenum == pos[1]
                " have to account for the possibility of a split escape
                " sequence
                if objend == 1
                    if getline(pos[1])[pos[2] - 2] == '\'
                        let objend = match(line, '\\\@<!'.a:char, 1) + 1
                        if objend == 0
                            let linenum += 1
                            continue
                        endif
                    else
                        " if we're sitting on a char, don't do anything, since
                        " it's impossible to know which direction to look
                        throw 'no-match'
                    endif
                endif
                let objend += pos[2] - 1
            endif
            break
        endif
        let linenum += 1
    endfor
    if objend == 0
        throw 'no-match'
    endif
    let objend -= a:inner
    let objendline = linenum

    return [objstartline, objstart, objendline, objend]
endfunction
" }}}
" f for folds {{{
function Textobj_fold(inner, count)
    if foldlevel(line('.')) == 0
        throw 'no-match'
    endif
    exe 'normal! '.a:count.'[z'
    let startline = line('.') + a:inner
    normal! ]z
    let endline = line('.') - a:inner

    return [startline, 1, endline, strlen(getline(endline))]
endfunction
" }}}
" , for function arguments {{{
function Textobj_arg(inner, count)
    let pos = getpos('.')
    let curchar = getline(pos[1])[pos[2] - 1]
    if curchar == ','
        if getline(pos[1])[pos[2] - 2] =~ '\s'
            normal! gE
        else
            exe "normal! \<BS>"
        endif
        return s:textobj_arg(a:inner, a:count)
    elseif curchar =~ '\s'
        normal! W
        return s:textobj_arg(a:inner, a:count)
    endif

    let line = strpart(getline(pos[1]), 0, pos[2])
    let lines = getline(1, pos[1] - 1) + [line]
    let linenum = pos[1]
    for line in reverse(lines)
        let argbegin = matchend(line, '.*\%(,\s*\|(\)') + 1
        if argbegin != 0
            while argbegin > strlen(line)
                let linenum += 1
                let line = getline(linenum)
                let argbegin = matchend(line, '^\s*') + 1
            endwhile
            break
        endif
        let linenum -= 1
    endfor
    if argbegin == 0
        throw 'no-match'
    endif
    let argstartline = linenum

    let line = strpart(getline(pos[1]), pos[2] - 1)
    let lines = [line] + getline(pos[1] + 1, line('$'))
    let linenum = pos[1]
    for line in lines
        let argend = match(line, '\zs.\?\%(,\|)\)') + 1
        if argend != 0
            if linenum == pos[1]
                let argend += pos[2] - 1
            endif
            if argend == 1 && getline(linenum)[argend - 1] == ')'
                let linenum -= 1
                let argend = strlen(getline(linenum))
            endif
            break
        endif
        let linenum += 1
    endfor
    if argend == 0
        throw 'no-match'
    endif
    let argendline = linenum

    if a:inner == 0
        let endline = getline(argendline)
        let startline = getline(argstartline)
        if argend >= strlen(endline)
            let argend = 0
            let argendline += 1
            let endline = getline(argendline)
        endif
        if endline[argend] == ')' && startline[argbegin - 2] != '('
            let argbegin = match(strpart(startline, 0, argbegin - 1), '\s*$')
            while argbegin == 0
                let argstartline -= 1
                let startline = getline(argstartline)
                let argbegin = strlen(startline)
            endwhile
        elseif endline[argend] != ')'
            let argend += matchend(strpart(endline, argend + 1), '^\s*') + 1
            if startline[argbegin - 2] == '('
                for line in [strpart(endline, argend)] +
                \           getline(argendline + 1, line('$'))
                    let argincr = matchend(line, '\s*\ze\S')
                    if argincr != -1
                        let argend += argincr
                        break
                    endif
                    let argendline += 1
                    let argend = 0
                endfor
            endif
        endif
        if argend >= strlen(endline)
            if argendline == argstartline
                let newbegin = matchend(strpart(endline, 0, argbegin), '.*,')
                if newbegin != -1
                    let argbegin = newbegin + 1
                endif
            endif
            let argend = 0
            let argendline += 1
        endif
    endif

    return [argstartline, argbegin, argendline, argend]
endfunction
" }}}
" }}}
" Text object loading {{{
for object in g:Textobj_defs
    call call('Textobj', object)
endfor
unlet object
" }}}