"============================================================================= " Copyright (c) 2007-2010 Takeshi NISHIDA " "============================================================================= " LOAD GUARD {{{1 if !l9#guardScriptLoading(expand(':p'), 0, 0, []) finish endif " }}}1 "============================================================================= " GLOBAL FUNCTIONS {{{1 " returns list of paths. " An argument for glob() is normalized in order to avoid a bug on Windows. function fuf#glob(expr) " Substitutes "\", because on Windows, "**\" doesn't include ".\", " but "**/" include "./". I don't know why. return split(glob(substitute(a:expr, '\', '/', 'g')), "\n") endfunction " function fuf#countModifiedFiles(files, time) return len(filter(copy(a:files), 'getftime(expand(v:val)) > a:time')) endfunction " function fuf#getCurrentTagFiles() return sort(filter(map(tagfiles(), 'fnamemodify(v:val, '':p'')'), 'filereadable(v:val)')) endfunction " function fuf#mapToSetSerialIndex(in, offset) for i in range(len(a:in)) let a:in[i].index = i + a:offset endfor return a:in endfunction " function fuf#updateMruList(mrulist, newItem, maxItem, exclude) let result = copy(a:mrulist) let result = filter(result,'v:val.word !=# a:newItem.word') let result = insert(result, a:newItem) if len(a:exclude) let result = filter(result, 'v:val.word !~ a:exclude') endif return result[0 : a:maxItem - 1] endfunction " takes suffix number. if no digits, returns -1 function fuf#suffixNumber(str) let s = matchstr(a:str, '\d\+$') return (len(s) ? str2nr(s) : -1) endfunction " "foo/bar/buz/hoge" -> { head: "foo/bar/buz/", tail: "hoge" } function fuf#splitPath(path) let head = matchstr(a:path, '^.*[/\\]') return { \ 'head' : head, \ 'tail' : a:path[strlen(head):] \ } endfunction " "foo/.../bar/...hoge" -> "foo/.../bar/../../hoge" function fuf#expandTailDotSequenceToParentDir(pattern) return substitute(a:pattern, '^\(.*[/\\]\)\?\zs\.\(\.\+\)\ze[^/\\]*$', \ '\=repeat(".." . l9#getPathSeparator(), len(submatch(2)))', '') endfunction " function fuf#formatPrompt(prompt, partialMatching, otherString) let indicator = escape((a:partialMatching ? '!' : '') . a:otherString, '\') return substitute(a:prompt, '[]', indicator, 'g') endfunction " function fuf#getFileLines(file) let bufnr = (type(a:file) ==# type(0) ? a:file : bufnr('^' . a:file . '$')) let lines = getbufline(bufnr, 1, '$') if !empty(lines) return lines endif return l9#readFile(a:file) endfunction " function fuf#makePreviewLinesAround(lines, indices, page, maxHeight) let index = ((empty(a:indices) ? 0 : a:indices[0]) \ + a:page * a:maxHeight) % len(a:lines) if empty(a:lines) || a:maxHeight <= 0 return [] endif let beg = max([0, index - a:maxHeight / 2]) let end = min([beg + a:maxHeight, len(a:lines)]) let beg = max([0, end - a:maxHeight]) let lines = [] for i in range(beg, end - 1) let mark = (count(a:indices, i) ? '>' : ' ') call add(lines, printf('%s%4d ', mark, i + 1) . a:lines[i]) endfor return lines endfunction " a:file: a path string or a buffer number function fuf#makePreviewLinesForFile(file, count, maxHeight) let lines = fuf#getFileLines(a:file) if empty(lines) return [] endif let bufnr = (type(a:file) ==# type(0) ? a:file : bufnr('^' . a:file . '$')) if exists('s:bufferCursorPosMap[bufnr]') let indices = [s:bufferCursorPosMap[bufnr][1] - 1] else let indices = [] endif return fuf#makePreviewLinesAround( \ lines, indices, a:count, a:maxHeight) endfunction " function fuf#echoWarning(msg) call l9#echoHl('WarningMsg', a:msg, '[fuf] ', 1) endfunction " function fuf#echoError(msg) call l9#echoHl('ErrorMsg', a:msg, '[fuf] ', 1) endfunction " function fuf#openBuffer(bufNr, mode, reuse) if a:reuse && ((a:mode ==# s:OPEN_TYPE_SPLIT && \ l9#moveToBufferWindowInCurrentTabpage(a:bufNr)) || \ (a:mode ==# s:OPEN_TYPE_VSPLIT && \ l9#moveToBufferWindowInCurrentTabpage(a:bufNr)) || \ (a:mode ==# s:OPEN_TYPE_TAB && \ l9#moveToBufferWindowInOtherTabpage(a:bufNr))) return endif execute printf({ \ s:OPEN_TYPE_CURRENT : '%sbuffer' , \ s:OPEN_TYPE_SPLIT : '%ssbuffer' , \ s:OPEN_TYPE_VSPLIT : 'vertical %ssbuffer', \ s:OPEN_TYPE_TAB : 'tab %ssbuffer' , \ }[a:mode], a:bufNr) endfunction " function fuf#openFile(path, mode, reuse) let bufNr = bufnr('^' . a:path . '$') if bufNr > -1 call fuf#openBuffer(bufNr, a:mode, a:reuse) else execute { \ s:OPEN_TYPE_CURRENT : 'edit ' , \ s:OPEN_TYPE_SPLIT : 'split ' , \ s:OPEN_TYPE_VSPLIT : 'vsplit ' , \ s:OPEN_TYPE_TAB : 'tabedit ', \ }[a:mode] . fnameescape(fnamemodify(a:path, ':~:.')) endif endfunction " function fuf#openTag(tag, mode) execute { \ s:OPEN_TYPE_CURRENT : 'tjump ' , \ s:OPEN_TYPE_SPLIT : 'stjump ' , \ s:OPEN_TYPE_VSPLIT : 'vertical stjump ', \ s:OPEN_TYPE_TAB : 'tab stjump ' , \ }[a:mode] . a:tag endfunction " function fuf#openHelp(tag, mode) execute { \ s:OPEN_TYPE_CURRENT : 'help ' , \ s:OPEN_TYPE_SPLIT : 'help ' , \ s:OPEN_TYPE_VSPLIT : 'vertical help ', \ s:OPEN_TYPE_TAB : 'tab help ' , \ }[a:mode] . a:tag endfunction " function fuf#prejump(mode) execute { \ s:OPEN_TYPE_CURRENT : '' , \ s:OPEN_TYPE_SPLIT : 'split' , \ s:OPEN_TYPE_VSPLIT : 'vsplit' , \ s:OPEN_TYPE_TAB : 'tab split', \ }[a:mode] endfunction " function fuf#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 fuf#makePathItem(fname, menu, appendsDirSuffix) let pathPair = fuf#splitPath(a:fname) let dirSuffix = (a:appendsDirSuffix && isdirectory(expand(a:fname)) \ ? l9#getPathSeparator() \ : '') return { \ 'word' : a:fname . dirSuffix, \ 'wordForPrimaryHead': s:toLowerForIgnoringCase(pathPair.head), \ 'wordForPrimaryTail': s:toLowerForIgnoringCase(pathPair.tail), \ 'wordForBoundary' : s:toLowerForIgnoringCase(s:getWordBoundaries(pathPair.tail)), \ 'wordForRefining' : s:toLowerForIgnoringCase(a:fname . dirSuffix), \ 'wordForRank' : s:toLowerForIgnoringCase(pathPair.tail), \ 'menu' : a:menu, \ } endfunction " function fuf#makeNonPathItem(word, menu) let wordL = s:toLowerForIgnoringCase(a:word) return { \ 'word' : a:word, \ 'wordForPrimary' : wordL, \ 'wordForBoundary': s:toLowerForIgnoringCase(s:getWordBoundaries(a:word)), \ 'wordForRefining': wordL, \ 'wordForRank' : wordL, \ 'menu' : a:menu, \ } endfunction " function fuf#makePatternSet(patternBase, interpreter, partialMatching) let MakeMatchingExpr = function(a:partialMatching \ ? 's:makePartialMatchingExpr' \ : 's:makeFuzzyMatchingExpr') let [primary; refinings] = split(a:patternBase, g:fuf_patternSeparator, 1) let elements = call(a:interpreter, [primary]) let primaryExprs = map(elements.matchingPairs, 'MakeMatchingExpr(v:val[0], v:val[1])') let refiningExprs = map(refinings, 's:makeRefiningExpr(v:val)') return { \ 'primary' : elements.primary, \ 'primaryForRank': elements.primaryForRank, \ 'filteringExpr' : join(primaryExprs + refiningExprs, ' && '), \ } endfunction " function fuf#enumExpandedDirsEntries(dir, exclude) let entries = fuf#glob(a:dir . '*') + fuf#glob(a:dir . '.*') " removes "*/." and "*/.." call filter(entries, 'v:val !~ ''\v(^|[/\\])\.\.?$''') call map(entries, 'fuf#makePathItem(v:val, "", 1)') if len(a:exclude) call filter(entries, 'v:val.word !~ a:exclude') endif return entries endfunction " function fuf#mapToSetAbbrWithSnippedWordAsPath(items) let maxLenStats = {} call map(a:items, 's:makeFileAbbrInfo(v:val, maxLenStats)') let snippedHeads = \ map(maxLenStats, 's:getSnippedHead(v:key[: -2], v:val)') return map(a:items, 's:setAbbrWithFileAbbrData(v:val, snippedHeads)') endfunction " function fuf#setAbbrWithFormattedWord(item, abbrIndex) let lenMenu = (exists('a:item.menu') ? len(a:item.menu) + 2 : 0) let abbrPrefix = (exists('a:item.abbrPrefix') ? a:item.abbrPrefix : '') let a:item.abbr = abbrPrefix . a:item.word if a:abbrIndex let a:item.abbr = printf('%4d: ', a:item.index) . a:item.abbr endif let a:item.abbr = l9#snipTail(a:item.abbr, g:fuf_maxMenuWidth - lenMenu, s:ABBR_SNIP_MASK) return a:item endfunction " function s:onCommandPre() for m in filter(copy(fuf#getModeNames()), 'fuf#{v:val}#requiresOnCommandPre()') call fuf#{m}#onCommandPre(getcmdtype() . getcmdline()) endfor " lets last entry become the newest in the history call histadd(getcmdtype(), getcmdline()) " this is not mapped again (:help recursive_mapping) return "\" endfunction " let s:modeNames = [] " function fuf#addMode(modeName) if count(g:fuf_modesDisable, a:modeName) > 0 return endif call add(s:modeNames, a:modeName) call fuf#{a:modeName}#renewCache() call fuf#{a:modeName}#onInit() if fuf#{a:modeName}#requiresOnCommandPre() " cnoremap has a problem, which doesn't expand cabbrev. cmap onCommandPre() endif endfunction " function fuf#getModeNames() return s:modeNames endfunction " function fuf#defineLaunchCommand(CmdName, modeName, prefixInitialPattern, tempVars) if empty(a:tempVars) let preCmd = '' else let preCmd = printf('call l9#tempvariables#setList(%s, %s) | ', \ string(s:TEMP_VARIABLES_GROUP), string(a:tempVars)) endif execute printf('command! -range -bang -narg=? %s %s call fuf#launch(%s, %s . , len())', \ a:CmdName, preCmd, string(a:modeName), a:prefixInitialPattern) endfunction " function fuf#defineKeyMappingInHandler(key, func) " hacks to be able to use feedkeys(). execute printf( \ 'inoremap %s =fuf#getRunningHandler().%s ? "" : ""', \ a:key, a:func) endfunction " let s:oneTimeVariables = [] " function fuf#setOneTimeVariables(...) let s:oneTimeVariables += a:000 endfunction " function fuf#launch(modeName, initialPattern, partialMatching) if exists('s:runningHandler') call fuf#echoWarning('FuzzyFinder is running.') endif if count(fuf#getModeNames(), a:modeName) == 0 echoerr 'This mode is not available: ' . a:modeName return endif let s:runningHandler = fuf#{a:modeName}#createHandler(copy(s:handlerBase)) let s:runningHandler.stats = fuf#loadDataFile(s:runningHandler.getModeName(), 'stats') let s:runningHandler.partialMatching = a:partialMatching let s:runningHandler.bufNrPrev = bufnr('%') let s:runningHandler.lastCol = -1 let s:runningHandler.windowRestoringCommand = winrestcmd() call s:runningHandler.onModeEnterPre() " NOTE: updatetime is set, because in Buffer-Tag mode on Vim 7.3 on Windows, " Vim keeps from triggering CursorMovedI for updatetime after system() is " called. I don't know why. call fuf#setOneTimeVariables( \ ['&completeopt', 'menuone'], \ ['&ignorecase', 0], \ ['&updatetime', 10], \ ) if s:runningHandler.getPreviewHeight() > 0 call fuf#setOneTimeVariables( \ ['&cmdheight', s:runningHandler.getPreviewHeight() + 1]) endif call l9#tempvariables#setList(s:TEMP_VARIABLES_GROUP, s:oneTimeVariables) let s:oneTimeVariables = [] call s:activateFufBuffer() augroup FufLocal autocmd! autocmd CursorMovedI call s:runningHandler.onCursorMovedI() autocmd InsertLeave nested call s:runningHandler.onInsertLeave() augroup END for [key, func] in [ \ [ g:fuf_keyOpen , 'onCr(' . s:OPEN_TYPE_CURRENT . ')' ], \ [ g:fuf_keyOpenSplit , 'onCr(' . s:OPEN_TYPE_SPLIT . ')' ], \ [ g:fuf_keyOpenVsplit , 'onCr(' . s:OPEN_TYPE_VSPLIT . ')' ], \ [ g:fuf_keyOpenTabpage , 'onCr(' . s:OPEN_TYPE_TAB . ')' ], \ [ '' , 'onBs()' ], \ [ '' , 'onBs()' ], \ [ '' , 'onDeleteWord()' ], \ [ g:fuf_keyPreview , 'onPreviewBase(1)' ], \ [ g:fuf_keyNextMode , 'onSwitchMode(+1)' ], \ [ g:fuf_keyPrevMode , 'onSwitchMode(-1)' ], \ [ g:fuf_keySwitchMatching, 'onSwitchMatching()' ], \ [ g:fuf_keyPrevPattern , 'onRecallPattern(+1)' ], \ [ g:fuf_keyNextPattern , 'onRecallPattern(-1)' ], \ ] call fuf#defineKeyMappingInHandler(key, func) endfor " Starts Insert mode and makes CursorMovedI event now. Command prompt is " needed to forces a completion menu to update every typing. call setline(1, s:runningHandler.getPrompt() . a:initialPattern) call s:runningHandler.onModeEnterPost() call feedkeys("A", 'n') " startinsert! does not work in InsertLeave event handler redraw endfunction " function fuf#loadDataFile(modeName, dataName) if !s:dataFileAvailable return [] endif let lines = l9#readFile(l9#concatPaths([g:fuf_dataDir, a:modeName, a:dataName])) return map(lines, 'eval(v:val)') endfunction " function fuf#saveDataFile(modeName, dataName, items) if !s:dataFileAvailable return -1 endif let lines = map(copy(a:items), 'string(v:val)') return l9#writeFile(lines, l9#concatPaths([g:fuf_dataDir, a:modeName, a:dataName])) endfunction " function fuf#getDataFileTime(modeName, dataName) if !s:dataFileAvailable return -1 endif return getftime(expand(l9#concatPaths([g:fuf_dataDir, a:modeName, a:dataName]))) endfunction " function s:createDataBufferListener(dataFile) let listener = { 'dataFile': a:dataFile } function listener.onWrite(lines) let [modeName, dataName] = split(self.dataFile, l9#getPathSeparator()) let items = map(filter(a:lines, '!empty(v:val)'), 'eval(v:val)') call fuf#saveDataFile(modeName, dataName, items) echo "Data files updated" return 1 endfunction return listener endfunction " function s:createEditDataListener() let listener = {} function listener.onComplete(dataFile, method) let bufName = '[fuf-info]' let lines = l9#readFile(l9#concatPaths([g:fuf_dataDir, a:dataFile])) call l9#tempbuffer#openWritable(bufName, 'vim', lines, 0, 0, 0, \ s:createDataBufferListener(a:dataFile)) endfunction return listener endfunction " function s:getEditableDataFiles(modeName) let dataFiles = fuf#{a:modeName}#getEditableDataNames() call filter(dataFiles, 'fuf#getDataFileTime(a:modeName, v:val) != -1') return map(dataFiles, 'l9#concatPaths([a:modeName, v:val])') endfunction " function fuf#editDataFile() let dataFiles = map(copy(fuf#getModeNames()), 's:getEditableDataFiles(v:val)') let dataFiles = l9#concat(dataFiles) call fuf#callbackitem#launch('', 0, '>Mode>', s:createEditDataListener(), dataFiles, 0) endfunction " function fuf#getRunningHandler() return s:runningHandler endfunction " function fuf#onComplete(findstart, base) return s:runningHandler.onComplete(a:findstart, a:base) endfunction " }}}1 "============================================================================= " LOCAL FUNCTIONS/VARIABLES {{{1 let s:TEMP_VARIABLES_GROUP = expand(':p') let s:ABBR_SNIP_MASK = '...' let s:OPEN_TYPE_CURRENT = 1 let s:OPEN_TYPE_SPLIT = 2 let s:OPEN_TYPE_VSPLIT = 3 let s:OPEN_TYPE_TAB = 4 " a:pattern: 'str' -> '\V\.\*s\.\*t\.\*r\.\*' function s:makeFuzzyMatchingExpr(target, pattern) let wi = '' for c in split(a:pattern, '\zs') if wi =~# '[^*?]$' && c !~ '[*?]' let wi .= '*' endif let wi .= c endfor return s:makePartialMatchingExpr(a:target, wi) endfunction " a:pattern: 'str' -> '\Vstr' " 'st*r' -> '\Vst\.\*r' function s:makePartialMatchingExpr(target, pattern) let patternMigemo = s:makeAdditionalMigemoPattern(a:pattern) if a:pattern !~ '[*?]' && empty(patternMigemo) " NOTE: stridx is faster than regexp matching return 'stridx(' . a:target . ', ' . string(a:pattern) . ') >= 0' endif return a:target . ' =~# ' . \ string(l9#convertWildcardToRegexp(a:pattern)) . patternMigemo endfunction " function s:makeRefiningExpr(pattern) if g:fuf_fuzzyRefining let expr = s:makeFuzzyMatchingExpr('v:val.wordForRefining', a:pattern) else let expr = s:makePartialMatchingExpr('v:val.wordForRefining', a:pattern) endif if a:pattern =~# '\D' return expr else return '(' . expr . ' || v:val.index == ' . string(a:pattern) . ')' endif endfunction " function s:makeAdditionalMigemoPattern(pattern) if !g:fuf_useMigemo || a:pattern =~# '[^\x01-\x7e]' return '' endif return '\|\m' . substitute(migemo(a:pattern), '\\_s\*', '.*', 'g') endfunction " function s:interpretPrimaryPatternForPathTail(pattern) let pattern = fuf#expandTailDotSequenceToParentDir(a:pattern) let pairL = fuf#splitPath(s:toLowerForIgnoringCase(pattern)) return { \ 'primary' : pattern, \ 'primaryForRank': pairL.tail, \ 'matchingPairs' : [['v:val.wordForPrimaryTail', pairL.tail],], \ } endfunction " function s:interpretPrimaryPatternForPath(pattern) let pattern = fuf#expandTailDotSequenceToParentDir(a:pattern) let patternL = s:toLowerForIgnoringCase(pattern) let pairL = fuf#splitPath(patternL) if g:fuf_splitPathMatching let matches = [ \ ['v:val.wordForPrimaryHead', pairL.head], \ ['v:val.wordForPrimaryTail', pairL.tail], \ ] else let matches = [ \ ['v:val.wordForPrimaryHead . v:val.wordForPrimaryTail', patternL], \ ] endif return { \ 'primary' : pattern, \ 'primaryForRank': pairL.tail, \ 'matchingPairs' : matches, \ } endfunction " function s:interpretPrimaryPatternForNonPath(pattern) let patternL = s:toLowerForIgnoringCase(a:pattern) return { \ 'primary' : a:pattern, \ 'primaryForRank': patternL, \ 'matchingPairs' : [['v:val.wordForPrimary', patternL],], \ } endfunction " function s:getWordBoundaries(word) return substitute(a:word, '\a\zs\l\+\|\zs\A', '', 'g') endfunction " function s:toLowerForIgnoringCase(str) return (g:fuf_ignoreCase ? tolower(a:str) : a:str) endfunction " function s:setRanks(item, pattern, exprBoundary, stats) "let word2 = substitute(a:eval_word, '\a\zs\l\+\|\zs\A', '', 'g') let a:item.ranks = [ \ s:evaluateLearningRank(a:item.word, a:stats), \ -s:scoreSequentialMatching(a:item.wordForRank, a:pattern), \ -s:scoreBoundaryMatching(a:item.wordForBoundary, \ a:pattern, a:exprBoundary), \ a:item.index, \ ] return a:item endfunction " function s:evaluateLearningRank(word, stats) for i in range(len(a:stats)) if a:stats[i].word ==# a:word return i endif endfor return len(a:stats) endfunction " range of return value is [0.0, 1.0] function s:scoreSequentialMatching(word, pattern) if empty(a:pattern) return str2float('0.0') endif let pos = stridx(a:word, a:pattern) if pos < 0 return str2float('0.0') endif let lenRest = len(a:word) - len(a:pattern) - pos return str2float(pos == 0 ? '0.5' : '0.0') + str2float('0.5') / (lenRest + 1) endfunction " range of return value is [0.0, 1.0] function s:scoreBoundaryMatching(wordForBoundary, pattern, exprBoundary) if empty(a:pattern) return str2float('0.0') endif if !eval(a:exprBoundary) return 0 endif return (s:scoreSequentialMatching(a:wordForBoundary, a:pattern) + 1) / 2 endfunction " function s:highlightPrompt(prompt) syntax clear execute printf('syntax match %s /^\V%s/', g:fuf_promptHighlight, escape(a:prompt, '\/')) endfunction " function s:highlightError() syntax clear syntax match Error /^.*$/ endfunction " function s:expandAbbrevMap(pattern, abbrevMap) let result = [a:pattern] for [pattern, subs] in items(a:abbrevMap) let exprs = result let result = [] for expr in exprs let result += map(copy(subs), 'substitute(expr, pattern, escape(v:val, ''\''), "g")') endfor endfor return l9#unique(result) endfunction " function s:makeFileAbbrInfo(item, maxLenStats) let head = matchstr(a:item.word, '^.*[/\\]\ze.') let a:item.abbr = { 'head' : head, \ 'tail' : a:item.word[strlen(head):], \ 'key' : head . '.', \ 'prefix' : printf('%4d: ', a:item.index), } if exists('a:item.abbrPrefix') let a:item.abbr.prefix .= a:item.abbrPrefix endif let len = len(a:item.abbr.prefix) + len(a:item.word) + \ (exists('a:item.menu') ? len(a:item.menu) + 2 : 0) if !exists('a:maxLenStats[a:item.abbr.key]') || len > a:maxLenStats[a:item.abbr.key] let a:maxLenStats[a:item.abbr.key] = len endif return a:item endfunction " function s:getSnippedHead(head, baseLen) return l9#snipMid(a:head, len(a:head) + g:fuf_maxMenuWidth - a:baseLen, s:ABBR_SNIP_MASK) endfunction " function s:setAbbrWithFileAbbrData(item, snippedHeads) let lenMenu = (exists('a:item.menu') ? len(a:item.menu) + 2 : 0) let abbr = a:item.abbr.prefix . a:snippedHeads[a:item.abbr.key] . a:item.abbr.tail let a:item.abbr = l9#snipTail(abbr, g:fuf_maxMenuWidth - lenMenu, s:ABBR_SNIP_MASK) return a:item endfunction " let s:FUF_BUF_NAME = '[fuf]' " function s:activateFufBuffer() " lcd . : To avoid the strange behavior that unnamed buffer changes its cwd " if 'autochdir' was set on. lcd . let cwd = getcwd() call l9#tempbuffer#openScratch(s:FUF_BUF_NAME, 'fuf', [], 1, 0, 1, {}) resize 1 " for issue #21 " lcd ... : countermeasure against auto-cd script lcd `=cwd` setlocal nocursorline " for highlighting setlocal nocursorcolumn " for highlighting setlocal omnifunc=fuf#onComplete redraw " for 'lazyredraw' if exists(':AcpLock') AcpLock elseif exists(':AutoComplPopLock') AutoComplPopLock endif endfunction " function s:deactivateFufBuffer() if exists(':AcpUnlock') AcpUnlock elseif exists(':AutoComplPopUnlock') AutoComplPopUnlock endif call l9#tempbuffer#close(s:FUF_BUF_NAME) endfunction " }}}1 "============================================================================= " s:handlerBase {{{1 let s:handlerBase = {} "----------------------------------------------------------------------------- " PURE VIRTUAL FUNCTIONS {{{2 " " " " s:handler.getModeName() " " " " s:handler.getPrompt() " " " " s:handler.getCompleteItems(patternSet) " " " " s:handler.onOpen(word, mode) " " " Before entering FuzzyFinder buffer. This function should return in a short time. " s:handler.onModeEnterPre() " " " After entering FuzzyFinder buffer. " s:handler.onModeEnterPost() " " " After leaving FuzzyFinder buffer. " s:handler.onModeLeavePost(opened) " " }}}2 "----------------------------------------------------------------------------- " function s:handlerBase.concretize(deriv) call extend(self, a:deriv, 'error') return self endfunction " function s:handlerBase.addStat(pattern, word) let stat = { 'pattern' : a:pattern, 'word' : a:word } call filter(self.stats, 'v:val !=# stat') call insert(self.stats, stat) let self.stats = self.stats[0 : g:fuf_learningLimit - 1] endfunction " function s:handlerBase.getMatchingCompleteItems(patternBase) let MakeMatchingExpr = function(self.partialMatching \ ? 's:makePartialMatchingExpr' \ : 's:makeFuzzyMatchingExpr') let patternSet = self.makePatternSet(a:patternBase) let exprBoundary = s:makeFuzzyMatchingExpr('a:wordForBoundary', patternSet.primaryForRank) let stats = filter( \ copy(self.stats), 'v:val.pattern ==# patternSet.primaryForRank') let items = self.getCompleteItems(patternSet.primary) " NOTE: In order to know an excess, plus 1 to limit number let items = l9#filterWithLimit( \ items, patternSet.filteringExpr, g:fuf_enumeratingLimit + 1) return map(items, \ 's:setRanks(v:val, patternSet.primaryForRank, exprBoundary, stats)') endfunction " function s:handlerBase.onComplete(findstart, base) if a:findstart return 0 elseif !self.existsPrompt(a:base) return [] endif call s:highlightPrompt(self.getPrompt()) let items = [] for patternBase in s:expandAbbrevMap(self.removePrompt(a:base), g:fuf_abbrevMap) let items += self.getMatchingCompleteItems(patternBase) if len(items) > g:fuf_enumeratingLimit let items = items[ : g:fuf_enumeratingLimit - 1] call s:highlightError() break endif endfor if empty(items) call s:highlightError() else call sort(items, 'fuf#compareRanks') if g:fuf_autoPreview call feedkeys("\\\=fuf#getRunningHandler().onPreviewBase(0) ? '' : ''\", 'n') else call feedkeys("\\", 'n') endif let self.lastFirstWord = items[0].word endif return items endfunction " function s:handlerBase.existsPrompt(line) return strlen(a:line) >= strlen(self.getPrompt()) && \ a:line[:strlen(self.getPrompt()) -1] ==# self.getPrompt() endfunction " function s:handlerBase.removePrompt(line) return a:line[(self.existsPrompt(a:line) ? strlen(self.getPrompt()) : 0):] endfunction " function s:handlerBase.restorePrompt(line) let i = 0 while i < len(self.getPrompt()) && i < len(a:line) && self.getPrompt()[i] ==# a:line[i] let i += 1 endwhile return self.getPrompt() . a:line[i : ] endfunction " function s:handlerBase.onCursorMovedI() if !self.existsPrompt(getline('.')) call setline('.', self.restorePrompt(getline('.'))) call feedkeys("\", 'n') elseif col('.') <= len(self.getPrompt()) " if the cursor is moved before command prompt call feedkeys(repeat("\", len(self.getPrompt()) - col('.') + 1), 'n') elseif col('.') > strlen(getline('.')) && col('.') != self.lastCol " if the cursor is placed on the end of the line and has been actually moved. let self.lastCol = col('.') let self.lastPattern = self.removePrompt(getline('.')) call feedkeys("\\", 'n') endif endfunction " function s:handlerBase.onInsertLeave() unlet s:runningHandler let tempVars = l9#tempvariables#getList(s:TEMP_VARIABLES_GROUP) call l9#tempvariables#end(s:TEMP_VARIABLES_GROUP) call s:deactivateFufBuffer() call fuf#saveDataFile(self.getModeName(), 'stats', self.stats) execute self.windowRestoringCommand let fOpen = exists('s:reservedCommand') if fOpen call self.onOpen(s:reservedCommand[0], s:reservedCommand[1]) unlet s:reservedCommand endif call self.onModeLeavePost(fOpen) if exists('self.reservedMode') call l9#tempvariables#setList(s:TEMP_VARIABLES_GROUP, tempVars) call fuf#launch(self.reservedMode, self.lastPattern, self.partialMatching) endif endfunction " function s:handlerBase.onCr(openType) if pumvisible() call feedkeys(printf("\\=fuf#getRunningHandler().onCr(%d) ? '' : ''\", \ a:openType), 'n') return endif if !empty(self.lastPattern) call self.addStat(self.lastPattern, self.removePrompt(getline('.'))) endif if !self.isOpenable(getline('.')) " To clear i_ expression (fuf#getRunningHandler().onCr...) echo '' return endif let s:reservedCommand = [self.removePrompt(getline('.')), a:openType] call feedkeys("\", 'n') " stopinsert behavior is strange... endfunction " function s:handlerBase.onBs() call feedkeys((pumvisible() ? "\\" : "\"), 'n') endfunction " function s:getLastBlockLength(pattern, patternIsPath) let separatorPos = strridx(a:pattern, g:fuf_patternSeparator) if separatorPos >= 0 return len(a:pattern) - separatorPos endif if a:patternIsPath && a:pattern =~# '[/\\].' return len(matchstr(a:pattern, '[^/\\]*.$')) endif return len(a:pattern) endfunction " function s:handlerBase.onDeleteWord() let pattern = self.removePrompt(getline('.')[ : col('.') - 2]) let numBs = s:getLastBlockLength(pattern, 1) call feedkeys((pumvisible() ? "\" : "") . repeat("\", numBs), 'n') endfunction " function s:handlerBase.onPreviewBase(repeatable) if self.getPreviewHeight() <= 0 return elseif !pumvisible() return elseif !self.existsPrompt(getline('.')) let word = self.removePrompt(getline('.')) elseif !exists('self.lastFirstWord') return else let word = self.lastFirstWord endif redraw if a:repeatable && exists('self.lastPreviewInfo') && self.lastPreviewInfo.word ==# word let self.lastPreviewInfo.count += 1 else let self.lastPreviewInfo = {'word': word, 'count': 0} endif let lines = self.makePreviewLines(word, self.lastPreviewInfo.count) let lines = lines[: self.getPreviewHeight() - 1] call map(lines, 'substitute(v:val, "\t", repeat(" ", &tabstop), "g")') call map(lines, 'strtrans(v:val)') call map(lines, 'l9#snipTail(v:val, &columns - 1, s:ABBR_SNIP_MASK)') echo join(lines, "\n") endfunction " function s:handlerBase.onSwitchMode(shift) let modes = copy(fuf#getModeNames()) call map(modes, '{ "ranks": [ fuf#{v:val}#getSwitchOrder(), v:val ] }') call filter(modes, 'v:val.ranks[0] >= 0') call sort(modes, 'fuf#compareRanks') let self.reservedMode = self.getModeName() for i in range(len(modes)) if modes[i].ranks[1] ==# self.getModeName() let self.reservedMode = modes[(i + a:shift) % len(modes)].ranks[1] break endif endfor call feedkeys("\", 'n') " stopinsert doesn't work. endfunction " function s:handlerBase.onSwitchMatching() let self.partialMatching = !self.partialMatching let self.lastCol = -1 call setline('.', self.restorePrompt(self.lastPattern)) call feedkeys("\", 'n') "call self.onCursorMovedI() endfunction " function s:handlerBase.onRecallPattern(shift) let patterns = map(copy(self.stats), 'v:val.pattern') if !exists('self.indexRecall') let self.indexRecall = -1 endif let self.indexRecall += a:shift if self.indexRecall < 0 let self.indexRecall = -1 elseif self.indexRecall >= len(patterns) let self.indexRecall = len(patterns) - 1 else call setline('.', self.getPrompt() . patterns[self.indexRecall]) call feedkeys("\", 'n') endif endfunction " }}}1 "============================================================================= " INITIALIZATION {{{1 augroup FufGlobal autocmd! autocmd BufLeave * let s:bufferCursorPosMap[bufnr('')] = getpos('.') augroup END let s:bufferCursorPosMap = {} " let s:DATA_FILE_VERSION = 400 " function s:checkDataFileCompatibility() if empty(g:fuf_dataDir) let s:dataFileAvailable = 0 return endif let versionPath = l9#concatPaths([g:fuf_dataDir, 'VERSION']) let lines = l9#readFile(versionPath) if empty(lines) call l9#writeFile([s:DATA_FILE_VERSION], versionPath) let s:dataFileAvailable = 1 elseif str2nr(lines[0]) == s:DATA_FILE_VERSION let s:dataFileAvailable = 1 else call fuf#echoWarning(printf( \ "=======================================================\n" . \ " Existing data files for FuzzyFinder is no longer \n" . \ " compatible with this version of FuzzyFinder. Remove \n" . \ " %-53s\n" . \ "=======================================================\n" , \ string(g:fuf_dataDir))) call l9#inputHl('Question', 'Press Enter') let s:dataFileAvailable = 0 endif endfunction call s:checkDataFileCompatibility() " }}}1 "============================================================================= " vim: set fdm=marker: