1"
2" cream-sort.vim -- Various sorting methods and algorithms
3"
4" Cream -- An easy-to-use configuration of the famous Vim text editor
5" [ http://cream.sourceforge.net ] Copyright (C) 2001-2011 Steve Hall
6"
7" License:
8" This program is free software; you can redistribute it and/or modify
9" it under the terms of the GNU General Public License as published by
10" the Free Software Foundation; either version 3 of the License, or
11" (at your option) any later version.
12" [ http://www.gnu.org/licenses/gpl.html ]
13"
14" This program is distributed in the hope that it will be useful, but
15" WITHOUT ANY WARRANTY; without even the implied warranty of
16" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17" General Public License for more details.
18"
19" You should have received a copy of the GNU General Public License
20" along with this program; if not, write to the Free Software
21" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22" 02111-1307, USA.
23
24" register as a Cream add-on {{{1
25if exists("$CREAM")
26	call Cream_addon_register(
27	\ 'Sort Selected Lines',
28	\ 'Sort the lines currently selected',
29	\ 'Sort the lines currently selected.',
30	\ 'Sort.Selected Lines',
31	\ '<Nil>',
32	\ 'BISort2'
33	\ )
34
35	call Cream_addon_register(
36	\ 'Sort Selected Lines, Inverse',
37	\ 'Reverse sort the lines currently selected',
38	\ 'Reverse sort the lines currently selected.',
39	\ 'Sort.Selected Lines, Inverse',
40	\ '<Nil>',
41	\ "BISort2Reverse"
42	\ )
43
44	call Cream_addon_register(
45	\ 'Invert Lines of Selection',
46	\ 'Invert the lines of the current selection',
47	\ 'Invert the lines of the current selection.',
48	\ 'Invert Selected Lines',
49	\ '<Nil>',
50	\ "Invert"
51	\ )
52
53	call Cream_addon_register(
54	\ 'Uniq',
55	\ 'Eliminate duplicate, sorted lines',
56	\ 'Eliminate duplicate lines of an alphabetically sorted selection, simliar to the Unix utility uniq.',
57	\ 'Uniq Selected Lines',
58	\ '<Nil>',
59	\ '<C-u>call Cream_uniq("v")'
60	\ )
61
62endif
63
64
65"*********************************************************************
66"  BISort and related moved to library where we can access them
67"  at startup.
68"*********************************************************************
69
70" File Sort (external sort) {{{1
71"---------------------------------------------------------------------
72" Version: 1.5
73" Date:    2002 October 23
74" Source:  http://vim.sourceforge.net/scripts/script.php?script_id=310
75" Author:  Steve Hall  [ digitect@mindspring.com ]
76"
77" Description:
78" Sort a file alphabetically by lines using external operating system
79" (Linux or Windows) commands which redirect the current file into
80" a sorted temporary file.
81"
82" Installation:
83" Simply copy this file and paste it into your vimrc. Or you can drop
84" the entire file into your plugins directory.
85"
86" Useage:
87" Use :call Cream_sortfile() to sort the current file alphabetically
88" by line. User is prompted to choose the column of the sort (default
89" is 1).
90"
91" Notes:
92" * The function uses the operating system's sort command (Windows or
93"   Linux) for speed since our original use was for 7mb dictionary
94"   wordlists(!).
95"
96" * Sorting is actually done in a temp file to preserve the original.
97"
98" * Please notice three other sort libraries included within the
99"   script below the advertised first one. They were "research" during
100"   the project and may serve certain users better than ours!
101"
102" ChangeLog:
103"
104" 2003-02-15 -- Version 1.5
105" * Unnamed buffer now saved as temporary file.
106"
107" 2002-10-23 -- Version 1.4
108" * Temp file overwrite option added.
109"
110" 2002-09-10 -- Version 1.3
111" * Quote path/filenames on Windows to handle spaces
112"
113" 2002-09-07 -- Version 1.2
114" * Added requirement that file be named (external redirection methods
115"   require it)
116" * Added prompt to save modified file before proceeding
117"
118" 2002-07-18 -- Version 1.1
119" * Added temp file verification.
120" * Changed Linux "--key" to "-k" to work with some Linuxes.
121" * Eliminated Linux "-o=[FILENAME]" option to simple redirection to
122"   work with some Linuxes.
123"
124" 2002-06-07 -- Version 1.0
125" * Initial Release
126
127" list as an add-on if Cream project in use
128if exists("$CREAM")
129	call Cream_addon_register(
130	\ 'Sort File',
131	\ 'Sort the current file by column',
132	\ 'Sort the current file alphabetically by line. User is prompted to choose the column of the sort (default is 1).',
133	\ 'Sort.File (external sort)',
134	\ 'call Cream_sortfile()'
135	\ )
136endif
137
138function! Cream_sortfile()
139" sort a file's lines by column number
140" * option: sort selection by alternate column number (default 1)
141" * multi-platform: Windows 95-XP and Linux
142
143	" require file to be named (can't sort an un-named file due to the
144	" external redirections that this module uses)
145	if expand("%") == ""
146		"let n = confirm("File must be named before proceeding...", "&Ok\n&Cancel", 1, "Info")
147		"if n != 1
148		"    call confirm("Canceled. Sort not performed.", "&Ok", 1, "Info")
149		"    return
150		"endif
151		"if has("browse")
152		"    browse confirm write
153		"else
154		"    confirm write
155		"endif
156		"" if still unnamed, quit
157		"if expand("%") == ""
158		"    call confirm(
159		"        \ "Can not continue, document unnamed. Quitting...\n" .
160		"        \ "\n", "&Ok", 1, "Info")
161		"    return
162		"endif
163		let myfile = tempname()
164		call confirm(
165			\ "Saving as temp file " . myfile . " ...\n" .
166			\ "\n", "&Ok", 1, "Info")
167		execute "saveas " . myfile
168	endif
169
170	" prompt to save modified first
171	if &modified == 1
172		let n = confirm("Do you wish to save changes first?", "&Ok\n&No\n&Cancel", 1, "Info")
173		if n == 1
174			write
175		elseif n == 2
176			" carry on
177		else
178			call confirm("Canceled. Sort not performed.", "&Ok", 1, "Info")
179			return
180		endif
181	endif
182
183	" current file
184	let myfile = fnamemodify(expand("%"), ":p")
185	" escape backslashes (for Windows)
186	let myfile = escape(myfile, "\\")
187	" temp file (append extension .tmp)
188	let myfiletmp = myfile . ".tmp"
189
190	" verify temp file doesn't already exist
191	if filewritable(myfiletmp) == 1
192		let n = confirm("Filename \"" . myfiletmp . "\" already exists. Overwrite and continue?", "&Ok\n&Cancel", 2, "Warning")
193		if n != 1
194			return
195		endif
196	elseif filewritable(myfiletmp) == 2
197		call confirm("Unable to continue. A directory with the name \"" . myfiletmp . "\" already exists.", "&Ok", 1, "Warning")
198		return
199	endif
200
201	" get column to sort on
202	let mycol = inputdialog("Please enter column number to sort on (default 1)", "1")
203	if mycol == ""
204		return
205	endif
206
207	" validate
208	" convert to number
209	let mycol = mycol + 0
210	" make sure not empty
211	if mycol == 0
212		let mycol = "1"
213	endif
214
215	" sort (by platform command)
216	if has("win32")
217
218		" fix back slashes if any (I use :set shellslash)
219		let myfile = substitute(myfile,'/','\\','g')
220		let myfiletmp = substitute(myfiletmp,'/','\\','g')
221
222		" Windows
223		"----------------------------------------------------------------------
224		" SORT /?
225		"
226		" Sorts input and writes results to the screen, a file, or another device
227		"
228		" SORT [/R] [/+n] [[drive1:][path1]filename1] [> [drive2:][path2]filename2]
229		" [command |] SORT [/R] [/+n] [> [drive2:][path2]filename2]
230		"
231		"   /R                         Reverses the sort order; that is, sorts Z to A,
232		"                              then 9 to 0.
233		"   /+n                        Sorts the file according to characters in
234		"                              column n.
235		"   [drive1:][path1]filename1  Specifies file(s) to be sorted
236		"   [drive2:][path2]filename2  Specifies a file where the sorted input is to be
237		"                              stored.
238		"   command                    Specifies a command whose output is to be sorted.
239
240		" command (extra quotes handle file/pathnames with spaces)
241		execute ":silent !SORT /+" . mycol . " " . "\"" . myfile . "\"" . " > " . "\"" . myfiletmp . "\""
242
243	elseif has("unix")
244
245		" Linux | Cygwin
246		"----------------------------------------------------------------------
247		" sort --help
248		"
249		" Usage: sort [OPTION]... [FILE]...
250		" Write sorted concatenation of all FILE(s) to standard output.
251		"
252		" Ordering options:
253		"
254		" Mandatory arguments to long options are mandatory for short options too.
255		"   -b, --ignore-leading-blanks ignore leading blanks
256		"   -d, --dictionary-order      consider only blanks and alphanumeric characters
257		"   -f, --ignore-case           fold lower case to upper case characters
258		"   -g, --general-numeric-sort  compare according to general numerical value
259		"   -i, --ignore-nonprinting    consider only printable characters
260		"   -M, --month-sort            compare (unknown) < `JAN' < ... < `DEC'
261		"   -n, --numeric-sort          compare according to string numerical value
262		"   -r, --reverse               reverse the result of comparisons
263		"
264		" Other options:
265		"
266		"   -c, --check               check whether input is sorted; do not sort
267		"   -k, --key=POS1[,POS2]     start a key at POS1, end it at POS 2 (origin 1)
268		"   -m, --merge               merge already sorted files; do not sort
269		"   -o, --output=FILE         write result to FILE instead of standard output
270		"   -s, --stable              stabilize sort by disabling last-resort comparison
271		"   -S, --buffer-size=SIZE    use SIZE for main memory buffer
272		"   -t, --field-separator=SEP use SEP instead of non- to whitespace transition
273		"   -T, --temporary-directory=DIR  use DIR for temporaries, not $TMPDIR or /tmp
274		"                               multiple options specify multiple directories
275		"   -u, --unique              with -c: check for strict ordering
276		"                               otherwise: output only the first of an equal run
277		"   -z, --zero-terminated     end lines with 0 byte, not newline
278		"       --help     display this help and exit
279		"       --version  output version information and exit
280		"
281		" POS is F[.C][OPTS], where F is the field number and C the character position
282		" in the field.  OPTS is one or more single-letter ordering options, which
283		" override global ordering options for that key.  If no key is given, use the
284		" entire line as the key.
285		"
286		" SIZE may be followed by the following multiplicative suffixes:
287		" % 1% of memory, b 1, K 1024 (default), and so on for M, G, T, P, E, Z, Y.
288		"
289		" With no FILE, or when FILE is -, read standard input.
290		"
291		" *** WARNING ***
292		" The locale specified by the environment affects sort order.
293		" Set LC_ALL=C to get the traditional sort order that uses
294		" native byte values.
295		"
296		" Report bugs to <bug-textutils@gnu.org>.
297
298		" command
299		" * original:
300		"     execute ":!sort --key=" . mycol . " -fid -o=" . myfiletmp . " " . myfile
301		" * some systems cannot use the "--key=" option, use "-k"
302		" * some systems cannot use the "-o=" option, use redirection
303		execute ":!sort -k " . mycol . " -fid " . myfile . " > " . myfiletmp
304
305	else
306		call confirm("Sorry! This platform untested. \n\nPlease contact us at\n      http://cream.sourceforge.net ", "&Ok", 1, "Info")
307		return
308	endif
309
310	" edit temp file
311	execute ":edit " . myfiletmp
312
313	" warn we're in temp file
314	call confirm("Now in temp file:\n  " . myfiletmp . "\n\nPreserved original file:\n  " . myfile . "\n\n", "&Ok", 1, "Warning")
315
316endfunction
317" 1}}}
318" Various other (unused) sort functions
319" Line Sort {{{1
320
321"" list as an add-on if Cream project in use
322"if exists("$CREAM")
323"    call Cream_addon_register(
324"    \ 'Sort Selection',
325"    \ 'Sort the current selection',
326"    \ 'Sort the current selection.',
327"    \ 'Sort.Selection',
328"    \ '<Nil>',
329"    \ 'Sort'
330"    \ )
331
332"    call Cream_addon_register(
333"    \ 'Sort Selection, Inverse',
334"    \ 'Reverse sort the current selection',
335"    \ 'Reverse sort the current selection.',
336"    \ 'Sort.Selection, Inverse',
337"    \ '<Nil>',
338"    \ 'SortReverse'
339"    \ )
340"endif
341
342" * Extended version found in :help eval-examples
343" * See also in explorer.vim
344" * Extremely slow (>10 min.) for large files (dictionaries)
345"
346" Author: Robert Webb <RobertW at beam.com.au>
347"
348
349" Function for use with Sort(), to compare two strings.
350func! <SID>Strcmp(str1, str2)
351	if (a:str1 < a:str2)
352		return -1
353	elseif (a:str1 > a:str2)
354		return 1
355	else
356		return 0
357	endif
358endfunction
359" Function for use with Sort(), to compare two strings.
360func! <SID>Stricmp(str1, str2)
361	let st1=substitute(a:str1,'^.*$', '\L&', "")
362	let st2=substitute(a:str1,'^.*$', '\L&', "")
363	if (st1 < st2)
364		return -1
365	elseif (st1 > st2)
366		return 1
367	else
368		return 0
369	endif
370endfunction
371
372" Sort lines.  SortR() is called recursively.
373func! <SID>SortR(start, end, cmp)
374	if (a:start >= a:end)
375		return
376	endif
377	let partition = a:start - 1
378	let middle = partition
379	let partStr = getline((a:start + a:end) / 2)
380	let i = a:start
381	while (i <= a:end)
382		let str = getline(i)
383		exec "let result = " . a:cmp . "(str, partStr)"
384		if (result <= 0)
385			" Need to put it before the partition.  Swap lines i and partition.
386			let partition = partition + 1
387			if (result == 0)
388				let middle = partition
389			endif
390			if (i != partition)
391				let str2 = getline(partition)
392				call setline(i, str2)
393				call setline(partition, str)
394			endif
395		endif
396		let i = i + 1
397	endwhile
398
399	" Now we have a pointer to the "middle" element, as far as partitioning
400	" goes, which could be anywhere before the partition.  Make sure it is at
401	" the end of the partition.
402	if (middle != partition)
403		let str = getline(middle)
404		let str2 = getline(partition)
405		call setline(middle, str2)
406		call setline(partition, str)
407	endif
408	call <SID>SortR(a:start, partition - 1, a:cmp)
409	call <SID>SortR(partition + 1, a:end, a:cmp)
410endfunc
411
412" To Sort a range of lines, pass the range to Sort() along with the name of a
413" function that will compare two lines.
414func! <SID>Sort(cmp) range
415	call <SID>SortR(a:firstline, a:lastline, a:cmp)
416endfunc
417
418" :Sort takes a range of lines and sorts them.
419command! -nargs=0 -range Sort <line1>,<line2>call <SID>Sort('<SID>Strcmp')
420command! -nargs=0 -range ISort <line1>,<line2>call <SID>Sort('<SID>Stricmp')
421
422"+++ Cream:
423command! -nargs=0 -range SortReverse <line1>,<line2>call <SID>Sort('<SID>StrcmpR')
424func! <SID>StrcmpR(str2, str1)
425	if (a:str1 < a:str2)
426		return -1
427	elseif (a:str1 > a:str2)
428		return 1
429	else
430		return 0
431	endif
432endfunction
433"+++
434
435" Line Sort (BISort) {{{1
436" Source: Piet Delport, vim@vim.org list, 2003-05-01
437
438"" list as an add-on if Cream project in use
439"if exists("$CREAM")
440"    call Cream_addon_register(
441"    \ 'Sort Selection',
442"    \ 'Sort the current selection',
443"    \ 'Sort the current selection.',
444"    \ 'Sort.Selection (BISort)',
445"    \ '<Nil>',
446"    \ 'call Cream_BISort_call("v")'
447"    \ )
448"
449"    call Cream_addon_register(
450"    \ 'Sort Selection, Inverse',
451"    \ 'Reverse sort the current selection',
452"    \ 'Reverse sort the current selection.',
453"    \ 'Sort.Selection, Inverse (BISort)',
454"    \ '<Nil>',
455"    \ 'call Cream_BISort_call_reverse("v")'
456"    \ )
457"endif
458
459function! Cream_BISort_call(mode)
460" calls BISort2, establishing the range
461	if a:mode != "v"
462		return
463	endif
464	normal gv
465	call BISort(line("'<"), line("'>"), '<SID>Strcmp')
466endfunction
467function! Cream_BISort_call_reverse(mode)
468" calls BISort2, establishing the range
469	if a:mode != "v"
470		return
471	endif
472	normal gv
473	call BISort(line("'>"), line("'<"), '<SID>StrcmpR')
474endfunction
475
476function! BISort(start, end, cmp)
477	let compare_ival_mid = 'let diff = '.a:cmp.'(i_val, getline(mid))'
478	let i = a:start + 1
479	while i <= a:end
480		" find insertion point via binary search
481		let i_val = getline(i)
482		let lo = a:start
483		let hi = i
484		while lo < hi
485			let mid = (lo + hi) / 2
486			exec compare_ival_mid
487			if diff < 0
488				let hi = mid
489			else
490				let lo = mid + 1
491				if diff == 0 | break | endif
492			endif
493		endwhile
494		" do insert
495		if lo < i
496			exec i.'d_'
497			call append(lo - 1, i_val)
498		endif
499		let i = i + 1
500	endwhile
501endfunction
502
503" Line length sorting {{{1
504"
505" See: genutils.vim by Hari Krishna Dara <hari_vim at yahoo.com>
506"        http://vim.sourceforge.net/scripts/script.php?script_id=197
507"
508
509
510" Perl file sorting {{{1
511" by Colin Keith, on the vim@vim.org mailing list 03 Jun 2002
512" * Uses Perl
513function! VimPerlSort(...) range
514	if !has('perl')
515		echoerr "Perl not installed, unable to continue"
516		return
517	endif
518
519	let s:col = 1
520	let s:order  = 0
521
522	" Calculate sort method
523	if exists('a:1') && a:1 > 0 && a:1 < 5
524		let s:order = a:1
525
526		" And if we want any column other than the first
527		if exists('a:2')
528		  let s:col = a:2
529		endif
530	endif
531
532	" alphabetical
533	if s:order == 1
534		let s:sort = 'my $x = substr($a, '. s:col. ', 1)||""; '.
535				   \ 'my $y = substr($b, '. s:col. ', 1)||""; '.
536				   \ 'return $x cmp $y;'
537	" reverse alphabetical
538	elseif s:order == 2
539		let s:sort = 'my $x = substr($a, '. s:col. ', 1)||""; '.
540				   \ 'my $y = substr($b, '. s:col. ', 1)||""; '.
541				   \ 'return $y cmp $x;'
542	" numerical
543	elseif s:order == 3
544		let s:sort = 'my $x = substr($a, '. s:col. ', 1)||0; '.
545				   \ 'my $y = substr($b, '. s:col. ', 1)||0; '.
546				   \ 'return $x <=> $y;'
547	" reverse numerical
548	elseif s:order == 4
549		let s:sort = 'my $x = substr($a, '. s:col. ', 1)||0; '.
550				   \ 'my $y = substr($b, '. s:col. ', 1)||0; '.
551				   \ 'return $y <=> $x;'
552	else
553		let s:sort = '$a cmp $b'
554	endif
555
556	" Load into memory
557	execute ':perl @data = sort { '. s:sort . ' } '.
558		\ '$curbuf->Get('. a:firstline. '..'. a:lastline. ')'
559	execute ':perl $curbuf->Set('. a:firstline. ', @data)'
560
561	" just to make sure the memory is released:
562	:perl @data=()
563
564endfunction
565" 1}}}
566" vim:foldmethod=marker
567