1 " lookupfile.vim: See plugin/lookupfile.vim
3 " Make sure line-continuations won't cause any problem. This will be restored
8 " Some onetime initialization of variables
9 if !exists('s:myBufNum')
10 let s:windowName = '[Lookup File]'
12 let s:popupIsHidden = 0
14 let g:lookupfile#lastPattern = ""
15 let g:lookupfile#lastResults = []
16 let g:lookupfile#lastStatsMsg = []
17 let g:lookupfile#recentFiles = []
19 function! lookupfile#OpenWindow(bang, initPat)
20 let origWinnr = winnr()
22 let _splitbelow = &splitbelow
26 " Temporarily modify isfname to avoid treating the name as a pattern.
29 if exists('+shellslash')
30 call genutils#OpenWinNoEa("1sp \\\\". escape(s:windowName, ' '))
32 call genutils#OpenWinNoEa("1sp \\". escape(s:windowName, ' '))
34 let s:myBufNum = bufnr('%')
36 let winnr = bufwinnr(s:myBufNum)
38 call genutils#OpenWinNoEa('1sb '. s:myBufNum)
46 let &splitbelow = _splitbelow
53 elseif a:initPat != ''
54 let initPat = a:initPat
55 elseif g:lookupfile#lastPattern != '' && g:LookupFile_PreserveLastPattern
56 let initPat = g:lookupfile#lastPattern
59 if getline('.') !=# initPat
61 call setline('.', initPat)
64 if !g:LookupFile_OnCursorMovedI
65 " This is a hack to bring up the popup immediately, while reopening the
66 " window, just for a better response.
67 aug LookupFileCursorHoldImm
69 au CursorMovedI <buffer> nested exec 'doautocmd LookupFile CursorHoldI' |
70 \ au! LookupFileCursorHoldImm
73 call s:LookupFileSet()
76 au CursorMovedI <buffer> call <SID>LookupFileSet()
77 au CursorMoved <buffer> call <SID>LookupFileSet()
78 au WinEnter <buffer> call <SID>LookupFileSet()
79 au TabEnter <buffer> call <SID>LookupFileSet()
80 au WinEnter * call <SID>LookupFileReset(0)
81 au TabEnter * call <SID>LookupFileReset(0)
82 au CursorMoved * call <SID>LookupFileReset(0)
83 " Better be safe than sorry.
84 au BufHidden <buffer> call <SID>LookupFileReset(1)
88 function! lookupfile#CloseWindow()
89 if bufnr('%') != s:myBufNum
93 call s:LookupFileReset(1)
97 function! lookupfile#ClearCache()
98 let g:lookupfile#lastPattern = ""
99 let g:lookupfile#lastResults = []
102 function! s:LookupFileSet()
103 if bufnr('%') != s:myBufNum || exists('s:_backspace')
106 let s:_backspace = &backspace
108 let s:_completeopt = &completeopt
109 set completeopt+=menuone
110 let s:_updatetime = &updatetime
111 let &updatetime = g:LookupFile_UpdateTime
114 function! s:LookupFileReset(force)
120 " Ignore the event while in the same buffer.
121 if exists('s:_backspace') && (a:force || (bufnr('%') != s:myBufNum))
122 let &backspace = s:_backspace
123 let &completeopt = s:_completeopt
124 let &updatetime = s:_updatetime
125 unlet s:_backspace s:_completeopt s:_updatetime
129 function! s:HidePopup()
130 let s:popupIsHidden = 1
134 function! lookupfile#IsPopupHidden()
135 return s:popupIsHidden
138 function! s:SetupBuf()
139 call genutils#SetupScratchBuffer()
142 setlocal bufhidden=hide
143 setlocal winfixheight
144 setlocal wrapmargin=0
146 setlocal completefunc=lookupfile#Complete
149 " Setup maps to open the file.
150 inoremap <silent> <buffer> <expr> <C-E> <SID>HidePopup()
151 inoremap <silent> <buffer> <expr> <CR> <SID>AcceptFile(0, "\<CR>")
152 inoremap <silent> <buffer> <expr> <C-O> <SID>AcceptFile(1, "\<C-O>")
153 " This prevents the "Whole line completion" from getting triggered with <BS>,
154 " however this might make the dropdown kind of flash.
155 inoremap <buffer> <expr> <BS> pumvisible()?"\<C-E>\<BS>":"\<BS>"
156 inoremap <buffer> <expr> <S-BS> pumvisible()?"\<C-E>\<BS>":"\<BS>"
157 " Make <C-Y> behave just like <CR>
158 imap <buffer> <C-Y> <CR>
159 if g:LookupFile_EscCancelsPopup
160 inoremap <buffer> <expr> <Esc> pumvisible()?"\<C-E>\<C-C>":"\<Esc>"
162 inoremap <buffer> <expr> <silent> <Down> <SID>GetCommand(1, 1, "\<C-N>",
164 inoremap <buffer> <expr> <silent> <Up> <SID>GetCommand(1, 1, "\<C-P>",
166 inoremap <buffer> <expr> <silent> <PageDown> <SID>GetCommand(1, 0,
168 inoremap <buffer> <expr> <silent> <PageUp> <SID>GetCommand(1, 0,
170 nnoremap <silent> <buffer> o :OpenFile<CR>
171 nnoremap <silent> <buffer> O :OpenFile!<CR>
172 command! -buffer -nargs=0 -bang OpenFile
173 \ :call <SID>OpenCurFile('<bang>' != '')
174 command! -buffer -nargs=0 -bang AddPattern :call <SID>AddPattern()
175 nnoremap <buffer> <silent> <Plug>LookupFile :call lookupfile#CloseWindow()<CR>
176 inoremap <buffer> <silent> <Plug>LookupFile <C-E><C-C>:call lookupfile#CloseWindow()<CR>
180 if g:LookupFile_ShowFiller
181 exec 'au' (g:LookupFile_OnCursorMovedI ? 'CursorMovedI' : 'CursorHoldI')
182 \ '<buffer> call <SID>ShowFiller()'
184 exec 'au' (g:LookupFile_OnCursorMovedI ? 'CursorMovedI' : 'CursorHoldI')
185 \ '<buffer> call lookupfile#LookupFile(0)'
189 function! s:GetCommand(withPopupTrigger, withSkipPat, actCmd, innerCmd)
191 if a:withPopupTrigger && !pumvisible()
192 let cmd .= "\<C-X>\<C-U>"
194 let cmd .= a:actCmd. "\<C-R>=(getline('.') == lookupfile#lastPattern) ? ".
195 \ a:innerCmd." : ''\<CR>"
199 function! s:AddPattern()
200 if g:LookupFile_PreservePatternHistory
201 silent! put! =g:lookupfile#lastPattern
206 function! s:AcceptFile(splitWin, key)
211 call lookupfile#LookupFile(0, 1)
214 if type(g:LookupFile_LookupAcceptFunc) == 2 ||
215 \ (type(g:LookupFile_LookupAcceptFunc) == 1 &&
216 \ substitute(g:LookupFile_LookupAcceptFunc, '\s', '', 'g') != '')
217 let acceptCmd = call(g:LookupFile_LookupAcceptFunc, [a:splitWin, a:key])
219 let acceptCmd = lookupfile#AcceptFile(a:splitWin, a:key)
222 return (!pumvisible() ? "\<C-X>\<C-U>" : '').acceptCmd
225 function! s:IsValid(fileName)
226 if bufnr('%') != s:myBufNum || a:fileName == ''
229 if !filereadable(a:fileName) && !isdirectory(a:fileName)
230 if g:LookupFile_AllowNewFiles
231 " Check if the parent directory exists, then we can create a new buffer
233 let parent = fnamemodify(a:fileName, ':h')
234 if parent == '' || (parent != '' && !isdirectory(parent))
243 function! lookupfile#AcceptFile(splitWin, key)
244 if len(g:lookupfile#lastResults) == 0 && !s:IsValid(getline('.'))
245 return "\<C-O>:echohl ErrorMsg | echo 'No such file or directory' | echohl NONE\<CR>"
248 " Skip the first match, which is essentially the same as pattern.
249 let nextCmd = "\<C-N>\<C-R>=(getline('.') == lookupfile#lastPattern)?\"\\<C-N>\":''\<CR>"
250 let acceptCmd = "\<C-Y>\<Esc>:AddPattern\<CR>:OpenFile".(a:splitWin?'!':'').
252 if getline('.') ==# g:lookupfile#lastPattern
253 if len(g:lookupfile#lastResults) == 0
254 " FIXME: shouldn't this be an error?
255 let acceptCmd = acceptCmd
256 elseif len(g:lookupfile#lastResults) == 1 || g:LookupFile_AlwaysAcceptFirst
257 " If there is only one file, we will also select it (if not already
259 let acceptCmd = nextCmd.acceptCmd
261 let acceptCmd = nextCmd
268 function! s:OpenCurFile(splitWin)
269 let fileName = getline('.')
270 if fileName =~ '^\s*$'
273 if !s:IsValid(fileName)
274 echohl ErrorMsg | echo 'No such file or directory' | echohl NONE
277 if type(g:LookupFile_LookupNotifyFunc) == 2 ||
278 \ (type(g:LookupFile_LookupNotifyFunc) == 1 &&
279 \ substitute(g:LookupFile_LookupNotifyFunc, '\s', '', 'g') != '')
280 call call(g:LookupFile_LookupNotifyFunc, [])
282 call lookupfile#CloseWindow()
284 " Update the recent files list.
285 if g:LookupFile_RecentFileListSize > 0
286 let curPos = index(g:lookupfile#recentFiles, fileName)
287 call add(g:lookupfile#recentFiles, fileName)
289 call remove(g:lookupfile#recentFiles, curPos)
290 elseif len(g:lookupfile#recentFiles) > g:LookupFile_RecentFileListSize
291 let g:lookupfile#recentFiles = g:lookupfile#recentFiles[
292 \ -g:LookupFile_RecentFileListSize :]
296 let bufnr = genutils#FindBufferForName(fileName)
297 let winnr = bufwinnr(bufnr)
298 if winnr == -1 && g:LookupFile_SearchForBufsInTabs
299 for i in range(tabpagenr('$'))
300 if index(tabpagebuflist(i+1), bufnr) != -1
301 " Switch to the tab and set winnr.
303 let winnr = bufwinnr(bufnr)
308 exec winnr.'wincmd w'
311 if &switchbuf ==# 'split' || a:splitWin
314 " First try opening as a buffer, if it fails, we will open as a file.
319 exec (splitOpen?'s':'').'buffer' bufnr
320 catch /^Vim\%((\a\+)\)\=:E325/
321 " Ignore, this anyway means the file was found.
324 exec (splitOpen?'split':'edit') fileName
325 catch /^Vim\%((\a\+)\)\=:E325/
326 " Ignore, this anyway means the file was found.
332 function! s:ShowFiller()
333 return lookupfile#LookupFile(1)
336 function! lookupfile#Complete(findstart, base)
340 call lookupfile#LookupFile(0, 1, a:base)
341 return g:lookupfile#lastStatsMsg+g:lookupfile#lastResults
345 function! lookupfile#LookupFile(showingFiller, ...)
346 let generateMode = (a:0 == 0 ? 0 : a:1)
348 let pattern = (a:0 > 1) ? a:2 : getline('.')
350 let pattern = getline('.')
351 " The normal completion behavior is to stop completion when cursor is moved.
352 if col('.') == 1 || (col('.') != col('$'))
356 if pattern == '' || (pattern ==# g:lookupfile#lastPattern && pumvisible())
360 if s:popupIsHidden && g:lookupfile#lastPattern ==# pattern
363 let s:popupIsHidden = 0
367 if len(g:lookupfile#recentFiles) == 0
368 let statusMsg = '<<< No recent files >>>'
371 let statusMsg = '<<< Showing '.len(g:lookupfile#recentFiles).' recent files >>>'
372 let files = reverse(copy(g:lookupfile#recentFiles))
374 elseif strlen(pattern) < g:LookupFile_MinPatLength
375 let statusMsg = '<<< Type at least '.g:LookupFile_MinPatLength.
378 " We ignore filler when we have the result in hand.
379 elseif g:lookupfile#lastPattern ==# pattern
380 " This helps at every startup as we start with the previous pattern.
381 let files = g:lookupfile#lastResults
382 elseif a:showingFiller
383 " Just show a filler and return. We could return this as the only match, but
384 " unless 'completeopt' has "menuone", menu doesn't get shown.
385 let statusMsg = '<<< Looking up files... hit ^C to break >>>'
388 if type(g:LookupFile_LookupFunc) == 2 ||
389 \ (type(g:LookupFile_LookupFunc) == 1 &&
390 \ substitute(g:LookupFile_LookupFunc, '\s', '', 'g') != '')
391 let files = call(g:LookupFile_LookupFunc, [pattern])
395 let &tags = eval(g:LookupFile_TagExpr)
396 let taglist = taglist(g:LookupFile_TagsExpandCamelCase ?
397 \ lookupfile#ExpandCamelCase(pattern) : pattern)
399 echohl ErrorMsg | echo "Exception: " . v:exception | echohl NONE
405 " Show the matches for what is typed so far.
406 if g:LookupFile_UsingSpecializedTags
407 let files = map(taglist, '{'.
408 \ '"word": fnamemodify(v:val["filename"], ":p"), '.
409 \ '"abbr": v:val["name"], '.
410 \ '"menu": fnamemodify(v:val["filename"], ":h"), '.
414 let files = map(taglist, 'fnamemodify(v:val["filename"], ":p")')
418 let pat = g:LookupFile_FileFilter
420 call filter(files, '(type(v:val) == 4) ? v:val["word"] !~ pat : v:val !~ pat')
423 if g:LookupFile_SortMethod ==# 'alpha'
424 " When UsingSpecializedTags, sort by the actual name (Timothy, Guo
425 " (firemeteor dot guo at gmail dot com)).
426 if type(get(files, 0)) == 4
427 call sort(files, "s:CmpByName")
432 let g:lookupfile#lastPattern = pattern
433 let g:lookupfile#lastResults = files
437 let statusMsg = '<<< '.len(files).' Matching >>>'
439 let statusMsg = '<<< None Matching >>>'
442 let msgLine = [{'word': pattern, 'abbr': statusMsg, 'menu': pattern}]
443 let g:lookupfile#lastStatsMsg = msgLine
445 call complete(1, msgLine+files)
450 function! lookupfile#ExpandCamelCase(str)
452 " Check if there are at least two consecutive uppercase letters to turn on
453 " the CamelCase expansion.
454 if match(a:str, '\u\u') != -1
455 let pat = '\C'.substitute(a:str, '\u\+',
456 \ '\=substitute(submatch(0), ".", '."'".'&\\U*'."'".', "g")', 'g')
462 function! s:CmpByName(i1, i2)
463 let ileft = a:i1["abbr"]
464 let iright = a:i2["abbr"]
465 return ileft == iright ? 0 : ileft > iright ? 1 : -1
469 let &cpo = s:save_cpo
472 " vim6:fdm=marker et sw=2