" Function to left and rigt align text.
"
" Written by:	Preben "Peppe" Guldberg <c928400@student.dtu.dk>
" Last change:	1998 Jul 11 by Bram Moolenaar
"
" 
" function Justify( [ textwidth [, maxspaces ] ] )
"
" Justify() will left and right align a line  by  filling  in  an  appropriate
" amount of spaces.  Extra  spaces  are  added  to  existing  spaces  starting
" from the  right  side  of  the  line  (these  lines  have  been  justified).
"
" The function takes two number arguments:
"
" textwidth argument
" -------------------
" If not specified, the value of the 'textwidth' option is used. If
" 'textwidth' is zero a value of 80 is used.
"
" Additionally the arguments 'tw' and '' are accepted. The value of
" 'textwidth' will be used. These are handy, if you just want to
" specify the maxspaces argument.
"
" maxspaces argument
" ------------------
" If specified, alignment will only be done, if the longest space run
" after alignment is no longer than maxspaces.
"
" An argument of '' is accepted, should the user like to specify both
" arguments.
"
" Todo:
" - indent should be kept
"
"
" Notes:
"
" If the line, adjusted for space runs and leading/trailing whitespace,
" is wider than the used textwidth, the line will be left untouched
" (no whitespace removed). This should be equivalent to the behaviour
" of :left, :right and :center.
"
" If the resulting line is shorter than the used textwidth it is left
" untouched.
"
" All space runs in the line are truncated before the alignment is
" carried out.
"
" If you have set 'noexpandtab', :retab! is used to replace space runs
" with whitespace using the value of 'tabstop'. This should be
" conformant with :left, :right and :center.
"
" If joinspaces is set, an extra space is added after '.', '?' and
" '!'. If 'cpooptions' include 'j', extra space is only added after
" '.'.  (This may on occasion conflict with maxspaces.)
"
"
" Related mappings:
"
" Mappings that will align text using the current text width, using at
" most four spaces in a space run.
nmap _j :%call Justify('tw',4)<CR>
vmap _j :call Justify('tw',4)<CR>
"
" Mappings that will remove space runs and format lines (might be
" useful prior to aligning the text).
nmap ,gq :%s/\s\+/ /g<CR>gq1G
vmap ,gq :s/\s\+/ /g<CR>gvgq

" Error function
function Justify_error(message)
    echohl Error
    echo "Justify( [tw , [maxspaces] ] ):   " . a:message
    echohl None
endfunction

" Now for the real thing
function Justify(...)
    
    if a:0 > 2
        call Justify_error("Too many arguments (max 2)")
        return 1
    endif

    " Set textwidth (accept 'tw' and '' as arguments)
    if a:0 >= 1
        if a:1 =~ '^\(tw\)\=$'
            let tw = &tw
        elseif a:1 =~ '^[0-9]\+$'
            let tw = a:1
        else
            call Justify_error("first argument must be a number, '' or 'tw'")
            return 2
        endif
    else
        let tw = &tw
    endif
    if tw == 0
        let tw = 80
    endif

    " Set maximum number of spaces between WORDs
    if a:0 == 2
        if a:2 == ''
            let maxspaces = tw
        elseif a:2 =~ '^[0-9]\+$'
            let maxspaces = a:2
        else
            call Justify_error("second argument must be a number or ''")
            return 3
        endif
    else
        let maxspaces = tw
    endif
    if maxspaces <= 1
        call Justify_error("meaningless if second argument is 0 or 1")
        return 4
    endif

    " Avoid substitution reports
    let report = &report
    set report=1000000

    " Trim leading, trailing and running whitespace
    let str = substitute( getline("."), '^\s\+', '', '')
    let str = substitute( str, '\s\+$', '', 'g')
    let str = substitute( str, '\s\+', ' ', 'g')

    " Check 'joinspaces' and 'cpo'
    if &js == 1
        if &cpo =~ 'j'
            let str = substitute( str, '\. ', '.  ', 'g')
        else
            let str = substitute( str, '\([.?!] \)', '\1 ', 'g')
        endif
    endif

    if strlen(str) < tw     " Extra spaces can be added
        " How many spaces should be added
        let s_add = tw - strlen(str)
        let s_nr  = strlen( substitute( str, '\S', '', 'g') )
        let s_dup = s_add / s_nr
        let s_mod = s_add % s_nr

        " Test if the changed line fits with tw
        if 0 <= (strlen(str) + (maxspaces - 1)*s_nr) - tw

            " Duplicate spaces
            while s_dup > 0
                let str = substitute( str, '\( \+\)', ' \1', 'g')
                let s_dup = s_dup - 1
            endwhile

            " Now replace the line
            let str = substitute( str, '\\', '\\\\', 'g')
            let str = substitute( str, '/', '\\/', 'g')
            exec 's/.*/' . str . '/'

            " Add extra spaces from the end
            norm $
            while s_mod > 0
                norm ByhP
                let s_mod = s_mod - 1
            endwhile
            norm ^

            " Convert to whitespace
            if &et == 0
                :.retab!
            endif

        endif   " Change of line
    endif   " Possible change

    let &report = report
endfunction

" EOF	vim: tw=78 ts=4 sw=4 sts=4 et ai
