diff options
| author | Richard M. Stallman | 1990-06-29 17:35:02 +0000 |
|---|---|---|
| committer | Richard M. Stallman | 1990-06-29 17:35:02 +0000 |
| commit | d32200acf11e0ded11cfb0793a5e4c3505bd30f3 (patch) | |
| tree | feffd358577dc3b5545dff0900ba1aba19db5d91 | |
| parent | 952d72ffc29c857dd56d4cb143ab4571902ed377 (diff) | |
| download | emacs-d32200acf11e0ded11cfb0793a5e4c3505bd30f3.tar.gz emacs-d32200acf11e0ded11cfb0793a5e4c3505bd30f3.zip | |
Initial revision
| -rw-r--r-- | lisp/sort.el | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/lisp/sort.el b/lisp/sort.el new file mode 100644 index 00000000000..30dd6916ba9 --- /dev/null +++ b/lisp/sort.el | |||
| @@ -0,0 +1,378 @@ | |||
| 1 | ;; Commands to sort text in an Emacs buffer. | ||
| 2 | ;; Copyright (C) 1986, 1987 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | ;; This file is part of GNU Emacs. | ||
| 5 | |||
| 6 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 7 | ;; it under the terms of the GNU General Public License as published by | ||
| 8 | ;; the Free Software Foundation; either version 1, or (at your option) | ||
| 9 | ;; any later version. | ||
| 10 | |||
| 11 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 12 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | ;; GNU General Public License for more details. | ||
| 15 | |||
| 16 | ;; You should have received a copy of the GNU General Public License | ||
| 17 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 18 | ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 19 | |||
| 20 | (provide 'sort) | ||
| 21 | |||
| 22 | ;; Original version of most of this contributed by Howie Kaye | ||
| 23 | |||
| 24 | (defun sort-subr (reverse nextrecfun endrecfun &optional startkeyfun endkeyfun) | ||
| 25 | "General text sorting routine to divide buffer into records and sort them. | ||
| 26 | Arguments are REVERSE NEXTRECFUN ENDRECFUN &optional STARTKEYFUN ENDKEYFUN. | ||
| 27 | |||
| 28 | We consider this portion of the buffer to be divided into disjoint pieces | ||
| 29 | called sort records. A portion of each sort record (perhaps all of it) | ||
| 30 | is designated as the sort key. The records are rearranged in the buffer | ||
| 31 | in order by their sort keys. The records may or may not be contiguous. | ||
| 32 | |||
| 33 | Usually the records are rearranged in order of ascending sort key. | ||
| 34 | If REVERSE is non-nil, they are rearranged in order of descending sort key. | ||
| 35 | |||
| 36 | The next four arguments are functions to be called to move point | ||
| 37 | across a sort record. They will be called many times from within sort-subr. | ||
| 38 | |||
| 39 | NEXTRECFUN is called with point at the end of the previous record. | ||
| 40 | It moves point to the start of the next record. | ||
| 41 | It should move point to the end of the buffer if there are no more records. | ||
| 42 | The first record is assumed to start at the position of point when sort-subr | ||
| 43 | is called. | ||
| 44 | |||
| 45 | ENDRECFUN is is called with point within the record. | ||
| 46 | It should move point to the end of the record. | ||
| 47 | |||
| 48 | STARTKEYFUN may moves from the start of the record to the start of the key. | ||
| 49 | It may return either return a non-nil value to be used as the key, or | ||
| 50 | else the key will be the substring between the values of point after | ||
| 51 | STARTKEYFUNC and ENDKEYFUN are called. | ||
| 52 | |||
| 53 | ENDKEYFUN moves from the start of the sort key to the end of the sort key. | ||
| 54 | ENDKEYFUN may be nil if STARTKEYFUN returns a value or if it would be the | ||
| 55 | same as ENDRECFUN." | ||
| 56 | (save-excursion | ||
| 57 | (message "Finding sort keys...") | ||
| 58 | (let* ((sort-lists (sort-build-lists nextrecfun endrecfun | ||
| 59 | startkeyfun endkeyfun)) | ||
| 60 | (old (reverse sort-lists))) | ||
| 61 | (if (null sort-lists) | ||
| 62 | () | ||
| 63 | (or reverse (setq sort-lists (nreverse sort-lists))) | ||
| 64 | (message "Sorting records...") | ||
| 65 | (setq sort-lists | ||
| 66 | (if (fboundp 'sortcar) | ||
| 67 | (sortcar sort-lists | ||
| 68 | (cond ((numberp (car (car sort-lists))) | ||
| 69 | '<) | ||
| 70 | ((consp (car (car sort-lists))) | ||
| 71 | 'buffer-substring-lessp) | ||
| 72 | (t | ||
| 73 | 'string<))) | ||
| 74 | (sort sort-lists | ||
| 75 | (cond ((numberp (car (car sort-lists))) | ||
| 76 | (function | ||
| 77 | (lambda (a b) | ||
| 78 | (< (car a) (car b))))) | ||
| 79 | ((consp (car (car sort-lists))) | ||
| 80 | (function | ||
| 81 | (lambda (a b) | ||
| 82 | (buffer-substring-lessp (car a) (car b))))) | ||
| 83 | (t | ||
| 84 | (function | ||
| 85 | (lambda (a b) | ||
| 86 | (string< (car a) (car b))))))))) | ||
| 87 | (if reverse (setq sort-lists (nreverse sort-lists))) | ||
| 88 | (message "Reordering buffer...") | ||
| 89 | (sort-reorder-buffer sort-lists old))) | ||
| 90 | (message "Reordering buffer... Done")) | ||
| 91 | nil) | ||
| 92 | |||
| 93 | ;; Parse buffer into records using the arguments as Lisp expressions; | ||
| 94 | ;; return a list of records. Each record looks like (KEY STARTPOS ENDPOS) | ||
| 95 | ;; where KEY is the sort key (a number or string), | ||
| 96 | ;; and STARTPOS and ENDPOS are the bounds of this record in the buffer. | ||
| 97 | |||
| 98 | ;; The records appear in the list lastmost first! | ||
| 99 | |||
| 100 | (defun sort-build-lists (nextrecfun endrecfun startkeyfun endkeyfun) | ||
| 101 | (let ((sort-lists ()) | ||
| 102 | (start-rec nil) | ||
| 103 | done key) | ||
| 104 | ;; Loop over sort records. | ||
| 105 | ;(goto-char (point-min)) -- it is the caller's responsibility to | ||
| 106 | ;arrange this if necessary | ||
| 107 | (while (not (eobp)) | ||
| 108 | (setq start-rec (point)) ;save record start | ||
| 109 | (setq done nil) | ||
| 110 | ;; Get key value, or move to start of key. | ||
| 111 | (setq key (catch 'key | ||
| 112 | (or (and startkeyfun (funcall startkeyfun)) | ||
| 113 | ;; If key was not returned as value, | ||
| 114 | ;; move to end of key and get key from the buffer. | ||
| 115 | (let ((start (point))) | ||
| 116 | (funcall (or endkeyfun | ||
| 117 | (prog1 endrecfun (setq done t)))) | ||
| 118 | (if (fboundp 'buffer-substring-lessp) | ||
| 119 | (cons start (point)) | ||
| 120 | (buffer-substring start (point))))))) | ||
| 121 | ;; Move to end of this record (start of next one, or end of buffer). | ||
| 122 | (cond ((prog1 done (setq done nil))) | ||
| 123 | (endrecfun (funcall endrecfun)) | ||
| 124 | (nextrecfun (funcall nextrecfun) (setq done t))) | ||
| 125 | (if key (setq sort-lists (cons | ||
| 126 | ;; consing optimization in case in which key | ||
| 127 | ;; is same as record. | ||
| 128 | (if (and (consp key) | ||
| 129 | (equal (car key) start-rec) | ||
| 130 | (equal (cdr key) (point))) | ||
| 131 | (cons key key) | ||
| 132 | (list key start-rec (point))) | ||
| 133 | sort-lists))) | ||
| 134 | (and (not done) nextrecfun (funcall nextrecfun))) | ||
| 135 | sort-lists)) | ||
| 136 | |||
| 137 | (defun sort-reorder-buffer (sort-lists old) | ||
| 138 | (let ((inhibit-quit t) | ||
| 139 | (last (point-min)) | ||
| 140 | (min (point-min)) (max (point-max))) | ||
| 141 | ;; Make sure insertions done for reordering | ||
| 142 | ;; do not go after any markers at the end of the sorted region, | ||
| 143 | ;; by inserting a space to separate them. | ||
| 144 | (goto-char (point-max)) | ||
| 145 | (insert-before-markers " ") | ||
| 146 | (narrow-to-region min (1- (point-max))) | ||
| 147 | (while sort-lists | ||
| 148 | (goto-char (point-max)) | ||
| 149 | (insert-buffer-substring (current-buffer) | ||
| 150 | last | ||
| 151 | (nth 1 (car old))) | ||
| 152 | (goto-char (point-max)) | ||
| 153 | (insert-buffer-substring (current-buffer) | ||
| 154 | (nth 1 (car sort-lists)) | ||
| 155 | (nth 2 (car sort-lists))) | ||
| 156 | (setq last (nth 2 (car old)) | ||
| 157 | sort-lists (cdr sort-lists) | ||
| 158 | old (cdr old))) | ||
| 159 | (goto-char (point-max)) | ||
| 160 | (insert-buffer-substring (current-buffer) | ||
| 161 | last | ||
| 162 | max) | ||
| 163 | ;; Delete the original copy of the text. | ||
| 164 | (delete-region min max) | ||
| 165 | ;; Get rid of the separator " ". | ||
| 166 | (goto-char (point-max)) | ||
| 167 | (narrow-to-region min (1+ (point))) | ||
| 168 | (delete-region (point) (1+ (point))))) | ||
| 169 | |||
| 170 | (defun sort-lines (reverse beg end) | ||
| 171 | "Sort lines in region alphabetically; argument means descending order. | ||
| 172 | Called from a program, there are three arguments: | ||
| 173 | REVERSE (non-nil means reverse order), BEG and END (region to sort)." | ||
| 174 | (interactive "P\nr") | ||
| 175 | (save-excursion | ||
| 176 | (save-restriction | ||
| 177 | (narrow-to-region beg end) | ||
| 178 | (goto-char (point-min)) | ||
| 179 | (sort-subr reverse 'forward-line 'end-of-line)))) | ||
| 180 | |||
| 181 | (defun sort-paragraphs (reverse beg end) | ||
| 182 | "Sort paragraphs in region alphabetically; argument means descending order. | ||
| 183 | Called from a program, there are three arguments: | ||
| 184 | REVERSE (non-nil means reverse order), BEG and END (region to sort)." | ||
| 185 | (interactive "P\nr") | ||
| 186 | (save-excursion | ||
| 187 | (save-restriction | ||
| 188 | (narrow-to-region beg end) | ||
| 189 | (goto-char (point-min)) | ||
| 190 | (sort-subr reverse | ||
| 191 | (function (lambda () (skip-chars-forward "\n \t\f"))) | ||
| 192 | 'forward-paragraph)))) | ||
| 193 | |||
| 194 | (defun sort-pages (reverse beg end) | ||
| 195 | "Sort pages in region alphabetically; argument means descending order. | ||
| 196 | Called from a program, there are three arguments: | ||
| 197 | REVERSE (non-nil means reverse order), BEG and END (region to sort)." | ||
| 198 | (interactive "P\nr") | ||
| 199 | (save-excursion | ||
| 200 | (save-restriction | ||
| 201 | (narrow-to-region beg end) | ||
| 202 | (goto-char (point-min)) | ||
| 203 | (sort-subr reverse | ||
| 204 | (function (lambda () (skip-chars-forward "\n"))) | ||
| 205 | 'forward-page)))) | ||
| 206 | |||
| 207 | (defvar sort-fields-syntax-table nil) | ||
| 208 | (if sort-fields-syntax-table nil | ||
| 209 | (let ((table (make-syntax-table)) | ||
| 210 | (i 0)) | ||
| 211 | (while (< i 256) | ||
| 212 | (modify-syntax-entry i "w" table) | ||
| 213 | (setq i (1+ i))) | ||
| 214 | (modify-syntax-entry ?\ " " table) | ||
| 215 | (modify-syntax-entry ?\t " " table) | ||
| 216 | (modify-syntax-entry ?\n " " table) | ||
| 217 | (modify-syntax-entry ?\. "_" table) ; for floating pt. numbers. -wsr | ||
| 218 | (setq sort-fields-syntax-table table))) | ||
| 219 | |||
| 220 | (defun sort-numeric-fields (field beg end) | ||
| 221 | "Sort lines in region numerically by the ARGth field of each line. | ||
| 222 | Fields are separated by whitespace and numbered from 1 up. | ||
| 223 | Specified field must contain a number in each line of the region. | ||
| 224 | With a negative arg, sorts by the -ARG'th field, in decending order. | ||
| 225 | Called from a program, there are three arguments: | ||
| 226 | FIELD, BEG and END. BEG and END specify region to sort." | ||
| 227 | (interactive "p\nr") | ||
| 228 | (sort-fields-1 field beg end | ||
| 229 | (function (lambda () | ||
| 230 | (sort-skip-fields (1- field)) | ||
| 231 | (string-to-int | ||
| 232 | (buffer-substring | ||
| 233 | (point) | ||
| 234 | (save-excursion | ||
| 235 | ;; This is just wrong! Even without floats... | ||
| 236 | ;; (skip-chars-forward "[0-9]") | ||
| 237 | (forward-sexp 1) | ||
| 238 | (point)))))) | ||
| 239 | nil)) | ||
| 240 | |||
| 241 | (defun sort-fields (field beg end) | ||
| 242 | "Sort lines in region lexicographically by the ARGth field of each line. | ||
| 243 | Fields are separated by whitespace and numbered from 1 up. | ||
| 244 | With a negative arg, sorts by the -ARG'th field, in decending order. | ||
| 245 | Called from a program, there are three arguments: | ||
| 246 | FIELD, BEG and END. BEG and END specify region to sort." | ||
| 247 | (interactive "p\nr") | ||
| 248 | (sort-fields-1 field beg end | ||
| 249 | (function (lambda () | ||
| 250 | (sort-skip-fields (1- field)) | ||
| 251 | nil)) | ||
| 252 | (function (lambda () (skip-chars-forward "^ \t\n"))))) | ||
| 253 | |||
| 254 | (defun sort-fields-1 (field beg end startkeyfun endkeyfun) | ||
| 255 | (let ((reverse (< field 0)) | ||
| 256 | (tbl (syntax-table))) | ||
| 257 | (setq field (max 1 field (- field))) | ||
| 258 | (unwind-protect | ||
| 259 | (save-excursion | ||
| 260 | (save-restriction | ||
| 261 | (narrow-to-region beg end) | ||
| 262 | (goto-char (point-min)) | ||
| 263 | (set-syntax-table sort-fields-syntax-table) | ||
| 264 | (sort-subr reverse | ||
| 265 | 'forward-line 'end-of-line | ||
| 266 | startkeyfun endkeyfun))) | ||
| 267 | (set-syntax-table tbl)))) | ||
| 268 | |||
| 269 | (defun sort-skip-fields (n) | ||
| 270 | (let ((eol (save-excursion (end-of-line 1) (point)))) | ||
| 271 | (forward-word n) | ||
| 272 | (if (> (point) eol) | ||
| 273 | (error "Line has too few fields: %s" | ||
| 274 | (buffer-substring (save-excursion | ||
| 275 | (beginning-of-line) (point)) | ||
| 276 | eol))) | ||
| 277 | (skip-chars-forward " \t"))) | ||
| 278 | |||
| 279 | |||
| 280 | (defun sort-regexp-fields (reverse record-regexp key-regexp beg end) | ||
| 281 | "Sort the region lexicographically as specifed by RECORD-REGEXP and KEY. | ||
| 282 | RECORD-REGEXP specifies the textual units which should be sorted. | ||
| 283 | For example, to sort lines RECORD-REGEXP would be \"^.*$\" | ||
| 284 | KEY specifies the part of each record (ie each match for RECORD-REGEXP) | ||
| 285 | is to be used for sorting. | ||
| 286 | If it is \"\\digit\" then the digit'th \"\\(...\\)\" match field from | ||
| 287 | RECORD-REGEXP is used. | ||
| 288 | If it is \"\\&\" then the whole record is used. | ||
| 289 | Otherwise, it is a regular-expression for which to search within the record. | ||
| 290 | If a match for KEY is not found within a record then that record is ignored. | ||
| 291 | |||
| 292 | With a negative prefix arg sorts in reverse order. | ||
| 293 | |||
| 294 | For example: to sort lines in the region by the first word on each line | ||
| 295 | starting with the letter \"f\", | ||
| 296 | RECORD-REGEXP would be \"^.*$\" and KEY would be \"\\=\\<f\\w*\\>\"" | ||
| 297 | (interactive "P\nsRegexp specifying records to sort: | ||
| 298 | sRegexp specifying key within record: \nr") | ||
| 299 | (cond ((or (equal key-regexp "") (equal key-regexp "\\&")) | ||
| 300 | (setq key-regexp 0)) | ||
| 301 | ((string-match "\\`\\\\[1-9]\\'" key-regexp) | ||
| 302 | (setq key-regexp (- (aref key-regexp 1) ?0)))) | ||
| 303 | (save-excursion | ||
| 304 | (save-restriction | ||
| 305 | (narrow-to-region beg end) | ||
| 306 | (goto-char (point-min)) | ||
| 307 | (let (sort-regexp-record-end) ;isn't dynamic scoping wonderful? | ||
| 308 | (re-search-forward record-regexp) | ||
| 309 | (setq sort-regexp-record-end (point)) | ||
| 310 | (goto-char (match-beginning 0)) | ||
| 311 | (sort-subr reverse | ||
| 312 | (function (lambda () | ||
| 313 | (and (re-search-forward record-regexp nil 'move) | ||
| 314 | (setq sort-regexp-record-end (match-end 0)) | ||
| 315 | (goto-char (match-beginning 0))))) | ||
| 316 | (function (lambda () | ||
| 317 | (goto-char sort-regexp-record-end))) | ||
| 318 | (function (lambda () | ||
| 319 | (let ((n 0)) | ||
| 320 | (cond ((numberp key-regexp) | ||
| 321 | (setq n key-regexp)) | ||
| 322 | ((re-search-forward | ||
| 323 | key-regexp sort-regexp-record-end t) | ||
| 324 | (setq n 0)) | ||
| 325 | (t (throw 'key nil))) | ||
| 326 | (condition-case () | ||
| 327 | (if (fboundp 'buffer-substring-lessp) | ||
| 328 | (cons (match-beginning n) | ||
| 329 | (match-end n)) | ||
| 330 | (buffer-substring (match-beginning n) | ||
| 331 | (match-end n))) | ||
| 332 | ;; if there was no such register | ||
| 333 | (error (throw 'key nil))))))))))) | ||
| 334 | |||
| 335 | |||
| 336 | (defvar sort-columns-subprocess t) | ||
| 337 | |||
| 338 | (defun sort-columns (reverse &optional beg end) | ||
| 339 | "Sort lines in region alphabetically by a certain range of columns. | ||
| 340 | For the purpose of this command, the region includes | ||
| 341 | the entire line that point is in and the entire line the mark is in. | ||
| 342 | The column positions of point and mark bound the range of columns to sort on. | ||
| 343 | A prefix argument means sort into reverse order. | ||
| 344 | |||
| 345 | Note that `sort-columns' rejects text that contains tabs, | ||
| 346 | because tabs could be split across the specified columns | ||
| 347 | and it doesn't know how to handle that. Also, when possible, | ||
| 348 | it uses the `sort' utility program, which doesn't understand tabs. | ||
| 349 | Use \\[untabify] to convert tabs to spaces before sorting." | ||
| 350 | (interactive "P\nr") | ||
| 351 | (save-excursion | ||
| 352 | (let (beg1 end1 col-beg1 col-end1 col-start col-end) | ||
| 353 | (goto-char (min beg end)) | ||
| 354 | (setq col-beg1 (current-column)) | ||
| 355 | (beginning-of-line) | ||
| 356 | (setq beg1 (point)) | ||
| 357 | (goto-char (max beg end)) | ||
| 358 | (setq col-end1 (current-column)) | ||
| 359 | (forward-line) | ||
| 360 | (setq end1 (point)) | ||
| 361 | (setq col-start (min col-beg1 col-end1)) | ||
| 362 | (setq col-end (max col-beg1 col-end1)) | ||
| 363 | (if (search-backward "\t" beg1 t) | ||
| 364 | (error "sort-columns does not work with tabs. Use M-x untabify.")) | ||
| 365 | (if (not (eq system-type 'vax-vms)) | ||
| 366 | ;; Use the sort utility if we can; it is 4 times as fast. | ||
| 367 | (call-process-region beg1 end1 "sort" t t nil | ||
| 368 | (if reverse "-rt\n" "-t\n") | ||
| 369 | (concat "+0." col-start) | ||
| 370 | (concat "-0." col-end)) | ||
| 371 | ;; On VMS, use Emacs's own facilities. | ||
| 372 | (save-excursion | ||
| 373 | (save-restriction | ||
| 374 | (narrow-to-region beg1 end1) | ||
| 375 | (goto-char beg1) | ||
| 376 | (sort-subr reverse 'forward-line 'end-of-line | ||
| 377 | (function (lambda () (move-to-column col-start) nil)) | ||
| 378 | (function (lambda () (move-to-column col-end) nil))))))))) | ||