+" Script: table.vim
+" Version: 0.1
+"
+" Maintainer: Usman Latif Email: latif@techuser.net
+" Webpage: http://www.techuser.net
+"
+" Description:
+" This script defines maps for easier editing and alignmnet of tables.
+" For usage and installation instructions consult the documentation
+" files that came with this script. In case you are missing the
+" documentation files, download a complete distribution of the files
+" from http://www.techuser.net/files
+
+
+map <silent> <Leader>tt :call TableToggle()<CR>
+map <silent> <Leader>th :call TableHeading()<CR>
+map <silent> <Leader>ta :call TableAlign()<CR>
+
+let s:tablemode = 0
+let s:heading = ''
+let s:fieldsep = ' \{2,}'
+
+" Function: TableHeading
+" Args: None
+"
+" use current line as the heading line of the table
+" current line should be non-empty
+
+func! TableHeading()
+ " get heading line and store it in a script variable
+ let s:heading = TrimWS(ExpandTabs(getline(".")))
+
+ if !ValidHeading(s:heading)
+ return
+ endif
+
+ " map keys to invoke table navigation functions
+ call EnableMaps()
+
+ let s:tablemode = 1
+endfunc
+
+" Function: ValidHeading
+" Args: None
+" Return: boolean
+"
+" returns 1 if heading is valid, i.e., non-whitespace
+" returns 0 otherwise
+
+func! ValidHeading(heading)
+ " heading line empty ==> invalid heading
+ let l:str = a:heading
+ if strlen(str) == matchend(str,'^ *')
+ return 0
+ endif
+ return 1
+endfunc
+
+" Function: TableToggle
+" Args: None
+"
+" Toggle Table Mode
+" Enable/Disable maps for tablemode keys
+
+func! TableToggle()
+
+ if !ValidHeading(s:heading)
+ return
+ endif
+
+ " enable/disable maps
+ if s:tablemode
+ call DisableMaps()
+ else
+ call EnableMaps()
+ endif
+
+ " toggle tablemode
+ let s:tablemode = !s:tablemode
+endfunc
+
+" Function: Enable Maps
+" Args: None
+"
+" Enable maps for tablemode keys
+
+func! EnableMaps()
+ nnoremap <silent> <Tab> :call NextField(0)<CR>
+ inoremap <silent> <Tab> <C-O>:call NextField(1)<CR>
+ nnoremap <silent> <S-Tab> :call PrevField()<CR>
+ inoremap <silent> <S-Tab> <C-O>:call PrevField()<CR>
+endfunc
+
+" Function: Disable Maps
+" Args: None
+"
+" Disable maps for tablemode keys
+
+func! DisableMaps()
+ nunmap <Tab>
+ iunmap <Tab>
+ nunmap <S-Tab>
+ iunmap <S-Tab>
+endfunc
+
+
+" Function: TableAlign
+" Args: None
+" Description: align the fields of the row with the fields of the heading
+
+func! TableAlign()
+ if !s:tablemode
+ return
+ endif
+ let temp = ""
+ let linetext = TrimWS(ExpandTabs(getline('.')))
+
+ let nfhead = LenWS(s:heading,0) + 1
+ let nftext = LenWS(linetext,0) + 1
+ let error = 0
+
+ while 1
+ " flag error if current field too big to fit
+ if (nfhead - strlen(temp)) <= 1 && strlen(temp) != 0
+ let error = 1
+ break
+ endif
+
+ " pad to next field of heading and add contents of the next text
+ " field after that
+ let temp = temp . Replicate(' ',nfhead - strlen(temp)-1) . Gettext(linetext,nftext-1)
+
+ let nfhead = NextFieldPos(s:heading,s:fieldsep,nfhead)
+ let nftext = NextFieldPos(linetext,s:fieldsep,nftext)
+
+ " If no next field exit loop
+ if nfhead == 0 || nftext == 0
+ " flag error if row to be aligned has more fields than heading
+ if nftext != 0
+ let error = 1
+ endif
+ break
+ endif
+ endwhile
+ if !error && temp != linetext
+ call setline('.',temp)
+ endif
+endfunc
+
+
+" Function: PrevField
+" Args: None
+"
+" position the cursor at the start of the prev field position
+
+func! PrevField()
+ let nextpos = 1
+ let lastpos = 1
+ let pos = col('.')
+ let linenum = line('.')
+ let fstfield = LenWS(s:heading,0) + 1
+
+ while nextpos != 0
+ let lastpos = nextpos
+ let nextpos = NextFieldPos(s:heading,s:fieldsep,nextpos)
+ if pos > lastpos && (pos <= nextpos || nextpos == 0)
+ let pos = lastpos
+ endif
+ endwhile
+
+ if pos <= fstfield && linenum != 1 && col('.') <= fstfield
+ let linenum = linenum - 1
+ let pos = lastpos
+ endif
+
+ call cursor(linenum,pos)
+endfunc
+
+
+" Function: NextField
+" Args: curmode
+"
+" position the cursor at the start of next field position
+" pad the current line with spaces if needed when in insertion
+" or replace mode
+
+func! NextField(curmode)
+ let l:pos = Max(col('.') - 2,0)
+ let l:startnext = NextFieldPos(s:heading,s:fieldsep,pos)
+ let l:linenum = line('.')
+
+ "If no nextfield on line goto next line
+ "append an empty line if in insert/replace mode
+ if startnext == 0
+ if a:curmode
+ call append(linenum,'')
+ endif
+ let linenum = linenum+1
+ let startnext = LenWS(s:heading,0) + 1
+ endif
+
+ let l:linetext = ExpandTabs(getline(linenum))
+ let l:linelen = strlen(linetext)
+
+ "If padding required
+ if linelen < startnext
+ let linetext = linetext . Replicate(' ',startnext-linelen+1)
+ call setline(linenum,linetext)
+ endif
+
+ if linenum > line('$')
+ let linenum = line('$')
+ let startnext = col('.')
+ endif
+ call cursor(linenum,startnext)
+endfunc
+
+
+" Function: NextFieldPos
+" Args: string,pattern,startposition
+"
+" returns the position of the end of field in which pos
+" is contained
+
+func! NextFieldPos(str,pat,pos)
+ return matchend(a:str,a:pat,a:pos) + 1
+endfunc
+
+
+" Function: Gettext
+" Args: str, pos
+" Description: Extract the text contents of a field from the
+" string str, starting at position pos
+
+func! Gettext(str,pos)
+ let endpos = match(a:str,s:fieldsep,a:pos)
+ if endpos == -1
+ let endpos = strlen(a:str) - 1
+ endif
+ return strpart(a:str,a:pos,endpos - a:pos + 1)
+endfunc
+
+
+" Function: TrimWS
+" Args: str
+" Description: Trim any WS at the end of the string str
+
+func! TrimWS(str)
+ let len = match(a:str,' \{1,}$',0)
+ if len == -1
+ return a:str
+ else
+ return strpart(a:str,0,len)
+ endif
+endfunc
+
+
+" Function: LenWS
+" Args: str, startpos
+" Description: Length of contiguous whitespace starting at
+" position startpos in string str
+
+func! LenWS(str,startpos)
+ let i = 0
+ while a:str[a:startpos+i] == ' '
+ let i = i + 1
+ endwhile
+ return i
+endfunc
+
+
+" Function: Replicate
+" Args: str,cnt
+"
+" Repeat the given string cnt number of times
+
+func! Replicate(str,cnt)
+ let l:temp = ""
+
+ let l:i = 0
+ while i < a:cnt
+ let temp = temp . a:str
+ let i = i + 1
+ endwhile
+
+ return temp
+endfunc
+
+
+" Function: ExpandTabs
+" Args: str
+" Return value: string
+"
+" Expand all tabs in the string to spaces
+" according to tabstop value
+
+func! ExpandTabs(str)
+ let l:str = a:str
+ let l:temp = ""
+
+ let l:i = 0
+ while i < strlen(str)
+ if str[i] == "\t"
+ let temp = temp . Replicate(' ',&tabstop)
+ else
+ let temp = temp . str[i]
+ endif
+ let i = i + 1
+ endwhile
+
+ return temp
+endfunc
+
+" Function: Max
+" Args: x,y
+" Description: return the max of x and y
+
+func! Max(x,y)
+ if a:x >= a:y
+ return a:x
+ else
+ return a:y
+ endif
+endfunc