]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/plugin/indent-object.vim
afb8edd295911dfd52942e9d02d28120a3a3c357
[rbdr/dotfiles] / vim / plugin / indent-object.vim
1 "--------------------------------------------------------------------------------
2 "
3 " Copyright (c) 2010 Michael Smith <msmith@msmith.id.au>
4 "
5 " http://github.com/michaeljsmith/vim-indent-object
6 "
7 " Permission is hereby granted, free of charge, to any person obtaining a copy
8 " of this software and associated documentation files (the "Software"), to
9 " deal in the Software without restriction, including without limitation the
10 " rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11 " sell copies of the Software, and to permit persons to whom the Software is
12 " furnished to do so, subject to the following conditions:
13 "
14 " The above copyright notice and this permission notice shall be included in
15 " all copies or substantial portions of the Software.
16 "
17 " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 " FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23 " IN THE SOFTWARE.
24 "
25 "--------------------------------------------------------------------------------
26
27 " Mappings excluding line below.
28 onoremap <silent>ai :<C-u>cal <Sid>HandleTextObjectMapping(0, 0, 0, [line("."), line("."), col("."), col(".")])<CR>
29 onoremap <silent>ii :<C-u>cal <Sid>HandleTextObjectMapping(1, 0, 0, [line("."), line("."), col("."), col(".")])<CR>
30 vnoremap <silent>ai :<C-u>cal <Sid>HandleTextObjectMapping(0, 0, 1, [line("'<"), line("'>"), col("'<"), col("'>")])<CR><Esc>gv
31 vnoremap <silent>ii :<C-u>cal <Sid>HandleTextObjectMapping(1, 0, 1, [line("'<"), line("'>"), col("'<"), col("'>")])<CR><Esc>gv
32
33 " Mappings including line below.
34 onoremap <silent>aI :<C-u>cal <Sid>HandleTextObjectMapping(0, 1, 0, [line("."), line("."), col("."), col(".")])<CR>
35 onoremap <silent>iI :<C-u>cal <Sid>HandleTextObjectMapping(1, 1, 0, [line("."), line("."), col("."), col(".")])<CR>
36 vnoremap <silent>aI :<C-u>cal <Sid>HandleTextObjectMapping(0, 1, 1, [line("'<"), line("'>"), col("'<"), col("'>")])<CR><Esc>gv
37 vnoremap <silent>iI :<C-u>cal <Sid>HandleTextObjectMapping(1, 1, 1, [line("'<"), line("'>"), col("'<"), col("'>")])<CR><Esc>gv
38
39 let s:l0 = -1
40 let s:l1 = -1
41 let s:c0 = -1
42 let s:c1 = -1
43
44 function! <Sid>TextObject(inner, incbelow, vis, range, count)
45
46 " Record the current state of the visual region.
47 let vismode = "V"
48
49 " Detect if this is a completely new visual selction session.
50 let new_vis = 0
51 let new_vis = new_vis || s:l0 != a:range[0]
52 let new_vis = new_vis || s:l1 != a:range[1]
53 let new_vis = new_vis || s:c0 != a:range[2]
54 let new_vis = new_vis || s:c1 != a:range[3]
55
56 let s:l0 = a:range[0]
57 let s:l1 = a:range[1]
58 let s:c0 = a:range[2]
59 let s:c1 = a:range[3]
60
61 " Repeatedly increase the scope of the selection.
62 let itr_cnt = 0
63 let cnt = a:count
64 while cnt > 0
65
66 " Look for the minimum indentation in the current visual region.
67 let l = s:l0
68 let idnt_invalid = 1000
69 let idnt = idnt_invalid
70 while l <= s:l1
71 if !(getline(l) =~ "^\\s*$")
72 let idnt = min([idnt, indent(l)])
73 endif
74 let l += 1
75 endwhile
76
77 " Keep track of where the range should be expanded to.
78 let l_1 = s:l0
79 let l_1o = l_1
80 let l2 = s:l1
81 let l2o = l2
82
83 " If we are highlighting only blank lines, we may not have found a
84 " valid indent. In this case we need to look for the next and previous
85 " non blank lines and check which of those has the largest indent.
86 if idnt == idnt_invalid
87 let idnt = 0
88 let pnb = prevnonblank(s:l0)
89 if pnb
90 let idnt = max([idnt, indent(pnb)])
91 let l_1 = pnb
92 endif
93 let nnb = nextnonblank(s:l0)
94 if nnb
95 let idnt = max([idnt, indent(nnb)])
96 endif
97
98 " If we are in whitespace at the beginning of a block, skip over
99 " it when we are selecting the range. Similarly, if we are in
100 " whitespace at the end, ignore it.
101 if idnt > indent(pnb)
102 let l_1 = nnb
103 endif
104 if idnt > indent(nnb)
105 let l2 = pnb
106 endif
107 endif
108
109 " Search backward for the first line with less indent than the target
110 " indent (skipping blank lines).
111 let blnk = getline(l_1) =~ "^\\s*$"
112 while l_1 > 0 && ((idnt == 0 && !blnk) || (idnt != 0 && (blnk || indent(l_1) >= idnt)))
113 if !blnk || !a:inner
114 let l_1o = l_1
115 endif
116 let l_1 -= 1
117 let blnk = getline(l_1) =~ "^\\s*$"
118 endwhile
119
120 " Search forward for the first line with more indent than the target
121 " indent (skipping blank lines).
122 let line_cnt = line("$")
123 let blnk = getline(l2) =~ "^\\s*$"
124 while l2 <= line_cnt && ((idnt == 0 && !blnk) || (idnt != 0 && (blnk || indent(l2) >= idnt)))
125 if !blnk || !a:inner
126 let l2o = l2
127 endif
128 let l2 += 1
129 let blnk = getline(l2) =~ "^\\s*$"
130 endwhile
131
132 " Determine which of these extensions to include. Include neither if
133 " we are selecting an 'inner' object. Exclude the bottom unless are
134 " told to include it.
135 let idnt2 = max([indent(l_1), indent(l2)])
136 if indent(l_1) < idnt2 || a:inner
137 let l_1 = l_1o
138 endif
139 if indent(l2) < idnt2 || a:inner || !a:incbelow
140 let l2 = l2o
141 endif
142 let l_1 = max([l_1, 1])
143 let l2 = min([l2, line("$")])
144
145 " Extend the columns to the start and end.
146 " If inner is selected, set the final cursor pos to the start
147 " of the text in the line.
148 let c_1 = 1
149 if a:inner
150 let c_1 = match(getline(l_1), "\\c\\S") + 1
151 endif
152 let c2 = len(getline(l2))
153 if !a:inner
154 let c2 += 1
155 endif
156
157 " Make sure there's no change if we haven't really made a
158 " significant change in linewise mode - this makes sure that
159 " we can iteratively increase selection in linewise mode.
160 if itr_cnt == 0 && vismode ==# 'V' && s:l0 == l_1 && s:l1 == l2
161 let c_1 = s:c0
162 let c2 = s:c1
163 endif
164
165 " Check whether the visual region has changed.
166 let chg = 0
167 let chg = chg || s:l0 != l_1
168 let chg = chg || s:l1 != l2
169 let chg = chg || s:c0 != c_1
170 let chg = chg || s:c1 != c2
171
172 if vismode ==# 'V' && new_vis
173 let chg = 1
174 endif
175
176 " Update the vars.
177 let s:l0 = l_1
178 let s:l1 = l2
179 let s:c0 = c_1
180 let s:c1 = c2
181
182 " If there was no change, then don't decrement the count (it didn't
183 " count because it didn't do anything).
184 if chg
185 let cnt = cnt - 1
186 else
187 " Since this didn't work, push the selection back one char. This
188 " will have the effect of getting the enclosing block. Do it at
189 " the beginning rather than the end - the beginning is very likely
190 " to be only one indentation level different.
191 if s:l0 == 0
192 return
193 endif
194 let s:l0 -= 1
195 let s:c0 = len(getline(s:l0))
196 endif
197
198 let itr_cnt += 1
199
200 endwhile
201
202 " Apply the range we have found. Make sure to use the current visual mode.
203 call cursor(s:l0, s:c0)
204 exe "normal! " . vismode
205 call cursor(s:l1, s:c1)
206 normal! o
207
208 " Update these static variables - we need to keep these up-to-date between
209 " invocations because it's the only way we can detect whether it's a new
210 " visual mode. We need to know if it's a new visual mode because otherwise
211 " if there's a single line block in visual line mode and we select it with
212 " "V", we can't tell whether it's already been selected using Vii.
213 exe "normal! \<Esc>"
214 let s:l0 = line("'<")
215 let s:l1 = line("'>")
216 let s:c0 = col("'<")
217 let s:c1 = col("'>")
218 normal gv
219
220 endfunction
221
222 function! <Sid>HandleTextObjectMapping(inner, incbelow, vis, range)
223 call <Sid>TextObject(a:inner, a:incbelow, a:vis, a:range, v:count1)
224 endfunction