Versioning the rest of my vimfiles directory.
[vimrc.git] / autoload / lookupfile.vim
blob0f6332cc194d5df1c10bdce117c36f9e2512bf49
1 " lookupfile.vim: See plugin/lookupfile.vim
3 " Make sure line-continuations won't cause any problem. This will be restored
4 "   at the end
5 let s:save_cpo = &cpo
6 set cpo&vim
8 " Some onetime initialization of variables
9 if !exists('s:myBufNum')
10   let s:windowName = '[Lookup File]'
11   let s:myBufNum = -1
12   let s:popupIsHidden = 0
13 endif
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()
21   let _isf = &isfname
22   let _splitbelow = &splitbelow
23   set nosplitbelow
24   try
25     if s:myBufNum == -1
26       " Temporarily modify isfname to avoid treating the name as a pattern.
27       set isfname-=\
28       set isfname-=[
29       if exists('+shellslash')
30         call genutils#OpenWinNoEa("1sp \\\\". escape(s:windowName, ' '))
31       else
32         call genutils#OpenWinNoEa("1sp \\". escape(s:windowName, ' '))
33       endif
34       let s:myBufNum = bufnr('%')
35     else
36       let winnr = bufwinnr(s:myBufNum)
37       if winnr == -1
38         call genutils#OpenWinNoEa('1sb '. s:myBufNum)
39       else
40         let wasVisible = 1
41         exec winnr 'wincmd w'
42       endif
43     endif
44   finally
45     let &isfname = _isf
46     let &splitbelow = _splitbelow
47   endtry
49   call s:SetupBuf()
50   let initPat = ''
51   if a:bang != ''
52     let initPat = ''
53   elseif a:initPat != ''
54     let initPat = a:initPat
55   elseif g:lookupfile#lastPattern != '' && g:LookupFile_PreserveLastPattern
56     let initPat = g:lookupfile#lastPattern
57   endif
58   $
59   if getline('.') !=# initPat
60     silent! put=''
61     call setline('.', initPat)
62   endif
63   startinsert!
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
68       au!
69       au CursorMovedI <buffer> nested exec 'doautocmd LookupFile CursorHoldI' |
70             \ au! LookupFileCursorHoldImm
71     aug END
72   endif
73   call s:LookupFileSet()
74   aug LookupFileReset
75     au!
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)
85   aug END
86 endfunction
88 function! lookupfile#CloseWindow()
89   if bufnr('%') != s:myBufNum
90     return
91   endif
93   call s:LookupFileReset(1)
94   close
95 endfunction
97 function! lookupfile#ClearCache()
98   let g:lookupfile#lastPattern = ""
99   let g:lookupfile#lastResults = []
100 endfunction
102 function! s:LookupFileSet()
103   if bufnr('%') != s:myBufNum || exists('s:_backspace')
104     return
105   endif
106   let s:_backspace = &backspace
107   set backspace=start
108   let s:_completeopt = &completeopt
109   set completeopt+=menuone
110   let s:_updatetime = &updatetime
111   let &updatetime = g:LookupFile_UpdateTime
112 endfunction
114 function! s:LookupFileReset(force)
115   if a:force
116     aug LookupFileReset
117       au!
118     aug END
119   endif
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
126   endif
127 endfunction
129 function! s:HidePopup()
130   let s:popupIsHidden = 1
131   return "\<C-E>"
132 endfunction
134 function! lookupfile#IsPopupHidden()
135   return s:popupIsHidden
136 endfunction
138 function! s:SetupBuf()
139   call genutils#SetupScratchBuffer()
140   resize 1
141   setlocal wrap
142   setlocal bufhidden=hide
143   setlocal winfixheight
144   setlocal wrapmargin=0
145   setlocal textwidth=0
146   setlocal completefunc=lookupfile#Complete
147   syn clear
148   set ft=lookupfile
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>"
161   endif
162   inoremap <buffer> <expr> <silent> <Down> <SID>GetCommand(1, 1, "\<C-N>",
163         \ "\"\\<Lt>C-N>\"")
164   inoremap <buffer> <expr> <silent> <Up> <SID>GetCommand(1, 1, "\<C-P>",
165         \ "\"\\<Lt>C-P>\"")
166   inoremap <buffer> <expr> <silent> <PageDown> <SID>GetCommand(1, 0,
167         \ "\<PageDown>", '')
168   inoremap <buffer> <expr> <silent> <PageUp> <SID>GetCommand(1, 0,
169         \ "\<PageUp>", '')
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>
178   aug LookupFile
179     au!
180     if g:LookupFile_ShowFiller
181       exec 'au' (g:LookupFile_OnCursorMovedI ? 'CursorMovedI' : 'CursorHoldI')
182             \ '<buffer> call <SID>ShowFiller()'
183     endif
184     exec 'au' (g:LookupFile_OnCursorMovedI ? 'CursorMovedI' : 'CursorHoldI')
185           \ '<buffer> call lookupfile#LookupFile(0)'
186   aug END
187 endfunction
189 function! s:GetCommand(withPopupTrigger, withSkipPat, actCmd, innerCmd)
190   let cmd = ''
191   if a:withPopupTrigger && !pumvisible()
192     let cmd .= "\<C-X>\<C-U>"
193   endif
194   let cmd .= a:actCmd. "\<C-R>=(getline('.') == lookupfile#lastPattern) ? ".
195         \ a:innerCmd." : ''\<CR>"
196   return cmd
197 endfunction
199 function! s:AddPattern()
200   if g:LookupFile_PreservePatternHistory
201     silent! put! =g:lookupfile#lastPattern
202     $
203   endif
204 endfunction
206 function! s:AcceptFile(splitWin, key)
207   if s:popupIsHidden
208     return a:key
209   endif
210   if !pumvisible()
211     call lookupfile#LookupFile(0, 1)
212   endif
213   let acceptCmd = ''
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])
218   else
219     let acceptCmd = lookupfile#AcceptFile(a:splitWin, a:key)
220   endif
222   return (!pumvisible() ? "\<C-X>\<C-U>" : '').acceptCmd
223 endfunction
225 function! s:IsValid(fileName)
226   if bufnr('%') != s:myBufNum || a:fileName == ''
227     return 0
228   endif
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
232       " (Ido feature)
233       let parent = fnamemodify(a:fileName, ':h')
234       if parent == '' || (parent != '' && !isdirectory(parent))
235         return 1
236       endif
237     endif
238     return 0
239   endif
240   return 1
241 endfunction
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>"
246   endif
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?'!':'').
251         \ "\<CR>"
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
258       " selected)
259       let acceptCmd = nextCmd.acceptCmd
260     else
261       let acceptCmd = nextCmd
262     endif
263   endif
265   return acceptCmd
266 endfunction
268 function! s:OpenCurFile(splitWin)
269   let fileName = getline('.')
270   if fileName =~ '^\s*$'
271     return
272   endif
273   if !s:IsValid(fileName)
274     echohl ErrorMsg | echo 'No such file or directory' | echohl NONE
275   endif
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, [])
281   endif
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)
288     if curPos != -1
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 :]
293     endif
294   endif
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.
302           exec 'tabnext' (i+1)
303           let winnr = bufwinnr(bufnr)
304         endif
305     endfor
306   endif
307   if winnr != -1
308     exec winnr.'wincmd w'
309   else
310     let splitOpen = 0
311     if &switchbuf ==# 'split' || a:splitWin
312       let splitOpen = 1
313     endif
314     " First try opening as a buffer, if it fails, we will open as a file.
315     try
316       if bufnr == -1
317         throw ''
318       endif
319       exec (splitOpen?'s':'').'buffer' bufnr
320     catch /^Vim\%((\a\+)\)\=:E325/
321       " Ignore, this anyway means the file was found.
322     catch
323       try
324         exec (splitOpen?'split':'edit') fileName
325       catch /^Vim\%((\a\+)\)\=:E325/
326         " Ignore, this anyway means the file was found.
327       endtry
328     endtry
329   endif
330 endfunction
332 function! s:ShowFiller()
333   return lookupfile#LookupFile(1)
334 endfunction
336 function! lookupfile#Complete(findstart, base)
337   if a:findstart
338     return 0
339   else
340     call lookupfile#LookupFile(0, 1, a:base)
341     return g:lookupfile#lastStatsMsg+g:lookupfile#lastResults
342   endif
343 endfunction
345 function! lookupfile#LookupFile(showingFiller, ...)
346   let generateMode = (a:0 == 0 ? 0 : a:1)
347   if generateMode
348     let pattern = (a:0 > 1) ? a:2 : getline('.')
349   else
350     let pattern = getline('.')
351     " The normal completion behavior is to stop completion when cursor is moved.
352     if col('.') == 1 || (col('.') != col('$'))
353       return ''
354     endif
355   endif
356   if pattern == '' || (pattern ==# g:lookupfile#lastPattern && pumvisible())
357     return ''
358   endif
360   if s:popupIsHidden && g:lookupfile#lastPattern ==# pattern
361     return ''
362   endif
363   let s:popupIsHidden = 0
365   let statusMsg = ''
366   if pattern == ' '
367     if len(g:lookupfile#recentFiles) == 0
368       let statusMsg = '<<< No recent files >>>'
369       let files = []
370     else
371       let statusMsg = '<<< Showing '.len(g:lookupfile#recentFiles).' recent files >>>'
372       let files = reverse(copy(g:lookupfile#recentFiles))
373     endif
374   elseif strlen(pattern) < g:LookupFile_MinPatLength
375     let statusMsg = '<<< Type at least '.g:LookupFile_MinPatLength.
376           \ ' characters >>>'
377     let files = []
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 >>>'
386     let files = []
387   else
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])
392     else
393       let _tags = &tags
394       try
395         let &tags = eval(g:LookupFile_TagExpr)
396         let taglist = taglist(g:LookupFile_TagsExpandCamelCase ?
397               \ lookupfile#ExpandCamelCase(pattern) : pattern)
398       catch
399         echohl ErrorMsg | echo "Exception: " . v:exception | echohl NONE
400         return ''
401       finally
402         let &tags = _tags
403       endtry
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"), '.
411               \ '"dup": 1, '.
412               \ '}')
413       else
414         let files = map(taglist, 'fnamemodify(v:val["filename"], ":p")')
415       endif
416     endif
418     let pat = g:LookupFile_FileFilter
419     if pat != ''
420       call filter(files, '(type(v:val) == 4) ? v:val["word"] !~ pat : v:val !~ pat')
421     endif
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")
428       else
429         call sort(files)
430       endif
431     endif
432     let g:lookupfile#lastPattern = pattern
433     let g:lookupfile#lastResults = files
434   endif
435   if statusMsg == ''
436     if len(files) > 0
437       let statusMsg = '<<< '.len(files).' Matching >>>'
438     else
439       let statusMsg = '<<< None Matching >>>'
440     endif
441   endif
442   let msgLine = [{'word': pattern, 'abbr': statusMsg, 'menu': pattern}]
443   let g:lookupfile#lastStatsMsg = msgLine
444   if !generateMode
445     call complete(1, msgLine+files)
446   endif
447   return ''
448 endfunction
450 function! lookupfile#ExpandCamelCase(str)
451   let pat = a: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')
457     let @*=pat
458   endif
459   return pat
460 endfunction
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
466 endfunc
468 " Restore cpo.
469 let &cpo = s:save_cpo
470 unlet s:save_cpo
472 " vim6:fdm=marker et sw=2