]>
Commit | Line | Data |
---|---|---|
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 |