blob: 4ef3bf95fa32deea98bf40575ae94d440a4b92e3 [file] [log] [blame]
Bram Moolenaar57e4ee42012-12-05 17:03:22 +01001" Function to left and right align text.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002"
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)
6
7
8" function Justify( [ textwidth [, maxspaces [, indent] ] ] )
9"
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.
14"
15" The function takes the following arguments:
16
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.
21"
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.
25
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.
30"
31" An argument of '' is accepted, should the user like to specify all
32" arguments.
33"
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.
37
38" indent argument
39" ---------------
40" This argument specifies how a line should be indented. The default is
41" to keep the current indentation.
42"
43" Negative values: Keep current amount of leading whitespace.
44" Positive values: Indent all lines with leading whitespace using this
45" amount of whitespace.
46"
47" Note that the value 0, needs to be quoted as a string. This value
48" leads to a left flushed text.
49"
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').
55"
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.
60"
61" As with the other arguments, '' may be used to get the default
62" behaviour.
63
64
65" Notes:
66"
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.
71"
72" If the resulting line is shorter than the used textwidth it is left
73" untouched.
74"
75" All space runs in the line are truncated before the alignment is
76" carried out.
77"
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.
81"
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.)
85
86
87" Related mappings:
88"
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.
91nmap _j :%call Justify('tw',4)<CR>
92vmap _j :call Justify('tw',4)<CR>
93"
94" Mappings that will remove space runs and format lines (might be useful
95" prior to aligning the text).
96nmap ,gq :%s/\s\+/ /g<CR>gq1G
97vmap ,gq :s/\s\+/ /g<CR>gvgq
98
99
100" User defined command:
101"
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.
104com! -range -nargs=* Justify <line1>,<line2>call Justify(<f-args>)
105"
106" The following commands are all equivalent:
107"
108" 1. Simplest use of Justify():
109" :call Justify()
110" :Justify
111"
112" 2. The _j mapping above via the ex command:
113" :%Justify tw 4
114"
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
119"
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/^/" /
122
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.
128"
129" Todo: Convert maps to the new script specific form
130
131" Error function
132function! Justify_error(message)
133 echohl Error
134 echo "Justify([tw, [maxspaces [, indent]]]): " . a:message
135 echohl None
136endfunction
137
138
139" Now for the real thing
140function! Justify(...) range
141
142 if a:0 > 3
143 call Justify_error("Too many arguments (max 3)")
144 return 1
145 endif
146
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
163
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
183
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
220
221 " Avoid substitution reports
222 let save_report = &report
223 set report=1000000
224
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
233
234 let cur = a:firstline
235 while cur <= a:lastline
236
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)
243
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
254
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')
Bram Moolenaar57e4ee42012-12-05 17:03:22 +0100259 let str_n = strdisplaywidth(str)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000260
261 " Possible addition of space after punctuation
262 if exists("join_str")
263 let str = substitute(str, join_str, '\1 ', 'g')
264 endif
Bram Moolenaar57e4ee42012-12-05 17:03:22 +0100265 let join_n = strdisplaywidth(str) - str_n
Bram Moolenaar071d4272004-06-13 20:20:40 +0000266
267 " Can extraspaces be added?
268 " Note that str_n may be less than strlen(str) [joinspaces above]
Bram Moolenaar57e4ee42012-12-05 17:03:22 +0100269 if strdisplaywidth(str) <= tw - indent_n && str_n > 0
Bram Moolenaar071d4272004-06-13 20:20:40 +0000270 " How many spaces should be added
271 let s_add = tw - str_n - indent_n - join_n
272 let s_nr = strlen(substitute(str, '\S', '', 'g') ) - join_n
273 let s_dup = s_add / s_nr
274 let s_mod = s_add % s_nr
275
276 " Test if the changed line fits with tw
277 if 0 <= (str_n + (maxspaces - 1)*s_nr + indent_n) - tw
278
279 " Duplicate spaces
280 while s_dup > 0
281 let str = substitute(str, '\( \+\)', ' \1', 'g')
282 let s_dup = s_dup - 1
283 endwhile
284
285 " Add extra spaces from the end
286 while s_mod > 0
287 let str = substitute(str, '\(\(\s\+\S\+\)\{' . s_mod . '}\)$', ' \1', '')
288 let s_mod = s_mod - 1
289 endwhile
290
291 " Indent the line
292 if indent_n > 0
293 let str = substitute(str, '^', indent_str, '' )
294 endif
295
296 " Replace the line
297 call setline(cur, str)
298
299 " Convert to whitespace
300 if &et == 0
301 exec cur . 'retab!'
302 endif
303
304 endif " Change of line
305 endif " Possible change
306
307 let cur = cur + 1
308 endwhile
309
310 norm ^
311
312 let &report = save_report
313
314endfunction
315
316" EOF vim: tw=78 ts=8 sw=4 sts=4 noet ai