diff options
| author | Ben Beltran <ben@freshout.us> | 2012-10-08 11:44:10 -0500 |
|---|---|---|
| committer | Ben Beltran <ben@freshout.us> | 2012-10-08 11:44:10 -0500 |
| commit | 0d23b6e515a01a5782532351821ebfa11f3d6cf2 (patch) | |
| tree | aa395b7e50ccb533d6b48b809016ac06f2d5bc8c /vim/autoload | |
| parent | a91731eac872b7837c2821341db5888702125cef (diff) | |
Add vim again :)
Diffstat (limited to 'vim/autoload')
| -rw-r--r-- | vim/autoload/.DS_Store | bin | 0 -> 6148 bytes | |||
| -rw-r--r-- | vim/autoload/Align.vim | 1029 | ||||
| -rw-r--r-- | vim/autoload/AlignMaps.vim | 330 | ||||
| -rwxr-xr-x | vim/autoload/EasyMotion.vim | 573 | ||||
| -rw-r--r-- | vim/autoload/ZoomWin.vim | 380 | ||||
| -rw-r--r-- | vim/autoload/conque_term.vim | 1590 | ||||
| -rw-r--r-- | vim/autoload/delimitMate.vim | 586 | ||||
| -rw-r--r-- | vim/autoload/pathogen.vim | 250 | ||||
| -rw-r--r-- | vim/autoload/rails.vim | 4560 | ||||
| -rw-r--r-- | vim/autoload/snipMate.vim | 435 | ||||
| -rw-r--r-- | vim/autoload/syntastic.vim | 24 | ||||
| -rw-r--r-- | vim/autoload/syntastic/c.vim | 171 | ||||
| -rw-r--r-- | vim/autoload/tagbar.vim | 3099 | ||||
| -rw-r--r-- | vim/autoload/togglebg.vim | 55 |
14 files changed, 13082 insertions, 0 deletions
diff --git a/vim/autoload/.DS_Store b/vim/autoload/.DS_Store Binary files differnew file mode 100644 index 0000000..5008ddf --- /dev/null +++ b/vim/autoload/.DS_Store diff --git a/vim/autoload/Align.vim b/vim/autoload/Align.vim new file mode 100644 index 0000000..bce3542 --- /dev/null +++ b/vim/autoload/Align.vim @@ -0,0 +1,1029 @@ +" Align: tool to align multiple fields based on one or more separators +" Author: Charles E. Campbell, Jr. +" Date: Mar 03, 2009 +" Version: 35 +" GetLatestVimScripts: 294 1 :AutoInstall: Align.vim +" GetLatestVimScripts: 1066 1 :AutoInstall: cecutil.vim +" Copyright: Copyright (C) 1999-2007 Charles E. Campbell, Jr. {{{1 +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" Align.vim is provided *as is* and comes with no warranty +" of any kind, either expressed or implied. By using this +" plugin, you agree that in no event will the copyright +" holder be liable for any damages resulting from the use +" of this software. +" +" Romans 1:16,17a : For I am not ashamed of the gospel of Christ, for it is {{{1 +" the power of God for salvation for everyone who believes; for the Jew first, +" and also for the Greek. For in it is revealed God's righteousness from +" faith to faith. + +" --------------------------------------------------------------------- +" Load Once: {{{1 +if exists("g:loaded_Align") || &cp + finish +endif +let g:loaded_Align = "v35" +if v:version < 700 + echohl WarningMsg + echo "***warning*** this version of Align needs vim 7.0" + echohl Normal + finish +endif +let s:keepcpo= &cpo +set cpo&vim +"DechoTabOn + +" --------------------------------------------------------------------- +" Debugging Support: {{{1 +"if !exists("g:loaded_Decho") | runtime plugin/Decho.vim | endif + +" --------------------------------------------------------------------- +" Options: {{{1 +if !exists("g:Align_xstrlen") + if &enc == "latin1" || $LANG == "en_US.UTF-8" || !has("multi_byte") + let g:Align_xstrlen= 0 + else + let g:Align_xstrlen= 1 + endif +endif + +" --------------------------------------------------------------------- +" Align#AlignCtrl: enter alignment patterns here {{{1 +" +" Styles = all alignment-break patterns are equivalent +" C cycle through alignment-break pattern(s) +" l left-justified alignment +" r right-justified alignment +" c center alignment +" - skip separator, treat as part of field +" : treat rest of line as field +" + repeat previous [lrc] style +" < left justify separators +" > right justify separators +" | center separators +" +" Builds = s:AlignPat s:AlignCtrl s:AlignPatQty +" C s:AlignPat s:AlignCtrl s:AlignPatQty +" p s:AlignPrePad +" P s:AlignPostPad +" w s:AlignLeadKeep +" W s:AlignLeadKeep +" I s:AlignLeadKeep +" l s:AlignStyle +" r s:AlignStyle +" - s:AlignStyle +" + s:AlignStyle +" : s:AlignStyle +" c s:AlignStyle +" g s:AlignGPat +" v s:AlignVPat +" < s:AlignSep +" > s:AlignSep +" | s:AlignSep +fun! Align#AlignCtrl(...) + +" call Dfunc("AlignCtrl(...) a:0=".a:0) + + " save options that will be changed + let keep_search = @/ + let keep_ic = &ic + + " turn ignorecase off + set noic + + " clear visual mode so that old visual-mode selections don't + " get applied to new invocations of Align(). + if v:version < 602 + if !exists("s:Align_gavemsg") + let s:Align_gavemsg= 1 + echomsg "Align needs at least Vim version 6.2 to clear visual-mode selection" + endif + elseif exists("s:dovisclear") +" call Decho("clearing visual mode a:0=".a:0." a:1<".a:1.">") + let clearvmode= visualmode(1) + endif + + " set up a list akin to an argument list + if a:0 > 0 + let A= s:QArgSplitter(a:1) + else + let A=[0] + endif + + if A[0] > 0 + let style = A[1] + + " Check for bad separator patterns (zero-length matches) + " (but zero-length patterns for g/v is ok) + if style !~# '[gv]' + let ipat= 2 + while ipat <= A[0] + if "" =~ A[ipat] + echoerr "AlignCtrl: separator<".A[ipat]."> matches zero-length string" + let &ic= keep_ic +" call Dret("AlignCtrl") + return + endif + let ipat= ipat + 1 + endwhile + endif + endif + +" call Decho("AlignCtrl() A[0]=".A[0]) + if !exists("s:AlignStyle") + let s:AlignStyle= "l" + endif + if !exists("s:AlignPrePad") + let s:AlignPrePad= 0 + endif + if !exists("s:AlignPostPad") + let s:AlignPostPad= 0 + endif + if !exists("s:AlignLeadKeep") + let s:AlignLeadKeep= 'w' + endif + + if A[0] == 0 + " ---------------------- + " List current selection + " ---------------------- + if !exists("s:AlignPatQty") + let s:AlignPatQty= 0 + endif + echo "AlignCtrl<".s:AlignCtrl."> qty=".s:AlignPatQty." AlignStyle<".s:AlignStyle."> Padding<".s:AlignPrePad."|".s:AlignPostPad."> LeadingWS=".s:AlignLeadKeep." AlignSep=".s:AlignSep +" call Decho("AlignCtrl<".s:AlignCtrl."> qty=".s:AlignPatQty." AlignStyle<".s:AlignStyle."> Padding<".s:AlignPrePad."|".s:AlignPostPad."> LeadingWS=".s:AlignLeadKeep." AlignSep=".s:AlignSep) + if exists("s:AlignGPat") && !exists("s:AlignVPat") + echo "AlignGPat<".s:AlignGPat.">" + elseif !exists("s:AlignGPat") && exists("s:AlignVPat") + echo "AlignVPat<".s:AlignVPat.">" + elseif exists("s:AlignGPat") && exists("s:AlignVPat") + echo "AlignGPat<".s:AlignGPat."> AlignVPat<".s:AlignVPat.">" + endif + let ipat= 1 + while ipat <= s:AlignPatQty + echo "Pat".ipat."<".s:AlignPat_{ipat}.">" +" call Decho("Pat".ipat."<".s:AlignPat_{ipat}.">") + let ipat= ipat + 1 + endwhile + + else + " ---------------------------------- + " Process alignment control settings + " ---------------------------------- +" call Decho("process the alignctrl settings") +" call Decho("style<".style.">") + + if style ==? "default" + " Default: preserve initial leading whitespace, left-justified, + " alignment on '=', one space padding on both sides + if exists("s:AlignCtrlStackQty") + " clear AlignCtrl stack + while s:AlignCtrlStackQty > 0 + call Align#AlignPop() + endwhile + unlet s:AlignCtrlStackQty + endif + " Set AlignCtrl to its default value + call Align#AlignCtrl("Ilp1P1=<",'=') + call Align#AlignCtrl("g") + call Align#AlignCtrl("v") + let s:dovisclear = 1 + let &ic = keep_ic + let @/ = keep_search +" call Dret("AlignCtrl") + return + endif + + if style =~# 'm' + " map support: Do an AlignPush now and the next call to Align() + " will do an AlignPop at exit +" call Decho("style case m: do AlignPush") + call Align#AlignPush() + let s:DoAlignPop= 1 + endif + + " = : record a list of alignment patterns that are equivalent + if style =~# "=" +" call Decho("style case =: record list of equiv alignment patterns") + let s:AlignCtrl = '=' + if A[0] >= 2 + let s:AlignPatQty= 1 + let s:AlignPat_1 = A[2] + let ipat = 3 + while ipat <= A[0] + let s:AlignPat_1 = s:AlignPat_1.'\|'.A[ipat] + let ipat = ipat + 1 + endwhile + let s:AlignPat_1= '\('.s:AlignPat_1.'\)' +" call Decho("AlignCtrl<".s:AlignCtrl."> AlignPat<".s:AlignPat_1.">") + endif + + "c : cycle through alignment pattern(s) + elseif style =~# 'C' +" call Decho("style case C: cycle through alignment pattern(s)") + let s:AlignCtrl = 'C' + if A[0] >= 2 + let s:AlignPatQty= A[0] - 1 + let ipat = 1 + while ipat < A[0] + let s:AlignPat_{ipat}= A[ipat+1] +" call Decho("AlignCtrl<".s:AlignCtrl."> AlignQty=".s:AlignPatQty." AlignPat_".ipat."<".s:AlignPat_{ipat}.">") + let ipat= ipat + 1 + endwhile + endif + endif + + if style =~# 'p' + let s:AlignPrePad= substitute(style,'^.*p\(\d\+\).*$','\1','') +" call Decho("style case p".s:AlignPrePad.": pre-separator padding") + if s:AlignPrePad == "" + echoerr "AlignCtrl: 'p' needs to be followed by a numeric argument' + let @/ = keep_search + let &ic= keep_ic +" call Dret("AlignCtrl") + return + endif + endif + + if style =~# 'P' + let s:AlignPostPad= substitute(style,'^.*P\(\d\+\).*$','\1','') +" call Decho("style case P".s:AlignPostPad.": post-separator padding") + if s:AlignPostPad == "" + echoerr "AlignCtrl: 'P' needs to be followed by a numeric argument' + let @/ = keep_search + let &ic= keep_ic +" call Dret("AlignCtrl") + return + endif + endif + + if style =~# 'w' +" call Decho("style case w: ignore leading whitespace") + let s:AlignLeadKeep= 'w' + elseif style =~# 'W' +" call Decho("style case w: keep leading whitespace") + let s:AlignLeadKeep= 'W' + elseif style =~# 'I' +" call Decho("style case w: retain initial leading whitespace") + let s:AlignLeadKeep= 'I' + endif + + if style =~# 'g' + " first list item is a "g" selector pattern +" call Decho("style case g: global selector pattern") + if A[0] < 2 + if exists("s:AlignGPat") + unlet s:AlignGPat +" call Decho("unlet s:AlignGPat") + endif + else + let s:AlignGPat= A[2] +" call Decho("s:AlignGPat<".s:AlignGPat.">") + endif + elseif style =~# 'v' + " first list item is a "v" selector pattern +" call Decho("style case v: global selector anti-pattern") + if A[0] < 2 + if exists("s:AlignVPat") + unlet s:AlignVPat +" call Decho("unlet s:AlignVPat") + endif + else + let s:AlignVPat= A[2] +" call Decho("s:AlignVPat<".s:AlignVPat.">") + endif + endif + + "[-lrc+:] : set up s:AlignStyle + if style =~# '[-lrc+:]' +" call Decho("style case [-lrc+:]: field justification") + let s:AlignStyle= substitute(style,'[^-lrc:+]','','g') +" call Decho("AlignStyle<".s:AlignStyle.">") + endif + + "[<>|] : set up s:AlignSep + if style =~# '[<>|]' +" call Decho("style case [-lrc+:]: separator justification") + let s:AlignSep= substitute(style,'[^<>|]','','g') +" call Decho("AlignSep ".s:AlignSep) + endif + endif + + " sanity + if !exists("s:AlignCtrl") + let s:AlignCtrl= '=' + endif + + " restore search and options + let @/ = keep_search + let &ic= keep_ic + +" call Dret("AlignCtrl ".s:AlignCtrl.'p'.s:AlignPrePad.'P'.s:AlignPostPad.s:AlignLeadKeep.s:AlignStyle) + return s:AlignCtrl.'p'.s:AlignPrePad.'P'.s:AlignPostPad.s:AlignLeadKeep.s:AlignStyle +endfun + +" --------------------------------------------------------------------- +" s:MakeSpace: returns a string with spacecnt blanks {{{1 +fun! s:MakeSpace(spacecnt) +" call Dfunc("MakeSpace(spacecnt=".a:spacecnt.")") + let str = "" + let spacecnt = a:spacecnt + while spacecnt > 0 + let str = str . " " + let spacecnt = spacecnt - 1 + endwhile +" call Dret("MakeSpace <".str.">") + return str +endfun + +" --------------------------------------------------------------------- +" Align#Align: align selected text based on alignment pattern(s) {{{1 +fun! Align#Align(hasctrl,...) range +" call Dfunc("Align#Align(hasctrl=".a:hasctrl.",...) a:0=".a:0) + + " sanity checks + if string(a:hasctrl) != "0" && string(a:hasctrl) != "1" + echohl Error|echo 'usage: Align#Align(hasctrl<'.a:hasctrl.'> (should be 0 or 1),"separator(s)" (you have '.a:0.') )'|echohl None +" call Dret("Align#Align") + return + endif + if exists("s:AlignStyle") && s:AlignStyle == ":" + echohl Error |echo '(Align#Align) your AlignStyle is ":", which implies "do-no-alignment"!'|echohl None +" call Dret("Align#Align") + return + endif + + " set up a list akin to an argument list + if a:0 > 0 + let A= s:QArgSplitter(a:1) + else + let A=[0] + endif + + " if :Align! was used, then the first argument is (should be!) an AlignCtrl string + " Note that any alignment control set this way will be temporary. + let hasctrl= a:hasctrl +" call Decho("hasctrl=".hasctrl) + if a:hasctrl && A[0] >= 1 +" call Decho("Align! : using A[1]<".A[1]."> for AlignCtrl") + if A[1] =~ '[gv]' + let hasctrl= hasctrl + 1 + call Align#AlignCtrl('m') + call Align#AlignCtrl(A[1],A[2]) +" call Decho("Align! : also using A[2]<".A[2]."> for AlignCtrl") + elseif A[1] !~ 'm' + call Align#AlignCtrl(A[1]."m") + else + call Align#AlignCtrl(A[1]) + endif + endif + + " Check for bad separator patterns (zero-length matches) + let ipat= 1 + hasctrl + while ipat <= A[0] + if "" =~ A[ipat] + echoerr "Align: separator<".A[ipat]."> matches zero-length string" +" call Dret("Align#Align") + return + endif + let ipat= ipat + 1 + endwhile + + " record current search pattern for subsequent restoration + let keep_search= @/ + let keep_ic = &ic + let keep_report= &report + set noic report=10000 + + if A[0] > hasctrl + " Align will accept a list of separator regexps +" call Decho("A[0]=".A[0].": accepting list of separator regexp") + + if s:AlignCtrl =~# "=" + "= : consider all separators to be equivalent +" call Decho("AlignCtrl: record list of equivalent alignment patterns") + let s:AlignCtrl = '=' + let s:AlignPat_1 = A[1 + hasctrl] + let s:AlignPatQty= 1 + let ipat = 2 + hasctrl + while ipat <= A[0] + let s:AlignPat_1 = s:AlignPat_1.'\|'.A[ipat] + let ipat = ipat + 1 + endwhile + let s:AlignPat_1= '\('.s:AlignPat_1.'\)' +" call Decho("AlignCtrl<".s:AlignCtrl."> AlignPat<".s:AlignPat_1.">") + + elseif s:AlignCtrl =~# 'C' + "c : cycle through alignment pattern(s) +" call Decho("AlignCtrl: cycle through alignment pattern(s)") + let s:AlignCtrl = 'C' + let s:AlignPatQty= A[0] - hasctrl + let ipat = 1 + while ipat <= s:AlignPatQty + let s:AlignPat_{ipat}= A[(ipat + hasctrl)] +" call Decho("AlignCtrl<".s:AlignCtrl."> AlignQty=".s:AlignPatQty." AlignPat_".ipat."<".s:AlignPat_{ipat}.">") + let ipat= ipat + 1 + endwhile + endif + endif + + " Initialize so that begline<endline and begcol<endcol. + " Ragged right: check if the column associated with '< or '> + " is greater than the line's string length -> ragged right. + " Have to be careful about visualmode() -- it returns the last visual + " mode used whether or not it was used currently. + let begcol = virtcol("'<")-1 + let endcol = virtcol("'>")-1 + if begcol > endcol + let begcol = virtcol("'>")-1 + let endcol = virtcol("'<")-1 + endif +" call Decho("begcol=".begcol." endcol=".endcol) + let begline = a:firstline + let endline = a:lastline + if begline > endline + let begline = a:lastline + let endline = a:firstline + endif +" call Decho("begline=".begline." endline=".endline) + let fieldcnt = 0 + if (begline == line("'>") && endline == line("'<")) || (begline == line("'<") && endline == line("'>")) + let vmode= visualmode() +" call Decho("vmode=".vmode) + if vmode == "\<c-v>" + if exists("g:Align_xstrlen") && g:Align_xstrlen + let ragged = ( col("'>") > s:Strlen(getline("'>")) || col("'<") > s:Strlen(getline("'<")) ) + else + let ragged = ( col("'>") > strlen(getline("'>")) || col("'<") > strlen(getline("'<")) ) + endif + else + let ragged= 1 + endif + else + let ragged= 1 + endif + if ragged + let begcol= 0 + endif +" call Decho("lines[".begline.",".endline."] col[".begcol.",".endcol."] ragged=".ragged." AlignCtrl<".s:AlignCtrl.">") + + " Keep user options + let etkeep = &l:et + let pastekeep= &l:paste + setlocal et paste + + " convert selected range of lines to use spaces instead of tabs + " but if first line's initial white spaces are to be retained + " then use 'em + if begcol <= 0 && s:AlignLeadKeep == 'I' + " retain first leading whitespace for all subsequent lines + let bgntxt= substitute(getline(begline),'^\(\s*\).\{-}$','\1','') +" call Decho("retaining 1st leading whitespace: bgntxt<".bgntxt.">") + set noet + endif + exe begline.",".endline."ret" + + " Execute two passes + " First pass: collect alignment data (max field sizes) + " Second pass: perform alignment + let pass= 1 + while pass <= 2 +" call Decho(" ") +" call Decho("---- Pass ".pass.": ----") + + let line= begline + while line <= endline + " Process each line + let txt = getline(line) +" call Decho(" ") +" call Decho("Pass".pass.": Line ".line." <".txt.">") + + " AlignGPat support: allows a selector pattern (akin to g/selector/cmd ) + if exists("s:AlignGPat") +" call Decho("Pass".pass.": AlignGPat<".s:AlignGPat.">") + if match(txt,s:AlignGPat) == -1 +" call Decho("Pass".pass.": skipping") + let line= line + 1 + continue + endif + endif + + " AlignVPat support: allows a selector pattern (akin to v/selector/cmd ) + if exists("s:AlignVPat") +" call Decho("Pass".pass.": AlignVPat<".s:AlignVPat.">") + if match(txt,s:AlignVPat) != -1 +" call Decho("Pass".pass.": skipping") + let line= line + 1 + continue + endif + endif + + " Always skip blank lines + if match(txt,'^\s*$') != -1 +" call Decho("Pass".pass.": skipping") + let line= line + 1 + continue + endif + + " Extract visual-block selected text (init bgntxt, endtxt) + if exists("g:Align_xstrlen") && g:Align_xstrlen + let txtlen= s:Strlen(txt) + else + let txtlen= strlen(txt) + endif + if begcol > 0 + " Record text to left of selected area + let bgntxt= strpart(txt,0,begcol) +" call Decho("Pass".pass.": record text to left: bgntxt<".bgntxt.">") + elseif s:AlignLeadKeep == 'W' + let bgntxt= substitute(txt,'^\(\s*\).\{-}$','\1','') +" call Decho("Pass".pass.": retaining all leading ws: bgntxt<".bgntxt.">") + elseif s:AlignLeadKeep == 'w' || !exists("bgntxt") + " No beginning text + let bgntxt= "" +" call Decho("Pass".pass.": no beginning text") + endif + if ragged + let endtxt= "" + else + " Elide any text lying outside selected columnar region + let endtxt= strpart(txt,endcol+1,txtlen-endcol) + let txt = strpart(txt,begcol,endcol-begcol+1) + endif +" call Decho(" ") +" call Decho("Pass".pass.": bgntxt<".bgntxt.">") +" call Decho("Pass".pass.": txt<". txt .">") +" call Decho("Pass".pass.": endtxt<".endtxt.">") + if !exists("s:AlignPat_{1}") + echohl Error|echo "no separators specified!"|echohl None +" call Dret("Align#Align") + return + endif + + " Initialize for both passes + let seppat = s:AlignPat_{1} + let ifield = 1 + let ipat = 1 + let bgnfield = 0 + let endfield = 0 + let alignstyle = s:AlignStyle + let doend = 1 + let newtxt = "" + let alignprepad = s:AlignPrePad + let alignpostpad= s:AlignPostPad + let alignsep = s:AlignSep + let alignophold = " " + let alignop = "l" +" call Decho("Pass".pass.": initial alignstyle<".alignstyle."> seppat<".seppat.">") + + " Process each field on the line + while doend > 0 + + " C-style: cycle through pattern(s) + if s:AlignCtrl == 'C' && doend == 1 + let seppat = s:AlignPat_{ipat} +" call Decho("Pass".pass.": processing field: AlignCtrl=".s:AlignCtrl." ipat=".ipat." seppat<".seppat.">") + let ipat = ipat + 1 + if ipat > s:AlignPatQty + let ipat = 1 + endif + endif + + " cyclic alignment/justification operator handling + let alignophold = alignop + let alignop = strpart(alignstyle,0,1) + if alignop == '+' || doend == 2 + let alignop= alignophold + else + let alignstyle = strpart(alignstyle,1).strpart(alignstyle,0,1) + let alignopnxt = strpart(alignstyle,0,1) + if alignop == ':' + let seppat = '$' + let doend = 2 +" call Decho("Pass".pass.": alignop<:> case: setting seppat<$> doend==2") + endif + endif + + " cylic separator alignment specification handling + let alignsepop= strpart(alignsep,0,1) + let alignsep = strpart(alignsep,1).alignsepop + + " mark end-of-field and the subsequent end-of-separator. + " Extend field if alignop is '-' + let endfield = match(txt,seppat,bgnfield) + let sepfield = matchend(txt,seppat,bgnfield) + let skipfield= sepfield +" call Decho("Pass".pass.": endfield=match(txt<".txt.">,seppat<".seppat.">,bgnfield=".bgnfield.")=".endfield) + while alignop == '-' && endfield != -1 + let endfield = match(txt,seppat,skipfield) + let sepfield = matchend(txt,seppat,skipfield) + let skipfield = sepfield + let alignop = strpart(alignstyle,0,1) + let alignstyle= strpart(alignstyle,1).strpart(alignstyle,0,1) +" call Decho("Pass".pass.": extend field: endfield<".strpart(txt,bgnfield,endfield-bgnfield)."> alignop<".alignop."> alignstyle<".alignstyle.">") + endwhile + let seplen= sepfield - endfield +" call Decho("Pass".pass.": seplen=[sepfield=".sepfield."] - [endfield=".endfield."]=".seplen) + + if endfield != -1 + if pass == 1 + " --------------------------------------------------------------------- + " Pass 1: Update FieldSize to max +" call Decho("Pass".pass.": before lead/trail remove: field<".strpart(txt,bgnfield,endfield-bgnfield).">") + let field = substitute(strpart(txt,bgnfield,endfield-bgnfield),'^\s*\(.\{-}\)\s*$','\1','') + if s:AlignLeadKeep == 'W' + let field = bgntxt.field + let bgntxt= "" + endif + if exists("g:Align_xstrlen") && g:Align_xstrlen + let fieldlen = s:Strlen(field) + else + let fieldlen = strlen(field) + endif + let sFieldSize = "FieldSize_".ifield + if !exists(sFieldSize) + let FieldSize_{ifield}= fieldlen +" call Decho("Pass".pass.": set FieldSize_{".ifield."}=".FieldSize_{ifield}." <".field.">") + elseif fieldlen > FieldSize_{ifield} + let FieldSize_{ifield}= fieldlen +" call Decho("Pass".pass.": oset FieldSize_{".ifield."}=".FieldSize_{ifield}." <".field.">") + endif + let sSepSize= "SepSize_".ifield + if !exists(sSepSize) + let SepSize_{ifield}= seplen +" call Decho(" set SepSize_{".ifield."}=".SepSize_{ifield}." <".field.">") + elseif seplen > SepSize_{ifield} + let SepSize_{ifield}= seplen +" call Decho("Pass".pass.": oset SepSize_{".ifield."}=".SepSize_{ifield}." <".field.">") + endif + + else + " --------------------------------------------------------------------- + " Pass 2: Perform Alignment + let prepad = strpart(alignprepad,0,1) + let postpad = strpart(alignpostpad,0,1) + let alignprepad = strpart(alignprepad,1).strpart(alignprepad,0,1) + let alignpostpad = strpart(alignpostpad,1).strpart(alignpostpad,0,1) + let field = substitute(strpart(txt,bgnfield,endfield-bgnfield),'^\s*\(.\{-}\)\s*$','\1','') + if s:AlignLeadKeep == 'W' + let field = bgntxt.field + let bgntxt= "" + endif + if doend == 2 + let prepad = 0 + let postpad= 0 + endif + if exists("g:Align_xstrlen") && g:Align_xstrlen + let fieldlen = s:Strlen(field) + else + let fieldlen = strlen(field) + endif + let sep = s:MakeSpace(prepad).strpart(txt,endfield,sepfield-endfield).s:MakeSpace(postpad) + if seplen < SepSize_{ifield} + if alignsepop == "<" + " left-justify separators + let sep = sep.s:MakeSpace(SepSize_{ifield}-seplen) + elseif alignsepop == ">" + " right-justify separators + let sep = s:MakeSpace(SepSize_{ifield}-seplen).sep + else + " center-justify separators + let sepleft = (SepSize_{ifield} - seplen)/2 + let sepright = SepSize_{ifield} - seplen - sepleft + let sep = s:MakeSpace(sepleft).sep.s:MakeSpace(sepright) + endif + endif + let spaces = FieldSize_{ifield} - fieldlen +" call Decho("Pass".pass.": Field #".ifield."<".field."> spaces=".spaces." be[".bgnfield.",".endfield."] pad=".prepad.','.postpad." FS_".ifield."<".FieldSize_{ifield}."> sep<".sep."> ragged=".ragged." doend=".doend." alignop<".alignop.">") + + " Perform alignment according to alignment style justification + if spaces > 0 + if alignop == 'c' + " center the field + let spaceleft = spaces/2 + let spaceright= FieldSize_{ifield} - spaceleft - fieldlen + let newtxt = newtxt.s:MakeSpace(spaceleft).field.s:MakeSpace(spaceright).sep + elseif alignop == 'r' + " right justify the field + let newtxt= newtxt.s:MakeSpace(spaces).field.sep + elseif ragged && doend == 2 + " left justify rightmost field (no trailing blanks needed) + let newtxt= newtxt.field + else + " left justfiy the field + let newtxt= newtxt.field.s:MakeSpace(spaces).sep + endif + elseif ragged && doend == 2 + " field at maximum field size and no trailing blanks needed + let newtxt= newtxt.field + else + " field is at maximum field size already + let newtxt= newtxt.field.sep + endif +" call Decho("Pass".pass.": newtxt<".newtxt.">") + endif " pass 1/2 + + " bgnfield indexes to end of separator at right of current field + " Update field counter + let bgnfield= sepfield + let ifield = ifield + 1 + if doend == 2 + let doend= 0 + endif + " handle end-of-text as end-of-field + elseif doend == 1 + let seppat = '$' + let doend = 2 + else + let doend = 0 + endif " endfield != -1 + endwhile " doend loop (as well as regularly separated fields) + + if pass == 2 + " Write altered line to buffer +" call Decho("Pass".pass.": bgntxt<".bgntxt."> line=".line) +" call Decho("Pass".pass.": newtxt<".newtxt.">") +" call Decho("Pass".pass.": endtxt<".endtxt.">") + call setline(line,bgntxt.newtxt.endtxt) + endif + + let line = line + 1 + endwhile " line loop + + let pass= pass + 1 + endwhile " pass loop +" call Decho("end of two pass loop") + + " Restore user options + let &l:et = etkeep + let &l:paste = pastekeep + + if exists("s:DoAlignPop") + " AlignCtrl Map support + call Align#AlignPop() + unlet s:DoAlignPop + endif + + " restore current search pattern + let @/ = keep_search + let &ic = keep_ic + let &report = keep_report + +" call Dret("Align#Align") + return +endfun + +" --------------------------------------------------------------------- +" Align#AlignPush: this command/function pushes an alignment control string onto a stack {{{1 +fun! Align#AlignPush() +" call Dfunc("AlignPush()") + + " initialize the stack + if !exists("s:AlignCtrlStackQty") + let s:AlignCtrlStackQty= 1 + else + let s:AlignCtrlStackQty= s:AlignCtrlStackQty + 1 + endif + + " construct an AlignCtrlStack entry + if !exists("s:AlignSep") + let s:AlignSep= '' + endif + let s:AlignCtrlStack_{s:AlignCtrlStackQty}= s:AlignCtrl.'p'.s:AlignPrePad.'P'.s:AlignPostPad.s:AlignLeadKeep.s:AlignStyle.s:AlignSep +" call Decho("AlignPush: AlignCtrlStack_".s:AlignCtrlStackQty."<".s:AlignCtrlStack_{s:AlignCtrlStackQty}.">") + + " push [GV] patterns onto their own stack + if exists("s:AlignGPat") + let s:AlignGPat_{s:AlignCtrlStackQty}= s:AlignGPat + else + let s:AlignGPat_{s:AlignCtrlStackQty}= "" + endif + if exists("s:AlignVPat") + let s:AlignVPat_{s:AlignCtrlStackQty}= s:AlignVPat + else + let s:AlignVPat_{s:AlignCtrlStackQty}= "" + endif + +" call Dret("AlignPush") +endfun + +" --------------------------------------------------------------------- +" Align#AlignPop: this command/function pops an alignment pattern from a stack {{{1 +" and into the AlignCtrl variables. +fun! Align#AlignPop() +" call Dfunc("Align#AlignPop()") + + " sanity checks + if !exists("s:AlignCtrlStackQty") + echoerr "AlignPush needs to be used prior to AlignPop" +" call Dret("Align#AlignPop <> : AlignPush needs to have been called first") + return "" + endif + if s:AlignCtrlStackQty <= 0 + unlet s:AlignCtrlStackQty + echoerr "AlignPush needs to be used prior to AlignPop" +" call Dret("Align#AlignPop <> : AlignPop needs to have been called first") + return "" + endif + + " pop top of AlignCtrlStack and pass value to AlignCtrl + let retval=s:AlignCtrlStack_{s:AlignCtrlStackQty} + unlet s:AlignCtrlStack_{s:AlignCtrlStackQty} + call Align#AlignCtrl(retval) + + " pop G pattern stack + if s:AlignGPat_{s:AlignCtrlStackQty} != "" + call Align#AlignCtrl('g',s:AlignGPat_{s:AlignCtrlStackQty}) + else + call Align#AlignCtrl('g') + endif + unlet s:AlignGPat_{s:AlignCtrlStackQty} + + " pop V pattern stack + if s:AlignVPat_{s:AlignCtrlStackQty} != "" + call Align#AlignCtrl('v',s:AlignVPat_{s:AlignCtrlStackQty}) + else + call Align#AlignCtrl('v') + endif + + unlet s:AlignVPat_{s:AlignCtrlStackQty} + let s:AlignCtrlStackQty= s:AlignCtrlStackQty - 1 + +" call Dret("Align#AlignPop <".retval."> : AlignCtrlStackQty=".s:AlignCtrlStackQty) + return retval +endfun + +" --------------------------------------------------------------------- +" Align#AlignReplaceQuotedSpaces: {{{1 +fun! Align#AlignReplaceQuotedSpaces() +" call Dfunc("AlignReplaceQuotedSpaces()") + + let l:line = getline(line(".")) + if exists("g:Align_xstrlen") && g:Align_xstrlen + let l:linelen = s:Strlen(l:line) + else + let l:linelen = strlen(l:line) + endif + let l:startingPos = 0 + let l:startQuotePos = 0 + let l:endQuotePos = 0 + let l:spacePos = 0 + let l:quoteRe = '\\\@<!"' + +" "call Decho("in replace spaces. line=" . line('.')) + while (1) + let l:startQuotePos = match(l:line, l:quoteRe, l:startingPos) + if (l:startQuotePos < 0) +" "call Decho("No more quotes to the end of line") + break + endif + let l:endQuotePos = match(l:line, l:quoteRe, l:startQuotePos + 1) + if (l:endQuotePos < 0) +" "call Decho("Mismatched quotes") + break + endif + let l:spaceReplaceRe = '^.\{' . (l:startQuotePos + 1) . '}.\{-}\zs\s\ze.*.\{' . (linelen - l:endQuotePos) . '}$' +" "call Decho('spaceReplaceRe="' . l:spaceReplaceRe . '"') + let l:newStr = substitute(l:line, l:spaceReplaceRe, '%', '') + while (l:newStr != l:line) +" "call Decho('newstr="' . l:newStr . '"') + let l:line = l:newStr + let l:newStr = substitute(l:line, l:spaceReplaceRe, '%', '') + endwhile + let l:startingPos = l:endQuotePos + 1 + endwhile + call setline(line('.'), l:line) + +" call Dret("AlignReplaceQuotedSpaces") +endfun + +" --------------------------------------------------------------------- +" s:QArgSplitter: to avoid \ processing by <f-args>, <q-args> is needed. {{{1 +" However, <q-args> doesn't split at all, so this function returns a list +" of arguments which has been: +" * split at whitespace +" * unless inside "..."s. One may escape characters with a backslash inside double quotes. +" along with a leading length-of-list. +" +" Examples: %Align "\"" will align on "s +" %Align " " will align on spaces +" +" The resulting list: qarglist[0] corresponds to a:0 +" qarglist[i] corresponds to a:{i} +fun! s:QArgSplitter(qarg) +" call Dfunc("s:QArgSplitter(qarg<".a:qarg.">)") + + if a:qarg =~ '".*"' + " handle "..." args, which may include whitespace + let qarglist = [] + let args = a:qarg +" call Decho("handle quoted arguments: args<".args.">") + while args != "" + let iarg = 0 + let arglen = strlen(args) +" call Decho("args[".iarg."]<".args[iarg]."> arglen=".arglen) + " find index to first not-escaped '"' + while args[iarg] != '"' && iarg < arglen + if args[iarg] == '\' + let args= strpart(args,1) + endif + let iarg= iarg + 1 + endwhile +" call Decho("args<".args."> iarg=".iarg." arglen=".arglen) + + if iarg > 0 + " handle left of quote or remaining section +" call Decho("handle left of quote or remaining section") + if args[iarg] == '"' + let qarglist= qarglist + split(strpart(args,0,iarg-1)) + else + let qarglist= qarglist + split(strpart(args,0,iarg)) + endif + let args = strpart(args,iarg) + let arglen = strlen(args) + + elseif iarg < arglen && args[0] == '"' + " handle "quoted" section +" call Decho("handle quoted section") + let iarg= 1 + while args[iarg] != '"' && iarg < arglen + if args[iarg] == '\' + let args= strpart(args,1) + endif + let iarg= iarg + 1 + endwhile +" call Decho("args<".args."> iarg=".iarg." arglen=".arglen) + if args[iarg] == '"' + call add(qarglist,strpart(args,1,iarg-1)) + let args= strpart(args,iarg+1) + else + let qarglist = qarglist + split(args) + let args = "" + endif + endif +" call Decho("qarglist".string(qarglist)." iarg=".iarg." args<".args.">") + endwhile + + else + " split at all whitespace + let qarglist= split(a:qarg) + endif + + let qarglistlen= len(qarglist) + let qarglist = insert(qarglist,qarglistlen) +" call Dret("s:QArgSplitter ".string(qarglist)) + return qarglist +endfun + +" --------------------------------------------------------------------- +" s:Strlen: this function returns the length of a string, even if its {{{1 +" using two-byte etc characters. +" Currently, its only used if g:Align_xstrlen is set to a +" nonzero value. Solution from Nicolai Weibull, vim docs +" (:help strlen()), Tony Mechelynck, and my own invention. +fun! s:Strlen(x) +" call Dfunc("s:Strlen(x<".a:x.">") + if g:Align_xstrlen == 1 + " number of codepoints (Latin a + combining circumflex is two codepoints) + " (comment from TM, solution from NW) + let ret= strlen(substitute(a:x,'.','c','g')) + + elseif g:Align_xstrlen == 2 + " number of spacing codepoints (Latin a + combining circumflex is one spacing + " codepoint; a hard tab is one; wide and narrow CJK are one each; etc.) + " (comment from TM, solution from TM) + let ret=strlen(substitute(a:x, '.\Z', 'x', 'g')) + + elseif g:Align_xstrlen == 3 + " virtual length (counting, for instance, tabs as anything between 1 and + " 'tabstop', wide CJK as 2 rather than 1, Arabic alif as zero when immediately + " preceded by lam, one otherwise, etc.) + " (comment from TM, solution from me) + let modkeep= &l:mod + exe "norm! o\<esc>" + call setline(line("."),a:x) + let ret= virtcol("$") - 1 + d + let &l:mod= modkeep + + else + " at least give a decent default + ret= strlen(a:x) + endif +" call Dret("s:Strlen ".ret) + return ret +endfun + +" --------------------------------------------------------------------- +" Set up default values: {{{1 +"call Decho("-- Begin AlignCtrl Initialization --") +call Align#AlignCtrl("default") +"call Decho("-- End AlignCtrl Initialization --") + +" --------------------------------------------------------------------- +" Restore: {{{1 +let &cpo= s:keepcpo +unlet s:keepcpo +" vim: ts=4 fdm=marker diff --git a/vim/autoload/AlignMaps.vim b/vim/autoload/AlignMaps.vim new file mode 100644 index 0000000..ace2de8 --- /dev/null +++ b/vim/autoload/AlignMaps.vim @@ -0,0 +1,330 @@ +" AlignMaps.vim : support functions for AlignMaps +" Author: Charles E. Campbell, Jr. +" Date: Mar 03, 2009 +" Version: 41 +" --------------------------------------------------------------------- +" Load Once: {{{1 +if &cp || exists("g:loaded_AlignMaps") + finish +endif +let g:loaded_AlignMaps= "v41" +let s:keepcpo = &cpo +set cpo&vim + +" ===================================================================== +" Functions: {{{1 + +" --------------------------------------------------------------------- +" AlignMaps#WrapperStart: {{{2 +fun! AlignMaps#WrapperStart(vis) range +" call Dfunc("AlignMaps#WrapperStart(vis=".a:vis.")") + + if a:vis + norm! '<ma'> + endif + + if line("'y") == 0 || line("'z") == 0 || !exists("s:alignmaps_wrapcnt") || s:alignmaps_wrapcnt <= 0 +" call Decho("wrapper initialization") + let s:alignmaps_wrapcnt = 1 + let s:alignmaps_keepgd = &gdefault + let s:alignmaps_keepsearch = @/ + let s:alignmaps_keepch = &ch + let s:alignmaps_keepmy = SaveMark("'y") + let s:alignmaps_keepmz = SaveMark("'z") + let s:alignmaps_posn = SaveWinPosn(0) + " set up fencepost blank lines + put ='' + norm! mz'a + put! ='' + ky + let s:alignmaps_zline = line("'z") + exe "'y,'zs/@/\177/ge" + else +" call Decho("embedded wrapper") + let s:alignmaps_wrapcnt = s:alignmaps_wrapcnt + 1 + norm! 'yjma'zk + endif + + " change some settings to align-standard values + set nogd + set ch=2 + AlignPush + norm! 'zk +" call Dret("AlignMaps#WrapperStart : alignmaps_wrapcnt=".s:alignmaps_wrapcnt." my=".line("'y")." mz=".line("'z")) +endfun + +" --------------------------------------------------------------------- +" AlignMaps#WrapperEnd: {{{2 +fun! AlignMaps#WrapperEnd() range +" call Dfunc("AlignMaps#WrapperEnd() alignmaps_wrapcnt=".s:alignmaps_wrapcnt." my=".line("'y")." mz=".line("'z")) + + " remove trailing white space introduced by whatever in the modification zone + 'y,'zs/ \+$//e + + " restore AlignCtrl settings + AlignPop + + let s:alignmaps_wrapcnt= s:alignmaps_wrapcnt - 1 + if s:alignmaps_wrapcnt <= 0 + " initial wrapper ending + exe "'y,'zs/\177/@/ge" + + " if the 'z line hasn't moved, then go ahead and restore window position + let zstationary= s:alignmaps_zline == line("'z") + + " remove fencepost blank lines. + " restore 'a + norm! 'yjmakdd'zdd + + " restore original 'y, 'z, and window positioning + call RestoreMark(s:alignmaps_keepmy) + call RestoreMark(s:alignmaps_keepmz) + if zstationary > 0 + call RestoreWinPosn(s:alignmaps_posn) +" call Decho("restored window positioning") + endif + + " restoration of options + let &gd= s:alignmaps_keepgd + let &ch= s:alignmaps_keepch + let @/ = s:alignmaps_keepsearch + + " remove script variables + unlet s:alignmaps_keepch + unlet s:alignmaps_keepsearch + unlet s:alignmaps_keepmy + unlet s:alignmaps_keepmz + unlet s:alignmaps_keepgd + unlet s:alignmaps_posn + endif + +" call Dret("AlignMaps#WrapperEnd : alignmaps_wrapcnt=".s:alignmaps_wrapcnt." my=".line("'y")." mz=".line("'z")) +endfun + +" --------------------------------------------------------------------- +" AlignMaps#StdAlign: some semi-standard align calls {{{2 +fun! AlignMaps#StdAlign(mode) range +" call Dfunc("AlignMaps#StdAlign(mode=".a:mode.")") + if a:mode == 1 + " align on @ +" call Decho("align on @") + AlignCtrl mIp1P1=l @ + 'a,.Align + elseif a:mode == 2 + " align on @, retaining all initial white space on each line +" call Decho("align on @, retaining all initial white space on each line") + AlignCtrl mWp1P1=l @ + 'a,.Align + elseif a:mode == 3 + " like mode 2, but ignore /* */-style comments +" call Decho("like mode 2, but ignore /* */-style comments") + AlignCtrl v ^\s*/[/*] + AlignCtrl mWp1P1=l @ + 'a,.Align + else + echoerr "(AlignMaps) AlignMaps#StdAlign doesn't support mode#".a:mode + endif +" call Dret("AlignMaps#StdAlign") +endfun + +" --------------------------------------------------------------------- +" AlignMaps#CharJoiner: joins lines which end in the given character (spaces {{{2 +" at end are ignored) +fun! AlignMaps#CharJoiner(chr) +" call Dfunc("AlignMaps#CharJoiner(chr=".a:chr.")") + let aline = line("'a") + let rep = line(".") - aline + while rep > 0 + norm! 'a + while match(getline(aline),a:chr . "\s*$") != -1 && rep >= 0 + " while = at end-of-line, delete it and join with next + norm! 'a$ + j! + let rep = rep - 1 + endwhile + " update rep(eat) count + let rep = rep - 1 + if rep <= 0 + " terminate loop if at end-of-block + break + endif + " prepare for next line + norm! jma + let aline = line("'a") + endwhile +" call Dret("AlignMaps#CharJoiner") +endfun + +" --------------------------------------------------------------------- +" AlignMaps#Equals: supports \t= and \T= {{{2 +fun! AlignMaps#Equals() range +" call Dfunc("AlignMaps#Equals()") + 'a,'zs/\s\+\([*/+\-%|&\~^]\==\)/ \1/e + 'a,'zs@ \+\([*/+\-%|&\~^]\)=@\1=@ge + 'a,'zs/==/\="\<Char-0x0f>\<Char-0x0f>"/ge + 'a,'zs/\([!<>:]\)=/\=submatch(1)."\<Char-0x0f>"/ge + norm g'zk + AlignCtrl mIp1P1=l = + AlignCtrl g = + 'a,'z-1Align + 'a,'z-1s@\([*/+\-%|&\~^!=]\)\( \+\)=@\2\1=@ge + 'a,'z-1s/\( \+\);/;\1/ge + if &ft == "c" || &ft == "cpp" +" call Decho("exception for ".&ft) + 'a,'z-1v/^\s*\/[*/]/s/\/[*/]/@&@/e + 'a,'z-1v/^\s*\/[*/]/s/\*\//@&/e + if exists("g:mapleader") + exe "norm 'zk" + call AlignMaps#StdAlign(1) + else + exe "norm 'zk" + call AlignMaps#StdAlign(1) + endif + 'y,'zs/^\(\s*\) @/\1/e + endif + 'a,'z-1s/\%x0f/=/ge + 'y,'zs/ @//eg +" call Dret("AlignMaps#Equals") +endfun + +" --------------------------------------------------------------------- +" AlignMaps#Afnc: useful for splitting one-line function beginnings {{{2 +" into one line per argument format +fun! AlignMaps#Afnc() +" call Dfunc("AlignMaps#Afnc()") + + " keep display quiet + let chkeep = &ch + let gdkeep = &gd + let vekeep = &ve + set ch=2 nogd ve= + + " will use marks y,z ; save current values + let mykeep = SaveMark("'y") + let mzkeep = SaveMark("'z") + + " Find beginning of function -- be careful to skip over comments + let cmmntid = synIDtrans(hlID("Comment")) + let stringid = synIDtrans(hlID("String")) + exe "norm! ]]" + while search(")","bW") != 0 +" call Decho("line=".line(".")." col=".col(".")) + let parenid= synIDtrans(synID(line("."),col("."),1)) + if parenid != cmmntid && parenid != stringid + break + endif + endwhile + norm! %my + s/(\s*\(\S\)/(\r \1/e + exe "norm! `y%" + s/)\s*\(\/[*/]\)/)\r\1/e + exe "norm! `y%mz" + 'y,'zs/\s\+$//e + 'y,'zs/^\s\+//e + 'y+1,'zs/^/ / + + " insert newline after every comma only one parenthesis deep + sil! exe "norm! `y\<right>h" + let parens = 1 + let cmmnt = 0 + let cmmntline= -1 + while parens >= 1 +" call Decho("parens=".parens." @a=".@a) + exe 'norm! ma "ay`a ' + if @a == "(" + let parens= parens + 1 + elseif @a == ")" + let parens= parens - 1 + + " comment bypass: /* ... */ or //... + elseif cmmnt == 0 && @a == '/' + let cmmnt= 1 + elseif cmmnt == 1 + if @a == '/' + let cmmnt = 2 " //... + let cmmntline= line(".") + elseif @a == '*' + let cmmnt= 3 " /*... + else + let cmmnt= 0 + endif + elseif cmmnt == 2 && line(".") != cmmntline + let cmmnt = 0 + let cmmntline= -1 + elseif cmmnt == 3 && @a == '*' + let cmmnt= 4 + elseif cmmnt == 4 + if @a == '/' + let cmmnt= 0 " ...*/ + elseif @a != '*' + let cmmnt= 3 + endif + + elseif @a == "," && parens == 1 && cmmnt == 0 + exe "norm! i\<CR>\<Esc>" + endif + endwhile + norm! `y%mz% + sil! 'y,'zg/^\s*$/d + + " perform substitutes to mark fields for Align + sil! 'y+1,'zv/^\//s/^\s\+\(\S\)/ \1/e + sil! 'y+1,'zv/^\//s/\(\S\)\s\+/\1 /eg + sil! 'y+1,'zv/^\//s/\* \+/*/ge + sil! 'y+1,'zv/^\//s/\w\zs\s*\*/ */ge + " func + " ws <- declaration -> <-ptr -> <-var-> <-[array][] -> <-glop-> <-end-> + sil! 'y+1,'zv/^\//s/^\s*\(\(\K\k*\s*\)\+\)\s\+\([(*]*\)\s*\(\K\k*\)\s*\(\(\[.\{-}]\)*\)\s*\(.\{-}\)\=\s*\([,)]\)\s*$/ \1@#\3@\4\5@\7\8/e + sil! 'y+1,'z+1g/^\s*\/[*/]/norm! kJ + sil! 'y+1,'z+1s%/[*/]%@&@%ge + sil! 'y+1,'z+1s%*/%@&%ge + AlignCtrl mIp0P0=l @ + sil! 'y+1,'zAlign + sil! 'y,'zs%@\(/[*/]\)@%\t\1 %e + sil! 'y,'zs%@\*/% */%e + sil! 'y,'zs/@\([,)]\)/\1/ + sil! 'y,'zs/@/ / + AlignCtrl mIlrp0P0= # @ + sil! 'y+1,'zAlign + sil! 'y+1,'zs/#/ / + sil! 'y+1,'zs/@// + sil! 'y+1,'zs/\(\s\+\)\([,)]\)/\2\1/e + + " Restore + call RestoreMark(mykeep) + call RestoreMark(mzkeep) + let &ch= chkeep + let &gd= gdkeep + let &ve= vekeep + +" call Dret("AlignMaps#Afnc") +endfun + +" --------------------------------------------------------------------- +" AlignMaps#FixMultiDec: converts a type arg,arg,arg; line to multiple lines {{{2 +fun! AlignMaps#FixMultiDec() +" call Dfunc("AlignMaps#FixMultiDec()") + + " save register x + let xkeep = @x + let curline = getline(".") +" call Decho("curline<".curline.">") + + " Get the type. I'm assuming one type per line (ie. int x; double y; on one line will not be handled properly) + let @x=substitute(curline,'^\(\s*[a-zA-Z_ \t][a-zA-Z0-9_ \t]*\)\s\+[(*]*\h.*$','\1','') +" call Decho("@x<".@x.">") + + " transform line + exe 's/,/;\r'.@x.' /ge' + + "restore register x + let @x= xkeep + +" call Dret("AlignMaps#FixMultiDec : my=".line("'y")." mz=".line("'z")) +endfun + +" --------------------------------------------------------------------- +" Restore: {{{1 +let &cpo= s:keepcpo +unlet s:keepcpo +" vim: ts=4 fdm=marker diff --git a/vim/autoload/EasyMotion.vim b/vim/autoload/EasyMotion.vim new file mode 100755 index 0000000..7c79dd8 --- /dev/null +++ b/vim/autoload/EasyMotion.vim @@ -0,0 +1,573 @@ +" EasyMotion - Vim motions on speed! +" +" Author: Kim Silkebækken <kim.silkebaekken+vim@gmail.com> +" Source repository: https://github.com/Lokaltog/vim-easymotion + +" Default configuration functions {{{ + function! EasyMotion#InitOptions(options) " {{{ + for [key, value] in items(a:options) + if ! exists('g:EasyMotion_' . key) + exec 'let g:EasyMotion_' . key . ' = ' . string(value) + endif + endfor + endfunction " }}} + function! EasyMotion#InitHL(group, colors) " {{{ + let group_default = a:group . 'Default' + + " Prepare highlighting variables + let guihl = printf('guibg=%s guifg=%s gui=%s', a:colors.gui[0], a:colors.gui[1], a:colors.gui[2]) + if !exists('g:CSApprox_loaded') + let ctermhl = &t_Co == 256 + \ ? printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm256[0], a:colors.cterm256[1], a:colors.cterm256[2]) + \ : printf('ctermbg=%s ctermfg=%s cterm=%s', a:colors.cterm[0], a:colors.cterm[1], a:colors.cterm[2]) + else + let ctermhl = '' + endif + + " Create default highlighting group + execute printf('hi default %s %s %s', group_default, guihl, ctermhl) + + " Check if the hl group exists + if hlexists(a:group) + redir => hlstatus | exec 'silent hi ' . a:group | redir END + + " Return if the group isn't cleared + if hlstatus !~ 'cleared' + return + endif + endif + + " No colors are defined for this group, link to defaults + execute printf('hi default link %s %s', a:group, group_default) + endfunction " }}} + function! EasyMotion#InitMappings(motions) "{{{ + for motion in keys(a:motions) + call EasyMotion#InitOptions({ 'mapping_' . motion : g:EasyMotion_leader_key . motion }) + endfor + + if g:EasyMotion_do_mapping + for [motion, fn] in items(a:motions) + if empty(g:EasyMotion_mapping_{motion}) + continue + endif + + silent exec 'nnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion#' . fn.name . '(0, ' . fn.dir . ')<CR>' + silent exec 'onoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :call EasyMotion#' . fn.name . '(0, ' . fn.dir . ')<CR>' + silent exec 'vnoremap <silent> ' . g:EasyMotion_mapping_{motion} . ' :<C-U>call EasyMotion#' . fn.name . '(1, ' . fn.dir . ')<CR>' + endfor + endif + endfunction "}}} +" }}} +" Motion functions {{{ + function! EasyMotion#F(visualmode, direction) " {{{ + let char = s:GetSearchChar(a:visualmode) + + if empty(char) + return + endif + + let re = '\C' . escape(char, '.$^~') + + call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1)) + endfunction " }}} + function! EasyMotion#T(visualmode, direction) " {{{ + let char = s:GetSearchChar(a:visualmode) + + if empty(char) + return + endif + + if a:direction == 1 + let re = '\C' . escape(char, '.$^~') . '\zs.' + else + let re = '\C.' . escape(char, '.$^~') + endif + + call s:EasyMotion(re, a:direction, a:visualmode ? visualmode() : '', mode(1)) + endfunction " }}} + function! EasyMotion#WB(visualmode, direction) " {{{ + call s:EasyMotion('\(\<.\|^$\)', a:direction, a:visualmode ? visualmode() : '', '') + endfunction " }}} + function! EasyMotion#WBW(visualmode, direction) " {{{ + call s:EasyMotion('\(\(^\|\s\)\@<=\S\|^$\)', a:direction, a:visualmode ? visualmode() : '', '') + endfunction " }}} + function! EasyMotion#E(visualmode, direction) " {{{ + call s:EasyMotion('\(.\>\|^$\)', a:direction, a:visualmode ? visualmode() : '', mode(1)) + endfunction " }}} + function! EasyMotion#EW(visualmode, direction) " {{{ + call s:EasyMotion('\(\S\(\s\|$\)\|^$\)', a:direction, a:visualmode ? visualmode() : '', mode(1)) + endfunction " }}} + function! EasyMotion#JK(visualmode, direction) " {{{ + call s:EasyMotion('^\(\w\|\s*\zs\|$\)', a:direction, a:visualmode ? visualmode() : '', '') + endfunction " }}} + function! EasyMotion#Search(visualmode, direction) " {{{ + call s:EasyMotion(@/, a:direction, a:visualmode ? visualmode() : '', '') + endfunction " }}} +" }}} +" Helper functions {{{ + function! s:Message(message) " {{{ + echo 'EasyMotion: ' . a:message + endfunction " }}} + function! s:Prompt(message) " {{{ + echohl Question + echo a:message . ': ' + echohl None + endfunction " }}} + function! s:VarReset(var, ...) " {{{ + if ! exists('s:var_reset') + let s:var_reset = {} + endif + + let buf = bufname("") + + if a:0 == 0 && has_key(s:var_reset, a:var) + " Reset var to original value + call setbufvar(buf, a:var, s:var_reset[a:var]) + elseif a:0 == 1 + let new_value = a:0 == 1 ? a:1 : '' + + " Store original value + let s:var_reset[a:var] = getbufvar(buf, a:var) + + " Set new var value + call setbufvar(buf, a:var, new_value) + endif + endfunction " }}} + function! s:SetLines(lines, key) " {{{ + try + " Try to join changes with previous undo block + undojoin + catch + endtry + + for [line_num, line] in a:lines + call setline(line_num, line[a:key]) + endfor + endfunction " }}} + function! s:GetChar() " {{{ + let char = getchar() + + if char == 27 + " Escape key pressed + redraw + + call s:Message('Cancelled') + + return '' + endif + + return nr2char(char) + endfunction " }}} + function! s:GetSearchChar(visualmode) " {{{ + call s:Prompt('Search for character') + + let char = s:GetChar() + + " Check that we have an input char + if empty(char) + " Restore selection + if ! empty(a:visualmode) + silent exec 'normal! gv' + endif + + return '' + endif + + return char + endfunction " }}} +" }}} +" Grouping algorithms {{{ + let s:grouping_algorithms = { + \ 1: 'SCTree' + \ , 2: 'Original' + \ } + " Single-key/closest target priority tree {{{ + " This algorithm tries to assign one-key jumps to all the targets closest to the cursor. + " It works recursively and will work correctly with as few keys as two. + function! s:GroupingAlgorithmSCTree(targets, keys) + " Prepare variables for working + let targets_len = len(a:targets) + let keys_len = len(a:keys) + + let groups = {} + + let keys = reverse(copy(a:keys)) + + " Semi-recursively count targets {{{ + " We need to know exactly how many child nodes (targets) this branch will have + " in order to pass the correct amount of targets to the recursive function. + + " Prepare sorted target count list {{{ + " This is horrible, I know. But dicts aren't sorted in vim, so we need to + " work around that. That is done by having one sorted list with key counts, + " and a dict which connects the key with the keys_count list. + + let keys_count = [] + let keys_count_keys = {} + + let i = 0 + for key in keys + call add(keys_count, 0) + + let keys_count_keys[key] = i + + let i += 1 + endfor + " }}} + + let targets_left = targets_len + let level = 0 + let i = 0 + + while targets_left > 0 + " Calculate the amount of child nodes based on the current level + let childs_len = (level == 0 ? 1 : (keys_len - 1) ) + + for key in keys + " Add child node count to the keys_count array + let keys_count[keys_count_keys[key]] += childs_len + + " Subtract the child node count + let targets_left -= childs_len + + if targets_left <= 0 + " Subtract the targets left if we added too many too + " many child nodes to the key count + let keys_count[keys_count_keys[key]] += targets_left + + break + endif + + let i += 1 + endfor + + let level += 1 + endwhile + " }}} + " Create group tree {{{ + let i = 0 + let key = 0 + + call reverse(keys_count) + + for key_count in keys_count + if key_count > 1 + " We need to create a subgroup + " Recurse one level deeper + let groups[a:keys[key]] = s:GroupingAlgorithmSCTree(a:targets[i : i + key_count - 1], a:keys) + elseif key_count == 1 + " Assign single target key + let groups[a:keys[key]] = a:targets[i] + else + " No target + continue + endif + + let key += 1 + let i += key_count + endfor + " }}} + + " Finally! + return groups + endfunction + " }}} + " Original {{{ + function! s:GroupingAlgorithmOriginal(targets, keys) + " Split targets into groups (1 level) + let targets_len = len(a:targets) + let keys_len = len(a:keys) + + let groups = {} + + let i = 0 + let root_group = 0 + try + while root_group < targets_len + let groups[a:keys[root_group]] = {} + + for key in a:keys + let groups[a:keys[root_group]][key] = a:targets[i] + + let i += 1 + endfor + + let root_group += 1 + endwhile + catch | endtry + + " Flatten the group array + if len(groups) == 1 + let groups = groups[a:keys[0]] + endif + + return groups + endfunction + " }}} + " Coord/key dictionary creation {{{ + function! s:CreateCoordKeyDict(groups, ...) + " Dict structure: + " 1,2 : a + " 2,3 : b + let sort_list = [] + let coord_keys = {} + let group_key = a:0 == 1 ? a:1 : '' + + for [key, item] in items(a:groups) + let key = ( ! empty(group_key) ? group_key : key) + + if type(item) == 3 + " Destination coords + + " The key needs to be zero-padded in order to + " sort correctly + let dict_key = printf('%05d,%05d', item[0], item[1]) + let coord_keys[dict_key] = key + + " We need a sorting list to loop correctly in + " PromptUser, dicts are unsorted + call add(sort_list, dict_key) + else + " Item is a dict (has children) + let coord_key_dict = s:CreateCoordKeyDict(item, key) + + " Make sure to extend both the sort list and the + " coord key dict + call extend(sort_list, coord_key_dict[0]) + call extend(coord_keys, coord_key_dict[1]) + endif + + unlet item + endfor + + return [sort_list, coord_keys] + endfunction + " }}} +" }}} +" Core functions {{{ + function! s:PromptUser(groups) "{{{ + " If only one possible match, jump directly to it {{{ + let group_values = values(a:groups) + + if len(group_values) == 1 + redraw + + return group_values[0] + endif + " }}} + " Prepare marker lines {{{ + let lines = {} + let hl_coords = [] + let coord_key_dict = s:CreateCoordKeyDict(a:groups) + + for dict_key in sort(coord_key_dict[0]) + let target_key = coord_key_dict[1][dict_key] + let [line_num, col_num] = split(dict_key, ',') + + let line_num = str2nr(line_num) + let col_num = str2nr(col_num) + + " Add original line and marker line + if ! has_key(lines, line_num) + let current_line = getline(line_num) + + let lines[line_num] = { 'orig': current_line, 'marker': current_line, 'mb_compensation': 0 } + endif + + " Compensate for byte difference between marker + " character and target character + " + " This has to be done in order to match the correct + " column; \%c matches the byte column and not display + " column. + let target_char_len = strlen(matchstr(lines[line_num]['marker'], '\%' . col_num . 'c.')) + let target_key_len = strlen(target_key) + + " Solve multibyte issues by matching the byte column + " number instead of the visual column + let col_num -= lines[line_num]['mb_compensation'] + + if strlen(lines[line_num]['marker']) > 0 + " Substitute marker character if line length > 0 + let lines[line_num]['marker'] = substitute(lines[line_num]['marker'], '\%' . col_num . 'c.', target_key, '') + else + " Set the line to the marker character if the line is empty + let lines[line_num]['marker'] = target_key + endif + + " Add highlighting coordinates + call add(hl_coords, '\%' . line_num . 'l\%' . col_num . 'c') + + " Add marker/target lenght difference for multibyte + " compensation + let lines[line_num]['mb_compensation'] += (target_char_len - target_key_len) + endfor + + let lines_items = items(lines) + " }}} + " Highlight targets {{{ + let target_hl_id = matchadd(g:EasyMotion_hl_group_target, join(hl_coords, '\|'), 1) + " }}} + + try + " Set lines with markers + call s:SetLines(lines_items, 'marker') + + redraw + + " Get target character {{{ + call s:Prompt('Target key') + + let char = s:GetChar() + " }}} + finally + " Restore original lines + call s:SetLines(lines_items, 'orig') + + " Un-highlight targets {{{ + if exists('target_hl_id') + call matchdelete(target_hl_id) + endif + " }}} + + redraw + endtry + + " Check if we have an input char {{{ + if empty(char) + throw 'Cancelled' + endif + " }}} + " Check if the input char is valid {{{ + if ! has_key(a:groups, char) + throw 'Invalid target' + endif + " }}} + + let target = a:groups[char] + + if type(target) == 3 + " Return target coordinates + return target + else + " Prompt for new target character + return s:PromptUser(target) + endif + endfunction "}}} + function! s:EasyMotion(regexp, direction, visualmode, mode) " {{{ + let orig_pos = [line('.'), col('.')] + let targets = [] + + try + " Reset properties {{{ + call s:VarReset('&scrolloff', 0) + call s:VarReset('&modified', 0) + call s:VarReset('&modifiable', 1) + call s:VarReset('&readonly', 0) + call s:VarReset('&spell', 0) + call s:VarReset('&virtualedit', '') + " }}} + " Find motion targets {{{ + let search_direction = (a:direction == 1 ? 'b' : '') + let search_stopline = line(a:direction == 1 ? 'w0' : 'w$') + + while 1 + let pos = searchpos(a:regexp, search_direction, search_stopline) + + " Reached end of search range + if pos == [0, 0] + break + endif + + " Skip folded lines + if foldclosed(pos[0]) != -1 + continue + endif + + call add(targets, pos) + endwhile + + let targets_len = len(targets) + if targets_len == 0 + throw 'No matches' + endif + " }}} + + let GroupingFn = function('s:GroupingAlgorithm' . s:grouping_algorithms[g:EasyMotion_grouping]) + let groups = GroupingFn(targets, split(g:EasyMotion_keys, '\zs')) + + " Shade inactive source {{{ + if g:EasyMotion_do_shade + let shade_hl_pos = '\%' . orig_pos[0] . 'l\%'. orig_pos[1] .'c' + + if a:direction == 1 + " Backward + let shade_hl_re = '\%'. line('w0') .'l\_.*' . shade_hl_pos + else + " Forward + let shade_hl_re = shade_hl_pos . '\_.*\%'. line('w$') .'l' + endif + + let shade_hl_id = matchadd(g:EasyMotion_hl_group_shade, shade_hl_re, 0) + endif + " }}} + + " Prompt user for target group/character + let coords = s:PromptUser(groups) + + " Update selection {{{ + if ! empty(a:visualmode) + keepjumps call cursor(orig_pos[0], orig_pos[1]) + + exec 'normal! ' . a:visualmode + endif + " }}} + " Handle operator-pending mode {{{ + if a:mode == 'no' + " This mode requires that we eat one more + " character to the right if we're using + " a forward motion + if a:direction != 1 + let coords[1] += 1 + endif + endif + " }}} + + " Update cursor position + call cursor(orig_pos[0], orig_pos[1]) + mark ' + call cursor(coords[0], coords[1]) + + call s:Message('Jumping to [' . coords[0] . ', ' . coords[1] . ']') + catch + redraw + + " Show exception message + call s:Message(v:exception) + + " Restore original cursor position/selection {{{ + if ! empty(a:visualmode) + silent exec 'normal! gv' + else + keepjumps call cursor(orig_pos[0], orig_pos[1]) + endif + " }}} + finally + " Restore properties {{{ + call s:VarReset('&scrolloff') + call s:VarReset('&modified') + call s:VarReset('&modifiable') + call s:VarReset('&readonly') + call s:VarReset('&spell') + call s:VarReset('&virtualedit') + " }}} + " Remove shading {{{ + if g:EasyMotion_do_shade && exists('shade_hl_id') + call matchdelete(shade_hl_id) + endif + " }}} + endtry + endfunction " }}} +" }}} + +" vim: fdm=marker:noet:ts=4:sw=4:sts=4 diff --git a/vim/autoload/ZoomWin.vim b/vim/autoload/ZoomWin.vim new file mode 100644 index 0000000..d2d993f --- /dev/null +++ b/vim/autoload/ZoomWin.vim @@ -0,0 +1,380 @@ +" ZoomWin: Brief-like ability to zoom into/out-of a window +" Author: Charles Campbell +" original version by Ron Aaron +" Date: Jan 26, 2009 +" Version: 23 +" History: see :help zoomwin-history {{{1 +" GetLatestVimScripts: 508 1 :AutoInstall: ZoomWin.vim + +" --------------------------------------------------------------------- +" Load Once: {{{1 +if &cp || exists("g:loaded_ZoomWin") + finish +endif +if v:version < 702 + echohl WarningMsg + echo "***warning*** this version of ZoomWin needs vim 7.2" + echohl Normal + finish +endif +let s:keepcpo = &cpo +let g:loaded_ZoomWin = "v23" +set cpo&vim +"DechoTabOn + +" ===================================================================== +" Functions: {{{1 + +" --------------------------------------------------------------------- +" ZoomWin#ZoomWin: toggles between a single-window and a multi-window layout {{{2 +" The original version was by Ron Aaron. +fun! ZoomWin#ZoomWin() +" let g:decho_hide= 1 "Decho +" call Dfunc("ZoomWin#ZoomWin() winbufnr(2)=".winbufnr(2)) + + " if the vim doesn't have +mksession, only a partial zoom is available {{{3 + if !has("mksession") + if !exists("s:partialzoom") + echomsg "missing the +mksession feature; only a partial zoom is available" + let s:partialzoom= 0 + endif + if v:version < 630 + echoerr "***sorry*** you need an updated vim, preferably with +mksession" + elseif s:partialzoom + " partial zoom out + let s:partialzoom= 0 + exe s:winrestore + else + " partial zoom in + let s:partialzoom= 1 + let s:winrestore = winrestcmd() + res + endif +" call Dret("ZoomWin#ZoomWin : partialzoom=".s:partialzoom) + return + endif + + " Close certain windows {{{3 + call s:ZoomWinPreserve(0) + + " save options. Force window minimum height/width to be >= 1 {{{3 + let keep_hidden = &hidden + let keep_write = &write + + if v:version < 603 + if &wmh == 0 || &wmw == 0 + let keep_wmh = &wmh + let keep_wmw = &wmw + silent! set wmh=1 wmw=1 + endif + endif + set hidden write + + if winbufnr(2) == -1 + " there's only one window - restore to multiple-windows mode {{{3 +" call Decho("there's only one window - restore to multiple windows") + + if exists("s:sessionfile") && filereadable(s:sessionfile) + " save position in current one-window-only +" call Decho("save position in current one-window-only in sponly") + let sponly = s:SavePosn(0) + let s:origline = line(".") + let s:origcol = virtcol(".") + + " source session file to restore window layout + let ei_keep= &ei + set ei=all + exe 'silent! so '.fnameescape(s:sessionfile) +" Decho("@@<".@@.">") + let v:this_session= s:sesskeep + + if exists("s:savedposn1") + " restore windows' positioning and buffers +" call Decho("restore windows, positions, buffers") + windo call s:RestorePosn(s:savedposn{winnr()})|unlet s:savedposn{winnr()} + call s:GotoWinNum(s:winkeep) + unlet s:winkeep + endif + + if line(".") != s:origline || virtcol(".") != s:origcol + " If the cursor hasn't moved from the original position, + " then let the position remain what it was in the original + " multi-window layout. +" call Decho("restore position using sponly") + call s:RestorePosn(sponly) + endif + + " delete session file and variable holding its name +" call Decho("delete session file") + call delete(s:sessionfile) + unlet s:sessionfile + let &ei=ei_keep + endif + + else " there's more than one window - go to only-one-window mode {{{3 +" call Decho("there's multiple windows - goto one-window-only") + + let s:winkeep = winnr() + let s:sesskeep = v:this_session + + " doesn't work with the command line window (normal mode q:) + if &bt == "nofile" && expand("%") == (v:version < 702 ? 'command-line' : '[Command Line]') + echoerr "***error*** ZoomWin#ZoomWin doesn't work with the ".expand("%")." window" +" call Dret("ZoomWin#ZoomWin : ".expand('%')." window error") + return + endif +" call Decho("1: @@<".@@.">") + + " disable all events (autocmds) +" call Decho("disable events") + let ei_keep= &ei + set ei=all +" call Decho("2: @@<".@@.">") + + " save window positioning commands +" call Decho("save window positioning commands") + windo let s:savedposn{winnr()}= s:SavePosn(1) + call s:GotoWinNum(s:winkeep) + + " set up name of session file +" call Decho("3: @@<".@@.">") + let s:sessionfile= tempname() +" call Decho("4: @@<".@@.">") + + " save session +" call Decho("save session") + let ssop_keep = &ssop + let &ssop = 'blank,help,winsize,folds,globals,localoptions,options' +" call Decho("5: @@<".@@.">") + exe 'mksession! '.fnameescape(s:sessionfile) +" call Decho("6: @@<".@@.">") + let keepyy= @@ + let keepy0= @0 + let keepy1= @1 + let keepy2= @2 + let keepy3= @3 + let keepy4= @4 + let keepy5= @5 + let keepy6= @6 + let keepy7= @7 + let keepy8= @8 + let keepy9= @9 + set lz ei=all bh= + if v:version >= 700 + try + exe "keepalt keepmarks new! ".fnameescape(s:sessionfile) + catch /^Vim\%((\a\+)\)\=:E/ + echoerr "Too many windows" + silent! call delete(s:sessionfile) + unlet s:sessionfile +" call Dret("ZoomWin#ZoomWin : too many windows") + return + endtry + silent! keepjumps keepmarks v/wincmd\|split\|resize/d + keepalt w! + keepalt bw! + else + exe "new! ".fnameescape(s:sessionfile) + v/wincmd\|split\|resize/d + w! + bw! + endif + let @@= keepyy + let @0= keepy0 + let @1= keepy1 + let @2= keepy2 + let @3= keepy3 + let @4= keepy4 + let @5= keepy5 + let @6= keepy6 + let @7= keepy7 + let @8= keepy8 + let @9= keepy9 + call histdel('search', -1) + let @/ = histget('search', -1) +" call Decho("7: @@<".@@.">") + + " restore user's session options and restore event handling +" call Decho("restore user session options and event handling") + set nolz + let &ssop = ssop_keep + silent! only! +" call Decho("8: @@<".@@.">") + let &ei = ei_keep + echomsg expand("%") +" call Decho("9: @@<".@@.">") + endif + + " restore user option settings {{{3 +" call Decho("restore user option settings") + let &hidden= keep_hidden + let &write = keep_write + if v:version < 603 + if exists("keep_wmw") + let &wmh= keep_wmh + let &wmw= keep_wmw + endif + endif + + " Re-open certain windows {{{3 + call s:ZoomWinPreserve(1) + +" call Dret("ZoomWin#ZoomWin") +endfun + +" --------------------------------------------------------------------- +" SavePosn: this function sets up a savedposn variable that {{{2 +" has the commands necessary to restore the view +" of the current window. +fun! s:SavePosn(savewinhoriz) +" call Dfunc("SavePosn(savewinhoriz=".a:savewinhoriz.") file<".expand("%").">") + let swline = line(".") + if swline == 1 && getline(1) == "" + " empty buffer + let savedposn= "silent b ".winbufnr(0) +" call Dret("SavePosn savedposn<".savedposn.">") + return savedposn + endif + let swcol = col(".") + let swwline = winline()-1 + let swwcol = virtcol(".") - wincol() + let savedposn = "silent b ".winbufnr(0)."|".swline."|silent norm! z\<cr>" + if swwline > 0 + let savedposn= savedposn.":silent norm! ".swwline."\<c-y>\<cr>:silent norm! zs\<cr>" + endif + let savedposn= savedposn.":silent call cursor(".swline.",".swcol.")\<cr>" + + if a:savewinhoriz + if swwcol > 0 + let savedposn= savedposn.":silent norm! ".swwcol."zl\<cr>" + endif + + " handle certain special settings for the multi-window savedposn call + " bufhidden buftype buflisted + let settings= "" + if &bh != "" + let settings="bh=".&bh + setlocal bh=hide + endif + if !&bl + let settings= settings." nobl" + setlocal bl + endif + if &bt != "" + let settings= settings." bt=".&bt + setlocal bt= + endif + if settings != "" + let savedposn= savedposn.":setlocal ".settings."\<cr>" + endif + + endif +" call Dret("SavePosn savedposn<".savedposn.">") + return savedposn +endfun + +" --------------------------------------------------------------------- +" s:RestorePosn: this function restores noname and scratch windows {{{2 +fun! s:RestorePosn(savedposn) +" call Dfunc("RestorePosn(savedposn<".a:savedposn.">) file<".expand("%").">") + if &scb + setlocal noscb + exe a:savedposn + setlocal scb + else + exe a:savedposn + endif +" call Dret("RestorePosn") +endfun + +" --------------------------------------------------------------------- +" CleanupSessionFile: if you exit Vim before cleaning up the {{{2 +" supposed-to-be temporary session file +fun! ZoomWin#CleanupSessionFile() +" call Dfunc("ZoomWin#CleanupSessionFile()") + if exists("s:sessionfile") && filereadable(s:sessionfile) +" call Decho("sessionfile exists and is readable; deleting it") + silent! call delete(s:sessionfile) + unlet s:sessionfile + endif +" call Dret("ZoomWin#CleanupSessionFile") +endfun + +" --------------------------------------------------------------------- +" GotoWinNum: this function puts cursor into specified window {{{2 +fun! s:GotoWinNum(winnum) +" call Dfunc("GotoWinNum(winnum=".a:winnum.") winnr=".winnr()) + if a:winnum != winnr() + exe a:winnum."wincmd w" + endif +" call Dret("GotoWinNum") +endfun + + +" --------------------------------------------------------------------- +" ZoomWinPreserve: This function, largely written by David Fishburn, {{{2 +" allows ZoomWin to "preserve" certain windows: +" +" TagList, by Yegappan Lakshmanan +" http://vim.sourceforge.net/scripts/script.php?script_id=273 +" +" WinManager, by Srinath Avadhanula +" http://vim.sourceforge.net/scripts/script.php?script_id=95 +" +" It does so by closing the associated window upon entry to ZoomWin +" and re-opening it upon exit by using commands provided by the +" utilities themselves. +fun! s:ZoomWinPreserve(open) +" call Dfunc("ZoomWinPreserve(open=".a:open.")") + + if a:open == 0 + + " Close Taglist + if exists('g:zoomwin_preserve_taglist') && exists('g:loaded_taglist') + " If taglist window is open then close it. + let s:taglist_winnum = bufwinnr(g:TagList_title) + if s:taglist_winnum != -1 + " Close the window + exec "silent! Tlist" + endif + endif + + " Close Winmanager + if exists('g:zoomwin_preserve_winmanager') && exists('g:loaded_winmanager') + " If the winmanager window is open then close it. + let s:is_winmgr_vis = IsWinManagerVisible() + if s:is_winmgr_vis == 1 + exec "WMClose" + endif + endif + + else + + " Re-open Taglist + if exists('g:zoomwin_preserve_taglist') && exists('g:loaded_taglist') + " If taglist window was open, open it again + if s:taglist_winnum != -1 + exec "silent! Tlist" + endif + endif + + " Re-Open Winmanager + if exists('g:zoomwin_preserve_winmanager') && exists('g:loaded_winmanager') + " If the winmanager window is open then close it. + if s:is_winmgr_vis == 1 + exec "WManager" + endif + endif + endif + +" call Dret("ZoomWinPreserve") +endfun + +" ===================================================================== +" Restore: {{{1 +let &cpo= s:keepcpo +unlet s:keepcpo + +" --------------------------------------------------------------------- +" Modelines: {{{1 +" vim: ts=4 fdm=marker diff --git a/vim/autoload/conque_term.vim b/vim/autoload/conque_term.vim new file mode 100644 index 0000000..2780c7d --- /dev/null +++ b/vim/autoload/conque_term.vim @@ -0,0 +1,1590 @@ +" FILE: plugin/conque_term.vim {{{ +" +" AUTHOR: Nico Raffo <nicoraffo@gmail.com> +" MODIFIED: 2010-05-27 +" VERSION: 1.1, for Vim 7.0 +" LICENSE: +" Conque - pty interaction in Vim +" Copyright (C) 2009-2010 Nico Raffo +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining a copy +" of this software and associated documentation files (the "Software"), to deal +" in the Software without restriction, including without limitation the rights +" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +" copies of the Software, and to permit persons to whom the Software is +" furnished to do so, subject to the following conditions: +" +" The above copyright notice and this permission notice shall be included in +" all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +" THE SOFTWARE. +" }}} + + +" ********************************************************************************************************** +" **** VIM FUNCTIONS *************************************************************************************** +" ********************************************************************************************************** + +" launch conque +function! conque_term#open(...) "{{{ + let command = get(a:000, 0, '') + let hooks = get(a:000, 1, []) + + " bare minimum validation + if has('python') != 1 + echohl WarningMsg | echomsg "Conque requires the Python interface to be installed" | echohl None + return 0 + endif + if empty(command) + echohl WarningMsg | echomsg "No command found" | echohl None + return 0 + else + let l:cargs = split(command, '\s') + if !executable(l:cargs[0]) + echohl WarningMsg | echomsg "Not an executable: " . l:cargs[0] | echohl None + return 0 + endif + endif + + " set buffer window options + let g:ConqueTerm_BufName = substitute(command, ' ', '\\ ', 'g') . "\\ -\\ " . g:ConqueTerm_Idx + call conque_term#set_buffer_settings(command, hooks) + let b:ConqueTerm_Var = 'ConqueTerm_' . g:ConqueTerm_Idx + let g:ConqueTerm_Var = 'ConqueTerm_' . g:ConqueTerm_Idx + let g:ConqueTerm_Idx += 1 + + " open command + try + let l:config = '{"color":' . string(g:ConqueTerm_Color) . ',"TERM":"' . g:ConqueTerm_TERM . '"}' + execute 'python ' . b:ConqueTerm_Var . ' = Conque()' + execute "python " . b:ConqueTerm_Var . ".open('" . conque_term#python_escape(command) . "', " . l:config . ")" + catch + echohl WarningMsg | echomsg "Unable to open command: " . command | echohl None + return 0 + endtry + + " set buffer mappings and auto commands + call conque_term#set_mappings('start') + + startinsert! + return 1 +endfunction "}}} + +" set buffer options +function! conque_term#set_buffer_settings(command, pre_hooks) "{{{ + + " optional hooks to execute, e.g. 'split' + for h in a:pre_hooks + sil exe h + endfor + sil exe "edit " . g:ConqueTerm_BufName + + " buffer settings + setlocal nocompatible " conque won't work in compatible mode + setlocal nopaste " conque won't work in paste mode + setlocal buftype=nofile " this buffer is not a file, you can't save it + setlocal nonumber " hide line numbers + setlocal foldcolumn=0 " reasonable left margin + setlocal nowrap " default to no wrap (esp with MySQL) + setlocal noswapfile " don't bother creating a .swp file + setlocal updatetime=50 " trigger cursorhold event after 50ms / XXX - global + setlocal scrolloff=0 " don't use buffer lines. it makes the 'clear' command not work as expected + setlocal sidescrolloff=0 " don't use buffer lines. it makes the 'clear' command not work as expected + setlocal sidescroll=1 " don't use buffer lines. it makes the 'clear' command not work as expected + setlocal foldmethod=manual " don't fold on {{{}}} and stuff + setlocal bufhidden=hide " when buffer is no longer displayed, don't wipe it out + setfiletype conque_term " useful + sil exe "setlocal syntax=" . g:ConqueTerm_Syntax + +endfunction " }}} + +" set key mappings and auto commands +function! conque_term#set_mappings(action) "{{{ + + " set action + if a:action == 'toggle' + if exists('b:conque_on') && b:conque_on == 1 + let l:action = 'stop' + echohl WarningMsg | echomsg "Terminal is paused" | echohl None + else + let l:action = 'start' + echohl WarningMsg | echomsg "Terminal is resumed" | echohl None + endif + else + let l:action = a:action + endif + + " if mappings are being removed, add 'un' + let map_modifier = 'nore' + if l:action == 'stop' + let map_modifier = 'un' + endif + + " remove all auto commands + if l:action == 'stop' + execute 'autocmd! ' . b:ConqueTerm_Var + + else + execute 'augroup ' . b:ConqueTerm_Var + + " handle unexpected closing of shell, passes HUP to parent and all child processes + execute 'autocmd ' . b:ConqueTerm_Var . ' BufUnload <buffer> python ' . b:ConqueTerm_Var . '.proc.signal(1)' + + " check for resized/scrolled buffer when entering buffer + execute 'autocmd ' . b:ConqueTerm_Var . ' BufEnter <buffer> python ' . b:ConqueTerm_Var . '.update_window_size()' + execute 'autocmd ' . b:ConqueTerm_Var . ' VimResized python ' . b:ConqueTerm_Var . '.update_window_size()' + + " set/reset updatetime on entering/exiting buffer + autocmd BufEnter <buffer> set updatetime=50 + autocmd BufLeave <buffer> set updatetime=2000 + + " check for resized/scrolled buffer when entering insert mode + " XXX - messed up since we enter insert mode at each updatetime + "execute 'autocmd InsertEnter <buffer> python ' . b:ConqueTerm_Var . '.screen.align()' + + " read more output when this isn't the current buffer + if g:ConqueTerm_ReadUnfocused == 1 + execute 'autocmd ' . b:ConqueTerm_Var . ' CursorHold * call conque_term#read_all()' + endif + + " poll for more output + sil execute 'autocmd ' . b:ConqueTerm_Var . ' CursorHoldI <buffer> python ' . b:ConqueTerm_Var . '.auto_read()' + endif + + " use F22 key to get more input + if l:action == 'start' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <expr> <F22> "\<left>\<right>"' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <expr> <F23> "\<right>\<left>"' + else + sil exe 'i' . map_modifier . 'map <silent> <buffer> <expr> <F22>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <expr> <F23>' + endif + + " map ASCII 1-31 + for c in range(1, 31) + " <Esc> + if c == 27 + continue + endif + if l:action == 'start' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <C-' . nr2char(64 + c) . '> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(' . c . '))<CR>' + else + sil exe 'i' . map_modifier . 'map <silent> <buffer> <C-' . nr2char(64 + c) . '>' + endif + endfor + if l:action == 'start' + sil exe 'n' . map_modifier . 'map <silent> <buffer> <C-c> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(3))<CR>' + else + sil exe 'n' . map_modifier . 'map <silent> <buffer> <C-c>' + endif + + " leave insert mode + if !exists('g:ConqueTerm_EscKey') || g:ConqueTerm_EscKey == '<Esc>' + " use <Esc><Esc> to send <Esc> to terminal + if l:action == 'start' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Esc><Esc> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(27))<CR>' + else + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Esc><Esc>' + endif + else + " use <Esc> to send <Esc> to terminal + if l:action == 'start' + sil exe 'i' . map_modifier . 'map <silent> <buffer> ' . g:ConqueTerm_EscKey . ' <Esc>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Esc> <C-o>:python ' . b:ConqueTerm_Var . '.write(chr(27))<CR>' + else + sil exe 'i' . map_modifier . 'map <silent> <buffer> ' . g:ConqueTerm_EscKey + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Esc>' + endif + endif + + " Map <C-w> in insert mode + if exists('g:ConqueTerm_CWInsert') && g:ConqueTerm_CWInsert == 1 + inoremap <silent> <buffer> <C-w>j <Esc><C-w>j + inoremap <silent> <buffer> <C-w>k <Esc><C-w>k + inoremap <silent> <buffer> <C-w>h <Esc><C-w>h + inoremap <silent> <buffer> <C-w>l <Esc><C-w>l + inoremap <silent> <buffer> <C-w>w <Esc><C-w>w + endif + + " map ASCII 33-127 + for i in range(33, 127) + " <Bar> + if i == 124 + if l:action == 'start' + sil exe "i" . map_modifier . "map <silent> <buffer> <Bar> <C-o>:python " . b:ConqueTerm_Var . ".write(chr(124))<CR>" + else + sil exe "i" . map_modifier . "map <silent> <buffer> <Bar>" + endif + continue + endif + if l:action == 'start' + sil exe "i" . map_modifier . "map <silent> <buffer> " . nr2char(i) . " <C-o>:python " . b:ConqueTerm_Var . ".write(chr(" . i . "))<CR>" + else + sil exe "i" . map_modifier . "map <silent> <buffer> " . nr2char(i) + endif + endfor + + " map ASCII 128-255 + for i in range(128, 255) + if l:action == 'start' + sil exe "i" . map_modifier . "map <silent> <buffer> " . nr2char(i) . " <C-o>:python " . b:ConqueTerm_Var . ".write('" . nr2char(i) . "')<CR>" + else + sil exe "i" . map_modifier . "map <silent> <buffer> " . nr2char(i) + endif + endfor + + " Special cases + if l:action == 'start' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <BS> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u0008")<CR>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Space> <C-o>:python ' . b:ConqueTerm_Var . '.write(" ")<CR>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Up> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[A")<CR>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Down> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[B")<CR>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Right> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[C")<CR>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Left> <C-o>:python ' . b:ConqueTerm_Var . '.write(u"\u001b[D")<CR>' + else + sil exe 'i' . map_modifier . 'map <silent> <buffer> <BS>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Space>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Up>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Down>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Right>' + sil exe 'i' . map_modifier . 'map <silent> <buffer> <Left>' + endif + + " send selected text into conque + if l:action == 'start' + sil exe 'v' . map_modifier . 'map <silent> <F9> :<C-u>call conque_term#send_selected(visualmode())<CR>' + else + sil exe 'v' . map_modifier . 'map <silent> <F9>' + endif + + " remap paste keys + if l:action == 'start' + sil exe 'n' . map_modifier . 'map <silent> <buffer> p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a' + sil exe 'n' . map_modifier . 'map <silent> <buffer> P :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a' + sil exe 'n' . map_modifier . 'map <silent> <buffer> ]p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a' + sil exe 'n' . map_modifier . 'map <silent> <buffer> [p :python ' . b:ConqueTerm_Var . '.write(vim.eval("@@"))<CR>a' + else + sil exe 'n' . map_modifier . 'map <silent> <buffer> p' + sil exe 'n' . map_modifier . 'map <silent> <buffer> P' + sil exe 'n' . map_modifier . 'map <silent> <buffer> ]p' + sil exe 'n' . map_modifier . 'map <silent> <buffer> [p' + endif + if has('gui_running') + if l:action == 'start' + sil exe 'i' . map_modifier . 'map <buffer> <S-Insert> <Esc>:<C-u>python ' . b:ConqueTerm_Var . ".write(vim.eval('@+'))<CR>a" + else + sil exe 'i' . map_modifier . 'map <buffer> <S-Insert>' + endif + endif + + " disable other normal mode keys which insert text + if l:action == 'start' + sil exe 'n' . map_modifier . 'map <silent> <buffer> r :echo "Replace mode disabled in shell."<CR>' + sil exe 'n' . map_modifier . 'map <silent> <buffer> R :echo "Replace mode disabled in shell."<CR>' + sil exe 'n' . map_modifier . 'map <silent> <buffer> c :echo "Change mode disabled in shell."<CR>' + sil exe 'n' . map_modifier . 'map <silent> <buffer> C :echo "Change mode disabled in shell."<CR>' + sil exe 'n' . map_modifier . 'map <silent> <buffer> s :echo "Change mode disabled in shell."<CR>' + sil exe 'n' . map_modifier . 'map <silent> <buffer> S :echo "Change mode disabled in shell."<CR>' + else + sil exe 'n' . map_modifier . 'map <silent> <buffer> r' + sil exe 'n' . map_modifier . 'map <silent> <buffer> R' + sil exe 'n' . map_modifier . 'map <silent> <buffer> c' + sil exe 'n' . map_modifier . 'map <silent> <buffer> C' + sil exe 'n' . map_modifier . 'map <silent> <buffer> s' + sil exe 'n' . map_modifier . 'map <silent> <buffer> S' + endif + + " set conque as on or off + if l:action == 'start' + let b:conque_on = 1 + else + let b:conque_on = 0 + endif + + " map command to start stop the shell + if a:action == 'start' + nnoremap <F5> :<C-u>call conque_term#set_mappings('toggle')<CR> + endif + +endfunction " }}} + + +" send selected text from another buffer +function! conque_term#send_selected(type) "{{{ + let reg_save = @@ + + " save user's sb settings + let sb_save = &switchbuf + set switchbuf=usetab + + " yank current selection + sil exe "normal! `<" . a:type . "`>y" + + " format yanked text + let @@ = substitute(@@, '^[\r\n]*', '', '') + let @@ = substitute(@@, '[\r\n]*$', '', '') + + " execute yanked text + sil exe ":sb " . g:ConqueTerm_BufName + sil exe 'python ' . g:ConqueTerm_Var . '.paste_selection()' + + " reset original values + let @@ = reg_save + sil exe 'set switchbuf=' . sb_save + + " scroll buffer left + startinsert! + normal 0zH +endfunction "}}} + +" read from all known conque buffers +function! conque_term#read_all() "{{{ + " don't run this if we're in a conque buffer + if exists('b:ConqueTerm_Var') + return + endif + + try + for i in range(1, g:ConqueTerm_Idx - 1) + execute 'python ConqueTerm_' . string(i) . '.read(1)' + endfor + catch + " probably a deleted buffer + endtry + + " restart updatetime + call feedkeys("f\e") +endfunction "}}} + +" util function to add enough \s to pass a string to python +function! conque_term#python_escape(input) "{{{ + let l:cleaned = a:input + let l:cleaned = substitute(l:cleaned, '\\', '\\\\', 'g') + let l:cleaned = substitute(l:cleaned, '\n', '\\n', 'g') + let l:cleaned = substitute(l:cleaned, '\r', '\\r', 'g') + let l:cleaned = substitute(l:cleaned, "'", "\\\\'", 'g') + return l:cleaned +endfunction "}}} + +" ********************************************************************************************************** +" **** PYTHON ********************************************************************************************** +" ********************************************************************************************************** + +if has('python') + +python << EOF + +import vim, re, time, math + +# CONFIG CONSTANTS {{{ + +CONQUE_CTL = { + 7:'bel', # bell + 8:'bs', # backspace + 9:'tab', # tab + 10:'nl', # new line + 13:'cr' # carriage return +} +# 11 : 'vt', # vertical tab +# 12 : 'ff', # form feed +# 14 : 'so', # shift out +# 15 : 'si' # shift in + +# Escape sequences +CONQUE_ESCAPE = { + 'm':'font', + 'J':'clear_screen', + 'K':'clear_line', + '@':'add_spaces', + 'A':'cursor_up', + 'B':'cursor_down', + 'C':'cursor_right', + 'D':'cursor_left', + 'G':'cursor_to_column', + 'H':'cursor', + 'P':'delete_chars', + 'f':'cursor', + 'g':'tab_clear', + 'r':'set_coords', + 'h':'set', + 'l':'reset' +} +# 'L':'insert_lines', +# 'M':'delete_lines', +# 'd':'cusor_vpos', + +# Alternate escape sequences, no [ +CONQUE_ESCAPE_PLAIN = { + 'D':'scroll_up', + 'E':'next_line', + 'H':'set_tab', + 'M':'scroll_down' +} +# 'N':'single_shift_2', +# 'O':'single_shift_3', +# '=':'alternate_keypad', +# '>':'numeric_keypad', +# '7':'save_cursor', +# '8':'restore_cursor', + +# Uber alternate escape sequences, with # or ? +CONQUE_ESCAPE_QUESTION = { + '1h':'new_line_mode', + '3h':'132_cols', + '4h':'smooth_scrolling', + '5h':'reverse_video', + '6h':'relative_origin', + '7h':'set_auto_wrap', + '8h':'set_auto_repeat', + '9h':'set_interlacing_mode', + '1l':'set_cursor_key', + '2l':'set_vt52', + '3l':'80_cols', + '4l':'set_jump_scrolling', + '5l':'normal_video', + '6l':'absolute_origin', + '7l':'reset_auto_wrap', + '8l':'reset_auto_repeat', + '9l':'reset_interlacing_mode' +} + +CONQUE_ESCAPE_HASH = { + '8':'screen_alignment_test' +} +# '3':'double_height_top', +# '4':'double_height_bottom', +# '5':'single_height_single_width', +# '6':'single_height_double_width', + +# Font codes {{{ +CONQUE_FONT = { + 0: {'description':'Normal (default)', 'attributes': {'cterm':'NONE','ctermfg':'NONE','ctermbg':'NONE','gui':'NONE','guifg':'NONE','guibg':'NONE'}, 'normal':True}, + 1: {'description':'Bold', 'attributes': {'cterm':'BOLD','gui':'BOLD'}, 'normal':False}, + 4: {'description':'Underlined', 'attributes': {'cterm':'UNDERLINE','gui':'UNDERLINE'}, 'normal':False}, + 5: {'description':'Blink (appears as Bold)', 'attributes': {'cterm':'BOLD','gui':'BOLD'}, 'normal':False}, + 7: {'description':'Inverse', 'attributes': {'cterm':'REVERSE','gui':'REVERSE'}, 'normal':False}, + 8: {'description':'Invisible (hidden)', 'attributes': {'ctermfg':'0','ctermbg':'0','guifg':'#000000','guibg':'#000000'}, 'normal':False}, + 22: {'description':'Normal (neither bold nor faint)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, + 24: {'description':'Not underlined', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, + 25: {'description':'Steady (not blinking)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, + 27: {'description':'Positive (not inverse)', 'attributes': {'cterm':'NONE','gui':'NONE'}, 'normal':True}, + 28: {'description':'Visible (not hidden)', 'attributes': {'ctermfg':'NONE','ctermbg':'NONE','guifg':'NONE','guibg':'NONE'}, 'normal':True}, + 30: {'description':'Set foreground color to Black', 'attributes': {'ctermfg':'16','guifg':'#000000'}, 'normal':False}, + 31: {'description':'Set foreground color to Red', 'attributes': {'ctermfg':'1','guifg':'#ff0000'}, 'normal':False}, + 32: {'description':'Set foreground color to Green', 'attributes': {'ctermfg':'2','guifg':'#00ff00'}, 'normal':False}, + 33: {'description':'Set foreground color to Yellow', 'attributes': {'ctermfg':'3','guifg':'#ffff00'}, 'normal':False}, + 34: {'description':'Set foreground color to Blue', 'attributes': {'ctermfg':'4','guifg':'#0000ff'}, 'normal':False}, + 35: {'description':'Set foreground color to Magenta', 'attributes': {'ctermfg':'5','guifg':'#990099'}, 'normal':False}, + 36: {'description':'Set foreground color to Cyan', 'attributes': {'ctermfg':'6','guifg':'#009999'}, 'normal':False}, + 37: {'description':'Set foreground color to White', 'attributes': {'ctermfg':'7','guifg':'#ffffff'}, 'normal':False}, + 39: {'description':'Set foreground color to default (original)', 'attributes': {'ctermfg':'NONE','guifg':'NONE'}, 'normal':True}, + 40: {'description':'Set background color to Black', 'attributes': {'ctermbg':'16','guibg':'#000000'}, 'normal':False}, + 41: {'description':'Set background color to Red', 'attributes': {'ctermbg':'1','guibg':'#ff0000'}, 'normal':False}, + 42: {'description':'Set background color to Green', 'attributes': {'ctermbg':'2','guibg':'#00ff00'}, 'normal':False}, + 43: {'description':'Set background color to Yellow', 'attributes': {'ctermbg':'3','guibg':'#ffff00'}, 'normal':False}, + 44: {'description':'Set background color to Blue', 'attributes': {'ctermbg':'4','guibg':'#0000ff'}, 'normal':False}, + 45: {'description':'Set background color to Magenta', 'attributes': {'ctermbg':'5','guibg':'#990099'}, 'normal':False}, + 46: {'description':'Set background color to Cyan', 'attributes': {'ctermbg':'6','guibg':'#009999'}, 'normal':False}, + 47: {'description':'Set background color to White', 'attributes': {'ctermbg':'7','guibg':'#ffffff'}, 'normal':False}, + 49: {'description':'Set background color to default (original).', 'attributes': {'ctermbg':'NONE','guibg':'NONE'}, 'normal':True}, + 90: {'description':'Set foreground color to Black', 'attributes': {'ctermfg':'8','guifg':'#000000'}, 'normal':False}, + 91: {'description':'Set foreground color to Red', 'attributes': {'ctermfg':'9','guifg':'#ff0000'}, 'normal':False}, + 92: {'description':'Set foreground color to Green', 'attributes': {'ctermfg':'10','guifg':'#00ff00'}, 'normal':False}, + 93: {'description':'Set foreground color to Yellow', 'attributes': {'ctermfg':'11','guifg':'#ffff00'}, 'normal':False}, + 94: {'description':'Set foreground color to Blue', 'attributes': {'ctermfg':'12','guifg':'#0000ff'}, 'normal':False}, + 95: {'description':'Set foreground color to Magenta', 'attributes': {'ctermfg':'13','guifg':'#990099'}, 'normal':False}, + 96: {'description':'Set foreground color to Cyan', 'attributes': {'ctermfg':'14','guifg':'#009999'}, 'normal':False}, + 97: {'description':'Set foreground color to White', 'attributes': {'ctermfg':'15','guifg':'#ffffff'}, 'normal':False}, + 100: {'description':'Set background color to Black', 'attributes': {'ctermbg':'8','guibg':'#000000'}, 'normal':False}, + 101: {'description':'Set background color to Red', 'attributes': {'ctermbg':'9','guibg':'#ff0000'}, 'normal':False}, + 102: {'description':'Set background color to Green', 'attributes': {'ctermbg':'10','guibg':'#00ff00'}, 'normal':False}, + 103: {'description':'Set background color to Yellow', 'attributes': {'ctermbg':'11','guibg':'#ffff00'}, 'normal':False}, + 104: {'description':'Set background color to Blue', 'attributes': {'ctermbg':'12','guibg':'#0000ff'}, 'normal':False}, + 105: {'description':'Set background color to Magenta', 'attributes': {'ctermbg':'13','guibg':'#990099'}, 'normal':False}, + 106: {'description':'Set background color to Cyan', 'attributes': {'ctermbg':'14','guibg':'#009999'}, 'normal':False}, + 107: {'description':'Set background color to White', 'attributes': {'ctermbg':'15','guibg':'#ffffff'}, 'normal':False} +} +# }}} + +# regular expression matching (almost) all control sequences +CONQUE_SEQ_REGEX = re.compile(ur"(\u001b\[?\??#?[0-9;]*[a-zA-Z@]|\u001b\][0-9];.*?\u0007|[\u0007-\u000f])", re.UNICODE) +CONQUE_SEQ_REGEX_CTL = re.compile(ur"^[\u0007-\u000f]$", re.UNICODE) +CONQUE_SEQ_REGEX_CSI = re.compile(ur"^\u001b\[", re.UNICODE) +CONQUE_SEQ_REGEX_TITLE = re.compile(ur"^\u001b\]", re.UNICODE) +CONQUE_SEQ_REGEX_HASH = re.compile(ur"^\u001b#", re.UNICODE) +CONQUE_SEQ_REGEX_ESC = re.compile(ur"^\u001b", re.UNICODE) + +# match table output +CONQUE_TABLE_OUTPUT = re.compile("^\s*\|\s.*\s\|\s*$|^\s*\+[=+-]+\+\s*$") + +# }}} + +################################################################################################### +class Conque: + + # CLASS PROPERTIES {{{ + + # screen object + screen = None + + # subprocess object + proc = None + + # terminal dimensions and scrolling region + columns = 80 # same as $COLUMNS + lines = 24 # same as $LINES + working_columns = 80 # can be changed by CSI ? 3 l/h + working_lines = 24 # can be changed by CSI r + + # top/bottom of the scroll region + top = 1 # relative to top of screen + bottom = 24 # relative to top of screen + + # cursor position + l = 1 # current cursor line + c = 1 # current cursor column + + # autowrap mode + autowrap = True + + # absolute coordinate mode + absolute_coords = True + + # tabstop positions + tabstops = [] + + # enable colors + enable_colors = True + + # color changes + color_changes = {} + + # color history + color_history = {} + + # don't wrap table output + unwrap_tables = True + + # wrap CUF/CUB around line breaks + wrap_cursor = False + + # }}} + + # constructor + def __init__(self): # {{{ + self.screen = ConqueScreen() + # }}} + + # start program and initialize this instance + def open(self, command, options): # {{{ + + # int vars + self.columns = vim.current.window.width + self.lines = vim.current.window.height + self.working_columns = vim.current.window.width + self.working_lines = vim.current.window.height + self.bottom = vim.current.window.height + + # init color + self.enable_colors = options['color'] + + # init tabstops + self.init_tabstops() + + # open command + self.proc = ConqueSubprocess() + self.proc.open(command, { 'TERM' : options['TERM'], 'CONQUE' : '1', 'LINES' : str(self.lines), 'COLUMNS' : str(self.columns)}) + # }}} + + # write to pty + def write(self, input): # {{{ + + + # check if window size has changed + self.update_window_size() + + # write and read + self.proc.write(input) + self.read(1) + # }}} + + # read from pty, and update buffer + def read(self, timeout = 1): # {{{ + # read from subprocess + output = self.proc.read(timeout) + # and strip null chars + output = output.replace(chr(0), '') + + if output == '': + return + + + + + + chunks = CONQUE_SEQ_REGEX.split(output) + + + + + + # don't go through all the csi regex if length is one (no matches) + if len(chunks) == 1: + + self.plain_text(chunks[0]) + + else: + for s in chunks: + if s == '': + continue + + + + + + + # Check for control character match {{{ + if CONQUE_SEQ_REGEX_CTL.match(s[0]): + + nr = ord(s[0]) + if nr in CONQUE_CTL: + getattr(self, 'ctl_' + CONQUE_CTL[nr])() + else: + + pass + # }}} + + # check for escape sequence match {{{ + elif CONQUE_SEQ_REGEX_CSI.match(s): + + if s[-1] in CONQUE_ESCAPE: + csi = self.parse_csi(s[2:]) + + getattr(self, 'csi_' + CONQUE_ESCAPE[s[-1]])(csi) + else: + + pass + # }}} + + # check for title match {{{ + elif CONQUE_SEQ_REGEX_TITLE.match(s): + + self.change_title(s[2], s[4:-1]) + # }}} + + # check for hash match {{{ + elif CONQUE_SEQ_REGEX_HASH.match(s): + + if s[-1] in CONQUE_ESCAPE_HASH: + getattr(self, 'hash_' + CONQUE_ESCAPE_HASH[s[-1]])() + else: + + pass + # }}} + + # check for other escape match {{{ + elif CONQUE_SEQ_REGEX_ESC.match(s): + + if s[-1] in CONQUE_ESCAPE_PLAIN: + getattr(self, 'esc_' + CONQUE_ESCAPE_PLAIN[s[-1]])() + else: + + pass + # }}} + + # else process plain text {{{ + else: + self.plain_text(s) + # }}} + + # set cursor position + self.screen.set_cursor(self.l, self.c) + + vim.command('redraw') + + + # }}} + + # for polling + def auto_read(self): # {{{ + self.read(1) + if self.c == 1: + vim.command('call feedkeys("\<F23>", "t")') + else: + vim.command('call feedkeys("\<F22>", "t")') + self.screen.set_cursor(self.l, self.c) + # }}} + + ############################################################################################### + # Plain text # {{{ + + def plain_text(self, input): + + current_line = self.screen[self.l] + + if len(current_line) < self.working_columns: + current_line = current_line + ' ' * (self.c - len(current_line)) + + # if line is wider than screen + if self.c + len(input) - 1 > self.working_columns: + # Table formatting hack + if self.unwrap_tables and CONQUE_TABLE_OUTPUT.match(input): + self.screen[self.l] = current_line[ : self.c - 1] + input + current_line[ self.c + len(input) - 1 : ] + self.apply_color(self.c, self.c + len(input)) + self.c += len(input) + return + + diff = self.c + len(input) - self.working_columns - 1 + # if autowrap is enabled + if self.autowrap: + self.screen[self.l] = current_line[ : self.c - 1] + input[ : -1 * diff ] + self.apply_color(self.c, self.working_columns) + self.ctl_nl() + self.ctl_cr() + remaining = input[ -1 * diff : ] + + self.plain_text(remaining) + else: + self.screen[self.l] = current_line[ : self.c - 1] + input[ : -1 * diff - 1 ] + input[-1] + self.apply_color(self.c, self.working_columns) + self.c = self.working_columns + + # no autowrap + else: + self.screen[self.l] = current_line[ : self.c - 1] + input + current_line[ self.c + len(input) - 1 : ] + self.apply_color(self.c, self.c + len(input)) + self.c += len(input) + + def apply_color(self, start, end): + + + # stop here if coloration is disabled + if not self.enable_colors: + return + + real_line = self.screen.get_real_line(self.l) + + # check for previous overlapping coloration + + to_del = [] + if self.color_history.has_key(real_line): + for i in range(len(self.color_history[real_line])): + syn = self.color_history[real_line][i] + + if syn['start'] >= start and syn['start'] < end: + + vim.command('syn clear ' + syn['name']) + to_del.append(i) + # outside + if syn['end'] > end: + + self.exec_highlight(real_line, end, syn['end'], syn['highlight']) + elif syn['end'] > start and syn['end'] <= end: + + vim.command('syn clear ' + syn['name']) + to_del.append(i) + # outside + if syn['start'] < start: + + self.exec_highlight(real_line, syn['start'], start, syn['highlight']) + + if len(to_del) > 0: + to_del.reverse() + for di in to_del: + del self.color_history[real_line][di] + + # if there are no new colors + if len(self.color_changes) == 0: + return + + highlight = '' + for attr in self.color_changes.keys(): + highlight = highlight + ' ' + attr + '=' + self.color_changes[attr] + + # execute the highlight + self.exec_highlight(real_line, start, end, highlight) + + def exec_highlight(self, real_line, start, end, highlight): + unique_key = str(self.proc.pid) + + syntax_name = 'EscapeSequenceAt_' + unique_key + '_' + str(self.l) + '_' + str(start) + '_' + str(len(self.color_history) + 1) + syntax_options = ' contains=ALLBUT,ConqueString,MySQLString,MySQLKeyword oneline' + syntax_region = 'syntax match ' + syntax_name + ' /\%' + str(real_line) + 'l\%>' + str(start - 1) + 'c.*\%<' + str(end + 1) + 'c/' + syntax_options + syntax_highlight = 'highlight ' + syntax_name + highlight + + vim.command(syntax_region) + vim.command(syntax_highlight) + + # add syntax name to history + if not self.color_history.has_key(real_line): + self.color_history[real_line] = [] + + self.color_history[real_line].append({'name':syntax_name, 'start':start, 'end':end, 'highlight':highlight}) + + # }}} + + ############################################################################################### + # Control functions {{{ + + def ctl_nl(self): + # if we're in a scrolling region, scroll instead of moving cursor down + if self.lines != self.working_lines and self.l == self.bottom: + del self.screen[self.top] + self.screen.insert(self.bottom, '') + elif self.l == self.bottom: + self.screen.append('') + else: + self.l += 1 + + self.color_changes = {} + + def ctl_cr(self): + self.c = 1 + + self.color_changes = {} + + def ctl_bs(self): + if self.c > 1: + self.c += -1 + + def ctl_bel(self): + print 'BELL' + + def ctl_tab(self): + # default tabstop location + ts = self.working_columns + + # check set tabstops + for i in range(self.c, len(self.tabstops)): + if self.tabstops[i]: + ts = i + 1 + break + + + + self.c = ts + + # }}} + + ############################################################################################### + # CSI functions {{{ + + def csi_font(self, csi): # {{{ + if not self.enable_colors: + return + + # defaults to 0 + if len(csi['vals']) == 0: + csi['vals'] = [0] + + # 256 xterm color foreground + if len(csi['vals']) == 3 and csi['vals'][0] == 38 and csi['vals'][1] == 5: + self.color_changes['ctermfg'] = str(csi['vals'][2]) + self.color_changes['guifg'] = '#' + self.xterm_to_rgb(csi['vals'][2]) + + # 256 xterm color background + elif len(csi['vals']) == 3 and csi['vals'][0] == 48 and csi['vals'][1] == 5: + self.color_changes['ctermbg'] = str(csi['vals'][2]) + self.color_changes['guibg'] = '#' + self.xterm_to_rgb(csi['vals'][2]) + + # 16 colors + else: + for val in csi['vals']: + if CONQUE_FONT.has_key(val): + + # ignore starting normal colors + if CONQUE_FONT[val]['normal'] and len(self.color_changes) == 0: + + continue + # clear color changes + elif CONQUE_FONT[val]['normal']: + + self.color_changes = {} + # save these color attributes for next plain_text() call + else: + + for attr in CONQUE_FONT[val]['attributes'].keys(): + if self.color_changes.has_key(attr) and (attr == 'cterm' or attr == 'gui'): + self.color_changes[attr] += ',' + CONQUE_FONT[val]['attributes'][attr] + else: + self.color_changes[attr] = CONQUE_FONT[val]['attributes'][attr] + # }}} + + def csi_clear_line(self, csi): # {{{ + + + # this escape defaults to 0 + if len(csi['vals']) == 0: + csi['val'] = 0 + + + + + # 0 means cursor right + if csi['val'] == 0: + self.screen[self.l] = self.screen[self.l][0 : self.c - 1] + + # 1 means cursor left + elif csi['val'] == 1: + self.screen[self.l] = ' ' * (self.c) + self.screen[self.l][self.c : ] + + # clear entire line + elif csi['val'] == 2: + self.screen[self.l] = '' + + # clear colors + if csi['val'] == 2 or (csi['val'] == 0 and self.c == 1): + real_line = self.screen.get_real_line(self.l) + if self.color_history.has_key(real_line): + for syn in self.color_history[real_line]: + vim.command('syn clear ' + syn['name']) + + + + # }}} + + def csi_cursor_right(self, csi): # {{{ + # we use 1 even if escape explicitly specifies 0 + if csi['val'] == 0: + csi['val'] = 1 + + + + + if self.wrap_cursor and self.c + csi['val'] > self.working_columns: + self.l += int(math.floor( (self.c + csi['val']) / self.working_columns )) + self.c = (self.c + csi['val']) % self.working_columns + return + + self.c = self.bound(self.c + csi['val'], 1, self.working_columns) + # }}} + + def csi_cursor_left(self, csi): # {{{ + # we use 1 even if escape explicitly specifies 0 + if csi['val'] == 0: + csi['val'] = 1 + + if self.wrap_cursor and csi['val'] >= self.c: + self.l += int(math.floor( (self.c - csi['val']) / self.working_columns )) + self.c = self.working_columns - (csi['val'] - self.c) % self.working_columns + return + + self.c = self.bound(self.c - csi['val'], 1, self.working_columns) + # }}} + + def csi_cursor_to_column(self, csi): # {{{ + self.c = self.bound(csi['val'], 1, self.working_columns) + # }}} + + def csi_cursor_up(self, csi): # {{{ + self.l = self.bound(self.l - csi['val'], self.top, self.bottom) + + self.color_changes = {} + # }}} + + def csi_cursor_down(self, csi): # {{{ + self.l = self.bound(self.l + csi['val'], self.top, self.bottom) + + self.color_changes = {} + # }}} + + def csi_clear_screen(self, csi): # {{{ + # default to 0 + if len(csi['vals']) == 0: + csi['val'] = 0 + + # 2 == clear entire screen + if csi['val'] == 2: + self.l = 1 + self.c = 1 + self.screen.clear() + + # 0 == clear down + elif csi['val'] == 0: + for l in range(self.bound(self.l + 1, 1, self.lines), self.lines + 1): + self.screen[l] = '' + + # clear end of current line + self.csi_clear_line(self.parse_csi('K')) + + # 1 == clear up + elif csi['val'] == 1: + for l in range(1, self.bound(self.l, 1, self.lines + 1)): + self.screen[l] = '' + + # clear beginning of current line + self.csi_clear_line(self.parse_csi('1K')) + + # clear coloration + if csi['val'] == 2 or csi['val'] == 0: + real_line = self.screen.get_real_line(self.l) + for line in self.color_history.keys(): + if line >= real_line: + for syn in self.color_history[line]: + vim.command('syn clear ' + syn['name']) + + self.color_changes = {} + # }}} + + def csi_delete_chars(self, csi): # {{{ + self.screen[self.l] = self.screen[self.l][ : self.c ] + self.screen[self.l][ self.c + csi['val'] : ] + # }}} + + def csi_add_spaces(self, csi): # {{{ + self.screen[self.l] = self.screen[self.l][ : self.c - 1] + ' ' * csi['val'] + self.screen[self.l][self.c : ] + # }}} + + def csi_cursor(self, csi): # {{{ + if len(csi['vals']) == 2: + new_line = csi['vals'][0] + new_col = csi['vals'][1] + else: + new_line = 1 + new_col = 1 + + if self.absolute_coords: + self.l = self.bound(new_line, 1, self.lines) + else: + self.l = self.bound(self.top + new_line - 1, self.top, self.bottom) + + self.c = self.bound(new_col, 1, self.working_columns) + if self.c > len(self.screen[self.l]): + self.screen[self.l] = self.screen[self.l] + ' ' * (self.c - len(self.screen[self.l])) + + # }}} + + def csi_set_coords(self, csi): # {{{ + if len(csi['vals']) == 2: + new_start = csi['vals'][0] + new_end = csi['vals'][1] + else: + new_start = 1 + new_end = vim.current.window.height + + self.top = new_start + self.bottom = new_end + self.working_lines = new_end - new_start + 1 + + # if cursor is outside scrolling region, reset it + if self.l < self.top: + self.l = self.top + elif self.l > self.bottom: + self.l = self.bottom + + self.color_changes = {} + # }}} + + def csi_tab_clear(self, csi): # {{{ + # this escape defaults to 0 + if len(csi['vals']) == 0: + csi['val'] = 0 + + + + if csi['val'] == 0: + self.tabstops[self.c - 1] = False + elif csi['val'] == 3: + for i in range(0, self.columns + 1): + self.tabstops[i] = False + # }}} + + def csi_set(self, csi): # {{{ + # 132 cols + if csi['val'] == 3: + self.csi_clear_screen(self.parse_csi('2J')) + self.working_columns = 132 + + # relative_origin + elif csi['val'] == 6: + self.absolute_coords = False + + # set auto wrap + elif csi['val'] == 7: + self.autowrap = True + + + self.color_changes = {} + # }}} + + def csi_reset(self, csi): # {{{ + # 80 cols + if csi['val'] == 3: + self.csi_clear_screen(self.parse_csi('2J')) + self.working_columns = 80 + + # absolute origin + elif csi['val'] == 6: + self.absolute_coords = True + + # reset auto wrap + elif csi['val'] == 7: + self.autowrap = False + + + self.color_changes = {} + # }}} + + # }}} + + ############################################################################################### + # ESC functions {{{ + + def esc_scroll_up(self): # {{{ + self.ctl_nl() + + self.color_changes = {} + # }}} + + def esc_next_line(self): # {{{ + self.ctl_nl() + self.c = 1 + # }}} + + def esc_set_tab(self): # {{{ + + if self.c <= len(self.tabstops): + self.tabstops[self.c - 1] = True + # }}} + + def esc_scroll_down(self): # {{{ + if self.l == self.top: + del self.screen[self.bottom] + self.screen.insert(self.top, '') + else: + self.l += -1 + + self.color_changes = {} + # }}} + + # }}} + + ############################################################################################### + # HASH functions {{{ + + def hash_screen_alignment_test(self): # {{{ + self.csi_clear_screen(self.parse_csi('2J')) + self.working_lines = self.lines + for l in range(1, self.lines + 1): + self.screen[l] = 'E' * self.working_columns + # }}} + + # }}} + + ############################################################################################### + # Random stuff {{{ + + def change_title(self, key, val): + + + if key == '0' or key == '2': + + vim.command('setlocal statusline=' + re.escape(val)) + + def paste(self): + self.write(vim.eval('@@')) + self.read(50) + + def paste_selection(self): + self.write(vim.eval('@@')) + + def update_window_size(self): + # resize if needed + if vim.current.window.width != self.columns or vim.current.window.height != self.lines: + + # reset all window size attributes to default + self.columns = vim.current.window.width + self.lines = vim.current.window.height + self.working_columns = vim.current.window.width + self.working_lines = vim.current.window.height + self.bottom = vim.current.window.height + + # reset screen object attributes + self.l = self.screen.reset_size(self.l) + + # reset tabstops + self.init_tabstops() + + + + # signal process that screen size has changed + self.proc.window_resize(self.lines, self.columns) + + def init_tabstops(self): + for i in range(0, self.columns + 1): + if i % 8 == 0: + self.tabstops.append(True) + else: + self.tabstops.append(False) + + # }}} + + ############################################################################################### + # Utility {{{ + + def parse_csi(self, s): # {{{ + attr = { 'key' : s[-1], 'flag' : '', 'val' : 1, 'vals' : [] } + + if len(s) == 1: + return attr + + full = s[0:-1] + + if full[0] == '?': + full = full[1:] + attr['flag'] = '?' + + if full != '': + vals = full.split(';') + for val in vals: + + val = re.sub("\D", "", val) + + if val != '': + attr['vals'].append(int(val)) + + if len(attr['vals']) == 1: + attr['val'] = int(attr['vals'][0]) + + return attr + # }}} + + def bound(self, val, min, max): # {{{ + if val > max: + return max + + if val < min: + return min + + return val + # }}} + + def xterm_to_rgb(self, color_code): # {{{ + if color_code < 16: + ascii_colors = ['000000', 'CD0000', '00CD00', 'CDCD00', '0000EE', 'CD00CD', '00CDCD', 'E5E5E5', + '7F7F7F', 'FF0000', '00FF00', 'FFFF00', '5C5CFF', 'FF00FF', '00FFFF', 'FFFFFF'] + return ascii_colors[color_code] + + elif color_code < 232: + cc = int(color_code) - 16 + + p1 = "%02x" % (math.floor(cc / 36) * (255/5)) + p2 = "%02x" % (math.floor((cc % 36) / 6) * (255/5)) + p3 = "%02x" % (math.floor(cc % 6) * (255/5)) + + return p1 + p2 + p3 + else: + grey_tone = "%02x" % math.floor((255/24) * (color_code - 232)) + return grey_tone + grey_tone + grey_tone + # }}} + + # }}} + + +import os, signal, pty, tty, select, fcntl, termios, struct + +################################################################################################### +class ConqueSubprocess: + + # process id + pid = 0 + + # stdout+stderr file descriptor + fd = None + + # constructor + def __init__(self): # {{{ + self.pid = 0 + # }}} + + # create the pty or whatever (whatever == windows) + def open(self, command, env = {}): # {{{ + command_arr = command.split() + executable = command_arr[0] + args = command_arr + + try: + self.pid, self.fd = pty.fork() + + except: + pass + + + # child proc, replace with command after altering terminal attributes + if self.pid == 0: + + # set requested environment variables + for k in env.keys(): + os.environ[k] = env[k] + + # set some attributes + try: + attrs = tty.tcgetattr(1) + attrs[0] = attrs[0] ^ tty.IGNBRK + attrs[0] = attrs[0] | tty.BRKINT | tty.IXANY | tty.IMAXBEL + attrs[2] = attrs[2] | tty.HUPCL + attrs[3] = attrs[3] | tty.ICANON | tty.ECHO | tty.ISIG | tty.ECHOKE + attrs[6][tty.VMIN] = 1 + attrs[6][tty.VTIME] = 0 + tty.tcsetattr(1, tty.TCSANOW, attrs) + except: + pass + + os.execvp(executable, args) + + # else master, do nothing + else: + pass + + # }}} + + # read from pty + # XXX - select.poll() doesn't work in OS X!!!!!!! + def read(self, timeout = 1): # {{{ + + output = '' + read_timeout = float(timeout) / 1000 + + try: + # what, no do/while? + while 1: + s_read, s_write, s_error = select.select( [ self.fd ], [], [], read_timeout) + + lines = '' + for s_fd in s_read: + try: + lines = os.read( self.fd, 32 ) + except: + pass + output = output + lines + + if lines == '': + break + except: + pass + + return output + # }}} + + # I guess this one's not bad + def write(self, input): # {{{ + try: + os.write(self.fd, input) + except: + pass + # }}} + + # signal process + def signal(self, signum): # {{{ + try: + os.kill(self.pid, signum) + except: + pass + # }}} + + # get process status + def get_status(self): #{{{ + + p_status = True + + try: + if os.waitpid( self.pid, os.WNOHANG )[0]: + p_status = False + except: + p_status = False + + return p_status + + # }}} + + # update window size in kernel, then send SIGWINCH to fg process + def window_resize(self, lines, columns): # {{{ + try: + fcntl.ioctl(self.fd, termios.TIOCSWINSZ, struct.pack("HHHH", lines, columns, 0, 0)) + os.kill(self.pid, signal.SIGWINCH) + except: + pass + + # }}} + + +################################################################################################### +# ConqueScreen is an extention of the vim.current.buffer object +# It restricts the working indices of the buffer object to the scroll region which pty is expecting +# It also uses 1-based indexes, to match escape sequence commands +# +# E.g.: +# s = ConqueScreen() +# ... +# s[5] = 'Set 5th line in terminal to this line' +# s.append('Add new line to terminal') +# s[5] = 'Since previous append() command scrolled the terminal down, this is a different line than first cb[5] call' +# + +import vim + +class ConqueScreen(object): + + # CLASS PROPERTIES {{{ + + # the buffer + buffer = None + + # screen and scrolling regions + screen_top = 1 + + # screen width + screen_width = 80 + screen_height = 80 + + # }}} + + def __init__(self): # {{{ + self.buffer = vim.current.buffer + + self.screen_top = 1 + self.screen_width = vim.current.window.width + self.screen_height = vim.current.window.height + # }}} + + ############################################################################################### + # List overload {{{ + def __len__(self): # {{{ + return len(self.buffer) + # }}} + + def __getitem__(self, key): # {{{ + real_line = self.get_real_idx(key) + + # if line is past buffer end, add lines to buffer + if real_line >= len(self.buffer): + for i in range(len(self.buffer), real_line + 1): + self.append(' ' * self.screen_width) + + return self.buffer[ real_line ] + # }}} + + def __setitem__(self, key, value): # {{{ + real_line = self.get_real_idx(key) + + # if line is past end of screen, append + if real_line == len(self.buffer): + self.buffer.append(value) + else: + self.buffer[ real_line ] = value + # }}} + + def __delitem__(self, key): # {{{ + del self.buffer[ self.screen_top + key - 2 ] + # }}} + + def append(self, value): # {{{ + if len(self.buffer) > self.screen_top + self.screen_height - 1: + self.buffer[len(self.buffer) - 1] = value + else: + self.buffer.append(value) + + if len(self.buffer) > self.screen_top + self.screen_height - 1: + self.screen_top += 1 + if vim.current.buffer.number == self.buffer.number: + vim.command('normal G') + # }}} + + def insert(self, line, value): # {{{ + + l = self.screen_top + line - 2 + self.buffer[l:l] = [ value ] + + # }}} + # }}} + + ############################################################################################### + # Util {{{ + def get_top(self): # {{{ + return self.screen_top + # }}} + + def get_real_idx(self, line): # {{{ + return (self.screen_top + line - 2) + # }}} + + def get_real_line(self, line): # {{{ + return (self.screen_top + line - 1) + # }}} + + def set_screen_width(self, width): # {{{ + self.screen_width = width + # }}} + + # }}} + + ############################################################################################### + def clear(self): # {{{ + self.buffer.append(' ') + vim.command('normal Gzt') + self.screen_top = len(self.buffer) + # }}} + + def set_cursor(self, line, column): # {{{ + # figure out line + real_line = self.screen_top + line - 1 + if real_line > len(self.buffer): + for l in range(len(self.buffer) - 1, real_line): + self.buffer.append('') + + # figure out column + real_column = column + if len(self.buffer[real_line - 1]) < real_column: + self.buffer[real_line - 1] = self.buffer[real_line - 1] + ' ' * (real_column - len(self.buffer[real_line - 1])) + + # python version is occasionally grumpy + try: + vim.current.window.cursor = (real_line, real_column - 1) + except: + vim.command('call cursor(' + str(real_line) + ', ' + str(real_column) + ')') + # }}} + + def reset_size(self, line): # {{{ + + + + + # save cursor line number + real_line = self.screen_top + line + + # reset screen size + self.screen_width = vim.current.window.width + self.screen_height = vim.current.window.height + self.screen_top = len(self.buffer) - vim.current.window.height + 1 + if self.screen_top < 1: + self.screen_top = 1 + + + # align bottom of buffer to bottom of screen + vim.command('normal ' + str(self.screen_height) + 'kG') + + # return new relative line number + return (real_line - self.screen_top) + # }}} + + def scroll_to_bottom(self): # {{{ + vim.current.window.cursor = (len(self.buffer) - 1, 1) + # }}} + + def align(self): # {{{ + # align bottom of buffer to bottom of screen + vim.command('normal ' + str(self.screen_height) + 'kG') + # }}} + + +EOF + +endif + diff --git a/vim/autoload/delimitMate.vim b/vim/autoload/delimitMate.vim new file mode 100644 index 0000000..bd10975 --- /dev/null +++ b/vim/autoload/delimitMate.vim @@ -0,0 +1,586 @@ +" File: autoload/delimitMate.vim +" Version: 2.6 +" Modified: 2011-01-14 +" Description: This plugin provides auto-completion for quotes, parens, etc. +" Maintainer: Israel Chauca F. <israelchauca@gmail.com> +" Manual: Read ":help delimitMate". +" ============================================================================ + +" Utilities {{{ + +let delimitMate_loaded = 1 + +function! delimitMate#ShouldJump() "{{{ + " Returns 1 if the next character is a closing delimiter. + let col = col('.') + let lcol = col('$') + let char = getline('.')[col - 1] + + " Closing delimiter on the right. + for cdel in b:_l_delimitMate_right_delims + b:_l_delimitMate_quotes_list + if char == cdel + return 1 + endif + endfor + + " Closing delimiter with space expansion. + let nchar = getline('.')[col] + if b:_l_delimitMate_expand_space && char == " " + for cdel in b:_l_delimitMate_right_delims + b:_l_delimitMate_quotes_list + if nchar == cdel + return 1 + endif + endfor + endif + + " Closing delimiter with CR expansion. + let uchar = getline(line('.') + 1)[0] + if b:_l_delimitMate_expand_cr && char == "" + for cdel in b:_l_delimitMate_right_delims + b:_l_delimitMate_quotes_list + if uchar == cdel + return 1 + endif + endfor + endif + + return 0 +endfunction "}}} + +function! delimitMate#IsEmptyPair(str) "{{{ + for pair in b:_l_delimitMate_matchpairs_list + if a:str == join( split( pair, ':' ),'' ) + return 1 + endif + endfor + for quote in b:_l_delimitMate_quotes_list + if a:str == quote . quote + return 1 + endif + endfor + return 0 +endfunction "}}} + +function! delimitMate#IsCRExpansion() " {{{ + let nchar = getline(line('.')-1)[-1:] + let schar = getline(line('.')+1)[:0] + let isEmpty = getline('.') == "" + if index(b:_l_delimitMate_left_delims, nchar) > -1 && + \ index(b:_l_delimitMate_left_delims, nchar) == index(b:_l_delimitMate_right_delims, schar) && + \ isEmpty + return 1 + elseif index(b:_l_delimitMate_quotes_list, nchar) > -1 && + \ index(b:_l_delimitMate_quotes_list, nchar) == index(b:_l_delimitMate_quotes_list, schar) && + \ isEmpty + return 1 + else + return 0 + endif +endfunction " }}} delimitMate#IsCRExpansion() + +function! delimitMate#IsSpaceExpansion() " {{{ + let line = getline('.') + let col = col('.')-2 + if col > 0 + let pchar = line[col - 1] + let nchar = line[col + 2] + let isSpaces = (line[col] == line[col+1] && line[col] == " ") + + if index(b:_l_delimitMate_left_delims, pchar) > -1 && + \ index(b:_l_delimitMate_left_delims, pchar) == index(b:_l_delimitMate_right_delims, nchar) && + \ isSpaces + return 1 + elseif index(b:_l_delimitMate_quotes_list, pchar) > -1 && + \ index(b:_l_delimitMate_quotes_list, pchar) == index(b:_l_delimitMate_quotes_list, nchar) && + \ isSpaces + return 1 + endif + endif + return 0 +endfunction " }}} IsSpaceExpansion() + +function! delimitMate#WithinEmptyPair() "{{{ + let cur = strpart( getline('.'), col('.')-2, 2 ) + return delimitMate#IsEmptyPair( cur ) +endfunction "}}} + +function! delimitMate#WriteBefore(str) "{{{ + let len = len(a:str) + let line = getline('.') + let col = col('.')-2 + if col < 0 + call setline('.',line[(col+len+1):]) + else + call setline('.',line[:(col)].line[(col+len+1):]) + endif + return a:str +endfunction " }}} + +function! delimitMate#WriteAfter(str) "{{{ + let len = len(a:str) + let line = getline('.') + let col = col('.')-2 + if (col) < 0 + call setline('.',a:str.line) + else + call setline('.',line[:(col)].a:str.line[(col+len):]) + endif + return '' +endfunction " }}} + +function! delimitMate#GetSyntaxRegion(line, col) "{{{ + return synIDattr(synIDtrans(synID(a:line, a:col, 1)), 'name') +endfunction " }}} + +function! delimitMate#GetCurrentSyntaxRegion() "{{{ + let col = col('.') + if col == col('$') + let col = col - 1 + endif + return delimitMate#GetSyntaxRegion(line('.'), col) +endfunction " }}} + +function! delimitMate#GetCurrentSyntaxRegionIf(char) "{{{ + let col = col('.') + let origin_line = getline('.') + let changed_line = strpart(origin_line, 0, col - 1) . a:char . strpart(origin_line, col - 1) + call setline('.', changed_line) + let region = delimitMate#GetSyntaxRegion(line('.'), col) + call setline('.', origin_line) + return region +endfunction "}}} + +function! delimitMate#IsForbidden(char) "{{{ + if b:_l_delimitMate_excluded_regions_enabled == 0 + return 0 + endif + "let result = index(b:_l_delimitMate_excluded_regions_list, delimitMate#GetCurrentSyntaxRegion()) >= 0 + if index(b:_l_delimitMate_excluded_regions_list, delimitMate#GetCurrentSyntaxRegion()) >= 0 + "echom "Forbidden 1!" + return 1 + endif + let region = delimitMate#GetCurrentSyntaxRegionIf(a:char) + "let result = index(b:_l_delimitMate_excluded_regions_list, region) >= 0 + "return result || region == 'Comment' + "echom "Forbidden 2!" + return index(b:_l_delimitMate_excluded_regions_list, region) >= 0 +endfunction "}}} + +function! delimitMate#FlushBuffer() " {{{ + let b:_l_delimitMate_buffer = [] + return '' +endfunction " }}} + +function! delimitMate#BalancedParens(char) "{{{ + " Returns: + " = 0 => Parens balanced. + " > 0 => More opening parens. + " < 0 => More closing parens. + + let line = getline('.') + let col = col('.') - 2 + let col = col >= 0 ? col : 0 + let list = split(line, '\zs') + let left = b:_l_delimitMate_left_delims[index(b:_l_delimitMate_right_delims, a:char)] + let right = a:char + let opening = 0 + let closing = 0 + + " If the cursor is not at the beginning, count what's behind it. + if col > 0 + " Find the first opening paren: + let start = index(list, left) + " Must be before cursor: + let start = start < col ? start : col - 1 + " Now count from the first opening until the cursor, this will prevent + " extra closing parens from being counted. + let opening = count(list[start : col - 1], left) + let closing = count(list[start : col - 1], right) + " I don't care if there are more closing parens than opening parens. + let closing = closing > opening ? opening : closing + endif + + " Evaluate parens from the cursor to the end: + let opening += count(list[col :], left) + let closing += count(list[col :], right) + + "echom "–––––––––" + "echom line + "echom col + ""echom left.":".a:char + "echom string(list) + "echom string(list[start : col - 1]) . " : " . string(list[col :]) + "echom opening . " - " . closing . " = " . (opening - closing) + + " Return the found balance: + return opening - closing +endfunction "}}} + +function! delimitMate#RmBuffer(num) " {{{ + if len(b:_l_delimitMate_buffer) > 0 + call remove(b:_l_delimitMate_buffer, 0, (a:num-1)) + endif + return "" +endfunction " }}} + +" }}} + +" Doers {{{ +function! delimitMate#SkipDelim(char) "{{{ + if delimitMate#IsForbidden(a:char) + return a:char + endif + let col = col('.') - 1 + let line = getline('.') + if col > 0 + let cur = line[col] + let pre = line[col-1] + else + let cur = line[col] + let pre = "" + endif + if pre == "\\" + " Escaped character + return a:char + elseif cur == a:char + " Exit pair + "return delimitMate#WriteBefore(a:char) + return a:char . delimitMate#Del() + elseif delimitMate#IsEmptyPair( pre . a:char ) + " Add closing delimiter and jump back to the middle. + call insert(b:_l_delimitMate_buffer, a:char) + return delimitMate#WriteAfter(a:char) + else + " Nothing special here, return the same character. + return a:char + endif +endfunction "}}} + +function! delimitMate#ParenDelim(char) " {{{ + if delimitMate#IsForbidden(a:char) + return '' + endif + " Try to balance matchpairs + if b:_l_delimitMate_balance_matchpairs && + \ delimitMate#BalancedParens(a:char) <= 0 + return '' + endif + let line = getline('.') + let col = col('.')-2 + let left = b:_l_delimitMate_left_delims[index(b:_l_delimitMate_right_delims,a:char)] + let smart_matchpairs = substitute(b:_l_delimitMate_smart_matchpairs, '\\!', left, 'g') + let smart_matchpairs = substitute(smart_matchpairs, '\\#', a:char, 'g') + "echom left.':'.smart_matchpairs . ':' . matchstr(line[col+1], smart_matchpairs) + if b:_l_delimitMate_smart_matchpairs != '' && + \ line[col+1:] =~ smart_matchpairs + return '' + elseif (col) < 0 + call setline('.',a:char.line) + call insert(b:_l_delimitMate_buffer, a:char) + else + "echom string(col).':'.line[:(col)].'|'.line[(col+1):] + call setline('.',line[:(col)].a:char.line[(col+1):]) + call insert(b:_l_delimitMate_buffer, a:char) + endif + return '' +endfunction " }}} + +function! delimitMate#QuoteDelim(char) "{{{ + if delimitMate#IsForbidden(a:char) + return a:char + endif + let line = getline('.') + let col = col('.') - 2 + if line[col] == "\\" + " Seems like a escaped character, insert one quotation mark. + return a:char + elseif line[col + 1] == a:char && + \ index(b:_l_delimitMate_nesting_quotes, a:char) < 0 + " Get out of the string. + return a:char . delimitMate#Del() + elseif (line[col] =~ '\w' && a:char == "'") || + \ (b:_l_delimitMate_smart_quotes && + \ (line[col] =~ '\w' || + \ line[col + 1] =~ '\w')) + " Seems like an apostrophe or a smart quote case, insert a single quote. + return a:char + elseif (line[col] == a:char && line[col + 1 ] != a:char) && b:_l_delimitMate_smart_quotes + " Seems like we have an unbalanced quote, insert one quotation mark and jump to the middle. + call insert(b:_l_delimitMate_buffer, a:char) + return delimitMate#WriteAfter(a:char) + else + " Insert a pair and jump to the middle. + call insert(b:_l_delimitMate_buffer, a:char) + call delimitMate#WriteAfter(a:char) + return a:char + endif +endfunction "}}} + +function! delimitMate#JumpOut(char) "{{{ + if delimitMate#IsForbidden(a:char) + return a:char + endif + let line = getline('.') + let col = col('.')-2 + if line[col+1] == a:char + return a:char . delimitMate#Del() + else + return a:char + endif +endfunction " }}} + +function! delimitMate#JumpAny(key) " {{{ + if delimitMate#IsForbidden('') + return a:key + endif + if !delimitMate#ShouldJump() + return a:key + endif + " Let's get the character on the right. + let char = getline('.')[col('.')-1] + if char == " " + " Space expansion. + "let char = char . getline('.')[col('.')] . delimitMate#Del() + return char . getline('.')[col('.')] . delimitMate#Del() . delimitMate#Del() + "call delimitMate#RmBuffer(1) + elseif char == "" + " CR expansion. + "let char = "\<CR>" . getline(line('.') + 1)[0] . "\<Del>" + let b:_l_delimitMate_buffer = [] + return "\<CR>" . getline(line('.') + 1)[0] . "\<Del>" + else + "call delimitMate#RmBuffer(1) + return char . delimitMate#Del() + endif +endfunction " delimitMate#JumpAny() }}} + +function! delimitMate#JumpMany() " {{{ + let line = getline('.')[col('.') - 1 : ] + let len = len(line) + let rights = "" + let found = 0 + let i = 0 + while i < len + let char = line[i] + if index(b:_l_delimitMate_quotes_list, char) >= 0 || + \ index(b:_l_delimitMate_right_delims, char) >= 0 + let rights .= "\<Right>" + let found = 1 + elseif found == 0 + let rights .= "\<Right>" + else + break + endif + let i += 1 + endwhile + if found == 1 + return rights + else + return '' + endif +endfunction " delimitMate#JumpMany() }}} + +function! delimitMate#ExpandReturn() "{{{ + if delimitMate#IsForbidden("") + return "\<CR>" + endif + if delimitMate#WithinEmptyPair() + " Expand: + call delimitMate#FlushBuffer() + "return "\<Esc>a\<CR>x\<CR>\<Esc>k$\"_xa" + return "\<CR>\<UP>\<Esc>o" + else + return "\<CR>" + endif +endfunction "}}} + +function! delimitMate#ExpandSpace() "{{{ + if delimitMate#IsForbidden("\<Space>") + return "\<Space>" + endif + if delimitMate#WithinEmptyPair() + " Expand: + call insert(b:_l_delimitMate_buffer, 's') + return delimitMate#WriteAfter(' ') . "\<Space>" + else + return "\<Space>" + endif +endfunction "}}} + +function! delimitMate#BS() " {{{ + if delimitMate#IsForbidden("") + return "\<BS>" + endif + if delimitMate#WithinEmptyPair() + "call delimitMate#RmBuffer(1) + return "\<BS>" . delimitMate#Del() +" return "\<Right>\<BS>\<BS>" + elseif delimitMate#IsSpaceExpansion() + "call delimitMate#RmBuffer(1) + return "\<BS>" . delimitMate#Del() + elseif delimitMate#IsCRExpansion() + return "\<BS>\<Del>" + else + return "\<BS>" + endif +endfunction " }}} delimitMate#BS() + +function! delimitMate#Del() " {{{ + if len(b:_l_delimitMate_buffer) > 0 + let line = getline('.') + let col = col('.') - 2 + call delimitMate#RmBuffer(1) + call setline('.', line[:col] . line[col+2:]) + return '' + else + return "\<Del>" + endif +endfunction " }}} + +function! delimitMate#Finish(move_back) " {{{ + let len = len(b:_l_delimitMate_buffer) + if len > 0 + let buffer = join(b:_l_delimitMate_buffer, '') + let len2 = len(buffer) + " Reset buffer: + let b:_l_delimitMate_buffer = [] + let line = getline('.') + let col = col('.') -2 + "echom 'col: ' . col . '-' . line[:col] . "|" . line[col+len+1:] . '%' . buffer + if col < 0 + call setline('.', line[col+len2+1:]) + else + call setline('.', line[:col] . line[col+len2+1:]) + endif + let i = 1 + let lefts = "" + while i <= len && a:move_back + let lefts = lefts . "\<Left>" + let i += 1 + endwhile + return substitute(buffer, "s", "\<Space>", 'g') . lefts + endif + return '' +endfunction " }}} + +" }}} + +" Tools: {{{ +function! delimitMate#TestMappings() "{{{ + let options = sort(keys(delimitMate#OptionsList())) + let optoutput = ['delimitMate Report', '==================', '', '* Options: ( ) default, (g) global, (b) buffer',''] + for option in options + exec 'call add(optoutput, ''('.(exists('b:delimitMate_'.option) ? 'b' : exists('g:delimitMate_'.option) ? 'g' : ' ').') delimitMate_''.option.'' = ''.string(b:_l_delimitMate_'.option.'))' + endfor + call append(line('$'), optoutput + ['--------------------','']) + + " Check if mappings were set. {{{ + let imaps = b:_l_delimitMate_right_delims + let imaps = imaps + ( b:_l_delimitMate_autoclose ? b:_l_delimitMate_left_delims : [] ) + let imaps = imaps + + \ b:_l_delimitMate_quotes_list + + \ b:_l_delimitMate_apostrophes_list + + \ ['<BS>', '<S-BS>', '<Del>', '<S-Tab>', '<Esc>'] + + \ ['<Up>', '<Down>', '<Left>', '<Right>', '<LeftMouse>', '<RightMouse>'] + + \ ['<Home>', '<End>', '<PageUp>', '<PageDown>', '<S-Down>', '<S-Up>', '<C-G>g'] + let imaps = imaps + ( b:_l_delimitMate_expand_cr ? ['<CR>'] : [] ) + let imaps = imaps + ( b:_l_delimitMate_expand_space ? ['<Space>'] : [] ) + + let vmaps = + \ b:_l_delimitMate_right_delims + + \ b:_l_delimitMate_left_delims + + \ b:_l_delimitMate_quotes_list + + let ibroken = [] + for map in imaps + if maparg(map, "i") !~? 'delimitMate' + let output = '' + if map == '|' + let map = '<Bar>' + endif + redir => output | execute "verbose imap ".map | redir END + let ibroken = ibroken + [map.": is not set:"] + split(output, '\n') + endif + endfor + + unlet! output + if ibroken == [] + let output = ['* Mappings:', '', 'All mappings were set-up.', '--------------------', '', ''] + else + let output = ['* Mappings:', ''] + ibroken + ['--------------------', ''] + endif + call append('$', output+['* Showcase:', '']) + " }}} + if b:_l_delimitMate_autoclose + " {{{ + for i in range(len(b:_l_delimitMate_left_delims)) + exec "normal Go0\<C-D>Open: " . b:_l_delimitMate_left_delims[i]. "|" + exec "normal o0\<C-D>Delete: " . b:_l_delimitMate_left_delims[i] . "\<BS>|" + exec "normal o0\<C-D>Exit: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . "|" + if b:_l_delimitMate_expand_space == 1 + exec "normal o0\<C-D>Space: " . b:_l_delimitMate_left_delims[i] . " |" + exec "normal o0\<C-D>Delete space: " . b:_l_delimitMate_left_delims[i] . " \<BS>|" + endif + if b:_l_delimitMate_expand_cr == 1 + exec "normal o0\<C-D>Car return: " . b:_l_delimitMate_left_delims[i] . "\<CR>|" + exec "normal Go0\<C-D>Delete car return: " . b:_l_delimitMate_left_delims[i] . "\<CR>0\<C-D>\<BS>|" + endif + call append(line('$'), '') + endfor + for i in range(len(b:_l_delimitMate_quotes_list)) + exec "normal Go0\<C-D>Open: " . b:_l_delimitMate_quotes_list[i] . "|" + exec "normal o0\<C-D>Delete: " . b:_l_delimitMate_quotes_list[i] . "\<BS>|" + exec "normal o0\<C-D>Exit: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . "|" + if b:_l_delimitMate_expand_space == 1 + exec "normal o0\<C-D>Space: " . b:_l_delimitMate_quotes_list[i] . " |" + exec "normal o0\<C-D>Delete space: " . b:_l_delimitMate_quotes_list[i] . " \<BS>|" + endif + if b:_l_delimitMate_expand_cr == 1 + exec "normal o0\<C-D>Car return: " . b:_l_delimitMate_quotes_list[i] . "\<CR>|" + exec "normal Go0\<C-D>Delete car return: " . b:_l_delimitMate_quotes_list[i] . "\<CR>\<BS>|" + endif + call append(line('$'), '') + endfor + "}}} + else + "{{{ + for i in range(len(b:_l_delimitMate_left_delims)) + exec "normal GoOpen & close: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . "|" + exec "normal oDelete: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . "\<BS>|" + exec "normal oExit: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . b:_l_delimitMate_right_delims[i] . "|" + if b:_l_delimitMate_expand_space == 1 + exec "normal oSpace: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . " |" + exec "normal oDelete space: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . " \<BS>|" + endif + if b:_l_delimitMate_expand_cr == 1 + exec "normal oCar return: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . "\<CR>|" + exec "normal GoDelete car return: " . b:_l_delimitMate_left_delims[i] . b:_l_delimitMate_right_delims[i] . "\<CR>\<BS>|" + endif + call append(line('$'), '') + endfor + for i in range(len(b:_l_delimitMate_quotes_list)) + exec "normal GoOpen & close: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . "|" + exec "normal oDelete: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . "\<BS>|" + exec "normal oExit: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . "|" + if b:_l_delimitMate_expand_space == 1 + exec "normal oSpace: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . " |" + exec "normal oDelete space: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . " \<BS>|" + endif + if b:_l_delimitMate_expand_cr == 1 + exec "normal oCar return: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . "\<CR>|" + exec "normal GoDelete car return: " . b:_l_delimitMate_quotes_list[i] . b:_l_delimitMate_quotes_list[i] . "\<CR>\<BS>|" + endif + call append(line('$'), '') + endfor + endif "}}} + redir => setoptions | set | filetype | redir END + call append(line('$'), split(setoptions,"\n") + \ + ['--------------------']) + setlocal nowrap +endfunction "}}} + +function! delimitMate#OptionsList() "{{{ + return {'autoclose' : 1,'matchpairs': &matchpairs, 'quotes' : '" '' `', 'nesting_quotes' : [], 'expand_cr' : 0, 'expand_space' : 0, 'smart_quotes' : 1, 'smart_matchpairs' : '\w', 'balance_matchpairs' : 0, 'excluded_regions' : 'Comment', 'excluded_ft' : '', 'apostrophes' : ''} +endfunction " delimitMate#OptionsList }}} +"}}} + +" vim:foldmethod=marker:foldcolumn=4 diff --git a/vim/autoload/pathogen.vim b/vim/autoload/pathogen.vim new file mode 100644 index 0000000..4071992 --- /dev/null +++ b/vim/autoload/pathogen.vim @@ -0,0 +1,250 @@ +" pathogen.vim - path option manipulation +" Maintainer: Tim Pope <http://tpo.pe/> +" Version: 2.0 + +" Install in ~/.vim/autoload (or ~\vimfiles\autoload). +" +" For management of individually installed plugins in ~/.vim/bundle (or +" ~\vimfiles\bundle), adding `call pathogen#infect()` to your .vimrc +" prior to `filetype plugin indent on` is the only other setup necessary. +" +" The API is documented inline below. For maximum ease of reading, +" :set foldmethod=marker + +if exists("g:loaded_pathogen") || &cp + finish +endif +let g:loaded_pathogen = 1 + +" Point of entry for basic default usage. Give a directory name to invoke +" pathogen#runtime_append_all_bundles() (defaults to "bundle"), or a full path +" to invoke pathogen#runtime_prepend_subdirectories(). Afterwards, +" pathogen#cycle_filetype() is invoked. +function! pathogen#infect(...) abort " {{{1 + let source_path = a:0 ? a:1 : 'bundle' + if source_path =~# '[\\/]' + call pathogen#runtime_prepend_subdirectories(source_path) + else + call pathogen#runtime_append_all_bundles(source_path) + endif + call pathogen#cycle_filetype() +endfunction " }}}1 + +" Split a path into a list. +function! pathogen#split(path) abort " {{{1 + if type(a:path) == type([]) | return a:path | endif + let split = split(a:path,'\\\@<!\%(\\\\\)*\zs,') + return map(split,'substitute(v:val,''\\\([\\,]\)'',''\1'',"g")') +endfunction " }}}1 + +" Convert a list to a path. +function! pathogen#join(...) abort " {{{1 + if type(a:1) == type(1) && a:1 + let i = 1 + let space = ' ' + else + let i = 0 + let space = '' + endif + let path = "" + while i < a:0 + if type(a:000[i]) == type([]) + let list = a:000[i] + let j = 0 + while j < len(list) + let escaped = substitute(list[j],'[,'.space.']\|\\[\,'.space.']\@=','\\&','g') + let path .= ',' . escaped + let j += 1 + endwhile + else + let path .= "," . a:000[i] + endif + let i += 1 + endwhile + return substitute(path,'^,','','') +endfunction " }}}1 + +" Convert a list to a path with escaped spaces for 'path', 'tag', etc. +function! pathogen#legacyjoin(...) abort " {{{1 + return call('pathogen#join',[1] + a:000) +endfunction " }}}1 + +" Remove duplicates from a list. +function! pathogen#uniq(list) abort " {{{1 + let i = 0 + let seen = {} + while i < len(a:list) + if (a:list[i] ==# '' && exists('empty')) || has_key(seen,a:list[i]) + call remove(a:list,i) + elseif a:list[i] ==# '' + let i += 1 + let empty = 1 + else + let seen[a:list[i]] = 1 + let i += 1 + endif + endwhile + return a:list +endfunction " }}}1 + +" \ on Windows unless shellslash is set, / everywhere else. +function! pathogen#separator() abort " {{{1 + return !exists("+shellslash") || &shellslash ? '/' : '\' +endfunction " }}}1 + +" Convenience wrapper around glob() which returns a list. +function! pathogen#glob(pattern) abort " {{{1 + let files = split(glob(a:pattern),"\n") + return map(files,'substitute(v:val,"[".pathogen#separator()."/]$","","")') +endfunction "}}}1 + +" Like pathogen#glob(), only limit the results to directories. +function! pathogen#glob_directories(pattern) abort " {{{1 + return filter(pathogen#glob(a:pattern),'isdirectory(v:val)') +endfunction "}}}1 + +" Turn filetype detection off and back on again if it was already enabled. +function! pathogen#cycle_filetype() " {{{1 + if exists('g:did_load_filetypes') + filetype off + filetype on + endif +endfunction " }}}1 + +" Checks if a bundle is 'disabled'. A bundle is considered 'disabled' if +" its 'basename()' is included in g:pathogen_disabled[]' or ends in a tilde. +function! pathogen#is_disabled(path) " {{{1 + if a:path =~# '\~$' + return 1 + elseif !exists("g:pathogen_disabled") + return 0 + endif + let sep = pathogen#separator() + return index(g:pathogen_disabled, strpart(a:path, strridx(a:path, sep)+1)) != -1 +endfunction "}}}1 + +" Prepend all subdirectories of path to the rtp, and append all 'after' +" directories in those subdirectories. +function! pathogen#runtime_prepend_subdirectories(path) " {{{1 + let sep = pathogen#separator() + let before = filter(pathogen#glob_directories(a:path.sep."*"), '!pathogen#is_disabled(v:val)') + let after = filter(pathogen#glob_directories(a:path.sep."*".sep."after"), '!pathogen#is_disabled(v:val[0:-7])') + let rtp = pathogen#split(&rtp) + let path = expand(a:path) + call filter(rtp,'v:val[0:strlen(path)-1] !=# path') + let &rtp = pathogen#join(pathogen#uniq(before + rtp + after)) + return &rtp +endfunction " }}}1 + +" For each directory in rtp, check for a subdirectory named dir. If it +" exists, add all subdirectories of that subdirectory to the rtp, immediately +" after the original directory. If no argument is given, 'bundle' is used. +" Repeated calls with the same arguments are ignored. +function! pathogen#runtime_append_all_bundles(...) " {{{1 + let sep = pathogen#separator() + let name = a:0 ? a:1 : 'bundle' + if "\n".s:done_bundles =~# "\\M\n".name."\n" + return "" + endif + let s:done_bundles .= name . "\n" + let list = [] + for dir in pathogen#split(&rtp) + if dir =~# '\<after$' + let list += filter(pathogen#glob_directories(substitute(dir,'after$',name,'').sep.'*[^~]'.sep.'after'), '!pathogen#is_disabled(v:val[0:-7])') + [dir] + else + let list += [dir] + filter(pathogen#glob_directories(dir.sep.name.sep.'*[^~]'), '!pathogen#is_disabled(v:val)') + endif + endfor + let &rtp = pathogen#join(pathogen#uniq(list)) + return 1 +endfunction + +let s:done_bundles = '' +" }}}1 + +" Invoke :helptags on all non-$VIM doc directories in runtimepath. +function! pathogen#helptags() " {{{1 + let sep = pathogen#separator() + for dir in pathogen#split(&rtp) + if (dir.sep)[0 : strlen($VIMRUNTIME)] !=# $VIMRUNTIME.sep && filewritable(dir.sep.'doc') == 2 && !empty(filter(split(glob(dir.sep.'doc'.sep.'*'),"\n>"),'!isdirectory(v:val)')) && (!filereadable(dir.sep.'doc'.sep.'tags') || filewritable(dir.sep.'doc'.sep.'tags')) + helptags `=dir.'/doc'` + endif + endfor +endfunction " }}}1 + +command! -bar Helptags :call pathogen#helptags() + +" Like findfile(), but hardcoded to use the runtimepath. +function! pathogen#runtime_findfile(file,count) "{{{1 + let rtp = pathogen#join(1,pathogen#split(&rtp)) + let file = findfile(a:file,rtp,a:count) + if file ==# '' + return '' + else + return fnamemodify(file,':p') + endif +endfunction " }}}1 + +" Backport of fnameescape(). +function! pathogen#fnameescape(string) " {{{1 + if exists('*fnameescape') + return fnameescape(a:string) + elseif a:string ==# '-' + return '\-' + else + return substitute(escape(a:string," \t\n*?[{`$\\%#'\"|!<"),'^[+>]','\\&','') + endif +endfunction " }}}1 + +function! s:find(count,cmd,file,lcd) " {{{1 + let rtp = pathogen#join(1,pathogen#split(&runtimepath)) + let file = pathogen#runtime_findfile(a:file,a:count) + if file ==# '' + return "echoerr 'E345: Can''t find file \"".a:file."\" in runtimepath'" + elseif a:lcd + let path = file[0:-strlen(a:file)-2] + execute 'lcd `=path`' + return a:cmd.' '.pathogen#fnameescape(a:file) + else + return a:cmd.' '.pathogen#fnameescape(file) + endif +endfunction " }}}1 + +function! s:Findcomplete(A,L,P) " {{{1 + let sep = pathogen#separator() + let cheats = { + \'a': 'autoload', + \'d': 'doc', + \'f': 'ftplugin', + \'i': 'indent', + \'p': 'plugin', + \'s': 'syntax'} + if a:A =~# '^\w[\\/]' && has_key(cheats,a:A[0]) + let request = cheats[a:A[0]].a:A[1:-1] + else + let request = a:A + endif + let pattern = substitute(request,'/\|\'.sep,'*'.sep,'g').'*' + let found = {} + for path in pathogen#split(&runtimepath) + let path = expand(path, ':p') + let matches = split(glob(path.sep.pattern),"\n") + call map(matches,'isdirectory(v:val) ? v:val.sep : v:val') + call map(matches,'expand(v:val, ":p")[strlen(path)+1:-1]') + for match in matches + let found[match] = 1 + endfor + endfor + return sort(keys(found)) +endfunction " }}}1 + +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Ve :execute s:find(<count>,'edit<bang>',<q-args>,0) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vedit :execute s:find(<count>,'edit<bang>',<q-args>,0) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vopen :execute s:find(<count>,'edit<bang>',<q-args>,1) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vsplit :execute s:find(<count>,'split',<q-args>,<bang>1) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vvsplit :execute s:find(<count>,'vsplit',<q-args>,<bang>1) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vtabedit :execute s:find(<count>,'tabedit',<q-args>,<bang>1) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vpedit :execute s:find(<count>,'pedit',<q-args>,<bang>1) +command! -bar -bang -range=1 -nargs=1 -complete=customlist,s:Findcomplete Vread :execute s:find(<count>,'read',<q-args>,<bang>1) + +" vim:set ft=vim ts=8 sw=2 sts=2: diff --git a/vim/autoload/rails.vim b/vim/autoload/rails.vim new file mode 100644 index 0000000..ca36075 --- /dev/null +++ b/vim/autoload/rails.vim @@ -0,0 +1,4560 @@ +" autoload/rails.vim +" Author: Tim Pope <http://tpo.pe/> + +" Install this file as autoload/rails.vim. + +if exists('g:autoloaded_rails') || &cp + finish +endif +let g:autoloaded_rails = '4.4' + +let s:cpo_save = &cpo +set cpo&vim + +" Utility Functions {{{1 + +let s:app_prototype = {} +let s:file_prototype = {} +let s:buffer_prototype = {} +let s:readable_prototype = {} + +function! s:add_methods(namespace, method_names) + for name in a:method_names + let s:{a:namespace}_prototype[name] = s:function('s:'.a:namespace.'_'.name) + endfor +endfunction + +function! s:function(name) + return function(substitute(a:name,'^s:',matchstr(expand('<sfile>'), '<SNR>\d\+_'),'')) +endfunction + +function! s:sub(str,pat,rep) + return substitute(a:str,'\v\C'.a:pat,a:rep,'') +endfunction + +function! s:gsub(str,pat,rep) + return substitute(a:str,'\v\C'.a:pat,a:rep,'g') +endfunction + +function! s:startswith(string,prefix) + return strpart(a:string, 0, strlen(a:prefix)) ==# a:prefix +endfunction + +function! s:compact(ary) + return s:sub(s:sub(s:gsub(a:ary,'\n\n+','\n'),'\n$',''),'^\n','') +endfunction + +function! s:uniq(list) + let seen = {} + let i = 0 + while i < len(a:list) + if has_key(seen,a:list[i]) + call remove(a:list, i) + else + let seen[a:list[i]] = 1 + let i += 1 + endif + endwhile + return a:list +endfunction + +function! s:scrub(collection,item) + " Removes item from a newline separated collection + let col = "\n" . a:collection + let idx = stridx(col,"\n".a:item."\n") + let cnt = 0 + while idx != -1 && cnt < 100 + let col = strpart(col,0,idx).strpart(col,idx+strlen(a:item)+1) + let idx = stridx(col,"\n".a:item."\n") + let cnt += 1 + endwhile + return strpart(col,1) +endfunction + +function! s:escarg(p) + return s:gsub(a:p,'[ !%#]','\\&') +endfunction + +function! s:esccmd(p) + return s:gsub(a:p,'[!%#]','\\&') +endfunction + +function! s:rquote(str) + " Imperfect but adequate for Ruby arguments + if a:str =~ '^[A-Za-z0-9_/.:-]\+$' + return a:str + elseif &shell =~? 'cmd' + return '"'.s:gsub(s:gsub(a:str,'\','\\'),'"','\\"').'"' + else + return "'".s:gsub(s:gsub(a:str,'\','\\'),"'","'\\\\''")."'" + endif +endfunction + +function! s:sname() + return fnamemodify(s:file,':t:r') +endfunction + +function! s:pop_command() + if exists("s:command_stack") && len(s:command_stack) > 0 + exe remove(s:command_stack,-1) + endif +endfunction + +function! s:push_chdir(...) + if !exists("s:command_stack") | let s:command_stack = [] | endif + if exists("b:rails_root") && (a:0 ? getcwd() !=# rails#app().path() : !s:startswith(getcwd(), rails#app().path())) + let chdir = exists("*haslocaldir") && haslocaldir() ? "lchdir " : "chdir " + call add(s:command_stack,chdir.s:escarg(getcwd())) + exe chdir.s:escarg(rails#app().path()) + else + call add(s:command_stack,"") + endif +endfunction + +function! s:app_path(...) dict + return join([self.root]+a:000,'/') +endfunction + +function! s:app_has_file(file) dict + return filereadable(self.path(a:file)) +endfunction + +function! s:app_find_file(name, ...) dict abort + let trim = strlen(self.path())+1 + if a:0 + let path = s:pathjoin(map(s:pathsplit(a:1),'self.path(v:val)')) + else + let path = s:pathjoin([self.path()]) + endif + let suffixesadd = s:pathjoin(get(a:000,1,&suffixesadd)) + let default = get(a:000,2,'') + let oldsuffixesadd = &l:suffixesadd + try + let &suffixesadd = suffixesadd + " Versions before 7.1.256 returned directories from findfile + if type(default) == type(0) && (v:version < 702 || default == -1) + let all = findfile(a:name,path,-1) + if v:version < 702 + call filter(all,'!isdirectory(v:val)') + endif + call map(all,'s:gsub(strpart(fnamemodify(v:val,":p"),trim),"\\\\","/")') + return default < 0 ? all : get(all,default-1,'') + elseif type(default) == type(0) + let found = findfile(a:name,path,default) + else + let i = 1 + let found = findfile(a:name,path) + while v:version < 702 && found != "" && isdirectory(found) + let i += 1 + let found = findfile(a:name,path,i) + endwhile + endif + return found == "" ? default : s:gsub(strpart(fnamemodify(found,':p'),trim),'\\','/') + finally + let &l:suffixesadd = oldsuffixesadd + endtry +endfunction + +call s:add_methods('app',['path','has_file','find_file']) + +" Split a path into a list. From pathogen.vim +function! s:pathsplit(path) abort + if type(a:path) == type([]) | return copy(a:path) | endif + let split = split(a:path,'\\\@<!\%(\\\\\)*\zs,') + return map(split,'substitute(v:val,''\\\([\\, ]\)'',''\1'',"g")') +endfunction + +" Convert a list to a path. From pathogen.vim +function! s:pathjoin(...) abort + let i = 0 + let path = "" + while i < a:0 + if type(a:000[i]) == type([]) + let list = a:000[i] + let j = 0 + while j < len(list) + let escaped = substitute(list[j],'[\\, ]','\\&','g') + if exists("+shellslash") && !&shellslash + let escaped = substitute(escaped,'^\(\w:\\\)\\','\1','') + endif + let path .= ',' . escaped + let j += 1 + endwhile + else + let path .= "," . a:000[i] + endif + let i += 1 + endwhile + return substitute(path,'^,','','') +endfunction + +function! s:readable_end_of(lnum) dict abort + if a:lnum == 0 + return 0 + endif + if self.name() =~# '\.yml$' + return -1 + endif + let cline = self.getline(a:lnum) + let spc = matchstr(cline,'^\s*') + let endpat = '\<end\>' + if matchstr(self.getline(a:lnum+1),'^'.spc) && !matchstr(self.getline(a:lnum+1),'^'.spc.endpat) && matchstr(cline,endpat) + return a:lnum + endif + let endl = a:lnum + while endl <= self.line_count() + let endl += 1 + if self.getline(endl) =~ '^'.spc.endpat + return endl + elseif self.getline(endl) =~ '^=begin\>' + while self.getline(endl) !~ '^=end\>' && endl <= self.line_count() + let endl += 1 + endwhile + let endl += 1 + elseif self.getline(endl) !~ '^'.spc && self.getline(endl) !~ '^\s*\%(#.*\)\=$' + return 0 + endif + endwhile + return 0 +endfunction + +function! s:endof(lnum) + return rails#buffer().end_of(a:lnum) +endfunction + +function! s:readable_last_opening_line(start,pattern,limit) dict abort + let line = a:start + while line > a:limit && self.getline(line) !~ a:pattern + let line -= 1 + endwhile + let lend = self.end_of(line) + if line > a:limit && (lend < 0 || lend >= a:start) + return line + else + return -1 + endif +endfunction + +function! s:lastopeningline(pattern,limit,start) + return rails#buffer().last_opening_line(a:start,a:pattern,a:limit) +endfunction + +function! s:readable_define_pattern() dict abort + if self.name() =~ '\.yml$' + return '^\%(\h\k*:\)\@=' + endif + let define = '^\s*def\s\+\(self\.\)\=' + if self.name() =~# '\.rake$' + let define .= "\\\|^\\s*\\%(task\\\|file\\)\\s\\+[:'\"]" + endif + if self.name() =~# '/schema\.rb$' + let define .= "\\\|^\\s*create_table\\s\\+[:'\"]" + endif + if self.type_name('test') + let define .= '\|^\s*test\s*[''"]' + endif + return define +endfunction + +function! s:readable_last_method_line(start) dict abort + return self.last_opening_line(a:start,self.define_pattern(),0) +endfunction + +function! s:lastmethodline(start) + return rails#buffer().last_method_line(a:start) +endfunction + +function! s:readable_last_method(start) dict abort + let lnum = self.last_method_line(a:start) + let line = self.getline(lnum) + if line =~# '^\s*test\s*\([''"]\).*\1' + let string = matchstr(line,'^\s*\w\+\s*\([''"]\)\zs.*\ze\1') + return 'test_'.s:gsub(string,' +','_') + elseif lnum + return s:sub(matchstr(line,'\%('.self.define_pattern().'\)\zs\h\%(\k\|[:.]\)*[?!=]\='),':$','') + else + return "" + endif +endfunction + +function! s:lastmethod(...) + return rails#buffer().last_method(a:0 ? a:1 : line(".")) +endfunction + +function! s:readable_last_format(start) dict abort + if self.type_name('view') + let format = fnamemodify(self.path(),':r:e') + if format == '' + return get({'rhtml': 'html', 'rxml': 'xml', 'rjs': 'js', 'haml': 'html'},fnamemodify(self.path(),':e'),'') + else + return format + endif + endif + let rline = self.last_opening_line(a:start,'\C^\s*\%(mail\>.*\|respond_to\)\s*\%(\<do\|{\)\s*|\zs\h\k*\ze|',self.last_method_line(a:start)) + if rline + let variable = matchstr(self.getline(rline),'\C^\s*\%(mail\>.*\|respond_to\)\s*\%(\<do\|{\)\s*|\zs\h\k*\ze|') + let line = a:start + while line > rline + let match = matchstr(self.getline(line),'\C^\s*'.variable.'\s*\.\s*\zs\h\k*') + if match != '' + return match + endif + let line -= 1 + endwhile + endif + return "" +endfunction + +function! s:lastformat(start) + return rails#buffer().last_format(a:start) +endfunction + +function! s:format(...) + let format = rails#buffer().last_format(a:0 > 1 ? a:2 : line(".")) + return format ==# '' && a:0 ? a:1 : format +endfunction + +call s:add_methods('readable',['end_of','last_opening_line','last_method_line','last_method','last_format','define_pattern']) + +let s:view_types = split('rhtml,erb,rxml,builder,rjs,mab,liquid,haml,dryml,mn,slim',',') + +function! s:viewspattern() + return '\%('.join(s:view_types,'\|').'\)' +endfunction + +function! s:controller(...) + return rails#buffer().controller_name(a:0 ? a:1 : 0) +endfunction + +function! s:readable_controller_name(...) dict abort + let f = self.name() + if has_key(self,'getvar') && self.getvar('rails_controller') != '' + return self.getvar('rails_controller') + elseif f =~ '\<app/views/layouts/' + return s:sub(f,'.*<app/views/layouts/(.{-})\..*','\1') + elseif f =~ '\<app/views/' + return s:sub(f,'.*<app/views/(.{-})/\k+\.\k+%(\.\k+)=$','\1') + elseif f =~ '\<app/helpers/.*_helper\.rb$' + return s:sub(f,'.*<app/helpers/(.{-})_helper\.rb$','\1') + elseif f =~ '\<app/controllers/.*\.rb$' + return s:sub(f,'.*<app/controllers/(.{-})%(_controller)=\.rb$','\1') + elseif f =~ '\<app/mailers/.*\.rb$' + return s:sub(f,'.*<app/mailers/(.{-})\.rb$','\1') + elseif f =~ '\<app/apis/.*_api\.rb$' + return s:sub(f,'.*<app/apis/(.{-})_api\.rb$','\1') + elseif f =~ '\<test/functional/.*_test\.rb$' + return s:sub(f,'.*<test/functional/(.{-})%(_controller)=_test\.rb$','\1') + elseif f =~ '\<test/unit/helpers/.*_helper_test\.rb$' + return s:sub(f,'.*<test/unit/helpers/(.{-})_helper_test\.rb$','\1') + elseif f =~ '\<spec/controllers/.*_spec\.rb$' + return s:sub(f,'.*<spec/controllers/(.{-})%(_controller)=_spec\.rb$','\1') + elseif f =~ '\<spec/helpers/.*_helper_spec\.rb$' + return s:sub(f,'.*<spec/helpers/(.{-})_helper_spec\.rb$','\1') + elseif f =~ '\<spec/views/.*/\w\+_view_spec\.rb$' + return s:sub(f,'.*<spec/views/(.{-})/\w+_view_spec\.rb$','\1') + elseif f =~ '\<components/.*_controller\.rb$' + return s:sub(f,'.*<components/(.{-})_controller\.rb$','\1') + elseif f =~ '\<components/.*\.'.s:viewspattern().'$' + return s:sub(f,'.*<components/(.{-})/\k+\.\k+$','\1') + elseif f =~ '\<app/models/.*\.rb$' && self.type_name('mailer') + return s:sub(f,'.*<app/models/(.{-})\.rb$','\1') + elseif f =~ '\<\%(public\|app/assets\)/stylesheets/.*\.css\%(\.\w\+\)\=$' + return s:sub(f,'.*<%(public|app/assets)/stylesheets/(.{-})\.css%(\.\w+)=$','\1') + elseif f =~ '\<\%(public\|app/assets\)/javascripts/.*\.js\%(\.\w\+\)\=$' + return s:sub(f,'.*<%(public|app/assets)/javascripts/(.{-})\.js%(\.\w+)=$','\1') + elseif a:0 && a:1 + return rails#pluralize(self.model_name()) + endif + return "" +endfunction + +function! s:model(...) + return rails#buffer().model_name(a:0 ? a:1 : 0) +endfunction + +function! s:readable_model_name(...) dict abort + let f = self.name() + if has_key(self,'getvar') && self.getvar('rails_model') != '' + return self.getvar('rails_model') + elseif f =~ '\<app/models/.*_observer.rb$' + return s:sub(f,'.*<app/models/(.*)_observer\.rb$','\1') + elseif f =~ '\<app/models/.*\.rb$' + return s:sub(f,'.*<app/models/(.*)\.rb$','\1') + elseif f =~ '\<test/unit/.*_observer_test\.rb$' + return s:sub(f,'.*<test/unit/(.*)_observer_test\.rb$','\1') + elseif f =~ '\<test/unit/.*_test\.rb$' + return s:sub(f,'.*<test/unit/(.*)_test\.rb$','\1') + elseif f =~ '\<spec/models/.*_spec\.rb$' + return s:sub(f,'.*<spec/models/(.*)_spec\.rb$','\1') + elseif f =~ '\<\%(test\|spec\)/fixtures/.*\.\w*\~\=$' + return rails#singularize(s:sub(f,'.*<%(test|spec)/fixtures/(.*)\.\w*\~=$','\1')) + elseif f =~ '\<\%(test\|spec\)/blueprints/.*\.rb$' + return s:sub(f,'.*<%(test|spec)/blueprints/(.{-})%(_blueprint)=\.rb$','\1') + elseif f =~ '\<\%(test\|spec\)/exemplars/.*_exemplar\.rb$' + return s:sub(f,'.*<%(test|spec)/exemplars/(.*)_exemplar\.rb$','\1') + elseif f =~ '\<\%(test/\|spec/\)\=factories/.*\.rb$' + return s:sub(f,'.*<%(test/|spec/)=factories/(.{-})%(_factory)=\.rb$','\1') + elseif f =~ '\<\%(test/\|spec/\)\=fabricators/.*\.rb$' + return s:sub(f,'.*<%(test/|spec/)=fabricators/(.{-})%(_fabricator)=\.rb$','\1') + elseif a:0 && a:1 + return rails#singularize(self.controller_name()) + endif + return "" +endfunction + +call s:add_methods('readable',['controller_name','model_name']) + +function! s:readfile(path,...) + let nr = bufnr('^'.a:path.'$') + if nr < 0 && exists('+shellslash') && ! &shellslash + let nr = bufnr('^'.s:gsub(a:path,'/','\\').'$') + endif + if bufloaded(nr) + return getbufline(nr,1,a:0 ? a:1 : '$') + elseif !filereadable(a:path) + return [] + elseif a:0 + return readfile(a:path,'',a:1) + else + return readfile(a:path) + endif +endfunction + +function! s:file_lines() dict abort + let ftime = getftime(self.path) + if ftime > get(self,last_lines_ftime,0) + let self.last_lines = readfile(self.path()) + let self.last_lines_ftime = ftime + endif + return get(self,'last_lines',[]) +endfunction + +function! s:file_getline(lnum,...) dict abort + if a:0 + return self.lines[lnum-1 : a:1-1] + else + return self.lines[lnum-1] + endif +endfunction + +function! s:buffer_lines() dict abort + return self.getline(1,'$') +endfunction + +function! s:buffer_getline(...) dict abort + if a:0 == 1 + return get(call('getbufline',[self.number()]+a:000),0,'') + else + return call('getbufline',[self.number()]+a:000) + endif +endfunction + +function! s:readable_line_count() dict abort + return len(self.lines()) +endfunction + +function! s:environment() + if exists('$RAILS_ENV') + return $RAILS_ENV + else + return "development" + endif +endfunction + +function! s:Complete_environments(...) + return s:completion_filter(rails#app().environments(),a:0 ? a:1 : "") +endfunction + +function! s:warn(str) + echohl WarningMsg + echomsg a:str + echohl None + " Sometimes required to flush output + echo "" + let v:warningmsg = a:str +endfunction + +function! s:error(str) + echohl ErrorMsg + echomsg a:str + echohl None + let v:errmsg = a:str +endfunction + +function! s:debug(str) + if exists("g:rails_debug") && g:rails_debug + echohl Debug + echomsg a:str + echohl None + endif +endfunction + +function! s:buffer_getvar(varname) dict abort + return getbufvar(self.number(),a:varname) +endfunction + +function! s:buffer_setvar(varname, val) dict abort + return setbufvar(self.number(),a:varname,a:val) +endfunction + +call s:add_methods('buffer',['getvar','setvar']) + +" }}}1 +" "Public" Interface {{{1 + +" RailsRoot() is the only official public function + +function! rails#underscore(str) + let str = s:gsub(a:str,'::','/') + let str = s:gsub(str,'(\u+)(\u\l)','\1_\2') + let str = s:gsub(str,'(\l|\d)(\u)','\1_\2') + let str = tolower(str) + return str +endfunction + +function! rails#camelize(str) + let str = s:gsub(a:str,'/(.=)','::\u\1') + let str = s:gsub(str,'%([_-]|<)(.)','\u\1') + return str +endfunction + +function! rails#singularize(word) + " Probably not worth it to be as comprehensive as Rails but we can + " still hit the common cases. + let word = a:word + if word =~? '\.js$' || word == '' + return word + endif + let word = s:sub(word,'eople$','ersons') + let word = s:sub(word,'%([Mm]ov|[aeio])@<!ies$','ys') + let word = s:sub(word,'xe[ns]$','xs') + let word = s:sub(word,'ves$','fs') + let word = s:sub(word,'ss%(es)=$','sss') + let word = s:sub(word,'s$','') + let word = s:sub(word,'%([nrt]ch|tatus|lias)\zse$','') + let word = s:sub(word,'%(nd|rt)\zsice$','ex') + return word +endfunction + +function! rails#pluralize(word) + let word = a:word + if word == '' + return word + endif + let word = s:sub(word,'[aeio]@<!y$','ie') + let word = s:sub(word,'%(nd|rt)@<=ex$','ice') + let word = s:sub(word,'%([osxz]|[cs]h)$','&e') + let word = s:sub(word,'f@<!f$','ve') + let word .= 's' + let word = s:sub(word,'ersons$','eople') + return word +endfunction + +function! rails#app(...) + let root = a:0 ? a:1 : RailsRoot() + " TODO: populate dynamically + " TODO: normalize path + return get(s:apps,root,0) +endfunction + +function! rails#buffer(...) + return extend(extend({'#': bufnr(a:0 ? a:1 : '%')},s:buffer_prototype,'keep'),s:readable_prototype,'keep') + endif +endfunction + +function! s:buffer_app() dict abort + if self.getvar('rails_root') != '' + return rails#app(self.getvar('rails_root')) + else + return 0 + endif +endfunction + +function! s:readable_app() dict abort + return self._app +endfunction + +function! RailsRevision() + return 1000*matchstr(g:autoloaded_rails,'^\d\+')+matchstr(g:autoloaded_rails,'[1-9]\d*$') +endfunction + +function! RailsRoot() + if exists("b:rails_root") + return b:rails_root + else + return "" + endif +endfunction + +function! s:app_file(name) + return extend(extend({'_app': self, '_name': a:name}, s:file_prototype,'keep'),s:readable_prototype,'keep') +endfunction + +function! s:file_path() dict abort + return self.app().path(self._name) +endfunction + +function! s:file_name() dict abort + return self._name +endfunction + +function! s:buffer_number() dict abort + return self['#'] +endfunction + +function! s:buffer_path() dict abort + return s:gsub(fnamemodify(bufname(self.number()),':p'),'\\ @!','/') +endfunction + +function! s:buffer_name() dict abort + let app = self.app() + let f = s:gsub(resolve(fnamemodify(bufname(self.number()),':p')),'\\ @!','/') + let f = s:sub(f,'/$','') + let sep = matchstr(f,'^[^\\/]\{3,\}\zs[\\/]') + if sep != "" + let f = getcwd().sep.f + endif + if s:startswith(tolower(f),s:gsub(tolower(app.path()),'\\ @!','/')) || f == "" + return strpart(f,strlen(app.path())+1) + else + if !exists("s:path_warn") + let s:path_warn = 1 + call s:warn("File ".f." does not appear to be under the Rails root ".self.app().path().". Please report to the rails.vim author!") + endif + return f + endif +endfunction + +function! RailsFilePath() + if !exists("b:rails_root") + return "" + else + return rails#buffer().name() + endif +endfunction + +function! RailsFile() + return RailsFilePath() +endfunction + +function! RailsFileType() + if !exists("b:rails_root") + return "" + else + return rails#buffer().type_name() + end +endfunction + +function! s:readable_calculate_file_type() dict abort + let f = self.name() + let e = fnamemodify(f,':e') + let r = "-" + let full_path = self.path() + let nr = bufnr('^'.full_path.'$') + if nr < 0 && exists('+shellslash') && ! &shellslash + let nr = bufnr('^'.s:gsub(full_path,'/','\\').'$') + endif + if f == "" + let r = f + elseif nr > 0 && getbufvar(nr,'rails_file_type') != '' + return getbufvar(nr,'rails_file_type') + elseif f =~ '_controller\.rb$' || f =~ '\<app/controllers/.*\.rb$' + if join(s:readfile(full_path,50),"\n") =~ '\<wsdl_service_name\>' + let r = "controller-api" + else + let r = "controller" + endif + elseif f =~ '_api\.rb' + let r = "api" + elseif f =~ '\<test/test_helper\.rb$' + let r = "test" + elseif f =~ '\<spec/spec_helper\.rb$' + let r = "spec" + elseif f =~ '_helper\.rb$' + let r = "helper" + elseif f =~ '\<app/metal/.*\.rb$' + let r = "metal" + elseif f =~ '\<app/mailers/.*\.rb' + let r = "mailer" + elseif f =~ '\<app/models/' + let top = join(s:readfile(full_path,50),"\n") + let class = matchstr(top,'\<Acti\w\w\u\w\+\%(::\h\w*\)\+\>') + if class == "ActiveResource::Base" + let class = "ares" + let r = "model-ares" + elseif class == 'ActionMailer::Base' + let r = "mailer" + elseif class != '' + let class = tolower(s:gsub(class,'[^A-Z]','')) + let r = "model-".class + elseif f =~ '_mailer\.rb$' + let r = "mailer" + elseif top =~ '\<\%(validates_\w\+_of\|set_\%(table_name\|primary_key\)\|has_one\|has_many\|belongs_to\)\>' + let r = "model-arb" + else + let r = "model" + endif + elseif f =~ '\<app/views/layouts\>.*\.' + let r = "view-layout-" . e + elseif f =~ '\<\%(app/views\|components\)/.*/_\k\+\.\k\+\%(\.\k\+\)\=$' + let r = "view-partial-" . e + elseif f =~ '\<app/views\>.*\.' || f =~ '\<components/.*/.*\.'.s:viewspattern().'$' + let r = "view-" . e + elseif f =~ '\<test/unit/.*_test\.rb$' + let r = "test-unit" + elseif f =~ '\<test/functional/.*_test\.rb$' + let r = "test-functional" + elseif f =~ '\<test/integration/.*_test\.rb$' + let r = "test-integration" + elseif f =~ '\<spec/lib/.*_spec\.rb$' + let r = 'spec-lib' + elseif f =~ '\<lib/.*\.rb$' + let r = 'lib' + elseif f =~ '\<spec/\w*s/.*_spec\.rb$' + let r = s:sub(f,'.*<spec/(\w*)s/.*','spec-\1') + elseif f =~ '\<features/.*\.feature$' + let r = 'cucumber-feature' + elseif f =~ '\<features/step_definitions/.*_steps\.rb$' + let r = 'cucumber-steps' + elseif f =~ '\<features/.*\.rb$' + let r = 'cucumber' + elseif f =~ '\<\%(test\|spec\)/fixtures\>' + if e == "yml" + let r = "fixtures-yaml" + else + let r = "fixtures" . (e == "" ? "" : "-" . e) + endif + elseif f =~ '\<test/.*_test\.rb' + let r = "test" + elseif f =~ '\<spec/.*_spec\.rb' + let r = "spec" + elseif f =~ '\<spec/support/.*\.rb' + let r = "spec" + elseif f =~ '\<db/migrate\>' + let r = "db-migration" + elseif f=~ '\<db/schema\.rb$' + let r = "db-schema" + elseif f =~ '\<vendor/plugins/.*/recipes/.*\.rb$' || f =~ '\.rake$' || f =~ '\<\%(Rake\|Cap\)file$' || f =~ '\<config/deploy\.rb$' + let r = "task" + elseif f =~ '\<log/.*\.log$' + let r = "log" + elseif e == "css" || e =~ "s[ac]ss" || e == "less" + let r = "stylesheet-".e + elseif e == "js" + let r = "javascript" + elseif e == "coffee" + let r = "javascript-coffee" + elseif e == "html" + let r = e + elseif f =~ '\<config/routes\>.*\.rb$' + let r = "config-routes" + elseif f =~ '\<config/' + let r = "config" + endif + return r +endfunction + +function! s:buffer_type_name(...) dict abort + let type = getbufvar(self.number(),'rails_cached_file_type') + if type == '' + let type = self.calculate_file_type() + endif + return call('s:match_type',[type == '-' ? '' : type] + a:000) +endfunction + +function! s:readable_type_name() dict abort + let type = self.calculate_file_type() + return call('s:match_type',[type == '-' ? '' : type] + a:000) +endfunction + +function! s:match_type(type,...) + if a:0 + return !empty(filter(copy(a:000),'a:type =~# "^".v:val."\\%(-\\|$\\)"')) + else + return a:type + endif +endfunction + +function! s:app_environments() dict + if self.cache.needs('environments') + call self.cache.set('environments',self.relglob('config/environments/','**/*','.rb')) + endif + return copy(self.cache.get('environments')) +endfunction + +function! s:app_default_locale() dict abort + if self.cache.needs('default_locale') + let candidates = map(filter(s:readfile(self.path('config/environment.rb')),'v:val =~ "^ *config.i18n.default_locale = :[\"'']\\=[A-Za-z-]\\+[\"'']\\= *$"'),'matchstr(v:val,"[A-Za-z-]\\+[\"'']\\= *$")') + call self.cache.set('default_locale',get(candidates,0,'en')) + endif + return self.cache.get('default_locale') +endfunction + +function! s:app_has(feature) dict + let map = { + \'test': 'test/', + \'spec': 'spec/', + \'cucumber': 'features/', + \'sass': 'public/stylesheets/sass/', + \'lesscss': 'app/stylesheets/', + \'coffee': 'app/scripts/'} + if self.cache.needs('features') + call self.cache.set('features',{}) + endif + let features = self.cache.get('features') + if !has_key(features,a:feature) + let path = get(map,a:feature,a:feature.'/') + let features[a:feature] = isdirectory(rails#app().path(path)) + endif + return features[a:feature] +endfunction + +" Returns the subset of ['test', 'spec', 'cucumber'] present on the app. +function! s:app_test_suites() dict + return filter(['test','spec','cucumber'],'self.has(v:val)') +endfunction + +call s:add_methods('app',['default_locale','environments','file','has','test_suites']) +call s:add_methods('file',['path','name','lines','getline']) +call s:add_methods('buffer',['app','number','path','name','lines','getline','type_name']) +call s:add_methods('readable',['app','calculate_file_type','type_name','line_count']) + +" }}}1 +" Ruby Execution {{{1 + +function! s:app_ruby_shell_command(cmd) dict abort + if self.path() =~ '://' + return "ruby ".a:cmd + else + return "ruby -C ".s:rquote(self.path())." ".a:cmd + endif +endfunction + +function! s:app_script_shell_command(cmd) dict abort + if self.has_file('script/rails') && a:cmd !~# '^rails\>' + let cmd = 'script/rails '.a:cmd + else + let cmd = 'script/'.a:cmd + endif + return self.ruby_shell_command(cmd) +endfunction + +function! s:app_background_script_command(cmd) dict abort + let cmd = s:esccmd(self.script_shell_command(a:cmd)) + if has_key(self,'options') && has_key(self.options,'gnu_screen') + let screen = self.options.gnu_screen + else + let screen = g:rails_gnu_screen + endif + if has("gui_win32") + if &shellcmdflag == "-c" && ($PATH . &shell) =~? 'cygwin' + silent exe "!cygstart -d ".s:rquote(self.path())." ruby ".a:cmd + else + exe "!start ".cmd + endif + elseif exists("$STY") && !has("gui_running") && screen && executable("screen") + silent exe "!screen -ln -fn -t ".s:sub(s:sub(a:cmd,'\s.*',''),'^%(script|-rcommand)/','rails-').' '.cmd + elseif exists("$TMUX") && !has("gui_running") && screen && executable("tmux") + silent exe '!tmux new-window -d -n "'.s:sub(s:sub(a:cmd,'\s.*',''),'^%(script|-rcommand)/','rails-').'" "'.cmd.'"' + else + exe "!".cmd + endif + return v:shell_error +endfunction + +function! s:app_execute_script_command(cmd) dict abort + exe '!'.s:esccmd(self.script_shell_command(a:cmd)) + return v:shell_error +endfunction + +function! s:app_lightweight_ruby_eval(ruby,...) dict abort + let def = a:0 ? a:1 : "" + if !executable("ruby") + return def + endif + let args = '-e '.s:rquote('begin; require %{rubygems}; rescue LoadError; end; begin; require %{active_support}; rescue LoadError; end; '.a:ruby) + let cmd = self.ruby_shell_command(args) + " If the shell is messed up, this command could cause an error message + silent! let results = system(cmd) + return v:shell_error == 0 ? results : def +endfunction + +function! s:app_eval(ruby,...) dict abort + let def = a:0 ? a:1 : "" + if !executable("ruby") + return def + endif + let args = "-r./config/boot -r ".s:rquote(self.path("config/environment"))." -e ".s:rquote(a:ruby) + let cmd = self.ruby_shell_command(args) + " If the shell is messed up, this command could cause an error message + silent! let results = system(cmd) + return v:shell_error == 0 ? results : def +endfunction + +call s:add_methods('app', ['ruby_shell_command','script_shell_command','execute_script_command','background_script_command','lightweight_ruby_eval','eval']) + +" }}}1 +" Commands {{{1 + +function! s:prephelp() + let fn = fnamemodify(s:file,':h:h').'/doc/' + if filereadable(fn.'rails.txt') + if !filereadable(fn.'tags') || getftime(fn.'tags') <= getftime(fn.'rails.txt') + silent! helptags `=fn` + endif + endif +endfunction + +function! RailsHelpCommand(...) + call s:prephelp() + let topic = a:0 ? a:1 : "" + if topic == "" || topic == "-" + return "help rails" + elseif topic =~ '^g:' + return "help ".topic + elseif topic =~ '^-' + return "help rails".topic + else + return "help rails-".topic + endif +endfunction + +function! s:BufCommands() + call s:BufFinderCommands() + call s:BufNavCommands() + call s:BufScriptWrappers() + command! -buffer -bar -nargs=? -bang -count -complete=customlist,s:Complete_rake Rake :call s:Rake(<bang>0,!<count> && <line1> ? -1 : <count>,<q-args>) + command! -buffer -bar -nargs=? -bang -range -complete=customlist,s:Complete_preview Rpreview :call s:Preview(<bang>0,<line1>,<q-args>) + command! -buffer -bar -nargs=? -bang -complete=customlist,s:Complete_environments Rlog :call s:Log(<bang>0,<q-args>) + command! -buffer -bar -nargs=* -bang -complete=customlist,s:Complete_set Rset :call s:Set(<bang>0,<f-args>) + command! -buffer -bar -nargs=0 Rtags :call rails#app().tags_command() + " Embedding all this logic directly into the command makes the error + " messages more concise. + command! -buffer -bar -nargs=? -bang Rdoc : + \ if <bang>0 || <q-args> =~ "^\\([:'-]\\|g:\\)" | + \ exe RailsHelpCommand(<q-args>) | + \ else | call s:Doc(<bang>0,<q-args>) | endif + command! -buffer -bar -nargs=0 -bang Rrefresh :if <bang>0|unlet! g:autoloaded_rails|source `=s:file`|endif|call s:Refresh(<bang>0) + if exists(":NERDTree") + command! -buffer -bar -nargs=? -complete=customlist,s:Complete_cd Rtree :NERDTree `=rails#app().path(<f-args>)` + endif + if exists("g:loaded_dbext") + command! -buffer -bar -nargs=? -complete=customlist,s:Complete_environments Rdbext :call s:BufDatabase(2,<q-args>)|let b:dbext_buffer_defaulted = 1 + endif + let ext = expand("%:e") + if ext =~ s:viewspattern() + " TODO: complete controller names with trailing slashes here + command! -buffer -bar -bang -nargs=? -range -complete=customlist,s:controllerList Rextract :<line1>,<line2>call s:Extract(<bang>0,<f-args>) + endif + if RailsFilePath() =~ '\<db/migrate/.*\.rb$' + command! -buffer -bar Rinvert :call s:Invert(<bang>0) + endif +endfunction + +function! s:Doc(bang, string) + if a:string != "" + if exists("g:rails_search_url") + let query = substitute(a:string,'[^A-Za-z0-9_.~-]','\="%".printf("%02X",char2nr(submatch(0)))','g') + let url = printf(g:rails_search_url, query) + else + return s:error("specify a g:rails_search_url with %s for a query placeholder") + endif + elseif isdirectory(rails#app().path("doc/api/classes")) + let url = rails#app().path("/doc/api/index.html") + elseif s:getpidfor("0.0.0.0","8808") > 0 + let url = "http://localhost:8808" + else + let url = "http://api.rubyonrails.org" + endif + call s:initOpenURL() + if exists(":OpenURL") + exe "OpenURL ".s:escarg(url) + else + return s:error("No :OpenURL command found") + endif +endfunction + +function! s:Log(bang,arg) + if a:arg == "" + let lf = "log/".s:environment().".log" + else + let lf = "log/".a:arg.".log" + endif + let size = getfsize(rails#app().path(lf)) + if size >= 1048576 + call s:warn("Log file is ".((size+512)/1024)."KB. Consider :Rake log:clear") + endif + if a:bang + exe "cgetfile ".lf + clast + else + if exists(":Tail") + Tail `=rails#app().path(lf)` + else + pedit `=rails#app().path(lf)` + endif + endif +endfunction + +function! rails#new_app_command(bang,...) + if a:0 == 0 + let msg = "rails.vim ".g:autoloaded_rails + if a:bang && exists('b:rails_root') && rails#buffer().type_name() == '' + echo msg." (Rails)" + elseif a:bang && exists('b:rails_root') + echo msg." (Rails-".rails#buffer().type_name().")" + elseif a:bang + echo msg + else + !rails + endif + return + endif + let args = map(copy(a:000),'expand(v:val)') + if a:bang + let args = ['--force'] + args + endif + exe '!rails '.join(map(copy(args),'s:rquote(v:val)'),' ') + for dir in args + if dir !~# '^-' && filereadable(dir.'/'.g:rails_default_file) + edit `=dir.'/'.g:rails_default_file` + return + endif + endfor +endfunction + +function! s:app_tags_command() dict + if exists("g:Tlist_Ctags_Cmd") + let cmd = g:Tlist_Ctags_Cmd + elseif executable("exuberant-ctags") + let cmd = "exuberant-ctags" + elseif executable("ctags-exuberant") + let cmd = "ctags-exuberant" + elseif executable("ctags") + let cmd = "ctags" + elseif executable("ctags.exe") + let cmd = "ctags.exe" + else + return s:error("ctags not found") + endif + exe '!'.cmd.' -f '.s:escarg(self.path("tmp/tags")).' -R --langmap="ruby:+.rake.builder.rjs" '.g:rails_ctags_arguments.' '.s:escarg(self.path()) +endfunction + +call s:add_methods('app',['tags_command']) + +function! s:Refresh(bang) + if exists("g:rubycomplete_rails") && g:rubycomplete_rails && has("ruby") && exists('g:rubycomplete_completions') + silent! ruby ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) + silent! ruby if defined?(ActiveSupport::Dependencies); ActiveSupport::Dependencies.clear; elsif defined?(Dependencies); Dependencies.clear; end + if a:bang + silent! ruby ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) + endif + endif + call rails#app().cache.clear() + silent doautocmd User BufLeaveRails + if a:bang + for key in keys(s:apps) + if type(s:apps[key]) == type({}) + call s:apps[key].cache.clear() + endif + call extend(s:apps[key],filter(copy(s:app_prototype),'type(v:val) == type(function("tr"))'),'force') + endfor + endif + let i = 1 + let max = bufnr('$') + while i <= max + let rr = getbufvar(i,"rails_root") + if rr != "" + call setbufvar(i,"rails_refresh",1) + endif + let i += 1 + endwhile + silent doautocmd User BufEnterRails +endfunction + +function! s:RefreshBuffer() + if exists("b:rails_refresh") && b:rails_refresh + let oldroot = b:rails_root + unlet! b:rails_root + let b:rails_refresh = 0 + call RailsBufInit(oldroot) + unlet! b:rails_refresh + endif +endfunction + +" }}}1 +" Rake {{{1 + +function! s:app_rake_tasks() dict + if self.cache.needs('rake_tasks') + call s:push_chdir() + try + let lines = split(system("rake -T"),"\n") + finally + call s:pop_command() + endtry + if v:shell_error != 0 + return [] + endif + call map(lines,'matchstr(v:val,"^rake\\s\\+\\zs\\S*")') + call filter(lines,'v:val != ""') + call self.cache.set('rake_tasks',lines) + endif + return self.cache.get('rake_tasks') +endfunction + +call s:add_methods('app', ['rake_tasks']) + +let s:efm_backtrace='%D(in\ %f),' + \.'%\\s%#from\ %f:%l:%m,' + \.'%\\s%#from\ %f:%l:,' + \.'%\\s#{RAILS_ROOT}/%f:%l:\ %#%m,' + \.'%\\s%##\ %f:%l:%m,' + \.'%\\s%##\ %f:%l,' + \.'%\\s%#[%f:%l:\ %#%m,' + \.'%\\s%#%f:%l:\ %#%m,' + \.'%\\s%#%f:%l:,' + \.'%m\ [%f:%l]:' + +function! s:makewithruby(arg,bang,...) + let old_make = &makeprg + try + let &l:makeprg = rails#app().ruby_shell_command(a:arg) + exe 'make'.(a:bang ? '!' : '') + if !a:bang + cwindow + endif + finally + let &l:makeprg = old_make + endtry +endfunction + +function! s:Rake(bang,lnum,arg) + let self = rails#app() + let lnum = a:lnum < 0 ? 0 : a:lnum + let old_makeprg = &l:makeprg + let old_errorformat = &l:errorformat + try + if exists('b:bundler_root') && b:bundler_root ==# rails#app().path() + let &l:makeprg = 'bundle exec rake' + else + let &l:makeprg = 'rake' + endif + let &l:errorformat = s:efm_backtrace + let arg = a:arg + if &filetype == "ruby" && arg == '' && g:rails_modelines + let mnum = s:lastmethodline(lnum) + let str = getline(mnum)."\n".getline(mnum+1)."\n".getline(mnum+2)."\n" + let pat = '\s\+\zs.\{-\}\ze\%(\n\|\s\s\|#{\@!\|$\)' + let mat = matchstr(str,'#\s*rake'.pat) + let mat = s:sub(mat,'\s+$','') + if mat != "" + let arg = mat + endif + endif + if arg == '' + let opt = s:getopt('task','bl') + if opt != '' + let arg = opt + else + let arg = rails#buffer().default_rake_task(lnum) + endif + endif + if !has_key(self,'options') | let self.options = {} | endif + if arg == '-' + let arg = get(self.options,'last_rake_task','') + endif + let self.options['last_rake_task'] = arg + let withrubyargs = '-r ./config/boot -r '.s:rquote(self.path('config/environment')).' -e "puts \%((in \#{Dir.getwd}))" ' + if arg =~# '^notes\>' + let &l:errorformat = '%-P%f:,\ \ *\ [%*[\ ]%l]\ [%t%*[^]]] %m,\ \ *\ [%*[\ ]%l] %m,%-Q' + " %D to chdir is apparently incompatible with %P multiline messages + call s:push_chdir(1) + exe 'make! '.arg + call s:pop_command() + if !a:bang + cwindow + endif + elseif arg =~# '^\%(stats\|routes\|secret\|time:zones\|db:\%(charset\|collation\|fixtures:identify\>.*\|migrate:status\|version\)\)\%([: ]\|$\)' + let &l:errorformat = '%D(in\ %f),%+G%.%#' + exe 'make! '.arg + if !a:bang + copen + endif + elseif arg =~ '^preview\>' + exe (lnum == 0 ? '' : lnum).'R'.s:gsub(arg,':','/') + elseif arg =~ '^runner:' + let arg = s:sub(arg,'^runner:','') + let root = matchstr(arg,'%\%(:\w\)*') + let file = expand(root).matchstr(arg,'%\%(:\w\)*\zs.*') + if file =~ '#.*$' + let extra = " -- -n ".matchstr(file,'#\zs.*') + let file = s:sub(file,'#.*','') + else + let extra = '' + endif + if self.has_file(file) || self.has_file(file.'.rb') + call s:makewithruby(withrubyargs.'-r"'.file.'"'.extra,a:bang,file !~# '_\%(spec\|test\)\%(\.rb\)\=$') + else + call s:makewithruby(withrubyargs.'-e '.s:esccmd(s:rquote(arg)),a:bang) + endif + elseif arg == 'run' || arg == 'runner' + call s:makewithruby(withrubyargs.'-r"'.RailsFilePath().'"',a:bang,RailsFilePath() !~# '_\%(spec\|test\)\%(\.rb\)\=$') + elseif arg =~ '^run:' + let arg = s:sub(arg,'^run:','') + let arg = s:sub(arg,'^\%:h',expand('%:h')) + let arg = s:sub(arg,'^%(\%|$|#@=)',expand('%')) + let arg = s:sub(arg,'#(\w+[?!=]=)$',' -- -n\1') + call s:makewithruby(withrubyargs.'-r'.arg,a:bang,arg !~# '_\%(spec\|test\)\.rb$') + else + exe 'make! '.arg + if !a:bang + cwindow + endif + endif + finally + let &l:errorformat = old_errorformat + let &l:makeprg = old_makeprg + endtry +endfunction + +function! s:readable_default_rake_task(lnum) dict abort + let app = self.app() + let lnum = a:lnum < 0 ? 0 : a:lnum + if self.getvar('&buftype') == 'quickfix' + return '-' + elseif self.getline(lnum) =~# '# rake ' + return matchstr(self.getline(lnum),'\C# rake \zs.*') + elseif self.getline(self.last_method_line(lnum)-1) =~# '# rake ' + return matchstr(self.getline(self.last_method_line(lnum)-1),'\C# rake \zs.*') + elseif self.getline(self.last_method_line(lnum)) =~# '# rake ' + return matchstr(self.getline(self.last_method_line(lnum)),'\C# rake \zs.*') + elseif self.getline(1) =~# '# rake ' && !lnum + return matchstr(self.getline(1),'\C# rake \zs.*') + elseif self.type_name('config-routes') + return 'routes' + elseif self.type_name('fixtures-yaml') && lnum + return "db:fixtures:identify LABEL=".self.last_method(lnum) + elseif self.type_name('fixtures') && lnum == 0 + return "db:fixtures:load FIXTURES=".s:sub(fnamemodify(self.name(),':r'),'^.{-}/fixtures/','') + elseif self.type_name('task') + let mnum = self.last_method_line(lnum) + let line = getline(mnum) + " We can't grab the namespace so only run tasks at the start of the line + if line =~# '^\%(task\|file\)\>' + return self.last_method(a:lnum) + else + return matchstr(self.getline(1),'\C# rake \zs.*') + endif + elseif self.type_name('spec') + if self.name() =~# '\<spec/spec_helper\.rb$' + return 'spec' + elseif lnum > 0 + return 'spec SPEC="'.self.path().'":'.lnum + else + return 'spec SPEC="'.self.path().'"' + endif + elseif self.type_name('test') + let meth = self.last_method(lnum) + if meth =~ '^test_' + let call = " -n".meth."" + else + let call = "" + endif + if self.type_name('test-unit','test-functional','test-integration') + return s:sub(s:gsub(self.type_name(),'-',':'),'unit$|functional$','&s').' TEST="'.self.path().'"'.s:sub(call,'^ ',' TESTOPTS=') + elseif self.name() =~# '\<test/test_helper\.rb$' + return 'test' + else + return 'test:recent TEST="'.self.path().'"'.s:sub(call,'^ ',' TESTOPTS=') + endif + elseif self.type_name('db-migration') + let ver = matchstr(self.name(),'\<db/migrate/0*\zs\d*\ze_') + if ver != "" + let method = self.last_method(lnum) + if method == "down" || lnum == 1 + return "db:migrate:down VERSION=".ver + elseif method == "up" || lnum == line('$') + return "db:migrate:up VERSION=".ver + elseif lnum > 0 + return "db:migrate:down db:migrate:up VERSION=".ver + else + return "db:migrate VERSION=".ver + endif + else + return 'db:migrate' + endif + elseif self.name() =~# '\<db/seeds\.rb$' + return 'db:seed' + elseif self.type_name('controller') && lnum + let lm = self.last_method(lnum) + if lm != '' + " rake routes doesn't support ACTION... yet... + return 'routes CONTROLLER='.self.controller_name().' ACTION='.lm + else + return 'routes CONTROLLER='.self.controller_name() + endif + elseif app.has('spec') && self.name() =~# '^app/.*\.\w\+$' && app.has_file(s:sub(self.name(),'^app/(.*)\.\w\+$','spec/\1_spec.rb')) + return 'spec SPEC="'.fnamemodify(s:sub(self.name(),'<app/','spec/'),':p:r').'_spec.rb"' + elseif app.has('spec') && self.name() =~# '^app/.*\.\w\+$' && app.has_file(s:sub(self.name(),'^app/(.*)$','spec/\1_spec.rb')) + return 'spec SPEC="'.fnamemodify(s:sub(self.name(),'<app/','spec/'),':p').'_spec.rb"' + elseif self.type_name('model') + return 'test:units TEST="'.fnamemodify(s:sub(self.name(),'<app/models/','test/unit/'),':p:r').'_test.rb"' + elseif self.type_name('api','mailer') + return 'test:units TEST="'.fnamemodify(s:sub(self.name(),'<app/%(apis|mailers|models)/','test/functional/'),':p:r').'_test.rb"' + elseif self.type_name('helper') + return 'test:units TEST="'.fnamemodify(s:sub(self.name(),'<app/','test/unit/'),':p:r').'_test.rb"' + elseif self.type_name('controller','helper','view') + if self.name() =~ '\<app/' && s:controller() !~# '^\%(application\)\=$' + return 'test:functionals TEST="'.s:escarg(app.path('test/functional/'.s:controller().'_controller_test.rb')).'"' + else + return 'test:functionals' + endif + elseif self.type_name('cucumber-feature') + if lnum > 0 + return 'cucumber FEATURE="'.self.path().'":'.lnum + else + return 'cucumber FEATURE="'.self.path().'"' + endif + elseif self.type_name('cucumber') + return 'cucumber' + else + return '' + endif +endfunction + +function! s:Complete_rake(A,L,P) + return s:completion_filter(rails#app().rake_tasks(),a:A) +endfunction + +call s:add_methods('readable',['default_rake_task']) + +" }}}1 +" Preview {{{1 + +function! s:initOpenURL() + if !exists(":OpenURL") + if has("gui_mac") || has("gui_macvim") || exists("$SECURITYSESSIONID") + command -bar -nargs=1 OpenURL :!open <args> + elseif has("gui_win32") + command -bar -nargs=1 OpenURL :!start cmd /cstart /b <args> + elseif executable("sensible-browser") + command -bar -nargs=1 OpenURL :!sensible-browser <args> + endif + endif +endfunction + +function! s:scanlineforuris(line) + let url = matchstr(a:line,"\\v\\C%(%(GET|PUT|POST|DELETE)\\s+|\\w+://[^/]*)/[^ \n\r\t<>\"]*[^] .,;\n\r\t<>\":]") + if url =~ '\C^\u\+\s\+' + let method = matchstr(url,'^\u\+') + let url = matchstr(url,'\s\+\zs.*') + if method !=? "GET" + let url .= (url =~ '?' ? '&' : '?') . '_method='.tolower(method) + endif + endif + if url != "" + return [url] + else + return [] + endif +endfunction + +function! s:readable_preview_urls(lnum) dict abort + let urls = [] + let start = self.last_method_line(a:lnum) - 1 + while start > 0 && self.getline(start) =~ '^\s*\%(\%(-\=\|<%\)#.*\)\=$' + let urls = s:scanlineforuris(self.getline(start)) + urls + let start -= 1 + endwhile + let start = 1 + while start < self.line_count() && self.getline(start) =~ '^\s*\%(\%(-\=\|<%\)#.*\)\=$' + let urls += s:scanlineforuris(self.getline(start)) + let start += 1 + endwhile + if has_key(self,'getvar') && self.getvar('rails_preview') != '' + let url += [self.getvar('rails_preview')] + end + if self.name() =~ '^public/stylesheets/sass/' + let urls = urls + [s:sub(s:sub(self.name(),'^public/stylesheets/sass/','/stylesheets/'),'\.s[ac]ss$','.css')] + elseif self.name() =~ '^public/' + let urls = urls + [s:sub(self.name(),'^public','')] + elseif self.name() =~ '^app/assets/stylesheets/' + let urls = urls + ['/assets/application.css'] + elseif self.name() =~ '^app/assets/javascripts/' + let urls = urls + ['/assets/application.js'] + elseif self.name() =~ '^app/stylesheets/' + let urls = urls + [s:sub(s:sub(self.name(),'^app/stylesheets/','/stylesheets/'),'\.less$','.css')] + elseif self.name() =~ '^app/scripts/' + let urls = urls + [s:sub(s:sub(self.name(),'^app/scripts/','/javascripts/'),'\.coffee$','.js')] + elseif self.controller_name() != '' && self.controller_name() != 'application' + if self.type_name('controller') && self.last_method(a:lnum) != '' + let urls += ['/'.self.controller_name().'/'.self.last_method(a:lnum).'/'] + elseif self.type_name('controller','view-layout','view-partial') + let urls += ['/'.self.controller_name().'/'] + elseif self.type_name('view') + let urls += ['/'.s:controller().'/'.fnamemodify(self.name(),':t:r:r').'/'] + endif + endif + return urls +endfunction + +call s:add_methods('readable',['preview_urls']) + +function! s:Preview(bang,lnum,arg) + let root = s:getopt("root_url") + if root == '' + let root = s:getopt("url") + endif + let root = s:sub(root,'/$','') + if a:arg =~ '://' + let uri = a:arg + elseif a:arg != '' + let uri = root.'/'.s:sub(a:arg,'^/','') + else + let uri = get(rails#buffer().preview_urls(a:lnum),0,'') + let uri = root.'/'.s:sub(s:sub(uri,'^/',''),'/$','') + endif + call s:initOpenURL() + if exists(':OpenURL') && !a:bang + exe 'OpenURL '.uri + else + " Work around bug where URLs ending in / get handled as FTP + let url = uri.(uri =~ '/$' ? '?' : '') + silent exe 'pedit '.url + wincmd w + if &filetype == '' + if uri =~ '\.css$' + setlocal filetype=css + elseif uri =~ '\.js$' + setlocal filetype=javascript + elseif getline(1) =~ '^\s*<' + setlocal filetype=xhtml + endif + endif + call RailsBufInit(rails#app().path()) + map <buffer> <silent> q :bwipe<CR> + wincmd p + if !a:bang + call s:warn("Define a :OpenURL command to use a browser") + endif + endif +endfunction + +function! s:Complete_preview(A,L,P) + return rails#buffer().preview_urls(a:L =~ '^\d' ? matchstr(a:L,'^\d\+') : line('.')) +endfunction + +" }}}1 +" Script Wrappers {{{1 + +function! s:BufScriptWrappers() + command! -buffer -bar -nargs=* -complete=customlist,s:Complete_script Rscript :call rails#app().script_command(<bang>0,<f-args>) + command! -buffer -bar -nargs=* -complete=customlist,s:Complete_generate Rgenerate :call rails#app().generate_command(<bang>0,<f-args>) + command! -buffer -bar -nargs=* -complete=customlist,s:Complete_destroy Rdestroy :call rails#app().destroy_command(<bang>0,<f-args>) + command! -buffer -bar -nargs=? -bang -complete=customlist,s:Complete_server Rserver :call rails#app().server_command(<bang>0,<q-args>) + command! -buffer -bang -nargs=1 -range=0 -complete=customlist,s:Complete_ruby Rrunner :call rails#app().runner_command(<bang>0 ? -2 : (<count>==<line2>?<count>:-1),<f-args>) + command! -buffer -nargs=1 -range=0 -complete=customlist,s:Complete_ruby Rp :call rails#app().runner_command(<count>==<line2>?<count>:-1,'p begin '.<f-args>.' end') + command! -buffer -nargs=1 -range=0 -complete=customlist,s:Complete_ruby Rpp :call rails#app().runner_command(<count>==<line2>?<count>:-1,'require %{pp}; pp begin '.<f-args>.' end') + command! -buffer -nargs=1 -range=0 -complete=customlist,s:Complete_ruby Ry :call rails#app().runner_command(<count>==<line2>?<count>:-1,'y begin '.<f-args>.' end') +endfunction + +function! s:app_generators() dict + if self.cache.needs('generators') + let generators = self.relglob("vendor/plugins/","*/generators/*") + let generators += self.relglob("","lib/generators/*") + call filter(generators,'v:val =~ "/$"') + let generators += split(glob(expand("~/.rails/generators")."/*"),"\n") + call map(generators,'s:sub(v:val,"^.*[\\\\/]generators[\\\\/]\\ze.","")') + call map(generators,'s:sub(v:val,"[\\\\/]$","")') + call self.cache.set('generators',generators) + endif + return sort(split(g:rails_generators,"\n") + self.cache.get('generators')) +endfunction + +function! s:app_script_command(bang,...) dict + let str = "" + let cmd = a:0 ? a:1 : "console" + let c = 2 + while c <= a:0 + let str .= " " . s:rquote(a:{c}) + let c += 1 + endwhile + if cmd ==# "plugin" + call self.cache.clear('generators') + endif + if a:bang || cmd =~# 'console' + return self.background_script_command(cmd.str) + else + return self.execute_script_command(cmd.str) + endif +endfunction + +function! s:app_runner_command(count,args) dict + if a:count == -2 + return self.script_command(a:bang,"runner",a:args) + else + let str = self.ruby_shell_command('-r./config/boot -e "require '."'commands/runner'".'" '.s:rquote(a:args)) + let res = s:sub(system(str),'\n$','') + if a:count < 0 + echo res + else + exe a:count.'put =res' + endif + endif +endfunction + +function! s:getpidfor(bind,port) + if has("win32") || has("win64") + let netstat = system("netstat -anop tcp") + let pid = matchstr(netstat,'\<'.a:bind.':'.a:port.'\>.\{-\}LISTENING\s\+\zs\d\+') + elseif executable('lsof') + let pid = system("lsof -i 4tcp@".a:bind.':'.a:port."|grep LISTEN|awk '{print $2}'") + let pid = s:sub(pid,'\n','') + else + let pid = "" + endif + return pid +endfunction + +function! s:app_server_command(bang,arg) dict + let port = matchstr(a:arg,'\%(-p\|--port=\=\)\s*\zs\d\+') + if port == '' + let port = "3000" + endif + " TODO: Extract bind argument + let bind = "0.0.0.0" + if a:bang && executable("ruby") + let pid = s:getpidfor(bind,port) + if pid =~ '^\d\+$' + echo "Killing server with pid ".pid + if !has("win32") + call system("ruby -e 'Process.kill(:TERM,".pid.")'") + sleep 100m + endif + call system("ruby -e 'Process.kill(9,".pid.")'") + sleep 100m + endif + if a:arg == "-" + return + endif + endif + if has_key(self,'options') && has_key(self.options,'gnu_screen') + let screen = self.options.gnu_screen + else + let screen = g:rails_gnu_screen + endif + if has("win32") || has("win64") || (exists("$STY") && !has("gui_running") && screen && executable("screen")) || (exists("$TMUX") && !has("gui_running") && screen && executable("tmux")) + call self.background_script_command('server '.a:arg) + else + " --daemon would be more descriptive but lighttpd does not support it + call self.execute_script_command('server '.a:arg." -d") + endif + call s:setopt('a:root_url','http://'.(bind=='0.0.0.0'?'localhost': bind).':'.port.'/') +endfunction + +function! s:app_destroy_command(bang,...) dict + if a:0 == 0 + return self.execute_script_command('destroy') + elseif a:0 == 1 + return self.execute_script_command('destroy '.s:rquote(a:1)) + endif + let str = "" + let c = 1 + while c <= a:0 + let str .= " " . s:rquote(a:{c}) + let c += 1 + endwhile + call self.execute_script_command('destroy'.str) + call self.cache.clear('user_classes') +endfunction + +function! s:app_generate_command(bang,...) dict + if a:0 == 0 + return self.execute_script_command('generate') + elseif a:0 == 1 + return self.execute_script_command('generate '.s:rquote(a:1)) + endif + let cmd = join(map(copy(a:000),'s:rquote(v:val)'),' ') + if cmd !~ '-p\>' && cmd !~ '--pretend\>' + let execstr = self.script_shell_command('generate '.cmd.' -p -f') + let res = system(execstr) + let g:res = res + let junk = '\%(\e\[[0-9;]*m\)\=' + let file = matchstr(res,junk.'\s\+\%(create\|force\)'.junk.'\s\+\zs\f\+\.rb\ze\n') + if file == "" + let file = matchstr(res,junk.'\s\+\%(identical\)'.junk.'\s\+\zs\f\+\.rb\ze\n') + endif + else + let file = "" + endif + if !self.execute_script_command('generate '.cmd) && file != '' + call self.cache.clear('user_classes') + call self.cache.clear('features') + if file =~ '^db/migrate/\d\d\d\d' + let file = get(self.relglob('',s:sub(file,'\d+','[0-9]*[0-9]')),-1,file) + endif + edit `=self.path(file)` + endif +endfunction + +call s:add_methods('app', ['generators','script_command','runner_command','server_command','destroy_command','generate_command']) + +function! s:Complete_script(ArgLead,CmdLine,P) + let cmd = s:sub(a:CmdLine,'^\u\w*\s+','') + if cmd !~ '^[ A-Za-z0-9_=:-]*$' + return [] + elseif cmd =~# '^\w*$' + return s:completion_filter(rails#app().relglob("script/","**/*"),a:ArgLead) + elseif cmd =~# '^\%(plugin\)\s\+'.a:ArgLead.'$' + return s:completion_filter(["discover","list","install","update","remove","source","unsource","sources"],a:ArgLead) + elseif cmd =~# '\%(plugin\)\s\+\%(install\|remove\)\s\+'.a:ArgLead.'$' || cmd =~ '\%(generate\|destroy\)\s\+plugin\s\+'.a:ArgLead.'$' + return s:pluginList(a:ArgLead,a:CmdLine,a:P) + elseif cmd =~# '^\%(generate\|destroy\)\s\+'.a:ArgLead.'$' + return s:completion_filter(rails#app().generators(),a:ArgLead) + elseif cmd =~# '^\%(generate\|destroy\)\s\+\w\+\s\+'.a:ArgLead.'$' + let target = matchstr(cmd,'^\w\+\s\+\%(\w\+:\)\=\zs\w\+\ze\s\+') + if target =~# '^\w*controller$' + return filter(s:controllerList(a:ArgLead,"",""),'v:val !=# "application"') + elseif target ==# 'generator' + return s:completion_filter(map(rails#app().relglob('lib/generators/','*'),'s:sub(v:val,"/$","")')) + elseif target ==# 'helper' + return s:helperList(a:ArgLead,"","") + elseif target ==# 'integration_test' || target ==# 'integration_spec' || target ==# 'feature' + return s:integrationtestList(a:ArgLead,"","") + elseif target ==# 'metal' + return s:metalList(a:ArgLead,"","") + elseif target ==# 'migration' || target ==# 'session_migration' + return s:migrationList(a:ArgLead,"","") + elseif target =~# '^\w*\%(model\|resource\)$' || target =~# '\w*scaffold\%(_controller\)\=$' || target ==# 'mailer' + return s:modelList(a:ArgLead,"","") + elseif target ==# 'observer' + let observers = s:observerList("","","") + let models = s:modelList("","","") + if cmd =~# '^destroy\>' + let models = [] + endif + call filter(models,'index(observers,v:val) < 0') + return s:completion_filter(observers + models,a:ArgLead) + else + return [] + endif + elseif cmd =~# '^\%(generate\|destroy\)\s\+scaffold\s\+\w\+\s\+'.a:ArgLead.'$' + return filter(s:controllerList(a:ArgLead,"",""),'v:val !=# "application"') + return s:completion_filter(rails#app().environments()) + elseif cmd =~# '^\%(console\)\s\+\(--\=\w\+\s\+\)\='.a:ArgLead."$" + return s:completion_filter(rails#app().environments()+["-s","--sandbox"],a:ArgLead) + elseif cmd =~# '^\%(server\)\s\+.*-e\s\+'.a:ArgLead."$" + return s:completion_filter(rails#app().environments(),a:ArgLead) + elseif cmd =~# '^\%(server\)\s\+' + if a:ArgLead =~# '^--environment=' + return s:completion_filter(map(copy(rails#app().environments()),'"--environment=".v:val'),a:ArgLead) + else + return filter(["-p","-b","-e","-m","-d","-u","-c","-h","--port=","--binding=","--environment=","--mime-types=","--daemon","--debugger","--charset=","--help"],'s:startswith(v:val,a:ArgLead)') + endif + endif + return "" +endfunction + +function! s:CustomComplete(A,L,P,cmd) + let L = "Rscript ".a:cmd." ".s:sub(a:L,'^\h\w*\s+','') + let P = a:P - strlen(a:L) + strlen(L) + return s:Complete_script(a:A,L,P) +endfunction + +function! s:Complete_server(A,L,P) + return s:CustomComplete(a:A,a:L,a:P,"server") +endfunction + +function! s:Complete_console(A,L,P) + return s:CustomComplete(a:A,a:L,a:P,"console") +endfunction + +function! s:Complete_generate(A,L,P) + return s:CustomComplete(a:A,a:L,a:P,"generate") +endfunction + +function! s:Complete_destroy(A,L,P) + return s:CustomComplete(a:A,a:L,a:P,"destroy") +endfunction + +function! s:Complete_ruby(A,L,P) + return s:completion_filter(rails#app().user_classes()+["ActiveRecord::Base"],a:A) +endfunction + +" }}}1 +" Navigation {{{1 + +function! s:BufNavCommands() + command! -buffer -bar -nargs=? -complete=customlist,s:Complete_cd Rcd :cd `=rails#app().path(<q-args>)` + command! -buffer -bar -nargs=? -complete=customlist,s:Complete_cd Rlcd :lcd `=rails#app().path(<q-args>)` + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find Rfind :call s:warn( 'Rfind has been deprecated in favor of :1R or :find' )|call s:Find(<count>,'<bang>' ,<f-args>) + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find REfind :call s:warn('REfind has been deprecated in favor of :1RE or :find')|call s:Find(<count>,'E<bang>',<f-args>) + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find RSfind :call s:warn('RSfind has been deprecated in favor of :1RS or :find')|call s:Find(<count>,'S<bang>',<f-args>) + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find RVfind :call s:warn('RVfind has been deprecated in favor of :1RV or :find')|call s:Find(<count>,'V<bang>',<f-args>) + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find RTfind :call s:warn('RTfind has been deprecated in favor of :1RT or :find')|call s:Find(<count>,'T<bang>',<f-args>) + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find Rsfind :call s:warn('Rsfind has been deprecated in favor of :1RS or :sfind')|<count>RSfind<bang> <args> + command! -buffer -bar -nargs=* -count=1 -complete=customlist,s:Complete_find Rtabfind :call s:warn('Rtabfind has been deprecated in favor of :1RT or :tabfind')|<count>RTfind<bang> <args> + command! -buffer -bar -nargs=* -bang -complete=customlist,s:Complete_edit Redit :call s:warn( 'Redit has been deprecated in favor of :R')|call s:Edit(<count>,'<bang>' ,<f-args>) + command! -buffer -bar -nargs=* -bang -complete=customlist,s:Complete_edit REedit :call s:warn('REedit has been deprecated in favor of :RE')|call s:Edit(<count>,'E<bang>',<f-args>) + command! -buffer -bar -nargs=* -bang -complete=customlist,s:Complete_edit RSedit :call s:warn('RSedit has been deprecated in favor of :RS')|call s:Edit(<count>,'S<bang>',<f-args>) + command! -buffer -bar -nargs=* -bang -complete=customlist,s:Complete_edit RVedit :call s:warn('RVedit has been deprecated in favor of :RV')|call s:Edit(<count>,'V<bang>',<f-args>) + command! -buffer -bar -nargs=* -bang -complete=customlist,s:Complete_edit RTedit :call s:warn('RTedit has been deprecated in favor of :RT')|call s:Edit(<count>,'T<bang>',<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_edit RDedit :call s:warn('RDedit has been deprecated in favor of :RD')|call s:Edit(<count>,'<line1>D<bang>',<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related A :call s:Alternate('<bang>', <line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related AE :call s:Alternate('E<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related AS :call s:Alternate('S<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related AV :call s:Alternate('V<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related AT :call s:Alternate('T<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related AD :call s:Alternate('D<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related AN :call s:Related('<bang>' ,<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related R :call s:Related('<bang>' ,<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related RE :call s:Related('E<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related RS :call s:Related('S<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related RV :call s:Related('V<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related RT :call s:Related('T<bang>',<line1>,<line2>,<count>,<f-args>) + command! -buffer -bar -nargs=* -range=0 -complete=customlist,s:Complete_related RD :call s:Related('D<bang>',<line1>,<line2>,<count>,<f-args>) +endfunction + +function! s:djump(def) + let def = s:sub(a:def,'^[#:]','') + if def =~ '^\d\+$' + exe def + elseif def =~ '^!' + if expand('%') !~ '://' && !isdirectory(expand('%:p:h')) + call mkdir(expand('%:p:h'),'p') + endif + elseif def != '' + let ext = matchstr(def,'\.\zs.*') + let def = matchstr(def,'[^.]*') + let v:errmsg = '' + silent! exe "djump ".def + if ext != '' && (v:errmsg == '' || v:errmsg =~ '^E387') + let rpat = '\C^\s*\%(mail\>.*\|respond_to\)\s*\%(\<do\|{\)\s*|\zs\h\k*\ze|' + let end = s:endof(line('.')) + let rline = search(rpat,'',end) + if rline > 0 + let variable = matchstr(getline(rline),rpat) + let success = search('\C^\s*'.variable.'\s*\.\s*\zs'.ext.'\>','',end) + if !success + silent! exe "djump ".def + endif + endif + endif + endif +endfunction + +function! s:Find(count,cmd,...) + let str = "" + if a:0 + let i = 1 + while i < a:0 + let str .= s:escarg(a:{i}) . " " + let i += 1 + endwhile + let file = a:{i} + let tail = matchstr(file,'[#!].*$\|:\d*\%(:in\>.*\)\=$') + if tail != "" + let file = s:sub(file,'[#!].*$|:\d*%(:in>.*)=$','') + endif + if file != "" + let file = s:RailsIncludefind(file) + endif + else + let file = s:RailsFind() + let tail = "" + endif + call s:findedit((a:count==1?'' : a:count).a:cmd,file.tail,str) +endfunction + +function! s:Edit(count,cmd,...) + if a:0 + let str = "" + let i = 1 + while i < a:0 + let str .= "`=a:".i."` " + let i += 1 + endwhile + let file = a:{i} + call s:findedit(s:editcmdfor(a:cmd),file,str) + else + exe s:editcmdfor(a:cmd) + endif +endfunction + +function! s:fuzzyglob(arg) + return s:gsub(s:gsub(a:arg,'[^/.]','[&]*'),'%(/|^)\.@!|\.','&*') +endfunction + +function! s:Complete_find(ArgLead, CmdLine, CursorPos) + let paths = s:pathsplit(&l:path) + let seen = {} + for path in paths + if s:startswith(path,rails#app().path()) && path !~ '[][*]' + let path = path[strlen(rails#app().path()) + 1 : ] + for file in rails#app().relglob(path == '' ? '' : path.'/',s:fuzzyglob(rails#underscore(a:ArgLead)), a:ArgLead =~# '\u' ? '.rb' : '') + let seen[file] = 1 + endfor + endif + endfor + return s:autocamelize(sort(keys(seen)),a:ArgLead) +endfunction + +function! s:Complete_edit(ArgLead, CmdLine, CursorPos) + return s:completion_filter(rails#app().relglob("",s:fuzzyglob(a:ArgLead)),a:ArgLead) +endfunction + +function! s:Complete_cd(ArgLead, CmdLine, CursorPos) + let all = rails#app().relglob("",a:ArgLead."*") + call filter(all,'v:val =~ "/$"') + return filter(all,'s:startswith(v:val,a:ArgLead)') +endfunction + +function! RailsIncludeexpr() + " Is this foolproof? + if mode() =~ '[iR]' || expand("<cfile>") != v:fname + return s:RailsIncludefind(v:fname) + else + return s:RailsIncludefind(v:fname,1) + endif +endfunction + +function! s:linepeak() + let line = getline(line(".")) + let line = s:sub(line,'^(.{'.col(".").'}).*','\1') + let line = s:sub(line,'([:"'."'".']|\%[qQ]=[[({<])=\f*$','') + return line +endfunction + +function! s:matchcursor(pat) + let line = getline(".") + let lastend = 0 + while lastend >= 0 + let beg = match(line,'\C'.a:pat,lastend) + let end = matchend(line,'\C'.a:pat,lastend) + if beg < col(".") && end >= col(".") + return matchstr(line,'\C'.a:pat,lastend) + endif + let lastend = end + endwhile + return "" +endfunction + +function! s:findit(pat,repl) + let res = s:matchcursor(a:pat) + if res != "" + return substitute(res,'\C'.a:pat,a:repl,'') + else + return "" + endif +endfunction + +function! s:findamethod(func,repl) + return s:findit('\s*\<\%('.a:func.'\)\s*(\=\s*[@:'."'".'"]\(\f\+\)\>.\=',a:repl) +endfunction + +function! s:findasymbol(sym,repl) + return s:findit('\s*\%(:\%('.a:sym.'\)\s*=>\|\<'.a:sym.':\)\s*(\=\s*[@:'."'".'"]\(\f\+\)\>.\=',a:repl) +endfunction + +function! s:findfromview(func,repl) + " ( ) ( ) ( \1 ) ( ) + return s:findit('\s*\%(<%\)\==\=\s*\<\%('.a:func.'\)\s*(\=\s*[@:'."'".'"]\(\f\+\)\>['."'".'"]\=\s*\%(%>\s*\)\=',a:repl) +endfunction + +function! s:RailsFind() + if filereadable(expand("<cfile>")) + return expand("<cfile>") + endif + + " UGH + let buffer = rails#buffer() + let format = s:format('html') + + let res = s:findit('\v\s*<require\s*\(=\s*File.dirname\(__FILE__\)\s*\+\s*[:'."'".'"](\f+)>.=',expand('%:h').'/\1') + if res != ""|return res.(fnamemodify(res,':e') == '' ? '.rb' : '')|endif + + let res = s:findit('\v<File.dirname\(__FILE__\)\s*\+\s*[:'."'".'"](\f+)>['."'".'"]=',expand('%:h').'\1') + if res != ""|return res|endif + + let res = rails#underscore(s:findit('\v\s*<%(include|extend)\(=\s*<([[:alnum:]_:]+)>','\1')) + if res != ""|return res.".rb"|endif + + let res = s:findamethod('require','\1') + if res != ""|return res.(fnamemodify(res,':e') == '' ? '.rb' : '')|endif + + let res = s:findamethod('belongs_to\|has_one\|composed_of\|validates_associated\|scaffold','app/models/\1.rb') + if res != ""|return res|endif + + let res = rails#singularize(s:findamethod('has_many\|has_and_belongs_to_many','app/models/\1')) + if res != ""|return res.".rb"|endif + + let res = rails#singularize(s:findamethod('create_table\|change_table\|drop_table\|add_column\|rename_column\|remove_column\|add_index','app/models/\1')) + if res != ""|return res.".rb"|endif + + let res = rails#singularize(s:findasymbol('through','app/models/\1')) + if res != ""|return res.".rb"|endif + + let res = s:findamethod('fixtures','fixtures/\1') + if res != "" + return RailsFilePath() =~ '\<spec/' ? 'spec/'.res : res + endif + + let res = s:findamethod('\%(\w\+\.\)\=resources','app/controllers/\1_controller.rb') + if res != ""|return res|endif + + let res = s:findamethod('\%(\w\+\.\)\=resource','app/controllers/\1') + if res != ""|return rails#pluralize(res)."_controller.rb"|endif + + let res = s:findasymbol('to','app/controllers/\1') + if res =~ '#'|return s:sub(res,'#','_controller.rb#')|endif + + let res = s:findamethod('root\s*\%(:to\s*=>\|\<to:\)\s*','app/controllers/\1') + if res =~ '#'|return s:sub(res,'#','_controller.rb#')|endif + + let res = s:findamethod('\%(match\|get\|put\|post\|delete\|redirect\)\s*(\=\s*[:''"][^''"]*[''"]\=\s*\%(\%(,\s*:to\s*\)\==>\|,\s*to:\)\s*','app/controllers/\1') + if res =~ '#'|return s:sub(res,'#','_controller.rb#')|endif + + let res = s:findamethod('layout','\=s:findlayout(submatch(1))') + if res != ""|return res|endif + + let res = s:findasymbol('layout','\=s:findlayout(submatch(1))') + if res != ""|return res|endif + + let res = s:findamethod('helper','app/helpers/\1_helper.rb') + if res != ""|return res|endif + + let res = s:findasymbol('controller','app/controllers/\1_controller.rb') + if res != ""|return res|endif + + let res = s:findasymbol('action','\1') + if res != ""|return res|endif + + let res = s:findasymbol('template','app/views/\1') + if res != ""|return res|endif + + let res = s:sub(s:sub(s:findasymbol('partial','\1'),'^/',''),'[^/]+$','_&') + if res != ""|return res."\n".s:findview(res)|endif + + let res = s:sub(s:sub(s:findfromview('render\s*(\=\s*\%(:partial\s\+=>\|partial:\)\s*','\1'),'^/',''),'[^/]+$','_&') + if res != ""|return res."\n".s:findview(res)|endif + + let res = s:findamethod('render\>\s*\%(:\%(template\|action\)\s\+=>\|template:\|action:\)\s*','\1.'.format.'\n\1') + if res != ""|return res|endif + + let res = s:sub(s:findfromview('render','\1'),'^/','') + if buffer.type_name('view') | let res = s:sub(res,'[^/]+$','_&') | endif + if res != ""|return res."\n".s:findview(res)|endif + + let res = s:findamethod('redirect_to\s*(\=\s*\%\(:action\s\+=>\|\<action:\)\s*','\1') + if res != ""|return res|endif + + let res = s:findfromview('stylesheet_link_tag','public/stylesheets/\1') + if res != '' && fnamemodify(res, ':e') == '' " Append the default extension iff the filename doesn't already contains an extension + let res .= '.css' + end + if res != ""|return res|endif + + let res = s:sub(s:findfromview('javascript_include_tag','public/javascripts/\1'),'/defaults>','/application') + if res != '' && fnamemodify(res, ':e') == '' " Append the default extension iff the filename doesn't already contains an extension + let res .= '.js' + end + if res != ""|return res|endif + + if buffer.type_name('controller') + let contr = s:controller() + let view = s:findit('\s*\<def\s\+\(\k\+\)\>(\=','/\1') + let res = s:findview(contr.'/'.view) + if res != ""|return res|endif + endif + + let old_isfname = &isfname + try + set isfname=@,48-57,/,-,_,:,# + " TODO: grab visual selection in visual mode + let cfile = expand("<cfile>") + finally + let &isfname = old_isfname + endtry + let res = s:RailsIncludefind(cfile,1) + return res +endfunction + +function! s:app_named_route_file(route) dict + call self.route_names() + if self.cache.has("named_routes") && has_key(self.cache.get("named_routes"),a:route) + return self.cache.get("named_routes")[a:route] + endif + return "" +endfunction + +function! s:app_route_names() dict + if self.cache.needs("named_routes") + let exec = "ActionController::Routing::Routes.named_routes.each {|n,r| puts %{#{n} app/controllers/#{r.requirements[:controller]}_controller.rb##{r.requirements[:action]}}}" + let string = self.eval(exec) + let routes = {} + for line in split(string,"\n") + let route = split(line," ") + let name = route[0] + let routes[name] = route[1] + endfor + call self.cache.set("named_routes",routes) + endif + + return keys(self.cache.get("named_routes")) +endfunction + +call s:add_methods('app', ['route_names','named_route_file']) + +function! RailsNamedRoutes() + return rails#app().route_names() +endfunction + +function! s:RailsIncludefind(str,...) + if a:str ==# "ApplicationController" + return "application_controller.rb\napp/controllers/application.rb" + elseif a:str ==# "Test::Unit::TestCase" + return "test/unit/testcase.rb" + endif + let str = a:str + if a:0 == 1 + " Get the text before the filename under the cursor. + " We'll cheat and peak at this in a bit + let line = s:linepeak() + let line = s:sub(line,'([:"'."'".']|\%[qQ]=[[({<])=\f*$','') + else + let line = "" + endif + let str = s:sub(str,'^\s*','') + let str = s:sub(str,'\s*$','') + let str = s:sub(str,'^:=[:@]','') + let str = s:sub(str,':0x\x+$','') " For #<Object:0x...> style output + let str = s:gsub(str,"[\"']",'') + if line =~# '\<\(require\|load\)\s*(\s*$' + return str + elseif str =~# '^\l\w*#\w\+$' + return 'app/controllers/'.s:sub(str,'#','_controller.rb#') + endif + let str = rails#underscore(str) + let fpat = '\(\s*\%("\f*"\|:\f*\|'."'\\f*'".'\)\s*,\s*\)*' + if a:str =~# '\u' + " Classes should always be in .rb files + let str .= '.rb' + elseif line =~# ':partial\s*=>\s*' + let str = s:sub(str,'[^/]+$','_&') + let str = s:findview(str) + elseif line =~# '\<layout\s*(\=\s*' || line =~# ':layout\s*=>\s*' + let str = s:findview(s:sub(str,'^/=','layouts/')) + elseif line =~# ':controller\s*=>\s*' + let str = 'app/controllers/'.str.'_controller.rb' + elseif line =~# '\<helper\s*(\=\s*' + let str = 'app/helpers/'.str.'_helper.rb' + elseif line =~# '\<fixtures\s*(\='.fpat + if RailsFilePath() =~# '\<spec/' + let str = s:sub(str,'^/@!','spec/fixtures/') + else + let str = s:sub(str,'^/@!','test/fixtures/') + endif + elseif line =~# '\<stylesheet_\(link_tag\|path\)\s*(\='.fpat + let str = s:sub(str,'^/@!','/stylesheets/') + if str != '' && fnamemodify(str, ':e') == '' + let str .= '.css' + endif + elseif line =~# '\<javascript_\(include_tag\|path\)\s*(\='.fpat + if str ==# "defaults" + let str = "application" + endif + let str = s:sub(str,'^/@!','/javascripts/') + if str != '' && fnamemodify(str, ':e') == '' + let str .= '.js' + endif + elseif line =~# '\<\(has_one\|belongs_to\)\s*(\=\s*' + let str = 'app/models/'.str.'.rb' + elseif line =~# '\<has_\(and_belongs_to_\)\=many\s*(\=\s*' + let str = 'app/models/'.rails#singularize(str).'.rb' + elseif line =~# '\<def\s\+' && expand("%:t") =~# '_controller\.rb' + let str = s:findview(str) + elseif str =~# '_\%(path\|url\)$' || (line =~# ':as\s*=>\s*$' && rails#buffer().type_name('config-routes')) + if line !~# ':as\s*=>\s*$' + let str = s:sub(str,'_%(path|url)$','') + let str = s:sub(str,'^hash_for_','') + endif + let file = rails#app().named_route_file(str) + if file == "" + let str = s:sub(str,'^formatted_','') + if str =~# '^\%(new\|edit\)_' + let str = 'app/controllers/'.s:sub(rails#pluralize(str),'^(new|edit)_(.*)','\2_controller.rb#\1') + elseif str ==# rails#singularize(str) + " If the word can't be singularized, it's probably a link to the show + " method. We should verify by checking for an argument, but that's + " difficult the way things here are currently structured. + let str = 'app/controllers/'.rails#pluralize(str).'_controller.rb#show' + else + let str = 'app/controllers/'.str.'_controller.rb#index' + endif + else + let str = file + endif + elseif str !~ '/' + " If we made it this far, we'll risk making it singular. + let str = rails#singularize(str) + let str = s:sub(str,'_id$','') + endif + if str =~ '^/' && !filereadable(str) + let str = s:sub(str,'^/','') + endif + if str =~# '^lib/' && !filereadable(str) + let str = s:sub(str,'^lib/','') + endif + return str +endfunction + +" }}}1 +" File Finders {{{1 + +function! s:addfilecmds(type) + let l = s:sub(a:type,'^.','\l&') + let cmds = 'ESVTD ' + let cmd = '' + while cmds != '' + let cplt = " -complete=customlist,".s:sid.l."List" + exe "command! -buffer -bar ".(cmd == 'D' ? '-range=0 ' : '')."-nargs=*".cplt." R".cmd.l." :call s:".l.'Edit("'.(cmd == 'D' ? '<line1>' : '').cmd.'<bang>",<f-args>)' + let cmd = strpart(cmds,0,1) + let cmds = strpart(cmds,1) + endwhile +endfunction + +function! s:BufFinderCommands() + command! -buffer -bar -nargs=+ Rnavcommand :call s:Navcommand(<bang>0,<f-args>) + call s:addfilecmds("metal") + call s:addfilecmds("model") + call s:addfilecmds("view") + call s:addfilecmds("controller") + call s:addfilecmds("mailer") + call s:addfilecmds("migration") + call s:addfilecmds("observer") + call s:addfilecmds("helper") + call s:addfilecmds("layout") + call s:addfilecmds("fixtures") + call s:addfilecmds("locale") + if rails#app().has('test') || rails#app().has('spec') + call s:addfilecmds("unittest") + call s:addfilecmds("functionaltest") + endif + if rails#app().has('test') || rails#app().has('spec') || rails#app().has('cucumber') + call s:addfilecmds("integrationtest") + endif + if rails#app().has('spec') + call s:addfilecmds("spec") + endif + call s:addfilecmds("stylesheet") + call s:addfilecmds("javascript") + call s:addfilecmds("plugin") + call s:addfilecmds("task") + call s:addfilecmds("lib") + call s:addfilecmds("environment") + call s:addfilecmds("initializer") +endfunction + +function! s:completion_filter(results,A) + let results = sort(type(a:results) == type("") ? split(a:results,"\n") : copy(a:results)) + call filter(results,'v:val !~# "\\~$"') + let filtered = filter(copy(results),'s:startswith(v:val,a:A)') + if !empty(filtered) | return filtered | endif + let regex = s:gsub(a:A,'[^/]','[&].*') + let filtered = filter(copy(results),'v:val =~# "^".regex') + if !empty(filtered) | return filtered | endif + let regex = s:gsub(a:A,'.','[&].*') + let filtered = filter(copy(results),'v:val =~# regex') + return filtered +endfunction + +function! s:autocamelize(files,test) + if a:test =~# '^\u' + return s:completion_filter(map(copy(a:files),'rails#camelize(v:val)'),a:test) + else + return s:completion_filter(a:files,a:test) + endif +endfunction + +function! s:app_relglob(path,glob,...) dict + if exists("+shellslash") && ! &shellslash + let old_ss = &shellslash + let &shellslash = 1 + endif + let path = a:path + if path !~ '^/' && path !~ '^\w:' + let path = self.path(path) + endif + let suffix = a:0 ? a:1 : '' + let full_paths = split(glob(path.a:glob.suffix),"\n") + let relative_paths = [] + for entry in full_paths + if suffix == '' && isdirectory(entry) && entry !~ '/$' + let entry .= '/' + endif + let relative_paths += [entry[strlen(path) : -strlen(suffix)-1]] + endfor + if exists("old_ss") + let &shellslash = old_ss + endif + return relative_paths +endfunction + +call s:add_methods('app', ['relglob']) + +function! s:relglob(...) + return join(call(rails#app().relglob,a:000,rails#app()),"\n") +endfunction + +function! s:helperList(A,L,P) + return s:autocamelize(rails#app().relglob("app/helpers/","**/*","_helper.rb"),a:A) +endfunction + +function! s:controllerList(A,L,P) + let con = rails#app().relglob("app/controllers/","**/*",".rb") + call map(con,'s:sub(v:val,"_controller$","")') + return s:autocamelize(con,a:A) +endfunction + +function! s:mailerList(A,L,P) + return s:autocamelize(rails#app().relglob("app/mailers/","**/*",".rb"),a:A) +endfunction + +function! s:viewList(A,L,P) + let c = s:controller(1) + let top = rails#app().relglob("app/views/",s:fuzzyglob(a:A)) + call filter(top,'v:val !~# "\\~$"') + if c != '' && a:A !~ '/' + let local = rails#app().relglob("app/views/".c."/","*.*[^~]") + return s:completion_filter(local+top,a:A) + endif + return s:completion_filter(top,a:A) +endfunction + +function! s:layoutList(A,L,P) + return s:completion_filter(rails#app().relglob("app/views/layouts/","*"),a:A) +endfunction + +function! s:stylesheetList(A,L,P) + let list = rails#app().relglob('app/assets/stylesheets/','**/*.*','') + call map(list,'s:sub(v:val,"\\..*$","")') + let list += rails#app().relglob('public/stylesheets/','**/*','.css') + if rails#app().has('sass') + call extend(list,rails#app().relglob('public/stylesheets/sass/','**/*','.s?ss')) + call s:uniq(list) + endif + return s:completion_filter(list,a:A) +endfunction + +function! s:javascriptList(A,L,P) + let list = rails#app().relglob('app/assets/javascripts/','**/*.*','') + call map(list,'s:sub(v:val,"\\..*$","")') + let list += rails#app().relglob("public/javascripts/","**/*",".js") + return s:completion_filter(list,a:A) +endfunction + +function! s:metalList(A,L,P) + return s:autocamelize(rails#app().relglob("app/metal/","**/*",".rb"),a:A) +endfunction + +function! s:modelList(A,L,P) + let models = rails#app().relglob("app/models/","**/*",".rb") + call filter(models,'v:val !~# "_observer$"') + return s:autocamelize(models,a:A) +endfunction + +function! s:observerList(A,L,P) + return s:autocamelize(rails#app().relglob("app/models/","**/*","_observer.rb"),a:A) +endfunction + +function! s:fixturesList(A,L,P) + return s:completion_filter(rails#app().relglob("test/fixtures/","**/*")+rails#app().relglob("spec/fixtures/","**/*"),a:A) +endfunction + +function! s:localeList(A,L,P) + return s:completion_filter(rails#app().relglob("config/locales/","**/*"),a:A) +endfunction + +function! s:migrationList(A,L,P) + if a:A =~ '^\d' + let migrations = rails#app().relglob("db/migrate/",a:A."[0-9_]*",".rb") + return map(migrations,'matchstr(v:val,"^[0-9]*")') + else + let migrations = rails#app().relglob("db/migrate/","[0-9]*[0-9]_*",".rb") + call map(migrations,'s:sub(v:val,"^[0-9]*_","")') + return s:autocamelize(migrations,a:A) + endif +endfunction + +function! s:unittestList(A,L,P) + let found = [] + if rails#app().has('test') + let found += rails#app().relglob("test/unit/","**/*","_test.rb") + endif + if rails#app().has('spec') + let found += rails#app().relglob("spec/models/","**/*","_spec.rb") + endif + return s:autocamelize(found,a:A) +endfunction + +function! s:functionaltestList(A,L,P) + let found = [] + if rails#app().has('test') + let found += rails#app().relglob("test/functional/","**/*","_test.rb") + endif + if rails#app().has('spec') + let found += rails#app().relglob("spec/controllers/","**/*","_spec.rb") + let found += rails#app().relglob("spec/mailers/","**/*","_spec.rb") + endif + return s:autocamelize(found,a:A) +endfunction + +function! s:integrationtestList(A,L,P) + if a:A =~# '^\u' + return s:autocamelize(rails#app().relglob("test/integration/","**/*","_test.rb"),a:A) + endif + let found = [] + if rails#app().has('test') + let found += rails#app().relglob("test/integration/","**/*","_test.rb") + endif + if rails#app().has('spec') + let found += rails#app().relglob("spec/requests/","**/*","_spec.rb") + let found += rails#app().relglob("spec/integration/","**/*","_spec.rb") + endif + if rails#app().has('cucumber') + let found += rails#app().relglob("features/","**/*",".feature") + endif + return s:completion_filter(found,a:A) +endfunction + +function! s:specList(A,L,P) + return s:completion_filter(rails#app().relglob("spec/","**/*","_spec.rb"),a:A) +endfunction + +function! s:pluginList(A,L,P) + if a:A =~ '/' + return s:completion_filter(rails#app().relglob('vendor/plugins/',matchstr(a:A,'.\{-\}/').'**/*'),a:A) + else + return s:completion_filter(rails#app().relglob('vendor/plugins/',"*","/init.rb"),a:A) + endif +endfunction + +" Task files, not actual rake tasks +function! s:taskList(A,L,P) + let all = rails#app().relglob("lib/tasks/","**/*",".rake") + if RailsFilePath() =~ '\<vendor/plugins/.' + let path = s:sub(RailsFilePath(),'<vendor/plugins/[^/]*/\zs.*','') + let all = rails#app().relglob(path."tasks/","**/*",".rake")+rails#app().relglob(path."lib/tasks/","**/*",".rake")+all + endif + return s:autocamelize(all,a:A) +endfunction + +function! s:libList(A,L,P) + let all = rails#app().relglob('lib/',"**/*",".rb") + if RailsFilePath() =~ '\<vendor/plugins/.' + let path = s:sub(RailsFilePath(),'<vendor/plugins/[^/]*/\zs.*','lib/') + let all = rails#app().relglob(path,"**/*",".rb") + all + endif + return s:autocamelize(all,a:A) +endfunction + +function! s:environmentList(A,L,P) + return s:completion_filter(rails#app().relglob("config/environments/","**/*",".rb"),a:A) +endfunction + +function! s:initializerList(A,L,P) + return s:completion_filter(rails#app().relglob("config/initializers/","**/*",".rb"),a:A) +endfunction + +function! s:Navcommand(bang,...) + let suffix = ".rb" + let filter = "**/*" + let prefix = "" + let default = "" + let name = "" + let i = 0 + while i < a:0 + let i += 1 + let arg = a:{i} + if arg =~# '^-suffix=' + let suffix = matchstr(arg,'-suffix=\zs.*') + elseif arg =~# '^-default=' + let default = matchstr(arg,'-default=\zs.*') + elseif arg =~# '^-\%(glob\|filter\)=' + let filter = matchstr(arg,'-\w*=\zs.*') + elseif arg !~# '^-' + " A literal '\n'. For evaluation below + if name == "" + let name = arg + else + let prefix .= "\\n".s:sub(arg,'/=$','/') + endif + endif + endwhile + let prefix = s:sub(prefix,'^\\n','') + if name !~ '^[A-Za-z]\+$' + return s:error("E182: Invalid command name") + endif + let cmds = 'ESVTD ' + let cmd = '' + while cmds != '' + exe 'command! -buffer -bar -bang -nargs=* -complete=customlist,'.s:sid.'CommandList R'.cmd.name." :call s:CommandEdit('".cmd."<bang>','".name."',\"".prefix."\",".string(suffix).",".string(filter).",".string(default).",<f-args>)" + let cmd = strpart(cmds,0,1) + let cmds = strpart(cmds,1) + endwhile +endfunction + +function! s:CommandList(A,L,P) + let cmd = matchstr(a:L,'\CR[A-Z]\=\w\+') + exe cmd." &" + let lp = s:last_prefix . "\n" + let res = [] + while lp != "" + let p = matchstr(lp,'.\{-\}\ze\n') + let lp = s:sub(lp,'.{-}\n','') + let res += rails#app().relglob(p,s:last_filter,s:last_suffix) + endwhile + if s:last_camelize + return s:autocamelize(res,a:A) + else + return s:completion_filter(res,a:A) + endif +endfunction + +function! s:CommandEdit(cmd,name,prefix,suffix,filter,default,...) + if a:0 && a:1 == "&" + let s:last_prefix = a:prefix + let s:last_suffix = a:suffix + let s:last_filter = a:filter + let s:last_camelize = (a:suffix =~# '\.rb$') + else + if a:default == "both()" + if s:model() != "" + let default = s:model() + else + let default = s:controller() + endif + elseif a:default == "model()" + let default = s:model(1) + elseif a:default == "controller()" + let default = s:controller(1) + else + let default = a:default + endif + call s:EditSimpleRb(a:cmd,a:name,a:0 ? a:1 : default,a:prefix,a:suffix) + endif +endfunction + +function! s:EditSimpleRb(cmd,name,target,prefix,suffix,...) + let cmd = s:findcmdfor(a:cmd) + if a:target == "" + " Good idea to emulate error numbers like this? + return s:error("E471: Argument required") + endif + let f = a:0 ? a:target : rails#underscore(a:target) + let jump = matchstr(f,'[#!].*\|:\d*\%(:in\)\=$') + let f = s:sub(f,'[#!].*|:\d*%(:in)=$','') + if jump =~ '^!' + let cmd = s:editcmdfor(cmd) + endif + if f == '.' + let f = s:sub(f,'\.$','') + else + let f .= a:suffix.jump + endif + let f = s:gsub(a:prefix,'\n',f.'\n').f + return s:findedit(cmd,f) +endfunction + +function! s:app_migration(file) dict + let arg = a:file + if arg =~ '^0$\|^0\=[#:]' + let suffix = s:sub(arg,'^0*','') + if self.has_file('db/schema.rb') + return 'db/schema.rb'.suffix + elseif self.has_file('db/'.s:environment().'_structure.sql') + return 'db/'.s:environment().'_structure.sql'.suffix + else + return 'db/schema.rb'.suffix + endif + elseif arg =~ '^\d$' + let glob = '00'.arg.'_*.rb' + elseif arg =~ '^\d\d$' + let glob = '0'.arg.'_*.rb' + elseif arg =~ '^\d\d\d$' + let glob = ''.arg.'_*.rb' + elseif arg == '' + let glob = '*.rb' + else + let glob = '*'.rails#underscore(arg).'*rb' + endif + let files = split(glob(self.path('db/migrate/').glob),"\n") + if arg == '' + return get(files,-1,'') + endif + call map(files,'strpart(v:val,1+strlen(self.path()))') + let keep = get(files,0,'') + if glob =~# '^\*.*\*rb' + let pattern = glob[1:-4] + call filter(files,'v:val =~# ''db/migrate/\d\+_''.pattern.''\.rb''') + let keep = get(files,0,keep) + endif + return keep +endfunction + +call s:add_methods('app', ['migration']) + +function! s:migrationEdit(cmd,...) + let cmd = s:findcmdfor(a:cmd) + let arg = a:0 ? a:1 : '' + let migr = arg == "." ? "db/migrate" : rails#app().migration(arg) + if migr != '' + call s:findedit(cmd,migr) + else + return s:error("Migration not found".(arg=='' ? '' : ': '.arg)) + endif +endfunction + +function! s:fixturesEdit(cmd,...) + if a:0 + let c = rails#underscore(a:1) + else + let c = rails#pluralize(s:model(1)) + endif + if c == "" + return s:error("E471: Argument required") + endif + let e = fnamemodify(c,':e') + let e = e == '' ? e : '.'.e + let c = fnamemodify(c,':r') + let file = get(rails#app().test_suites(),0,'test').'/fixtures/'.c.e + if file =~ '\.\w\+$' && rails#app().find_file(c.e,["test/fixtures","spec/fixtures"]) ==# '' + call s:edit(a:cmd,file) + else + call s:findedit(a:cmd,rails#app().find_file(c.e,["test/fixtures","spec/fixtures"],[".yml",".csv"],file)) + endif +endfunction + +function! s:localeEdit(cmd,...) + let c = a:0 ? a:1 : rails#app().default_locale() + if c =~# '\.' + call s:edit(a:cmd,rails#app().find_file(c,'config/locales',[],'config/locales/'.c)) + else + call s:findedit(a:cmd,rails#app().find_file(c,'config/locales',['.yml','.rb'],'config/locales/'.c)) + endif +endfunction + +function! s:metalEdit(cmd,...) + if a:0 + call s:EditSimpleRb(a:cmd,"metal",a:1,"app/metal/",".rb") + else + call s:EditSimpleRb(a:cmd,"metal",'config/boot',"",".rb") + endif +endfunction + +function! s:modelEdit(cmd,...) + call s:EditSimpleRb(a:cmd,"model",a:0? a:1 : s:model(1),"app/models/",".rb") +endfunction + +function! s:observerEdit(cmd,...) + call s:EditSimpleRb(a:cmd,"observer",a:0? a:1 : s:model(1),"app/models/","_observer.rb") +endfunction + +function! s:viewEdit(cmd,...) + if a:0 && a:1 =~ '^[^!#:]' + let view = matchstr(a:1,'[^!#:]*') + elseif rails#buffer().type_name('controller','mailer') + let view = s:lastmethod(line('.')) + else + let view = '' + endif + if view == '' + return s:error("No view name given") + elseif view == '.' + return s:edit(a:cmd,'app/views') + elseif view !~ '/' && s:controller(1) != '' + let view = s:controller(1) . '/' . view + endif + if view !~ '/' + return s:error("Cannot find view without controller") + endif + let file = "app/views/".view + let found = s:findview(view) + if found != '' + let dir = fnamemodify(rails#app().path(found),':h') + if !isdirectory(dir) + if a:0 && a:1 =~ '!' + call mkdir(dir,'p') + else + return s:error('No such directory') + endif + endif + call s:edit(a:cmd,found) + elseif file =~ '\.\w\+$' + call s:findedit(a:cmd,file) + else + let format = s:format(rails#buffer().type_name('mailer') ? 'text' : 'html') + if glob(rails#app().path(file.'.'.format).'.*[^~]') != '' + let file .= '.' . format + endif + call s:findedit(a:cmd,file) + endif +endfunction + +function! s:findview(name) + let self = rails#buffer() + let name = a:name + let pre = 'app/views/' + if name !~# '/' + let controller = self.controller_name(1) + if controller != '' + let name = controller.'/'.name + endif + endif + if name =~# '\.\w\+\.\w\+$' || name =~# '\.'.s:viewspattern().'$' + return pre.name + else + for format in ['.'.s:format('html'), ''] + for type in s:view_types + if self.app().has_file(pre.name.format.'.'.type) + return pre.name.format.'.'.type + endif + endfor + endfor + endif + return '' +endfunction + +function! s:findlayout(name) + return s:findview("layouts/".(a:name == '' ? 'application' : a:name)) +endfunction + +function! s:layoutEdit(cmd,...) + if a:0 + return s:viewEdit(a:cmd,"layouts/".a:1) + endif + let file = s:findlayout(s:controller(1)) + if file == "" + let file = s:findlayout("application") + endif + if file == "" + let file = "app/views/layouts/application.html.erb" + endif + call s:edit(a:cmd,s:sub(file,'^/','')) +endfunction + +function! s:controllerEdit(cmd,...) + let suffix = '.rb' + if a:0 == 0 + let controller = s:controller(1) + if rails#buffer().type_name() =~# '^view\%(-layout\|-partial\)\@!' + let suffix .= '#'.expand('%:t:r') + endif + else + let controller = a:1 + endif + if rails#app().has_file("app/controllers/".controller."_controller.rb") || !rails#app().has_file("app/controllers/".controller.".rb") + let suffix = "_controller".suffix + endif + return s:EditSimpleRb(a:cmd,"controller",controller,"app/controllers/",suffix) +endfunction + +function! s:mailerEdit(cmd,...) + return s:EditSimpleRb(a:cmd,"mailer",a:0? a:1 : s:controller(1),"app/mailers/\napp/models/",".rb") +endfunction + +function! s:helperEdit(cmd,...) + return s:EditSimpleRb(a:cmd,"helper",a:0? a:1 : s:controller(1),"app/helpers/","_helper.rb") +endfunction + +function! s:stylesheetEdit(cmd,...) + let name = a:0 ? a:1 : s:controller(1) + if rails#app().has('sass') && rails#app().has_file('public/stylesheets/sass/'.name.'.sass') + return s:EditSimpleRb(a:cmd,"stylesheet",name,"public/stylesheets/sass/",".sass",1) + elseif rails#app().has('sass') && rails#app().has_file('public/stylesheets/sass/'.name.'.scss') + return s:EditSimpleRb(a:cmd,"stylesheet",name,"public/stylesheets/sass/",".scss",1) + elseif rails#app().has('lesscss') && rails#app().has_file('app/stylesheets/'.name.'.less') + return s:EditSimpleRb(a:cmd,"stylesheet",name,"app/stylesheets/",".less",1) + else + let types = rails#app().relglob('app/assets/stylesheets/'.name,'.*','') + if !empty(types) + return s:EditSimpleRb(a:cmd,'stylesheet',name,'app/assets/stylesheets/',types[0],1) + else + return s:EditSimpleRb(a:cmd,'stylesheet',name,'public/stylesheets/','.css',1) + endif + endif +endfunction + +function! s:javascriptEdit(cmd,...) + let name = a:0 ? a:1 : s:controller(1) + if rails#app().has('coffee') && rails#app().has_file('app/scripts/'.name.'.coffee') + return s:EditSimpleRb(a:cmd,'javascript',name,'app/scripts/','.coffee',1) + elseif rails#app().has('coffee') && rails#app().has_file('app/scripts/'.name.'.js') + return s:EditSimpleRb(a:cmd,'javascript',name,'app/scripts/','.js',1) + else + let types = rails#app().relglob('app/assets/javascripts/'.name,'.*','') + if !empty(types) + return s:EditSimpleRb(a:cmd,'javascript',name,'app/assets/javascripts/',types[0],1) + else + return s:EditSimpleRb(a:cmd,'javascript',name,'public/javascripts/','.js',1) + endif + endif +endfunction + +function! s:unittestEdit(cmd,...) + let f = rails#underscore(a:0 ? matchstr(a:1,'[^!#:]*') : s:model(1)) + let jump = a:0 ? matchstr(a:1,'[!#:].*') : '' + if jump =~ '!' + let cmd = s:editcmdfor(a:cmd) + else + let cmd = s:findcmdfor(a:cmd) + endif + let mapping = {'test': ['test/unit/','_test.rb'], 'spec': ['spec/models/','_spec.rb']} + let tests = map(filter(rails#app().test_suites(),'has_key(mapping,v:val)'),'get(mapping,v:val)') + if empty(tests) + let tests = [mapping['test']] + endif + for [prefix, suffix] in tests + if !a:0 && rails#buffer().type_name('model-aro') && f != '' && f !~# '_observer$' + if rails#app().has_file(prefix.f.'_observer'.suffix) + return s:findedit(cmd,prefix.f.'_observer'.suffix.jump) + endif + endif + endfor + for [prefix, suffix] in tests + if rails#app().has_file(prefix.f.suffix) + return s:findedit(cmd,prefix.f.suffix.jump) + endif + endfor + return s:EditSimpleRb(a:cmd,"unittest",f.jump,tests[0][0],tests[0][1],1) +endfunction + +function! s:functionaltestEdit(cmd,...) + let f = rails#underscore(a:0 ? matchstr(a:1,'[^!#:]*') : s:controller(1)) + let jump = a:0 ? matchstr(a:1,'[!#:].*') : '' + if jump =~ '!' + let cmd = s:editcmdfor(a:cmd) + else + let cmd = s:findcmdfor(a:cmd) + endif + let mapping = {'test': [['test/functional/'],['_test.rb','_controller_test.rb']], 'spec': [['spec/controllers/','spec/mailers/'],['_spec.rb','_controller_spec.rb']]} + let tests = map(filter(rails#app().test_suites(),'has_key(mapping,v:val)'),'get(mapping,v:val)') + if empty(tests) + let tests = [mapping[tests]] + endif + for [prefixes, suffixes] in tests + for prefix in prefixes + for suffix in suffixes + if rails#app().has_file(prefix.f.suffix) + return s:findedit(cmd,prefix.f.suffix.jump) + endif + endfor + endfor + endfor + return s:EditSimpleRb(a:cmd,"functionaltest",f.jump,tests[0][0][0],tests[0][1][0],1) +endfunction + +function! s:integrationtestEdit(cmd,...) + if !a:0 + return s:EditSimpleRb(a:cmd,"integrationtest","test/test_helper\nfeatures/support/env\nspec/spec_helper","",".rb") + endif + let f = rails#underscore(matchstr(a:1,'[^!#:]*')) + let jump = matchstr(a:1,'[!#:].*') + if jump =~ '!' + let cmd = s:editcmdfor(a:cmd) + else + let cmd = s:findcmdfor(a:cmd) + endif + let tests = [['test/integration/','_test.rb'], [ 'spec/requests/','_spec.rb'], [ 'spec/integration/','_spec.rb'], [ 'features/','.feature']] + call filter(tests, 'isdirectory(rails#app().path(v:val[0]))') + if empty(tests) + let tests = [['test/integration/','_test.rb']] + endif + for [prefix, suffix] in tests + if rails#app().has_file(prefix.f.suffix) + return s:findedit(cmd,prefix.f.suffix.jump) + elseif rails#app().has_file(prefix.rails#underscore(f).suffix) + return s:findedit(cmd,prefix.rails#underscore(f).suffix.jump) + endif + endfor + return s:EditSimpleRb(a:cmd,"integrationtest",f.jump,tests[0][0],tests[0][1],1) +endfunction + +function! s:specEdit(cmd,...) + if a:0 + return s:EditSimpleRb(a:cmd,"spec",a:1,"spec/","_spec.rb") + else + call s:EditSimpleRb(a:cmd,"spec","spec_helper","spec/",".rb") + endif +endfunction + +function! s:pluginEdit(cmd,...) + let cmd = s:findcmdfor(a:cmd) + let plugin = "" + let extra = "" + if RailsFilePath() =~ '\<vendor/plugins/.' + let plugin = matchstr(RailsFilePath(),'\<vendor/plugins/\zs[^/]*\ze') + let extra = "vendor/plugins/" . plugin . "/\n" + endif + if a:0 + if a:1 =~ '^[^/.]*/\=$' && rails#app().has_file("vendor/plugins/".a:1."/init.rb") + return s:EditSimpleRb(a:cmd,"plugin",s:sub(a:1,'/$',''),"vendor/plugins/","/init.rb") + elseif plugin == "" + call s:edit(cmd,"vendor/plugins/".s:sub(a:1,'\.$','')) + elseif a:1 == "." + call s:findedit(cmd,"vendor/plugins/".plugin) + elseif isdirectory(rails#app().path("vendor/plugins/".matchstr(a:1,'^[^/]*'))) + call s:edit(cmd,"vendor/plugins/".a:1) + else + call s:findedit(cmd,"vendor/plugins/".a:1."\nvendor/plugins/".plugin."/".a:1) + endif + else + call s:findedit(a:cmd,"Gemfile") + endif +endfunction + +function! s:taskEdit(cmd,...) + let plugin = "" + let extra = "" + if RailsFilePath() =~ '\<vendor/plugins/.' + let plugin = matchstr(RailsFilePath(),'\<vendor/plugins/[^/]*') + let extra = plugin."/tasks/\n".plugin."/lib/tasks/\n" + endif + if a:0 + call s:EditSimpleRb(a:cmd,"task",a:1,extra."lib/tasks/",".rake") + else + call s:findedit(a:cmd,(plugin != "" ? plugin."/Rakefile\n" : "")."Rakefile") + endif +endfunction + +function! s:libEdit(cmd,...) + let extra = "" + if RailsFilePath() =~ '\<vendor/plugins/.' + let extra = s:sub(RailsFilePath(),'<vendor/plugins/[^/]*/\zs.*','lib/')."\n" + endif + if a:0 + call s:EditSimpleRb(a:cmd,"lib",a:0? a:1 : "",extra."lib/",".rb") + else + call s:EditSimpleRb(a:cmd,"lib","seeds","db/",".rb") + endif +endfunction + +function! s:environmentEdit(cmd,...) + if a:0 || rails#app().has_file('config/application.rb') + return s:EditSimpleRb(a:cmd,"environment",a:0? a:1 : "../application","config/environments/",".rb") + else + return s:EditSimpleRb(a:cmd,"environment","environment","config/",".rb") + endif +endfunction + +function! s:initializerEdit(cmd,...) + return s:EditSimpleRb(a:cmd,"initializer",a:0? a:1 : "../routes","config/initializers/",".rb") +endfunction + +" }}}1 +" Alternate/Related {{{1 + +function! s:findcmdfor(cmd) + let bang = '' + if a:cmd =~ '\!$' + let bang = '!' + let cmd = s:sub(a:cmd,'\!$','') + else + let cmd = a:cmd + endif + if cmd =~ '^\d' + let num = matchstr(cmd,'^\d\+') + let cmd = s:sub(cmd,'^\d+','') + else + let num = '' + endif + if cmd == '' || cmd == 'E' || cmd == 'F' + return num.'find'.bang + elseif cmd == 'S' + return num.'sfind'.bang + elseif cmd == 'V' + return 'vert '.num.'sfind'.bang + elseif cmd == 'T' + return num.'tabfind'.bang + elseif cmd == 'D' + return num.'read'.bang + else + return num.cmd.bang + endif +endfunction + +function! s:editcmdfor(cmd) + let cmd = s:findcmdfor(a:cmd) + let cmd = s:sub(cmd,'<sfind>','split') + let cmd = s:sub(cmd,'find>','edit') + return cmd +endfunction + +function! s:try(cmd) abort + if !exists(":try") + " I've seen at least one weird setup without :try + exe a:cmd + else + try + exe a:cmd + catch + call s:error(s:sub(v:exception,'^.{-}:\zeE','')) + return 0 + endtry + endif + return 1 +endfunction + +function! s:findedit(cmd,files,...) abort + let cmd = s:findcmdfor(a:cmd) + let files = type(a:files) == type([]) ? copy(a:files) : split(a:files,"\n") + if len(files) == 1 + let file = files[0] + else + let file = get(filter(copy(files),'rails#app().has_file(s:sub(v:val,"#.*|:\\d*$",""))'),0,get(files,0,'')) + endif + if file =~ '[#!]\|:\d*\%(:in\)\=$' + let djump = matchstr(file,'!.*\|#\zs.*\|:\zs\d*\ze\%(:in\)\=$') + let file = s:sub(file,'[#!].*|:\d*%(:in)=$','') + else + let djump = '' + endif + if file == '' + let testcmd = "edit" + elseif isdirectory(rails#app().path(file)) + let arg = file == "." ? rails#app().path() : rails#app().path(file) + let testcmd = s:editcmdfor(cmd).' '.(a:0 ? a:1 . ' ' : '').s:escarg(arg) + exe testcmd + return + elseif rails#app().path() =~ '://' || cmd =~ 'edit' || cmd =~ 'split' + if file !~ '^/' && file !~ '^\w:' && file !~ '://' + let file = s:escarg(rails#app().path(file)) + endif + let testcmd = s:editcmdfor(cmd).' '.(a:0 ? a:1 . ' ' : '').file + else + let testcmd = cmd.' '.(a:0 ? a:1 . ' ' : '').file + endif + if s:try(testcmd) + call s:djump(djump) + endif +endfunction + +function! s:edit(cmd,file,...) + let cmd = s:editcmdfor(a:cmd) + let cmd .= ' '.(a:0 ? a:1 . ' ' : '') + let file = a:file + if file !~ '^/' && file !~ '^\w:' && file !~ '://' + exe cmd."`=fnamemodify(rails#app().path(file),':.')`" + else + exe cmd.file + endif +endfunction + +function! s:Alternate(cmd,line1,line2,count,...) + if a:0 + if a:count && a:cmd !~# 'D' + return call('s:Find',[1,a:line1.a:cmd]+a:000) + elseif a:count + return call('s:Edit',[1,a:line1.a:cmd]+a:000) + else + return call('s:Edit',[1,a:cmd]+a:000) + endif + else + let file = s:getopt(a:count ? 'related' : 'alternate', 'bl') + if file == '' + let file = rails#buffer().related(a:count) + endif + if file != '' + call s:findedit(a:cmd,file) + else + call s:warn("No alternate file is defined") + endif + endif +endfunction + +function! s:Related(cmd,line1,line2,count,...) + if a:count == 0 && a:0 == 0 + return s:Alternate(a:cmd,a:line1,a:line1,a:line1) + else + return call('s:Alternate',[a:cmd,a:line1,a:line2,a:count]+a:000) + endif +endfunction + +function! s:Complete_related(A,L,P) + if a:L =~# '^[[:alpha:]]' + return s:Complete_edit(a:A,a:L,a:P) + else + return s:Complete_find(a:A,a:L,a:P) + endif +endfunction + +function! s:readable_related(...) dict abort + let f = self.name() + if a:0 && a:1 + let lastmethod = self.last_method(a:1) + if self.type_name('controller','mailer') && lastmethod != "" + let root = s:sub(s:sub(s:sub(f,'/application%(_controller)=\.rb$','/shared_controller.rb'),'/%(controllers|models|mailers)/','/views/'),'%(_controller)=\.rb$','/'.lastmethod) + let format = self.last_format(a:1) + if format == '' + let format = self.type_name('mailer') ? 'text' : 'html' + endif + if glob(self.app().path().'/'.root.'.'.format.'.*[^~]') != '' + return root . '.' . format + else + return root + endif + elseif f =~ '\<config/environments/' + return "config/database.yml#". fnamemodify(f,':t:r') + elseif f =~ '\<config/database\.yml$' + if lastmethod != "" + return "config/environments/".lastmethod.".rb" + else + return "config/application.rb\nconfig/environment.rb" + endif + elseif f =~ '\<config/routes\.rb$' | return "config/database.yml" + elseif f =~ '\<config/\%(application\|environment\)\.rb$' + return "config/routes.rb" + elseif self.type_name('view-layout') + return s:sub(s:sub(f,'/views/','/controllers/'),'/layouts/(\k+)\..*$','/\1_controller.rb') + elseif self.type_name('view') + let controller = s:sub(s:sub(f,'/views/','/controllers/'),'/(\k+%(\.\k+)=)\..*$','_controller.rb#\1') + let controller2 = s:sub(s:sub(f,'/views/','/controllers/'),'/(\k+%(\.\k+)=)\..*$','.rb#\1') + let mailer = s:sub(s:sub(f,'/views/','/mailers/'),'/(\k+%(\.\k+)=)\..*$','.rb#\1') + let model = s:sub(s:sub(f,'/views/','/models/'),'/(\k+)\..*$','.rb#\1') + if self.app().has_file(s:sub(controller,'#.{-}$','')) + return controller + elseif self.app().has_file(s:sub(controller2,'#.{-}$','')) + return controller2 + elseif self.app().has_file(s:sub(mailer,'#.{-}$','')) + return mailer + elseif self.app().has_file(s:sub(model,'#.{-}$','')) || model =~ '_mailer\.rb#' + return model + else + return controller + endif + elseif self.type_name('controller') + return s:sub(s:sub(f,'/controllers/','/helpers/'),'%(_controller)=\.rb$','_helper.rb') + " elseif self.type_name('helper') + " return s:findlayout(s:controller()) + elseif self.type_name('model-arb') + let table_name = matchstr(join(self.getline(1,50),"\n"),'\n\s*set_table_name\s*[:"'']\zs\w\+') + if table_name == '' + let table_name = rails#pluralize(s:gsub(s:sub(fnamemodify(f,':r'),'.{-}<app/models/',''),'/','_')) + endif + return self.app().migration('0#'.table_name) + elseif self.type_name('model-aro') + return s:sub(f,'_observer\.rb$','.rb') + elseif self.type_name('db-schema') + return self.app().migration(1) + endif + endif + if f =~ '\<config/environments/' + return "config/application.rb\nconfig/environment.rb" + elseif f == 'README' + return "config/database.yml" + elseif f =~ '\<config/database\.yml$' | return "config/routes.rb" + elseif f =~ '\<config/routes\.rb$' + return "config/application.rb\nconfig/environment.rb" + elseif f =~ '\<config/\%(application\|environment\)\.rb$' + return "config/database.yml" + elseif f ==# 'Gemfile' + return 'Gemfile.lock' + elseif f ==# 'Gemfile.lock' + return 'Gemfile' + elseif f =~ '\<db/migrate/' + let migrations = sort(self.app().relglob('db/migrate/','*','.rb')) + let me = matchstr(f,'\<db/migrate/\zs.*\ze\.rb$') + if !exists('l:lastmethod') || lastmethod == 'down' + let candidates = reverse(filter(copy(migrations),'v:val < me')) + let migration = "db/migrate/".get(candidates,0,migrations[-1]).".rb" + else + let candidates = filter(copy(migrations),'v:val > me') + let migration = "db/migrate/".get(candidates,0,migrations[0]).".rb" + endif + return migration . (exists('l:lastmethod') && lastmethod != '' ? '#'.lastmethod : '') + elseif f =~ '\<application\.js$' + return "app/helpers/application_helper.rb" + elseif self.type_name('javascript') + return "public/javascripts/application.js" + elseif self.type_name('db/schema') + return self.app().migration('') + elseif self.type_name('view') + let spec1 = fnamemodify(f,':s?\<app/?spec/?')."_spec.rb" + let spec2 = fnamemodify(f,':r:s?\<app/?spec/?')."_spec.rb" + let spec3 = fnamemodify(f,':r:r:s?\<app/?spec/?')."_spec.rb" + if self.app().has_file(spec1) + return spec1 + elseif self.app().has_file(spec2) + return spec2 + elseif self.app().has_file(spec3) + return spec3 + elseif self.app().has('spec') + return spec2 + else + if self.type_name('view-layout') + let dest = fnamemodify(f,':r:s?/layouts\>??').'/layout.'.fnamemodify(f,':e') + else + let dest = f + endif + return s:sub(s:sub(dest,'<app/views/','test/functional/'),'/[^/]*$','_controller_test.rb') + endif + elseif self.type_name('controller-api') + let api = s:sub(s:sub(f,'/controllers/','/apis/'),'_controller\.rb$','_api.rb') + return api + elseif self.type_name('api') + return s:sub(s:sub(f,'/apis/','/controllers/'),'_api\.rb$','_controller.rb') + elseif self.type_name('fixtures') && f =~ '\<spec/' + let file = rails#singularize(fnamemodify(f,":t:r")).'_spec.rb' + return file + elseif self.type_name('fixtures') + let file = rails#singularize(fnamemodify(f,":t:r")).'_test.rb' + return file + elseif f == '' + call s:warn("No filename present") + elseif f =~ '\<test/unit/routing_test\.rb$' + return 'config/routes.rb' + elseif self.type_name('spec-view') + return s:sub(s:sub(f,'<spec/','app/'),'_spec\.rb$','') + elseif fnamemodify(f,":e") == "rb" + let file = fnamemodify(f,":r") + if file =~ '_\%(test\|spec\)$' + let file = s:sub(file,'_%(test|spec)$','.rb') + else + let file .= '_test.rb' + endif + if self.type_name('helper') + return s:sub(file,'<app/helpers/','test/unit/helpers/')."\n".s:sub(s:sub(file,'_test\.rb$','_spec.rb'),'<app/helpers/','spec/helpers/') + elseif self.type_name('model') + return s:sub(file,'<app/models/','test/unit/')."\n".s:sub(s:sub(file,'_test\.rb$','_spec.rb'),'<app/models/','spec/models/') + elseif self.type_name('controller') + return s:sub(file,'<app/controllers/','test/functional/')."\n".s:sub(s:sub(file,'_test\.rb$','_spec.rb'),'app/controllers/','spec/controllers/') + elseif self.type_name('mailer') + return s:sub(file,'<app/m%(ailer|odel)s/','test/unit/')."\n".s:sub(s:sub(file,'_test\.rb$','_spec.rb'),'<app/','spec/') + elseif self.type_name('test-unit') + return s:sub(s:sub(file,'test/unit/helpers/','app/helpers/'),'test/unit/','app/models/')."\n".s:sub(file,'test/unit/','lib/') + elseif self.type_name('test-functional') + if file =~ '_api\.rb' + return s:sub(file,'test/functional/','app/apis/') + elseif file =~ '_controller\.rb' + return s:sub(file,'test/functional/','app/controllers/') + else + return s:sub(file,'test/functional/','') + endif + elseif self.type_name('spec-lib') + return s:sub(file,'<spec/','') + elseif self.type_name('lib') + return s:sub(f, '<lib/(.*)\.rb$', 'test/unit/\1_test.rb')."\n".s:sub(f, '<lib/(.*)\.rb$', 'spec/lib/\1_spec.rb') + elseif self.type_name('spec') + return s:sub(file,'<spec/','app/') + elseif file =~ '\<vendor/.*/lib/' + return s:sub(file,'<vendor/.{-}/\zslib/','test/') + elseif file =~ '\<vendor/.*/test/' + return s:sub(file,'<vendor/.{-}/\zstest/','lib/') + else + return fnamemodify(file,':t')."\n".s:sub(s:sub(f,'\.rb$','_spec.rb'),'^app/','spec/') + endif + else + return "" + endif +endfunction + +call s:add_methods('readable',['related']) + +" }}}1 +" Partial Extraction {{{1 + +function! s:Extract(bang,...) range abort + if a:0 == 0 || a:0 > 1 + return s:error("Incorrect number of arguments") + endif + if a:1 =~ '[^a-z0-9_/.]' + return s:error("Invalid partial name") + endif + let rails_root = rails#app().path() + let ext = expand("%:e") + let file = s:sub(a:1,'%(/|^)\zs_\ze[^/]*$','') + let first = a:firstline + let last = a:lastline + let range = first.",".last + if rails#buffer().type_name('view-layout') + if RailsFilePath() =~ '\<app/views/layouts/application\>' + let curdir = 'app/views/shared' + if file !~ '/' + let file = "shared/" .file + endif + else + let curdir = s:sub(RailsFilePath(),'.*<app/views/layouts/(.*)%(\.\w*)$','app/views/\1') + endif + else + let curdir = fnamemodify(RailsFilePath(),':h') + endif + let curdir = rails_root."/".curdir + let dir = fnamemodify(file,":h") + let fname = fnamemodify(file,":t") + if fnamemodify(fname,":e") == "" + let name = fname + let fname .= ".".matchstr(expand("%:t"),'\.\zs.*') + elseif fnamemodify(fname,":e") !~ '^'.s:viewspattern().'$' + let name = fnamemodify(fname,":r") + let fname .= ".".ext + else + let name = fnamemodify(fname,":r:r") + endif + let var = "@".name + let collection = "" + if dir =~ '^/' + let out = (rails_root).dir."/_".fname + elseif dir == "" || dir == "." + let out = (curdir)."/_".fname + elseif isdirectory(curdir."/".dir) + let out = (curdir)."/".dir."/_".fname + else + let out = (rails_root)."/app/views/".dir."/_".fname + endif + if filereadable(out) && !a:bang + return s:error('E13: File exists (add ! to override)') + endif + if !isdirectory(fnamemodify(out,':h')) + if a:bang + call mkdir(fnamemodify(out,':h'),'p') + else + return s:error('No such directory') + endif + endif + " No tabs, they'll just complicate things + if ext =~? '^\%(rhtml\|erb\|dryml\)$' + let erub1 = '\<\%\s*' + let erub2 = '\s*-=\%\>' + else + let erub1 = '' + let erub2 = '' + endif + let spaces = matchstr(getline(first),"^ *") + if getline(last+1) =~ '\v^\s*'.erub1.'end'.erub2.'\s*$' + let fspaces = matchstr(getline(last+1),"^ *") + if getline(first-1) =~ '\v^'.fspaces.erub1.'for\s+(\k+)\s+in\s+([^ %>]+)'.erub2.'\s*$' + let collection = s:sub(getline(first-1),'^'.fspaces.erub1.'for\s+(\k+)\s+in\s+([^ >]+)'.erub2.'\s*$','\1>\2') + elseif getline(first-1) =~ '\v^'.fspaces.erub1.'([^ %>]+)\.each\s+do\s+\|\s*(\k+)\s*\|'.erub2.'\s*$' + let collection = s:sub(getline(first-1),'^'.fspaces.erub1.'([^ %>]+)\.each\s+do\s+\|\s*(\k+)\s*\|'.erub2.'\s*$','\2>\1') + endif + if collection != '' + let var = matchstr(collection,'^\k\+') + let collection = s:sub(collection,'^\k+\>','') + let first -= 1 + let last += 1 + endif + else + let fspaces = spaces + endif + let renderstr = "render :partial => '".fnamemodify(file,":r:r")."'" + if collection != "" + let renderstr .= ", :collection => ".collection + elseif "@".name != var + let renderstr .= ", :object => ".var + endif + if ext =~? '^\%(rhtml\|erb\|dryml\)$' + let renderstr = "<%= ".renderstr." %>" + elseif ext == "rxml" || ext == "builder" + let renderstr = "xml << ".s:sub(renderstr,"render ","render(").")" + elseif ext == "rjs" + let renderstr = "page << ".s:sub(renderstr,"render ","render(").")" + elseif ext == "haml" + let renderstr = "= ".renderstr + elseif ext == "mn" + let renderstr = "_".renderstr + endif + let buf = @@ + silent exe range."yank" + let partial = @@ + let @@ = buf + let old_ai = &ai + try + let &ai = 0 + silent exe "norm! :".first.",".last."change\<CR>".fspaces.renderstr."\<CR>.\<CR>" + finally + let &ai = old_ai + endtry + if renderstr =~ '<%' + norm ^6w + else + norm ^5w + endif + let ft = &ft + let shortout = fnamemodify(out,':.') + silent split `=shortout` + silent %delete + let &ft = ft + let @@ = partial + silent put + 0delete + let @@ = buf + if spaces != "" + silent! exe '%substitute/^'.spaces.'//' + endif + silent! exe '%substitute?\%(\w\|[@:"'."'".'-]\)\@<!'.var.'\>?'.name.'?g' + 1 +endfunction + +" }}}1 +" Migration Inversion {{{1 + +function! s:mkeep(str) + " Things to keep (like comments) from a migration statement + return matchstr(a:str,' #[^{].*') +endfunction + +function! s:mextargs(str,num) + if a:str =~ '^\s*\w\+\s*(' + return s:sub(matchstr(a:str,'^\s*\w\+\s*\zs(\%([^,)]\+[,)]\)\{,'.a:num.'\}'),',$',')') + else + return s:sub(s:sub(matchstr(a:str,'\w\+\>\zs\s*\%([^,){ ]*[, ]*\)\{,'.a:num.'\}'),'[, ]*$',''),'^\s+',' ') + endif +endfunction + +function! s:migspc(line) + return matchstr(a:line,'^\s*') +endfunction + +function! s:invertrange(beg,end) + let str = "" + let lnum = a:beg + while lnum <= a:end + let line = getline(lnum) + let add = "" + if line == '' + let add = ' ' + elseif line =~ '^\s*\(#[^{].*\)\=$' + let add = line + elseif line =~ '\<create_table\>' + let add = s:migspc(line)."drop_table".s:mextargs(line,1).s:mkeep(line) + let lnum = s:endof(lnum) + elseif line =~ '\<drop_table\>' + let add = s:sub(line,'<drop_table>\s*\(=\s*([^,){ ]*).*','create_table \1 do |t|'."\n".matchstr(line,'^\s*').'end').s:mkeep(line) + elseif line =~ '\<add_column\>' + let add = s:migspc(line).'remove_column'.s:mextargs(line,2).s:mkeep(line) + elseif line =~ '\<remove_column\>' + let add = s:sub(line,'<remove_column>','add_column') + elseif line =~ '\<add_index\>' + let add = s:migspc(line).'remove_index'.s:mextargs(line,1) + let mat = matchstr(line,':name\s*=>\s*\zs[^ ,)]*') + if mat != '' + let add = s:sub(add,'\)=$',', :name => '.mat.'&') + else + let mat = matchstr(line,'\<add_index\>[^,]*,\s*\zs\%(\[[^]]*\]\|[:"'."'".']\w*["'."'".']\=\)') + if mat != '' + let add = s:sub(add,'\)=$',', :column => '.mat.'&') + endif + endif + let add .= s:mkeep(line) + elseif line =~ '\<remove_index\>' + let add = s:sub(s:sub(line,'<remove_index','add_index'),':column\s*\=\>\s*','') + elseif line =~ '\<rename_\%(table\|column\|index\)\>' + let add = s:sub(line,'<rename_%(table\s*\(=\s*|%(column|index)\s*\(=\s*[^,]*,\s*)\zs([^,]*)(,\s*)([^,]*)','\3\2\1') + elseif line =~ '\<change_column\>' + let add = s:migspc(line).'change_column'.s:mextargs(line,2).s:mkeep(line) + elseif line =~ '\<change_column_default\>' + let add = s:migspc(line).'change_column_default'.s:mextargs(line,2).s:mkeep(line) + elseif line =~ '\.update_all(\(["'."'".']\).*\1)$' || line =~ '\.update_all \(["'."'".']\).*\1$' + " .update_all('a = b') => .update_all('b = a') + let pre = matchstr(line,'^.*\.update_all[( ][}'."'".'"]') + let post = matchstr(line,'["'."'".'])\=$') + let mat = strpart(line,strlen(pre),strlen(line)-strlen(pre)-strlen(post)) + let mat = s:gsub(','.mat.',','%(,\s*)@<=([^ ,=]{-})(\s*\=\s*)([^,=]{-})%(\s*,)@=','\3\2\1') + let add = pre.s:sub(s:sub(mat,'^,',''),',$','').post + elseif line =~ '^s\*\%(if\|unless\|while\|until\|for\)\>' + let lnum = s:endof(lnum) + endif + if lnum == 0 + return -1 + endif + if add == "" + let add = s:sub(line,'^\s*\zs.*','raise ActiveRecord::IrreversibleMigration') + elseif add == " " + let add = "" + endif + let str = add."\n".str + let lnum += 1 + endwhile + let str = s:gsub(str,'(\s*raise ActiveRecord::IrreversibleMigration\n)+','\1') + return str +endfunction + +function! s:Invert(bang) + let err = "Could not parse method" + let src = "up" + let dst = "down" + let beg = search('\%('.&l:define.'\).*'.src.'\>',"w") + let end = s:endof(beg) + if beg + 1 == end + let src = "down" + let dst = "up" + let beg = search('\%('.&l:define.'\).*'.src.'\>',"w") + let end = s:endof(beg) + endif + if !beg || !end + return s:error(err) + endif + let str = s:invertrange(beg+1,end-1) + if str == -1 + return s:error(err) + endif + let beg = search('\%('.&l:define.'\).*'.dst.'\>',"w") + let end = s:endof(beg) + if !beg || !end + return s:error(err) + endif + if foldclosed(beg) > 0 + exe beg."foldopen!" + endif + if beg + 1 < end + exe (beg+1).",".(end-1)."delete _" + endif + if str != '' + exe beg.'put =str' + exe 1+beg + endif +endfunction + +" }}}1 +" Cache {{{1 + +let s:cache_prototype = {'dict': {}} + +function! s:cache_clear(...) dict + if a:0 == 0 + let self.dict = {} + elseif has_key(self,'dict') && has_key(self.dict,a:1) + unlet! self.dict[a:1] + endif +endfunction + +function! rails#cache_clear(...) + if exists('b:rails_root') + return call(rails#app().cache.clear,a:000,rails#app().cache) + endif +endfunction + +function! s:cache_get(...) dict + if a:0 == 1 + return self.dict[a:1] + else + return self.dict + endif +endfunction + +function! s:cache_has(key) dict + return has_key(self.dict,a:key) +endfunction + +function! s:cache_needs(key) dict + return !has_key(self.dict,a:key) +endfunction + +function! s:cache_set(key,value) dict + let self.dict[a:key] = a:value +endfunction + +call s:add_methods('cache', ['clear','needs','has','get','set']) + +let s:app_prototype.cache = s:cache_prototype + +" }}}1 +" Syntax {{{1 + +function! s:resetomnicomplete() + if exists("+completefunc") && &completefunc == 'syntaxcomplete#Complete' + if exists("g:loaded_syntax_completion") + " Ugly but necessary, until we have our own completion + unlet g:loaded_syntax_completion + silent! delfunction syntaxcomplete#Complete + endif + endif +endfunction + +function! s:helpermethods() + return "" + \."action_name atom_feed audio_path audio_tag auto_discovery_link_tag " + \."button_tag button_to button_to_function " + \."cache capture cdata_section check_box check_box_tag collection_select concat content_for content_tag content_tag_for controller controller_name controller_path convert_to_model cookies csrf_meta_tag csrf_meta_tags current_cycle cycle " + \."date_select datetime_select debug distance_of_time_in_words distance_of_time_in_words_to_now div_for dom_class dom_id " + \."email_field email_field_tag escape_javascript escape_once excerpt " + \."favicon_link_tag field_set_tag fields_for file_field file_field_tag flash form_for form_tag " + \."grouped_collection_select grouped_options_for_select " + \."headers hidden_field hidden_field_tag highlight " + \."image_alt image_path image_submit_tag image_tag " + \."j javascript_cdata_section javascript_include_tag javascript_path javascript_tag " + \."l label label_tag link_to link_to_function link_to_if link_to_unless link_to_unless_current localize logger " + \."mail_to " + \."number_field number_field_tag number_to_currency number_to_human number_to_human_size number_to_percentage number_to_phone number_with_delimiter number_with_precision " + \."option_groups_from_collection_for_select options_for_select options_from_collection_for_select " + \."params password_field password_field_tag path_to_audio path_to_image path_to_javascript path_to_stylesheet path_to_video phone_field phone_field_tag pluralize provide " + \."radio_button radio_button_tag range_field range_field_tag raw render request request_forgery_protection_token reset_cycle response " + \."safe_concat safe_join sanitize sanitize_css search_field search_field_tag select select_date select_datetime select_day select_hour select_minute select_month select_second select_tag select_time select_year session simple_format strip_links strip_tags stylesheet_link_tag stylesheet_path submit_tag " + \."t tag telephone_field telephone_field_tag text_area text_area_tag text_field text_field_tag time_ago_in_words time_select time_tag time_zone_options_for_select time_zone_select translate truncate " + \."url_field url_field_tag url_for url_options " + \."video_path video_tag " + \."word_wrap" +endfunction + +function! s:app_user_classes() dict + if self.cache.needs("user_classes") + let controllers = self.relglob("app/controllers/","**/*",".rb") + call map(controllers,'v:val == "application" ? v:val."_controller" : v:val') + let classes = + \ self.relglob("app/models/","**/*",".rb") + + \ controllers + + \ self.relglob("app/helpers/","**/*",".rb") + + \ self.relglob("lib/","**/*",".rb") + call map(classes,'rails#camelize(v:val)') + call self.cache.set("user_classes",classes) + endif + return self.cache.get('user_classes') +endfunction + +function! s:app_user_assertions() dict + if self.cache.needs("user_assertions") + if self.has_file("test/test_helper.rb") + let assertions = map(filter(s:readfile(self.path("test/test_helper.rb")),'v:val =~ "^ def assert_"'),'matchstr(v:val,"^ def \\zsassert_\\w\\+")') + else + let assertions = [] + endif + call self.cache.set("user_assertions",assertions) + endif + return self.cache.get('user_assertions') +endfunction + +call s:add_methods('app', ['user_classes','user_assertions']) + +function! s:BufSyntax() + if (!exists("g:rails_syntax") || g:rails_syntax) + let buffer = rails#buffer() + let s:javascript_functions = "$ $$ $A $F $H $R $w jQuery" + let classes = s:gsub(join(rails#app().user_classes(),' '),'::',' ') + if &syntax == 'ruby' + if classes != '' + exe "syn keyword rubyRailsUserClass ".classes." containedin=rubyClassDeclaration,rubyModuleDeclaration,rubyClass,rubyModule" + endif + if buffer.type_name() == '' + syn keyword rubyRailsMethod params request response session headers cookies flash + endif + if buffer.type_name('api') + syn keyword rubyRailsAPIMethod api_method inflect_names + endif + if buffer.type_name() ==# 'model' || buffer.type_name('model-arb') + syn keyword rubyRailsARMethod default_scope named_scope scope serialize + syn keyword rubyRailsARAssociationMethod belongs_to has_one has_many has_and_belongs_to_many composed_of accepts_nested_attributes_for + syn keyword rubyRailsARCallbackMethod before_create before_destroy before_save before_update before_validation before_validation_on_create before_validation_on_update + syn keyword rubyRailsARCallbackMethod after_create after_destroy after_save after_update after_validation after_validation_on_create after_validation_on_update + syn keyword rubyRailsARCallbackMethod around_create around_destroy around_save around_update + syn keyword rubyRailsARCallbackMethod after_commit after_find after_initialize after_rollback after_touch + syn keyword rubyRailsARClassMethod attr_accessible attr_protected attr_readonly establish_connection set_inheritance_column set_locking_column set_primary_key set_sequence_name set_table_name + syn keyword rubyRailsARValidationMethod validate validates validate_on_create validate_on_update validates_acceptance_of validates_associated validates_confirmation_of validates_each validates_exclusion_of validates_format_of validates_inclusion_of validates_length_of validates_numericality_of validates_presence_of validates_size_of validates_uniqueness_of + syn keyword rubyRailsMethod logger + endif + if buffer.type_name('model-aro') + syn keyword rubyRailsARMethod observe + endif + if buffer.type_name('mailer') + syn keyword rubyRailsMethod logger url_for polymorphic_path polymorphic_url + syn keyword rubyRailsRenderMethod mail render + syn keyword rubyRailsControllerMethod attachments default helper helper_attr helper_method + endif + if buffer.type_name('helper','view') + syn keyword rubyRailsViewMethod polymorphic_path polymorphic_url + exe "syn keyword rubyRailsHelperMethod ".s:gsub(s:helpermethods(),'<%(content_for|select)\s+','') + syn match rubyRailsHelperMethod '\<select\>\%(\s*{\|\s*do\>\|\s*(\=\s*&\)\@!' + syn match rubyRailsHelperMethod '\<\%(content_for?\=\|current_page?\)' + syn match rubyRailsViewMethod '\.\@<!\<\(h\|html_escape\|u\|url_encode\)\>' + if buffer.type_name('view-partial') + syn keyword rubyRailsMethod local_assigns + endif + elseif buffer.type_name('controller') + syn keyword rubyRailsMethod params request response session headers cookies flash + syn keyword rubyRailsRenderMethod render + syn keyword rubyRailsMethod logger polymorphic_path polymorphic_url + syn keyword rubyRailsControllerMethod helper helper_attr helper_method filter layout url_for serialize exempt_from_layout filter_parameter_logging hide_action cache_sweeper protect_from_forgery caches_page cache_page caches_action expire_page expire_action rescue_from + syn keyword rubyRailsRenderMethod head redirect_to render_to_string respond_with + syn match rubyRailsRenderMethod '\<respond_to\>?\@!' + syn keyword rubyRailsFilterMethod before_filter append_before_filter prepend_before_filter after_filter append_after_filter prepend_after_filter around_filter append_around_filter prepend_around_filter skip_before_filter skip_after_filter + syn keyword rubyRailsFilterMethod verify + endif + if buffer.type_name('db-migration','db-schema') + syn keyword rubyRailsMigrationMethod create_table change_table drop_table rename_table add_column rename_column change_column change_column_default remove_column add_index remove_index rename_index execute + endif + if buffer.type_name('test') + if !empty(rails#app().user_assertions()) + exe "syn keyword rubyRailsUserMethod ".join(rails#app().user_assertions()) + endif + syn keyword rubyRailsTestMethod add_assertion assert assert_block assert_equal assert_in_delta assert_instance_of assert_kind_of assert_match assert_nil assert_no_match assert_not_equal assert_not_nil assert_not_same assert_nothing_raised assert_nothing_thrown assert_operator assert_raise assert_respond_to assert_same assert_send assert_throws assert_recognizes assert_generates assert_routing flunk fixtures fixture_path use_transactional_fixtures use_instantiated_fixtures assert_difference assert_no_difference assert_valid + syn keyword rubyRailsTestMethod test setup teardown + if !buffer.type_name('test-unit') + syn match rubyRailsTestControllerMethod '\.\@<!\<\%(get\|post\|put\|delete\|head\|process\|assigns\)\>' + syn keyword rubyRailsTestControllerMethod get_via_redirect post_via_redirect put_via_redirect delete_via_redirect request_via_redirect + syn keyword rubyRailsTestControllerMethod assert_response assert_redirected_to assert_template assert_recognizes assert_generates assert_routing assert_dom_equal assert_dom_not_equal assert_select assert_select_rjs assert_select_encoded assert_select_email assert_tag assert_no_tag + endif + elseif buffer.type_name('spec') + syn keyword rubyRailsTestMethod describe context it its specify shared_examples_for it_should_behave_like before after around subject fixtures controller_name helper_name + syn match rubyRailsTestMethod '\<let\>!\=' + syn keyword rubyRailsTestMethod violated pending expect double mock mock_model stub_model + syn match rubyRailsTestMethod '\.\@<!\<stub\>!\@!' + if !buffer.type_name('spec-model') + syn match rubyRailsTestControllerMethod '\.\@<!\<\%(get\|post\|put\|delete\|head\|process\|assigns\)\>' + syn keyword rubyRailsTestControllerMethod integrate_views + syn keyword rubyRailsMethod params request response session flash + syn keyword rubyRailsMethod polymorphic_path polymorphic_url + endif + endif + if buffer.type_name('task') + syn match rubyRailsRakeMethod '^\s*\zs\%(task\|file\|namespace\|desc\|before\|after\|on\)\>\%(\s*=\)\@!' + endif + if buffer.type_name('model-awss') + syn keyword rubyRailsMethod member + endif + if buffer.type_name('config-routes') + syn match rubyRailsMethod '\.\zs\%(connect\|named_route\)\>' + syn keyword rubyRailsMethod match get put post delete redirect root resource resources collection member nested scope namespace controller constraints + endif + syn keyword rubyRailsMethod debugger + syn keyword rubyRailsMethod alias_attribute alias_method_chain attr_accessor_with_default attr_internal attr_internal_accessor attr_internal_reader attr_internal_writer delegate mattr_accessor mattr_reader mattr_writer superclass_delegating_accessor superclass_delegating_reader superclass_delegating_writer + syn keyword rubyRailsMethod cattr_accessor cattr_reader cattr_writer class_inheritable_accessor class_inheritable_array class_inheritable_array_writer class_inheritable_hash class_inheritable_hash_writer class_inheritable_option class_inheritable_reader class_inheritable_writer inheritable_attributes read_inheritable_attribute reset_inheritable_attributes write_inheritable_array write_inheritable_attribute write_inheritable_hash + syn keyword rubyRailsInclude require_dependency + + syn region rubyString matchgroup=rubyStringDelimiter start=+\%(:order\s*=>\s*\)\@<="+ skip=+\\\\\|\\"+ end=+"+ contains=@rubyStringSpecial,railsOrderSpecial + syn region rubyString matchgroup=rubyStringDelimiter start=+\%(:order\s*=>\s*\)\@<='+ skip=+\\\\\|\\'+ end=+'+ contains=@rubyStringSpecial,railsOrderSpecial + syn match railsOrderSpecial +\c\<\%(DE\|A\)SC\>+ contained + syn region rubyString matchgroup=rubyStringDelimiter start=+\%(:conditions\s*=>\s*\[\s*\)\@<="+ skip=+\\\\\|\\"+ end=+"+ contains=@rubyStringSpecial,railsConditionsSpecial + syn region rubyString matchgroup=rubyStringDelimiter start=+\%(:conditions\s*=>\s*\[\s*\)\@<='+ skip=+\\\\\|\\'+ end=+'+ contains=@rubyStringSpecial,railsConditionsSpecial + syn match railsConditionsSpecial +?\|:\h\w*+ contained + syn cluster rubyNotTop add=railsOrderSpecial,railsConditionsSpecial + + " XHTML highlighting inside %Q<> + unlet! b:current_syntax + let removenorend = !exists("g:html_no_rendering") + let g:html_no_rendering = 1 + syn include @htmlTop syntax/xhtml.vim + if removenorend + unlet! g:html_no_rendering + endif + let b:current_syntax = "ruby" + " Restore syn sync, as best we can + if !exists("g:ruby_minlines") + let g:ruby_minlines = 50 + endif + syn sync fromstart + exe "syn sync minlines=" . g:ruby_minlines + syn case match + syn region rubyString matchgroup=rubyStringDelimiter start=+%Q\=<+ end=+>+ contains=@htmlTop,@rubyStringSpecial + syn cluster htmlArgCluster add=@rubyStringSpecial + syn cluster htmlPreProc add=@rubyStringSpecial + + elseif &syntax =~# '^eruby\>' || &syntax == 'haml' + syn case match + if classes != '' + exe 'syn keyword '.&syntax.'RailsUserClass '.classes.' contained containedin=@'.&syntax.'RailsRegions' + endif + if &syntax == 'haml' + exe 'syn cluster hamlRailsRegions contains=hamlRubyCodeIncluded,hamlRubyCode,hamlRubyHash,@hamlEmbeddedRuby,rubyInterpolation' + else + exe 'syn cluster erubyRailsRegions contains=erubyOneLiner,erubyBlock,erubyExpression,rubyInterpolation' + endif + exe 'syn keyword '.&syntax.'RailsHelperMethod '.s:gsub(s:helpermethods(),'<%(content_for|select)\s+','').' contained containedin=@'.&syntax.'RailsRegions' + exe 'syn match '.&syntax.'RailsHelperMethod "\<select\>\%(\s*{\|\s*do\>\|\s*(\=\s*&\)\@!" contained containedin=@'.&syntax.'RailsRegions' + exe 'syn match '.&syntax.'RailsHelperMethod "\<\%(content_for?\=\|current_page?\)" contained containedin=@'.&syntax.'RailsRegions' + exe 'syn keyword '.&syntax.'RailsMethod debugger polymorphic_path polymorphic_url contained containedin=@'.&syntax.'RailsRegions' + exe 'syn match '.&syntax.'RailsViewMethod "\.\@<!\<\(h\|html_escape\|u\|url_encode\)\>" contained containedin=@'.&syntax.'RailsRegions' + if buffer.type_name('view-partial') + exe 'syn keyword '.&syntax.'RailsMethod local_assigns contained containedin=@'.&syntax.'RailsRegions' + endif + exe 'syn keyword '.&syntax.'RailsRenderMethod render contained containedin=@'.&syntax.'RailsRegions' + exe 'syn case match' + set isk+=$ + exe 'syn keyword javascriptRailsFunction contained '.s:javascript_functions + exe 'syn cluster htmlJavaScript add=javascriptRailsFunction' + elseif &syntax == "yaml" + syn case match + " Modeled after syntax/eruby.vim + unlet! b:current_syntax + let g:main_syntax = 'eruby' + syn include @rubyTop syntax/ruby.vim + unlet g:main_syntax + syn cluster yamlRailsRegions contains=yamlRailsOneLiner,yamlRailsBlock,yamlRailsExpression + syn region yamlRailsOneLiner matchgroup=yamlRailsDelimiter start="^%%\@!" end="$" contains=@rubyRailsTop containedin=ALLBUT,@yamlRailsRegions,yamlRailsComment keepend oneline + syn region yamlRailsBlock matchgroup=yamlRailsDelimiter start="<%%\@!" end="%>" contains=@rubyTop containedin=ALLBUT,@yamlRailsRegions,yamlRailsComment + syn region yamlRailsExpression matchgroup=yamlRailsDelimiter start="<%=" end="%>" contains=@rubyTop containedin=ALLBUT,@yamlRailsRegions,yamlRailsComment + syn region yamlRailsComment matchgroup=yamlRailsDelimiter start="<%#" end="%>" contains=rubyTodo,@Spell containedin=ALLBUT,@yamlRailsRegions,yamlRailsComment keepend + syn match yamlRailsMethod '\.\@<!\<\(h\|html_escape\|u\|url_encode\)\>' contained containedin=@yamlRailsRegions + if classes != '' + exe "syn keyword yamlRailsUserClass ".classes." contained containedin=@yamlRailsRegions" + endif + let b:current_syntax = "yaml" + elseif &syntax == "html" + syn case match + set isk+=$ + exe "syn keyword javascriptRailsFunction contained ".s:javascript_functions + syn cluster htmlJavaScript add=javascriptRailsFunction + elseif &syntax == "javascript" || &syntax == "coffee" + " The syntax file included with Vim incorrectly sets syn case ignore. + syn case match + set isk+=$ + exe "syn keyword javascriptRailsFunction ".s:javascript_functions + + endif + endif + call s:HiDefaults() +endfunction + +function! s:HiDefaults() + hi def link rubyRailsAPIMethod rubyRailsMethod + hi def link rubyRailsARAssociationMethod rubyRailsARMethod + hi def link rubyRailsARCallbackMethod rubyRailsARMethod + hi def link rubyRailsARClassMethod rubyRailsARMethod + hi def link rubyRailsARValidationMethod rubyRailsARMethod + hi def link rubyRailsARMethod rubyRailsMethod + hi def link rubyRailsRenderMethod rubyRailsMethod + hi def link rubyRailsHelperMethod rubyRailsMethod + hi def link rubyRailsViewMethod rubyRailsMethod + hi def link rubyRailsMigrationMethod rubyRailsMethod + hi def link rubyRailsControllerMethod rubyRailsMethod + hi def link rubyRailsFilterMethod rubyRailsMethod + hi def link rubyRailsTestControllerMethod rubyRailsTestMethod + hi def link rubyRailsTestMethod rubyRailsMethod + hi def link rubyRailsRakeMethod rubyRailsMethod + hi def link rubyRailsMethod railsMethod + hi def link rubyRailsInclude rubyInclude + hi def link rubyRailsUserClass railsUserClass + hi def link rubyRailsUserMethod railsUserMethod + hi def link erubyRailsHelperMethod erubyRailsMethod + hi def link erubyRailsViewMethod erubyRailsMethod + hi def link erubyRailsRenderMethod erubyRailsMethod + hi def link erubyRailsMethod railsMethod + hi def link erubyRailsUserMethod railsUserMethod + hi def link erubyRailsUserClass railsUserClass + hi def link hamlRailsHelperMethod hamlRailsMethod + hi def link hamlRailsViewMethod hamlRailsMethod + hi def link hamlRailsRenderMethod hamlRailsMethod + hi def link hamlRailsMethod railsMethod + hi def link hamlRailsUserMethod railsUserMethod + hi def link hamlRailsUserClass railsUserClass + hi def link railsUserMethod railsMethod + hi def link yamlRailsDelimiter Delimiter + hi def link yamlRailsMethod railsMethod + hi def link yamlRailsComment Comment + hi def link yamlRailsUserClass railsUserClass + hi def link yamlRailsUserMethod railsUserMethod + hi def link javascriptRailsFunction railsMethod + hi def link railsUserClass railsClass + hi def link railsMethod Function + hi def link railsClass Type + hi def link railsOrderSpecial railsStringSpecial + hi def link railsConditionsSpecial railsStringSpecial + hi def link railsStringSpecial Identifier +endfunction + +function! rails#log_syntax() + if has('conceal') + syn match railslogEscape '\e\[[0-9;]*m' conceal + syn match railslogEscapeMN '\e\[[0-9;]*m' conceal nextgroup=railslogModelNum,railslogEscapeMN skipwhite contained + syn match railslogEscapeSQL '\e\[[0-9;]*m' conceal nextgroup=railslogSQL,railslogEscapeSQL skipwhite contained + else + syn match railslogEscape '\e\[[0-9;]*m' + syn match railslogEscapeMN '\e\[[0-9;]*m' nextgroup=railslogModelNum,railslogEscapeMN skipwhite contained + syn match railslogEscapeSQL '\e\[[0-9;]*m' nextgroup=railslogSQL,railslogEscapeSQL skipwhite contained + endif + syn match railslogRender '\%(^\s*\%(\e\[[0-9;]*m\)\=\)\@<=\%(Processing\|Rendering\|Rendered\|Redirected\|Completed\)\>' + syn match railslogComment '^\s*# .*' + syn match railslogModel '\%(^\s*\%(\e\[[0-9;]*m\)\=\)\@<=\u\%(\w\|:\)* \%(Load\%( Including Associations\| IDs For Limited Eager Loading\)\=\|Columns\|Count\|Create\|Update\|Destroy\|Delete all\)\>' skipwhite nextgroup=railslogModelNum,railslogEscapeMN + syn match railslogModel '\%(^\s*\%(\e\[[0-9;]*m\)\=\)\@<=SQL\>' skipwhite nextgroup=railslogModelNum,railslogEscapeMN + syn region railslogModelNum start='(' end=')' contains=railslogNumber contained skipwhite nextgroup=railslogSQL,railslogEscapeSQL + syn match railslogSQL '\u[^\e]*' contained + " Destroy generates multiline SQL, ugh + syn match railslogSQL '\%(^ \%(\e\[[0-9;]*m\)\=\)\@<=\%(FROM\|WHERE\|ON\|AND\|OR\|ORDER\) .*$' + syn match railslogNumber '\<\d\+\>%' + syn match railslogNumber '[ (]\@<=\<\d\+\.\d\+\>\.\@!' + syn region railslogString start='"' skip='\\"' end='"' oneline contained + syn region railslogHash start='{' end='}' oneline contains=railslogHash,railslogString + syn match railslogIP '\<\d\{1,3\}\%(\.\d\{1,3}\)\{3\}\>' + syn match railslogTimestamp '\<\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\>' + syn match railslogSessionID '\<\x\{32\}\>' + syn match railslogIdentifier '^\s*\%(Session ID\|Parameters\)\ze:' + syn match railslogSuccess '\<2\d\d \u[A-Za-z0-9 ]*\>' + syn match railslogRedirect '\<3\d\d \u[A-Za-z0-9 ]*\>' + syn match railslogError '\<[45]\d\d \u[A-Za-z0-9 ]*\>' + syn match railslogError '^DEPRECATION WARNING\>' + syn keyword railslogHTTP OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT + syn region railslogStackTrace start=":\d\+:in `\w\+'$" end="^\s*$" keepend fold + hi def link railslogEscapeMN railslogEscape + hi def link railslogEscapeSQL railslogEscape + hi def link railslogEscape Ignore + hi def link railslogComment Comment + hi def link railslogRender Keyword + hi def link railslogModel Type + hi def link railslogSQL PreProc + hi def link railslogNumber Number + hi def link railslogString String + hi def link railslogSessionID Constant + hi def link railslogIdentifier Identifier + hi def link railslogRedirect railslogSuccess + hi def link railslogSuccess Special + hi def link railslogError Error + hi def link railslogHTTP Special +endfunction + +" }}}1 +" Mappings {{{1 + +function! s:BufMappings() + nnoremap <buffer> <silent> <Plug>RailsFind :<C-U>call <SID>Find(v:count1,'E')<CR> + nnoremap <buffer> <silent> <Plug>RailsSplitFind :<C-U>call <SID>Find(v:count1,'S')<CR> + nnoremap <buffer> <silent> <Plug>RailsVSplitFind :<C-U>call <SID>Find(v:count1,'V')<CR> + nnoremap <buffer> <silent> <Plug>RailsTabFind :<C-U>call <SID>Find(v:count1,'T')<CR> + if g:rails_mappings + if !hasmapto("<Plug>RailsFind") + nmap <buffer> gf <Plug>RailsFind + endif + if !hasmapto("<Plug>RailsSplitFind") + nmap <buffer> <C-W>f <Plug>RailsSplitFind + endif + if !hasmapto("<Plug>RailsTabFind") + nmap <buffer> <C-W>gf <Plug>RailsTabFind + endif + if exists("$CREAM") + imap <buffer> <C-CR> <C-O><Plug>RailsFind + imap <buffer> <M-[> <C-O><Plug>RailsAlternate + imap <buffer> <M-]> <C-O><Plug>RailsRelated + endif + endif + " SelectBuf you're a dirty hack + let v:errmsg = "" +endfunction + +" }}}1 +" Database {{{1 + +function! s:extractdbvar(str,arg) + return matchstr("\n".a:str."\n",'\n'.a:arg.'=\zs.\{-\}\ze\n') +endfunction + +function! s:app_dbext_settings(environment) dict + if self.cache.needs('dbext_settings') + call self.cache.set('dbext_settings',{}) + endif + let cache = self.cache.get('dbext_settings') + if !has_key(cache,a:environment) + let dict = {} + if self.has_file("config/database.yml") + let cmdb = 'require %{yaml}; File.open(%q{'.self.path().'/config/database.yml}) {|f| y = YAML::load(f); e = y[%{' + let cmde = '}]; i=0; e=y[e] while e.respond_to?(:to_str) && (i+=1)<16; e.each{|k,v|puts k.to_s+%{=}+v.to_s}}' + let out = self.lightweight_ruby_eval(cmdb.a:environment.cmde) + let adapter = s:extractdbvar(out,'adapter') + let adapter = get({'mysql2': 'mysql', 'postgresql': 'pgsql', 'sqlite3': 'sqlite', 'sqlserver': 'sqlsrv', 'sybase': 'asa', 'oracle': 'ora'},adapter,adapter) + let dict['type'] = toupper(adapter) + let dict['user'] = s:extractdbvar(out,'username') + let dict['passwd'] = s:extractdbvar(out,'password') + if dict['passwd'] == '' && adapter == 'mysql' + " Hack to override password from .my.cnf + let dict['extra'] = ' --password=' + else + let dict['extra'] = '' + endif + let dict['dbname'] = s:extractdbvar(out,'database') + if dict['dbname'] == '' + let dict['dbname'] = s:extractdbvar(out,'dbfile') + endif + if dict['dbname'] != '' && dict['dbname'] !~ '^:' && adapter =~? '^sqlite' + let dict['dbname'] = self.path(dict['dbname']) + endif + let dict['profile'] = '' + if adapter == 'ora' + let dict['srvname'] = s:extractdbvar(out,'database') + else + let dict['srvname'] = s:extractdbvar(out,'host') + endif + let dict['host'] = s:extractdbvar(out,'host') + let dict['port'] = s:extractdbvar(out,'port') + let dict['dsnname'] = s:extractdbvar(out,'dsn') + if dict['host'] =~? '^\cDBI:' + if dict['host'] =~? '\c\<Trusted[_ ]Connection\s*=\s*yes\>' + let dict['integratedlogin'] = 1 + endif + let dict['host'] = matchstr(dict['host'],'\c\<\%(Server\|Data Source\)\s*=\s*\zs[^;]*') + endif + call filter(dict,'v:val != ""') + endif + let cache[a:environment] = dict + endif + return cache[a:environment] +endfunction + +function! s:BufDatabase(...) + if exists("s:lock_database") || !exists('g:loaded_dbext') || !exists('b:rails_root') + return + endif + let self = rails#app() + let s:lock_database = 1 + if (a:0 && a:1 > 1) + call self.cache.clear('dbext_settings') + endif + if (a:0 > 1 && a:2 != '') + let env = a:2 + else + let env = s:environment() + endif + if (!self.cache.has('dbext_settings') || !has_key(self.cache.get('dbext_settings'),env)) && (a:0 ? a:1 : 0) <= 0 + unlet! s:lock_database + return + endif + let dict = self.dbext_settings(env) + for key in ['type', 'profile', 'bin', 'user', 'passwd', 'dbname', 'srvname', 'host', 'port', 'dsnname', 'extra', 'integratedlogin'] + let b:dbext_{key} = get(dict,key,'') + endfor + if b:dbext_type == 'PGSQL' + let $PGPASSWORD = b:dbext_passwd + elseif exists('$PGPASSWORD') + let $PGPASSWORD = '' + endif + unlet! s:lock_database +endfunction + +call s:add_methods('app', ['dbext_settings']) + +" }}}1 +" Abbreviations {{{1 + +function! s:selectiveexpand(pat,good,default,...) + if a:0 > 0 + let nd = a:1 + else + let nd = "" + endif + let c = nr2char(getchar(0)) + let good = a:good + if c == "" " ^] + return s:sub(good.(a:0 ? " ".a:1 : ''),'\s+$','') + elseif c == "\t" + return good.(a:0 ? " ".a:1 : '') + elseif c =~ a:pat + return good.c.(a:0 ? a:1 : '') + else + return a:default.c + endif +endfunction + +function! s:TheCWord() + let l = s:linepeak() + if l =~ '\<\%(find\|first\|last\|all\|paginate\)\>' + return s:selectiveexpand('..',':conditions => ',':c') + elseif l =~ '\<render\s*(\=\s*:partial\s*=>\s*' + return s:selectiveexpand('..',':collection => ',':c') + elseif l =~ '\<\%(url_for\|link_to\|form_tag\)\>' || l =~ ':url\s*=>\s*{\s*' + return s:selectiveexpand('..',':controller => ',':c') + else + return s:selectiveexpand('..',':conditions => ',':c') + endif +endfunction + +function! s:AddSelectiveExpand(abbr,pat,expn,...) + let expn = s:gsub(s:gsub(a:expn ,'[\"|]','\\&'),'\<','\\<Lt>') + let expn2 = s:gsub(s:gsub(a:0 ? a:1 : '','[\"|]','\\&'),'\<','\\<Lt>') + if a:0 + exe "inoreabbrev <buffer> <silent> ".a:abbr." <C-R>=<SID>selectiveexpand(".string(a:pat).",\"".expn."\",".string(a:abbr).",\"".expn2."\")<CR>" + else + exe "inoreabbrev <buffer> <silent> ".a:abbr." <C-R>=<SID>selectiveexpand(".string(a:pat).",\"".expn."\",".string(a:abbr).")<CR>" + endif +endfunction + +function! s:AddTabExpand(abbr,expn) + call s:AddSelectiveExpand(a:abbr,'..',a:expn) +endfunction + +function! s:AddBracketExpand(abbr,expn) + call s:AddSelectiveExpand(a:abbr,'[[.]',a:expn) +endfunction + +function! s:AddColonExpand(abbr,expn) + call s:AddSelectiveExpand(a:abbr,'[:.]',a:expn) +endfunction + +function! s:AddParenExpand(abbr,expn,...) + if a:0 + call s:AddSelectiveExpand(a:abbr,'(',a:expn,a:1) + else + call s:AddSelectiveExpand(a:abbr,'(',a:expn,'') + endif +endfunction + +function! s:BufAbbreviations() + command! -buffer -bar -nargs=* -bang Rabbrev :call s:Abbrev(<bang>0,<f-args>) + " Some of these were cherry picked from the TextMate snippets + if g:rails_abbreviations + let buffer = rails#buffer() + " Limit to the right filetypes. But error on the liberal side + if buffer.type_name('controller','view','helper','test-functional','test-integration') + Rabbrev pa[ params + Rabbrev rq[ request + Rabbrev rs[ response + Rabbrev se[ session + Rabbrev hd[ headers + Rabbrev coo[ cookies + Rabbrev fl[ flash + Rabbrev rr( render + Rabbrev ra( render :action\ =>\ + Rabbrev rc( render :controller\ =>\ + Rabbrev rf( render :file\ =>\ + Rabbrev ri( render :inline\ =>\ + Rabbrev rj( render :json\ =>\ + Rabbrev rl( render :layout\ =>\ + Rabbrev rp( render :partial\ =>\ + Rabbrev rt( render :text\ =>\ + Rabbrev rx( render :xml\ =>\ + endif + if buffer.type_name('view','helper') + Rabbrev dotiw distance_of_time_in_words + Rabbrev taiw time_ago_in_words + endif + if buffer.type_name('controller') + Rabbrev re( redirect_to + Rabbrev rea( redirect_to :action\ =>\ + Rabbrev rec( redirect_to :controller\ =>\ + Rabbrev rst( respond_to + endif + if buffer.type_name() ==# 'model' || buffer.type_name('model-arb') + Rabbrev bt( belongs_to + Rabbrev ho( has_one + Rabbrev hm( has_many + Rabbrev habtm( has_and_belongs_to_many + Rabbrev co( composed_of + Rabbrev va( validates_associated + Rabbrev vb( validates_acceptance_of + Rabbrev vc( validates_confirmation_of + Rabbrev ve( validates_exclusion_of + Rabbrev vf( validates_format_of + Rabbrev vi( validates_inclusion_of + Rabbrev vl( validates_length_of + Rabbrev vn( validates_numericality_of + Rabbrev vp( validates_presence_of + Rabbrev vu( validates_uniqueness_of + endif + if buffer.type_name('db-migration','db-schema') + Rabbrev mac( add_column + Rabbrev mrnc( rename_column + Rabbrev mrc( remove_column + Rabbrev mct( create_table + Rabbrev mcht( change_table + Rabbrev mrnt( rename_table + Rabbrev mdt( drop_table + Rabbrev mcc( t.column + endif + if buffer.type_name('test') + Rabbrev ase( assert_equal + Rabbrev asko( assert_kind_of + Rabbrev asnn( assert_not_nil + Rabbrev asr( assert_raise + Rabbrev asre( assert_response + Rabbrev art( assert_redirected_to + endif + Rabbrev :a :action\ =>\ + " hax + Rabbrev :c :co________\ =>\ + inoreabbrev <buffer> <silent> :c <C-R>=<SID>TheCWord()<CR> + Rabbrev :i :id\ =>\ + Rabbrev :o :object\ =>\ + Rabbrev :p :partial\ =>\ + Rabbrev logd( logger.debug + Rabbrev logi( logger.info + Rabbrev logw( logger.warn + Rabbrev loge( logger.error + Rabbrev logf( logger.fatal + Rabbrev fi( find + Rabbrev AR:: ActiveRecord + Rabbrev AV:: ActionView + Rabbrev AC:: ActionController + Rabbrev AD:: ActionDispatch + Rabbrev AS:: ActiveSupport + Rabbrev AM:: ActionMailer + Rabbrev AO:: ActiveModel + Rabbrev AE:: ActiveResource + endif +endfunction + +function! s:Abbrev(bang,...) abort + if !exists("b:rails_abbreviations") + let b:rails_abbreviations = {} + endif + if a:0 > 3 || (a:bang && (a:0 != 1)) + return s:error("Rabbrev: invalid arguments") + endif + if a:0 == 0 + for key in sort(keys(b:rails_abbreviations)) + echo key . join(b:rails_abbreviations[key],"\t") + endfor + return + endif + let lhs = a:1 + let root = s:sub(lhs,'%(::|\(|\[)$','') + if a:bang + if has_key(b:rails_abbreviations,root) + call remove(b:rails_abbreviations,root) + endif + exe "iunabbrev <buffer> ".root + return + endif + if a:0 > 3 || a:0 < 2 + return s:error("Rabbrev: invalid arguments") + endif + let rhs = a:2 + if has_key(b:rails_abbreviations,root) + call remove(b:rails_abbreviations,root) + endif + if lhs =~ '($' + let b:rails_abbreviations[root] = ["(", rhs . (a:0 > 2 ? "\t".a:3 : "")] + if a:0 > 2 + call s:AddParenExpand(root,rhs,a:3) + else + call s:AddParenExpand(root,rhs) + endif + return + endif + if a:0 > 2 + return s:error("Rabbrev: invalid arguments") + endif + if lhs =~ ':$' + call s:AddColonExpand(root,rhs) + elseif lhs =~ '\[$' + call s:AddBracketExpand(root,rhs) + elseif lhs =~ '\w$' + call s:AddTabExpand(lhs,rhs) + else + return s:error("Rabbrev: unimplemented") + endif + let b:rails_abbreviations[root] = [matchstr(lhs,'\W*$'),rhs] +endfunction + +" }}}1 +" Settings {{{1 + +function! s:Set(bang,...) + let c = 1 + let defscope = '' + for arg in a:000 + if arg =~? '^<[abgl]\=>$' + let defscope = (matchstr(arg,'<\zs.*\ze>')) + elseif arg !~ '=' + if defscope != '' && arg !~ '^\w:' + let arg = defscope.':'.opt + endif + let val = s:getopt(arg) + if val == '' && !has_key(s:opts(),arg) + call s:error("No such rails.vim option: ".arg) + else + echo arg."=".val + endif + else + let opt = matchstr(arg,'[^=]*') + let val = s:sub(arg,'^[^=]*\=','') + if defscope != '' && opt !~ '^\w:' + let opt = defscope.':'.opt + endif + call s:setopt(opt,val) + endif + endfor +endfunction + +function! s:getopt(opt,...) + let app = rails#app() + let opt = a:opt + if a:0 + let scope = a:1 + elseif opt =~ '^[abgl]:' + let scope = tolower(matchstr(opt,'^\w')) + let opt = s:sub(opt,'^\w:','') + else + let scope = 'abgl' + endif + let lnum = a:0 > 1 ? a:2 : line('.') + if scope =~ 'l' && &filetype != 'ruby' + let scope = s:sub(scope,'l','b') + endif + if scope =~ 'l' + call s:LocalModelines(lnum) + endif + let var = s:sname().'_'.opt + let lastmethod = s:lastmethod(lnum) + if lastmethod == '' | let lastmethod = ' ' | endif + " Get buffer option + if scope =~ 'l' && exists('b:_'.var) && has_key(b:_{var},lastmethod) + return b:_{var}[lastmethod] + elseif exists('b:'.var) && (scope =~ 'b' || (scope =~ 'l' && lastmethod == ' ')) + return b:{var} + elseif scope =~ 'a' && has_key(app,'options') && has_key(app.options,opt) + return app.options[opt] + elseif scope =~ 'g' && exists("g:".s:sname()."_".opt) + return g:{var} + else + return "" + endif +endfunction + +function! s:setopt(opt,val) + let app = rails#app() + if a:opt =~? '[abgl]:' + let scope = matchstr(a:opt,'^\w') + let opt = s:sub(a:opt,'^\w:','') + else + let scope = '' + let opt = a:opt + endif + let defscope = get(s:opts(),opt,'a') + if scope == '' + let scope = defscope + endif + if &filetype != 'ruby' && (scope ==# 'B' || scope ==# 'l') + let scope = 'b' + endif + let var = s:sname().'_'.opt + if opt =~ '\W' + return s:error("Invalid option ".a:opt) + elseif scope ==# 'B' && defscope == 'l' + if !exists('b:_'.var) | let b:_{var} = {} | endif + let b:_{var}[' '] = a:val + elseif scope =~? 'b' + let b:{var} = a:val + elseif scope =~? 'a' + if !has_key(app,'options') | let app.options = {} | endif + let app.options[opt] = a:val + elseif scope =~? 'g' + let g:{var} = a:val + elseif scope =~? 'l' + if !exists('b:_'.var) | let b:_{var} = {} | endif + let lastmethod = s:lastmethod(lnum) + let b:_{var}[lastmethod == '' ? ' ' : lastmethod] = a:val + else + return s:error("Invalid scope for ".a:opt) + endif +endfunction + +function! s:opts() + return {'alternate': 'b', 'controller': 'b', 'gnu_screen': 'a', 'model': 'b', 'preview': 'l', 'task': 'b', 'related': 'l', 'root_url': 'a'} +endfunction + +function! s:Complete_set(A,L,P) + if a:A =~ '=' + let opt = matchstr(a:A,'[^=]*') + return [opt."=".s:getopt(opt)] + else + let extra = matchstr(a:A,'^[abgl]:') + return filter(sort(map(keys(s:opts()),'extra.v:val')),'s:startswith(v:val,a:A)') + endif + return [] +endfunction + +function! s:BufModelines() + if !g:rails_modelines + return + endif + let lines = getline("$")."\n".getline(line("$")-1)."\n".getline(1)."\n".getline(2)."\n".getline(3)."\n" + let pat = '\s\+\zs.\{-\}\ze\%(\n\|\s\s\|#{\@!\|%>\|-->\|$\)' + let cnt = 1 + let mat = matchstr(lines,'\C\<Rset'.pat) + let matend = matchend(lines,'\C\<Rset'.pat) + while mat != "" && cnt < 10 + let mat = s:sub(mat,'\s+$','') + let mat = s:gsub(mat,'\|','\\|') + if mat != '' + silent! exe "Rset <B> ".mat + endif + let mat = matchstr(lines,'\C\<Rset'.pat,matend) + let matend = matchend(lines,'\C\<Rset'.pat,matend) + let cnt += 1 + endwhile +endfunction + +function! s:LocalModelines(lnum) + if !g:rails_modelines + return + endif + let lbeg = s:lastmethodline(a:lnum) + let lend = s:endof(lbeg) + if lbeg == 0 || lend == 0 + return + endif + let lines = "\n" + let lnum = lbeg + while lnum < lend && lnum < lbeg + 5 + let lines .= getline(lnum) . "\n" + let lnum += 1 + endwhile + let pat = '\s\+\zs.\{-\}\ze\%(\n\|\s\s\|#{\@!\|%>\|-->\|$\)' + let cnt = 1 + let mat = matchstr(lines,'\C\<rset'.pat) + let matend = matchend(lines,'\C\<rset'.pat) + while mat != "" && cnt < 10 + let mat = s:sub(mat,'\s+$','') + let mat = s:gsub(mat,'\|','\\|') + if mat != '' + silent! exe "Rset <l> ".mat + endif + let mat = matchstr(lines,'\C\<rset'.pat,matend) + let matend = matchend(lines,'\C\<rset'.pat,matend) + let cnt += 1 + endwhile +endfunction + +" }}}1 +" Detection {{{1 + +function! s:app_source_callback(file) dict + if self.cache.needs('existence') + call self.cache.set('existence',{}) + endif + let cache = self.cache.get('existence') + if !has_key(cache,a:file) + let cache[a:file] = self.has_file(a:file) + endif + if cache[a:file] + sandbox source `=self.path(a:file)` + endif +endfunction + +call s:add_methods('app',['source_callback']) + +function! RailsBufInit(path) + let firsttime = !(exists("b:rails_root") && b:rails_root == a:path) + let b:rails_root = a:path + if !has_key(s:apps,a:path) + let s:apps[a:path] = deepcopy(s:app_prototype) + let s:apps[a:path].root = a:path + endif + let app = s:apps[a:path] + let buffer = rails#buffer() + " Apparently rails#buffer().calculate_file_type() can be slow if the + " underlying file system is slow (even though it doesn't really do anything + " IO related). This caching is a temporary hack; if it doesn't cause + " problems it should probably be refactored. + let b:rails_cached_file_type = buffer.calculate_file_type() + if g:rails_history_size > 0 + if !exists("g:RAILS_HISTORY") + let g:RAILS_HISTORY = "" + endif + let path = a:path + let g:RAILS_HISTORY = s:scrub(g:RAILS_HISTORY,path) + if has("win32") + let g:RAILS_HISTORY = s:scrub(g:RAILS_HISTORY,s:gsub(path,'\\','/')) + endif + let path = fnamemodify(path,':p:~:h') + let g:RAILS_HISTORY = s:scrub(g:RAILS_HISTORY,path) + if has("win32") + let g:RAILS_HISTORY = s:scrub(g:RAILS_HISTORY,s:gsub(path,'\\','/')) + endif + let g:RAILS_HISTORY = path."\n".g:RAILS_HISTORY + let g:RAILS_HISTORY = s:sub(g:RAILS_HISTORY,'%(.{-}\n){,'.g:rails_history_size.'}\zs.*','') + endif + call app.source_callback("config/syntax.vim") + if expand('%:t') =~ '\.yml\.example$' + setlocal filetype=yaml + elseif expand('%:e') =~ '^\%(rjs\|rxml\|builder\)$' + setlocal filetype=ruby + elseif firsttime + " Activate custom syntax + let &syntax = &syntax + endif + if expand('%:e') == 'log' + nnoremap <buffer> <silent> R :checktime<CR> + nnoremap <buffer> <silent> G :checktime<Bar>$<CR> + nnoremap <buffer> <silent> q :bwipe<CR> + setlocal modifiable filetype=railslog noswapfile autoread foldmethod=syntax + if exists('+concealcursor') + setlocal concealcursor=nc conceallevel=2 + else + silent %s/\%(\e\[[0-9;]*m\|\r$\)//ge + endif + setlocal readonly nomodifiable + $ + endif + call s:BufSettings() + call s:BufCommands() + call s:BufAbbreviations() + " snippetsEmu.vim + if exists('g:loaded_snippet') + silent! runtime! ftplugin/rails_snippets.vim + " filetype snippets need to come last for higher priority + exe "silent! runtime! ftplugin/".&filetype."_snippets.vim" + endif + let t = rails#buffer().type_name() + let t = "-".t + let f = '/'.RailsFilePath() + if f =~ '[ !#$%\,]' + let f = '' + endif + runtime! macros/rails.vim + silent doautocmd User Rails + if t != '-' + exe "silent doautocmd User Rails".s:gsub(t,'-','.') + endif + if f != '' + exe "silent doautocmd User Rails".f + endif + call app.source_callback("config/rails.vim") + call s:BufModelines() + call s:BufMappings() + return b:rails_root +endfunction + +function! s:SetBasePath() + let self = rails#buffer() + if self.app().path() =~ '://' + return + endif + let transformed_path = s:pathsplit(s:pathjoin([self.app().path()]))[0] + let add_dot = self.getvar('&path') =~# '^\.\%(,\|$\)' + let old_path = s:pathsplit(s:sub(self.getvar('&path'),'^\.%(,|$)','')) + call filter(old_path,'!s:startswith(v:val,transformed_path)') + + let path = ['app', 'app/models', 'app/controllers', 'app/helpers', 'config', 'lib', 'app/views'] + if self.controller_name() != '' + let path += ['app/views/'.self.controller_name(), 'public'] + endif + if self.app().has('test') + let path += ['test', 'test/unit', 'test/functional', 'test/integration'] + endif + if self.app().has('spec') + let path += ['spec', 'spec/models', 'spec/controllers', 'spec/helpers', 'spec/views', 'spec/lib', 'spec/requests', 'spec/integration'] + endif + let path += ['app/*', 'vendor', 'vendor/plugins/*/lib', 'vendor/plugins/*/test', 'vendor/rails/*/lib', 'vendor/rails/*/test'] + call map(path,'self.app().path(v:val)') + call self.setvar('&path',(add_dot ? '.,' : '').s:pathjoin([self.app().path()],path,old_path)) +endfunction + +function! s:BufSettings() + if !exists('b:rails_root') + return '' + endif + let self = rails#buffer() + call s:SetBasePath() + let rp = s:gsub(self.app().path(),'[ ,]','\\&') + if stridx(&tags,rp.'/tmp/tags') == -1 + let &l:tags = rp . '/tmp/tags,' . &tags . ',' . rp . '/tags' + endif + if has("gui_win32") || has("gui_running") + let code = '*.rb;*.rake;Rakefile' + let templates = '*.'.join(s:view_types,';*.') + let fixtures = '*.yml;*.csv' + let statics = '*.html;*.css;*.js;*.xml;*.xsd;*.sql;.htaccess;README;README_FOR_APP' + let b:browsefilter = "" + \."All Rails Files\t".code.';'.templates.';'.fixtures.';'.statics."\n" + \."Source Code (*.rb, *.rake)\t".code."\n" + \."Templates (*.rhtml, *.rxml, *.rjs)\t".templates."\n" + \."Fixtures (*.yml, *.csv)\t".fixtures."\n" + \."Static Files (*.html, *.css, *.js)\t".statics."\n" + \."All Files (*.*)\t*.*\n" + endif + call self.setvar('&includeexpr','RailsIncludeexpr()') + call self.setvar('&suffixesadd', ".rb,.".join(s:view_types,',.')) + let ft = self.getvar('&filetype') + if ft =~ '^\%(e\=ruby\|[yh]aml\|coffee\|css\|s[ac]ss\|lesscss\)$' + call self.setvar('&shiftwidth',2) + call self.setvar('&softtabstop',2) + call self.setvar('&expandtab',1) + if exists('+completefunc') && self.getvar('&completefunc') == '' + call self.setvar('&completefunc','syntaxcomplete#Complete') + endif + endif + if ft == 'ruby' + call self.setvar('&define',self.define_pattern()) + " This really belongs in after/ftplugin/ruby.vim but we'll be nice + if exists('g:loaded_surround') && self.getvar('surround_101') == '' + call self.setvar('surround_5', "\r\nend") + call self.setvar('surround_69', "\1expr: \1\rend") + call self.setvar('surround_101', "\r\nend") + endif + elseif ft == 'yaml' || fnamemodify(self.name(),':e') == 'yml' + call self.setvar('&define',self.define_pattern()) + elseif ft =~# '^eruby\>' + if exists("g:loaded_allml") + call self.setvar('allml_stylesheet_link_tag', "<%= stylesheet_link_tag '\r' %>") + call self.setvar('allml_javascript_include_tag', "<%= javascript_include_tag '\r' %>") + call self.setvar('allml_doctype_index', 10) + endif + if exists("g:loaded_ragtag") + call self.setvar('ragtag_stylesheet_link_tag', "<%= stylesheet_link_tag '\r' %>") + call self.setvar('ragtag_javascript_include_tag', "<%= javascript_include_tag '\r' %>") + call self.setvar('ragtag_doctype_index', 10) + endif + elseif ft == 'haml' + if exists("g:loaded_allml") + call self.setvar('allml_stylesheet_link_tag', "= stylesheet_link_tag '\r'") + call self.setvar('allml_javascript_include_tag', "= javascript_include_tag '\r'") + call self.setvar('allml_doctype_index', 10) + endif + if exists("g:loaded_ragtag") + call self.setvar('ragtag_stylesheet_link_tag', "= stylesheet_link_tag '\r'") + call self.setvar('ragtag_javascript_include_tag', "= javascript_include_tag '\r'") + call self.setvar('ragtag_doctype_index', 10) + endif + endif + if ft =~# '^eruby\>' || ft ==# 'yaml' + " surround.vim + if exists("g:loaded_surround") + " The idea behind the || part here is that one can normally define the + " surrounding to omit the hyphen (since standard ERuby does not use it) + " but have it added in Rails ERuby files. Unfortunately, this makes it + " difficult if you really don't want a hyphen in Rails ERuby files. If + " this is your desire, you will need to accomplish it via a rails.vim + " autocommand. + if self.getvar('surround_45') == '' || self.getvar('surround_45') == "<% \r %>" " - + call self.setvar('surround_45', "<% \r -%>") + endif + if self.getvar('surround_61') == '' " = + call self.setvar('surround_61', "<%= \r %>") + endif + if self.getvar("surround_35") == '' " # + call self.setvar('surround_35', "<%# \r %>") + endif + if self.getvar('surround_101') == '' || self.getvar('surround_101')== "<% \r %>\n<% end %>" "e + call self.setvar('surround_5', "<% \r -%>\n<% end -%>") + call self.setvar('surround_69', "<% \1expr: \1 -%>\r<% end -%>") + call self.setvar('surround_101', "<% \r -%>\n<% end -%>") + endif + endif + endif +endfunction + +" }}}1 +" Autocommands {{{1 + +augroup railsPluginAuto + autocmd! + autocmd User BufEnterRails call s:RefreshBuffer() + autocmd User BufEnterRails call s:resetomnicomplete() + autocmd User BufEnterRails call s:BufDatabase(-1) + autocmd User dbextPreConnection call s:BufDatabase(1) + autocmd BufWritePost */config/database.yml call rails#cache_clear("dbext_settings") + autocmd BufWritePost */test/test_helper.rb call rails#cache_clear("user_assertions") + autocmd BufWritePost */config/routes.rb call rails#cache_clear("named_routes") + autocmd BufWritePost */config/environment.rb call rails#cache_clear("default_locale") + autocmd BufWritePost */config/environments/*.rb call rails#cache_clear("environments") + autocmd BufWritePost */tasks/**.rake call rails#cache_clear("rake_tasks") + autocmd BufWritePost */generators/** call rails#cache_clear("generators") + autocmd FileType * if exists("b:rails_root") | call s:BufSettings() | endif + autocmd Syntax ruby,eruby,yaml,haml,javascript,coffee,railslog if exists("b:rails_root") | call s:BufSyntax() | endif + autocmd QuickFixCmdPre *make* call s:push_chdir() + autocmd QuickFixCmdPost *make* call s:pop_command() +augroup END + +" }}}1 +" Initialization {{{1 + +map <SID>xx <SID>xx +let s:sid = s:sub(maparg("<SID>xx"),'xx$','') +unmap <SID>xx +let s:file = expand('<sfile>:p') + +if !exists('s:apps') + let s:apps = {} +endif + +" }}}1 + +let &cpo = s:cpo_save + +" vim:set sw=2 sts=2: diff --git a/vim/autoload/snipMate.vim b/vim/autoload/snipMate.vim new file mode 100644 index 0000000..0ad5a58 --- /dev/null +++ b/vim/autoload/snipMate.vim @@ -0,0 +1,435 @@ +fun! Filename(...) + let filename = expand('%:t:r') + if filename == '' | return a:0 == 2 ? a:2 : '' | endif + return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g') +endf + +fun s:RemoveSnippet() + unl! g:snipPos s:curPos s:snipLen s:endCol s:endLine s:prevLen + \ s:lastBuf s:oldWord + if exists('s:update') + unl s:startCol s:origWordLen s:update + if exists('s:oldVars') | unl s:oldVars s:oldEndCol | endif + endif + aug! snipMateAutocmds +endf + +fun snipMate#expandSnip(snip, col) + let lnum = line('.') | let col = a:col + + let snippet = s:ProcessSnippet(a:snip) + " Avoid error if eval evaluates to nothing + if snippet == '' | return '' | endif + + " Expand snippet onto current position with the tab stops removed + let snipLines = split(substitute(snippet, '$\d\+\|${\d\+.\{-}}', '', 'g'), "\n", 1) + + let line = getline(lnum) + let afterCursor = strpart(line, col - 1) + " Keep text after the cursor + if afterCursor != "\t" && afterCursor != ' ' + let line = strpart(line, 0, col - 1) + let snipLines[-1] .= afterCursor + else + let afterCursor = '' + " For some reason the cursor needs to move one right after this + if line != '' && col == 1 && &ve != 'all' && &ve != 'onemore' + let col += 1 + endif + endif + + call setline(lnum, line.snipLines[0]) + + " Autoindent snippet according to previous indentation + let indent = matchend(line, '^.\{-}\ze\(\S\|$\)') + 1 + call append(lnum, map(snipLines[1:], "'".strpart(line, 0, indent - 1)."'.v:val")) + + " Open any folds snippet expands into + if &fen | sil! exe lnum.','.(lnum + len(snipLines) - 1).'foldopen' | endif + + let [g:snipPos, s:snipLen] = s:BuildTabStops(snippet, lnum, col - indent, indent) + + if s:snipLen + aug snipMateAutocmds + au CursorMovedI * call s:UpdateChangedSnip(0) + au InsertEnter * call s:UpdateChangedSnip(1) + aug END + let s:lastBuf = bufnr(0) " Only expand snippet while in current buffer + let s:curPos = 0 + let s:endCol = g:snipPos[s:curPos][1] + let s:endLine = g:snipPos[s:curPos][0] + + call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1]) + let s:prevLen = [line('$'), col('$')] + if g:snipPos[s:curPos][2] != -1 | return s:SelectWord() | endif + else + unl g:snipPos s:snipLen + " Place cursor at end of snippet if no tab stop is given + let newlines = len(snipLines) - 1 + call cursor(lnum + newlines, indent + len(snipLines[-1]) - len(afterCursor) + \ + (newlines ? 0: col - 1)) + endif + return '' +endf + +" Prepare snippet to be processed by s:BuildTabStops +fun s:ProcessSnippet(snip) + let snippet = a:snip + " Evaluate eval (`...`) expressions. + " Backquotes prefixed with a backslash "\" are ignored. + " Using a loop here instead of a regex fixes a bug with nested "\=". + if stridx(snippet, '`') != -1 + while match(snippet, '\(^\|[^\\]\)`.\{-}[^\\]`') != -1 + let snippet = substitute(snippet, '\(^\|[^\\]\)\zs`.\{-}[^\\]`\ze', + \ substitute(eval(matchstr(snippet, '\(^\|[^\\]\)`\zs.\{-}[^\\]\ze`')), + \ "\n\\%$", '', ''), '') + endw + let snippet = substitute(snippet, "\r", "\n", 'g') + let snippet = substitute(snippet, '\\`', '`', 'g') + endif + + " Place all text after a colon in a tab stop after the tab stop + " (e.g. "${#:foo}" becomes "${:foo}foo"). + " This helps tell the position of the tab stops later. + let snippet = substitute(snippet, '${\d\+:\(.\{-}\)}', '&\1', 'g') + + " Update the a:snip so that all the $# become the text after + " the colon in their associated ${#}. + " (e.g. "${1:foo}" turns all "$1"'s into "foo") + let i = 1 + while stridx(snippet, '${'.i) != -1 + let s = matchstr(snippet, '${'.i.':\zs.\{-}\ze}') + if s != '' + let snippet = substitute(snippet, '$'.i, s.'&', 'g') + endif + let i += 1 + endw + + if &et " Expand tabs to spaces if 'expandtab' is set. + return substitute(snippet, '\t', repeat(' ', &sts ? &sts : &sw), 'g') + endif + return snippet +endf + +" Counts occurences of haystack in needle +fun s:Count(haystack, needle) + let counter = 0 + let index = stridx(a:haystack, a:needle) + while index != -1 + let index = stridx(a:haystack, a:needle, index+1) + let counter += 1 + endw + return counter +endf + +" Builds a list of a list of each tab stop in the snippet containing: +" 1.) The tab stop's line number. +" 2.) The tab stop's column number +" (by getting the length of the string between the last "\n" and the +" tab stop). +" 3.) The length of the text after the colon for the current tab stop +" (e.g. "${1:foo}" would return 3). If there is no text, -1 is returned. +" 4.) If the "${#:}" construct is given, another list containing all +" the matches of "$#", to be replaced with the placeholder. This list is +" composed the same way as the parent; the first item is the line number, +" and the second is the column. +fun s:BuildTabStops(snip, lnum, col, indent) + let snipPos = [] + let i = 1 + let withoutVars = substitute(a:snip, '$\d\+', '', 'g') + while stridx(a:snip, '${'.i) != -1 + let beforeTabStop = matchstr(withoutVars, '^.*\ze${'.i.'\D') + let withoutOthers = substitute(withoutVars, '${\('.i.'\D\)\@!\d\+.\{-}}', '', 'g') + + let j = i - 1 + call add(snipPos, [0, 0, -1]) + let snipPos[j][0] = a:lnum + s:Count(beforeTabStop, "\n") + let snipPos[j][1] = a:indent + len(matchstr(withoutOthers, '.*\(\n\|^\)\zs.*\ze${'.i.'\D')) + if snipPos[j][0] == a:lnum | let snipPos[j][1] += a:col | endif + + " Get all $# matches in another list, if ${#:name} is given + if stridx(withoutVars, '${'.i.':') != -1 + let snipPos[j][2] = len(matchstr(withoutVars, '${'.i.':\zs.\{-}\ze}')) + let dots = repeat('.', snipPos[j][2]) + call add(snipPos[j], []) + let withoutOthers = substitute(a:snip, '${\d\+.\{-}}\|$'.i.'\@!\d\+', '', 'g') + while match(withoutOthers, '$'.i.'\(\D\|$\)') != -1 + let beforeMark = matchstr(withoutOthers, '^.\{-}\ze'.dots.'$'.i.'\(\D\|$\)') + call add(snipPos[j][3], [0, 0]) + let snipPos[j][3][-1][0] = a:lnum + s:Count(beforeMark, "\n") + let snipPos[j][3][-1][1] = a:indent + (snipPos[j][3][-1][0] > a:lnum + \ ? len(matchstr(beforeMark, '.*\n\zs.*')) + \ : a:col + len(beforeMark)) + let withoutOthers = substitute(withoutOthers, '$'.i.'\ze\(\D\|$\)', '', '') + endw + endif + let i += 1 + endw + return [snipPos, i - 1] +endf + +fun snipMate#jumpTabStop(backwards) + let leftPlaceholder = exists('s:origWordLen') + \ && s:origWordLen != g:snipPos[s:curPos][2] + if leftPlaceholder && exists('s:oldEndCol') + let startPlaceholder = s:oldEndCol + 1 + endif + + if exists('s:update') + call s:UpdatePlaceholderTabStops() + else + call s:UpdateTabStops() + endif + + " Don't reselect placeholder if it has been modified + if leftPlaceholder && g:snipPos[s:curPos][2] != -1 + if exists('startPlaceholder') + let g:snipPos[s:curPos][1] = startPlaceholder + else + let g:snipPos[s:curPos][1] = col('.') + let g:snipPos[s:curPos][2] = 0 + endif + endif + + let s:curPos += a:backwards ? -1 : 1 + " Loop over the snippet when going backwards from the beginning + if s:curPos < 0 | let s:curPos = s:snipLen - 1 | endif + + if s:curPos == s:snipLen + let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2] + call s:RemoveSnippet() + return sMode ? "\<tab>" : TriggerSnippet() + endif + + call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1]) + + let s:endLine = g:snipPos[s:curPos][0] + let s:endCol = g:snipPos[s:curPos][1] + let s:prevLen = [line('$'), col('$')] + + return g:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord() +endf + +fun s:UpdatePlaceholderTabStops() + let changeLen = s:origWordLen - g:snipPos[s:curPos][2] + unl s:startCol s:origWordLen s:update + if !exists('s:oldVars') | return | endif + " Update tab stops in snippet if text has been added via "$#" + " (e.g., in "${1:foo}bar$1${2}"). + if changeLen != 0 + let curLine = line('.') + + for pos in g:snipPos + if pos == g:snipPos[s:curPos] | continue | endif + let changed = pos[0] == curLine && pos[1] > s:oldEndCol + let changedVars = 0 + let endPlaceholder = pos[2] - 1 + pos[1] + " Subtract changeLen from each tab stop that was after any of + " the current tab stop's placeholders. + for [lnum, col] in s:oldVars + if lnum > pos[0] | break | endif + if pos[0] == lnum + if pos[1] > col || (pos[2] == -1 && pos[1] == col) + let changed += 1 + elseif col < endPlaceholder + let changedVars += 1 + endif + endif + endfor + let pos[1] -= changeLen * changed + let pos[2] -= changeLen * changedVars " Parse variables within placeholders + " e.g., "${1:foo} ${2:$1bar}" + + if pos[2] == -1 | continue | endif + " Do the same to any placeholders in the other tab stops. + for nPos in pos[3] + let changed = nPos[0] == curLine && nPos[1] > s:oldEndCol + for [lnum, col] in s:oldVars + if lnum > nPos[0] | break | endif + if nPos[0] == lnum && nPos[1] > col + let changed += 1 + endif + endfor + let nPos[1] -= changeLen * changed + endfor + endfor + endif + unl s:endCol s:oldVars s:oldEndCol +endf + +fun s:UpdateTabStops() + let changeLine = s:endLine - g:snipPos[s:curPos][0] + let changeCol = s:endCol - g:snipPos[s:curPos][1] + if exists('s:origWordLen') + let changeCol -= s:origWordLen + unl s:origWordLen + endif + let lnum = g:snipPos[s:curPos][0] + let col = g:snipPos[s:curPos][1] + " Update the line number of all proceeding tab stops if <cr> has + " been inserted. + if changeLine != 0 + let changeLine -= 1 + for pos in g:snipPos + if pos[0] >= lnum + if pos[0] == lnum | let pos[1] += changeCol | endif + let pos[0] += changeLine + endif + if pos[2] == -1 | continue | endif + for nPos in pos[3] + if nPos[0] >= lnum + if nPos[0] == lnum | let nPos[1] += changeCol | endif + let nPos[0] += changeLine + endif + endfor + endfor + elseif changeCol != 0 + " Update the column of all proceeding tab stops if text has + " been inserted/deleted in the current line. + for pos in g:snipPos + if pos[1] >= col && pos[0] == lnum + let pos[1] += changeCol + endif + if pos[2] == -1 | continue | endif + for nPos in pos[3] + if nPos[0] > lnum | break | endif + if nPos[0] == lnum && nPos[1] >= col + let nPos[1] += changeCol + endif + endfor + endfor + endif +endf + +fun s:SelectWord() + let s:origWordLen = g:snipPos[s:curPos][2] + let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1, + \ s:origWordLen) + let s:prevLen[1] -= s:origWordLen + if !empty(g:snipPos[s:curPos][3]) + let s:update = 1 + let s:endCol = -1 + let s:startCol = g:snipPos[s:curPos][1] - 1 + endif + if !s:origWordLen | return '' | endif + let l = col('.') != 1 ? 'l' : '' + if &sel == 'exclusive' + return "\<esc>".l.'v'.s:origWordLen."l\<c-g>" + endif + return s:origWordLen == 1 ? "\<esc>".l.'gh' + \ : "\<esc>".l.'v'.(s:origWordLen - 1)."l\<c-g>" +endf + +" This updates the snippet as you type when text needs to be inserted +" into multiple places (e.g. in "${1:default text}foo$1bar$1", +" "default text" would be highlighted, and if the user types something, +" UpdateChangedSnip() would be called so that the text after "foo" & "bar" +" are updated accordingly) +" +" It also automatically quits the snippet if the cursor is moved out of it +" while in insert mode. +fun s:UpdateChangedSnip(entering) + if exists('g:snipPos') && bufnr(0) != s:lastBuf + call s:RemoveSnippet() + elseif exists('s:update') " If modifying a placeholder + if !exists('s:oldVars') && s:curPos + 1 < s:snipLen + " Save the old snippet & word length before it's updated + " s:startCol must be saved too, in case text is added + " before the snippet (e.g. in "foo$1${2}bar${1:foo}"). + let s:oldEndCol = s:startCol + let s:oldVars = deepcopy(g:snipPos[s:curPos][3]) + endif + let col = col('.') - 1 + + if s:endCol != -1 + let changeLen = col('$') - s:prevLen[1] + let s:endCol += changeLen + else " When being updated the first time, after leaving select mode + if a:entering | return | endif + let s:endCol = col - 1 + endif + + " If the cursor moves outside the snippet, quit it + if line('.') != g:snipPos[s:curPos][0] || col < s:startCol || + \ col - 1 > s:endCol + unl! s:startCol s:origWordLen s:oldVars s:update + return s:RemoveSnippet() + endif + + call s:UpdateVars() + let s:prevLen[1] = col('$') + elseif exists('g:snipPos') + if !a:entering && g:snipPos[s:curPos][2] != -1 + let g:snipPos[s:curPos][2] = -2 + endif + + let col = col('.') + let lnum = line('.') + let changeLine = line('$') - s:prevLen[0] + + if lnum == s:endLine + let s:endCol += col('$') - s:prevLen[1] + let s:prevLen = [line('$'), col('$')] + endif + if changeLine != 0 + let s:endLine += changeLine + let s:endCol = col + endif + + " Delete snippet if cursor moves out of it in insert mode + if (lnum == s:endLine && (col > s:endCol || col < g:snipPos[s:curPos][1])) + \ || lnum > s:endLine || lnum < g:snipPos[s:curPos][0] + call s:RemoveSnippet() + endif + endif +endf + +" This updates the variables in a snippet when a placeholder has been edited. +" (e.g., each "$1" in "${1:foo} $1bar $1bar") +fun s:UpdateVars() + let newWordLen = s:endCol - s:startCol + 1 + let newWord = strpart(getline('.'), s:startCol, newWordLen) + if newWord == s:oldWord || empty(g:snipPos[s:curPos][3]) + return + endif + + let changeLen = g:snipPos[s:curPos][2] - newWordLen + let curLine = line('.') + let startCol = col('.') + let oldStartSnip = s:startCol + let updateTabStops = changeLen != 0 + let i = 0 + + for [lnum, col] in g:snipPos[s:curPos][3] + if updateTabStops + let start = s:startCol + if lnum == curLine && col <= start + let s:startCol -= changeLen + let s:endCol -= changeLen + endif + for nPos in g:snipPos[s:curPos][3][(i):] + " This list is in ascending order, so quit if we've gone too far. + if nPos[0] > lnum | break | endif + if nPos[0] == lnum && nPos[1] > col + let nPos[1] -= changeLen + endif + endfor + if lnum == curLine && col > start + let col -= changeLen + let g:snipPos[s:curPos][3][i][1] = col + endif + let i += 1 + endif + + " "Very nomagic" is used here to allow special characters. + call setline(lnum, substitute(getline(lnum), '\%'.col.'c\V'. + \ escape(s:oldWord, '\'), escape(newWord, '\&'), '')) + endfor + if oldStartSnip != s:startCol + call cursor(0, startCol + s:startCol - oldStartSnip) + endif + + let s:oldWord = newWord + let g:snipPos[s:curPos][2] = newWordLen +endf +" vim:noet:sw=4:ts=4:ft=vim diff --git a/vim/autoload/syntastic.vim b/vim/autoload/syntastic.vim new file mode 100644 index 0000000..a477736 --- /dev/null +++ b/vim/autoload/syntastic.vim @@ -0,0 +1,24 @@ + +function! syntastic#ErrorBalloonExpr() + if !exists('b:syntastic_balloons') | return '' | endif + return get(b:syntastic_balloons, v:beval_lnum, '') +endfunction + +function! syntastic#HighlightErrors(errors, termfunc, ...) + call clearmatches() + let forcecb = a:0 && a:1 + for item in a:errors + let group = item['type'] == 'E' ? 'SpellBad' : 'SpellCap' + if item['col'] && !forcecb + let lastcol = col([item['lnum'], '$']) + let lcol = min([lastcol, item['col']]) + call matchadd(group, '\%'.item['lnum'].'l\%'.lcol.'c') + else + let term = a:termfunc(item) + if len(term) > 0 + call matchadd(group, '\%' . item['lnum'] . 'l' . term) + endif + endif + endfor +endfunction + diff --git a/vim/autoload/syntastic/c.vim b/vim/autoload/syntastic/c.vim new file mode 100644 index 0000000..c7232df --- /dev/null +++ b/vim/autoload/syntastic/c.vim @@ -0,0 +1,171 @@ +if exists("g:loaded_syntastic_c_autoload") + finish +endif +let g:loaded_syntastic_c_autoload = 1 + +let s:save_cpo = &cpo +set cpo&vim + +" initialize c/cpp syntax checker handlers +function! s:Init() + let s:handlers = [] + let s:cflags = {} + + call s:RegHandler('gtk', 'syntastic#c#CheckPKG', + \ ['gtk', 'gtk+-2.0', 'gtk+', 'glib-2.0', 'glib']) + call s:RegHandler('glib', 'syntastic#c#CheckPKG', + \ ['glib', 'glib-2.0', 'glib']) + call s:RegHandler('glade', 'syntastic#c#CheckPKG', + \ ['glade', 'libglade-2.0', 'libglade']) + call s:RegHandler('libsoup', 'syntastic#c#CheckPKG', + \ ['libsoup', 'libsoup-2.4', 'libsoup-2.2']) + call s:RegHandler('webkit', 'syntastic#c#CheckPKG', + \ ['webkit', 'webkit-1.0']) + call s:RegHandler('cairo', 'syntastic#c#CheckPKG', + \ ['cairo', 'cairo']) + call s:RegHandler('pango', 'syntastic#c#CheckPKG', + \ ['pango', 'pango']) + call s:RegHandler('libxml', 'syntastic#c#CheckPKG', + \ ['libxml', 'libxml-2.0', 'libxml']) + call s:RegHandler('freetype', 'syntastic#c#CheckPKG', + \ ['freetype', 'freetype2', 'freetype']) + call s:RegHandler('SDL', 'syntastic#c#CheckPKG', + \ ['sdl', 'sdl']) + call s:RegHandler('opengl', 'syntastic#c#CheckPKG', + \ ['opengl', 'gl']) + call s:RegHandler('ruby', 'syntastic#c#CheckRuby', []) + call s:RegHandler('Python\.h', 'syntastic#c#CheckPython', []) + call s:RegHandler('php\.h', 'syntastic#c#CheckPhp', []) +endfunction + +" search the first 100 lines for include statements that are +" given in the handlers dictionary +function! syntastic#c#SearchHeaders() + let includes = '' + let files = [] + let found = [] + let lines = filter(getline(1, 100), 'v:val =~# "#\s*include"') + + " search current buffer + for line in lines + let file = matchstr(line, '"\zs\S\+\ze"') + if file != '' + call add(files, file) + continue + endif + for handler in s:handlers + if line =~# handler["regex"] + let includes .= call(handler["func"], handler["args"]) + call add(found, handler["regex"]) + break + endif + endfor + endfor + + " search included headers + for hfile in files + if hfile != '' + let filename = expand('%:p:h') . ((has('win32') || has('win64')) ? + \ '\' : '/') . hfile + try + let lines = readfile(filename, '', 100) + catch /E484/ + continue + endtry + let lines = filter(lines, 'v:val =~# "#\s*include"') + for handler in s:handlers + if index(found, handler["regex"]) != -1 + continue + endif + for line in lines + if line =~# handler["regex"] + let includes .= call(handler["func"], handler["args"]) + call add(found, handler["regex"]) + break + endif + endfor + endfor + endif + endfor + + return includes +endfunction + +" try to find library with 'pkg-config' +" search possible libraries from first to last given +" argument until one is found +function! syntastic#c#CheckPKG(name, ...) + if executable('pkg-config') + if !has_key(s:cflags, a:name) + for i in range(a:0) + let l:cflags = system('pkg-config --cflags '.a:000[i]) + " since we cannot necessarily trust the pkg-config exit code + " we have to check for an error output as well + if v:shell_error == 0 && l:cflags !~? 'not found' + let l:cflags = ' '.substitute(l:cflags, "\n", '', '') + let s:cflags[a:name] = l:cflags + return l:cflags + endif + endfor + else + return s:cflags[a:name] + endif + endif + return '' +endfunction + +" try to find PHP includes with 'php-config' +function! syntastic#c#CheckPhp() + if executable('php-config') + if !exists('s:php_flags') + let s:php_flags = system('php-config --includes') + let s:php_flags = ' ' . substitute(s:php_flags, "\n", '', '') + endif + return s:php_flags + endif + return '' +endfunction + +" try to find the ruby headers with 'rbconfig' +function! syntastic#c#CheckRuby() + if executable('ruby') + if !exists('s:ruby_flags') + let s:ruby_flags = system('ruby -r rbconfig -e ' + \ . '''puts Config::CONFIG["archdir"]''') + let s:ruby_flags = substitute(s:ruby_flags, "\n", '', '') + let s:ruby_flags = ' -I' . s:ruby_flags + endif + return s:ruby_flags + endif + return '' +endfunction + +" try to find the python headers with distutils +function! syntastic#c#CheckPython() + if executable('python') + if !exists('s:python_flags') + let s:python_flags = system('python -c ''from distutils import ' + \ . 'sysconfig; import sys; sys.stdout.write(sysconfig.get_python_inc())''') + let s:python_flags = substitute(s:python_flags, "\n", '', '') + let s:python_flags = ' -I' . s:python_flags + endif + return s:python_flags + endif + return '' +endfunction + +" return a handler dictionary object +function! s:RegHandler(regex, function, args) + let handler = {} + let handler["regex"] = a:regex + let handler["func"] = function(a:function) + let handler["args"] = a:args + call add(s:handlers, handler) +endfunction + +call s:Init() + +let &cpo = s:save_cpo +unlet s:save_cpo + +" vim: set et sts=4 sw=4: diff --git a/vim/autoload/tagbar.vim b/vim/autoload/tagbar.vim new file mode 100644 index 0000000..48ad320 --- /dev/null +++ b/vim/autoload/tagbar.vim @@ -0,0 +1,3099 @@ +" ============================================================================ +" File: tagbar.vim +" Description: List the current file's tags in a sidebar, ordered by class etc +" Author: Jan Larres <jan@majutsushi.net> +" Licence: Vim licence +" Website: http://majutsushi.github.com/tagbar/ +" Version: 2.3 +" Note: This plugin was heavily inspired by the 'Taglist' plugin by +" Yegappan Lakshmanan and uses a small amount of code from it. +" +" Original taglist copyright notice: +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" taglist.vim is provided *as is* and comes with no warranty of +" any kind, either expressed or implied. In no event will the +" copyright holder be liable for any damamges resulting from the +" use of this software. +" ============================================================================ + +scriptencoding utf-8 + +" Initialization {{{1 + +" Basic init {{{2 + +if !exists('g:tagbar_ctags_bin') + if executable('ctags-exuberant') + let g:tagbar_ctags_bin = 'ctags-exuberant' + elseif executable('exuberant-ctags') + let g:tagbar_ctags_bin = 'exuberant-ctags' + elseif executable('exctags') + let g:tagbar_ctags_bin = 'exctags' + elseif has('macunix') && executable('/usr/local/bin/ctags') + " Homebrew default location + let g:tagbar_ctags_bin = '/usr/local/bin/ctags' + elseif has('macunix') && executable('/opt/local/bin/ctags') + " Macports default location + let g:tagbar_ctags_bin = '/opt/local/bin/ctags' + elseif executable('ctags') + let g:tagbar_ctags_bin = 'ctags' + elseif executable('ctags.exe') + let g:tagbar_ctags_bin = 'ctags.exe' + elseif executable('tags') + let g:tagbar_ctags_bin = 'tags' + else + echomsg 'Tagbar: Exuberant ctags not found, skipping plugin' + finish + endif +else + " reset 'wildignore' temporarily in case *.exe is included in it + let wildignore_save = &wildignore + set wildignore& + + let g:tagbar_ctags_bin = expand(g:tagbar_ctags_bin) + + let &wildignore = wildignore_save + + if !executable(g:tagbar_ctags_bin) + echomsg 'Tagbar: Exuberant ctags not found in specified place,' + \ 'skipping plugin' + finish + endif +endif + +redir => s:ftype_out +silent filetype +redir END +if s:ftype_out !~# 'detection:ON' + echomsg 'Tagbar: Filetype detection is turned off, skipping plugin' + unlet s:ftype_out + finish +endif +unlet s:ftype_out + +let s:icon_closed = g:tagbar_iconchars[0] +let s:icon_open = g:tagbar_iconchars[1] + +let s:type_init_done = 0 +let s:autocommands_done = 0 +let s:checked_ctags = 0 +let s:window_expanded = 0 + +let s:access_symbols = { + \ 'public' : '+', + \ 'protected' : '#', + \ 'private' : '-' +\ } + +let g:loaded_tagbar = 1 + +let s:last_highlight_tline = 0 +let s:debug = 0 +let s:debug_file = '' + +" s:Init() {{{2 +function! s:Init() + if !s:type_init_done + call s:InitTypes() + endif + + if !s:checked_ctags + if !s:CheckForExCtags() + return + endif + endif +endfunction + +" s:InitTypes() {{{2 +function! s:InitTypes() + call s:LogDebugMessage('Initializing types') + + let s:known_types = {} + + " Ant {{{3 + let type_ant = {} + let type_ant.ctagstype = 'ant' + let type_ant.kinds = [ + \ {'short' : 'p', 'long' : 'projects', 'fold' : 0}, + \ {'short' : 't', 'long' : 'targets', 'fold' : 0} + \ ] + let s:known_types.ant = type_ant + " Asm {{{3 + let type_asm = {} + let type_asm.ctagstype = 'asm' + let type_asm.kinds = [ + \ {'short' : 'm', 'long' : 'macros', 'fold' : 0}, + \ {'short' : 't', 'long' : 'types', 'fold' : 0}, + \ {'short' : 'd', 'long' : 'defines', 'fold' : 0}, + \ {'short' : 'l', 'long' : 'labels', 'fold' : 0} + \ ] + let s:known_types.asm = type_asm + " ASP {{{3 + let type_aspvbs = {} + let type_aspvbs.ctagstype = 'asp' + let type_aspvbs.kinds = [ + \ {'short' : 'd', 'long' : 'constants', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 's', 'long' : 'subroutines', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0} + \ ] + let s:known_types.aspvbs = type_aspvbs + " Awk {{{3 + let type_awk = {} + let type_awk.ctagstype = 'awk' + let type_awk.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let s:known_types.awk = type_awk + " Basic {{{3 + let type_basic = {} + let type_basic.ctagstype = 'basic' + let type_basic.kinds = [ + \ {'short' : 'c', 'long' : 'constants', 'fold' : 0}, + \ {'short' : 'g', 'long' : 'enumerations', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'l', 'long' : 'labels', 'fold' : 0}, + \ {'short' : 't', 'long' : 'types', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0} + \ ] + let s:known_types.basic = type_basic + " BETA {{{3 + let type_beta = {} + let type_beta.ctagstype = 'beta' + let type_beta.kinds = [ + \ {'short' : 'f', 'long' : 'fragments', 'fold' : 0}, + \ {'short' : 's', 'long' : 'slots', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'patterns', 'fold' : 0} + \ ] + let s:known_types.beta = type_beta + " C {{{3 + let type_c = {} + let type_c.ctagstype = 'c' + let type_c.kinds = [ + \ {'short' : 'd', 'long' : 'macros', 'fold' : 1}, + \ {'short' : 'p', 'long' : 'prototypes', 'fold' : 1}, + \ {'short' : 'g', 'long' : 'enums', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0}, + \ {'short' : 't', 'long' : 'typedefs', 'fold' : 0}, + \ {'short' : 's', 'long' : 'structs', 'fold' : 0}, + \ {'short' : 'u', 'long' : 'unions', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'members', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let type_c.sro = '::' + let type_c.kind2scope = { + \ 'g' : 'enum', + \ 's' : 'struct', + \ 'u' : 'union' + \ } + let type_c.scope2kind = { + \ 'enum' : 'g', + \ 'struct' : 's', + \ 'union' : 'u' + \ } + let s:known_types.c = type_c + " C++ {{{3 + let type_cpp = {} + let type_cpp.ctagstype = 'c++' + let type_cpp.kinds = [ + \ {'short' : 'd', 'long' : 'macros', 'fold' : 1}, + \ {'short' : 'p', 'long' : 'prototypes', 'fold' : 1}, + \ {'short' : 'g', 'long' : 'enums', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0}, + \ {'short' : 't', 'long' : 'typedefs', 'fold' : 0}, + \ {'short' : 'n', 'long' : 'namespaces', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 's', 'long' : 'structs', 'fold' : 0}, + \ {'short' : 'u', 'long' : 'unions', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'members', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0} + \ ] + let type_cpp.sro = '::' + let type_cpp.kind2scope = { + \ 'g' : 'enum', + \ 'n' : 'namespace', + \ 'c' : 'class', + \ 's' : 'struct', + \ 'u' : 'union' + \ } + let type_cpp.scope2kind = { + \ 'enum' : 'g', + \ 'namespace' : 'n', + \ 'class' : 'c', + \ 'struct' : 's', + \ 'union' : 'u' + \ } + let s:known_types.cpp = type_cpp + " C# {{{3 + let type_cs = {} + let type_cs.ctagstype = 'c#' + let type_cs.kinds = [ + \ {'short' : 'd', 'long' : 'macros', 'fold' : 1}, + \ {'short' : 'f', 'long' : 'fields', 'fold' : 0}, + \ {'short' : 'g', 'long' : 'enums', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0}, + \ {'short' : 't', 'long' : 'typedefs', 'fold' : 0}, + \ {'short' : 'n', 'long' : 'namespaces', 'fold' : 0}, + \ {'short' : 'i', 'long' : 'interfaces', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 's', 'long' : 'structs', 'fold' : 0}, + \ {'short' : 'E', 'long' : 'events', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'properties', 'fold' : 0} + \ ] + let type_cs.sro = '.' + let type_cs.kind2scope = { + \ 'n' : 'namespace', + \ 'i' : 'interface', + \ 'c' : 'class', + \ 's' : 'struct', + \ 'g' : 'enum' + \ } + let type_cs.scope2kind = { + \ 'namespace' : 'n', + \ 'interface' : 'i', + \ 'class' : 'c', + \ 'struct' : 's', + \ 'enum' : 'g' + \ } + let s:known_types.cs = type_cs + " COBOL {{{3 + let type_cobol = {} + let type_cobol.ctagstype = 'cobol' + let type_cobol.kinds = [ + \ {'short' : 'd', 'long' : 'data items', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'file descriptions', 'fold' : 0}, + \ {'short' : 'g', 'long' : 'group items', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'paragraphs', 'fold' : 0}, + \ {'short' : 'P', 'long' : 'program ids', 'fold' : 0}, + \ {'short' : 's', 'long' : 'sections', 'fold' : 0} + \ ] + let s:known_types.cobol = type_cobol + " DOS Batch {{{3 + let type_dosbatch = {} + let type_dosbatch.ctagstype = 'dosbatch' + let type_dosbatch.kinds = [ + \ {'short' : 'l', 'long' : 'labels', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0} + \ ] + let s:known_types.dosbatch = type_dosbatch + " Eiffel {{{3 + let type_eiffel = {} + let type_eiffel.ctagstype = 'eiffel' + let type_eiffel.kinds = [ + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'features', 'fold' : 0} + \ ] + let type_eiffel.sro = '.' " Not sure, is nesting even possible? + let type_eiffel.kind2scope = { + \ 'c' : 'class', + \ 'f' : 'feature' + \ } + let type_eiffel.scope2kind = { + \ 'class' : 'c', + \ 'feature' : 'f' + \ } + let s:known_types.eiffel = type_eiffel + " Erlang {{{3 + let type_erlang = {} + let type_erlang.ctagstype = 'erlang' + let type_erlang.kinds = [ + \ {'short' : 'm', 'long' : 'modules', 'fold' : 0}, + \ {'short' : 'd', 'long' : 'macro definitions', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'r', 'long' : 'record definitions', 'fold' : 0} + \ ] + let type_erlang.sro = '.' " Not sure, is nesting even possible? + let type_erlang.kind2scope = { + \ 'm' : 'module' + \ } + let type_erlang.scope2kind = { + \ 'module' : 'm' + \ } + let s:known_types.erlang = type_erlang + " Flex {{{3 + " Vim doesn't support Flex out of the box, this is based on rough + " guesses and probably requires + " http://www.vim.org/scripts/script.php?script_id=2909 + " Improvements welcome! + let type_mxml = {} + let type_mxml.ctagstype = 'flex' + let type_mxml.kinds = [ + \ {'short' : 'v', 'long' : 'global variables', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'properties', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'x', 'long' : 'mxtags', 'fold' : 0} + \ ] + let type_mxml.sro = '.' + let type_mxml.kind2scope = { + \ 'c' : 'class' + \ } + let type_mxml.scope2kind = { + \ 'class' : 'c' + \ } + let s:known_types.mxml = type_mxml + " Fortran {{{3 + let type_fortran = {} + let type_fortran.ctagstype = 'fortran' + let type_fortran.kinds = [ + \ {'short' : 'm', 'long' : 'modules', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'programs', 'fold' : 0}, + \ {'short' : 'k', 'long' : 'components', 'fold' : 0}, + \ {'short' : 't', 'long' : 'derived types and structures', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'common blocks', 'fold' : 0}, + \ {'short' : 'b', 'long' : 'block data', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'entry points', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 's', 'long' : 'subroutines', 'fold' : 0}, + \ {'short' : 'l', 'long' : 'labels', 'fold' : 0}, + \ {'short' : 'n', 'long' : 'namelists', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0} + \ ] + let type_fortran.sro = '.' " Not sure, is nesting even possible? + let type_fortran.kind2scope = { + \ 'm' : 'module', + \ 'p' : 'program', + \ 'f' : 'function', + \ 's' : 'subroutine' + \ } + let type_fortran.scope2kind = { + \ 'module' : 'm', + \ 'program' : 'p', + \ 'function' : 'f', + \ 'subroutine' : 's' + \ } + let s:known_types.fortran = type_fortran + " HTML {{{3 + let type_html = {} + let type_html.ctagstype = 'html' + let type_html.kinds = [ + \ {'short' : 'f', 'long' : 'JavaScript funtions', 'fold' : 0}, + \ {'short' : 'a', 'long' : 'named anchors', 'fold' : 0} + \ ] + let s:known_types.html = type_html + " Java {{{3 + let type_java = {} + let type_java.ctagstype = 'java' + let type_java.kinds = [ + \ {'short' : 'p', 'long' : 'packages', 'fold' : 1}, + \ {'short' : 'f', 'long' : 'fields', 'fold' : 0}, + \ {'short' : 'g', 'long' : 'enum types', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'enum constants', 'fold' : 0}, + \ {'short' : 'i', 'long' : 'interfaces', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0} + \ ] + let type_java.sro = '.' + let type_java.kind2scope = { + \ 'g' : 'enum', + \ 'i' : 'interface', + \ 'c' : 'class' + \ } + let type_java.scope2kind = { + \ 'enum' : 'g', + \ 'interface' : 'i', + \ 'class' : 'c' + \ } + let s:known_types.java = type_java + " JavaScript {{{3 + " JavaScript is weird -- it does have scopes, but ctags doesn't seem to + " properly generate the information for them, instead it simply uses the + " complete name. So ctags has to be fixed before I can do anything here. + " Alternatively jsctags/doctorjs will be used if available. + let type_javascript = {} + let type_javascript.ctagstype = 'javascript' + let jsctags = s:CheckFTCtags('jsctags', 'javascript') + if jsctags != '' + let type_javascript.kinds = [ + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let type_javascript.sro = '.' + let type_javascript.kind2scope = { + \ 'v' : 'namespace', + \ 'f' : 'namespace' + \ } + let type_javascript.scope2kind = { + \ 'namespace' : 'v' + \ } + let type_javascript.ctagsbin = jsctags + let type_javascript.ctagsargs = '-f -' + else + let type_javascript.kinds = [ + \ {'short' : 'v', 'long' : 'global variables', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'properties', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + endif + let s:known_types.javascript = type_javascript + " Lisp {{{3 + let type_lisp = {} + let type_lisp.ctagstype = 'lisp' + let type_lisp.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let s:known_types.lisp = type_lisp + " Lua {{{3 + let type_lua = {} + let type_lua.ctagstype = 'lua' + let type_lua.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let s:known_types.lua = type_lua + " Make {{{3 + let type_make = {} + let type_make.ctagstype = 'make' + let type_make.kinds = [ + \ {'short' : 'm', 'long' : 'macros', 'fold' : 0} + \ ] + let s:known_types.make = type_make + " Matlab {{{3 + let type_matlab = {} + let type_matlab.ctagstype = 'matlab' + let type_matlab.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let s:known_types.matlab = type_matlab + " Ocaml {{{3 + let type_ocaml = {} + let type_ocaml.ctagstype = 'ocaml' + let type_ocaml.kinds = [ + \ {'short' : 'M', 'long' : 'modules or functors', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'global variables', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'C', 'long' : 'constructors', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'exceptions', 'fold' : 0}, + \ {'short' : 't', 'long' : 'type names', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'r', 'long' : 'structure fields', 'fold' : 0} + \ ] + let type_ocaml.sro = '.' " Not sure, is nesting even possible? + let type_ocaml.kind2scope = { + \ 'M' : 'Module', + \ 'c' : 'class', + \ 't' : 'type' + \ } + let type_ocaml.scope2kind = { + \ 'Module' : 'M', + \ 'class' : 'c', + \ 'type' : 't' + \ } + let s:known_types.ocaml = type_ocaml + " Pascal {{{3 + let type_pascal = {} + let type_pascal.ctagstype = 'pascal' + let type_pascal.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'procedures', 'fold' : 0} + \ ] + let s:known_types.pascal = type_pascal + " Perl {{{3 + let type_perl = {} + let type_perl.ctagstype = 'perl' + let type_perl.kinds = [ + \ {'short' : 'p', 'long' : 'packages', 'fold' : 1}, + \ {'short' : 'c', 'long' : 'constants', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'formats', 'fold' : 0}, + \ {'short' : 'l', 'long' : 'labels', 'fold' : 0}, + \ {'short' : 's', 'long' : 'subroutines', 'fold' : 0} + \ ] + let s:known_types.perl = type_perl + " PHP {{{3 + let type_php = {} + let type_php.ctagstype = 'php' + let type_php.kinds = [ + \ {'short' : 'i', 'long' : 'interfaces', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'd', 'long' : 'constant definitions', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0}, + \ {'short' : 'j', 'long' : 'javascript functions', 'fold' : 0} + \ ] + let s:known_types.php = type_php + " Python {{{3 + let type_python = {} + let type_python.ctagstype = 'python' + let type_python.kinds = [ + \ {'short' : 'i', 'long' : 'imports', 'fold' : 1}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'members', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0} + \ ] + let type_python.sro = '.' + let type_python.kind2scope = { + \ 'c' : 'class', + \ 'f' : 'function', + \ 'm' : 'function' + \ } + let type_python.scope2kind = { + \ 'class' : 'c', + \ 'function' : 'f' + \ } + let s:known_types.python = type_python + " REXX {{{3 + let type_rexx = {} + let type_rexx.ctagstype = 'rexx' + let type_rexx.kinds = [ + \ {'short' : 's', 'long' : 'subroutines', 'fold' : 0} + \ ] + let s:known_types.rexx = type_rexx + " Ruby {{{3 + let type_ruby = {} + let type_ruby.ctagstype = 'ruby' + let type_ruby.kinds = [ + \ {'short' : 'm', 'long' : 'modules', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'methods', 'fold' : 0}, + \ {'short' : 'F', 'long' : 'singleton methods', 'fold' : 0} + \ ] + let type_ruby.sro = '.' + let type_ruby.kind2scope = { + \ 'c' : 'class', + \ 'm' : 'class' + \ } + let type_ruby.scope2kind = { + \ 'class' : 'c' + \ } + let s:known_types.ruby = type_ruby + " Scheme {{{3 + let type_scheme = {} + let type_scheme.ctagstype = 'scheme' + let type_scheme.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 's', 'long' : 'sets', 'fold' : 0} + \ ] + let s:known_types.scheme = type_scheme + " Shell script {{{3 + let type_sh = {} + let type_sh.ctagstype = 'sh' + let type_sh.kinds = [ + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let s:known_types.sh = type_sh + let s:known_types.csh = type_sh + let s:known_types.zsh = type_sh + " SLang {{{3 + let type_slang = {} + let type_slang.ctagstype = 'slang' + let type_slang.kinds = [ + \ {'short' : 'n', 'long' : 'namespaces', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0} + \ ] + let s:known_types.slang = type_slang + " SML {{{3 + let type_sml = {} + let type_sml.ctagstype = 'sml' + let type_sml.kinds = [ + \ {'short' : 'e', 'long' : 'exception declarations', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'function definitions', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'functor definitions', 'fold' : 0}, + \ {'short' : 's', 'long' : 'signature declarations', 'fold' : 0}, + \ {'short' : 'r', 'long' : 'structure declarations', 'fold' : 0}, + \ {'short' : 't', 'long' : 'type definitions', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'value bindings', 'fold' : 0} + \ ] + let s:known_types.sml = type_sml + " SQL {{{3 + " The SQL ctags parser seems to be buggy for me, so this just uses the + " normal kinds even though scopes should be available. Improvements + " welcome! + let type_sql = {} + let type_sql.ctagstype = 'sql' + let type_sql.kinds = [ + \ {'short' : 'P', 'long' : 'packages', 'fold' : 1}, + \ {'short' : 'c', 'long' : 'cursors', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'F', 'long' : 'record fields', 'fold' : 0}, + \ {'short' : 'L', 'long' : 'block label', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'procedures', 'fold' : 0}, + \ {'short' : 's', 'long' : 'subtypes', 'fold' : 0}, + \ {'short' : 't', 'long' : 'tables', 'fold' : 0}, + \ {'short' : 'T', 'long' : 'triggers', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0}, + \ {'short' : 'i', 'long' : 'indexes', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'events', 'fold' : 0}, + \ {'short' : 'U', 'long' : 'publications', 'fold' : 0}, + \ {'short' : 'R', 'long' : 'services', 'fold' : 0}, + \ {'short' : 'D', 'long' : 'domains', 'fold' : 0}, + \ {'short' : 'V', 'long' : 'views', 'fold' : 0}, + \ {'short' : 'n', 'long' : 'synonyms', 'fold' : 0}, + \ {'short' : 'x', 'long' : 'MobiLink Table Scripts', 'fold' : 0}, + \ {'short' : 'y', 'long' : 'MobiLink Conn Scripts', 'fold' : 0} + \ ] + let s:known_types.sql = type_sql + " Tcl {{{3 + let type_tcl = {} + let type_tcl.ctagstype = 'tcl' + let type_tcl.kinds = [ + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'procedures', 'fold' : 0} + \ ] + let s:known_types.tcl = type_tcl + " LaTeX {{{3 + let type_tex = {} + let type_tex.ctagstype = 'tex' + let type_tex.kinds = [ + \ {'short' : 'p', 'long' : 'parts', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'chapters', 'fold' : 0}, + \ {'short' : 's', 'long' : 'sections', 'fold' : 0}, + \ {'short' : 'u', 'long' : 'subsections', 'fold' : 0}, + \ {'short' : 'b', 'long' : 'subsubsections', 'fold' : 0}, + \ {'short' : 'P', 'long' : 'paragraphs', 'fold' : 0}, + \ {'short' : 'G', 'long' : 'subparagraphs', 'fold' : 0} + \ ] + let s:known_types.tex = type_tex + " Vera {{{3 + " Why are variables 'virtual'? + let type_vera = {} + let type_vera.ctagstype = 'vera' + let type_vera.kinds = [ + \ {'short' : 'd', 'long' : 'macros', 'fold' : 1}, + \ {'short' : 'g', 'long' : 'enums', 'fold' : 0}, + \ {'short' : 'T', 'long' : 'typedefs', 'fold' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'members', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 't', 'long' : 'tasks', 'fold' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'programs', 'fold' : 0} + \ ] + let type_vera.sro = '.' " Nesting doesn't seem to be possible + let type_vera.kind2scope = { + \ 'g' : 'enum', + \ 'c' : 'class', + \ 'v' : 'virtual' + \ } + let type_vera.scope2kind = { + \ 'enum' : 'g', + \ 'class' : 'c', + \ 'virtual' : 'v' + \ } + let s:known_types.vera = type_vera + " Verilog {{{3 + let type_verilog = {} + let type_verilog.ctagstype = 'verilog' + let type_verilog.kinds = [ + \ {'short' : 'c', 'long' : 'constants', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'events', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'modules', 'fold' : 0}, + \ {'short' : 'n', 'long' : 'net data types', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'ports', 'fold' : 0}, + \ {'short' : 'r', 'long' : 'register data types', 'fold' : 0}, + \ {'short' : 't', 'long' : 'tasks', 'fold' : 0} + \ ] + let s:known_types.verilog = type_verilog + " VHDL {{{3 + " The VHDL ctags parser unfortunately doesn't generate proper scopes + let type_vhdl = {} + let type_vhdl.ctagstype = 'vhdl' + let type_vhdl.kinds = [ + \ {'short' : 'P', 'long' : 'packages', 'fold' : 1}, + \ {'short' : 'c', 'long' : 'constants', 'fold' : 0}, + \ {'short' : 't', 'long' : 'types', 'fold' : 0}, + \ {'short' : 'T', 'long' : 'subtypes', 'fold' : 0}, + \ {'short' : 'r', 'long' : 'records', 'fold' : 0}, + \ {'short' : 'e', 'long' : 'entities', 'fold' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'p', 'long' : 'procedures', 'fold' : 0} + \ ] + let s:known_types.vhdl = type_vhdl + " Vim {{{3 + let type_vim = {} + let type_vim.ctagstype = 'vim' + let type_vim.kinds = [ + \ {'short' : 'v', 'long' : 'variables', 'fold' : 1}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0}, + \ {'short' : 'a', 'long' : 'autocommand groups', 'fold' : 1}, + \ {'short' : 'c', 'long' : 'commands', 'fold' : 0}, + \ {'short' : 'm', 'long' : 'maps', 'fold' : 1} + \ ] + let s:known_types.vim = type_vim + " YACC {{{3 + let type_yacc = {} + let type_yacc.ctagstype = 'yacc' + let type_yacc.kinds = [ + \ {'short' : 'l', 'long' : 'labels', 'fold' : 0} + \ ] + let s:known_types.yacc = type_yacc + " }}}3 + + let user_defs = s:GetUserTypeDefs() + for [key, value] in items(user_defs) + if !has_key(s:known_types, key) || + \ (has_key(value, 'replace') && value.replace) + let s:known_types[key] = value + else + call extend(s:known_types[key], value) + endif + endfor + + " Create a dictionary of the kind order for fast + " access in sorting functions + for type in values(s:known_types) + let i = 0 + let type.kinddict = {} + for kind in type.kinds + let type.kinddict[kind.short] = i + let i += 1 + endfor + endfor + + let s:type_init_done = 1 +endfunction + +" s:GetUserTypeDefs() {{{2 +function! s:GetUserTypeDefs() + call s:LogDebugMessage('Initializing user types') + + redir => defs + silent execute 'let g:' + redir END + + let deflist = split(defs, '\n') + call map(deflist, 'substitute(v:val, ''^\S\+\zs.*'', "", "")') + call filter(deflist, 'v:val =~ "^tagbar_type_"') + + let defdict = {} + for defstr in deflist + let type = substitute(defstr, '^tagbar_type_', '', '') + execute 'let defdict["' . type . '"] = g:' . defstr + endfor + + " If the user only specified one of kind2scope and scope2kind use it to + " generate the other one + " Also, transform the 'kind' definitions into dictionary format + for def in values(defdict) + if has_key(def, 'kinds') + let kinds = def.kinds + let def.kinds = [] + for kind in kinds + let kindlist = split(kind, ':') + let kinddict = {'short' : kindlist[0], 'long' : kindlist[1]} + if len(kindlist) == 3 + let kinddict.fold = kindlist[2] + else + let kinddict.fold = 0 + endif + call add(def.kinds, kinddict) + endfor + endif + + if has_key(def, 'kind2scope') && !has_key(def, 'scope2kind') + let def.scope2kind = {} + for [key, value] in items(def.kind2scope) + let def.scope2kind[value] = key + endfor + elseif has_key(def, 'scope2kind') && !has_key(def, 'kind2scope') + let def.kind2scope = {} + for [key, value] in items(def.scope2kind) + let def.kind2scope[value] = key + endfor + endif + endfor + + return defdict +endfunction + +" s:RestoreSession() {{{2 +" Properly restore Tagbar after a session got loaded +function! s:RestoreSession() + call s:LogDebugMessage('Restoring session') + + let tagbarwinnr = bufwinnr('__Tagbar__') + if tagbarwinnr == -1 + " Tagbar wasn't open in the saved session, nothing to do + return + else + let in_tagbar = 1 + if winnr() != tagbarwinnr + execute tagbarwinnr . 'wincmd w' + let in_tagbar = 0 + endif + endif + + call s:Init() + + call s:InitWindow(g:tagbar_autoclose) + + " Leave the Tagbar window and come back so the update event gets triggered + wincmd p + execute tagbarwinnr . 'wincmd w' + + if !in_tagbar + wincmd p + endif +endfunction + +" s:MapKeys() {{{2 +function! s:MapKeys() + call s:LogDebugMessage('Mapping keys') + + nnoremap <script> <silent> <buffer> <2-LeftMouse> + \ :call <SID>JumpToTag(0)<CR> + nnoremap <script> <silent> <buffer> <LeftRelease> + \ <LeftRelease>:call <SID>CheckMouseClick()<CR> + + inoremap <script> <silent> <buffer> <2-LeftMouse> + \ <C-o>:call <SID>JumpToTag(0)<CR> + inoremap <script> <silent> <buffer> <LeftRelease> + \ <LeftRelease><C-o>:call <SID>CheckMouseClick()<CR> + + nnoremap <script> <silent> <buffer> <CR> :call <SID>JumpToTag(0)<CR> + nnoremap <script> <silent> <buffer> p :call <SID>JumpToTag(1)<CR> + nnoremap <script> <silent> <buffer> <Space> :call <SID>ShowPrototype()<CR> + + nnoremap <script> <silent> <buffer> + :call <SID>OpenFold()<CR> + nnoremap <script> <silent> <buffer> <kPlus> :call <SID>OpenFold()<CR> + nnoremap <script> <silent> <buffer> zo :call <SID>OpenFold()<CR> + nnoremap <script> <silent> <buffer> - :call <SID>CloseFold()<CR> + nnoremap <script> <silent> <buffer> <kMinus> :call <SID>CloseFold()<CR> + nnoremap <script> <silent> <buffer> zc :call <SID>CloseFold()<CR> + nnoremap <script> <silent> <buffer> o :call <SID>ToggleFold()<CR> + nnoremap <script> <silent> <buffer> za :call <SID>ToggleFold()<CR> + + nnoremap <script> <silent> <buffer> * :call <SID>SetFoldLevel(99)<CR> + nnoremap <script> <silent> <buffer> <kMultiply> + \ :call <SID>SetFoldLevel(99)<CR> + nnoremap <script> <silent> <buffer> zR :call <SID>SetFoldLevel(99)<CR> + nnoremap <script> <silent> <buffer> = :call <SID>SetFoldLevel(0)<CR> + nnoremap <script> <silent> <buffer> zM :call <SID>SetFoldLevel(0)<CR> + + nnoremap <script> <silent> <buffer> <C-N> + \ :call <SID>GotoNextToplevelTag(1)<CR> + nnoremap <script> <silent> <buffer> <C-P> + \ :call <SID>GotoNextToplevelTag(-1)<CR> + + nnoremap <script> <silent> <buffer> s :call <SID>ToggleSort()<CR> + nnoremap <script> <silent> <buffer> x :call <SID>ZoomWindow()<CR> + nnoremap <script> <silent> <buffer> q :call <SID>CloseWindow()<CR> + nnoremap <script> <silent> <buffer> <F1> :call <SID>ToggleHelp()<CR> +endfunction + +" s:CreateAutocommands() {{{2 +function! s:CreateAutocommands() + call s:LogDebugMessage('Creating autocommands') + + augroup TagbarAutoCmds + autocmd! + autocmd BufEnter __Tagbar__ nested call s:QuitIfOnlyWindow() + autocmd BufUnload __Tagbar__ call s:CleanUp() + autocmd CursorHold __Tagbar__ call s:ShowPrototype() + + autocmd BufWritePost * + \ if line('$') < g:tagbar_updateonsave_maxlines | + \ call s:AutoUpdate(fnamemodify(expand('<afile>'), ':p')) | + \ endif + autocmd BufEnter,CursorHold,FileType * call + \ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p')) + autocmd BufDelete * call + \ s:CleanupFileinfo(fnamemodify(expand('<afile>'), ':p')) + augroup END + + let s:autocommands_done = 1 +endfunction + +" s:CheckForExCtags() {{{2 +" Test whether the ctags binary is actually Exuberant Ctags and not GNU ctags +" (or something else) +function! s:CheckForExCtags() + call s:LogDebugMessage('Checking for Exuberant Ctags') + + let ctags_cmd = s:EscapeCtagsCmd(g:tagbar_ctags_bin, '--version') + if ctags_cmd == '' + return + endif + + let ctags_output = s:ExecuteCtags(ctags_cmd) + + if v:shell_error || ctags_output !~# 'Exuberant Ctags' + echoerr 'Tagbar: Ctags doesn''t seem to be Exuberant Ctags!' + echomsg 'GNU ctags will NOT WORK.' + \ 'Please download Exuberant Ctags from ctags.sourceforge.net' + \ 'and install it in a directory in your $PATH' + \ 'or set g:tagbar_ctags_bin.' + echomsg 'Executed command: "' . ctags_cmd . '"' + if !empty(ctags_output) + echomsg 'Command output:' + for line in split(ctags_output, '\n') + echomsg line + endfor + endif + return 0 + elseif !s:CheckExCtagsVersion(ctags_output) + echoerr 'Tagbar: Exuberant Ctags is too old!' + echomsg 'You need at least version 5.5 for Tagbar to work.' + \ 'Please download a newer version from ctags.sourceforge.net.' + echomsg 'Executed command: "' . ctags_cmd . '"' + if !empty(ctags_output) + echomsg 'Command output:' + for line in split(ctags_output, '\n') + echomsg line + endfor + endif + return 0 + else + let s:checked_ctags = 1 + return 1 + endif +endfunction + +" s:CheckExCtagsVersion() {{{2 +function! s:CheckExCtagsVersion(output) + call s:LogDebugMessage('Checking Exuberant Ctags version') + + if a:output =~ 'Exuberant Ctags Development' + return 1 + endif + + let matchlist = matchlist(a:output, '\vExuberant Ctags (\d+)\.(\d+)') + let major = matchlist[1] + let minor = matchlist[2] + + return major >= 6 || (major == 5 && minor >= 5) +endfunction + +" s:CheckFTCtags() {{{2 +function! s:CheckFTCtags(bin, ftype) + if executable(a:bin) + return a:bin + endif + + if exists('g:tagbar_type_' . a:ftype) + execute 'let userdef = ' . 'g:tagbar_type_' . a:ftype + if has_key(userdef, 'ctagsbin') + return userdef.ctagsbin + else + return '' + endif + endif + + return '' +endfunction + +" Prototypes {{{1 +" Base tag {{{2 +let s:BaseTag = {} + +" s:BaseTag._init() {{{3 +function! s:BaseTag._init(name) dict + let self.name = a:name + let self.fields = {} + let self.fields.line = 0 + let self.path = '' + let self.fullpath = a:name + let self.depth = 0 + let self.parent = {} + let self.tline = -1 + let self.fileinfo = {} +endfunction + +" s:BaseTag.isNormalTag() {{{3 +function! s:BaseTag.isNormalTag() dict + return 0 +endfunction + +" s:BaseTag.isPseudoTag() {{{3 +function! s:BaseTag.isPseudoTag() dict + return 0 +endfunction + +" s:BaseTag.isKindheader() {{{3 +function! s:BaseTag.isKindheader() dict + return 0 +endfunction + +" s:BaseTag.getPrototype() {{{3 +function! s:BaseTag.getPrototype() dict + return '' +endfunction + +" s:BaseTag._getPrefix() {{{3 +function! s:BaseTag._getPrefix() dict + let fileinfo = self.fileinfo + + if has_key(self, 'children') && !empty(self.children) + if fileinfo.tagfolds[self.fields.kind][self.fullpath] + let prefix = s:icon_closed + else + let prefix = s:icon_open + endif + else + let prefix = ' ' + endif + if has_key(self.fields, 'access') + let prefix .= get(s:access_symbols, self.fields.access, ' ') + else + let prefix .= ' ' + endif + + return prefix +endfunction + +" s:BaseTag.initFoldState() {{{3 +function! s:BaseTag.initFoldState() dict + let fileinfo = self.fileinfo + + if s:known_files.has(fileinfo.fpath) && + \ has_key(fileinfo._tagfolds_old[self.fields.kind], self.fullpath) + " The file has been updated and the tag was there before, so copy its + " old fold state + let fileinfo.tagfolds[self.fields.kind][self.fullpath] = + \ fileinfo._tagfolds_old[self.fields.kind][self.fullpath] + elseif self.depth >= fileinfo.foldlevel + let fileinfo.tagfolds[self.fields.kind][self.fullpath] = 1 + else + let fileinfo.tagfolds[self.fields.kind][self.fullpath] = + \ fileinfo.kindfolds[self.fields.kind] + endif +endfunction + +" s:BaseTag.getClosedParentTline() {{{3 +function! s:BaseTag.getClosedParentTline() dict + let tagline = self.tline + let fileinfo = self.fileinfo + + let parent = self.parent + while !empty(parent) + if parent.isFolded() + let tagline = parent.tline + break + endif + let parent = parent.parent + endwhile + + return tagline +endfunction + +" s:BaseTag.isFoldable() {{{3 +function! s:BaseTag.isFoldable() dict + return has_key(self, 'children') && !empty(self.children) +endfunction + +" s:BaseTag.isFolded() {{{3 +function! s:BaseTag.isFolded() dict + return self.fileinfo.tagfolds[self.fields.kind][self.fullpath] +endfunction + +" s:BaseTag.openFold() {{{3 +function! s:BaseTag.openFold() dict + if self.isFoldable() + let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = 0 + endif +endfunction + +" s:BaseTag.closeFold() {{{3 +function! s:BaseTag.closeFold() dict + let newline = line('.') + + if !empty(self.parent) && self.parent.isKindheader() + " Tag is child of generic 'kind' + call self.parent.closeFold() + let newline = self.parent.tline + elseif self.isFoldable() && !self.isFolded() + " Tag is parent of a scope and is not folded + let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = 1 + let newline = self.tline + elseif !empty(self.parent) + " Tag is normal child, so close parent + let parent = self.parent + let self.fileinfo.tagfolds[parent.fields.kind][parent.fullpath] = 1 + let newline = parent.tline + endif + + return newline +endfunction + +" s:BaseTag.setFolded() {{{3 +function! s:BaseTag.setFolded(folded) dict + let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = a:folded +endfunction + +" s:BaseTag.openParents() {{{3 +function! s:BaseTag.openParents() dict + let parent = self.parent + + while !empty(parent) + call parent.openFold() + let parent = parent.parent + endwhile +endfunction + +" Normal tag {{{2 +let s:NormalTag = copy(s:BaseTag) + +" s:NormalTag.New() {{{3 +function! s:NormalTag.New(name) dict + let newobj = copy(self) + + call newobj._init(a:name) + + return newobj +endfunction + +" s:NormalTag.isNormalTag() {{{3 +function! s:NormalTag.isNormalTag() dict + return 1 +endfunction + +" s:NormalTag.str() {{{3 +function! s:NormalTag.str() dict + let fileinfo = self.fileinfo + let typeinfo = s:known_types[fileinfo.ftype] + + let suffix = get(self.fields, 'signature', '') + if has_key(self.fields, 'type') + let suffix .= ' : ' . self.fields.type + elseif has_key(typeinfo, 'kind2scope') && + \ has_key(typeinfo.kind2scope, self.fields.kind) + let suffix .= ' : ' . typeinfo.kind2scope[self.fields.kind] + endif + + return self._getPrefix() . self.name . suffix . "\n" +endfunction + +" s:NormalTag.getPrototype() {{{3 +function! s:NormalTag.getPrototype() dict + return self.prototype +endfunction + +" Pseudo tag {{{2 +let s:PseudoTag = copy(s:BaseTag) + +" s:PseudoTag.New() {{{3 +function! s:PseudoTag.New(name) dict + let newobj = copy(self) + + call newobj._init(a:name) + + return newobj +endfunction + +" s:PseudoTag.isPseudoTag() {{{3 +function! s:PseudoTag.isPseudoTag() dict + return 1 +endfunction + +" s:PseudoTag.str() {{{3 +function! s:PseudoTag.str() dict + let fileinfo = self.fileinfo + let typeinfo = s:known_types[fileinfo.ftype] + + let suffix = get(self.fields, 'signature', '') + if has_key(typeinfo.kind2scope, self.fields.kind) + let suffix .= ' : ' . typeinfo.kind2scope[self.fields.kind] + endif + + return self._getPrefix() . self.name . '*' . suffix +endfunction + +" Kind header {{{2 +let s:KindheaderTag = copy(s:BaseTag) + +" s:KindheaderTag.New() {{{3 +function! s:KindheaderTag.New(name) dict + let newobj = copy(self) + + call newobj._init(a:name) + + return newobj +endfunction + +" s:KindheaderTag.isKindheader() {{{3 +function! s:KindheaderTag.isKindheader() dict + return 1 +endfunction + +" s:KindheaderTag.getPrototype() {{{3 +function! s:KindheaderTag.getPrototype() dict + return self.name . ': ' . + \ self.numtags . ' ' . (self.numtags > 1 ? 'tags' : 'tag') +endfunction + +" s:KindheaderTag.isFoldable() {{{3 +function! s:KindheaderTag.isFoldable() dict + return 1 +endfunction + +" s:KindheaderTag.isFolded() {{{3 +function! s:KindheaderTag.isFolded() dict + return self.fileinfo.kindfolds[self.short] +endfunction + +" s:KindheaderTag.openFold() {{{3 +function! s:KindheaderTag.openFold() dict + let self.fileinfo.kindfolds[self.short] = 0 +endfunction + +" s:KindheaderTag.closeFold() {{{3 +function! s:KindheaderTag.closeFold() dict + let self.fileinfo.kindfolds[self.short] = 1 + return line('.') +endfunction + +" s:KindheaderTag.toggleFold() {{{3 +function! s:KindheaderTag.toggleFold() dict + let fileinfo = s:known_files.getCurrent() + + let fileinfo.kindfolds[self.short] = !fileinfo.kindfolds[self.short] +endfunction + +" File info {{{2 +let s:FileInfo = {} + +" s:FileInfo.New() {{{3 +function! s:FileInfo.New(fname, ftype) dict + let newobj = copy(self) + + " The complete file path + let newobj.fpath = a:fname + + " File modification time + let newobj.mtime = getftime(a:fname) + + " The vim file type + let newobj.ftype = a:ftype + + " List of the tags that are present in the file, sorted according to the + " value of 'g:tagbar_sort' + let newobj.tags = [] + + " Dictionary of the tags, indexed by line number in the file + let newobj.fline = {} + + " Dictionary of the tags, indexed by line number in the tagbar + let newobj.tline = {} + + " Dictionary of the folding state of 'kind's, indexed by short name + let newobj.kindfolds = {} + let typeinfo = s:known_types[a:ftype] + " copy the default fold state from the type info + for kind in typeinfo.kinds + let newobj.kindfolds[kind.short] = + \ g:tagbar_foldlevel == 0 ? 1 : kind.fold + endfor + + " Dictionary of dictionaries of the folding state of individual tags, + " indexed by kind and full path + let newobj.tagfolds = {} + for kind in typeinfo.kinds + let newobj.tagfolds[kind.short] = {} + endfor + + " The current foldlevel of the file + let newobj.foldlevel = g:tagbar_foldlevel + + return newobj +endfunction + +" s:FileInfo.reset() {{{3 +" Reset stuff that gets regenerated while processing a file and save the old +" tag folds +function! s:FileInfo.reset() dict + let self.mtime = getftime(self.fpath) + let self.tags = [] + let self.fline = {} + let self.tline = {} + + let self._tagfolds_old = self.tagfolds + let self.tagfolds = {} + + let typeinfo = s:known_types[self.ftype] + for kind in typeinfo.kinds + let self.tagfolds[kind.short] = {} + endfor +endfunction + +" s:FileInfo.clearOldFolds() {{{3 +function! s:FileInfo.clearOldFolds() dict + if exists('self._tagfolds_old') + unlet self._tagfolds_old + endif +endfunction + +" s:FileInfo.sortTags() {{{3 +function! s:FileInfo.sortTags() dict + if has_key(s:compare_typeinfo, 'sort') + if s:compare_typeinfo.sort + call s:SortTags(self.tags, 's:CompareByKind') + else + call s:SortTags(self.tags, 's:CompareByLine') + endif + elseif g:tagbar_sort + call s:SortTags(self.tags, 's:CompareByKind') + else + call s:SortTags(self.tags, 's:CompareByLine') + endif +endfunction + +" s:FileInfo.openKindFold() {{{3 +function! s:FileInfo.openKindFold(kind) dict + let self.kindfolds[a:kind.short] = 0 +endfunction + +" s:FileInfo.closeKindFold() {{{3 +function! s:FileInfo.closeKindFold(kind) dict + let self.kindfolds[a:kind.short] = 1 +endfunction + +" Known files {{{2 +let s:known_files = { + \ '_current' : {}, + \ '_files' : {} +\ } + +" s:known_files.getCurrent() {{{3 +function! s:known_files.getCurrent() dict + return self._current +endfunction + +" s:known_files.setCurrent() {{{3 +function! s:known_files.setCurrent(fileinfo) dict + let self._current = a:fileinfo +endfunction + +" s:known_files.get() {{{3 +function! s:known_files.get(fname) dict + return get(self._files, a:fname, {}) +endfunction + +" s:known_files.put() {{{3 +" Optional second argument is the filename +function! s:known_files.put(fileinfo, ...) dict + if a:0 == 1 + let self._files[a:1] = a:fileinfo + else + let fname = a:fileinfo.fpath + let self._files[fname] = a:fileinfo + endif +endfunction + +" s:known_files.has() {{{3 +function! s:known_files.has(fname) dict + return has_key(self._files, a:fname) +endfunction + +" s:known_files.rm() {{{3 +function! s:known_files.rm(fname) dict + if s:known_files.has(a:fname) + call remove(self._files, a:fname) + endif +endfunction + +" Window management {{{1 +" s:ToggleWindow() {{{2 +function! s:ToggleWindow() + let tagbarwinnr = bufwinnr("__Tagbar__") + if tagbarwinnr != -1 + call s:CloseWindow() + return + endif + + call s:OpenWindow('') +endfunction + +" s:OpenWindow() {{{2 +function! s:OpenWindow(flags) + let autofocus = a:flags =~# 'f' + let jump = a:flags =~# 'j' + let autoclose = a:flags =~# 'c' + + " If the tagbar window is already open check jump flag + " Also set the autoclose flag if requested + let tagbarwinnr = bufwinnr('__Tagbar__') + if tagbarwinnr != -1 + if winnr() != tagbarwinnr && jump + execute tagbarwinnr . 'wincmd w' + if autoclose + let w:autoclose = autoclose + endif + endif + return + endif + + call s:Init() + + " Expand the Vim window to accomodate for the Tagbar window if requested + if g:tagbar_expand && !s:window_expanded && has('gui_running') + let &columns += g:tagbar_width + 1 + let s:window_expanded = 1 + endif + + let eventignore_save = &eventignore + set eventignore=all + + let openpos = g:tagbar_left ? 'topleft vertical ' : 'botright vertical ' + exe 'silent keepalt ' . openpos . g:tagbar_width . 'split ' . '__Tagbar__' + + let &eventignore = eventignore_save + + call s:InitWindow(autoclose) + + wincmd p + + " Jump back to the tagbar window if autoclose or autofocus is set. Can't + " just stay in it since it wouldn't trigger the update event + if g:tagbar_autoclose || autofocus || g:tagbar_autofocus + let tagbarwinnr = bufwinnr('__Tagbar__') + execute tagbarwinnr . 'wincmd w' + endif +endfunction + +" s:InitWindow() {{{2 +function! s:InitWindow(autoclose) + setlocal noreadonly " in case the "view" mode is used + setlocal buftype=nofile + setlocal bufhidden=hide + setlocal noswapfile + setlocal nobuflisted + setlocal nomodifiable + setlocal filetype=tagbar + setlocal nolist + setlocal nonumber + setlocal nowrap + setlocal winfixwidth + setlocal textwidth=0 + setlocal nocursorline + setlocal nocursorcolumn + + if exists('+relativenumber') + setlocal norelativenumber + endif + + setlocal nofoldenable + setlocal foldcolumn=0 + " Reset fold settings in case a plugin set them globally to something + " expensive. Apparently 'foldexpr' gets executed even if 'foldenable' is + " off, and then for every appended line (like with :put). + setlocal foldmethod& + setlocal foldexpr& + + " Earlier versions have a bug in local, evaluated statuslines + if v:version > 701 || (v:version == 701 && has('patch097')) + setlocal statusline=%!TagbarGenerateStatusline() + else + setlocal statusline=Tagbar + endif + + " Script-local variable needed since compare functions can't + " take extra arguments + let s:compare_typeinfo = {} + + let s:is_maximized = 0 + let s:short_help = 1 + + let w:autoclose = a:autoclose + + if has('balloon_eval') + setlocal balloonexpr=TagbarBalloonExpr() + set ballooneval + endif + + let cpoptions_save = &cpoptions + set cpoptions&vim + + if !hasmapto('JumpToTag', 'n') + call s:MapKeys() + endif + + if !s:autocommands_done + call s:CreateAutocommands() + endif + + let &cpoptions = cpoptions_save +endfunction + +" s:CloseWindow() {{{2 +function! s:CloseWindow() + let tagbarwinnr = bufwinnr('__Tagbar__') + if tagbarwinnr == -1 + return + endif + + let tagbarbufnr = winbufnr(tagbarwinnr) + + if winnr() == tagbarwinnr + if winbufnr(2) != -1 + " Other windows are open, only close the tagbar one + close + wincmd p + endif + else + " Go to the tagbar window, close it and then come back to the + " original window + let curbufnr = bufnr('%') + execute tagbarwinnr . 'wincmd w' + close + " Need to jump back to the original window only if we are not + " already in that window + let winnum = bufwinnr(curbufnr) + if winnr() != winnum + exe winnum . 'wincmd w' + endif + endif + + " If the Vim window has been expanded, and Tagbar is not open in any other + " tabpages, shrink the window again + if s:window_expanded + let tablist = [] + for i in range(tabpagenr('$')) + call extend(tablist, tabpagebuflist(i + 1)) + endfor + + if index(tablist, tagbarbufnr) == -1 + let &columns -= g:tagbar_width + 1 + let s:window_expanded = 0 + endif + endif +endfunction + +" s:ZoomWindow() {{{2 +function! s:ZoomWindow() + if s:is_maximized + execute 'vert resize ' . g:tagbar_width + let s:is_maximized = 0 + else + vert resize + let s:is_maximized = 1 + endif +endfunction + +" Tag processing {{{1 +" s:ProcessFile() {{{2 +" Execute ctags and put the information into a 'FileInfo' object +function! s:ProcessFile(fname, ftype) + call s:LogDebugMessage('ProcessFile called on ' . a:fname) + + if !s:IsValidFile(a:fname, a:ftype) + call s:LogDebugMessage('Not a valid file, returning') + return + endif + + let ctags_output = s:ExecuteCtagsOnFile(a:fname, a:ftype) + + if ctags_output == -1 + call s:LogDebugMessage('Ctags error when processing file') + " put an empty entry into known_files so the error message is only + " shown once + call s:known_files.put({}, a:fname) + return + elseif ctags_output == '' + call s:LogDebugMessage('Ctags output empty') + return + endif + + " If the file has only been updated preserve the fold states, otherwise + " create a new entry + if s:known_files.has(a:fname) + let fileinfo = s:known_files.get(a:fname) + call fileinfo.reset() + else + let fileinfo = s:FileInfo.New(a:fname, a:ftype) + endif + + let typeinfo = s:known_types[a:ftype] + + " Parse the ctags output lines + call s:LogDebugMessage('Parsing ctags output') + let rawtaglist = split(ctags_output, '\n\+') + for line in rawtaglist + " skip comments + if line =~# '^!_TAG_' + continue + endif + + let parts = split(line, ';"') + if len(parts) == 2 " Is a valid tag line + let taginfo = s:ParseTagline(parts[0], parts[1], typeinfo, fileinfo) + let fileinfo.fline[taginfo.fields.line] = taginfo + call add(fileinfo.tags, taginfo) + endif + endfor + + " Process scoped tags + let processedtags = [] + if has_key(typeinfo, 'kind2scope') + call s:LogDebugMessage('Processing scoped tags') + + let scopedtags = [] + let is_scoped = 'has_key(typeinfo.kind2scope, v:val.fields.kind) || + \ has_key(v:val, "scope")' + let scopedtags += filter(copy(fileinfo.tags), is_scoped) + call filter(fileinfo.tags, '!(' . is_scoped . ')') + + call s:AddScopedTags(scopedtags, processedtags, {}, 0, + \ typeinfo, fileinfo) + + if !empty(scopedtags) + echoerr 'Tagbar: ''scopedtags'' not empty after processing,' + \ 'this should never happen!' + \ 'Please contact the script maintainer with an example.' + endif + endif + call s:LogDebugMessage('Number of top-level tags: ' . len(processedtags)) + + " Create a placeholder tag for the 'kind' header for folding purposes + for kind in typeinfo.kinds + + let curtags = filter(copy(fileinfo.tags), + \ 'v:val.fields.kind ==# kind.short') + call s:LogDebugMessage('Processing kind: ' . kind.short . + \ ', number of tags: ' . len(curtags)) + + if empty(curtags) + continue + endif + + let kindtag = s:KindheaderTag.New(kind.long) + let kindtag.short = kind.short + let kindtag.numtags = len(curtags) + let kindtag.fileinfo = fileinfo + + for tag in curtags + let tag.parent = kindtag + endfor + endfor + + if !empty(processedtags) + call extend(fileinfo.tags, processedtags) + endif + + " Clear old folding information from previous file version to prevent leaks + call fileinfo.clearOldFolds() + + " Sort the tags + let s:compare_typeinfo = typeinfo + call fileinfo.sortTags() + + call s:known_files.put(fileinfo) +endfunction + +" s:ExecuteCtagsOnFile() {{{2 +function! s:ExecuteCtagsOnFile(fname, ftype) + call s:LogDebugMessage('ExecuteCtagsOnFile called on ' . a:fname) + + let typeinfo = s:known_types[a:ftype] + + if has_key(typeinfo, 'ctagsargs') + let ctags_args = ' ' . typeinfo.ctagsargs . ' ' + else + let ctags_args = ' -f - ' + let ctags_args .= ' --format=2 ' + let ctags_args .= ' --excmd=pattern ' + let ctags_args .= ' --fields=nksSa ' + let ctags_args .= ' --extra= ' + let ctags_args .= ' --sort=yes ' + + " Include extra type definitions + if has_key(typeinfo, 'deffile') + let ctags_args .= ' --options=' . typeinfo.deffile . ' ' + endif + + let ctags_type = typeinfo.ctagstype + + let ctags_kinds = '' + for kind in typeinfo.kinds + let ctags_kinds .= kind.short + endfor + + let ctags_args .= ' --language-force=' . ctags_type . + \ ' --' . ctags_type . '-kinds=' . ctags_kinds . ' ' + endif + + if has_key(typeinfo, 'ctagsbin') + " reset 'wildignore' temporarily in case *.exe is included in it + let wildignore_save = &wildignore + set wildignore& + let ctags_bin = expand(typeinfo.ctagsbin) + let &wildignore = wildignore_save + else + let ctags_bin = g:tagbar_ctags_bin + endif + + let ctags_cmd = s:EscapeCtagsCmd(ctags_bin, ctags_args, a:fname) + if ctags_cmd == '' + return '' + endif + + let ctags_output = s:ExecuteCtags(ctags_cmd) + + if v:shell_error || ctags_output =~ 'Warning: cannot open source file' + echoerr 'Tagbar: Could not execute ctags for ' . a:fname . '!' + echomsg 'Executed command: "' . ctags_cmd . '"' + if !empty(ctags_output) + call s:LogDebugMessage('Command output:') + call s:LogDebugMessage(ctags_output) + echomsg 'Command output:' + for line in split(ctags_output, '\n') + echomsg line + endfor + endif + return -1 + endif + + call s:LogDebugMessage('Ctags executed successfully') + return ctags_output +endfunction + +" s:ParseTagline() {{{2 +" Structure of a tag line: +" tagname<TAB>filename<TAB>expattern;"fields +" fields: <TAB>name:value +" fields that are always present: kind, line +function! s:ParseTagline(part1, part2, typeinfo, fileinfo) + let basic_info = split(a:part1, '\t') + + let taginfo = s:NormalTag.New(basic_info[0]) + let taginfo.file = basic_info[1] + + " the pattern can contain tabs and thus may have been split up, so join + " the rest of the items together again + let pattern = join(basic_info[2:], "\t") + let start = 2 " skip the slash and the ^ + let end = strlen(pattern) - 1 + if pattern[end - 1] ==# '$' + let end -= 1 + let dollar = '\$' + else + let dollar = '' + endif + let pattern = strpart(pattern, start, end - start) + let taginfo.pattern = '\V\^\C' . pattern . dollar + let prototype = substitute(pattern, '^[[:space:]]\+', '', '') + let prototype = substitute(prototype, '[[:space:]]\+$', '', '') + let taginfo.prototype = prototype + + let fields = split(a:part2, '\t') + let taginfo.fields.kind = remove(fields, 0) + for field in fields + " can't use split() since the value can contain ':' + let delimit = stridx(field, ':') + let key = strpart(field, 0, delimit) + let val = strpart(field, delimit + 1) + if len(val) > 0 + let taginfo.fields[key] = val + endif + endfor + " Needed for jsctags + if has_key(taginfo.fields, 'lineno') + let taginfo.fields.line = taginfo.fields.lineno + endif + + " Make some information easier accessible + if has_key(a:typeinfo, 'scope2kind') + for scope in keys(a:typeinfo.scope2kind) + if has_key(taginfo.fields, scope) + let taginfo.scope = scope + let taginfo.path = taginfo.fields[scope] + + let taginfo.fullpath = taginfo.path . a:typeinfo.sro . + \ taginfo.name + break + endif + endfor + let taginfo.depth = len(split(taginfo.path, '\V' . a:typeinfo.sro)) + endif + + let taginfo.fileinfo = a:fileinfo + + " Needed for folding + try + call taginfo.initFoldState() + catch /^Vim(\a\+):E716:/ " 'Key not present in Dictionary' + " The tag has a 'kind' that doesn't exist in the type definition + echoerr 'Your ctags and Tagbar configurations are out of sync!' + \ 'Please read '':help tagbar-extend''.' + endtry + + return taginfo +endfunction + +" s:AddScopedTags() {{{2 +" Recursively process tags. Unfortunately there is a problem: not all tags in +" a hierarchy are actually there. For example, in C++ a class can be defined +" in a header file and implemented in a .cpp file (so the class itself doesn't +" appear in the .cpp file and thus doesn't generate a tag). Another example +" are anonymous structures like namespaces, structs, enums, and unions, that +" also don't get a tag themselves. These tags are thus called 'pseudo-tags' in +" Tagbar. Properly parsing them is quite tricky, so try not to think about it +" too much. +function! s:AddScopedTags(tags, processedtags, parent, depth, + \ typeinfo, fileinfo) + if !empty(a:parent) + let curpath = a:parent.fullpath + let pscope = a:typeinfo.kind2scope[a:parent.fields.kind] + else + let curpath = '' + let pscope = '' + endif + + let is_cur_tag = 'v:val.depth == a:depth' + + if !empty(curpath) + " Check whether the tag is either a direct child at the current depth + " or at least a proper grandchild with pseudo-tags in between. If it + " is a direct child also check for matching scope. + let is_cur_tag .= ' && + \ (v:val.path ==# curpath || + \ match(v:val.path, ''\V\^\C'' . curpath . a:typeinfo.sro) == 0) && + \ (v:val.path ==# curpath ? (v:val.scope ==# pscope) : 1)' + endif + + let curtags = filter(copy(a:tags), is_cur_tag) + + if !empty(curtags) + call filter(a:tags, '!(' . is_cur_tag . ')') + + let realtags = [] + let pseudotags = [] + + while !empty(curtags) + let tag = remove(curtags, 0) + + if tag.path != curpath + " tag is child of a pseudo-tag, so create a new pseudo-tag and + " add all its children to it + let pseudotag = s:ProcessPseudoTag(curtags, tag, a:parent, + \ a:typeinfo, a:fileinfo) + + call add(pseudotags, pseudotag) + else + call add(realtags, tag) + endif + endwhile + + " Recursively add the children of the tags on the current level + for tag in realtags + let tag.parent = a:parent + + if !has_key(a:typeinfo.kind2scope, tag.fields.kind) + continue + endif + + if !has_key(tag, 'children') + let tag.children = [] + endif + + call s:AddScopedTags(a:tags, tag.children, tag, a:depth + 1, + \ a:typeinfo, a:fileinfo) + endfor + call extend(a:processedtags, realtags) + + " Recursively add the children of the tags that are children of the + " pseudo-tags on the current level + for tag in pseudotags + call s:ProcessPseudoChildren(a:tags, tag, a:depth, a:typeinfo, + \ a:fileinfo) + endfor + call extend(a:processedtags, pseudotags) + endif + + " Now we have to check if there are any pseudo-tags at the current level + " so we have to check for real tags at a lower level, i.e. grandchildren + let is_grandchild = 'v:val.depth > a:depth' + + if !empty(curpath) + let is_grandchild .= + \ ' && match(v:val.path, ''\V\^\C'' . curpath . a:typeinfo.sro) == 0' + endif + + let grandchildren = filter(copy(a:tags), is_grandchild) + + if !empty(grandchildren) + call s:AddScopedTags(a:tags, a:processedtags, a:parent, a:depth + 1, + \ a:typeinfo, a:fileinfo) + endif +endfunction + +" s:ProcessPseudoTag() {{{2 +function! s:ProcessPseudoTag(curtags, tag, parent, typeinfo, fileinfo) + let curpath = !empty(a:parent) ? a:parent.fullpath : '' + + let pseudoname = substitute(a:tag.path, curpath, '', '') + let pseudoname = substitute(pseudoname, '\V\^' . a:typeinfo.sro, '', '') + let pseudotag = s:CreatePseudoTag(pseudoname, a:parent, a:tag.scope, + \ a:typeinfo, a:fileinfo) + let pseudotag.children = [a:tag] + + " get all the other (direct) children of the current pseudo-tag + let ispseudochild = 'v:val.path ==# a:tag.path && v:val.scope ==# a:tag.scope' + let pseudochildren = filter(copy(a:curtags), ispseudochild) + if !empty(pseudochildren) + call filter(a:curtags, '!(' . ispseudochild . ')') + call extend(pseudotag.children, pseudochildren) + endif + + return pseudotag +endfunction + +" s:ProcessPseudoChildren() {{{2 +function! s:ProcessPseudoChildren(tags, tag, depth, typeinfo, fileinfo) + for childtag in a:tag.children + let childtag.parent = a:tag + + if !has_key(a:typeinfo.kind2scope, childtag.fields.kind) + continue + endif + + if !has_key(childtag, 'children') + let childtag.children = [] + endif + + call s:AddScopedTags(a:tags, childtag.children, childtag, a:depth + 1, + \ a:typeinfo, a:fileinfo) + endfor + + let is_grandchild = 'v:val.depth > a:depth && + \ match(v:val.path, ''^\C'' . a:tag.fullpath) == 0' + let grandchildren = filter(copy(a:tags), is_grandchild) + if !empty(grandchildren) + call s:AddScopedTags(a:tags, a:tag.children, a:tag, a:depth + 1, + \ a:typeinfo, a:fileinfo) + endif +endfunction + +" s:CreatePseudoTag() {{{2 +function! s:CreatePseudoTag(name, parent, scope, typeinfo, fileinfo) + if !empty(a:parent) + let curpath = a:parent.fullpath + let pscope = a:typeinfo.kind2scope[a:parent.fields.kind] + else + let curpath = '' + let pscope = '' + endif + + let pseudotag = s:PseudoTag.New(a:name) + let pseudotag.fields.kind = a:typeinfo.scope2kind[a:scope] + + let parentscope = substitute(curpath, a:name . '$', '', '') + let parentscope = substitute(parentscope, + \ '\V\^' . a:typeinfo.sro . '\$', '', '') + + if pscope != '' + let pseudotag.fields[pscope] = parentscope + let pseudotag.scope = pscope + let pseudotag.path = parentscope + let pseudotag.fullpath = + \ pseudotag.path . a:typeinfo.sro . pseudotag.name + endif + let pseudotag.depth = len(split(pseudotag.path, '\V' . a:typeinfo.sro)) + + let pseudotag.parent = a:parent + + let pseudotag.fileinfo = a:fileinfo + + call pseudotag.initFoldState() + + return pseudotag +endfunction + +" Sorting {{{1 +" s:SortTags() {{{2 +function! s:SortTags(tags, comparemethod) + call sort(a:tags, a:comparemethod) + + for tag in a:tags + if has_key(tag, 'children') + call s:SortTags(tag.children, a:comparemethod) + endif + endfor +endfunction + +" s:CompareByKind() {{{2 +function! s:CompareByKind(tag1, tag2) + let typeinfo = s:compare_typeinfo + + if typeinfo.kinddict[a:tag1.fields.kind] <# + \ typeinfo.kinddict[a:tag2.fields.kind] + return -1 + elseif typeinfo.kinddict[a:tag1.fields.kind] ># + \ typeinfo.kinddict[a:tag2.fields.kind] + return 1 + else + " Ignore '~' prefix for C++ destructors to sort them directly under + " the constructors + if a:tag1.name[0] ==# '~' + let name1 = a:tag1.name[1:] + else + let name1 = a:tag1.name + endif + if a:tag2.name[0] ==# '~' + let name2 = a:tag2.name[1:] + else + let name2 = a:tag2.name + endif + + if name1 <=# name2 + return -1 + else + return 1 + endif + endif +endfunction + +" s:CompareByLine() {{{2 +function! s:CompareByLine(tag1, tag2) + return a:tag1.fields.line - a:tag2.fields.line +endfunction + +" s:ToggleSort() {{{2 +function! s:ToggleSort() + let fileinfo = s:known_files.getCurrent() + if empty(fileinfo) + return + endif + + let curline = line('.') + + match none + + let s:compare_typeinfo = s:known_types[fileinfo.ftype] + + if has_key(s:compare_typeinfo, 'sort') + let s:compare_typeinfo.sort = !s:compare_typeinfo.sort + else + let g:tagbar_sort = !g:tagbar_sort + endif + + call fileinfo.sortTags() + + call s:RenderContent() + + execute curline +endfunction + +" Display {{{1 +" s:RenderContent() {{{2 +function! s:RenderContent(...) + call s:LogDebugMessage('RenderContent called') + + if a:0 == 1 + let fileinfo = a:1 + else + let fileinfo = s:known_files.getCurrent() + endif + + if empty(fileinfo) + call s:LogDebugMessage('Empty fileinfo, returning') + return + endif + + let tagbarwinnr = bufwinnr('__Tagbar__') + + if &filetype == 'tagbar' + let in_tagbar = 1 + else + let in_tagbar = 0 + let prevwinnr = winnr() + execute tagbarwinnr . 'wincmd w' + endif + + if !empty(s:known_files.getCurrent()) && + \ fileinfo.fpath ==# s:known_files.getCurrent().fpath + " We're redisplaying the same file, so save the view + call s:LogDebugMessage('Redisplaying file' . fileinfo.fpath) + let saveline = line('.') + let savecol = col('.') + let topline = line('w0') + endif + + let lazyredraw_save = &lazyredraw + set lazyredraw + let eventignore_save = &eventignore + set eventignore=all + + setlocal modifiable + + silent %delete _ + + call s:PrintHelp() + + let typeinfo = s:known_types[fileinfo.ftype] + + " Print tags + call s:PrintKinds(typeinfo, fileinfo) + + " Delete empty lines at the end of the buffer + for linenr in range(line('$'), 1, -1) + if getline(linenr) =~ '^$' + execute 'silent ' . linenr . 'delete _' + else + break + endif + endfor + + setlocal nomodifiable + + if !empty(s:known_files.getCurrent()) && + \ fileinfo.fpath ==# s:known_files.getCurrent().fpath + let scrolloff_save = &scrolloff + set scrolloff=0 + + call cursor(topline, 1) + normal! zt + call cursor(saveline, savecol) + + let &scrolloff = scrolloff_save + else + " Make sure as much of the Tagbar content as possible is shown in the + " window by jumping to the top after drawing + execute 1 + call winline() + + " Invalidate highlight cache from old file + let s:last_highlight_tline = 0 + endif + + let &lazyredraw = lazyredraw_save + let &eventignore = eventignore_save + + if !in_tagbar + execute prevwinnr . 'wincmd w' + endif +endfunction + +" s:PrintKinds() {{{2 +function! s:PrintKinds(typeinfo, fileinfo) + call s:LogDebugMessage('PrintKinds called') + + let first_tag = 1 + + for kind in a:typeinfo.kinds + let curtags = filter(copy(a:fileinfo.tags), + \ 'v:val.fields.kind ==# kind.short') + call s:LogDebugMessage('Printing kind: ' . kind.short . + \ ', number of (top-level) tags: ' . len(curtags)) + + if empty(curtags) + continue + endif + + if has_key(a:typeinfo, 'kind2scope') && + \ has_key(a:typeinfo.kind2scope, kind.short) + " Scoped tags + for tag in curtags + if g:tagbar_compact && first_tag && s:short_help + silent 0put =tag.str() + else + silent put =tag.str() + endif + + " Save the current tagbar line in the tag for easy + " highlighting access + let curline = line('.') + let tag.tline = curline + let a:fileinfo.tline[curline] = tag + + " Print children + if tag.isFoldable() && !tag.isFolded() + for ckind in a:typeinfo.kinds + let childtags = filter(copy(tag.children), + \ 'v:val.fields.kind ==# ckind.short') + if len(childtags) > 0 + " Print 'kind' header of following children + if !has_key(a:typeinfo.kind2scope, ckind.short) + silent put =' [' . ckind.long . ']' + let a:fileinfo.tline[line('.')] = tag + endif + for childtag in childtags + call s:PrintTag(childtag, 1, + \ a:fileinfo, a:typeinfo) + endfor + endif + endfor + endif + + if !g:tagbar_compact + silent put _ + endif + + let first_tag = 0 + endfor + else + " Non-scoped tags + let kindtag = curtags[0].parent + + if kindtag.isFolded() + let foldmarker = s:icon_closed + else + let foldmarker = s:icon_open + endif + + if g:tagbar_compact && first_tag && s:short_help + silent 0put =foldmarker . ' ' . kind.long + else + silent put =foldmarker . ' ' . kind.long + endif + + let curline = line('.') + let kindtag.tline = curline + let a:fileinfo.tline[curline] = kindtag + + if !kindtag.isFolded() + for tag in curtags + let str = tag.str() + silent put =' ' . str + + " Save the current tagbar line in the tag for easy + " highlighting access + let curline = line('.') + let tag.tline = curline + let a:fileinfo.tline[curline] = tag + let tag.depth = 1 + endfor + endif + + if !g:tagbar_compact + silent put _ + endif + + let first_tag = 0 + endif + endfor +endfunction + +" s:PrintTag() {{{2 +function! s:PrintTag(tag, depth, fileinfo, typeinfo) + " Print tag indented according to depth + silent put =repeat(' ', a:depth * 2) . a:tag.str() + + " Save the current tagbar line in the tag for easy + " highlighting access + let curline = line('.') + let a:tag.tline = curline + let a:fileinfo.tline[curline] = a:tag + + " Recursively print children + if a:tag.isFoldable() && !a:tag.isFolded() + for ckind in a:typeinfo.kinds + let childtags = filter(copy(a:tag.children), + \ 'v:val.fields.kind ==# ckind.short') + if len(childtags) > 0 + " Print 'kind' header of following children + if !has_key(a:typeinfo.kind2scope, ckind.short) + silent put =' ' . repeat(' ', a:depth * 2) . + \ '[' . ckind.long . ']' + let a:fileinfo.tline[line('.')] = a:tag + endif + for childtag in childtags + call s:PrintTag(childtag, a:depth + 1, + \ a:fileinfo, a:typeinfo) + endfor + endif + endfor + endif +endfunction + +" s:PrintHelp() {{{2 +function! s:PrintHelp() + if !g:tagbar_compact && s:short_help + silent 0put ='\" Press <F1> for help' + silent put _ + elseif !s:short_help + silent 0put ='\" Tagbar keybindings' + silent put ='\"' + silent put ='\" --------- General ---------' + silent put ='\" <Enter> : Jump to tag definition' + silent put ='\" <Space> : Display tag prototype' + silent put ='\"' + silent put ='\" ---------- Folds ----------' + silent put ='\" +, zo : Open fold' + silent put ='\" -, zc : Close fold' + silent put ='\" o, za : Toggle fold' + silent put ='\" *, zR : Open all folds' + silent put ='\" =, zM : Close all folds' + silent put ='\"' + silent put ='\" ---------- Misc -----------' + silent put ='\" s : Toggle sort' + silent put ='\" x : Zoom window in/out' + silent put ='\" q : Close window' + silent put ='\" <F1> : Remove help' + silent put _ + endif +endfunction + +" s:RenderKeepView() {{{2 +" The gist of this function was taken from NERDTree by Martin Grenfell. +function! s:RenderKeepView(...) + if a:0 == 1 + let line = a:1 + else + let line = line('.') + endif + + let curcol = col('.') + let topline = line('w0') + + call s:RenderContent() + + let scrolloff_save = &scrolloff + set scrolloff=0 + + call cursor(topline, 1) + normal! zt + call cursor(line, curcol) + + let &scrolloff = scrolloff_save + + redraw +endfunction + +" User actions {{{1 +" s:HighlightTag() {{{2 +function! s:HighlightTag() + let tagline = 0 + + let tag = s:GetNearbyTag() + if !empty(tag) + let tagline = tag.tline + endif + + " Don't highlight the tag again if it's the same one as last time. + " This prevents the Tagbar window from jumping back after scrolling with + " the mouse. + if tagline == s:last_highlight_tline + return + else + let s:last_highlight_tline = tagline + endif + + let eventignore_save = &eventignore + set eventignore=all + + let tagbarwinnr = bufwinnr('__Tagbar__') + let prevwinnr = winnr() + execute tagbarwinnr . 'wincmd w' + + match none + + " No tag above cursor position so don't do anything + if tagline == 0 + execute prevwinnr . 'wincmd w' + let &eventignore = eventignore_save + redraw + return + endif + + if g:tagbar_autoshowtag + call s:OpenParents(tag) + endif + + " Check whether the tag is inside a closed fold and highlight the parent + " instead in that case + let tagline = tag.getClosedParentTline() + + " Go to the line containing the tag + execute tagline + + " Make sure the tag is visible in the window + call winline() + + let foldpat = '[' . s:icon_open . s:icon_closed . ' ]' + let pattern = '/^\%' . tagline . 'l\s*' . foldpat . '[-+# ]\zs[^( ]\+\ze/' + execute 'match TagbarHighlight ' . pattern + + execute prevwinnr . 'wincmd w' + + let &eventignore = eventignore_save + + redraw +endfunction + +" s:JumpToTag() {{{2 +function! s:JumpToTag(stay_in_tagbar) + let taginfo = s:GetTagInfo(line('.'), 1) + + let autoclose = w:autoclose + + if empty(taginfo) || has_key(taginfo, 'numtags') + return + endif + + let tagbarwinnr = winnr() + + let eventignore_save = &eventignore + set eventignore=all + + " This elaborate construct will try to switch to the correct + " buffer/window; if the buffer isn't currently shown in a window it will + " open it in the first window with a non-special buffer in it + wincmd p + let filebufnr = bufnr(taginfo.fileinfo.fpath) + if bufnr('%') != filebufnr + let filewinnr = bufwinnr(filebufnr) + if filewinnr != -1 + execute filewinnr . 'wincmd w' + else + for i in range(1, winnr('$')) + execute i . 'wincmd w' + if &buftype == '' + execute 'buffer ' . filebufnr + break + endif + endfor + endif + " To make ctrl-w_p work we switch between the Tagbar window and the + " correct window once + execute tagbarwinnr . 'wincmd w' + wincmd p + endif + + " Mark current position so it can be jumped back to + mark ' + + " Jump to the line where the tag is defined. Don't use the search pattern + " since it doesn't take the scope into account and thus can fail if tags + " with the same name are defined in different scopes (e.g. classes) + execute taginfo.fields.line + + " If the file has been changed but not saved, the tag may not be on the + " saved line anymore, so search for it in the vicinity of the saved line + if match(getline('.'), taginfo.pattern) == -1 + let interval = 1 + let forward = 1 + while search(taginfo.pattern, 'W' . forward ? '' : 'b') == 0 + if !forward + if interval > line('$') + break + else + let interval = interval * 2 + endif + endif + let forward = !forward + endwhile + endif + + " If the tag is on a different line after unsaved changes update the tag + " and file infos/objects + let curline = line('.') + if taginfo.fields.line != curline + let taginfo.fields.line = curline + let taginfo.fileinfo.fline[curline] = taginfo + endif + + " Center the tag in the window + normal! z. + + if foldclosed('.') != -1 + .foldopen! + endif + + redraw + + let &eventignore = eventignore_save + + if a:stay_in_tagbar + call s:HighlightTag() + execute tagbarwinnr . 'wincmd w' + elseif g:tagbar_autoclose || autoclose + call s:CloseWindow() + else + call s:HighlightTag() + endif +endfunction + +" s:ShowPrototype() {{{2 +function! s:ShowPrototype() + let taginfo = s:GetTagInfo(line('.'), 1) + + if empty(taginfo) + return + endif + + echo taginfo.getPrototype() +endfunction + +" s:ToggleHelp() {{{2 +function! s:ToggleHelp() + let s:short_help = !s:short_help + + " Prevent highlighting from being off after adding/removing the help text + match none + + call s:RenderContent() + + execute 1 + redraw +endfunction + +" s:GotoNextToplevelTag() {{{2 +function! s:GotoNextToplevelTag(direction) + let curlinenr = line('.') + let newlinenr = line('.') + + if a:direction == 1 + let range = range(line('.') + 1, line('$')) + else + let range = range(line('.') - 1, 1, -1) + endif + + for tmplinenr in range + let taginfo = s:GetTagInfo(tmplinenr, 0) + + if empty(taginfo) + continue + elseif empty(taginfo.parent) + let newlinenr = tmplinenr + break + endif + endfor + + if curlinenr != newlinenr + execute newlinenr + call winline() + endif + + redraw +endfunction + +" Folding {{{1 +" s:OpenFold() {{{2 +function! s:OpenFold() + let fileinfo = s:known_files.getCurrent() + if empty(fileinfo) + return + endif + + let curline = line('.') + + let tag = s:GetTagInfo(curline, 0) + if empty(tag) + return + endif + + call tag.openFold() + + call s:RenderKeepView() +endfunction + +" s:CloseFold() {{{2 +function! s:CloseFold() + let fileinfo = s:known_files.getCurrent() + if empty(fileinfo) + return + endif + + match none + + let curline = line('.') + + let curtag = s:GetTagInfo(curline, 0) + if empty(curtag) + return + endif + + let newline = curtag.closeFold() + + call s:RenderKeepView(newline) +endfunction + +" s:ToggleFold() {{{2 +function! s:ToggleFold() + let fileinfo = s:known_files.getCurrent() + if empty(fileinfo) + return + endif + + match none + + let curtag = s:GetTagInfo(line('.'), 0) + if empty(curtag) + return + endif + + let newline = line('.') + + if curtag.isKindheader() + call curtag.toggleFold() + elseif curtag.isFoldable() + if curtag.isFolded() + call curtag.openFold() + else + let newline = curtag.closeFold() + endif + else + let newline = curtag.closeFold() + endif + + call s:RenderKeepView(newline) +endfunction + +" s:SetFoldLevel() {{{2 +function! s:SetFoldLevel(level) + if a:level < 0 + echoerr 'Foldlevel can''t be negative' + return + endif + + let fileinfo = s:known_files.getCurrent() + if empty(fileinfo) + return + endif + + call s:SetFoldLevelRecursive(fileinfo, fileinfo.tags, a:level) + + let typeinfo = s:known_types[fileinfo.ftype] + + " Apply foldlevel to 'kind's + if a:level == 0 + for kind in typeinfo.kinds + call fileinfo.closeKindFold(kind) + endfor + else + for kind in typeinfo.kinds + call fileinfo.openKindFold(kind) + endfor + endif + + let fileinfo.foldlevel = a:level + + call s:RenderContent() +endfunction + +" s:SetFoldLevelRecursive() {{{2 +" Apply foldlevel to normal tags +function! s:SetFoldLevelRecursive(fileinfo, tags, level) + for tag in a:tags + if tag.depth >= a:level + call tag.setFolded(1) + else + call tag.setFolded(0) + endif + + if has_key(tag, 'children') + call s:SetFoldLevelRecursive(a:fileinfo, tag.children, a:level) + endif + endfor +endfunction + +" s:OpenParents() {{{2 +function! s:OpenParents(...) + let tagline = 0 + + if a:0 == 1 + let tag = a:1 + else + let tag = s:GetNearbyTag() + endif + + call tag.openParents() + + call s:RenderKeepView() +endfunction + +" Helper functions {{{1 +" s:CleanUp() {{{2 +function! s:CleanUp() + silent autocmd! TagbarAutoCmds + + unlet s:is_maximized + unlet s:compare_typeinfo + unlet s:short_help +endfunction + +" s:CleanupFileinfo() {{{2 +function! s:CleanupFileinfo(fname) + call s:known_files.rm(a:fname) +endfunction + +" s:QuitIfOnlyWindow() {{{2 +function! s:QuitIfOnlyWindow() + " Before quitting Vim, delete the tagbar buffer so that + " the '0 mark is correctly set to the previous buffer. + if winbufnr(2) == -1 + " Check if there is more than one tab page + if tabpagenr('$') == 1 + bdelete + quit + else + close + endif + endif +endfunction + +" s:AutoUpdate() {{{2 +function! s:AutoUpdate(fname) + call s:LogDebugMessage('AutoUpdate called on ' . a:fname) + + " Don't do anything if tagbar is not open or if we're in the tagbar window + let tagbarwinnr = bufwinnr('__Tagbar__') + if tagbarwinnr == -1 || &filetype == 'tagbar' + call s:LogDebugMessage('Tagbar window not open or in Tagbar window') + return + endif + + " Only consider the main filetype in cases like 'python.django' + let ftype = get(split(&filetype, '\.'), 0, '') + call s:LogDebugMessage('Vim filetype: ' . &filetype . + \ ', sanitized filetype: ' . ftype) + + " Don't do anything if the file isn't supported + if !s:IsValidFile(a:fname, ftype) + call s:LogDebugMessage('Not a valid file, stopping processing') + return + endif + + " Process the file if it's unknown or the information is outdated + " Also test for entries that exist but are empty, which will be the case + " if there was an error during the ctags execution + if s:known_files.has(a:fname) && !empty(s:known_files.get(a:fname)) + if s:known_files.get(a:fname).mtime != getftime(a:fname) + call s:LogDebugMessage('Filedata outdated, updating ' . a:fname) + call s:ProcessFile(a:fname, ftype) + endif + elseif !s:known_files.has(a:fname) + call s:LogDebugMessage('Unknown file, processing ' . a:fname) + call s:ProcessFile(a:fname, ftype) + endif + + let fileinfo = s:known_files.get(a:fname) + + " If we don't have an entry for the file by now something must have gone + " wrong, so don't change the tagbar content + if empty(fileinfo) + call s:LogDebugMessage('fileinfo empty after processing: ' . a:fname) + return + endif + + " Display the tagbar content + call s:RenderContent(fileinfo) + + " Call setCurrent after rendering so RenderContent can check whether the + " same file is redisplayed + if !empty(fileinfo) + call s:LogDebugMessage('Setting current file to ' . a:fname) + call s:known_files.setCurrent(fileinfo) + endif + + call s:HighlightTag() + call s:LogDebugMessage('AutoUpdate finished successfully') +endfunction + +" s:IsValidFile() {{{2 +function! s:IsValidFile(fname, ftype) + if a:fname == '' || a:ftype == '' + call s:LogDebugMessage('Empty filename or type') + return 0 + endif + + if !filereadable(a:fname) + call s:LogDebugMessage('File not readable') + return 0 + endif + + if !has_key(s:known_types, a:ftype) + call s:LogDebugMessage('Unsupported filetype: ' . a:ftype) + return 0 + endif + + return 1 +endfunction + +" s:EscapeCtagsCmd() {{{2 +" Assemble the ctags command line in a way that all problematic characters are +" properly escaped and converted to the system's encoding +" Optional third parameter is a file name to run ctags on +function! s:EscapeCtagsCmd(ctags_bin, args, ...) + call s:LogDebugMessage('EscapeCtagsCmd called') + call s:LogDebugMessage('ctags_bin: ' . a:ctags_bin) + call s:LogDebugMessage('ctags_args: ' . a:args) + + if exists('+shellslash') + let shellslash_save = &shellslash + set noshellslash + endif + + if a:0 == 1 + let fname = shellescape(a:1) + else + let fname = '' + endif + + let ctags_cmd = shellescape(a:ctags_bin) . ' ' . a:args . ' ' . fname + + if exists('+shellslash') + let &shellslash = shellslash_save + endif + + " Needed for cases where 'encoding' is different from the system's + " encoding + if g:tagbar_systemenc != &encoding + let ctags_cmd = iconv(ctags_cmd, &encoding, g:tagbar_systemenc) + elseif $LANG != '' + let ctags_cmd = iconv(ctags_cmd, &encoding, $LANG) + endif + + call s:LogDebugMessage('Escaped ctags command: ' . ctags_cmd) + + if ctags_cmd == '' + echoerr 'Tagbar: Encoding conversion failed!' + \ 'Please make sure your system is set up correctly' + \ 'and that Vim is compiled with the "iconv" feature.' + endif + + return ctags_cmd +endfunction + +" s:ExecuteCtags() {{{2 +" Execute ctags with necessary shell settings +" Partially based on the discussion at +" http://vim.1045645.n5.nabble.com/bad-default-shellxquote-in-Widows-td1208284.html +function! s:ExecuteCtags(ctags_cmd) + if exists('+shellslash') + let shellslash_save = &shellslash + set noshellslash + endif + + if &shell =~ 'cmd\.exe' + let shellxquote_save = &shellxquote + set shellxquote=\" + let shellcmdflag_save = &shellcmdflag + set shellcmdflag=/s\ /c + endif + + let ctags_output = system(a:ctags_cmd) + + if &shell =~ 'cmd\.exe' + let &shellxquote = shellxquote_save + let &shellcmdflag = shellcmdflag_save + endif + + if exists('+shellslash') + let &shellslash = shellslash_save + endif + + return ctags_output +endfunction + +" s:GetTagInfo() {{{2 +" Return the info dictionary of the tag on the specified line. If the line +" does not contain a valid tag (for example because it is empty or only +" contains a pseudo-tag) return an empty dictionary. +function! s:GetTagInfo(linenr, ignorepseudo) + let fileinfo = s:known_files.getCurrent() + + if empty(fileinfo) + return {} + endif + + " Don't do anything in empty and comment lines + let curline = getline(a:linenr) + if curline =~ '^\s*$' || curline[0] == '"' + return {} + endif + + " Check if there is a tag on the current line + if !has_key(fileinfo.tline, a:linenr) + return {} + endif + + let taginfo = fileinfo.tline[a:linenr] + + " Check if the current tag is not a pseudo-tag + if a:ignorepseudo && taginfo.isPseudoTag() + return {} + endif + + return taginfo +endfunction + +" s:GetNearbyTag() {{{2 +" Get the tag info for a file near the cursor in the current file +function! s:GetNearbyTag() + let fileinfo = s:known_files.getCurrent() + + let curline = line('.') + let tag = {} + + " If a tag appears in a file more than once (for example namespaces in + " C++) only one of them has a 'tline' entry and can thus be highlighted. + " The only way to solve this would be to go over the whole tag list again, + " making everything slower. Since this should be a rare occurence and + " highlighting isn't /that/ important ignore it for now. + for line in range(curline, 1, -1) + if has_key(fileinfo.fline, line) + let tag = fileinfo.fline[line] + break + endif + endfor + + return tag +endfunction + +" s:CheckMouseClick() {{{2 +function! s:CheckMouseClick() + let line = getline('.') + let curcol = col('.') + + if (match(line, s:icon_open . '[-+ ]') + 1) == curcol + call s:CloseFold() + elseif (match(line, s:icon_closed . '[-+ ]') + 1) == curcol + call s:OpenFold() + elseif g:tagbar_singleclick + call s:JumpToTag(0) + endif +endfunction + +" s:DetermineFiletype() {{{2 +function! s:DetectFiletype(bufnr) + " Filetype has already been detected for loaded buffers, but not + " necessarily for unloaded ones + let ftype = getbufvar(a:bufnr, '&filetype') + + if bufloaded(a:bufnr) + return ftype + endif + + if ftype != '' + return ftype + endif + + " Unloaded buffer with non-detected filetype, need to detect filetype + " manually + let bufname = bufname(a:bufnr) + + let eventignore_save = &eventignore + set eventignore=FileType + let filetype_save = &filetype + + exe 'doautocmd filetypedetect BufRead ' . bufname + let ftype = &filetype + + let &filetype = filetype_save + let &eventignore = eventignore_save + + return ftype +endfunction + + +" TagbarBalloonExpr() {{{2 +function! TagbarBalloonExpr() + let taginfo = s:GetTagInfo(v:beval_lnum, 1) + + if empty(taginfo) + return + endif + + return taginfo.getPrototype() +endfunction + +" TagbarGenerateStatusline() {{{2 +function! TagbarGenerateStatusline() + if g:tagbar_sort + let text = '[Name]' + else + let text = '[Order]' + endif + + if !empty(s:known_files.getCurrent()) + let filename = fnamemodify(s:known_files.getCurrent().fpath, ':t') + let text .= ' ' . filename + endif + + return text +endfunction + +" Debugging {{{1 +" s:StartDebug() {{{2 +function! s:StartDebug(filename) + if empty(a:filename) + let s:debug_file = 'tagbardebug.log' + else + let s:debug_file = a:filename + endif + + " Empty log file + exe 'redir! > ' . s:debug_file + redir END + + " Check whether the log file could be created + if !filewritable(s:debug_file) + echomsg 'Tagbar: Unable to create log file ' . s:debug_file + let s:debug_file = '' + return + endif + + let s:debug = 1 +endfunction + +" s:StopDebug() {{{2 +function! s:StopDebug() + let s:debug = 0 + let s:debug_file = '' +endfunction + +" s:LogDebugMessage() {{{2 +function! s:LogDebugMessage(msg) + if s:debug + exe 'redir >> ' . s:debug_file + silent echon strftime('%H:%M:%S') . ': ' . a:msg . "\n" + redir END + endif +endfunction + +" Autoload functions {{{1 +function! tagbar#ToggleWindow() + call s:ToggleWindow() +endfunction + +function! tagbar#OpenWindow(...) + let flags = a:0 > 0 ? a:1 : '' + call s:OpenWindow(flags) +endfunction + +function! tagbar#CloseWindow() + call s:CloseWindow() +endfunction + +function! tagbar#SetFoldLevel(...) + call s:SetFoldLevel(a:1) +endfunction + +function! tagbar#OpenParents() + call s:OpenParents() +endfunction + +function! tagbar#StartDebug(...) + let filename = a:0 > 0 ? a:1 : '' + call s:StartDebug(filename) +endfunction + +function! tagbar#StopDebug() + call s:StopDebug() +endfunction + +function! tagbar#RestoreSession() + call s:RestoreSession() +endfunction + +" Automatically open Tagbar if one of the open buffers contains a supported +" file +function! tagbar#autoopen() + call s:Init() + + for bufnr in range(1, bufnr('$')) + if buflisted(bufnr) + let ftype = s:DetectFiletype(bufnr) + if s:IsValidFile(bufname(bufnr), ftype) + call s:OpenWindow('') + return + endif + endif + endfor +endfunction + +" Modeline {{{1 +" vim: ts=8 sw=4 sts=4 et foldenable foldmethod=marker foldcolumn=1 diff --git a/vim/autoload/togglebg.vim b/vim/autoload/togglebg.vim new file mode 100644 index 0000000..108511f --- /dev/null +++ b/vim/autoload/togglebg.vim @@ -0,0 +1,55 @@ +" Toggle Background +" Modified: 2011 Apr 29 +" Maintainer: Ethan Schoonover +" License: OSI approved MIT license + +if exists("g:loaded_togglebg") + finish +endif +let g:loaded_togglebg = 1 + +" noremap is a bit misleading here if you are unused to vim mapping. +" in fact, there is remapping, but only of script locally defined remaps, in +" this case <SID>TogBG. The <script> argument modifies the noremap scope in +" this regard (and the noremenu below). +nnoremap <unique> <script> <Plug>ToggleBackground <SID>TogBG +inoremap <unique> <script> <Plug>ToggleBackground <ESC><SID>TogBG<ESC>a +vnoremap <unique> <script> <Plug>ToggleBackground <ESC><SID>TogBG<ESC>gv +nnoremenu <script> Window.Toggle\ Background <SID>TogBG +inoremenu <script> Window.Toggle\ Background <ESC><SID>TogBG<ESC>a +vnoremenu <script> Window.Toggle\ Background <ESC><SID>TogBG<ESC>gv +tmenu Window.Toggle\ Background Toggle light and dark background modes +nnoremenu <script> ToolBar.togglebg <SID>TogBG +inoremenu <script> ToolBar.togglebg <ESC><SID>TogBG<ESC>a +vnoremenu <script> ToolBar.togglebg <ESC><SID>TogBG<ESC>gv +tmenu ToolBar.togglebg Toggle light and dark background modes +noremap <SID>TogBG :call <SID>TogBG()<CR> + +function! s:TogBG() + let &background = ( &background == "dark"? "light" : "dark" ) + if exists("g:colors_name") + exe "colorscheme " . g:colors_name + endif +endfunction + +if !exists(":ToggleBG") + command ToggleBG :call s:TogBG() +endif + +function! ToggleBackground() + echo "Please update your ToggleBackground mapping. ':help togglebg' for information." +endfunction + +function! togglebg#map(mapActivation) + try + exe "silent! nmap <unique> ".a:mapActivation." <Plug>ToggleBackground" + exe "silent! imap <unique> ".a:mapActivation." <Plug>ToggleBackground" + exe "silent! vmap <unique> ".a:mapActivation." <Plug>ToggleBackground" + finally + return 0 + endtry +endfunction + +if !exists("no_plugin_maps") && !hasmapto('<Plug>ToggleBackground') + call togglebg#map("<F5>") +endif |