if exists( "g:__XPOPUP_VIM__" ) && g:__XPOPUP_VIM__ >= XPT#ver finish endif let g:__XPOPUP_VIM__ = XPT#ver let s:oldcpo = &cpo set cpo-=< cpo+=B runtime plugin/debug.vim exe XPT#let_sid let s:log = CreateLogger( 'warn' ) let s:log = CreateLogger( 'debug' ) fun! s:SetIfNotExist(k,v) if !exists(a:k) exe "let ".a:k."=".string(a:v) endif endfunction let s:opt = { 'doCallback':'doCallback', 'enlarge':'enlarge', 'acceptEmpty':'acceptEmpty', 'tabNav':'tabNav', } let s:CHECK_PUM = 1 let s:errorTolerance = 3 let s:sessionPrototype = { 'callback':{}, 'list':[], 'key':'', 'prefixIndex':{}, 'popupCount':0, 'sessCount':0, 'errorInputCount':0, 'line':0, 'col':0, 'prefix':'', 'ignoreCase':0, 'acceptEmpty':0, 'matchWholeName':0, 'matchPrefix':0, 'strictInput':0, 'tabNav':0, 'last':'', 'currentText':'', 'longest':'', 'matched':'', 'matchedCallback':'', 'currentList':[], } fun! XPPopupNew(callback,data,...) let sess = deepcopy(s:sessionPrototype) let sess.callback = a:callback let sess.data = a:data call sess.createPrefixIndex([]) if a:0 > 0 let items = a:1 if type( items ) == type( '' ) call sess.SetTriggerKey(items) elseif type(items) == type([]) call sess.addList(items) else call s:log.Error( 'unsupported items type as pum items:' . str( items ) ) endif endif return sess endfunction fun! s:popup(start_col,opt) dict let doCallback = get(a:opt,s:opt.doCallback,1) let ifEnlarge = get(a:opt,s:opt.enlarge,1) let self.popupCount += 1 let cursorIndex = col(".") - 1 - 1 let self.line = line(".") let self.col = a:start_col let self.prefix = s:GetTextBeforeCursor(self) let self.ignoreCase = self.prefix !~# '\u' if self.key != '' let self.longest = self.prefix let actions = self.KeyPopup(doCallback,ifEnlarge) else let self.currentList = s:filterCompleteList(self) if ifEnlarge let self.longest = s:LongestPrefix(self) else let self.longest = self.prefix endif let actions = self.ListPopup(doCallback,ifEnlarge) endif let actions = s:CreateSession(self) . actions call s:ApplyMapAndSetting() return actions endfunction fun PUMclear() return "\\\" endfunction fun! s:CreateSession(sess) if !exists( 'b:__xpp_sess_count' ) let b:__xpp_sess_count = 0 endif let action = '' let b:__xpp_sess_count += 1 let a:sess.sessCount = b:__xpp_sess_count if exists( 'b:__xpp_current_session' ) call s:End() if pumvisible() let action .= PUMclear() endif endif let b:__xpp_current_session = a:sess return action endfunction fun! s:SetAcceptEmpty(acc) dict let self.acceptEmpty = !!a:acc return self endfunction fun! s:SetMatchWholeName(mwn) dict let self.matchWholeName = !!a:mwn return self endfunction fun! s:SetOption(opt) dict if type(a:opt) == type([]) for optname in a:opt let self[optname] = 1 endfor elseif type(a:opt) == type({}) for [key,value] in items(a:opt) let self[key] = value endfor endif endfunction fun! s:KeyPopup(doCallback,ifEnlarge) dict let actionList = [] if a:ifEnlarge let actionList = [ 'clearPum', 'clearPrefix', 'typeLongest', 'triggerKey', 'setLongest' ] if a:doCallback let actionList += [ 'checkAndCallback' ] endif else let actionList = [ 'clearPum', 'clearPrefix', 'typeLongest', 'triggerKey', 'removeTrailing', 'forcePumShow' ] endif return "\=XPPprocess(" . string( actionList ) . ")\" endfunction fun! s:ListPopup(doCallback,ifEnlarge) dict let actionClosePum = '' let actionList = [] if self.longest !=# self.prefix let actionList += ['clearPum', 'clearPrefix', 'clearPum', 'typeLongest' ] endif if 0 else if self.popupCount > 1 && a:ifEnlarge && self.acceptEmpty && self.prefix == '' let self.matched = '' let self.matchedCallback = 'onOneMatch' let actionList = [] let actionList += [ 'clearPum', 'clearPrefix', 'clearPum', 'callback' ] elseif len(self.currentList) == 0 let self.matched = '' let self.matchedCallback = 'onEmpty' let actionList += ['callback'] elseif len(self.currentList) == 1 && a:doCallback if self.matchPrefix let self.matched = type(self.currentList[0]) == type({}) ? self.currentList[0].word : self.currentList[0] let self.matchedCallback = 'onOneMatch' let actionList += ['clearPum', 'clearPrefix', 'clearPum', 'typeMatched', 'callback'] else let actionClosePum = PUMclear() let actionList += [ 'popup', 'fixPopup' ] endif elseif self.prefix != "" && self.longest ==? self.prefix if self.matchPrefix && a:doCallback let self.matched = '' for item in self.currentList let key = type(item) == type({}) ? item.word : item if key ==? self.prefix let self.matched = key let self.matchedCallback = 'onOneMatch' let actionList += ['clearPum', 'clearPrefix', 'clearPum', 'typeLongest', 'callback'] break endif endfor if self.matched == '' let actionClosePum = PUMclear() let actionList += [ 'popup', 'fixPopup' ] endif else let actionClosePum = PUMclear() let actionList += [ 'popup', 'fixPopup' ] endif else let actionClosePum = PUMclear() let actionList += [ 'popup', 'fixPopup' ] endif endif let self.matchPrefix = 1 return actionClosePum . "\=XPPprocess(" . string( actionList ) . ")\" endfunction fun! s:SetTriggerKey(key) dict let self.key = a:key endfunction fun! s:sessionPrototype.addList(list) let list = a:list if list == [] return endif if type( list[0] ) == type( '' ) call map( list, '{"word" : v:val, "icase" : 1 }' ) else call map( list, '{"word" : v:val["word"],' . '"info": get(v:val, "info", ""),' . '"menu": get(v:val, "menu", ""),' . '"icase": 1 }' ) endif let self.list += list call self.updatePrefixIndex(list) endfunction fun! s:sessionPrototype.createPrefixIndex(list) let self.prefixIndex = { 'keys' : {}, 'lowerkeys' : {}, 'ori' : {}, 'lower' : {} } call self.updatePrefixIndex(a:list) endfunction fun! s:sessionPrototype.updatePrefixIndex(list) if g:xptemplate_pum_quick_back == 0 return endif for item in a:list let key = (type(item) == type({})) ?item.word : item if !has_key(self.prefixIndex.keys,key) let self.prefixIndex.keys[key] = 1 call s:UpdateIndex(self.prefixIndex.ori,key) endif let lowerKey = substitute(key, '.', '\l&', 'g') if !has_key(self.prefixIndex.lowerkeys,lowerKey) let self.prefixIndex.lowerkeys[lowerKey] = 1 call s:UpdateIndex(self.prefixIndex.lower,lowerKey) endif endfor endfunction fun! s:_InitBuffer() if exists( 'b:__xpp_buffer_init' ) return endif let b:_xpp_map_saver = xpt#msvr#New(1) call xpt#msvr#AddList(b:_xpp_map_saver, 'i_', 'i_', 'i_', 'i_', 'i_', 'i_', 'i_', 'i_', ) let b:_xpp_setting_switch = xpt#settingswitch#New() let co = {"menu":1, "menuone":1, "longest":1} for k in split(&completeopt, ',') let co[k] = 1 endfor let new_completeopt = join( keys(co), ',' ) call xpt#settingswitch#AddList(b:_xpp_setting_switch, [ '&l:cinkeys', '' ], [ '&l:indentkeys', '' ], [ '&completeopt', new_completeopt ], ) let b:__xpp_buffer_init = 1 endfunction fun! XPPprocess(list) if !exists("b:__xpp_current_session") call s:log.Error("session does not exist!") return "" endif let sess = b:__xpp_current_session if len(a:list) == 0 return "\\" endif let actionName = a:list[0] let nextList = a:list[1 :] let postAction = "" if actionName == 'clearPrefix' let n = col(".") - sess.col let postAction = repeat( "\", n ) elseif actionName == 'clearPum' if pumvisible() let postAction = "\" endif elseif actionName == 'triggerKey' let postAction = sess.key elseif actionName == 'setLongest' let current = s:GetTextBeforeCursor(sess) if len(current) > len(sess.longest) let postAction = repeat( "\", len( current ) - len( sess.longest ) ) . current[len(sess.longest) :] let sess.longest = s:GetTextBeforeCursor(sess) if pumvisible() let nextList = [ 'clearPum', 'clearPrefix', 'typeLongest', 'triggerKey' ] + nextList else let nextList = [ 'clearPrefix', 'clearPum', 'typeLongest' ] + nextList endif endif elseif actionName == 'removeTrailing' let current = s:GetTextBeforeCursor(sess) if len(current) > len(sess.longest) let postAction = repeat( "\", len( current ) - len( sess.longest ) ) endif elseif actionName == 'forcePumShow' let postAction = "\\" elseif actionName == 'checkAndCallback' if pumvisible() return "\\" else let current = s:GetTextBeforeCursor(sess) let sess.matched = current let sess.matchedCallback = 'onOneMatch' call s:End() let postAction = "" if has_key(sess.callback,sess.matchedCallback) let postAction = sess.callback[sess.matchedCallback](sess) return postAction else return '' endif endif elseif actionName == 'keymodeEnlarge' let current = s:GetTextBeforeCursor(sess) if sess.acceptEmpty && current == '' let sess.longest = '' let sess.matched = '' let sess.matchedCallback = 'onOneMatch' let nextList = [ 'callback' ] elseif current !=# sess.currentText let sess.longest = sess.currentText let sess.matched = sess.currentText let sess.matchedCallback = 'onOneMatch' let nextList = [ 'clearPrefix', 'typeLongest', 'callback' ] else return sess.popup(sess.col, { 'doCallback' : 1, 'enlarge':1 } ) endif elseif actionName == 'enlarge' let current = s:GetTextBeforeCursor(sess) if current !=# sess.currentText let sess.longest = sess.currentText let sess.matched = sess.currentText let sess.matchedCallback = 'onOneMatch' let nextList = [ 'clearPrefix', 'typeLongest', 'callback' ] else return sess.popup(sess.col, { 'doCallback' : 1, 'enlarge':1 } ) endif elseif actionName == 'typeMatched' let postAction = sess.matched elseif actionName == 'typeLongest' let postAction = sess.longest elseif actionName == 'type' let postAction = remove(nextList,0) elseif actionName == 'popup' call complete(sess.col,sess.currentList) elseif actionName == 'fixPopup' let current = s:GetTextBeforeCursor(sess) let i = 0 let j = -1 for v in sess.currentList let key = type(v) == type({}) ? v.word : v if key ==# current let j = i break endif let i += 1 endfor if j != -1 let postAction .= repeat( "\", j + 1 ) endif elseif actionName == 'callback' call s:End() let postAction = "" if has_key(sess.callback,sess.matchedCallback) let postAction = sess.callback[sess.matchedCallback](sess) return postAction endif elseif actionName == 'end' call s:End() let postAction = '' else endif if !empty(nextList) let postAction .= "\=XPPprocess(" . string( nextList ) . ")\" else let postAction .= g:xpt_post_action endif return postAction endfunction fun! s:GetTextBeforeCursor(sess) let c = col( "." ) if c == 1 return '' endif return getline(".")[ a:sess.col - 1 : c - 2 ] endfunction fun! XPPcomplete(col,list) let oldcfu = &completefunc set completefunc=XPPcompleteFunc return "\\" endfunction fun! XPPcr() if !s:PopupCheck(s:CHECK_PUM) call feedkeys("\", 'mt') return "" endif return "\=XPPaccept()\" endfunction fun! XPPup(key) if !s:PopupCheck(s:CHECK_PUM) call feedkeys( a:key, 'mt' ) return "" endif return "\" endfunction fun! XPPdown(key) if !s:PopupCheck(s:CHECK_PUM) call feedkeys( a:key, 'mt' ) return "" endif return "\" endfunction fun! XPPcallback() if !exists("b:__xpp_current_session") return "" endif let sess = b:__xpp_current_session call s:End() if has_key(sess.callback,sess.matchedCallback) let post = sess.callback[sess.matchedCallback](sess) else let post = "" endif return post endfunction fun! XPPshorten() if !s:PopupCheck(! s:CHECK_PUM) let s:pos = getpos(".")[ 1 : 2 ] return "\\=XPPcorrectPos()\\" endif if !pumvisible() return "\" endif let sess = b:__xpp_current_session let current = s:GetTextBeforeCursor(sess) if sess.key != '' return "\" endif if current == '' call s:End() return "\" endif let actions = "\" let actions = "" if g:xptemplate_pum_quick_back == 1 let prefixMap = (sess.ignoreCase) ? sess.prefixIndex.lower : sess.prefixIndex.ori let shorterKey = s:FindShorter(prefixMap, ( sess.ignoreCase ? substitute(current, '.', '\l&', 'g') : current )) else let shorterKey = current[0 : -2] endif let action = actions . repeat( "\", len(current) - len(shorterKey) ) . "\=XPPrepopup(0, 'noenlarge')\" return action endfunction fun! XPPenlarge(key) if !s:PopupCheck(s:CHECK_PUM) call feedkeys( a:key, 'm' ) return "" endif return "\=XPPrepopup(1, 'enlarge')\" endfunction fun! XPPcancel(key) if !s:PopupCheck() call feedkeys( a:key, 'mt' ) return "" endif return "\=XPPprocess(" . string( [ 'clearPum', 'clearPrefix', 'typeLongest', 'end' ] ) . ")\" endfunction fun! XPPaccept() if !s:PopupCheck() call feedkeys("\", 'mt') return "" endif let sess = b:__xpp_current_session let beforeCursor = col( "." ) - 2 let beforeCursor = beforeCursor == -1 ? 0 : beforeCursor let toType = getline(sess.line)[sess.col - 1 : beforeCursor] return "\=XPPprocess(" . string( [ 'clearPum', 'clearPrefix', 'type', toType, 'end' ] ) . ")\" endfunction fun! XPPrepopup(doCallback,ifEnlarge) if !exists("b:__xpp_current_session") return "" endif let sess = b:__xpp_current_session if sess.key != '' let sess.currentText = s:GetTextBeforeCursor(sess) let action = "\" . "\=XPPprocess(" . string( [ 'keymodeEnlarge' ] ) . ")\" return action else let action = sess.popup(sess.col, { 'doCallback' : a:doCallback, 'enlarge':a:ifEnlarge == 'enlarge' } ) return action endif endfunction fun! XPPcorrectPos() let p = getpos(".")[1:2] if p != s:pos unlet s:pos return "\" else unlet s:pos return "" endif endfunction fun! s:ApplyMapAndSetting() call s:_InitBuffer() if exists( 'b:__xpp_pushed' ) return endif let b:__xpp_pushed = 1 call xpt#msvr#Save(b:_xpp_map_saver) let sess = b:__xpp_current_session exe 'inoremap ' '=XPPup("\UP>")' exe 'inoremap ' '=XPPdown("\DOWN>")' exe 'inoremap ' '=XPPshorten()' exe 'inoremap ' '=XPPcancel("\C-e>")' if sess.tabNav exe 'inoremap ' '=XPPup("\S-Tab>")' exe 'inoremap ' '=XPPdown("\TAB>")' exe 'inoremap ' '=XPPenlarge("\CR>")' exe 'inoremap ' '=XPPenlarge("\C-y>")' else exe 'inoremap ' '=XPPenlarge("\TAB>")' exe 'inoremap ' '=XPPenlarge("\CR>")' exe 'inoremap ' '=XPPenlarge("\C-y>")' endif augroup XPpopup au! au CursorMovedI * call s:CheckAndFinish() au InsertEnter * call XPPend() augroup END call xpt#settingswitch#Switch(b:_xpp_setting_switch) if exists( ':AcpLock' ) AcpLock endif endfunction fun! s:ClearMapAndSetting() call s:_InitBuffer() if !exists( 'b:__xpp_pushed' ) return endif unlet b:__xpp_pushed augroup XPpopup au! augroup END call xpt#msvr#Restore(b:_xpp_map_saver) call xpt#settingswitch#Restore(b:_xpp_setting_switch) if exists( ':AcpUnlock' ) try AcpUnlock catch /.*/ endtry endif endfunction fun! s:CheckAndFinish() if !exists( 'b:__xpp_current_session' ) call s:End() return '' endif let sess = b:__xpp_current_session if !pumvisible() if line( "." ) == sess.line if sess.strictInput if col(".") > sess.col call feedkeys( "\", 'n' ) endif else return s:MistakeTypeEnd() endif else return s:MistakeTypeEnd() endif endif return '' endfunction fun! s:MistakeTypeEnd() call s:End() return PUMclear() endfunction fun! XPPhasSession() return exists("b:__xpp_current_session") endfunction fun! XPPend() call s:End() if pumvisible() return PUMclear() endif return '' endfunction fun! s:End() call s:ClearMapAndSetting() if exists("b:__xpp_current_session") unlet b:__xpp_current_session endif endfunction fun! s:PopupCheck(...) let checkPum = (a:0 == 0 || a:1) if !exists("b:__xpp_current_session") call s:End() return 0 endif let sess = b:__xpp_current_session if sess.line != line(".") || col(".") < sess.col || (checkPum && !pumvisible()) call s:End() return 0 endif return 1 endfunction fun! s:UpdateIndex(map,key) let [i,len] = [0,len(a:key)] while i < len let prefix = a:key[0 : i - 1] if !has_key(a:map,prefix) let a:map[prefix] = 1 else let a:map[prefix] += 1 endif let i += 1 endwhile endfunction fun! s:LongestPrefix(sess) let longest = ".*" for e in a:sess.currentList let key = (type(e) == type({})) ? e.word : e if longest == ".*" let longest = a:sess.ignoreCase ? substitute(key, '.', '\l&', 'g') : key else while key !~ '^\V' . ( a:sess.ignoreCase ? '\c' : '\C' ) . escape(longest, '\') && len(longest) > 0 let longest = longest[ : -2 ] " remove one char endwhile endif endfor let longest = ( longest == '.*' ) ? '' : longest if a:sess.prefix !=# longest[: len(a:sess.prefix) - 1] let longest = a:sess.prefix . longest[len(a:sess.prefix) :] endif return longest endfunction fun! s:filterCompleteList(sess) let list = [] let caseOption = a:sess.ignoreCase ? '\c' : '\C' if a:sess.matchWholeName let pattern = '\V\^' . caseOption . a:sess.prefix . '\$' else let pattern = '\V\^' . caseOption . a:sess.prefix endif for item in a:sess.list let key = (type(item) == type({})) ? item.word : item if key =~ pattern let list += [item] endif endfor return list endfunction fun! s:FindShorter(map,key) let key = a:key if len(key) == 1 return '' endif let nmatch = has_key(a:map,key) ? a:map[key] : 1 if !has_key(a:map,key[: -2]) return key[: -2] endif let key = key[: -2] while key != '' && a:map[key] == nmatch let key = key[: -2] endwhile return key endfunction fun! s:ClassPrototype(...) let p = {} for name in a:000 let p[ name ] = function( '' . s:sid . name ) endfor return p endfunction let s:sessionPrototype2 = s:ClassPrototype( 'popup', 'SetAcceptEmpty', 'SetMatchWholeName', 'SetTriggerKey', 'SetOption', 'KeyPopup', 'ListPopup', ) call extend( s:sessionPrototype, s:sessionPrototype2, 'force' ) let &cpo = s:oldcpo