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