| 1 | " ku - Support to do something |
|---|
| 2 | " Version: 0.0.0 |
|---|
| 3 | " Copyright: Copyright (C) 2008 kana <http://nicht.s8.xrea.com/> |
|---|
| 4 | " License: MIT license (see <http://www.opensource.org/licenses/mit-license>) |
|---|
| 5 | " $Id$ "{{{1 |
|---|
| 6 | " FIXME: s:do(): Force action on unmatched pattern. |
|---|
| 7 | " FIXME: more smart sorting: |
|---|
| 8 | " - considering last component. |
|---|
| 9 | " - type buffer: full path vs. relative path. |
|---|
| 10 | " FIXME: review on case sensitivity. |
|---|
| 11 | " FIXME: alternative implementation (getchar()), if necessary. |
|---|
| 12 | " |
|---|
| 13 | " Variables and Constants "{{{1 |
|---|
| 14 | " Script-local "{{{2 |
|---|
| 15 | |
|---|
| 16 | let s:FALSE = 0 |
|---|
| 17 | let s:TRUE = !s:FALSE |
|---|
| 18 | |
|---|
| 19 | let s:TYPE_NUMBER = type(0) |
|---|
| 20 | let s:TYPE_STRING = type('') |
|---|
| 21 | let s:TYPE_FUNCTION = type(function('function')) |
|---|
| 22 | let s:TYPE_LIST = type([]) |
|---|
| 23 | let s:TYPE_DICTONARY = type({}) |
|---|
| 24 | |
|---|
| 25 | " Key sequence to start (omni) completion |
|---|
| 26 | " without auto-selecting the first match. |
|---|
| 27 | let s:KEYS_TO_START_COMPLETION = "\<C-x>\<C-o>\<C-p>" |
|---|
| 28 | |
|---|
| 29 | " The prompt for user input. |
|---|
| 30 | " This is necessary to publish CursorMovedI event for each typing. |
|---|
| 31 | " Note that the length should be 1 to avoid some problems. |
|---|
| 32 | let s:PROMPT = '>' |
|---|
| 33 | |
|---|
| 34 | |
|---|
| 35 | " Flag to avoid infinite loop by auto-directory-insertion. |
|---|
| 36 | let s:auto_directory_insertion_done_p = s:FALSE |
|---|
| 37 | |
|---|
| 38 | " Flag which indicates whether the ku window is opened with bang (:Ku!). |
|---|
| 39 | " Possible values are '' or '!'. |
|---|
| 40 | let s:bang = '' |
|---|
| 41 | |
|---|
| 42 | " The buffer number of the ku buffer. |
|---|
| 43 | let s:INVALID_BUFNR = -3339 |
|---|
| 44 | " to be reloadable (for debugging) |
|---|
| 45 | if exists('s:bufnr') && s:bufnr != s:INVALID_BUFNR && bufexists(s:bufnr) |
|---|
| 46 | execute s:bufnr 'bwipeout' |
|---|
| 47 | endif |
|---|
| 48 | let s:bufnr = s:INVALID_BUFNR |
|---|
| 49 | |
|---|
| 50 | " Fallback actions for all types. |
|---|
| 51 | if !exists('s:fallback_actions') |
|---|
| 52 | let s:fallback_actions = {} |
|---|
| 53 | endif |
|---|
| 54 | |
|---|
| 55 | " The last column of the cursor. |
|---|
| 56 | let s:INVALID_COL = -3338 |
|---|
| 57 | let s:last_col = s:INVALID_COL |
|---|
| 58 | |
|---|
| 59 | " Items gathered by the last completion. |
|---|
| 60 | let s:last_items = [] |
|---|
| 61 | |
|---|
| 62 | " Preferred type of items. |
|---|
| 63 | let s:INVALID_TYPE = '*all*' |
|---|
| 64 | let s:preferred_type = s:INVALID_TYPE |
|---|
| 65 | |
|---|
| 66 | " All available types. |
|---|
| 67 | if !exists('s:types') |
|---|
| 68 | let s:types = {} |
|---|
| 69 | endif |
|---|
| 70 | |
|---|
| 71 | " Misc. values to restore the original state. |
|---|
| 72 | let s:completeopt = '' |
|---|
| 73 | let s:ignorecase = '' |
|---|
| 74 | let s:winrestcmd = '' |
|---|
| 75 | |
|---|
| 76 | |
|---|
| 77 | |
|---|
| 78 | |
|---|
| 79 | " Global "{{{2 |
|---|
| 80 | " Flag for debugging. |
|---|
| 81 | if !exists('g:ku_debug_p') |
|---|
| 82 | let g:ku_debug_p = s:FALSE |
|---|
| 83 | endif |
|---|
| 84 | |
|---|
| 85 | " Patterns for junk items. These items are listed at the last. |
|---|
| 86 | " FIXME: How about g:ku_important_item_pattern? |
|---|
| 87 | if !exists('g:ku_junk_item_pattern') |
|---|
| 88 | let g:ku_junk_item_pattern = '\(\~\|\.o\|\.swp\)$' |
|---|
| 89 | endif |
|---|
| 90 | |
|---|
| 91 | " Flag to use the type of items for sorting without s:preferred_type. |
|---|
| 92 | if !exists('g:ku_sort_by_type_first_p') |
|---|
| 93 | let g:ku_sort_by_type_first_p = s:TRUE |
|---|
| 94 | endif |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | |
|---|
| 98 | |
|---|
| 99 | |
|---|
| 100 | |
|---|
| 101 | |
|---|
| 102 | |
|---|
| 103 | " Interfaces "{{{1 |
|---|
| 104 | function! ku#start(bang, type) abort "{{{2 |
|---|
| 105 | " Save/Set up several values. |
|---|
| 106 | let s:bang = a:bang |
|---|
| 107 | let s:preferred_type = s:valid_type_name_p(a:type) ? a:type : s:INVALID_TYPE |
|---|
| 108 | |
|---|
| 109 | let s:completeopt = &completeopt |
|---|
| 110 | set completeopt+=menuone |
|---|
| 111 | let s:ignorecase = &ignorecase |
|---|
| 112 | set ignorecase |
|---|
| 113 | let s:winrestcmd = winrestcmd() |
|---|
| 114 | |
|---|
| 115 | " Do some initialization for each type -- before opening the ku buffer. |
|---|
| 116 | for type_name in keys(s:types) |
|---|
| 117 | call s:types[type_name].initialize('before-open') |
|---|
| 118 | endfor |
|---|
| 119 | |
|---|
| 120 | " Open a new window and switch to the ku buffer. |
|---|
| 121 | if !s:ku_buffer_exists_p() |
|---|
| 122 | topleft new |
|---|
| 123 | call s:initialize_ku_buffer() |
|---|
| 124 | let s:bufnr = bufnr('') |
|---|
| 125 | else |
|---|
| 126 | execute 'topleft' s:bufnr 'sbuffer' |
|---|
| 127 | endif |
|---|
| 128 | 2 wincmd _ |
|---|
| 129 | |
|---|
| 130 | " Do some initialization for each type -- after opening the ku buffer. |
|---|
| 131 | for type_name in keys(s:types) |
|---|
| 132 | call s:types[type_name].initialize('after-open') |
|---|
| 133 | endfor |
|---|
| 134 | |
|---|
| 135 | " Start Insert mode. |
|---|
| 136 | % delete _ |
|---|
| 137 | call append(1, '') |
|---|
| 138 | normal! 2G |
|---|
| 139 | call feedkeys('i', 'n') |
|---|
| 140 | endfunction |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | |
|---|
| 144 | |
|---|
| 145 | function! ku#default_key_mappings() "{{{2 |
|---|
| 146 | call s:remap('<C-c>', '<Plug>(ku-cancel)') |
|---|
| 147 | call s:remap('<C-m>', '<Plug>(ku-do-default)') |
|---|
| 148 | call s:remap('<C-i>', '<Plug>(ku-choose-action)') |
|---|
| 149 | call s:remap('<C-j>', '<Plug>(ku-next-type)') |
|---|
| 150 | call s:remap('<C-k>', '<Plug>(ku-previous-type)') |
|---|
| 151 | endfunction |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | |
|---|
| 155 | |
|---|
| 156 | function! ku#register_type(args) "{{{2 |
|---|
| 157 | " See s:valid_type_definition_p() for the detail of a:args. |
|---|
| 158 | if !s:valid_type_definition_p(a:args) |
|---|
| 159 | echohl ErrorMsg |
|---|
| 160 | echomsg 'Invalid type definition:' string(a:args) |
|---|
| 161 | echohl None |
|---|
| 162 | return s:FALSE |
|---|
| 163 | endif |
|---|
| 164 | |
|---|
| 165 | let s:types[a:args.name] = a:args |
|---|
| 166 | |
|---|
| 167 | return s:TRUE |
|---|
| 168 | endfunction |
|---|
| 169 | |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | |
|---|
| 173 | function! ku#unregister_type(name) "{{{2 |
|---|
| 174 | if has_key(s:types, a:name) |
|---|
| 175 | call remove(s:types, a:name) |
|---|
| 176 | endif |
|---|
| 177 | return s:TRUE |
|---|
| 178 | endfunction |
|---|
| 179 | |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | |
|---|
| 183 | function! ku#custom_key(type_name, key, new_action_name) "{{{2 |
|---|
| 184 | if !s:valid_type_name_p(a:type_name) |
|---|
| 185 | \ || !s:type_has_action_p(s:types[a:type_name], a:new_action_name) |
|---|
| 186 | return '' |
|---|
| 187 | endif |
|---|
| 188 | |
|---|
| 189 | let old_action_name = get(s:types[a:type_name].keys, a:key, '') |
|---|
| 190 | let s:types[(a:type_name)].keys[(a:key)] = a:new_action_name |
|---|
| 191 | return old_action_name |
|---|
| 192 | endfunction |
|---|
| 193 | |
|---|
| 194 | |
|---|
| 195 | |
|---|
| 196 | |
|---|
| 197 | function! ku#custom_action(type_name, action_name, new_action_function) "{{{2 |
|---|
| 198 | if !s:callable_p(a:new_action_function) |
|---|
| 199 | return s:FALSE |
|---|
| 200 | endif |
|---|
| 201 | |
|---|
| 202 | if a:type_name ==# '*fallback*' |
|---|
| 203 | let s:fallback_actions[a:action_name] = a:new_action_function |
|---|
| 204 | elseif s:valid_type_name_p(a:type_name) |
|---|
| 205 | let s:types[a:type_name].actions[a:action_name] = a:new_action_function |
|---|
| 206 | else |
|---|
| 207 | return s:TRUE |
|---|
| 208 | endif |
|---|
| 209 | return s:TRUE |
|---|
| 210 | endfunction |
|---|
| 211 | |
|---|
| 212 | |
|---|
| 213 | |
|---|
| 214 | |
|---|
| 215 | function! ku#bang() "{{{2 |
|---|
| 216 | return s:bang |
|---|
| 217 | endfunction |
|---|
| 218 | |
|---|
| 219 | |
|---|
| 220 | |
|---|
| 221 | |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | |
|---|
| 225 | |
|---|
| 226 | " Core "{{{1 |
|---|
| 227 | function! s:end() "{{{2 |
|---|
| 228 | if s:end_locked_p |
|---|
| 229 | return |
|---|
| 230 | endif |
|---|
| 231 | let s:end_locked_p = s:TRUE |
|---|
| 232 | |
|---|
| 233 | let n = winnr('#') - 1 |
|---|
| 234 | |
|---|
| 235 | close |
|---|
| 236 | |
|---|
| 237 | let &completeopt = s:completeopt |
|---|
| 238 | let &ignorecase = s:ignorecase |
|---|
| 239 | execute n 'wincmd w' |
|---|
| 240 | execute s:winrestcmd |
|---|
| 241 | |
|---|
| 242 | let s:end_locked_p = s:FALSE |
|---|
| 243 | endfunction |
|---|
| 244 | let s:end_locked_p = s:FALSE |
|---|
| 245 | |
|---|
| 246 | |
|---|
| 247 | |
|---|
| 248 | |
|---|
| 249 | function! s:complete(findstart, base) "{{{2 |
|---|
| 250 | " items = a list of items |
|---|
| 251 | " item = a dictionary as described in :help complete-items. |
|---|
| 252 | " '^_ku_.*$' - additional keys used by ku. |
|---|
| 253 | " '^_{type}_.*$' - additional keys used by type {type}. |
|---|
| 254 | if a:findstart |
|---|
| 255 | let s:last_items = [] |
|---|
| 256 | return 0 |
|---|
| 257 | else |
|---|
| 258 | let s:last_items = [] |
|---|
| 259 | let pattern = (s:contains_the_prompt_p(a:base) |
|---|
| 260 | \ ? a:base[len(s:PROMPT):] |
|---|
| 261 | \ : a:base) |
|---|
| 262 | for type_name in (s:preferred_type ==# s:INVALID_TYPE |
|---|
| 263 | \ ? keys(s:types) : [s:preferred_type]) |
|---|
| 264 | let new_items = s:types[type_name].gather(pattern) |
|---|
| 265 | for new_item in new_items |
|---|
| 266 | let new_item['_ku_type'] = s:types[type_name] |
|---|
| 267 | let new_item['_ku_sort_priority'] |
|---|
| 268 | \ = [ |
|---|
| 269 | \ (match(new_item.word, g:ku_junk_item_pattern) < 0 ? 0 : 1), |
|---|
| 270 | \ (g:ku_sort_by_type_first_p ? type_name : 0), |
|---|
| 271 | \ s:match(new_item.word, s:make_asis_regexp(pattern)), |
|---|
| 272 | \ match(new_item.word, s:make_skip_regexp(pattern)), |
|---|
| 273 | \ new_item.word, |
|---|
| 274 | \ (!g:ku_sort_by_type_first_p ? type_name : 0), |
|---|
| 275 | \ ] |
|---|
| 276 | endfor |
|---|
| 277 | call extend(s:last_items, new_items) |
|---|
| 278 | endfor |
|---|
| 279 | |
|---|
| 280 | call filter(s:last_items, '0 <= v:val._ku_sort_priority[3]') |
|---|
| 281 | call sort(s:last_items, function('s:compare_items')) |
|---|
| 282 | return s:last_items |
|---|
| 283 | endif |
|---|
| 284 | endfunction |
|---|
| 285 | |
|---|
| 286 | |
|---|
| 287 | function! s:compare_items(a, b) |
|---|
| 288 | return s:compare_lists(a:a._ku_sort_priority, a:b._ku_sort_priority) |
|---|
| 289 | endfunction |
|---|
| 290 | |
|---|
| 291 | function! s:compare_lists(a, b) |
|---|
| 292 | " Assumption: len(a:a) == len(a:b) |
|---|
| 293 | for i in range(len(a:a)) |
|---|
| 294 | if a:a[i] < a:b[i] |
|---|
| 295 | return -1 |
|---|
| 296 | elseif a:a[i] > a:b[i] |
|---|
| 297 | return 1 |
|---|
| 298 | endif |
|---|
| 299 | endfor |
|---|
| 300 | return 0 |
|---|
| 301 | endfunction |
|---|
| 302 | |
|---|
| 303 | |
|---|
| 304 | |
|---|
| 305 | |
|---|
| 306 | function! s:do(choose_p) "{{{2 |
|---|
| 307 | let item = s:guess_the_appropriate_item() |
|---|
| 308 | |
|---|
| 309 | " To avoid doing some actions on this buffer and/or this window, close the |
|---|
| 310 | " ku window. |
|---|
| 311 | call s:end() |
|---|
| 312 | |
|---|
| 313 | " Do the specified aciton. |
|---|
| 314 | if type(item) == s:TYPE_DICTONARY |
|---|
| 315 | let ActionFunction |
|---|
| 316 | \ = (a:choose_p |
|---|
| 317 | \ ? s:choose_action_for_item(item) |
|---|
| 318 | \ : s:type_action(item._ku_type, item._ku_type.keys['*default*'])) |
|---|
| 319 | call s:apply(ActionFunction, [item]) |
|---|
| 320 | endif |
|---|
| 321 | endfunction |
|---|
| 322 | |
|---|
| 323 | |
|---|
| 324 | |
|---|
| 325 | |
|---|
| 326 | function! s:switch_preferred_type(d) "{{{2 |
|---|
| 327 | let type_names = sort(keys(s:types)) |
|---|
| 328 | call insert(type_names, s:INVALID_TYPE, 0) |
|---|
| 329 | |
|---|
| 330 | let s:preferred_type = type_names[ |
|---|
| 331 | \ (index(type_names, s:preferred_type) + a:d + len(type_names)) |
|---|
| 332 | \ % len(type_names) |
|---|
| 333 | \ ] |
|---|
| 334 | endfunction |
|---|
| 335 | |
|---|
| 336 | |
|---|
| 337 | |
|---|
| 338 | |
|---|
| 339 | function! s:on_InsertEnter() "{{{2 |
|---|
| 340 | let s:last_col = s:INVALID_COL |
|---|
| 341 | let s:auto_directory_insertion_done_p = s:FALSE |
|---|
| 342 | return s:on_CursorMovedI() |
|---|
| 343 | endfunction |
|---|
| 344 | |
|---|
| 345 | |
|---|
| 346 | |
|---|
| 347 | |
|---|
| 348 | function! s:on_CursorMovedI() "{{{2 |
|---|
| 349 | " Calling setline() has a side effect to the cursor. If the cursor beyond |
|---|
| 350 | " the end of line (i.e. getline('.') < col('.')), the cursor will be move at |
|---|
| 351 | " the last character of the current line after calling setline(). |
|---|
| 352 | let c0 = col('.') |
|---|
| 353 | call setline(1, '') " dummy setting to know whether the cursor is moved. |
|---|
| 354 | let c1 = col('.') |
|---|
| 355 | call setline(1, printf('type=%s', s:preferred_type)) |
|---|
| 356 | |
|---|
| 357 | " The order of these conditions are important. |
|---|
| 358 | let line = getline('.') |
|---|
| 359 | if !s:contains_the_prompt_p(line) |
|---|
| 360 | let keys = repeat("\<Right>", len(s:PROMPT)) |
|---|
| 361 | call s:complete_the_prompt() |
|---|
| 362 | elseif col('.') <= len(s:PROMPT) |
|---|
| 363 | " The cursor is inside the prompt. |
|---|
| 364 | let keys = repeat("\<Right>", len(s:PROMPT) - col('.') + 1) |
|---|
| 365 | elseif len(line) < col('.') && col('.') != s:last_col |
|---|
| 366 | " New character is inserted. |
|---|
| 367 | " FIXME: path separator assumption |
|---|
| 368 | if (!s:auto_directory_insertion_done_p) |
|---|
| 369 | \ && line[-1:] == '/' |
|---|
| 370 | \ && len(s:PROMPT) + 2 <= len(line) |
|---|
| 371 | let text = s:text_for_auto_completion(line,s:complete(s:FALSE,line[:-2])) |
|---|
| 372 | if text != '' |
|---|
| 373 | call setline('.', text) |
|---|
| 374 | let keys = "\<End>/" |
|---|
| 375 | let s:auto_directory_insertion_done_p = s:TRUE |
|---|
| 376 | else |
|---|
| 377 | let keys = s:KEYS_TO_START_COMPLETION |
|---|
| 378 | let s:auto_directory_insertion_done_p = s:FALSE |
|---|
| 379 | endif |
|---|
| 380 | else |
|---|
| 381 | let keys = s:KEYS_TO_START_COMPLETION |
|---|
| 382 | let s:auto_directory_insertion_done_p = s:FALSE |
|---|
| 383 | endif |
|---|
| 384 | else |
|---|
| 385 | let keys = '' |
|---|
| 386 | endif |
|---|
| 387 | |
|---|
| 388 | let s:last_col = col('.') |
|---|
| 389 | return (c0 != c1 ? "\<Right>" : '') . keys |
|---|
| 390 | endfunction |
|---|
| 391 | |
|---|
| 392 | |
|---|
| 393 | |
|---|
| 394 | |
|---|
| 395 | |
|---|
| 396 | |
|---|
| 397 | |
|---|
| 398 | |
|---|
| 399 | " Misc. "{{{1 |
|---|
| 400 | function! s:SID_PREFIX() "{{{2 |
|---|
| 401 | return matchstr(expand('<sfile>'), '<SNR>\d\+_') |
|---|
| 402 | endfunction |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | |
|---|
| 406 | |
|---|
| 407 | function! s:nop(...) "{{{2 |
|---|
| 408 | return |
|---|
| 409 | endfunction |
|---|
| 410 | |
|---|
| 411 | |
|---|
| 412 | |
|---|
| 413 | |
|---|
| 414 | function! s:string(s) "{{{2 |
|---|
| 415 | " like strtrans(), but convert into more human-readable notation on special |
|---|
| 416 | " keys such as <Left>. |
|---|
| 417 | return strtrans(a:s) " FIXME: NIY. |
|---|
| 418 | endfunction |
|---|
| 419 | |
|---|
| 420 | |
|---|
| 421 | |
|---|
| 422 | |
|---|
| 423 | function! s:initialize_ku_buffer() "{{{2 |
|---|
| 424 | " The current buffer is the ku buffer which is not initialized yet. |
|---|
| 425 | |
|---|
| 426 | " Basic settings. |
|---|
| 427 | setlocal buftype=nofile |
|---|
| 428 | setlocal bufhidden=hide |
|---|
| 429 | setlocal noswapfile |
|---|
| 430 | setlocal nobuflisted |
|---|
| 431 | let &l:omnifunc = s:SID_PREFIX() . 'complete' |
|---|
| 432 | silent file `='*ku*'` |
|---|
| 433 | |
|---|
| 434 | " Autocommands. |
|---|
| 435 | autocmd InsertEnter <buffer> call feedkeys(<SID>on_InsertEnter(), 'n') |
|---|
| 436 | autocmd CursorMovedI <buffer> call feedkeys(<SID>on_CursorMovedI(), 'n') |
|---|
| 437 | autocmd BufLeave <buffer> call <SID>end() |
|---|
| 438 | autocmd WinLeave <buffer> call <SID>end() |
|---|
| 439 | autocmd TabLeave <buffer> call <SID>end() |
|---|
| 440 | |
|---|
| 441 | " Mappings. |
|---|
| 442 | nnoremap <buffer> <silent> <Plug>(ku-cancel) |
|---|
| 443 | \ :<C-u>call <SID>end()<Return> |
|---|
| 444 | nnoremap <buffer> <silent> <Plug>(ku-do-default) |
|---|
| 445 | \ :<C-u>call <SID>do(0)<Return> |
|---|
| 446 | nnoremap <buffer> <silent> <Plug>(ku-choose-action) |
|---|
| 447 | \ :<C-u>call <SID>do(1)<Return> |
|---|
| 448 | nnoremap <buffer> <silent> <Plug>(ku-next-type) |
|---|
| 449 | \ :<C-u>call <SID>switch_preferred_type(1)<Return> |
|---|
| 450 | nnoremap <buffer> <silent> <Plug>(ku-previous-type) |
|---|
| 451 | \ :<C-u>call <SID>switch_preferred_type(-1)<Return> |
|---|
| 452 | |
|---|
| 453 | nnoremap <buffer> <silent> <Plug>(ku-%-enter-insert) a |
|---|
| 454 | inoremap <buffer> <silent> <Plug>(ku-%-leave-insert) <Esc> |
|---|
| 455 | inoremap <buffer> <silent> <Plug>(ku-%-accept-completion) <C-y> |
|---|
| 456 | inoremap <buffer> <silent> <Plug>(ku-%-cancel-completion) <C-e> |
|---|
| 457 | |
|---|
| 458 | imap <buffer> <silent> <Plug>(ku-cancel) |
|---|
| 459 | \ <Plug>(ku-%-leave-insert)<Plug>(ku-cancel) |
|---|
| 460 | imap <buffer> <silent> <Plug>(ku-do-default) |
|---|
| 461 | \ <Plug>(ku-%-accept-completion) |
|---|
| 462 | \<Plug>(ku-%-leave-insert) |
|---|
| 463 | \<Plug>(ku-do-default) |
|---|
| 464 | imap <buffer> <silent> <Plug>(ku-choose-action) |
|---|
| 465 | \ <Plug>(ku-%-accept-completion) |
|---|
| 466 | \<Plug>(ku-%-leave-insert) |
|---|
| 467 | \<Plug>(ku-choose-action) |
|---|
| 468 | imap <buffer> <silent> <Plug>(ku-next-type) |
|---|
| 469 | \ <Plug>(ku-%-cancel-completion) |
|---|
| 470 | \<Plug>(ku-%-leave-insert) |
|---|
| 471 | \<Plug>(ku-next-type) |
|---|
| 472 | \<Plug>(ku-%-enter-insert) |
|---|
| 473 | imap <buffer> <silent> <Plug>(ku-previous-type) |
|---|
| 474 | \ <Plug>(ku-%-cancel-completion) |
|---|
| 475 | \<Plug>(ku-%-leave-insert) |
|---|
| 476 | \<Plug>(ku-previous-type) |
|---|
| 477 | \<Plug>(ku-%-enter-insert) |
|---|
| 478 | |
|---|
| 479 | inoremap <buffer> <expr> <BS> pumvisible() ? "\<C-e>\<BS>" : "\<BS>" |
|---|
| 480 | imap <buffer> <C-h> <BS> |
|---|
| 481 | " <C-n>/<C-p> ... Vim doesn't expand these keys in Insert mode completion. |
|---|
| 482 | |
|---|
| 483 | " User initialization. |
|---|
| 484 | silent doautocmd User KuBufferInitialize |
|---|
| 485 | if !exists('#User#KuBufferInitialize') |
|---|
| 486 | call ku#default_key_mappings() |
|---|
| 487 | endif |
|---|
| 488 | endfunction |
|---|
| 489 | |
|---|
| 490 | |
|---|
| 491 | |
|---|
| 492 | |
|---|
| 493 | function! s:ku_buffer_exists_p() "{{{2 |
|---|
| 494 | return (s:bufnr != s:INVALID_BUFNR) && bufexists(s:bufnr) |
|---|
| 495 | endfunction |
|---|
| 496 | |
|---|
| 497 | |
|---|
| 498 | |
|---|
| 499 | |
|---|
| 500 | function! s:map(re_p, lhs, rhs) "{{{2 |
|---|
| 501 | for mode in ['n', 'i'] " FIXME: should include other modes? |
|---|
| 502 | execute mode.(a:re_p ? 'map' : 'noremap') '<buffer> <silent>' a:lhs a:rhs |
|---|
| 503 | endfor |
|---|
| 504 | endfunction |
|---|
| 505 | |
|---|
| 506 | |
|---|
| 507 | function! s:remap(lhs, rhs) |
|---|
| 508 | return s:map(s:TRUE, a:lhs, a:rhs) |
|---|
| 509 | endfunction |
|---|
| 510 | |
|---|
| 511 | |
|---|
| 512 | function! s:noremap(lhs, rhs) |
|---|
| 513 | return s:map(s:FALSE, a:lhs, a:rhs) |
|---|
| 514 | endfunction |
|---|
| 515 | |
|---|
| 516 | |
|---|
| 517 | |
|---|
| 518 | |
|---|
| 519 | function! s:match(s, pattern) "{{{2 |
|---|
| 520 | let NUM_MAX = 2147483647 " FIXME: valid value. |
|---|
| 521 | let i = match(a:s, a:pattern) |
|---|
| 522 | return 0 <= i ? i : NUM_MAX |
|---|
| 523 | endfunction |
|---|
| 524 | |
|---|
| 525 | |
|---|
| 526 | |
|---|
| 527 | |
|---|
| 528 | function! s:make_asis_regexp(s) "{{{2 |
|---|
| 529 | " FIXME: case sensitivity |
|---|
| 530 | return '\c\V' . substitute(substitute(a:s, '\s\+', ' ', 'g'), '\', '\\', 'g') |
|---|
| 531 | endfunction |
|---|
| 532 | |
|---|
| 533 | |
|---|
| 534 | |
|---|
| 535 | |
|---|
| 536 | function! s:make_skip_regexp(s) "{{{2 |
|---|
| 537 | " FIXME: case sensitivity |
|---|
| 538 | " FIXME: path separator assumption |
|---|
| 539 | let p_asis = s:make_asis_regexp(substitute(a:s, '/', ' / ', 'g')) |
|---|
| 540 | return substitute(p_asis, '\s', '\\.\\*', 'g') |
|---|
| 541 | endfunction |
|---|
| 542 | |
|---|
| 543 | |
|---|
| 544 | |
|---|
| 545 | |
|---|
| 546 | function! s:complete_the_prompt() "{{{2 |
|---|
| 547 | call setline('.', s:PROMPT . getline('.')) |
|---|
| 548 | endfunction |
|---|
| 549 | |
|---|
| 550 | |
|---|
| 551 | |
|---|
| 552 | |
|---|
| 553 | function! s:contains_the_prompt_p(s) "{{{2 |
|---|
| 554 | return len(s:PROMPT) <= len(a:s) && a:s[:len(s:PROMPT) - 1] ==# s:PROMPT |
|---|
| 555 | endfunction |
|---|
| 556 | |
|---|
| 557 | |
|---|
| 558 | |
|---|
| 559 | |
|---|
| 560 | function! s:text_for_auto_completion(line, items) "{{{2 |
|---|
| 561 | " Note that a:line always ends with '/', because this function is always |
|---|
| 562 | " called by typing '/'. |
|---|
| 563 | " FIXME: path separator assumption. |
|---|
| 564 | let line_components = split(a:line[len(s:PROMPT):], '/', s:TRUE) |
|---|
| 565 | for item in a:items |
|---|
| 566 | let item_components = split(item.word, '/', s:TRUE) |
|---|
| 567 | |
|---|
| 568 | if len(line_components) <= len(item_components) |
|---|
| 569 | " Discard items which don't match the line from the head of them. |
|---|
| 570 | " Note that line_components[-1] is always '' and line_components[-2] is |
|---|
| 571 | " almost imperfect, so that they aren't used. |
|---|
| 572 | let i = 0 |
|---|
| 573 | for i in range(len(line_components) - 2) |
|---|
| 574 | if line_components[i] != item_components[i] |
|---|
| 575 | break |
|---|
| 576 | endif |
|---|
| 577 | endfor |
|---|
| 578 | if line_components[i] != item_components[i] |
|---|
| 579 | continue |
|---|
| 580 | endif |
|---|
| 581 | " OK |
|---|
| 582 | elseif isdirectory(item.word) " for type file. |
|---|
| 583 | " OK |
|---|
| 584 | else |
|---|
| 585 | continue |
|---|
| 586 | endif |
|---|
| 587 | |
|---|
| 588 | return join(item_components[:len(line_components) - 2], '/') |
|---|
| 589 | endfor |
|---|
| 590 | return '' |
|---|
| 591 | endfunction |
|---|
| 592 | |
|---|
| 593 | |
|---|
| 594 | |
|---|
| 595 | |
|---|
| 596 | function! s:guess_the_appropriate_item() "{{{2 |
|---|
| 597 | " If there are two or more items which have the same 'word', the first item |
|---|
| 598 | " appeared in s:last_item will be choosen. Because it's not possible to |
|---|
| 599 | " know which item was selected in ins-completion-menu. (It's possible if |
|---|
| 600 | " some keys (<C-n>, <C-p> and others) are mapped when pumvisible(), but Vim |
|---|
| 601 | " doesn't expand such mappings). |
|---|
| 602 | " |
|---|
| 603 | " But the types of such items are usually different, and s:preferred_type can |
|---|
| 604 | " be used to avoid this problem. |
|---|
| 605 | " |
|---|
| 606 | " Another idea is merging actions of same 'word' items. |
|---|
| 607 | " But it seems to be dirty. |
|---|
| 608 | |
|---|
| 609 | let line = getline('.') |
|---|
| 610 | if len(s:last_items) == 0 |
|---|
| 611 | " There is no item which matches to the user input pattern. |
|---|
| 612 | " So return the user input pattern instead of any item. |
|---|
| 613 | let item = line |
|---|
| 614 | elseif s:contains_the_prompt_p(line) |
|---|
| 615 | " User seems to choose the first item in the completion list. |
|---|
| 616 | let item = s:last_items[0] |
|---|
| 617 | else |
|---|
| 618 | " User seems to choose an item in the completion list, and the line is |
|---|
| 619 | " altered by the completion list. |
|---|
| 620 | for i in s:last_items |
|---|
| 621 | if line ==# i.word |
|---|
| 622 | break |
|---|
| 623 | endif |
|---|
| 624 | endfor |
|---|
| 625 | let item = (line ==# i.word ? i : 0) |
|---|
| 626 | endif |
|---|
| 627 | |
|---|
| 628 | return item |
|---|
| 629 | endfunction |
|---|
| 630 | |
|---|
| 631 | |
|---|
| 632 | |
|---|
| 633 | |
|---|
| 634 | function! s:choose_action_for_item(item) "{{{2 |
|---|
| 635 | call s:show_available_actions_message(a:item) |
|---|
| 636 | |
|---|
| 637 | let c = nr2char(getchar()) |
|---|
| 638 | redraw " clear the menu message lines to avoid hit-enter prompt. |
|---|
| 639 | |
|---|
| 640 | if has_key(a:item._ku_type.keys, c) |
|---|
| 641 | let Action = s:type_action(a:item._ku_type, a:item._ku_type.keys[c]) |
|---|
| 642 | if s:callable_p(Action) |
|---|
| 643 | return Action |
|---|
| 644 | endif |
|---|
| 645 | endif |
|---|
| 646 | |
|---|
| 647 | echo 'The key' s:string(c) 'is not associated with any action' |
|---|
| 648 | \ '-- nothing happened.' |
|---|
| 649 | return function('s:nop') |
|---|
| 650 | endfunction |
|---|
| 651 | |
|---|
| 652 | |
|---|
| 653 | |
|---|
| 654 | |
|---|
| 655 | function! s:show_available_actions_message(item) "{{{2 |
|---|
| 656 | let items = items(a:item._ku_type.keys) |
|---|
| 657 | call map(items, '[v:val[1], v:val[0]]') |
|---|
| 658 | call sort(items) |
|---|
| 659 | call filter(items, 'v:val[1] !=# "*default*"') |
|---|
| 660 | let keys = map(copy(items), 'v:val[1]') |
|---|
| 661 | let names = map(copy(items), 'v:val[0]') |
|---|
| 662 | let max_key_length = max(map(copy(keys), 'len(s:string(v:val))')) |
|---|
| 663 | let max_name_length = max(map(copy(names), 'len(v:val)')) |
|---|
| 664 | let padding = 3 |
|---|
| 665 | let max_cell_length = max_key_length + 3 + max_name_length + padding |
|---|
| 666 | let format = '%*s%*s - %-*s' |
|---|
| 667 | |
|---|
| 668 | let max_column = max([1, (&columns + padding - 1) / max_cell_length]) |
|---|
| 669 | let max_column = min([max_column, 4]) |
|---|
| 670 | let n = len(items) |
|---|
| 671 | let max_row = n / max_column + (n % max_column != 0) |
|---|
| 672 | |
|---|
| 673 | echo printf('Available actions for %s (type %s) are:', |
|---|
| 674 | \ a:item.word, a:item._ku_type.name) |
|---|
| 675 | for r in range(max_row) |
|---|
| 676 | let i = r |
|---|
| 677 | echo '' |
|---|
| 678 | while i < n |
|---|
| 679 | echon printf(format, |
|---|
| 680 | \ (i == r ? 0 : padding), '', |
|---|
| 681 | \ max_key_length, s:string(items[i][1]), |
|---|
| 682 | \ max_name_length, items[i][0]) |
|---|
| 683 | let i += max_row |
|---|
| 684 | endwhile |
|---|
| 685 | endfor |
|---|
| 686 | echo '' |
|---|
| 687 | endfunction |
|---|
| 688 | |
|---|
| 689 | |
|---|
| 690 | |
|---|
| 691 | |
|---|
| 692 | function! s:valid_type_name_p(name) "{{{2 |
|---|
| 693 | return has_key(s:types, a:name) |
|---|
| 694 | endfunction |
|---|
| 695 | |
|---|
| 696 | |
|---|
| 697 | |
|---|
| 698 | |
|---|
| 699 | function! s:valid_type_definition_p(args) "{{{2 |
|---|
| 700 | if type(a:args) != s:TYPE_DICTONARY |
|---|
| 701 | return s:FALSE |
|---|
| 702 | endif |
|---|
| 703 | |
|---|
| 704 | " -- 'name' |
|---|
| 705 | if !s:has_valid_entry(a:args, 'name', s:TYPE_STRING) |
|---|
| 706 | return s:FALSE |
|---|
| 707 | endif |
|---|
| 708 | if !(a:args.name =~# '^[a-z]\+$') | return s:FALSE | endif |
|---|
| 709 | |
|---|
| 710 | " -- 'gather' |
|---|
| 711 | if !s:has_valid_entry(a:args, 'gather', s:TYPE_FUNCTION) |
|---|
| 712 | return s:FALSE |
|---|
| 713 | endif |
|---|
| 714 | |
|---|
| 715 | " -- 'initialize' |
|---|
| 716 | if has_key(a:args, 'initialize') |
|---|
| 717 | if !(type(a:args.initialize) == s:TYPE_FUNCTION) | return s:FALSE | endif |
|---|
| 718 | else |
|---|
| 719 | let a:args.initialize = function('s:nop') |
|---|
| 720 | endif |
|---|
| 721 | |
|---|
| 722 | " -- 'keys' |
|---|
| 723 | if !s:has_valid_entry(a:args, 'keys', s:TYPE_DICTONARY) |
|---|
| 724 | return s:FALSE |
|---|
| 725 | endif |
|---|
| 726 | for k in keys(a:args.keys) |
|---|
| 727 | if !(type(k) == s:TYPE_STRING && type(a:args.keys[k]) == s:TYPE_STRING) |
|---|
| 728 | return s:FALSE |
|---|
| 729 | endif |
|---|
| 730 | endfor |
|---|
| 731 | if !has_key(a:args.keys, '*default*') |
|---|
| 732 | return s:FALSE |
|---|
| 733 | endif |
|---|
| 734 | |
|---|
| 735 | " -- 'actions' |
|---|
| 736 | if !s:has_valid_entry(a:args, 'actions', s:TYPE_DICTONARY) |
|---|
| 737 | return s:FALSE |
|---|
| 738 | endif |
|---|
| 739 | for k in keys(a:args.actions) |
|---|
| 740 | if !(type(k) == s:TYPE_STRING && s:callable_p(a:args.actions[k])) |
|---|
| 741 | return s:FALSE |
|---|
| 742 | endif |
|---|
| 743 | endfor |
|---|
| 744 | for n in values(a:args.keys) |
|---|
| 745 | if !s:type_has_action_p(a:args, n) |
|---|
| 746 | return s:FALSE |
|---|
| 747 | endif |
|---|
| 748 | endfor |
|---|
| 749 | |
|---|
| 750 | |
|---|
| 751 | " -- other entries -- |
|---|
| 752 | |
|---|
| 753 | return s:TRUE |
|---|
| 754 | endfunction |
|---|
| 755 | |
|---|
| 756 | |
|---|
| 757 | |
|---|
| 758 | |
|---|
| 759 | function! s:type_has_action_p(type, action_name) "{{{2 |
|---|
| 760 | return s:callable_p(s:type_action(a:type, a:action_name)) |
|---|
| 761 | endfunction |
|---|
| 762 | |
|---|
| 763 | |
|---|
| 764 | |
|---|
| 765 | |
|---|
| 766 | function! s:type_action(type, action_name) "{{{2 |
|---|
| 767 | let Action = get(a:type.actions, a:action_name, 0) |
|---|
| 768 | if s:callable_p(Action) |
|---|
| 769 | return Action |
|---|
| 770 | endif |
|---|
| 771 | return get(s:fallback_actions, a:action_name, 0) |
|---|
| 772 | endfunction |
|---|
| 773 | |
|---|
| 774 | |
|---|
| 775 | |
|---|
| 776 | |
|---|
| 777 | function! s:has_valid_entry(dict, key, type) "{{{2 |
|---|
| 778 | return has_key(a:dict, a:key) && type(a:dict[a:key]) == a:type |
|---|
| 779 | endfunction |
|---|
| 780 | |
|---|
| 781 | |
|---|
| 782 | |
|---|
| 783 | |
|---|
| 784 | function! s:callable_p(obj) "{{{2 |
|---|
| 785 | return (type(a:obj) == s:TYPE_FUNCTION) |
|---|
| 786 | \ || ((type(a:obj) == s:TYPE_DICTONARY) && has_key(a:obj, '__call__')) |
|---|
| 787 | endfunction |
|---|
| 788 | |
|---|
| 789 | |
|---|
| 790 | |
|---|
| 791 | |
|---|
| 792 | function! s:apply(obj, args) "{{{2 |
|---|
| 793 | if type(a:obj) == s:TYPE_FUNCTION |
|---|
| 794 | return call(a:obj, a:args) |
|---|
| 795 | elseif type(a:obj) == s:TYPE_DICTONARY && has_key(a:obj, '__call__') |
|---|
| 796 | return call(a:obj.__call__, a:args, a:obj) |
|---|
| 797 | else |
|---|
| 798 | throw 'This object is not callable:' string(a:obj) |
|---|
| 799 | endif |
|---|
| 800 | endfunction |
|---|
| 801 | |
|---|
| 802 | |
|---|
| 803 | |
|---|
| 804 | |
|---|
| 805 | function! s:pa(f, ...) "{{{2 |
|---|
| 806 | " pa = Partial Apply |
|---|
| 807 | " Returns a callable object g, |
|---|
| 808 | " where g(b, ...) is equivalent to f(a, ..., b, ...). |
|---|
| 809 | let g = {} |
|---|
| 810 | let g.f = a:f |
|---|
| 811 | let g.args = copy(a:000) " a:000 will be lost after this execution. |
|---|
| 812 | function! g.__call__(...) |
|---|
| 813 | return call(self.f, self.args + a:000) |
|---|
| 814 | endfunction |
|---|
| 815 | return g |
|---|
| 816 | endfunction |
|---|
| 817 | |
|---|
| 818 | |
|---|
| 819 | |
|---|
| 820 | |
|---|
| 821 | |
|---|
| 822 | |
|---|
| 823 | |
|---|
| 824 | |
|---|
| 825 | " Built-in Types "{{{1 |
|---|
| 826 | " common definitions "{{{2 |
|---|
| 827 | function! s:_type_any_action_ex(item) |
|---|
| 828 | call feedkeys(':', 'n') |
|---|
| 829 | call feedkeys(ku#bang(), 'n') |
|---|
| 830 | call feedkeys(' ', 'n') |
|---|
| 831 | call feedkeys(a:item.word, 'n') |
|---|
| 832 | call feedkeys("\<C-b>", 'n') |
|---|
| 833 | endfunction |
|---|
| 834 | call ku#custom_action('*fallback*', 'ex', function('s:_type_any_action_ex')) |
|---|
| 835 | |
|---|
| 836 | |
|---|
| 837 | function! s:_type_any_action_xcd(cd_command, item) |
|---|
| 838 | " FIXME: escape special characters. |
|---|
| 839 | if isdirectory(a:item.word) |
|---|
| 840 | execute a:cd_command a:item.word |
|---|
| 841 | elseif filereadable(a:item.word) |
|---|
| 842 | execute a:cd_command fnamemodify(a:item.word, ':h') |
|---|
| 843 | else |
|---|
| 844 | echo printf('Item %s (type: %s) is not a file or directory.', |
|---|
| 845 | \ a:item.word, a:item._ku_type.name) |
|---|
| 846 | endif |
|---|
| 847 | endfunction |
|---|
| 848 | call ku#custom_action('*fallback*', 'cd', s:pa('s:_type_any_action_xcd', 'cd')) |
|---|
| 849 | call ku#custom_action('*fallback*', 'cd-local', |
|---|
| 850 | \ s:pa('s:_type_any_action_xcd', 'lcd')) |
|---|
| 851 | |
|---|
| 852 | |
|---|
| 853 | |
|---|
| 854 | |
|---|
| 855 | " buffer "{{{2 |
|---|
| 856 | " FIXME: how about unlisted buffers? |
|---|
| 857 | let s:_type_buffer_cached_items = [] |
|---|
| 858 | let s:_type_buffer_last_bufnr = s:INVALID_BUFNR |
|---|
| 859 | function! s:_type_buffer_gather(pattern) |
|---|
| 860 | if s:_type_buffer_last_bufnr == bufnr('$') |
|---|
| 861 | call filter(s:_type_buffer_cached_items, 'bufexists(v:val._buffer_number)') |
|---|
| 862 | else |
|---|
| 863 | let s:_type_buffer_cached_items = [] |
|---|
| 864 | let format = 'buffer %' . len(bufnr('$')) . 'd' |
|---|
| 865 | for i in range(1, bufnr('$')) |
|---|
| 866 | if bufexists(i) && buflisted(i) |
|---|
| 867 | call add(s:_type_buffer_cached_items, |
|---|
| 868 | \ { |
|---|
| 869 | \ 'word': bufname(i), |
|---|
| 870 | \ 'menu': printf(format, i), |
|---|
| 871 | \ 'dup': s:TRUE, |
|---|
| 872 | \ '_buffer_number': i, |
|---|
| 873 | \ }) |
|---|
| 874 | endif |
|---|
| 875 | endfor |
|---|
| 876 | let s:_type_buffer_last_bufnr = bufnr('$') |
|---|
| 877 | endif |
|---|
| 878 | return s:_type_buffer_cached_items |
|---|
| 879 | endfunction |
|---|
| 880 | |
|---|
| 881 | |
|---|
| 882 | function! s:_type_buffer_action_open(item) |
|---|
| 883 | execute a:item._buffer_number 'buffer'.ku#bang() |
|---|
| 884 | endfunction |
|---|
| 885 | function! s:_type_buffer_action_xsplit(modifier, item) |
|---|
| 886 | execute a:modifier a:item._buffer_number 'sbuffer' |
|---|
| 887 | endfunction |
|---|
| 888 | function! s:_type_buffer_action_xdelete(command, item) |
|---|
| 889 | execute a:item._buffer_number a:command.ku#bang() |
|---|
| 890 | endfunction |
|---|
| 891 | |
|---|
| 892 | |
|---|
| 893 | call ku#register_type({ |
|---|
| 894 | \ 'name': 'buffer', |
|---|
| 895 | \ 'gather': function('s:_type_buffer_gather'), |
|---|
| 896 | \ 'keys': { |
|---|
| 897 | \ "*default*": 'open', |
|---|
| 898 | \ "o": 'open', |
|---|
| 899 | \ "\<C-o>": 'open', |
|---|
| 900 | \ "n": 'split', |
|---|
| 901 | \ "\<C-n>": 'split', |
|---|
| 902 | \ "s": 'split', |
|---|
| 903 | \ "\<C-s>": 'split', |
|---|
| 904 | \ "v": 'vsplit', |
|---|
| 905 | \ "\<C-v>": 'vsplit', |
|---|
| 906 | \ "h": 'split-left', |
|---|
| 907 | \ "\<C-h>": 'split-left', |
|---|
| 908 | \ "H": 'split-far-left', |
|---|
| 909 | \ "j": 'split-down', |
|---|
| 910 | \ "\<C-j>": 'split-down', |
|---|
| 911 | \ "J": 'split-bottom', |
|---|
| 912 | \ "k": 'split-up', |
|---|
| 913 | \ "\<C-k>": 'split-up', |
|---|
| 914 | \ "K": 'split-top', |
|---|
| 915 | \ "l": 'split-right', |
|---|
| 916 | \ "\<C-l>": 'split-right', |
|---|
| 917 | \ "L": 'split-far-right', |
|---|
| 918 | \ ";": 'ex', |
|---|
| 919 | \ ":": 'ex', |
|---|
| 920 | \ "/": 'cd', |
|---|
| 921 | \ "?": 'cd-local', |
|---|
| 922 | \ "U": 'unload', |
|---|
| 923 | \ "D": 'delete', |
|---|
| 924 | \ "W": 'wipeout', |
|---|
| 925 | \ }, |
|---|
| 926 | \ 'actions': { |
|---|
| 927 | \ 'open': |
|---|
| 928 | \ function('s:_type_buffer_action_open'), |
|---|
| 929 | \ 'split': |
|---|
| 930 | \ s:pa('s:_type_buffer_action_xsplit', ''), |
|---|
| 931 | \ 'vsplit': |
|---|
| 932 | \ s:pa('s:_type_buffer_action_xsplit', 'vertical'), |
|---|
| 933 | \ 'split-left': |
|---|
| 934 | \ s:pa('s:_type_buffer_action_xsplit', 'vertical leftabove'), |
|---|
| 935 | \ 'split-far-left': |
|---|
| 936 | \ s:pa('s:_type_buffer_action_xsplit', 'vertical topleft'), |
|---|
| 937 | \ 'split-down': |
|---|
| 938 | \ s:pa('s:_type_buffer_action_xsplit', 'rightbelow'), |
|---|
| 939 | \ 'split-bottom': |
|---|
| 940 | \ s:pa('s:_type_buffer_action_xsplit', 'botright'), |
|---|
| 941 | \ 'split-up': |
|---|
| 942 | \ s:pa('s:_type_buffer_action_xsplit', 'leftabove'), |
|---|
| 943 | \ 'split-top': |
|---|
| 944 | \ s:pa('s:_type_buffer_action_xsplit', 'topleft'), |
|---|
| 945 | \ 'split-right': |
|---|
| 946 | \ s:pa('s:_type_buffer_action_xsplit', 'vertical rightbelow'), |
|---|
| 947 | \ 'split-far-right': |
|---|
| 948 | \ s:pa('s:_type_buffer_action_xsplit', 'vertical botright'), |
|---|
| 949 | \ 'unload': |
|---|
| 950 | \ s:pa('s:_type_buffer_action_xdelete', 'bunload'), |
|---|
| 951 | \ 'delete': |
|---|
| 952 | \ s:pa('s:_type_buffer_action_xdelete', 'bdelete'), |
|---|
| 953 | \ 'wipeout': |
|---|
| 954 | \ s:pa('s:_type_buffer_action_xdelete', 'bwipeout'), |
|---|
| 955 | \ }, |
|---|
| 956 | \ }) |
|---|
| 957 | |
|---|
| 958 | |
|---|
| 959 | |
|---|
| 960 | |
|---|
| 961 | " file "{{{2 |
|---|
| 962 | " Note: Timestamps of directories will be updated when files/directories are |
|---|
| 963 | " created or removed. |
|---|
| 964 | " key = full path of the directory |
|---|
| 965 | " value = [time stamp of the directory, list of items in the directory] |
|---|
| 966 | " FIXME: how about when the cache becames too large? |
|---|
| 967 | let s:_type_file_cache = {} |
|---|
| 968 | |
|---|
| 969 | function! s:_type_file_gather(pattern) |
|---|
| 970 | " FIXME: path separetor assumption. |
|---|
| 971 | " All components but the last one in a:pattern are treated as-is. |
|---|
| 972 | let last_slash = strridx(a:pattern, '/') |
|---|
| 973 | if 0 <= last_slash |
|---|
| 974 | let base_directory = a:pattern[:last_slash] |
|---|
| 975 | let last_component = a:pattern[last_slash+1:] |
|---|
| 976 | let cache_key = fnamemodify(base_directory, ':p') |
|---|
| 977 | else |
|---|
| 978 | let base_directory = '' |
|---|
| 979 | let last_component = a:pattern |
|---|
| 980 | let cache_key = fnamemodify('.', ':p') |
|---|
| 981 | endif |
|---|
| 982 | |
|---|
| 983 | if !has_key(s:_type_file_cache, cache_key) |
|---|
| 984 | \ || getftime(cache_key) != s:_type_file_cache[cache_key][0] |
|---|
| 985 | let items = [] |
|---|
| 986 | for f in ['*', '.[^.]*'] |
|---|
| 987 | for item in split(glob(base_directory . f), '\n') |
|---|
| 988 | call add(items, {'word': item, 'menu': 'file', 'dup': s:TRUE}) |
|---|
| 989 | endfor |
|---|
| 990 | endfor |
|---|
| 991 | let s:_type_file_cache[cache_key] = [getftime(cache_key), items] |
|---|
| 992 | endif |
|---|
| 993 | return s:_type_file_cache[cache_key][1] |
|---|
| 994 | endfunction |
|---|
| 995 | |
|---|
| 996 | |
|---|
| 997 | " FIXME: filename with special characters -- should escape? |
|---|
| 998 | function! s:_type_file_action_open(item) |
|---|
| 999 | execute 'edit'.ku#bang() a:item.word |
|---|
| 1000 | endfunction |
|---|
| 1001 | function! s:_type_file_action_xsplit(modifier, item) |
|---|
| 1002 | execute a:modifier 'split' a:item.word |
|---|
| 1003 | endfunction |
|---|
| 1004 | |
|---|
| 1005 | |
|---|
| 1006 | call ku#register_type({ |
|---|
| 1007 | \ 'name': 'file', |
|---|
| 1008 | \ 'gather': function('s:_type_file_gather'), |
|---|
| 1009 | \ 'keys': { |
|---|
| 1010 | \ "*default*": 'open', |
|---|
| 1011 | \ "o": 'open', |
|---|
| 1012 | \ "\<C-o>": 'open', |
|---|
| 1013 | \ "n": 'split', |
|---|
| 1014 | \ "\<C-n>": 'split', |
|---|
| 1015 | \ "s": 'split', |
|---|
| 1016 | \ "\<C-s>": 'split', |
|---|
| 1017 | \ "v": 'vsplit', |
|---|
| 1018 | \ "\<C-v>": 'vsplit', |
|---|
| 1019 | \ "h": 'split-left', |
|---|
| 1020 | \ "\<C-h>": 'split-left', |
|---|
| 1021 | \ "H": 'split-far-left', |
|---|
| 1022 | \ "j": 'split-down', |
|---|
| 1023 | \ "\<C-j>": 'split-down', |
|---|
| 1024 | \ "J": 'split-bottom', |
|---|
| 1025 | \ "k": 'split-up', |
|---|
| 1026 | \ "\<C-k>": 'split-up', |
|---|
| 1027 | \ "K": 'split-top', |
|---|
| 1028 | \ "l": 'split-right', |
|---|
| 1029 | \ "\<C-l>": 'split-right', |
|---|
| 1030 | \ "L": 'split-far-right', |
|---|
| 1031 | \ ";": 'ex', |
|---|
| 1032 | \ ":": 'ex', |
|---|
| 1033 | \ "/": 'cd', |
|---|
| 1034 | \ "?": 'cd-local', |
|---|
| 1035 | \ }, |
|---|
| 1036 | \ 'actions': { |
|---|
| 1037 | \ 'open': |
|---|
| 1038 | \ function('s:_type_file_action_open'), |
|---|
| 1039 | \ 'split': |
|---|
| 1040 | \ s:pa('s:_type_file_action_xsplit', ''), |
|---|
| 1041 | \ 'vsplit': |
|---|
| 1042 | \ s:pa('s:_type_file_action_xsplit', 'vertical'), |
|---|
| 1043 | \ 'split-left': |
|---|
| 1044 | \ s:pa('s:_type_file_action_xsplit', 'vertical leftabove'), |
|---|
| 1045 | \ 'split-far-left': |
|---|
| 1046 | \ s:pa('s:_type_file_action_xsplit', 'vertical topleft'), |
|---|
| 1047 | \ 'split-down': |
|---|
| 1048 | \ s:pa('s:_type_file_action_xsplit', 'rightbelow'), |
|---|
| 1049 | \ 'split-bottom': |
|---|
| 1050 | \ s:pa('s:_type_file_action_xsplit', 'botright'), |
|---|
| 1051 | \ 'split-up': |
|---|
| 1052 | \ s:pa('s:_type_file_action_xsplit', 'leftabove'), |
|---|
| 1053 | \ 'split-top': |
|---|
| 1054 | \ s:pa('s:_type_file_action_xsplit', 'topleft'), |
|---|
| 1055 | \ 'split-right': |
|---|
| 1056 | \ s:pa('s:_type_file_action_xsplit', 'vertical rightbelow'), |
|---|
| 1057 | \ 'split-far-right': |
|---|
| 1058 | \ s:pa('s:_type_file_action_xsplit', 'vertical botright'), |
|---|
| 1059 | \ }, |
|---|
| 1060 | \ }) |
|---|
| 1061 | |
|---|
| 1062 | |
|---|
| 1063 | |
|---|
| 1064 | |
|---|
| 1065 | |
|---|
| 1066 | |
|---|
| 1067 | |
|---|
| 1068 | |
|---|
| 1069 | " Fin. "{{{1 |
|---|
| 1070 | |
|---|
| 1071 | silent doautocmd User KuLoaded |
|---|
| 1072 | |
|---|
| 1073 | |
|---|
| 1074 | |
|---|
| 1075 | |
|---|
| 1076 | |
|---|
| 1077 | |
|---|
| 1078 | |
|---|
| 1079 | |
|---|
| 1080 | " __END__ |
|---|
| 1081 | " vim: foldmethod=marker |
|---|