Merge branch 'vim-with-runtime' into feat/quickfix-title
[vim_extended.git] / runtime / macros / justify.vim
blobaa4a9ca585beeea9c0397bc86bc6cbce4fcac8de
1 " Function to left and rigt align text.
3 " Written by:   Preben "Peppe" Guldberg <c928400@student.dtu.dk>
4 " Created:      980806 14:13 (or around that time anyway)
5 " Revised:      001103 00:36 (See "Revisions" below)
8 " function Justify( [ textwidth [, maxspaces [, indent] ] ] )
10 " Justify()  will  left  and  right  align  a  line  by  filling  in  an
11 " appropriate amount of spaces.  Extra  spaces  are  added  to  existing
12 " spaces starting from the right side of the line.  As an  example,  the
13 " following documentation has been justified.
15 " The function takes the following arguments:
17 " textwidth argument
18 " ------------------
19 " If not specified, the value of the 'textwidth'  option  is  used.   If
20 " 'textwidth' is zero a value of 80 is used.
22 " Additionally the arguments 'tw' and '' are  accepted.   The  value  of
23 " 'textwidth' will be used. These are handy, if you just want to specify
24 " the maxspaces argument.
26 " maxspaces argument
27 " ------------------
28 " If specified, alignment will only be done, if the  longest  space  run
29 " after alignment is no longer than maxspaces.
31 " An argument of '' is accepted, should the user  like  to  specify  all
32 " arguments.
34 " To aid user defined commands, negative  values  are  accepted  aswell.
35 " Using a negative value specifies the default behaviour: any length  of
36 " space runs will be used to justify the text.
38 " indent argument
39 " ---------------
40 " This argument specifies how a line should be indented. The default  is
41 " to keep the current indentation.
43 " Negative  values:  Keep  current   amount   of   leading   whitespace.
44 " Positive values: Indent all lines with leading whitespace  using  this
45 " amount of whitespace.
47 " Note that the value 0, needs to be quoted as  a  string.   This  value
48 " leads to a left flushed text.
50 " Additionally units of  'shiftwidth'/'sw'  and  'tabstop'/'ts'  may  be
51 " added. In this case, if the value of indent is positive, the amount of
52 " whitespace to be  added  will  be  multiplied  by  the  value  of  the
53 " 'shiftwidth' and 'tabstop' settings.  If these  units  are  used,  the
54 "  argument must  be  given  as  a  string,  eg.   Justify('','','2sw').
56 " If the values of 'sw' or 'tw' are negative, they  are  treated  as  if
57 " they were 0, which means that the text is flushed left.  There  is  no
58 " check if a negative number prefix is used to  change  the  sign  of  a
59 " negative 'sw' or 'ts' value.
61 " As with the other arguments,  ''  may  be  used  to  get  the  default
62 " behaviour.
65 " Notes:
67 " If the line, adjusted for space runs and leading/trailing  whitespace,
68 " is wider than the used textwidth, the line will be left untouched  (no
69 " whitespace removed).  This should be equivalent to  the  behaviour  of
70 " :left, :right and :center.
72 " If the resulting line is shorter than the used textwidth  it  is  left
73 " untouched.
75 " All space runs in the line  are  truncated  before  the  alignment  is
76 " carried out.
78 " If you have set 'noexpandtab', :retab!  is used to replace space  runs
79 "  with whitespace  using  the  value  of  'tabstop'.   This  should  be
80 " conformant with :left, :right and :center.
82 " If joinspaces is set, an extra space is added after '.', '?' and  '!'.
83 " If 'cpooptions' include 'j', extra space  is  only  added  after  '.'.
84 " (This may on occasion conflict with maxspaces.)
87 " Related mappings:
89 " Mappings that will align text using the current text width,  using  at
90 " most four spaces in a  space  run  and  keeping  current  indentation.
91 nmap _j :%call Justify('tw',4)<CR>
92 vmap _j :call Justify('tw',4)<CR>
94 " Mappings that will remove space runs and format lines (might be useful
95 " prior to aligning the text).
96 nmap ,gq :%s/\s\+/ /g<CR>gq1G
97 vmap ,gq :s/\s\+/ /g<CR>gvgq
100 " User defined command:
102 " The following is an ex command that works as a shortcut to the Justify
103 " function.  Arguments to Justify() can  be  added  after  the  command.
104 com! -range -nargs=* Justify <line1>,<line2>call Justify(<f-args>)
106 " The following commands are all equivalent:
108 " 1. Simplest use of Justify():
109 "       :call Justify()
110 "       :Justify
112 " 2. The _j mapping above via the ex command:
113 "       :%Justify tw 4
115 " 3.  Justify  visualised  text  at  72nd  column  while  indenting  all
116 " previously indented text two shiftwidths
117 "       :'<,'>call Justify(72,'','2sw')
118 "       :'<,'>Justify 72 -1 2sw
120 " This documentation has been justified  using  the  following  command:
121 ":se et|kz|1;/^" function Justify(/+,'z-g/^" /s/^" //|call Justify(70,3)|s/^/" /
123 " Revisions:
124 " 001103: If 'joinspaces' was set, calculations could be wrong.
125 "         Tabs at start of line could also lead to errors.
126 "         Use setline() instead of "exec 's/foo/bar/' - safer.
127 "         Cleaned up the code a bit.
129 " Todo:   Convert maps to the new script specific form
131 " Error function
132 function! Justify_error(message)
133     echohl Error
134     echo "Justify([tw, [maxspaces [, indent]]]): " . a:message
135     echohl None
136 endfunction
139 " Now for the real thing
140 function! Justify(...) range
142     if a:0 > 3
143     call Justify_error("Too many arguments (max 3)")
144     return 1
145     endif
147     " Set textwidth (accept 'tw' and '' as arguments)
148     if a:0 >= 1
149         if a:1 =~ '^\(tw\)\=$'
150             let tw = &tw
151         elseif a:1 =~ '^\d\+$'
152             let tw = a:1
153         else
154             call Justify_error("tw must be a number (>0), '' or 'tw'")
155             return 2
156         endif
157     else
158         let tw = &tw
159     endif
160     if tw == 0
161         let tw = 80
162     endif
164     " Set maximum number of spaces between WORDs
165     if a:0 >= 2
166         if a:2 == ''
167             let maxspaces = tw
168         elseif a:2 =~ '^-\d\+$'
169             let maxspaces = tw
170         elseif a:2 =~ '^\d\+$'
171             let maxspaces = a:2
172         else
173             call Justify_error("maxspaces must be a number or ''")
174             return 3
175         endif
176     else
177         let maxspaces = tw
178     endif
179     if maxspaces <= 1
180         call Justify_error("maxspaces should be larger than 1")
181         return 4
182     endif
184     " Set the indentation style (accept sw and ts units)
185     let indent_fix = ''
186     if a:0 >= 3
187         if (a:3 == '') || a:3 =~ '^-[1-9]\d*\(shiftwidth\|sw\|tabstop\|ts\)\=$'
188             let indent = -1
189         elseif a:3 =~ '^-\=0\(shiftwidth\|sw\|tabstop\|ts\)\=$'
190             let indent = 0
191         elseif a:3 =~ '^\d\+\(shiftwidth\|sw\|tabstop\|ts\)\=$'
192             let indent = substitute(a:3, '\D', '', 'g')
193         elseif a:3 =~ '^\(shiftwidth\|sw\|tabstop\|ts\)$'
194             let indent = 1
195         else
196             call Justify_error("indent: a number with 'sw'/'ts' unit")
197             return 5
198         endif
199         if indent >= 0
200             while indent > 0
201                 let indent_fix = indent_fix . ' '
202                 let indent = indent - 1
203             endwhile
204             let indent_sw = 0
205             if a:3 =~ '\(shiftwidth\|sw\)'
206                 let indent_sw = &sw
207             elseif a:3 =~ '\(tabstop\|ts\)'
208                 let indent_sw = &ts
209             endif
210             let indent_fix2 = ''
211             while indent_sw > 0
212                 let indent_fix2 = indent_fix2 . indent_fix
213                 let indent_sw = indent_sw - 1
214             endwhile
215             let indent_fix = indent_fix2
216         endif
217     else
218         let indent = -1
219     endif
221     " Avoid substitution reports
222     let save_report = &report
223     set report=1000000
225     " Check 'joinspaces' and 'cpo'
226     if &js == 1
227         if &cpo =~ 'j'
228             let join_str = '\(\. \)'
229         else
230             let join_str = '\([.!?!] \)'
231         endif
232     endif
234     let cur = a:firstline
235     while cur <= a:lastline
237         let str_orig = getline(cur)
238         let save_et = &et
239         set et
240         exec cur . "retab"
241         let &et = save_et
242         let str = getline(cur)
244         let indent_str = indent_fix
245         let indent_n = strlen(indent_str)
246         " Shall we remember the current indentation
247         if indent < 0
248             let indent_orig = matchstr(str_orig, '^\s*')
249             if strlen(indent_orig) > 0
250                 let indent_str = indent_orig
251                 let indent_n = strlen(matchstr(str, '^\s*'))
252             endif
253         endif
255         " Trim trailing, leading and running whitespace
256         let str = substitute(str, '\s\+$', '', '')
257         let str = substitute(str, '^\s\+', '', '')
258         let str = substitute(str, '\s\+', ' ', 'g')
259         " Use substitute() hack to get strlen in characters instead of bytes
260         let str_n = strlen(substitute(str, '.', 'x', 'g'))
262         " Possible addition of space after punctuation
263         if exists("join_str")
264             let str = substitute(str, join_str, '\1 ', 'g')
265         endif
266         let join_n = strlen(substitute(str, '.', 'x', 'g')) - str_n
268         " Can extraspaces be added?
269         " Note that str_n may be less than strlen(str) [joinspaces above]
270         if strlen(substitute(str, '.', 'x', 'g')) < tw - indent_n && str_n > 0
271             " How many spaces should be added
272             let s_add = tw - str_n - indent_n - join_n
273             let s_nr  = strlen(substitute(str, '\S', '', 'g') ) - join_n
274             let s_dup = s_add / s_nr
275             let s_mod = s_add % s_nr
277             " Test if the changed line fits with tw
278             if 0 <= (str_n + (maxspaces - 1)*s_nr + indent_n) - tw
280                 " Duplicate spaces
281                 while s_dup > 0
282                     let str = substitute(str, '\( \+\)', ' \1', 'g')
283                     let s_dup = s_dup - 1
284                 endwhile
286                 " Add extra spaces from the end
287                 while s_mod > 0
288                     let str = substitute(str, '\(\(\s\+\S\+\)\{' . s_mod .  '}\)$', ' \1', '')
289                     let s_mod = s_mod - 1
290                 endwhile
292                 " Indent the line
293                 if indent_n > 0
294                     let str = substitute(str, '^', indent_str, '' )
295                 endif
297                 " Replace the line
298                 call setline(cur, str)
300                 " Convert to whitespace
301                 if &et == 0
302                     exec cur . 'retab!'
303                 endif
305             endif   " Change of line
306         endif   " Possible change
308         let cur = cur + 1
309     endwhile
311     norm ^
313     let &report = save_report
315 endfunction
317 " EOF   vim: tw=78 ts=8 sw=4 sts=4 noet ai