| 6 | | " FIXME: Translate the following FIXMEs in English. |
| 7 | | " |
| 8 | | " FIXME: ドキュメント更新。 |
| 9 | | " |
| 10 | | " FIXME: s:do(): どのitemにもマッチしない入力であってもactionに渡す。 |
| 11 | | " 例えば今の状況ではtype fileで新規ファイルを開けないので、 |
| 12 | | " このような処理は必要である。 |
| 13 | | " |
| 14 | | " ただし、これをするならばどのtypeのactionを使うかが問題。 |
| 15 | | " s:preferred_typeが定義されている場合に限定か。 |
| 16 | | " |
| 17 | | " FIXME: より「賢い」ソーティング (優先順位: 低) |
| 18 | | " - パスっぽいものは最後のコンポーネントを重視するのはどうか? |
| 19 | | " - type buffer: フルパスと相対パスのどちらを使うべきか? |
| 20 | | " - パスっぽいものは相対パス優先の方がいい? |
| 21 | | " '/'の値は若い以上、今のままでは自動的に相対パスが軽視されることになる。 |
| 22 | | " |
| 23 | | " FIXME: strtrans()の代替作成。<Left>等を正しく変換するように。 |
| 24 | | " |
| 25 | | " FIXME: type file - stub. |
| 26 | | " FIXME: type directory - NIY. |
| 27 | | " |
| 28 | | " FIXME: 各種比較でのcase sensitivityの統一・見直し。 |
| 29 | | " |
| 30 | | " FIXME: 実装方法の選択 |
| 31 | | " (A) ins-completion-menuを利用(Omni completion) |
| 32 | | " + Insert modeの基本的な操作体系を利用可能。 |
| 33 | | " - 一部キーをmappingできないため、細かい情報を取れない。 |
| 34 | | " * 特に問題な点は |
| 35 | | " 「どの項目が選択されているか」と |
| 36 | | " 「選択状況が変化したか」を |
| 37 | | " 知ることができないこと。 |
| 38 | | " * このために |
| 39 | | " 「actionを<Tab>で選んで実行」というI/Fや、 |
| 40 | | " 「利用可能なactionを適宜表示」ということができない。 |
| 41 | | " - CursorMovedIのフックを利用しての検出のため、カオス過ぎる。 |
| 42 | | " 特にs:PROMPTはひどい発明。 |
| 43 | | " |
| 44 | | " (B) getchar()で自力管理 |
| 45 | | " + キー入力を全て把握できるため、ins-completion-menuの問題はない。 |
| 46 | | " - キー入力を自力で管理する以上、 |
| 47 | | " 基本的な操作体系も含めて実装しなければならない。 |
| 48 | | " * itemsの一覧表示はins-completion-menu以外の方法が取れる。 |
| 49 | | " 例えばtype毎に並べたitemsのうち、 |
| 50 | | " preferred以外はfoldする等。 |
| 51 | | " |
| 52 | | " buffuzzy/zapitの経験から(A)を利用していたが、 |
| 53 | | " itemsの表示はバッファを使ってどうとでもできるので、 |
| 54 | | " (B)も一考の価値はある。 |
| 55 | | " |
| 56 | | " ただ、(A)でもある程度の問題は解決してしまったため、一先ずは(A)で行く。 |
| 57 | | " (B)には(B)で魅力的なメリットがあるので、落ち着いたらそちらも考える。 |
| | 6 | " FIXME: auto-complete 1 component for each typing '/' (like bluewind). |
| | 7 | " FIXME: s:do(): Force action on unmatched pattern. |
| | 8 | " FIXME: more smart sorting: |
| | 9 | " - considering last component. |
| | 10 | " - type buffer: full path vs. relative path. |
| | 11 | " FIXME: review on case sensitivity. |
| | 12 | " FIXME: alternative implementation (getchar()), if necessary. |
| | 539 | endfunction |
| | 540 | |
| | 541 | |
| | 542 | |
| | 543 | |
| | 544 | function! s:show_available_actions_message(item) "{{{2 |
| | 545 | " FIXME: like ls(1). |
| | 546 | let actions = a:item._ku_type.actions |
| | 547 | let max_key_length = max(map(copy(actions), 'len(s:string(v:val.key))')) |
| | 548 | let max_name_length = max(map(copy(actions), 'len(v:val.name)')) |
| | 549 | let padding = 3 |
| | 550 | let max_cell_length = max_key_length + 3 + max_name_length + padding |
| | 551 | let format = '%*s%*s - %-*s' |
| | 552 | |
| | 553 | let max_column = max([1, (&columns + padding - 1) / max_cell_length]) |
| | 554 | let max_column = min([max_column, 4]) |
| | 555 | let n = len(actions) |
| | 556 | let max_row = n / max_column + (n % max_column != 0) |
| | 557 | |
| | 558 | echo printf('Available actions for %s (type %s) are:', |
| | 559 | \ a:item.word, a:item._ku_type.name) |
| | 560 | for r in range(max_row) |
| | 561 | let i = r |
| | 562 | echo '' |
| | 563 | while i < n |
| | 564 | echon printf(format, |
| | 565 | \ (i == r ? 0 : padding), '', |
| | 566 | \ max_key_length, s:string(actions[i].key), |
| | 567 | \ max_name_length, actions[i].name) |
| | 568 | let i += max_row |
| | 569 | endwhile |
| | 570 | endfor |
| | 571 | echo '' |
| 610 | | if !s:has_valid_entry(v, 'key', T_STR) | return s:FALSE | endif |
| 611 | | if !s:has_valid_entry(v, 'name', T_STR) | return s:FALSE | endif |
| 612 | | if !s:has_valid_entry(v, 'function', T_FUNC) | return s:FALSE | endif |
| | 611 | if !s:has_valid_entry(v, 'key', s:TYPE_STRING) | return s:FALSE | endif |
| | 612 | if !s:has_valid_entry(v, 'name', s:TYPE_STRING) | return s:FALSE | endif |
| | 613 | if !(has_key(v, 'function') && s:callable_p(v.function)) |
| | 614 | return s:FALSE |
| | 615 | endif |
| 617 | | " It returns a list of items. Each item is a string. |
| 618 | | if !s:has_valid_entry(a:args, 'gather', T_FUNC) | return s:FALSE | endif |
| 619 | | |
| 620 | | " FIXME: other entries. |
| | 620 | " It returns a list of items. Each item is a |complete-items|. |
| | 621 | if !s:has_valid_entry(a:args,'gather',s:TYPE_FUNCTION) |return s:FALSE |endif |
| | 622 | |
| | 623 | " -- Function to initialize some information of 'gather'. |
| | 624 | " It will be called with no argument. |
| | 625 | " Its returning value will be discarded. |
| | 626 | " This entry may be missing, if so, nothing will be happened on |
| | 627 | " initialization. |
| | 628 | if has_key(a:args, 'initialize') |
| | 629 | if !(type(a:args.initialize) == s:TYPE_FUNCTION) | return s:FALSE | endif |
| | 630 | else |
| | 631 | let a:args.initialize = function('s:nop') |
| | 632 | endif |
| | 633 | |
| | 634 | " -- other entries -- |
| | 635 | |
| | 649 | function! s:callable_p(obj) "{{{2 |
| | 650 | return (type(a:obj) == s:TYPE_FUNCTION) |
| | 651 | \ || ((type(a:obj) == s:TYPE_DICTONARY) && has_key(a:obj, '__call__')) |
| | 652 | endfunction |
| | 653 | |
| | 654 | |
| | 655 | |
| | 656 | |
| | 657 | function! s:apply(obj, args) "{{{2 |
| | 658 | if type(a:obj) == s:TYPE_FUNCTION |
| | 659 | return call(a:obj, a:args) |
| | 660 | elseif type(a:obj) == s:TYPE_DICTONARY && has_key(a:obj, '__call__') |
| | 661 | return call(a:obj.__call__, a:args, a:obj) |
| | 662 | else |
| | 663 | throw 'This object is not callable:' string(a:obj) |
| | 664 | endif |
| | 665 | endfunction |
| | 666 | |
| | 667 | |
| | 668 | |
| | 669 | |
| | 670 | function! s:pa(f, ...) "{{{2 |
| | 671 | " pa = Partial Apply |
| | 672 | " Returns a callable object g, |
| | 673 | " where g(b, ...) is equivalent to f(a, ..., b, ...). |
| | 674 | let g = {} |
| | 675 | let g.f = a:f |
| | 676 | let g.args = copy(a:000) " a:000 will be lost after this execution. |
| | 677 | function! g.__call__(...) |
| | 678 | return call(self.f, self.args + a:000) |
| | 679 | endfunction |
| | 680 | return g |
| | 681 | endfunction |
| | 682 | |
| | 683 | |
| | 684 | |
| | 685 | |
| 666 | | function! s:_type_buffer_action_split_open(item) |
| 667 | | execute a:item._buffer_number 'sbuffer' |
| 668 | | endfunction |
| 669 | | function! s:_type_buffer_action_vsplit_open(item) |
| 670 | | execute 'vertical' a:item._buffer_number 'sbuffer' |
| 671 | | endfunction |
| 672 | | |
| 673 | | |
| 674 | | " FIXME: other variants: hjkl HJKL. |
| | 721 | function! s:_type_buffer_action_xsplit(modifier, item) |
| | 722 | execute a:modifier a:item._buffer_number 'sbuffer' |
| | 723 | endfunction |
| | 724 | function! s:_type_buffer_action_xdelete(command, item) |
| | 725 | execute a:item._buffer_number a:command.ku#bang() |
| | 726 | endfunction |
| | 727 | |
| | 728 | |
| 685 | | \ 'name': 'vsplit open', |
| 686 | | \ 'function': function('s:_type_buffer_action_vsplit_open')}, |
| | 743 | \ 'name': 'vsplit', |
| | 744 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 745 | \ 'vertical')}, |
| | 746 | \ {'key': 'h', |
| | 747 | \ 'name': 'split left', |
| | 748 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 749 | \ 'vertical leftabove')}, |
| | 750 | \ {'key': 'j', |
| | 751 | \ 'name': 'split down', |
| | 752 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 753 | \ 'rightbelow')}, |
| | 754 | \ {'key': 'k', |
| | 755 | \ 'name': 'split up', |
| | 756 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 757 | \ 'leftabove')}, |
| | 758 | \ {'key': 'l', |
| | 759 | \ 'name': 'split right', |
| | 760 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 761 | \ 'vertical rightbelow')}, |
| | 762 | \ {'key': "\<C-h>", |
| | 763 | \ 'name': 'split left', |
| | 764 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 765 | \ 'vertical leftabove')}, |
| | 766 | \ {'key': "\<C-j>", |
| | 767 | \ 'name': 'split down', |
| | 768 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 769 | \ 'rightbelow')}, |
| | 770 | \ {'key': "\<C-k>", |
| | 771 | \ 'name': 'split up', |
| | 772 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 773 | \ 'leftabove')}, |
| | 774 | \ {'key': "\<C-l>", |
| | 775 | \ 'name': 'split right', |
| | 776 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 777 | \ 'vertical rightbelow')}, |
| | 778 | \ {'key': 'H', |
| | 779 | \ 'name': 'split far left', |
| | 780 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 781 | \ 'vertical topleft')}, |
| | 782 | \ {'key': 'J', |
| | 783 | \ 'name': 'split bottom', |
| | 784 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 785 | \ 'botright')}, |
| | 786 | \ {'key': 'K', |
| | 787 | \ 'name': 'split top', |
| | 788 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 789 | \ 'topleft')}, |
| | 790 | \ {'key': 'L', |
| | 791 | \ 'name': 'split far right', |
| | 792 | \ 'function': s:pa(function('s:_type_buffer_action_xsplit'), |
| | 793 | \ 'vertical botright')}, |
| | 794 | \ {'key': 'U', |
| | 795 | \ 'name': 'unload', |
| | 796 | \ 'function': s:pa(function('s:_type_buffer_action_xdelete'), |
| | 797 | \ 'bunload')}, |
| | 798 | \ {'key': 'D', |
| | 799 | \ 'name': 'delete', |
| | 800 | \ 'function': s:pa(function('s:_type_buffer_action_xdelete'), |
| | 801 | \ 'bdelete')}, |
| | 802 | \ {'key': 'W', |
| | 803 | \ 'name': 'wipeout', |
| | 804 | \ 'function': s:pa(function('s:_type_buffer_action_xdelete'), |
| | 805 | \ 'bwipeout')}, |
| 695 | | " FIXME: stub. |
| 696 | | let s:_type_file_cached_items = [] |
| 697 | | let s:_type_file_last_bufnr = s:INVALID_BUFNR |
| | 813 | " FIXME: action idea: source |
| | 814 | " FIXME: action idea: ':' - set up ":<cursor> <item>" for any Ex command. |
| | 815 | " FIXME: unexpected propmt on some environments when glob() is called. |
| | 816 | " it happens when the pattern contains '{foo,bar}'. |
| | 817 | " FIXME: smart caching. |
| | 818 | " FIXME: can't list the root directory. |
| | 819 | function! s:_type_file_initialize() |
| | 820 | let s:_type_file_cache = {} |
| | 821 | let s:_type_file_frags_count = -1 |
| | 822 | endfunction |
| | 823 | |
| 699 | | if s:_type_file_last_bufnr != bufnr('$') |
| 700 | | let s:_type_file_cached_items = [] |
| 701 | | for i in range(1, bufnr('$')) |
| 702 | | if bufexists(i) && buflisted(i) && getbufvar(i, '&buftype') == '' |
| 703 | | call add(s:_type_file_cached_items, |
| 704 | | \ { |
| 705 | | \ 'word': bufname(i), |
| 706 | | \ 'menu': 'file', |
| 707 | | \ 'dup': s:TRUE, |
| 708 | | \ }) |
| 709 | | endif |
| 710 | | endfor |
| 711 | | let s:_type_file_last_bufnr = bufnr('$') |
| 712 | | endif |
| 713 | | return s:_type_file_cached_items |
| 714 | | endfunction |
| 715 | | |
| 716 | | |
| | 825 | let c = len(split(a:pattern, '/', s:TRUE)) - 1 |
| | 826 | if s:_type_file_frags_count != c |
| | 827 | let s:_type_file_frags_count = c |
| | 828 | let s:_type_file_cache[c] |
| | 829 | \ = map(split(glob(s:make_glob_pattern(a:pattern)), '\n'), |
| | 830 | \ "{'word': v:val, 'menu': 'file', 'dup': s:TRUE}") |
| | 831 | endif |
| | 832 | return s:_type_file_cache[c] |
| | 833 | endfunction |
| | 834 | |
| | 835 | function! s:make_glob_pattern(s) |
| | 836 | " FIXME: path separetor assumption. |
| | 837 | let frags = split(substitute(a:s, '\s\+', '*', 'g'), '/', 1) |
| | 838 | call map(frags, '"{,.??,.[^.]}*" . v:val . "*"') |
| | 839 | call map(frags, 'substitute(v:val, "\\*\\+", "*", "g")') |
| | 840 | return join(frags, '/') |
| | 841 | endfunction |
| | 842 | |
| | 843 | |
| | 844 | " FIXME: filename with special characters -- should escape? |
| 738 | | \ 'name': 'vsplit open', |
| 739 | | \ 'function': function('s:_type_file_action_vsplit_open')}, |
| | 868 | \ 'name': 'vsplit', |
| | 869 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 870 | \ 'vertical')}, |
| | 871 | \ {'key': 'h', |
| | 872 | \ 'name': 'split left', |
| | 873 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 874 | \ 'vertical leftabove')}, |
| | 875 | \ {'key': 'j', |
| | 876 | \ 'name': 'split down', |
| | 877 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 878 | \ 'rightbelow')}, |
| | 879 | \ {'key': 'k', |
| | 880 | \ 'name': 'split up', |
| | 881 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 882 | \ 'leftabove')}, |
| | 883 | \ {'key': 'l', |
| | 884 | \ 'name': 'split right', |
| | 885 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 886 | \ 'vertical rightbelow')}, |
| | 887 | \ {'key': "\<C-h>", |
| | 888 | \ 'name': 'split left', |
| | 889 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 890 | \ 'vertical leftabove')}, |
| | 891 | \ {'key': "\<C-j>", |
| | 892 | \ 'name': 'split down', |
| | 893 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 894 | \ 'rightbelow')}, |
| | 895 | \ {'key': "\<C-k>", |
| | 896 | \ 'name': 'split up', |
| | 897 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 898 | \ 'leftabove')}, |
| | 899 | \ {'key': "\<C-l>", |
| | 900 | \ 'name': 'split right', |
| | 901 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 902 | \ 'vertical rightbelow')}, |
| | 903 | \ {'key': 'H', |
| | 904 | \ 'name': 'split far left', |
| | 905 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 906 | \ 'vertical topleft')}, |
| | 907 | \ {'key': 'J', |
| | 908 | \ 'name': 'split bottom', |
| | 909 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 910 | \ 'botright')}, |
| | 911 | \ {'key': 'K', |
| | 912 | \ 'name': 'split top', |
| | 913 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 914 | \ 'topleft')}, |
| | 915 | \ {'key': 'L', |
| | 916 | \ 'name': 'split far right', |
| | 917 | \ 'function': s:pa(function('s:_type_file_action_xsplit'), |
| | 918 | \ 'vertical botright')}, |