|
| 1 | +function! textobj#argument#select_a() |
| 2 | + return s:Select(g:argument_separator, 1, v:count1, visualmode()) |
| 3 | +endfunction |
| 4 | + |
| 5 | +function! textobj#argument#select_i() |
| 6 | + return s:Select(g:argument_separator, 0, v:count1, visualmode()) |
| 7 | +endfunction |
| 8 | + |
| 9 | +function! textobj#argument#select_I() |
| 10 | + return s:Select(g:argument_separator, 0, v:count1, visualmode()) |
| 11 | +endfunction |
| 12 | + |
| 13 | +function! textobj#argument#move_n() |
| 14 | + return s:Move(g:argument_separator, 1, v:count1, 0, visualmode()) |
| 15 | +endfunction |
| 16 | + |
| 17 | +function! textobj#argument#move_N() |
| 18 | + return s:Move(g:argument_separator, 1, v:count1, 1, visualmode()) |
| 19 | +endfunction |
| 20 | + |
| 21 | +function! textobj#argument#move_p() |
| 22 | + return s:Move(g:argument_separator, 0, v:count1, 0, visualmode()) |
| 23 | +endfunction |
| 24 | + |
| 25 | +function! textobj#argument#move_P() |
| 26 | + return s:Move(g:argument_separator, 0, v:count1, 1, visualmode()) |
| 27 | +endfunction |
| 28 | + |
| 29 | +" |
| 30 | +" 移动列表中的参数 |
| 31 | +" |
| 32 | +" 列表使用括号括起来 (i.e. '()', '[]', or '{}'). |
| 33 | +" 参数使用 a:sep 分隔(e.g. ',') |
| 34 | +" |
| 35 | +" 如果 a:direction 大于 0 则向右移动,否则向左移动 |
| 36 | +" 如果设置了 a:to_end,则支持一键移动到最左端最右端的参数 |
| 37 | +" |
| 38 | +function! s:Move(sep, direction, times, to_end, ...) |
| 39 | + let lbracket = g:argument_left_bracket |
| 40 | + let rbracket = g:argument_right_bracket |
| 41 | + let save_unnamed = @" |
| 42 | + let save_ic = &ic |
| 43 | + let &ic = 0 |
| 44 | + |
| 45 | + " 记录起始光标字符,用于判断是不是括号 |
| 46 | + " 如果是括号,则 last 的跳数需要减一,用于跳到括号附近的参数 |
| 47 | + exe "normal! vy" |
| 48 | + let oristart = @" |
| 49 | + |
| 50 | + try |
| 51 | + " 向后搜索且到不使用文件环绕, |
| 52 | + " 如果 direction 为 true 多了个 flag c,差别: |
| 53 | + " 1. 如果停留在{start}上,那当前光标会作为结果,没有flag会返回0 |
| 54 | + " 2. 如果停留在 {end} 上,那会被认为是嵌套组,返回 0,没有flag会跳到分割符 |
| 55 | + " 3. 如果停留在分割符上,那当前光标会作为结果,没有flag会返回上个分割符 |
| 56 | + " 4. 如果停留在分割符右边,那上个分割会作为结果,没有flag也一样 |
| 57 | + " |
| 58 | + " 总结意图:向后搜索匹配的分割符或左括号 |
| 59 | + " 正向搜索且起始位置位于 {start} 需要接受光标下的左括号,所以需要flag c |
| 60 | + let flags = a:direction ? 'bcW' : 'bW' |
| 61 | + if searchpair(lbracket, a:sep, rbracket, flags, |
| 62 | + \ 's:IsCursorOnStringOrComment()') <= 0 |
| 63 | + " 不在列表内,则向前搜索是不是右左括号 |
| 64 | + " 用于列表外能直接跳转到参数列表内 |
| 65 | + let flags = a:direction ? 'W' : 'bW' |
| 66 | + if search(lbracket, flags, line('.')) <= 0 |
| 67 | + return |
| 68 | + endif |
| 69 | + " 重新定位起始光标字符 |
| 70 | + exe "normal! vy" |
| 71 | + let oristart = @" |
| 72 | + endif |
| 73 | + " 经过 searchpair 此时光标会移动到左括号或分割符上 |
| 74 | + " 获取当前光标字符 |
| 75 | + exe "normal! yl" |
| 76 | + let first = @" |
| 77 | + |
| 78 | + " times 支持多跳 |
| 79 | + let times = a:times |
| 80 | + " 如果起始位置是括号,为了能指向括号的下个参数,跳数减一 |
| 81 | + if (a:direction && oristart =~ lbracket) || (!a:direction && oristart =~ rbracket) |
| 82 | + let times = times - 1 |
| 83 | + endif |
| 84 | + " 根据 direction 确定是向前确定还是向后确定右边界 |
| 85 | + " 搜索分割符或括号 |
| 86 | + " 因为没有 flag c,所以即使当前光标为分割符也会跳过当前的 |
| 87 | + let flags = a:direction ? 'W' : 'bW' |
| 88 | + " 如果是到最端,则不受 times 限制 |
| 89 | + while (times > 0 || a:to_end) && |
| 90 | + \ (@" =~ a:sep || (a:direction && @" =~ lbracket) || (!a:direction && @" =~ rbracket)) |
| 91 | + \ && searchpair(lbracket, a:sep, rbracket, flags, 's:IsCursorOnStringOrComment()') > 0 |
| 92 | + let times -= 1 |
| 93 | + exe "normal! yl" |
| 94 | + endwhile |
| 95 | + " 记录最后一跳后,当前光标下的字符,可能是左右括号或分割符 |
| 96 | + let last = @" |
| 97 | + |
| 98 | + if a:sep =~ first && a:sep =~ last |
| 99 | + " 左右两边为分割符 |
| 100 | + " (a, b /**/, c /* */, d |
| 101 | + " · |
| 102 | + " 正向搜索 |
| 103 | + " (a, b /**/, c /* */, d |
| 104 | + " ↑ ↑ · |
| 105 | + " 反向搜索 |
| 106 | + " (a, b /**/, c /* */, d |
| 107 | + " ↑ · ↑ |
| 108 | + " 将右边界向右移动到下个参数,忽略注释 |
| 109 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 110 | + elseif a:sep =~ first |
| 111 | + " 左边为分割符,右边为括号 |
| 112 | + " a, b /**/, c , d/* */) |
| 113 | + " · |
| 114 | + if a:direction |
| 115 | + " 正向搜索 |
| 116 | + " a, b /**/, c , d/* */) |
| 117 | + " ↑ · ↑ |
| 118 | + " 向后搜索匹配的分割符或左括号 |
| 119 | + call searchpair(lbracket, a:sep, rbracket, 'bW', 's:IsCursorOnStringOrComment()') |
| 120 | + " 将右边界向右移动到下个参数,忽略注释 |
| 121 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 122 | + else |
| 123 | + " 反向搜索 |
| 124 | + " (a, b /**/, c , d/* */) |
| 125 | + " ↑· ↑ · |
| 126 | + " 将右边界向右移动到下个参数,忽略注释 |
| 127 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 128 | + endif |
| 129 | + elseif a:sep =~ last |
| 130 | + " 左边为括号,右边为分割符 |
| 131 | + " 正向搜索 |
| 132 | + " (a, b , c /**/, d) |
| 133 | + " ↑· ↑ |
| 134 | + " (a, b , c /**/, d) |
| 135 | + " ↑· ↑ · |
| 136 | + " 反向搜索 |
| 137 | + " 左边为括号,右边肯定也是括号 |
| 138 | + " 将右边界向右移动到下个参数,忽略注释 |
| 139 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 140 | + else |
| 141 | + " 两边为括号 |
| 142 | + if lbracket =~ first && lbracket =~ last |
| 143 | + " (a, b , c , d) |
| 144 | + " ↑ |
| 145 | + " ↑ |
| 146 | + " · |
| 147 | + " (a, b , c , d) |
| 148 | + " ↑ |
| 149 | + " ↑ |
| 150 | + " ·· |
| 151 | + " 将右边界向右移动到下个参数,忽略注释 |
| 152 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 153 | + elseif rbracket =~ first && rbracket =~ last |
| 154 | + " (a, b , c , d) |
| 155 | + " ↑ |
| 156 | + " ↑ |
| 157 | + " · |
| 158 | + " (a, b , c , d) |
| 159 | + " ↑ |
| 160 | + " ↑ |
| 161 | + " ·· |
| 162 | + call searchpair(lbracket, a:sep, rbracket, 'bW', 's:IsCursorOnStringOrComment()') |
| 163 | + " 将右边界向右移动到下个参数,忽略注释 |
| 164 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 165 | + elseif a:direction |
| 166 | + " (a, b , c , d) |
| 167 | + " ↑· ↑ |
| 168 | + " (a, b , c , d) |
| 169 | + " ↑· ·↑ |
| 170 | + call searchpair(lbracket, a:sep, rbracket, 'bW', 's:IsCursorOnStringOrComment()') |
| 171 | + " 将右边界向右移动到下个参数,忽略注释 |
| 172 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 173 | + else |
| 174 | + " (a, b , c , d) |
| 175 | + " ↑ ·↑ |
| 176 | + " (a, b , c , d) |
| 177 | + " ↑· ·↑ |
| 178 | + " 将右边界向右移动到下个参数,忽略注释 |
| 179 | + call searchpair('\%0l', '', '\S', 'W', 's:IsCursorOnComment()') |
| 180 | + endif |
| 181 | + endif |
| 182 | + |
| 183 | + return ['v', getpos('.'), 0] |
| 184 | + |
| 185 | + finally |
| 186 | + let @" = save_unnamed |
| 187 | + let &ic = save_ic |
| 188 | + endtry |
| 189 | +endfunction |
| 190 | + |
| 191 | +" |
| 192 | +" 选择列表中的参数 |
| 193 | +" |
| 194 | +" 列表使用括号括起来 (i.e. '()', '[]', or '{}'). |
| 195 | +" 参数使用 a:sep 分隔(e.g. ',') |
| 196 | +" |
| 197 | +" 如果设置了 a:outer,则不止选择参数本身,还包括注释和分隔符 |
| 198 | +" |
| 199 | +function! s:Select(sep, outer, times, ...) |
| 200 | + let lbracket = g:argument_left_bracket |
| 201 | + let rbracket = g:argument_right_bracket |
| 202 | + let save_mb = getpos("'b") |
| 203 | + let save_me = getpos("'e") |
| 204 | + let save_unnamed = @" |
| 205 | + let save_ic = &ic |
| 206 | + let &ic = 0 |
| 207 | + let save_ww = &ww |
| 208 | + let &ww = '<,>' |
| 209 | + |
| 210 | + try |
| 211 | + if searchpair(lbracket, a:sep, rbracket, 'bcW', |
| 212 | + \ 's:IsCursorOnStringOrComment()') <= 0 |
| 213 | + if search(lbracket, 'W', line('.')) <= 0 |
| 214 | + return |
| 215 | + endif |
| 216 | + endif |
| 217 | + |
| 218 | + exe "normal! ylmb" |
| 219 | + let first = @" |
| 220 | + |
| 221 | + let times = a:times |
| 222 | + while times > 0 && (@" =~ a:sep || @" =~ lbracket) && searchpair(lbracket, a:sep, rbracket, |
| 223 | + \ 'W', 's:IsCursorOnStringOrComment()') > 0 |
| 224 | + let times -= 1 |
| 225 | + exe "normal! yl" |
| 226 | + endwhile |
| 227 | + |
| 228 | + let last = @" |
| 229 | + |
| 230 | + " 只选择参数本身,忽略注释 |
| 231 | + if !a:outer |
| 232 | + call search('\S', 'bW', '', '', 's:IsCursorOnComment()') |
| 233 | + exe "keepjumps normal! me`b" |
| 234 | + call search('\S', 'W', '', '', 's:IsCursorOnComment()') |
| 235 | + else |
| 236 | + " 在 outer 下,左右两边都是分割符的情况 |
| 237 | + if a:sep =~ first && a:sep =~ last |
| 238 | + exe "normal \<Left>" |
| 239 | + exe "keepjumps normal! me`b" |
| 240 | + elseif a:sep =~ first |
| 241 | + " 左边为分割符,右边为括号 |
| 242 | + exe "normal \<Left>" |
| 243 | + exe "keepjumps normal! me`b" |
| 244 | + elseif a:sep =~ last |
| 245 | + " 左边为括号,右边为分割符 |
| 246 | + " |
| 247 | + call search('\S', 'W') |
| 248 | + exe "normal \<Left>" |
| 249 | + exe "keepjumps normal! me`b" |
| 250 | + exe "normal \<Right>" |
| 251 | + else |
| 252 | + " 两边为括号 |
| 253 | + exe "keepjumps normal! me`b" |
| 254 | + exe "normal \<Right>" |
| 255 | + exe "normal! mb`e" |
| 256 | + exe "normal \<Left>" |
| 257 | + exe "keepjumps normal! me`b" |
| 258 | + endif |
| 259 | + endif |
| 260 | + |
| 261 | + let head = getpos('.') |
| 262 | + |
| 263 | + exe "keepjumps normal! `e" |
| 264 | + |
| 265 | + " selection options 的值为 exclusive/inclusive |
| 266 | + " exclusive 模式意味着选择区的最后一个字符不包括在操作范围内,非闭区间 |
| 267 | + " 所以用 <Space> 将右边界向右移动一个字符 |
| 268 | + " 请注意,如果光标位于行尾,则 <space> 可以转到下一行,而 'l' 则不能 |
| 269 | + if &sel == "exclusive" |
| 270 | + exe "keepjumps normal! \<Right>" |
| 271 | + endif |
| 272 | + |
| 273 | + exe "keepjumps normal! \<Esc>" |
| 274 | + |
| 275 | + return ['v', head, getpos('.')] |
| 276 | + |
| 277 | + finally |
| 278 | + call setpos("'b", save_mb) |
| 279 | + call setpos("'e", save_me) |
| 280 | + let @" = save_unnamed |
| 281 | + let &ic = save_ic |
| 282 | + let &ww = save_ww |
| 283 | + endtry |
| 284 | +endfunction |
| 285 | + |
| 286 | +" 判断当前光标下的文字是不是注释 |
| 287 | +function! s:IsCursorOnComment() |
| 288 | + " synID 返回位置文本的语法 ID,再用 syncIDattr 根据语法 ID 获取语法信息 |
| 289 | + " 最后根据 name 是否包含 comment 关键词判断是否为注释 |
| 290 | + " 这种方式有点取巧,这个取决于是否有语法信息以及定义的语法信息名字是否为 |
| 291 | + " comment |
| 292 | + return synIDattr(synID(line("."), col("."), 0), "name") =~? "comment" |
| 293 | +endfunction |
| 294 | + |
| 295 | +function! s:IsCursorOnStringOrComment() |
| 296 | + let syn = synIDattr(synID(line("."), col("."), 0), "name") |
| 297 | + return syn =~? "string" || syn =~? "comment" |
| 298 | +endfunction |
0 commit comments