Bram Moolenaar | 57e4ee4 | 2012-12-05 17:03:22 +0100 | [diff] [blame] | 1 | " Function to left and right align text. |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 2 | " |
| 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. |
| 91 | nmap _j :%call Justify('tw',4)<CR> |
| 92 | vmap _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). |
| 96 | nmap ,gq :%s/\s\+/ /g<CR>gq1G |
| 97 | vmap ,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. |
| 104 | com! -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 |
| 132 | function! Justify_error(message) |
| 133 | echohl Error |
| 134 | echo "Justify([tw, [maxspaces [, indent]]]): " . a:message |
| 135 | echohl None |
| 136 | endfunction |
| 137 | |
| 138 | |
| 139 | " Now for the real thing |
| 140 | function! 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 Moolenaar | 57e4ee4 | 2012-12-05 17:03:22 +0100 | [diff] [blame] | 259 | let str_n = strdisplaywidth(str) |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 260 | |
| 261 | " Possible addition of space after punctuation |
| 262 | if exists("join_str") |
| 263 | let str = substitute(str, join_str, '\1 ', 'g') |
| 264 | endif |
Bram Moolenaar | 57e4ee4 | 2012-12-05 17:03:22 +0100 | [diff] [blame] | 265 | let join_n = strdisplaywidth(str) - str_n |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 266 | |
| 267 | " Can extraspaces be added? |
| 268 | " Note that str_n may be less than strlen(str) [joinspaces above] |
Bram Moolenaar | 57e4ee4 | 2012-12-05 17:03:22 +0100 | [diff] [blame] | 269 | if strdisplaywidth(str) <= tw - indent_n && str_n > 0 |
Bram Moolenaar | 071d427 | 2004-06-13 20:20:40 +0000 | [diff] [blame] | 270 | " 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 | |
| 314 | endfunction |
| 315 | |
| 316 | " EOF vim: tw=78 ts=8 sw=4 sts=4 noet ai |