root/lang/vim/ku/trunk/autoload/ku.vim @ 6579

Revision 6579, 27.5 kB (checked in by kana, 7 years ago)

lang/vim/ku:
* Sync with my local repository (from 2008-02-11T14:51:28+0900 to

2008-02-12T01:43:48+09:00).

  • Property svn:keywords set to Id
Line 
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
16let s:FALSE = 0
17let s:TRUE = !s:FALSE
18
19let s:TYPE_NUMBER = type(0)
20let s:TYPE_STRING = type('')
21let s:TYPE_FUNCTION = type(function('function'))
22let s:TYPE_LIST = type([])
23let s:TYPE_DICTONARY = type({})
24
25" Key sequence to start (omni) completion
26" without auto-selecting the first match.
27let 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.
32let s:PROMPT = '>'
33
34
35" Flag to avoid infinite loop by auto-directory-insertion.
36let 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 '!'.
40let s:bang = ''
41
42" The buffer number of the ku buffer.
43let s:INVALID_BUFNR = -3339
44  " to be reloadable (for debugging)
45if exists('s:bufnr') && s:bufnr != s:INVALID_BUFNR && bufexists(s:bufnr)
46  execute s:bufnr 'bwipeout'
47endif
48let s:bufnr = s:INVALID_BUFNR
49
50" Fallback actions for all types.
51if !exists('s:fallback_actions')
52  let s:fallback_actions = {}
53endif
54
55" The last column of the cursor.
56let s:INVALID_COL = -3338
57let s:last_col = s:INVALID_COL
58
59" Items gathered by the last completion.
60let s:last_items = []
61
62" Preferred type of items.
63let s:INVALID_TYPE = '*all*'
64let s:preferred_type = s:INVALID_TYPE
65
66" All available types.
67if !exists('s:types')
68  let s:types = {}
69endif
70
71" Misc. values to restore the original state.
72let s:completeopt = ''
73let s:ignorecase = ''
74let s:winrestcmd = ''
75
76
77
78
79" Global  "{{{2
80" Flag for debugging.
81if !exists('g:ku_debug_p')
82  let g:ku_debug_p = s:FALSE
83endif
84
85" Patterns for junk items.  These items are listed at the last.
86" FIXME: How about g:ku_important_item_pattern?
87if !exists('g:ku_junk_item_pattern')
88  let g:ku_junk_item_pattern = '\(\~\|\.o\|\.swp\)$'
89endif
90
91" Flag to use the type of items for sorting without s:preferred_type.
92if !exists('g:ku_sort_by_type_first_p')
93  let g:ku_sort_by_type_first_p = s:TRUE
94endif
95
96
97
98
99
100
101
102
103" Interfaces  "{{{1
104function! 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')
140endfunction
141
142
143
144
145function! 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)')
151endfunction
152
153
154
155
156function! 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
168endfunction
169
170
171
172
173function! 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
178endfunction
179
180
181
182
183function! 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
192endfunction
193
194
195
196
197function! 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
210endfunction
211
212
213
214
215function! ku#bang()  "{{{2
216  return s:bang
217endfunction
218
219
220
221
222
223
224
225
226" Core  "{{{1
227function! 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
243endfunction
244let s:end_locked_p = s:FALSE
245
246
247
248
249function! 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
284endfunction
285
286
287function! s:compare_items(a, b)
288  return s:compare_lists(a:a._ku_sort_priority, a:b._ku_sort_priority)
289endfunction
290
291function! 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
301endfunction
302
303
304
305
306function! 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
321endfunction
322
323
324
325
326function! 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    \ ]
334endfunction
335
336
337
338
339function! 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()
343endfunction
344
345
346
347
348function! 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
390endfunction
391
392
393
394
395
396
397
398
399" Misc.  "{{{1
400function! s:SID_PREFIX()  "{{{2
401  return matchstr(expand('<sfile>'), '<SNR>\d\+_')
402endfunction
403
404
405
406
407function! s:nop(...)  "{{{2
408  return
409endfunction
410
411
412
413
414function! 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.
418endfunction
419
420
421
422
423function! 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
488endfunction
489
490
491
492
493function! s:ku_buffer_exists_p()  "{{{2
494  return (s:bufnr != s:INVALID_BUFNR) && bufexists(s:bufnr)
495endfunction
496
497
498
499
500function! 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
504endfunction
505
506
507function! s:remap(lhs, rhs)
508  return s:map(s:TRUE, a:lhs, a:rhs)
509endfunction
510
511
512function! s:noremap(lhs, rhs)
513  return s:map(s:FALSE, a:lhs, a:rhs)
514endfunction
515
516
517
518
519function! 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
523endfunction
524
525
526
527
528function! s:make_asis_regexp(s)  "{{{2
529  " FIXME: case sensitivity
530  return '\c\V' . substitute(substitute(a:s, '\s\+', ' ', 'g'), '\', '\\', 'g')
531endfunction
532
533
534
535
536function! 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')
541endfunction
542
543
544
545
546function! s:complete_the_prompt()  "{{{2
547  call setline('.', s:PROMPT . getline('.'))
548endfunction
549
550
551
552
553function! s:contains_the_prompt_p(s)  "{{{2
554  return len(s:PROMPT) <= len(a:s) && a:s[:len(s:PROMPT) - 1] ==# s:PROMPT
555endfunction
556
557
558
559
560function! 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 ''
591endfunction
592
593
594
595
596function! 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
629endfunction
630
631
632
633
634function! 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')
650endfunction
651
652
653
654
655function! 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 ''
687endfunction
688
689
690
691
692function! s:valid_type_name_p(name)  "{{{2
693  return has_key(s:types, a:name)
694endfunction
695
696
697
698
699function! 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
754endfunction
755
756
757
758
759function! s:type_has_action_p(type, action_name)  "{{{2
760  return s:callable_p(s:type_action(a:type, a:action_name))
761endfunction
762
763
764
765
766function! 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)
772endfunction
773
774
775
776
777function! s:has_valid_entry(dict, key, type)  "{{{2
778  return has_key(a:dict, a:key) && type(a:dict[a:key]) == a:type
779endfunction
780
781
782
783
784function! 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__'))
787endfunction
788
789
790
791
792function! 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
800endfunction
801
802
803
804
805function! 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
816endfunction
817
818
819
820
821
822
823
824
825" Built-in Types  "{{{1
826" common definitions  "{{{2
827function! 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')
833endfunction
834call ku#custom_action('*fallback*', 'ex', function('s:_type_any_action_ex'))
835
836
837function! 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
847endfunction
848call ku#custom_action('*fallback*', 'cd', s:pa('s:_type_any_action_xcd', 'cd'))
849call 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?
857let s:_type_buffer_cached_items = []
858let s:_type_buffer_last_bufnr = s:INVALID_BUFNR
859function! 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
879endfunction
880
881
882function! s:_type_buffer_action_open(item)
883  execute a:item._buffer_number 'buffer'.ku#bang()
884endfunction
885function! s:_type_buffer_action_xsplit(modifier, item)
886  execute a:modifier a:item._buffer_number 'sbuffer'
887endfunction
888function! s:_type_buffer_action_xdelete(command, item)
889  execute a:item._buffer_number a:command.ku#bang()
890endfunction
891
892
893call 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?
967let s:_type_file_cache = {}
968
969function! 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]
994endfunction
995
996
997" FIXME: filename with special characters -- should escape?
998function! s:_type_file_action_open(item)
999  execute 'edit'.ku#bang() a:item.word
1000endfunction
1001function! s:_type_file_action_xsplit(modifier, item)
1002  execute a:modifier 'split' a:item.word
1003endfunction
1004
1005
1006call 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
1071silent doautocmd User KuLoaded
1072
1073
1074
1075
1076
1077
1078
1079
1080" __END__
1081" vim: foldmethod=marker
Note: See TracBrowser for help on using the browser.