Mercurial > dotfiles
diff .vim/plugin/fuzzyfinder.vim @ 0:c30d68fbd368
Initial import from svn.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Wed, 26 Nov 2008 10:56:09 -0600 |
parents | |
children |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/.vim/plugin/fuzzyfinder.vim @@ -0,0 +1,1676 @@ +"============================================================================= +" fuzzyfinder.vim : Fuzzy/Partial pattern explorer for +" buffer/file/MRU/command/favorite/tag/etc. +"============================================================================= +" +" Author: Takeshi NISHIDA <ns9tks@DELETE-ME.gmail.com> +" Version: 2.13, for Vim 7.1 +" Licence: MIT Licence +" URL: http://www.vim.org/scripts/script.php?script_id=1984 +" +" GetLatestVimScripts: 1984 1 :AutoInstall: fuzzyfinder.vim +" +"============================================================================= +" DOCUMENT: {{{1 +" Japanese: http://vim.g.hatena.ne.jp/keyword/fuzzyfinder.vim +" +"----------------------------------------------------------------------------- +" Description: +" Fuzzyfinder provides convenient ways to quickly reach the buffer/file you +" want. Fuzzyfinder finds matching files/buffers with a fuzzy/partial +" pattern to which it converted the entered pattern. +" +" E.g.: entered pattern -> fuzzy pattern / partial pattern +" abc -> *a*b*c* / *abc* +" a?c -> *a?c* / *a?c* +" dir/file -> dir/*f*i*l*e* / dir/*file* +" d*r/file -> d*r/*f*i*l*e* / d*r/*file* +" ../**/s -> ../**/*s* / ../**/*s* +" +" (** allows searching a directory tree.) +" +" You will be happy when: +" "./OhLongLongLongLongLongFile.txt" +" "./AhLongLongLongLongLongName.txt" +" "./AhLongLongLongLongLongFile.txt" <- you want :O +" Type "AF" and "AhLongLongLongLongLongFile.txt" will be select. :D +" +" Fuzzyfinder has some modes: +" - Buffer mode +" - File mode +" - Directory mode (yet another :cd command) +" - MRU-file mode (most recently used files) +" - MRU-command mode (most recently used command-lines) +" - Favorite-file mode +" - Tag mode (yet another :tag command) +" - Tagged-file mode (files which are included in current tags) +" +" Fuzzyfinder supports the multibyte. +" +"----------------------------------------------------------------------------- +" Installation: +" Drop this file in your plugin directory. +" +"----------------------------------------------------------------------------- +" Usage: +" Starting Fuzzyfinder: +" You can start Fuzzyfinder by the following commands: +" +" :FuzzyFinderBuffer - launchs buffer-mode Fuzzyfinder. +" :FuzzyFinderFile - launchs file-mode Fuzzyfinder. +" :FuzzyFinderDir - launchs directory-mode Fuzzyfinder. +" :FuzzyFinderMruFile - launchs MRU-file-mode Fuzzyfinder. +" :FuzzyFinderMruCmd - launchs MRU-command-mode Fuzzyfinder. +" :FuzzyFinderFavFile - launchs favorite-file-mode Fuzzyfinder. +" :FuzzyFinderTag - launchs tag-mode Fuzzyfinder. +" :FuzzyFinderTaggedFile - launchs tagged-file-mode Fuzzyfinder. +" +" It is recommended to map these commands. These commands can take initial +" text as a command argument. The text will be entered after Fuzzyfinder +" launched. If a command was executed with a ! modifier (e.g. +" :FuzzyFinderTag!), it enables the partial matching instead of the fuzzy +" matching. +" +" +" In Fuzzyfinder: +" The entered pattern is converted to the fuzzy pattern and buffers/files +" which match the pattern is shown in a completion menu. +" +" A completion menu is shown when you type at the end of the line and the +" length of entered pattern is more than setting value. By default, it is +" shown at the beginning. +" +" If too many items (200, by default) were matched, the completion is +" aborted to reduce nonresponse. +" +" If an item were matched with entered pattern exactly, it is shown first. +" The item whose file name has longer prefix matching is placed upper. +" Also, an item which matched more sequentially is placed upper. The item +" whose index were matched with a number suffixed with entered pattern is +" placed lower. the first item in the completion menu will be selected +" automatically. +" +" You can open a selected item in various ways: +" <CR> - opens in a previous window. +" <C-j> - opens in a split window. +" <C-k> - opens in a vertical-split window. +" <C-]> - opens in a new tab page. +" In MRU-command mode, <CR> executes a selected command and others just +" put it into a command-line. These key mappings are customizable. +" +" To cancel and return to previous window, leave Insert mode. +" +" To Switch the mode without leaving Insert mode, use <C-l> or <C-o>. +" This key mapping is customizable. +" +" If you want to temporarily change whether or not to ignore case, use +" <C-t>. This key mapping is customizable. +" +" To Hide The Completion Temporarily Menu In Fuzzyfinder: +" You can close it by <C-e> and reopen it by <C-x><C-u>. +" +" About Highlighting: +" Fuzzyfinder highlights the buffer with "Error" group when the completion +" item was not found or the completion process was aborted. +" +" About Alternative Approach For Tag Jump: +" Following mappings are replacements for :tag and <C-]>: +" +" nnoremap <silent> <C-f><C-t> :FuzzyFinderTag!<CR> +" nnoremap <silent> <C-]> :FuzzyFinderTag! <C-r>=expand('<cword>')<CR><CR> +" +" In the tag mode, it is recommended to use partial matching instead of +" fuzzy matching. +" +" About Tagged File Mode: +" The files which are included in the current tags are the ones which are +" related to the current working environment. So this mode is a pseudo +" project mode. +" +" About Usage Of Command Argument: +" As an example, if you want to launch file-mode Fuzzyfinder with the full +" path of current directory, map like below: +" +" nnoremap <C-p> :FuzzyFinderFile <C-r>=fnamemodify(getcwd(), ':p')<CR><CR> +" +" Instead, if you want the directory of current buffer and not current +" directory: +" +" nnoremap <C-p> :FuzzyFinderFile <C-r>=expand('%:~:.')[:-1-len(expand('%:~:.:t'))]<CR><CR> +" +" About Abbreviations And Multiple Search: +" You can use abbreviations and multiple search in each mode. For example, +" set as below: +" +" let g:FuzzyFinderOptions.Base.abbrev_map = { +" \ "^WORK" : [ +" \ "~/project/**/src/", +" \ ".vim/plugin/", +" \ ], +" \ } +" +" And type "WORKtxt" in file-mode Fuzzyfinder, then it searches by +" following patterns: +" +" "~/project/**/src/*t*x*t*" +" ".vim/plugin/*t*x*t*" +" +" Adding Favorite Files: +" You can add a favorite file by the following commands: +" +" :FuzzyFinderAddFavFile {filename} +" +" If you do not specify the filename, current file name is used. +" +" About Information File: +" Fuzzyfinder writes information of the MRU, favorite, etc to the file by +" default (~/.vimfuzzyfinder). + +" :FuzzyFinderEditInfo command is helpful in editing your information +" file. This command reads the information file in new unnamed buffer. +" Write the buffer and the information file will be updated. +" +" About Cache: +" Once a cache was created, It is not updated automatically to improve +" response by default. To update it, use :FuzzyFinderRemoveCache command. +" +" About Migemo: +" Migemo is a search method for Japanese language. +" +"----------------------------------------------------------------------------- +" Options: +" You can set options via g:FuzzyFinderOptions which is a dictionary. See +" the folded section named "GLOBAL OPTIONS:" for details. To easily set +" options for customization, put necessary entries from GLOBAL OPTIONS into +" your vimrc file and edit those values. +" +"----------------------------------------------------------------------------- +" Setting Example: +" let g:FuzzyFinderOptions = { 'Base':{}, 'Buffer':{}, 'File':{}, 'Dir':{}, 'MruFile':{}, 'MruCmd':{}, 'FavFile':{}, 'Tag':{}, 'TaggedFile':{}} +" let g:FuzzyFinderOptions.Base.ignore_case = 1 +" let g:FuzzyFinderOptions.Base.abbrev_map = { +" \ '\C^VR' : [ +" \ '$VIMRUNTIME/**', +" \ '~/.vim/**', +" \ '$VIM/.vim/**', +" \ '$VIM/vimfiles/**', +" \ ], +" \ } +" let g:FuzzyFinderOptions.MruFile.max_item = 200 +" let g:FuzzyFinderOptions.MruCmd.max_item = 200 +" nnoremap <silent> <C-n> :FuzzyFinderBuffer<CR> +" nnoremap <silent> <C-m> :FuzzyFinderFile <C-r>=expand('%:~:.')[:-1-len(expand('%:~:.:t'))]<CR><CR> +" nnoremap <silent> <C-j> :FuzzyFinderMruFile<CR> +" nnoremap <silent> <C-k> :FuzzyFinderMruCmd<CR> +" nnoremap <silent> <C-p> :FuzzyFinderDir <C-r>=expand('%:p:~')[:-1-len(expand('%:p:~:t'))]<CR><CR> +" nnoremap <silent> <C-f><C-d> :FuzzyFinderDir<CR> +" nnoremap <silent> <C-f><C-f> :FuzzyFinderFavFile<CR> +" nnoremap <silent> <C-f><C-t> :FuzzyFinderTag!<CR> +" nnoremap <silent> <C-f><C-g> :FuzzyFinderTaggedFile<CR> +" noremap <silent> g] :FuzzyFinderTag! <C-r>=expand('<cword>')<CR><CR> +" nnoremap <silent> <C-f>F :FuzzyFinderAddFavFile<CR> +" nnoremap <silent> <C-f><C-e> :FuzzyFinderEditInfo<CR> +" +"----------------------------------------------------------------------------- +" Special Thanks: +" Vincent Wang +" Ingo Karkat +" Nikolay Golubev +" Brian Doyle +" id:secondlife +" Matt Tolton +" +"----------------------------------------------------------------------------- +" ChangeLog: +" 2.13: +" - Fixed a bug that a directory disappeared when a file in that directroy +" was being opened in File/Mru-File mode. +" +" 2.12: +" - Changed to be able to show completion items in the order of recently +" used in Buffer mode. +" - Added g:FuzzyFinderOptions.Buffer.mru_order option. +" +" 2.11: +" - Changed that a dot sequence of entered pattern is expanded to parent +" directroies in File/Dir mode. +" E.g.: "foo/...bar" -> "foo/../../bar" +" - Fixed a bug that a prompt string was excessively inserted. +" +" 2.10: +" - Changed not to show a current buffer in a completion menu. +" - Fixed a bug that a filename to open was not been escaped. +" - Added 'prompt' option. +" - Added 'prompt_highlight' option. +" - Removed g:FuzzyFinderOptions.MruFile.no_special_buffer option. +" +" 2.9: +" - Enhanced <BS> behavior in Fuzzyfinder and added 'smart_bs' option. +" - Fixed a bug that entered pattern was not been escaped. +" - Fixed not to insert "zv" with "c/pattern<CR>" command in Normal mode. +" - Avoid the slow down problem caused by filereadable() check for the MRU +" information in BufEnter/BufWritePost. +" +" 2.8.1: +" - Fixed a bug caused by the non-escaped buffer name "[Fuzzyfinder]". +" - Fixed a command to open in a new tab page in Buffer mode. +" 2.8: +" - Added 'trim_length' option. +" - Added 'switch_order' option. +" - Fixed a bug that entered command did not become the newest in the +" history. +" - Fixed a bug that folds could not open with <CR> in a command-line when +" searching. +" - Removed 'excluded_indicator' option. Now a completion list in Buffer +" mode is the same as a result of :buffers. +" +" 2.7: +" - Changed to find an item whose index is matched with the number +" suffixed with entered pattern. +" - Fixed the cache bug after changing current directroy in File mode. +" +" 2.6.2: +" - Fixed not to miss changes in options when updates the MRU information. +" +" 2.6.1: +" - Fixed a bug related to floating-point support. +" - Added support for GetLatestVimScripts. +" +" 2.6: +" - Revived MRU-command mode. The problem with a command-line abbreviation +" was solved. +" - Changed the specification of the information file. +" - Added :FuzzyFinderEditInfo command. + +" 2.5.1: +" - Fixed to be able to match "foo/./bar" by "foo/**/bar" in File mode. +" - Fixed to be able to open a space-containing file in File mode. +" - Fixed to honor the current working directory properly in File mode. +" +" 2.5: +" - Fixed the bug that a wrong initial text is entered after switching to a +" next mode. +" - Fixed the bug that it does not return to previous window after leaving +" Fuzzyfinder one. +" +" 2.4: +" - Fixed the bug that Fuzzyfinder fails to open a file caused by auto-cd +" plugin/script. +" +" 2.3: +" - Added a key mapping to open items in a new tab page and +" g:FuzzyFinderOptions.Base.key_open_tab opton. +" - Changed to show Fuzzyfinder window above last window even if +" 'splitbelow' was set. +" - Changed to set nocursorline and nocursorcolumn in Fuzzyfinder. +" - Fixed not to push up a buffer number unlimitedly. +" +" 2.2: +" - Added new feature, which is the partial matching. +" - Fixed the bug that an error occurs when "'" was entered. +" +" 2.1: +" - Restructured the option system AGAIN. Sorry :p +" - Changed to inherit a typed text when switching a mode without leaving +" Insert mode. +" - Changed commands which launch explorers to be able to take a argument +" for initial text. +" - Changed to complete file names by relative path and not full path in +" the buffer/mru-file/tagged-file mode. +" - Changed to highlight a typed text when the completion item was not +" found or the completion process was aborted. +" - Changed to create caches for each tag file and not working directory +" in the tag/tagged-file mode. +" - Fixed the bug that the buffer mode couldn't open a unnamed buffer. +" - Added 'matching_limit' option. +" - Removed 'max_match' option. Use 'matching_limit' option instead. +" - Removed 'initial_text' option. Use command argument instead. +" - Removed the MRU-command mode. +" +" 2.0: +" - Added the tag mode. +" - Added the tagged-file mode. +" - Added :FuzzyFinderRemoveCache command. +" - Restructured the option system. many options are changed names or +" default values of some options. +" - Changed to hold and reuse caches of completion lists by default. +" - Changed to set filetype 'fuzzyfinder'. +" - Disabled the MRU-command mode by default because there are problems. +" - Removed FuzzyFinderAddMode command. +" +" 1.5: +" - Added the directory mode. +" - Fixed the bug that it caused an error when switch a mode in Insert +" mode. +" - Changed g:FuzzyFinder_KeySwitchMode type to a list. +" +" 1.4: +" - Changed the specification of the information file. +" - Added the MRU-commands mode. +" - Renamed :FuzzyFinderAddFavorite command to :FuzzyFinderAddFavFile. +" - Renamed g:FuzzyFinder_MruModeVars option to +" g:FuzzyFinder_MruFileModeVars. +" - Renamed g:FuzzyFinder_FavoriteModeVars option to +" g:FuzzyFinder_FavFileModeVars. +" - Changed to show registered time of each item in MRU/favorite mode. +" - Added 'timeFormat' option for MRU/favorite modes. +" +" 1.3: +" - Fixed a handling of multi-byte characters. +" +" 1.2: +" - Added support for Migemo. (Migemo is Japanese search method.) +" +" 1.1: +" - Added the favorite mode. +" - Added new features, which are abbreviations and multiple search. +" - Added 'abbrevMap' option for each mode. +" - Added g:FuzzyFinder_MruModeVars['ignoreSpecialBuffers'] option. +" - Fixed the bug that it did not work correctly when a user have mapped +" <C-p> or <Down>. +" +" 1.0: +" - Added the MRU mode. +" - Added commands to add and use original mode. +" - Improved the sorting algorithm for completion items. +" - Added 'initialInput' option to automatically insert a text at the +" beginning of a mode. +" - Changed that 'excludedPath' option works for the entire path. +" - Renamed some options. +" - Changed default values of some options. +" - Packed the mode-specific options to dictionaries. +" - Removed some options. +" +" 0.6: +" - Fixed some bugs. + +" 0.5: +" - Improved response by aborting processing too many items. +" - Changed to be able to open a buffer/file not only in previous window +" but also in new window. +" - Fixed a bug that recursive searching with '**' does not work. +" - Added g:FuzzyFinder_CompletionItemLimit option. +" - Added g:FuzzyFinder_KeyOpen option. +" +" 0.4: +" - Improved response of the input. +" - Improved the sorting algorithm for completion items. It is based on +" the matching level. 1st is perfect matching, 2nd is prefix matching, +" and 3rd is fuzzy matching. +" - Added g:FuzzyFinder_ExcludePattern option. +" - Removed g:FuzzyFinder_WildIgnore option. +" - Removed g:FuzzyFinder_EchoPattern option. +" - Removed g:FuzzyFinder_PathSeparator option. +" - Changed the default value of g:FuzzyFinder_MinLengthFile from 1 to 0. +" +" 0.3: +" - Added g:FuzzyFinder_IgnoreCase option. +" - Added g:FuzzyFinder_KeyToggleIgnoreCase option. +" - Added g:FuzzyFinder_EchoPattern option. +" - Changed the open command in a buffer mode from ":edit" to ":buffer" to +" avoid being reset cursor position. +" - Changed the default value of g:FuzzyFinder_KeyToggleMode from +" <C-Space> to <F12> because <C-Space> does not work on some CUI +" environments. +" - Changed to avoid being loaded by Vim before 7.0. +" - Fixed a bug with making a fuzzy pattern which has '\'. +" +" 0.2: +" - A bug it does not work on Linux is fixed. +" +" 0.1: +" - First release. +" +" }}}1 +"============================================================================= +" INCLUDE GUARD: {{{1 +if exists('loaded_fuzzyfinder') || v:version < 701 + finish +endif +let loaded_fuzzyfinder = 1 + +" }}}1 +"============================================================================= +" FUNCTION: {{{1 +"----------------------------------------------------------------------------- +" LIST FUNCTIONS: + +function! s:Unique(in) + let sorted = sort(a:in) + if len(sorted) < 2 + return sorted + endif + let last = remove(sorted, 0) + let result = [last] + for item in sorted + if item != last + call add(result, item) + let last = item + endif + endfor + return result +endfunction + +" [ [0], [1,2], [3] ] -> [ 0, 1, 2, 3 ] +function! s:Concat(in) + let result = [] + for l in a:in + let result += l + endfor + return result +endfunction + +" [ [ 0, 1 ], [ 2, 3, 4 ], ] -> [ [0,2], [0,3], [0,4], [1,2], [1,3], [1,4] ] +function! s:CartesianProduct(lists) + if empty(a:lists) + return [] + endif + "let result = map((a:lists[0]), '[v:val]') + let result = [ [] ] + for l in a:lists + let temp = [] + for r in result + let temp += map(copy(l), 'add(copy(r), v:val)') + endfor + let result = temp + endfor + return result +endfunction + +" copy + filter + limit +function! s:FilterEx(in, expr, limit) + if a:limit <= 0 + return filter(copy(a:in), a:expr) + endif + let result = [] + let stride = a:limit * 3 / 2 " x1.5 + for i in range(0, len(a:in) - 1, stride) + let result += filter(a:in[i : i + stride - 1], a:expr) + if len(result) >= a:limit + return remove(result, 0, a:limit - 1) + endif + endfor + return result +endfunction + +" +function! s:FilterMatching(entries, key, pattern, index, limit) + return s:FilterEx(a:entries, 'v:val[''' . a:key . '''] =~ ' . string(a:pattern) . ' || v:val.index == ' . a:index, a:limit) +endfunction + +function! s:ExtendIndexToEach(in, offset) + for i in range(len(a:in)) + let a:in[i].index = i + a:offset + endfor + return a:in +endfunction + +function! s:UpdateMruList(mrulist, new_item, key, max_item, excluded) + let result = copy(a:mrulist) + let result = filter(result,'v:val[a:key] != a:new_item[a:key]') + let result = insert(result, a:new_item) + let result = filter(result, 'v:val[a:key] !~ a:excluded') + return result[0 : a:max_item - 1] +endfunction + +"----------------------------------------------------------------------------- +" STRING FUNCTIONS: + +" trims a:str and add a:mark if a length of a:str is more than a:len +function! s:TrimLast(str, len) + if a:len <= 0 || len(a:str) <= a:len + return a:str + endif + return a:str[:(a:len - len(s:ABBR_TRIM_MARK) - 1)] . s:ABBR_TRIM_MARK +endfunction + +" takes suffix numer. if no digits, returns -1 +function! s:SuffixNumber(str) + let s = matchstr(a:str, '\d\+$') + return (len(s) ? str2nr(s) : -1) +endfunction + +function! s:ConvertWildcardToRegexp(expr) + let re = escape(a:expr, '\') + for [pat, sub] in [ [ '*', '\\.\\*' ], [ '?', '\\.' ], [ '[', '\\[' ], ] + let re = substitute(re, pat, sub, 'g') + endfor + return '\V' . re +endfunction + +" "foo/bar/hoge" -> { head: "foo/bar/", tail: "hoge" } +function! s:SplitPath(path) + let dir = matchstr(a:path, '^.*[/\\]') + return { + \ 'head' : dir, + \ 'tail' : a:path[strlen(dir):] + \ } +endfunction + +function! s:EscapeFilename(fn) + return escape(a:fn, " \t\n*?[{`$%#'\"|!<") +endfunction + +" "foo/.../bar/...hoge" -> "foo/.../bar/../../hoge" +function! s:ExpandTailDotSequenceToParentDir(base) + return substitute(a:base, '^\(.*[/\\]\)\?\zs\.\(\.\+\)\ze[^/\\]*$', + \ '\=repeat(".." . s:PATH_SEPARATOR, len(submatch(2)))', '') +endfunction + +"----------------------------------------------------------------------------- +" FUNCTIONS FOR COMPLETION ITEM: + +function! s:FormatCompletionItem(expr, number, abbr, trim_len, time, base_pattern, evals_path_tail) + if a:evals_path_tail + let rate = s:EvaluateMatchingRate(s:SplitPath(matchstr(a:expr, '^.*[^/\\]')).tail, + \ s:SplitPath(a:base_pattern).tail) + else + let rate = s:EvaluateMatchingRate(a:expr, a:base_pattern) + endif + return { + \ 'word' : a:expr, + \ 'abbr' : s:TrimLast((a:number >= 0 ? printf('%2d: ', a:number) : '') . a:abbr, a:trim_len), + \ 'menu' : printf('%s[%s]', (len(a:time) ? a:time . ' ' : ''), s:MakeRateStar(rate, 5)), + \ 'ranks' : [-rate, (a:number >= 0 ? a:number : a:expr)] + \ } +endfunction + +function! s:EvaluateMatchingRate(expr, pattern) + if a:expr == a:pattern + return s:MATCHING_RATE_BASE + endif + let rate = 0 + let rate_increment = (s:MATCHING_RATE_BASE * 9) / (len(a:pattern) * 10) " zero divide ok + let matched = 1 + let i_pattern = 0 + for i_expr in range(len(a:expr)) + if a:expr[i_expr] == a:pattern[i_pattern] + let rate += rate_increment + let matched = 1 + let i_pattern += 1 + if i_pattern >= len(a:pattern) + break + endif + elseif matched + let rate_increment = rate_increment / 2 + let matched = 0 + endif + endfor + return rate +endfunction + +function! s:MakeRateStar(rate, base) + let len = (a:base * a:rate) / s:MATCHING_RATE_BASE + return repeat('*', len) . repeat('.', a:base - len) +endfunction + +"----------------------------------------------------------------------------- +" MISC FUNCTIONS: + +function! s:IsAvailableMode(mode) + return exists('a:mode.mode_available') && a:mode.mode_available +endfunction + +function! s:GetAvailableModes() + return filter(values(g:FuzzyFinderMode), 's:IsAvailableMode(v:val)') +endfunction + +function! s:GetSortedAvailableModes() + let modes = filter(items(g:FuzzyFinderMode), 's:IsAvailableMode(v:val[1])') + let modes = map(modes, 'extend(v:val[1], { "ranks" : [v:val[1].switch_order, v:val[0]] })') + return sort(modes, 's:CompareRanks') +endfunction + +function! s:GetSidPrefix() + return matchstr(expand('<sfile>'), '<SNR>\d\+_') +endfunction + +function! s:OnCmdCR() + for m in s:GetAvailableModes() + call m.extend_options() + call m.on_command_pre(getcmdtype() . getcmdline()) + endfor + " lets last entry become the newest in the history + if getcmdtype() =~ '[:/=@]' + call histadd(getcmdtype(), getcmdline()) + endif + + " this is not mapped again (:help recursive_mapping) + return "\<CR>" +endfunction + +function! s:ExpandAbbrevMap(base, abbrev_map) + let result = [a:base] + + " expand + for [pattern, sub_list] in items(a:abbrev_map) + let exprs = result + let result = [] + for expr in exprs + let result += map(copy(sub_list), 'substitute(expr, pattern, v:val, "g")') + endfor + endfor + + return s:Unique(result) +endfunction + +" "**" is expanded to ["**", "."]. E.g.: "foo/**/bar" -> [ "foo/./bar", "foo/**/bar" ] +function! s:ExpandEx(dir) + if a:dir !~ '\S' + return [''] + endif + + " [ ["foo/"], ["**/", "./" ], ["bar/"] ] + let lists = [] + for i in split(a:dir, '[/\\]\zs') + let m = matchlist(i, '^\*\{2,}\([/\\]*\)$') + call add(lists, (empty(m) ? [i] : [i, '.' . m[1]])) + endfor + + " expand wlidcards + return split(join(map(s:CartesianProduct(lists), 'expand(join(v:val, ""))'), "\n"), "\n") +endfunction + +function! s:EnumExpandedDirsEntries(dir, excluded) + let dirs = s:ExpandEx(a:dir) + let entries = s:Concat(map(copy(dirs), 'split(glob(v:val . ".*"), "\n") + ' . + \ 'split(glob(v:val . "*" ), "\n")')) + if len(dirs) <= 1 + call map(entries, 'extend(s:SplitPath(v:val), { "suffix" : (isdirectory(v:val) ? s:PATH_SEPARATOR : ""), "head" : a:dir })') + else + call map(entries, 'extend(s:SplitPath(v:val), { "suffix" : (isdirectory(v:val) ? s:PATH_SEPARATOR : "") })') + endif + if len(a:excluded) + call filter(entries, '(v:val.head . v:val.tail . v:val.suffix) !~ a:excluded') + endif + return entries +endfunction + +function! s:GetTagList(tagfile) + return map(readfile(a:tagfile), 'matchstr(v:val, ''^[^!\t][^\t]*'')') +endfunction + +function! s:GetTaggedFileList(tagfile) + execute 'cd ' . fnamemodify(a:tagfile, ':h') + let result = map(readfile(a:tagfile), 'fnamemodify(matchstr(v:val, ''^[^!\t][^\t]*\t\zs[^\t]\+''), '':p:~'')') + cd - + return result +endfunction + +function! s:HighlightPrompt(prompt, highlight) + syntax clear + execute printf('syntax match %s /^\V%s/', a:highlight, escape(a:prompt, '\')) +endfunction + +function! s:HighlightError() + syntax clear + syntax match Error /^.*$/ +endfunction + +function! s:CompareTimeDescending(i1, i2) + return a:i1.time == a:i2.time ? 0 : a:i1.time > a:i2.time ? -1 : +1 +endfunction + +function! s:CompareRanks(i1, i2) + if exists('a:i1.ranks') && exists('a:i2.ranks') + for i in range(min([len(a:i1.ranks), len(a:i2.ranks)])) + if a:i1.ranks[i] > a:i2.ranks[i] + return +1 + elseif a:i1.ranks[i] < a:i2.ranks[i] + return -1 + endif + endfor + endif + return 0 +endfunction + +function! s:GetCurrentTagFiles() + return sort(filter(map(tagfiles(), 'fnamemodify(v:val, '':p'')'), 'filereadable(v:val)')) +endfunction + +" }}}1 +"============================================================================= +" OBJECT: {{{1 +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode = { 'Base' : {} } + +function! g:FuzzyFinderMode.Base.launch(initial_text, partial_matching, prev_bufnr, tag_files) + " initializes this object + call self.extend_options() + let self.partial_matching = a:partial_matching + let self.prev_bufnr = a:prev_bufnr + let self.tag_files = a:tag_files " to get local value of current buffer + let self.last_col = -1 + call s:InfoFileManager.load() + if !s:IsAvailableMode(self) + echo 'This mode is not available: ' . self.to_str() + return + endif + + call s:WindowManager.activate(self.make_complete_func('CompleteFunc')) + call s:OptionManager.set('completeopt', 'menuone') + call s:OptionManager.set('ignorecase', self.ignore_case) + + " local autocommands + augroup FuzzyfinderLocal + autocmd! + execute 'autocmd CursorMovedI <buffer> call ' . self.to_str('on_cursor_moved_i()') + execute 'autocmd InsertLeave <buffer> nested call ' . self.to_str('on_insert_leave()' ) + augroup END + + " local mapping + for [lhs, rhs] in [ + \ [ self.key_open , self.to_str('on_cr(0, 0)' ) ], + \ [ self.key_open_split , self.to_str('on_cr(1, 0)' ) ], + \ [ self.key_open_vsplit, self.to_str('on_cr(2, 0)' ) ], + \ [ self.key_open_tab , self.to_str('on_cr(3, 0)' ) ], + \ [ '<BS>' , self.to_str('on_bs()' ) ], + \ [ '<C-h>' , self.to_str('on_bs()' ) ], + \ [ self.key_next_mode , self.to_str('on_switch_mode(+1)' ) ], + \ [ self.key_prev_mode , self.to_str('on_switch_mode(-1)' ) ], + \ [ self.key_ignore_case, self.to_str('on_switch_ignore_case()') ], + \ ] + " hacks to be able to use feedkeys(). + execute printf('inoremap <buffer> <silent> %s <C-r>=%s ? "" : ""<CR>', lhs, rhs) + endfor + + call self.on_mode_enter() + + " Starts Insert mode and makes CursorMovedI event now. Command prompt is + " needed to forces a completion menu to update every typing. + call setline(1, self.prompt . a:initial_text) + call feedkeys("A", 'n') " startinsert! does not work in InsertLeave handler +endfunction + +function! g:FuzzyFinderMode.Base.on_cursor_moved_i() + let ln = getline('.') + let cl = col('.') + if !self.exists_prompt(ln) + " if command prompt is removed + "call setline('.', self.prompt . ln) + call setline('.', self.restore_prompt(ln)) + call feedkeys(repeat("\<Right>", len(getline('.')) - len(ln)), 'n') + elseif cl <= len(self.prompt) + " if the cursor is moved before command prompt + call feedkeys(repeat("\<Right>", len(self.prompt) - cl + 1), 'n') + elseif cl > strlen(ln) && cl != self.last_col + " if the cursor is placed on the end of the line and has been actually moved. + let self.last_col = cl + call feedkeys("\<C-x>\<C-u>", 'n') + endif +endfunction + +function! g:FuzzyFinderMode.Base.on_insert_leave() + let text = getline('.') + call self.on_mode_leave() + call self.empty_cache_if_existed(0) + call s:OptionManager.restore_all() + call s:WindowManager.deactivate() + + " switchs to next mode, or finishes fuzzyfinder. + if exists('s:reserved_switch_mode') + let m = self.next_mode(s:reserved_switch_mode < 0) + call m.launch(self.remove_prompt(text), self.partial_matching, self.prev_bufnr, self.tag_files) + unlet s:reserved_switch_mode + else + if exists('s:reserved_command') + call feedkeys(self.on_open(s:reserved_command[0], s:reserved_command[1]), 'n') + unlet s:reserved_command + endif + endif +endfunction + +function! g:FuzzyFinderMode.Base.on_buf_enter() +endfunction + +function! g:FuzzyFinderMode.Base.on_buf_write_post() +endfunction + +function! g:FuzzyFinderMode.Base.on_command_pre(cmd) +endfunction + +function! g:FuzzyFinderMode.Base.on_cr(index, check_dir) + if pumvisible() + call feedkeys(printf("\<C-y>\<C-r>=%s(%d, 1) ? '' : ''\<CR>", self.to_str('on_cr'), a:index), 'n') + elseif !a:check_dir || getline('.') !~ '[/\\]$' + let s:reserved_command = [self.remove_prompt(getline('.')), a:index] + call feedkeys("\<Esc>", 'n') + endif +endfunction + +function! g:FuzzyFinderMode.Base.on_bs() + let bs_count = 1 + if self.smart_bs && col('.') > 2 && getline('.')[col('.') - 2] =~ '[/\\]' + let bs_count = len(matchstr(getline('.')[:col('.') - 3], '[^/\\]*$')) + 1 + endif + call feedkeys((pumvisible() ? "\<C-e>" : "") . repeat("\<BS>", bs_count), 'n') +endfunction + +function! g:FuzzyFinderMode.Base.on_mode_enter() +endfunction + +function! g:FuzzyFinderMode.Base.on_mode_leave() +endfunction + +function! g:FuzzyFinderMode.Base.on_open(expr, mode) + return [ + \ ':edit ', + \ ':split ', + \ ':vsplit ', + \ ':tabedit ', + \ ][a:mode] . s:EscapeFilename(a:expr) . "\<CR>" +endfunction + +function! g:FuzzyFinderMode.Base.on_switch_mode(next_prev) + let s:reserved_switch_mode = a:next_prev + call feedkeys("\<Esc>", 'n') +endfunction + +function! g:FuzzyFinderMode.Base.on_switch_ignore_case() + let &ignorecase = !&ignorecase + echo "ignorecase = " . &ignorecase + let self.last_col = -1 + call self.on_cursor_moved_i() +endfunction + +" export string list +function! g:FuzzyFinderMode.Base.serialize_info() + let header = self.to_key() . "\t" + return map(copy(self.info), 'header . string(v:val)') +endfunction + +" import related items from string list +function! g:FuzzyFinderMode.Base.deserialize_info(lines) + let header = self.to_key() . "\t" + let self.info = map(filter(copy(a:lines), 'v:val[: len(header) - 1] ==# header'), + \ 'eval(v:val[len(header) :])') +endfunction + +function! g:FuzzyFinderMode.Base.complete(findstart, base) + if a:findstart + return 0 + elseif !self.exists_prompt(a:base) || len(self.remove_prompt(a:base)) < self.min_length + return [] + endif + call s:HighlightPrompt(self.prompt, self.prompt_highlight) + " FIXME: ExpandAbbrevMap duplicates index + let result = [] + for expanded_base in s:ExpandAbbrevMap(self.remove_prompt(a:base), self.abbrev_map) + let result += self.on_complete(expanded_base) + endfor + call sort(result, 's:CompareRanks') + if empty(result) + call s:HighlightError() + else + call feedkeys("\<C-p>\<Down>", 'n') + endif + return result +endfunction + +" This function is set to 'completefunc' which doesn't accept dictionary-functions. +function! g:FuzzyFinderMode.Base.make_complete_func(name) + execute printf("function! s:%s(findstart, base)\n" . + \ " return %s.complete(a:findstart, a:base)\n" . + \ "endfunction", a:name, self.to_str()) + return s:GetSidPrefix() . a:name +endfunction + +" fuzzy : 'str' -> {'base':'str', 'wi':'*s*t*r*', 're':'\V\.\*s\.\*t\.\*r\.\*'} +" partial: 'str' -> {'base':'str', 'wi':'*str*', 're':'\V\.\*str\.\*'} +function! g:FuzzyFinderMode.Base.make_pattern(base) + if self.partial_matching + let wi = (a:base !~ '^[*?]' ? '*' : '') . a:base . + \ (a:base =~ '[^*?]$' ? '*' : '') + let re = s:ConvertWildcardToRegexp(wi) + return { 'base': a:base, 'wi':wi, 're': re } + else + let wi = '' + for char in split(a:base, '\zs') + if wi !~ '[*?]$' && char !~ '[*?]' + let wi .= '*'. char + else + let wi .= char + endif + endfor + + if wi !~ '[*?]$' + let wi .= '*' + endif + + let re = s:ConvertWildcardToRegexp(wi) + + if self.migemo_support && a:base !~ '[^\x01-\x7e]' + let re .= '\|\m.*' . substitute(migemo(a:base), '\\_s\*', '.*', 'g') . '.*' + endif + + return { 'base': a:base, 'wi':wi, 're': re } + endif +endfunction + +" glob with caching-feature, etc. +function! g:FuzzyFinderMode.Base.glob_ex(dir, file, excluded, index, matching_limit) + let key = fnamemodify(a:dir, ':p') + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') + echo 'Caching file list...' + let self.cache[key] = s:EnumExpandedDirsEntries(key, a:excluded) + call s:ExtendIndexToEach(self.cache[key], 1) + endif + echo 'Filtering file list...' + "return map(s:FilterEx(self.cache[key], 'v:val.tail =~ ' . string(a:file), a:matching_limit), + return map(s:FilterMatching(self.cache[key], 'tail', a:file, a:index, a:matching_limit), + \ '{ "index" : v:val.index, "path" : (v:val.head == key ? a:dir : v:val.head) . v:val.tail . v:val.suffix }') +endfunction + +function! g:FuzzyFinderMode.Base.glob_dir_ex(dir, file, excluded, index, matching_limit) + let key = fnamemodify(a:dir, ':p') + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') + echo 'Caching file list...' + let self.cache[key] = filter(s:EnumExpandedDirsEntries(key, a:excluded), 'len(v:val.suffix)') + call insert(self.cache[key], { 'head' : key, 'tail' : '..', 'suffix' : s:PATH_SEPARATOR }) + call insert(self.cache[key], { 'head' : key, 'tail' : '.' , 'suffix' : '' }) + call s:ExtendIndexToEach(self.cache[key], 1) + endif + echo 'Filtering file list...' + "return map(s:FilterEx(self.cache[key], 'v:val.tail =~ ' . string(a:file), a:matching_limit), + return map(s:FilterMatching(self.cache[key], 'tail', a:file, a:index, a:matching_limit), + \ '{ "index" : v:val.index, "path" : (v:val.head == key ? a:dir : v:val.head) . v:val.tail . v:val.suffix }') +endfunction + +function! g:FuzzyFinderMode.Base.empty_cache_if_existed(force) + if exists('self.cache') && (a:force || !exists('self.lasting_cache') || !self.lasting_cache) + unlet self.cache + "let self.cache = (type(self.cache) == type({}) ? {} : + " \ type(self.cache) == type([]) ? [] : + " \ type(self.cache) == type('') ? '' : 0) + endif +endfunction + +function! g:FuzzyFinderMode.Base.to_key() + return filter(keys(g:FuzzyFinderMode), 'g:FuzzyFinderMode[v:val] is self')[0] +endfunction + +" returns 'g:FuzzyFinderMode.{key}{.argument}' +function! g:FuzzyFinderMode.Base.to_str(...) + return 'g:FuzzyFinderMode.' . self.to_key() . (a:0 > 0 ? '.' . a:1 : '') +endfunction + +" takes in g:FuzzyFinderOptions +function! g:FuzzyFinderMode.Base.extend_options() + let n = filter(keys(g:FuzzyFinderMode), 'g:FuzzyFinderMode[v:val] is self')[0] + call extend(self, g:FuzzyFinderOptions.Base, 'force') + call extend(self, g:FuzzyFinderOptions[self.to_key()], 'force') +endfunction + +function! g:FuzzyFinderMode.Base.next_mode(rev) + let modes = (a:rev ? s:GetSortedAvailableModes() : reverse(s:GetSortedAvailableModes())) + let m_last = modes[-1] + for m in modes + if m is self + break + endif + let m_last = m + endfor + return m_last + " vim crashed using map() +endfunction + +function! g:FuzzyFinderMode.Base.exists_prompt(in) + return strlen(a:in) >= strlen(self.prompt) && a:in[:strlen(self.prompt) -1] ==# self.prompt +endfunction + +function! g:FuzzyFinderMode.Base.remove_prompt(in) + return a:in[(self.exists_prompt(a:in) ? strlen(self.prompt) : 0):] +endfunction + +function! g:FuzzyFinderMode.Base.restore_prompt(in) + let i = 0 + while i < len(self.prompt) && i < len(a:in) && self.prompt[i] ==# a:in[i] + let i += 1 + endwhile + return self.prompt . a:in[i : ] +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.Buffer = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.Buffer.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'path', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, v:val.time, a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.Buffer.on_open(expr, mode) + " attempts to convert the path to the number for handling unnamed buffer + return printf([ + \ ':%sbuffer', + \ ':%ssbuffer', + \ ':vertical :%ssbuffer', + \ ':tab :%ssbuffer', + \ ][a:mode] . "\<CR>", filter(self.cache, 'v:val.path == a:expr')[0].buf_nr) +endfunction + +function! g:FuzzyFinderMode.Buffer.on_mode_enter() + let self.cache = map(filter(range(1, bufnr('$')), 'buflisted(v:val) && v:val != self.prev_bufnr'), + \ 'self.make_item(v:val)') + if self.mru_order + call s:ExtendIndexToEach(sort(self.cache, 's:CompareTimeDescending'), 1) + endif +endfunction + +function! g:FuzzyFinderMode.Buffer.on_buf_enter() + call self.update_buf_times() +endfunction + +function! g:FuzzyFinderMode.Buffer.on_buf_write_post() + call self.update_buf_times() +endfunction + +function! g:FuzzyFinderMode.Buffer.update_buf_times() + if !exists('self.buf_times') + let self.buf_times = {} + endif + let self.buf_times[bufnr('%')] = localtime() +endfunction + +function! g:FuzzyFinderMode.Buffer.make_item(nr) + return { + \ 'index' : a:nr, + \ 'buf_nr' : a:nr, + \ 'path' : empty(bufname(a:nr)) ? '[No Name]' : fnamemodify(bufname(a:nr), ':~:.'), + \ 'time' : (exists('self.buf_times[a:nr]') ? strftime(self.time_format, self.buf_times[a:nr]) : ''), + \ } +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.File = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.File.on_complete(base) + let base = s:ExpandTailDotSequenceToParentDir(a:base) + let patterns = map(s:SplitPath(base), 'self.make_pattern(v:val)') + let result = self.glob_ex(patterns.head.base, patterns.tail.re, self.excluded_path, s:SuffixNumber(patterns.tail.base), self.matching_limit) + let result = filter(result, 'bufnr("^" . v:val.path . "$") != self.prev_bufnr') + if len(result) >= self.matching_limit + call s:HighlightError() + endif + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, "", base, 1)') +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.Dir = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.Dir.on_complete(base) + let base = s:ExpandTailDotSequenceToParentDir(a:base) + let patterns = map(s:SplitPath(base), 'self.make_pattern(v:val)') + let result = self.glob_dir_ex(patterns.head.base, patterns.tail.re, self.excluded_path, s:SuffixNumber(patterns.tail.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, "", base, 1)') +endfunction + +function! g:FuzzyFinderMode.Dir.on_open(expr, mode) + return ':cd ' . escape(a:expr, ' ') . [ + \ "\<CR>", + \ "", + \ "", + \ "", + \ ][a:mode] +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.MruFile = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.MruFile.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'path', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, v:val.time, a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.MruFile.on_mode_enter() + let self.cache = copy(self.info) + let self.cache = filter(self.cache, 'bufnr("^" . v:val.path . "$") != self.prev_bufnr') + let self.cache = filter(self.cache, 'filereadable(v:val.path)') + let self.cache = map(self.cache, '{ "path" : fnamemodify(v:val.path, ":~:."), "time" : strftime(self.time_format, v:val.time) }') + let self.cache = s:ExtendIndexToEach(self.cache, 1) +endfunction + +function! g:FuzzyFinderMode.MruFile.on_buf_enter() + call self.update_info() +endfunction + +function! g:FuzzyFinderMode.MruFile.on_buf_write_post() + call self.update_info() +endfunction + +function! g:FuzzyFinderMode.MruFile.update_info() + "if !empty(&buftype) || !filereadable(expand('%')) + if !empty(&buftype) + return + endif + call s:InfoFileManager.load() + let self.info = s:UpdateMruList(self.info, { 'path' : expand('%:p'), 'time' : localtime() }, + \ 'path', self.max_item, self.excluded_path) + call s:InfoFileManager.save() +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.MruCmd = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.MruCmd.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'command', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.command, v:val.index, v:val.command, self.trim_length, v:val.time, a:base, 0)') +endfunction + +function! g:FuzzyFinderMode.MruCmd.on_open(expr, mode) + redraw + " use feedkeys to remap <CR> + return a:expr . [ + \ "\<C-r>=feedkeys(\"\\<CR>\", 'm')?'':''\<CR>", + \ "", + \ "", + \ "", + \ ][a:mode] +endfunction + +function! g:FuzzyFinderMode.MruCmd.on_mode_enter() + let self.cache = s:ExtendIndexToEach(map(copy(self.info), + \ '{ "command" : v:val.command, "time" : strftime(self.time_format, v:val.time) }'), 1) +endfunction + +function! g:FuzzyFinderMode.MruCmd.on_command_pre(cmd) + call self.update_info(a:cmd) +endfunction + +function! g:FuzzyFinderMode.MruCmd.update_info(cmd) + call s:InfoFileManager.load() + let self.info = s:UpdateMruList(self.info, { 'command' : a:cmd, 'time' : localtime() }, + \ 'command', self.max_item, self.excluded_command) + call s:InfoFileManager.save() +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.FavFile = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.FavFile.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = s:FilterMatching(self.cache, 'path', patterns.re, s:SuffixNumber(patterns.base), 0) + return map(result, 's:FormatCompletionItem(v:val.path, v:val.index, v:val.path, self.trim_length, v:val.time, a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.FavFile.on_mode_enter() + let self.cache = copy(self.info) + let self.cache = filter(self.cache, 'bufnr("^" . v:val.path . "$") != self.prev_bufnr') + let self.cache = map(self.cache, '{ "path" : fnamemodify(v:val.path, ":~:."), "time" : strftime(self.time_format, v:val.time) }') + let self.cache = s:ExtendIndexToEach(self.cache, 1) +endfunction + +function! g:FuzzyFinderMode.FavFile.add(in_file, adds) + call s:InfoFileManager.load() + + let file = fnamemodify((empty(a:in_file) ? expand('%') : a:in_file), ':p:~') + + call filter(self.info, 'v:val.path != file') + if a:adds + call add(self.info, { 'path' : file, 'time' : localtime() }) + endif + + call s:InfoFileManager.save() +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.Tag = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.Tag.on_complete(base) + let patterns = self.make_pattern(a:base) + let result = self.find_tag(patterns.re, self.matching_limit) + if len(result) >= self.matching_limit + call s:HighlightError() + endif + return map(result, 's:FormatCompletionItem(v:val, -1, v:val, self.trim_length, "", a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.Tag.on_open(expr, mode) + return [ + \ ':tjump ', + \ ':stjump ', + \ ':vertical :stjump ', + \ ':tab :stjump ', + \ ][a:mode] . a:expr . "\<CR>" +endfunction + +function! g:FuzzyFinderMode.Tag.find_tag(pattern, matching_limit) + if !len(self.tag_files) + return [] + endif + + let key = join(self.tag_files, "\n") + + " cache not created or tags file updated? + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') || max(map(copy(self.tag_files), 'getftime(v:val) >= self.cache[key].time')) + echo 'Caching tag list...' + let self.cache[key] = { + \ 'time' : localtime(), + \ 'data' : s:Unique(s:Concat(map(copy(self.tag_files), 's:GetTagList(v:val)'))), + \ } + endif + + echo 'Filtering tag list...' + return s:FilterEx(self.cache[key].data, 'v:val =~ ' . string(a:pattern), a:matching_limit) +endfunction + +"----------------------------------------------------------------------------- +let g:FuzzyFinderMode.TaggedFile = copy(g:FuzzyFinderMode.Base) + +function! g:FuzzyFinderMode.TaggedFile.on_complete(base) + let patterns = self.make_pattern(a:base) + echo 'Making tagged file list...' + let result = self.find_tagged_file(patterns.re, self.matching_limit) + if len(result) >= self.matching_limit + call s:HighlightError() + endif + return map(result, 's:FormatCompletionItem(v:val, -1, v:val, self.trim_length, "", a:base, 1)') +endfunction + +function! g:FuzzyFinderMode.TaggedFile.find_tagged_file(pattern, matching_limit) + if !len(self.tag_files) + return [] + endif + + let key = join(self.tag_files, "\n") + + " cache not created or tags file updated? + call extend(self, { 'cache' : {} }, 'keep') + if !exists('self.cache[key]') || max(map(copy(self.tag_files), 'getftime(v:val) >= self.cache[key].time')) + echo 'Caching tagged-file list...' + let self.cache[key] = { + \ 'time' : localtime(), + \ 'data' : s:Unique(s:Concat(map(copy(self.tag_files), 's:GetTaggedFileList(v:val)'))), + \ } + endif + + echo 'Filtering tagged-file list...' + return s:FilterEx(map(self.cache[key].data, 'fnamemodify(v:val, '':.'')'), + \ 'v:val =~ ' . string(a:pattern), + \ a:matching_limit) + +endfunction + +"----------------------------------------------------------------------------- +" sets or restores temporary options +let s:OptionManager = { 'originals' : {} } + +function! s:OptionManager.set(name, value) + call extend(self.originals, { a:name : eval('&' . a:name) }, 'keep') + execute printf('let &%s = a:value', a:name) +endfunction + +function! s:OptionManager.restore_all() + for [name, value] in items(self.originals) + execute printf('let &%s = value', name) + endfor + let self.originals = {} +endfunction + +"----------------------------------------------------------------------------- +" manages buffer/window for fuzzyfinder +let s:WindowManager = { 'buf_nr' : -1 } + +function! s:WindowManager.activate(complete_func) + let self.prev_winnr = winnr() + let cwd = getcwd() + + if !bufexists(self.buf_nr) + leftabove 1new + file `='[Fuzzyfinder]'` + let self.buf_nr = bufnr('%') + elseif bufwinnr(self.buf_nr) == -1 + leftabove 1split + execute self.buf_nr . 'buffer' + delete _ + elseif bufwinnr(self.buf_nr) != bufwinnr('%') + execute bufwinnr(self.buf_nr) . 'wincmd w' + endif + + " countermeasure for auto-cd script + execute ':lcd ' . cwd + + setlocal filetype=fuzzyfinder + setlocal bufhidden=delete + setlocal buftype=nofile + setlocal noswapfile + setlocal nobuflisted + setlocal modifiable + setlocal nocursorline " for highlighting + setlocal nocursorcolumn " for highlighting + let &l:completefunc = a:complete_func + + redraw " for 'lazyredraw' + + " suspend autocomplpop.vim + if exists(':AutoComplPopLock') + :AutoComplPopLock + endif +endfunction + +function! s:WindowManager.deactivate() + " resume autocomplpop.vim + if exists(':AutoComplPopUnlock') + :AutoComplPopUnlock + endif + + close + execute self.prev_winnr . 'wincmd w' +endfunction + +"----------------------------------------------------------------------------- +let s:InfoFileManager = { 'originals' : {} } + +function! s:InfoFileManager.load() + for m in s:GetAvailableModes() + let m.info = [] + endfor + + try + let lines = readfile(expand(self.get_info_file())) + catch /.*/ + return + endtry + + " compatibility check + if !count(lines, self.get_info_version_line()) + call self.warn_old_info() + let g:FuzzyFinderOptions.Base.info_file = '' + return + endif + + for m in s:GetAvailableModes() + call m.deserialize_info(lines) + endfor +endfunction + +function! s:InfoFileManager.save() + let lines = [ self.get_info_version_line() ] + for m in s:GetAvailableModes() + let lines += m.serialize_info() + endfor + + try + call writefile(lines, expand(self.get_info_file())) + catch /.*/ + endtry +endfunction + +function! s:InfoFileManager.edit() + + new + file `='[FuzzyfinderInfo]'` + let self.bufnr = bufnr('%') + + setlocal filetype=vim + setlocal bufhidden=delete + setlocal buftype=acwrite + setlocal noswapfile + + augroup FuzzyfinderInfo + autocmd! + autocmd BufWriteCmd <buffer> call s:InfoFileManager.on_buf_write_cmd() + augroup END + + execute '0read ' . expand(self.get_info_file()) + setlocal nomodified + +endfunction + +function! s:InfoFileManager.on_buf_write_cmd() + for m in s:GetAvailableModes() + call m.deserialize_info(getline(1, '$')) + endfor + call self.save() + setlocal nomodified + execute printf('%dbdelete! ', self.bufnr) + echo "Information file updated" +endfunction + +function! s:InfoFileManager.get_info_version_line() + return "VERSION\t206" +endfunction + +function! s:InfoFileManager.get_info_file() + return g:FuzzyFinderOptions.Base.info_file +endfunction + +function! s:InfoFileManager.warn_old_info() + echohl WarningMsg + echo printf("==================================================\n" . + \ " Your Fuzzyfinder information file is no longer \n" . + \ " supported. Please remove \n" . + \ " %-48s\n" . + \ "==================================================\n" , + \ '"' . expand(self.get_info_file()) . '".') + echohl None +endfunction + +" }}}1 +"============================================================================= +" GLOBAL OPTIONS: {{{1 +" stores user-defined g:FuzzyFinderOptions ------------------------------ {{{2 +let user_options = (exists('g:FuzzyFinderOptions') ? g:FuzzyFinderOptions : {}) +" }}}2 + +" Initializes g:FuzzyFinderOptions. +let g:FuzzyFinderOptions = { 'Base':{}, 'Buffer':{}, 'File':{}, 'Dir':{}, 'MruFile':{}, 'MruCmd':{}, 'FavFile':{}, 'Tag':{}, 'TaggedFile':{}} +"----------------------------------------------------------------------------- +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in previous window. +let g:FuzzyFinderOptions.Base.key_open = '<CR>' +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in split new window +let g:FuzzyFinderOptions.Base.key_open_split = '<C-j>' +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in vertical-split new window. +let g:FuzzyFinderOptions.Base.key_open_vsplit = '<C-k>' +" [All Mode] This is mapped to select completion item or finish input and +" open a buffer/file in a new tab page. +let g:FuzzyFinderOptions.Base.key_open_tab = '<C-]>' +" [All Mode] This is mapped to switch to the next mode. +let g:FuzzyFinderOptions.Base.key_next_mode = '<C-l>' +" [All Mode] This is mapped to switch to the previous mode. +let g:FuzzyFinderOptions.Base.key_prev_mode = '<C-o>' +" [All Mode] This is mapped to temporarily switch whether or not to ignore +" case. +let g:FuzzyFinderOptions.Base.key_ignore_case = '<C-t>' +" [All Mode] This is the file name to write information of the MRU, etc. If +" "" was set, Fuzzyfinder does not write to the file. +let g:FuzzyFinderOptions.Base.info_file = '~/.vimfuzzyfinder' +" [All Mode] Fuzzyfinder does not start a completion if a length of entered +" text is less than this. +let g:FuzzyFinderOptions.Base.min_length = 0 +" [All Mode] This is a dictionary. Each value must be a list. All matchs of a +" key in entered text is expanded with the value. +let g:FuzzyFinderOptions.Base.abbrev_map = {} +" [All Mode] Fuzzyfinder ignores case in search patterns if non-zero is set. +let g:FuzzyFinderOptions.Base.ignore_case = 1 +" [All Mode] This is a string to format time string. See :help strftime() for +" details. +let g:FuzzyFinderOptions.Base.time_format = '(%x %H:%M:%S)' +" [All Mode] If a length of completion item is more than this, it is trimmed +" when shown in completion menu. +let g:FuzzyFinderOptions.Base.trim_length = 80 +" [All Mode] Fuzzyfinder does not remove caches of completion lists at the end +" of explorer to reuse at the next time if non-zero was set. +let g:FuzzyFinderOptions.Base.lasting_cache = 1 +" [All Mode] Fuzzyfinder uses Migemo if non-zero is set. +let g:FuzzyFinderOptions.Base.migemo_support = 0 +"----------------------------------------------------------------------------- +" [Buffer Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.Buffer.mode_available = 1 +" [Buffer Mode] The prompt string. +let g:FuzzyFinderOptions.Buffer.prompt = '>Buffer>' +" [Buffer Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.Buffer.prompt_highlight = 'Question' +" [Buffer Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.Buffer.smart_bs = 1 +" [Buffer Mode] The completion items is sorted in the order of recently used +" if non-zero is set. +let g:FuzzyFinderOptions.Buffer.mru_order = 1 +" [Buffer Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.Buffer.switch_order = 10 +"----------------------------------------------------------------------------- +" [File Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.File.mode_available = 1 +" [File Mode] The prompt string. +let g:FuzzyFinderOptions.File.prompt = '>File>' +" [File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.File.prompt_highlight = 'Question' +" [File Mode] Pressing <BS> after a path separator deletes one directory name +" if non-zero is set. +let g:FuzzyFinderOptions.File.smart_bs = 1 +" [File Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.File.switch_order = 20 +" [File Mode] The items matching this are excluded from the completion list. +let g:FuzzyFinderOptions.File.excluded_path = '\v\~$|\.o$|\.exe$|\.bak$|\.swp$|((^|[/\\])\.[/\\]$)' +" [File Mode] If a number of matched items was over this, the completion +" process is aborted. +let g:FuzzyFinderOptions.File.matching_limit = 200 +"----------------------------------------------------------------------------- +" [Directory Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.Dir.mode_available = 1 +" [Directory Mode] The prompt string. +let g:FuzzyFinderOptions.Dir.prompt = '>Dir>' +" [Directory Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.Dir.prompt_highlight = 'Question' +" [Directory Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.Dir.smart_bs = 1 +" [Directory Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.Dir.switch_order = 30 +" [Directory Mode] The items matching this are excluded from the completion +" list. +let g:FuzzyFinderOptions.Dir.excluded_path = '\v(^|[/\\])\.{1,2}[/\\]$' +"----------------------------------------------------------------------------- +" [Mru-File Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.MruFile.mode_available = 1 +" [Mru-File Mode] The prompt string. +let g:FuzzyFinderOptions.MruFile.prompt = '>MruFile>' +" [Mru-File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.MruFile.prompt_highlight = 'Question' +" [Mru-File Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.MruFile.smart_bs = 1 +" [Mru-File Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.MruFile.switch_order = 40 +" [Mru-File Mode] The items matching this are excluded from the completion +" list. +let g:FuzzyFinderOptions.MruFile.excluded_path = '\v\~$|\.bak$|\.swp$' +" [Mru-File Mode] This is an upper limit of MRU items to be stored. +let g:FuzzyFinderOptions.MruFile.max_item = 99 +"----------------------------------------------------------------------------- +" [Mru-Cmd Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.MruCmd.mode_available = 1 +" [Mru-Cmd Mode] The prompt string. +let g:FuzzyFinderOptions.MruCmd.prompt = '>MruCmd>' +" [Mru-Cmd Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.MruCmd.prompt_highlight = 'Question' +" [Mru-Cmd Mode] Pressing <BS> after a path separator deletes one directory +" name if non-zero is set. +let g:FuzzyFinderOptions.MruCmd.smart_bs = 0 +" [Mru-Cmd Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.MruCmd.switch_order = 50 +" [Mru-Cmd Mode] The items matching this are excluded from the completion +" list. +let g:FuzzyFinderOptions.MruCmd.excluded_command = '^$' +" [Mru-Cmd Mode] This is an upper limit of MRU items to be stored. +let g:FuzzyFinderOptions.MruCmd.max_item = 99 +"----------------------------------------------------------------------------- +" [Favorite-File Mode] This disables all functions of this mode if zero was +" set. +let g:FuzzyFinderOptions.FavFile.mode_available = 1 +" [Favorite-File Mode] The prompt string. +let g:FuzzyFinderOptions.FavFile.prompt = '>FavFile>' +" [Favorite-File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.FavFile.prompt_highlight = 'Question' +" [Favorite-File Mode] Pressing <BS> after a path separator deletes one +" directory name if non-zero is set. +let g:FuzzyFinderOptions.FavFile.smart_bs = 1 +" [Favorite-File Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.FavFile.switch_order = 60 +"----------------------------------------------------------------------------- +" [Tag Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.Tag.mode_available = 1 +" [Tag Mode] The prompt string. +let g:FuzzyFinderOptions.Tag.prompt = '>Tag>' +" [Tag Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.Tag.prompt_highlight = 'Question' +" [Tag Mode] Pressing <BS> after a path separator deletes one directory name +" if non-zero is set. +let g:FuzzyFinderOptions.Tag.smart_bs = 0 +" [Tag Mode] This is used to sort modes for switching to the next/previous +" mode. +let g:FuzzyFinderOptions.Tag.switch_order = 70 +" [Tag Mode] The items matching this are excluded from the completion list. +let g:FuzzyFinderOptions.Tag.excluded_path = '\v\~$|\.bak$|\.swp$' +" [Tag Mode] If a number of matched items was over this, the completion +" process is aborted. +let g:FuzzyFinderOptions.Tag.matching_limit = 200 +"----------------------------------------------------------------------------- +" [Tagged-File Mode] This disables all functions of this mode if zero was set. +let g:FuzzyFinderOptions.TaggedFile.mode_available = 1 +" [Tagged-File Mode] The prompt string. +let g:FuzzyFinderOptions.TaggedFile.prompt = '>TaggedFile>' +" [Tagged-File Mode] The highlight group name for a prompt string. +let g:FuzzyFinderOptions.TaggedFile.prompt_highlight = 'Question' +" [Tagged-File Mode] Pressing <BS> after a path separator deletes one +" directory name if non-zero is set. +let g:FuzzyFinderOptions.TaggedFile.smart_bs = 0 +" [Tagged-File Mode] This is used to sort modes for switching to the +" next/previous mode. +let g:FuzzyFinderOptions.TaggedFile.switch_order = 80 +" [Tagged-File Mode] If a number of matched items was over this, the +" completion process is aborted. +let g:FuzzyFinderOptions.TaggedFile.matching_limit = 200 + +" overwrites default values of g:FuzzyFinderOptions with user-defined values - {{{2 +call map(user_options, 'extend(g:FuzzyFinderOptions[v:key], v:val, ''force'')') +call map(copy(g:FuzzyFinderMode), 'v:val.extend_options()') +" }}}2 + +" }}}1 +"============================================================================= +" COMMANDS/AUTOCOMMANDS/MAPPINGS/ETC.: {{{1 + +let s:PATH_SEPARATOR = (has('win32') || has('win64') ? '\' : '/') +let s:MATCHING_RATE_BASE = 10000000 +let s:ABBR_TRIM_MARK = '...' + +augroup FuzzyfinderGlobal + autocmd! + autocmd BufEnter * for m in s:GetAvailableModes() | call m.extend_options() | call m.on_buf_enter() | endfor + autocmd BufWritePost * for m in s:GetAvailableModes() | call m.extend_options() | call m.on_buf_write_post() | endfor +augroup END + +" cnoremap has a problem, which doesn't expand cabbrev. +cmap <silent> <expr> <CR> <SID>OnCmdCR() + +command! -bang -narg=? -complete=buffer FuzzyFinderBuffer call g:FuzzyFinderMode.Buffer.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderFile call g:FuzzyFinderMode.File.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=dir FuzzyFinderDir call g:FuzzyFinderMode.Dir.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderMruFile call g:FuzzyFinderMode.MruFile.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderMruCmd call g:FuzzyFinderMode.MruCmd.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderFavFile call g:FuzzyFinderMode.FavFile.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=tag FuzzyFinderTag call g:FuzzyFinderMode.Tag.launch (<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderTaggedFile call g:FuzzyFinderMode.TaggedFile.launch(<q-args>, len(<q-bang>), bufnr('%'), s:GetCurrentTagFiles()) +command! -bang -narg=? -complete=file FuzzyFinderEditInfo call s:InfoFileManager.edit() +command! -bang -narg=? -complete=file FuzzyFinderAddFavFile call g:FuzzyFinderMode.FavFile.add(<q-args>, 1) +command! -bang -narg=0 FuzzyFinderRemoveCache for m in s:GetAvailableModes() | call m.empty_cache_if_existed(1) | endfor + +" }}}1 +"============================================================================= +" vim: set fdm=marker: