tablist.el (66960B)
1 ;;; tablist.el --- Extended tabulated-list-mode -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2013, 2014 Andreas Politz 4 5 ;; Author: Andreas Politz <politza@fh-trier.de> 6 ;; Keywords: extensions, lisp 7 ;; Package: tablist 8 ;; Version: 1.1 9 ;; Package-Requires: ((emacs "24.3")) 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 ;; 26 ;; This package adds marks and filters to tabulated-list-mode. It 27 ;; also kind of puts a dired face on tabulated list buffers. 28 ;; 29 ;; It can be used by deriving from tablist-mode and some features by 30 ;; using tablist-minor-mode inside a tabulated-list-mode buffer. 31 ;; 32 33 ;;; Code: 34 35 (require 'cl-lib) 36 (require 'ring) 37 (require 'tabulated-list) 38 (require 'dired) 39 (require 'tablist-filter) 40 41 ;; 42 ;; *Macros 43 ;; 44 45 (defmacro tablist-save-marks (&rest body) 46 "Eval body, while preserving all marks." 47 (let ((marks (make-symbol "marks"))) 48 `(let (,marks) 49 (save-excursion 50 (goto-char (point-min)) 51 (let ((re "^\\([^ ]\\)")) 52 (while (re-search-forward re nil t) 53 (push (cons (tabulated-list-get-id) 54 (tablist-get-mark-state)) 55 ,marks)))) 56 (unwind-protect 57 (progn ,@body) 58 (save-excursion 59 (dolist (m ,marks) 60 (let ((id (pop m))) 61 (goto-char (point-min)) 62 (while (and id (not (eobp))) 63 (when (equal id (tabulated-list-get-id)) 64 (tablist-put-mark-state m) 65 (setq id nil)) 66 (forward-line))))))))) 67 68 (defmacro tablist-with-remembering-entry (&rest body) 69 "Remember where body left of and restore previous position. 70 71 If the current entry is still visible, move to it. Otherwise move 72 to the next visible one after it. If that also fails, goto to 73 the beginning of the buffer. Finally move point to the major 74 column." 75 (declare (indent 0) (debug t)) 76 (let ((re (make-symbol "re")) 77 (id (make-symbol "id")) 78 (col (make-symbol "col"))) 79 `(let ((,re 80 (replace-regexp-in-string 81 "[\t ]+" "[\t ]*" (regexp-quote 82 (or (thing-at-point 'line) "")) 83 t t)) 84 (,id (tabulated-list-get-id)) 85 (,col (tablist-current-column))) 86 (progn 87 ,@body 88 (let (success pos) 89 (goto-char (point-min)) 90 (setq pos (point)) 91 (while (and (setq success (re-search-forward ,re nil t)) 92 (> (point) (prog1 pos (setq pos (point)))) 93 (forward-line -1) 94 (not (equal ,id (tabulated-list-get-id)))) 95 (forward-line)) 96 (unless success 97 (goto-char (point-min)) 98 (while (and (not (eobp)) 99 (not success)) 100 (if (equal (tabulated-list-get-id) ,id) 101 (setq success t) 102 (forward-line)))) 103 (unless (and success (not (invisible-p (point)))) 104 (goto-char (point-min))) 105 (tablist-skip-invisible-entries) 106 (tablist-move-to-column 107 (or ,col (car (tablist-major-columns)))) 108 (dolist (win (get-buffer-window-list)) 109 (set-window-point win (point)))))))) 110 111 (defmacro tablist-with-filter-displayed (&rest body) 112 "Display the current filter in the mode while evalling BODY." 113 (let ((state (make-symbol "state"))) 114 `(let ((,state (tablist-display-filter 'state))) 115 (tablist-display-filter t) 116 (unwind-protect 117 (progn ,@body) 118 (tablist-display-filter ,state))))) 119 120 ;; 121 ;; *Mode Maps 122 ;; 123 124 (defvar tablist-mode-filter-map 125 (let ((kmap (make-sparse-keymap))) 126 (define-key kmap "p" #'tablist-pop-filter) 127 (define-key kmap "r" #'tablist-push-regexp-filter) 128 (define-key kmap "=" #'tablist-push-equal-filter) 129 (define-key kmap "n" #'tablist-push-numeric-filter) 130 (define-key kmap "!" #'tablist-negate-filter) 131 (define-key kmap "t" #'tablist-toggle-first-filter-logic) 132 (define-key kmap "/" #'tablist-display-filter) 133 (define-key kmap "z" #'tablist-suspend-filter) 134 135 (define-key kmap "a" #'tablist-push-named-filter) 136 (define-key kmap "s" #'tablist-name-current-filter) 137 (define-key kmap "D" #'tablist-delete-named-filter) 138 (define-key kmap "d" #'tablist-deconstruct-named-filter) 139 (define-key kmap "e" #'tablist-edit-filter) 140 (define-key kmap "C" #'tablist-clear-filter) 141 kmap)) 142 143 (defvar tablist-mode-mark-map 144 (let ((kmap (make-sparse-keymap))) 145 (define-key kmap "c" #'tablist-change-marks) 146 (define-key kmap "!" #'tablist-unmark-all-marks) 147 (define-key kmap "r" #'tablist-mark-items-regexp) 148 (define-key kmap "n" #'tablist-mark-items-numeric) 149 (define-key kmap "m" #'tablist-mark-forward) 150 kmap)) 151 152 (defvar tablist-mode-regexp-map 153 (let ((kmap (make-sparse-keymap))) 154 ;; (define-key kmap "&" #'tablist-flag-gargabe-items) 155 (define-key kmap "m" #'tablist-mark-items-regexp) 156 kmap)) 157 158 (defvar tablist-minor-mode-map 159 (let ((kmap (make-sparse-keymap))) 160 (define-key kmap "m" #'tablist-mark-forward) 161 (define-key kmap (kbd "DEL") #'tablist-unmark-backward) 162 (define-key kmap "k" #'tablist-do-kill-lines) 163 (define-key kmap "U" #'tablist-unmark-all-marks) 164 (define-key kmap "u" #'tablist-unmark-forward) 165 (define-key kmap "t" #'tablist-toggle-marks) 166 167 (define-key kmap (kbd "TAB") #'tablist-forward-column) 168 (define-key kmap "\t" #'tablist-forward-column) 169 (define-key kmap [backtab] #'tablist-backward-column) 170 171 (define-key kmap "%" tablist-mode-regexp-map) 172 (define-key kmap "*" tablist-mode-mark-map) 173 (define-key kmap "/" tablist-mode-filter-map) 174 175 ;; (define-key kmap "e" #'tablist-edit-column) 176 ;; (define-key kmap "i" #'tablist-insert-entry) 177 (define-key kmap "s" #'tablist-sort) 178 (define-key kmap [remap back-to-indentation] #'tablist-move-to-major-column) 179 (define-key kmap [remap next-line] #'tablist-next-line) 180 (define-key kmap [remap previous-line] #'tablist-previous-line) 181 (define-key kmap "<" #'tablist-shrink-column) 182 (define-key kmap ">" #'tablist-enlarge-column) 183 (define-key kmap "q" #'tablist-quit) 184 (define-key kmap "G" #'tablist-revert) 185 (define-key kmap (kbd "C-c C-e") #'tablist-export-csv) 186 kmap)) 187 188 (defvar tablist-mode-map 189 (let ((kmap (copy-keymap tablist-minor-mode-map))) 190 (set-keymap-parent kmap tabulated-list-mode-map) 191 (define-key kmap "d" #'tablist-flag-forward) 192 (define-key kmap (kbd "RET") #'tablist-find-entry) 193 (define-key kmap "f" #'tablist-find-entry) 194 ;; (define-key kmap "~" #'tablist-flag-gargabe-items) 195 (define-key kmap "D" #'tablist-do-delete) 196 ;; (define-key kmap "C" #'tablist-do-copy) 197 ;; (define-key kmap "R" #'tablist-do-rename) 198 (define-key kmap "x" #'tablist-do-flagged-delete) 199 ;; (define-key kmap "F" #'tablist-find-marked-items) 200 ;; (define-key kmap (kbd "C-o") #'tablist-display-item) 201 kmap)) 202 203 ;; 204 ;; *Variables 205 ;; 206 207 ;; Marking 208 (defvar tablist-umark-filtered-entries t) 209 (defvar tablist-marker-char dired-marker-char 210 "The character used for marking.") 211 (defvar tablist-marker-face 'dired-mark 212 "The face used for the mark character.") 213 (defvar tablist-marked-face 'dired-marked 214 "The face used for marked major columns.") 215 216 ;; Operations 217 (defvar-local tablist-operations-function nil 218 "A function for handling operations on the entries. 219 220 The function is called with varying number of arguments, while 221 the first one is always a symbol describing one of the following 222 operations. 223 224 `supported-operations' 225 226 This is the only mandatory operation. There are no other 227 arguments and the function should return a list of symbols of 228 supported operations. 229 230 `delete' 231 232 The 2nd argument will be a list of entry ID's. The function 233 should somehow delete these entries and update 234 `tabulated-list-entries'. 235 236 `find-entry' 237 238 The 2nd argument is the ID of an entry. The function should 239 somehow find/display this entry, i.e. a kind of default 240 operation. 241 242 `edit-column' 243 244 The function is called with 3 further arguments: ID, INDEX and 245 NEW-COLUMN, where ID represents the entry to edit, INDEX is the index 246 of the column and NEW-COLUMN is the proposed new value for this 247 column. It should either 248 249 i. return a new edited complete entry and update 250 `tabulated-list-entries', or 251 252 ii. throw an error, if NEW-COLUMN is not a valid value for this 253 column. 254 255 `complete' 256 257 The function is called with 4 further arguments: ID, INDEX, 258 STRING and POS, where ID represents an entry, INDEX is the index 259 of the column to complete, STRING it's current value and POS an 260 offset of the current position of point into STRING. 261 262 The function should return a collection for this column, suitable 263 as argument for the function `completion-in-region'.") 264 265 ;; Differentiating columns 266 (defvar-local tablist-major-columns nil 267 "Columns used to mark and when querying.") 268 269 ;; Filter 270 (defvar-local tablist-current-filter nil) 271 (defvar-local tablist-filter-suspended nil) 272 (defvar tablist-named-filter nil) 273 274 ;; History variables 275 (defvar tablist-column-name-history nil) 276 277 ;; Hooks 278 (defvar tablist-selection-changed-functions nil 279 "A hook run when ever point moves to a different entry.") 280 281 ;; Context Window 282 (defvar-local tablist-context-window nil) 283 (defvar-local tablist-context-window-function nil) 284 (defvar tablist-context-window-display-action 285 `((display-buffer-reuse-window 286 tablist-display-buffer-split-below-and-attach) 287 (window-height . 0.25) 288 (inhibit-same-window . t))) 289 290 ;; 291 ;; *Setup 292 ;; 293 294 (defvar savehist-additional-variables) 295 (add-hook 'savehist-save-hook 296 (lambda nil 297 (add-to-list 'savehist-additional-variables 'tablist-named-filter))) 298 299 ;;;###autoload 300 (define-minor-mode tablist-minor-mode 301 "Toggle tablist minor mode." 302 :global nil 303 (unless (derived-mode-p 'tabulated-list-mode) 304 (error "Buffer is not in Tabulated List Mode")) 305 (tablist-init (not tablist-minor-mode))) 306 307 ;;;###autoload 308 (define-derived-mode tablist-mode tabulated-list-mode "TL" 309 (tablist-init)) 310 311 (defun tablist-init (&optional disable) 312 (let ((cleaned-misc (cl-remove 'tablist-current-filter 313 mode-line-misc-info :key #'car-safe))) 314 (cond 315 ((not disable) 316 (set (make-local-variable 'mode-line-misc-info) 317 (append 318 (list 319 (list 'tablist-current-filter 320 '(:eval (format " [%s]" 321 (if tablist-filter-suspended 322 "suspended" 323 "filtered"))))))) 324 (add-hook 'post-command-hook 325 'tablist-selection-changed-handler nil t) 326 (add-hook 'tablist-selection-changed-functions 327 'tablist-context-window-update nil t)) 328 (t 329 (setq mode-line-misc-info cleaned-misc) 330 (remove-hook 'post-command-hook 331 'tablist-selection-changed-handler t) 332 (remove-hook 'tablist-selection-changed-functions 333 'tablist-context-window-update t))))) 334 335 (defun tablist-quit () 336 (interactive) 337 (tablist-hide-context-window) 338 (quit-window)) 339 340 (defvar-local tablist-selected-id nil) 341 (defvar tablist-edit-column-minor-mode) 342 343 (defun tablist-selection-changed-handler () 344 (unless tablist-edit-column-minor-mode 345 (let ((id tablist-selected-id) 346 (selected (tabulated-list-get-id))) 347 (unless (eq selected id) 348 (setq tablist-selected-id selected) 349 (run-hook-with-args 350 'tablist-selection-changed-functions 351 tablist-selected-id))))) 352 353 (defvar tablist-context-window-update--timer nil) 354 355 (defun tablist-context-window-update (&optional id) 356 (when (and tablist-context-window-function 357 (window-live-p tablist-context-window) 358 (not tablist-edit-column-minor-mode)) 359 (unless id 360 (setq id (tabulated-list-get-id))) 361 (when (timerp tablist-context-window-update--timer) 362 (cancel-timer tablist-context-window-update--timer)) 363 (setq tablist-context-window-update--timer 364 (run-with-idle-timer 0.1 nil 365 (lambda (fn window) 366 (when (window-live-p window) 367 (with-selected-window window 368 (set-window-dedicated-p nil nil) 369 (save-selected-window 370 (funcall fn id)) 371 (when (window-live-p (selected-window)) 372 (set-window-dedicated-p nil t))))) 373 tablist-context-window-function 374 tablist-context-window)))) 375 376 (defun tablist-display-context-window () 377 (interactive) 378 (unless tablist-context-window-function 379 (error "No function for handling a context is defined")) 380 (unless (window-live-p tablist-context-window) 381 (setq tablist-context-window 382 (display-buffer 383 (current-buffer) 384 tablist-context-window-display-action))) 385 (prog1 386 tablist-context-window 387 (tablist-context-window-update))) 388 389 (defun tablist-hide-context-window () 390 (interactive) 391 (when (window-live-p tablist-context-window) 392 (let ((ignore-window-parameters t)) 393 (delete-window tablist-context-window))) 394 (setq tablist-context-window nil)) 395 396 (defun tablist-toggle-context-window () 397 (interactive) 398 (if (window-live-p tablist-context-window) 399 (tablist-hide-context-window) 400 (tablist-display-context-window))) 401 402 ;; 403 ;; *Marking 404 ;; 405 406 (defun tablist-revert () 407 "Revert the list with marks preserved, position kept." 408 (interactive) 409 (tablist-save-marks 410 (tablist-with-remembering-entry 411 (tabulated-list-revert)))) 412 413 (defun tablist-major-columns () 414 (if (null tablist-major-columns) 415 (number-sequence 0 (1- (length tabulated-list-format))) 416 (if (numberp tablist-major-columns) 417 (list tablist-major-columns) 418 tablist-major-columns))) 419 420 (defun tablist-put-mark (&optional pos) 421 "Put a mark before the entry at POS. 422 423 POS defaults to point. Use `tablist-marker-char', 424 `tablist-marker-face', `tablist-marked-face' and 425 `tablist-major-columns' to determine how to mark and what to put 426 a face on." 427 (when (or (null tabulated-list-padding) 428 (< tabulated-list-padding 1)) 429 (setq tabulated-list-padding 1) 430 (tabulated-list-revert)) 431 (save-excursion 432 (and pos (goto-char pos)) 433 (unless (tabulated-list-get-id) 434 (error "No entry at this position")) 435 (let ((inhibit-read-only t)) 436 (tabulated-list-put-tag 437 (string tablist-marker-char)) 438 (put-text-property 439 (point-at-bol) 440 (1+ (point-at-bol)) 441 'face tablist-marker-face) 442 (let ((columns (tablist-column-offsets))) 443 (dolist (c (tablist-major-columns)) 444 (when (and (>= c 0) 445 (< c (length columns))) 446 (let ((beg (+ (point-at-bol) 447 (nth c columns))) 448 (end (if (= c (1- (length columns))) 449 (point-at-eol) 450 (+ (point-at-bol) 451 (nth (1+ c) columns))))) 452 (cond 453 ((and tablist-marked-face 454 (not (eq tablist-marker-char ?\s))) 455 (tablist--save-face-property beg end) 456 (put-text-property 457 beg end 'face tablist-marked-face)) 458 (t (tablist--restore-face-property beg end)))))))))) 459 460 (defun tablist-mark-forward (&optional arg interactive) 461 "Mark ARG entries forward. 462 463 ARG is interpreted as a prefix-arg. If interactive is non-nil, 464 maybe use the active region instead of ARG. 465 466 See `tablist-put-mark' for how entries are marked." 467 (interactive (list current-prefix-arg t)) 468 (cond 469 ;; Mark files in the active region. 470 ((and interactive (use-region-p)) 471 (save-excursion 472 (goto-char (region-beginning)) 473 (beginning-of-line) 474 (tablist-repeat-over-lines 475 (1+ (count-lines 476 (point) 477 (save-excursion 478 (goto-char (region-end)) 479 (beginning-of-line) 480 (point)))) 481 'tablist-put-mark))) 482 ;; Mark the current (or next ARG) files. 483 (t 484 (tablist-repeat-over-lines 485 (prefix-numeric-value arg) 486 'tablist-put-mark)))) 487 488 (defun tablist-mark-backward (&optional arg interactive) 489 "Mark ARG entries backward. 490 491 See `tablist-mark-forward'." 492 (interactive (list current-prefix-arg t)) 493 (tablist-mark-forward (- (prefix-numeric-value arg)) 494 interactive)) 495 496 (defun tablist-unmark-forward (&optional arg interactive) 497 "Unmark ARG entries forward. 498 499 See `tablist-mark-forward'." 500 (interactive (list current-prefix-arg t)) 501 (let ((tablist-marker-char ?\s) 502 tablist-marked-face) 503 (tablist-mark-forward arg interactive))) 504 505 (defun tablist-unmark-backward (&optional arg interactive) 506 "Unmark ARG entries backward. 507 508 See `tablist-mark-forward'." 509 (interactive (list current-prefix-arg t)) 510 (let ((tablist-marker-char ?\s) 511 tablist-marked-face) 512 (tablist-mark-backward arg interactive))) 513 514 (defun tablist-flag-forward (&optional arg interactive) 515 "Flag ARG entries forward. 516 517 See `tablist-mark-forward'." 518 (interactive (list current-prefix-arg t)) 519 (let ((tablist-marker-char ?D) 520 (tablist-marked-face 'dired-flagged)) 521 (tablist-mark-forward arg interactive))) 522 523 (defun tablist-change-marks (old new) 524 "Change all OLD marks to NEW marks. 525 526 OLD and NEW are both characters used to mark files." 527 (interactive 528 (let* ((cursor-in-echo-area t) 529 (old (progn (message "Change (old mark): ") (read-char))) 530 (new (progn (message "Change %c marks to (new mark): " old) 531 (read-char)))) 532 (list old new))) 533 (when (eq new ?\n) 534 (error "Mark character \\n is not allowed")) 535 (let ((default-mark-p (equal tablist-marker-char new)) 536 (tablist-marker-char old)) 537 (save-excursion 538 (tablist-map-over-marks 539 (lambda nil 540 (pcase new 541 (?D 542 (tablist-flag-forward 1)) 543 (_ 544 (let ((tablist-marker-char new) 545 (tablist-marked-face 546 (and default-mark-p 547 tablist-marked-face))) 548 (tablist-put-mark))))))))) 549 550 (defun tablist-unmark-all-marks (&optional marks interactive) 551 "Remove all marks in MARKS. 552 553 MARKS should be a string of mark characters to match and defaults 554 to all marks. Interactively, remove all marks, unless a prefix 555 arg was given, in which case ask about which ones to remove. 556 Give a message, if interactive is non-nil. 557 558 Returns the number of unmarked marks." 559 (interactive 560 (list (if current-prefix-arg 561 (read-string "Remove marks: ")) t)) 562 (let ((re (if marks 563 (tablist-marker-regexp marks) 564 "^[^ ]")) 565 (removed 0)) 566 (save-excursion 567 (goto-char (point-min)) 568 (while (re-search-forward re nil t) 569 (let ((tablist-marker-char ?\s) 570 tablist-marker-face 571 tablist-marked-face) 572 (tablist-put-mark)) 573 (cl-incf removed))) 574 (when interactive 575 (message "Removed %d marks" removed)) 576 removed)) 577 578 (defun tablist-toggle-marks () 579 "Unmark all marked and mark all unmarked entries. 580 581 See `tablist-put-mark'." 582 (interactive) 583 (let ((marked-re (tablist-marker-regexp)) 584 (not-marked-re 585 (let ((tablist-marker-char ?\s)) 586 (tablist-marker-regexp)))) 587 (save-excursion 588 (goto-char (point-min)) 589 (tablist-skip-invisible-entries) 590 (while (not (eobp)) 591 (cond 592 ((looking-at marked-re) 593 (save-excursion (tablist-unmark-backward -1))) 594 ((looking-at not-marked-re) 595 (tablist-put-mark))) 596 (tablist-forward-entry))) 597 (tablist-move-to-major-column))) 598 599 (defun tablist-get-marked-items (&optional arg distinguish-one-marked) 600 "Return marked or ARG entries." 601 (let ((items (save-excursion 602 (tablist-map-over-marks 603 (lambda () (cons (tabulated-list-get-id) 604 (tabulated-list-get-entry))) 605 arg nil distinguish-one-marked)))) 606 (if (and distinguish-one-marked 607 (eq (car items) t)) 608 items 609 (nreverse items)))) 610 611 (defun tablist-mark-items-regexp (column-name regexp) 612 "Mark entries matching REGEXP in column COLUMN-NAME." 613 (interactive 614 (tablist-read-regexp-filter "Mark" current-prefix-arg )) 615 (tablist-map-with-filter 616 'tablist-put-mark 617 `(=~ ,column-name ,regexp))) 618 619 (defun tablist-mark-items-numeric (binop column-name operand) 620 "Mark items fulfilling BINOP with arg OPERAND in column COLUMN-NAME. 621 622 First the column's value is coerced to a number N. Then the test 623 proceeds as \(BINOP N OPERAND\)." 624 (interactive 625 (tablist-read-numeric-filter "Mark" current-prefix-arg)) 626 (tablist-map-with-filter 627 'tablist-put-mark 628 `(,binop ,column-name ,operand))) 629 630 (defun tablist-map-over-marks (fn &optional arg show-progress 631 distinguish-one-marked) 632 (prog1 633 (cond 634 ((and arg (integerp arg)) 635 (let (results) 636 (tablist-repeat-over-lines 637 arg 638 (lambda () 639 (if show-progress (sit-for 0)) 640 (push (funcall fn) results))) 641 (if (< arg 0) 642 (nreverse results) 643 results))) 644 (arg 645 ;; non-nil, non-integer ARG means use current item: 646 (tablist-skip-invisible-entries) 647 (unless (eobp) 648 (list (funcall fn)))) 649 (t 650 (cl-labels ((search (re) 651 (let (success) 652 (tablist-skip-invisible-entries) 653 (while (and (setq success 654 (re-search-forward re nil t)) 655 (invisible-p (point))) 656 (tablist-forward-entry)) 657 success))) 658 (let ((regexp (tablist-marker-regexp)) 659 next-position results found) 660 (save-excursion 661 (goto-char (point-min)) 662 ;; remember position of next marked file before BODY 663 ;; can insert lines before the just found file, 664 ;; confusing us by finding the same marked file again 665 ;; and again and... 666 (setq next-position (and (search regexp) 667 (point-marker)) 668 found (not (null next-position))) 669 (while next-position 670 (goto-char next-position) 671 (if show-progress (sit-for 0)) 672 (push (funcall fn) results) 673 ;; move after last match 674 (goto-char next-position) 675 (forward-line 1) 676 (set-marker next-position nil) 677 (setq next-position (and (search regexp) 678 (point-marker))))) 679 (if (and distinguish-one-marked (= (length results) 1)) 680 (setq results (cons t results))) 681 (if found 682 results 683 (unless (or (eobp) (invisible-p (point))) 684 (list (funcall fn)))))))) 685 (tablist-move-to-major-column))) 686 687 (defun tablist-marker-regexp (&optional marks) 688 "Return a regexp matching marks in MARKS. 689 690 MARKS should be a string of mark characters to match and defaults 691 to the current value of `tablist-marker-char' as a string." 692 (concat (format "^[%s]" 693 (or marks (string tablist-marker-char))))) 694 695 (defun tablist-get-mark-state () 696 "Return the mark state of the entry at point." 697 (save-excursion 698 (beginning-of-line) 699 (when (looking-at "^\\([^ ]\\)") 700 (let ((mark (buffer-substring 701 (match-beginning 1) 702 (match-end 1)))) 703 (tablist-move-to-major-column) 704 (list (aref mark 0) 705 (get-text-property 0 'face mark) 706 (get-text-property (point) 'face)))))) 707 708 (defun tablist-put-mark-state (state) 709 "Set the mark of the entry at point according to STATE. 710 711 STATE is a return value of `tablist-get-mark-state'." 712 (cl-destructuring-bind (tablist-marker-char 713 tablist-marker-face 714 tablist-marked-face) 715 state 716 (tablist-put-mark))) 717 718 (defun tablist-mark-prompt (arg items) 719 "Return a string suitable for use in a tablist prompt." 720 ;; distinguish-one-marked can cause the first element to be just t. 721 (if (eq (car items) t) (setq items (cdr items))) 722 (let ((count (length items))) 723 (if (= count 1) 724 (car items) 725 ;; more than 1 item: 726 (if (integerp arg) 727 ;; abs(arg) = count 728 ;; Perhaps this is nicer, but it also takes more screen space: 729 ;;(format "[%s %d items]" (if (> arg 0) "next" "previous") 730 ;; count) 731 (format "[next %d item%s]" 732 arg (dired-plural-s arg)) 733 (format "%c [%d item%s]" dired-marker-char count 734 (dired-plural-s count)))))) 735 736 ;; 737 ;; *Movement 738 ;; 739 740 (defun tablist-forward-entry (&optional n) 741 "Move past the next N unfiltered entries." 742 (unless n (setq n 1)) 743 (while (and (> n 0) 744 (not (eobp))) 745 (forward-line) 746 (when (invisible-p (point)) 747 (tablist-skip-invisible-entries)) 748 (cl-decf n)) 749 (while (and (< n 0) 750 (not (bobp))) 751 (forward-line -1) 752 (when (invisible-p (point)) 753 (tablist-skip-invisible-entries t)) 754 (cl-incf n)) 755 n) 756 757 (defun tablist-next-line (&optional n) 758 (interactive "p") 759 (when (and (< n 0) 760 (save-excursion 761 (end-of-line 0) 762 (tablist-skip-invisible-entries t) 763 (bobp))) 764 (signal 'beginning-of-buffer nil)) 765 (when (and (> n 0) 766 (save-excursion 767 (tablist-forward-entry) 768 (eobp))) 769 (signal 'end-of-buffer nil)) 770 771 (let ((col (tablist-current-column))) 772 (tablist-forward-entry (or n 1)) 773 (if col 774 (tablist-move-to-column col) 775 (tablist-move-to-major-column)))) 776 777 (defun tablist-previous-line (&optional n) 778 (interactive "p") 779 (tablist-next-line (- (or n 1)))) 780 781 (defun tablist-repeat-over-lines (arg function) 782 "Call FUNCTION for the next ARG entries." 783 ;; Move out of potentially invisible area. 784 (tablist-skip-invisible-entries) 785 (let ((pos (make-marker))) 786 (while (and (> arg 0) 787 (not (eobp))) 788 (cl-decf arg) 789 (save-excursion 790 (tablist-forward-entry) 791 (move-marker pos (1+ (point)))) 792 (unless (eobp) 793 (save-excursion (funcall function))) 794 ;; Advance to the next line--actually, to the line that *was* next. 795 ;; (If FUNCTION inserted some new lines in between, skip them.) 796 (goto-char pos)) 797 (while (and (< arg 0) (not (bobp))) 798 (cl-incf arg) 799 (tablist-forward-entry -1) 800 (save-excursion (funcall function))) 801 (move-marker pos nil) 802 (tablist-move-to-major-column))) 803 804 (defun tablist-move-to-column (n) 805 "Move to the N'th list column." 806 (interactive "p") 807 (when (tabulated-list-get-id) 808 (let ((columns (tablist-column-offsets))) 809 (when (or (< n 0) 810 (>= n (length columns))) 811 (error "No such column: %s" n)) 812 (beginning-of-line) 813 (forward-char (nth n columns)) 814 (when (and (plist-get (nthcdr 3 (elt tabulated-list-format n)) 815 :right-align) 816 (not (= n (1- (length columns))))) 817 (forward-char (1- (car (cdr (elt tabulated-list-format n))))))))) 818 819 (defun tablist-move-to-major-column (&optional first-skip-invisible-p) 820 "Move to the first major column." 821 (interactive (list t)) 822 (when first-skip-invisible-p 823 (tablist-skip-invisible-entries)) 824 (tablist-move-to-column (car (tablist-major-columns)))) 825 826 (defun tablist-forward-column (n) 827 "Move n columns forward, while wrapping around." 828 (interactive "p") 829 (unless (tabulated-list-get-id) 830 (error "No entry on this line")) 831 (let* ((columns (tablist-column-offsets)) 832 (current (1- (length columns)))) 833 ;; find current column 834 (while (and (>= current 0) 835 (> (nth current columns) 836 (current-column))) 837 (cl-decf current)) 838 ;; there may be an invisible spec here 839 (when (bolp) 840 (forward-char)) 841 ;; before any columns 842 (when (< current 0) 843 (goto-char (+ (point-at-bol) (if (> n 0) 844 (car columns) 845 (car (last columns))))) 846 (setq n (* (cl-signum n) (1- (abs n))))) 847 (when (/= n 0) 848 (tablist-move-to-column 849 (mod (+ current n) (length columns)))))) 850 851 (defun tablist-backward-column (n) 852 "Move n columns backward, while wrapping around." 853 (interactive "p") 854 (tablist-forward-column (- n))) 855 856 ;; 857 (defun tablist-skip-invisible-entries (&optional backward) 858 "Skip invisible entries BACKWARD or forward. 859 860 Do nothing, if the entry at point is visible. Otherwise move to 861 the beginning of the next visible entry in the direction 862 determined by BACKWARD. 863 864 Return t, if point is now in a visible area." 865 866 (cond 867 ((and backward 868 (not (bobp)) 869 (get-text-property (point) 'invisible)) 870 (when (get-text-property (1- (point)) 'invisible) 871 (goto-char (previous-single-property-change 872 (point) 873 'invisible nil (point-min)))) 874 (forward-line -1)) 875 ((and (not backward) 876 (not (eobp)) 877 (get-text-property (point) 'invisible)) 878 (goto-char (next-single-property-change 879 (point) 880 'invisible nil (point-max))))) 881 (not (invisible-p (point)))) 882 883 ;; 884 ;; *Operations 885 ;; 886 887 (defun tablist-yes-or-no-p (operation arg items) 888 "Query the user whether to proceed with some operation. 889 890 Operation should be a symbol or string describing the operation, 891 ARG the prefix-arg of the command used in 892 `tablist-get-marked-items' or elsewhere, to get the ITEMS." 893 894 (let ((pp-items (mapcar 'tablist-pretty-print-entry 895 (mapcar 'cdr items))) 896 dired-no-confirm 897 (op-str (upcase-initials 898 (if (stringp operation) 899 operation 900 (symbol-name operation))))) 901 (dired-mark-pop-up 902 (format " *%s*" op-str) nil 903 pp-items 904 dired-deletion-confirmer 905 (format "%s %s " 906 op-str 907 (tablist-mark-prompt arg pp-items))))) 908 909 (defun tablist-operation-available-p (op) 910 (and (functionp tablist-operations-function) 911 (memq op (funcall tablist-operations-function 912 'supported-operations)))) 913 914 (defun tablist-do-delete (&optional arg) 915 "Delete ARG entries." 916 (interactive "P") 917 (unless (tablist-operation-available-p 'delete) 918 (error "Deleting entries is not available in this buffer")) 919 (let ((items (tablist-get-marked-items arg))) 920 (when (tablist-yes-or-no-p 'delete arg items) 921 (tablist-do-kill-lines arg) 922 (funcall tablist-operations-function 923 'delete (mapcar 'car items)) 924 (tablist-move-to-major-column)))) 925 926 (defun tablist-do-flagged-delete (&optional interactive) 927 "Delete all entries marked with a D." 928 (interactive "p") 929 (let* ((tablist-marker-char ?D)) 930 (if (save-excursion 931 (goto-char (point-min)) 932 (re-search-forward (tablist-marker-regexp) nil t)) 933 (tablist-do-delete) 934 (or (not interactive) 935 (message "(No deletions requested)"))))) 936 937 (defun tablist-do-kill-lines (&optional arg interactive) 938 "Remove ARG lines from the display." 939 (interactive (list current-prefix-arg t)) 940 (save-excursion 941 (let ((positions 942 (tablist-map-over-marks 'point arg)) 943 (inhibit-read-only t)) 944 (dolist (pos positions) 945 (goto-char pos) 946 (tabulated-list-delete-entry)) 947 (when interactive 948 (message (format "Killed %d line%s" 949 (length positions) 950 (dired-plural-s (length positions)))))))) 951 952 (defun tablist-do-operation (arg fn operation &optional delete-p revert-p) 953 "Operate on marked items. 954 955 ARG should be the `current-prefix-arg', FN is a function of two 956 arguments \(ID ENTRY\) handling the operation. It gets called 957 repeatedly with all marked items. OPERATION is a symbol or string 958 describing the operation, it is used for display. 959 960 Optional non-nil DELETE-P means, remove the items from the display. 961 Optional REVERT-P means, revert the display afterwards." 962 (let ((items (tablist-get-marked-items arg))) 963 (unless items 964 (error "No items marked")) 965 (when (tablist-yes-or-no-p operation arg items) 966 (when delete-p 967 (tablist-do-kill-lines arg)) 968 (dolist (item items) 969 (funcall fn (car item))) 970 (when revert-p 971 (tablist-revert)) 972 (tablist-move-to-major-column)))) 973 974 ;; 975 ;; *Editing 976 ;; 977 (defvar tablist-edit-column-minor-mode-map 978 (let ((kmap (make-sparse-keymap))) 979 (set-keymap-parent kmap (current-global-map)) 980 (define-key kmap [remap self-insert-command] #'self-insert-command) 981 (define-key kmap "\r" #'tablist-edit-column-commit) 982 (define-key kmap (kbd "C-g") #'tablist-edit-column-quit) 983 (define-key kmap (kbd "C-c C-c") #'tablist-edit-column-commit) 984 (define-key kmap (kbd "C-c C-q") #'tablist-edit-column-quit) 985 (define-key kmap "\t" #'tablist-edit-column-complete) 986 (define-key kmap (kbd "TAB") #'tablist-edit-column-complete) 987 (define-key kmap [remap end-of-buffer] #'end-of-line) 988 (define-key kmap [remap beginning-of-buffer] #'beginning-of-line) 989 (define-key kmap [remap mark-whole-buffer] #'tablist-edit-column-mark-field) 990 kmap)) 991 992 (define-minor-mode tablist-edit-column-minor-mode 993 "Toggle tablist-edit-column minor mode." 994 :global nil 995 (unless (or tablist-minor-mode 996 (derived-mode-p 'tablist-mode)) 997 (error "Not in a tablist buffer")) 998 (cond 999 (tablist-edit-column-minor-mode 1000 (add-to-list 'mode-line-misc-info 1001 '(tablist-edit-column-minor-mode "[edit]")) 1002 (add-hook 'post-command-hook 'tablist-edit-column-constrain-point nil t) 1003 (read-only-mode -1)) 1004 (t 1005 (remove-hook 'post-command-hook 'tablist-edit-column-constrain-point t) 1006 (read-only-mode 1)))) 1007 1008 (defun tablist-edit-column (&optional n) 1009 (interactive "P") 1010 (unless n (setq n (tablist-current-column))) 1011 (tablist-assert-column-editable n) 1012 (let* ((offsets (append (tablist-column-offsets) 1013 (list (- (point-at-eol) 1014 (point-at-bol))))) 1015 (beg (+ (point-at-bol) 1016 (nth n offsets))) 1017 (end (+ (point-at-bol) 1018 (nth (1+ n) offsets))) 1019 (entry (tabulated-list-get-entry beg)) 1020 (inhibit-read-only t) 1021 (inhibit-field-text-motion t) 1022 (alist `((entry . ,entry) 1023 (column . ,n) 1024 (id . ,(tabulated-list-get-id beg)))) 1025 ov) 1026 (goto-char beg) 1027 (delete-region beg end) 1028 (add-text-properties 1029 (point-at-bol) (point-at-eol) 1030 '(read-only t field t)) 1031 (unless (= beg (point-at-bol)) 1032 (put-text-property (1- beg) beg 'rear-nonsticky t)) 1033 (save-excursion 1034 ;; Keep one read-only space at the end for keeping text 1035 ;; properties. 1036 (insert 1037 (propertize 1038 (concat 1039 (tablist-nth-entry n entry) 1040 (propertize " " 1041 'display `(space :align-to ,(- end (point-at-bol))))) 1042 'field nil 1043 'front-sticky '(tablist-edit) 1044 'rear-nonsticky '(read-only field) 1045 'tablist-edit alist)) 1046 (setq end (point))) 1047 (add-text-properties 1048 (1- end) end '(read-only t field 'tablist-edit-end)) 1049 (setq ov (make-overlay beg end)) 1050 (overlay-put ov 'priority 9999) 1051 (overlay-put ov 'face '(:background "deep sky blue" :foreground "white")) 1052 (overlay-put ov 'evaporate t) 1053 (overlay-put ov 'tablist-edit t) 1054 (tablist-edit-column-minor-mode 1))) 1055 1056 (defun tablist-edit-column-quit () 1057 (interactive) 1058 (tablist-edit-column-commit t)) 1059 1060 (defun tablist-edit-column-commit (&optional abandon-edit) 1061 (interactive (list current-prefix-arg)) 1062 (let ((inhibit-read-only t) 1063 (inhibit-field-text-motion t) 1064 bounds) 1065 (condition-case nil 1066 (setq bounds (tablist-edit-column-bounds)) 1067 (error 1068 (tablist-edit-column-minor-mode -1) 1069 (tabulated-list-revert) 1070 (put-text-property (point-min) (point-max) 1071 'tablist-edit nil) 1072 (error "Unable to complete the edit"))) 1073 (let* ((beg (car bounds)) 1074 (end (cdr bounds)) 1075 (alist (get-text-property beg 'tablist-edit)) 1076 (column (cdr (assq 'column alist))) 1077 (id (cdr (assq 'id alist))) 1078 (entry (cdr (assq 'entry alist))) 1079 (item (buffer-substring-no-properties beg (1- end)))) 1080 1081 (unless abandon-edit 1082 ;; Throws an error, if item is invalid. 1083 (setq entry (funcall tablist-operations-function 1084 'edit-column id column item))) 1085 (tablist-edit-column-minor-mode -1) 1086 (remove-overlays beg end 'tablist-edit t) 1087 (put-text-property beg end 'tablist-edit nil) 1088 (delete-region (point-at-bol) (1+ (point-at-eol))) 1089 (save-excursion 1090 (tabulated-list-print-entry id entry)) 1091 (forward-char (nth column (tablist-column-offsets)))))) 1092 1093 (defun tablist-edit-column-complete () 1094 (interactive) 1095 (unless (tablist-operation-available-p 'complete) 1096 (error "Completion not available")) 1097 (cl-destructuring-bind (beg &rest end) 1098 (tablist-edit-column-bounds t) 1099 (let* ((string (buffer-substring-no-properties 1100 beg end)) 1101 (alist (get-text-property beg 'tablist-edit)) 1102 (completions (funcall tablist-operations-function 1103 'complete 1104 (cdr (assq 'id alist)) 1105 (cdr (assq 'column alist)) 1106 string 1107 (- (point) beg)))) 1108 (unless completions 1109 (error "No completions available")) 1110 (completion-in-region beg end completions)))) 1111 1112 (defun tablist-column-editable (n) 1113 (and (tablist-operation-available-p 'edit-column) 1114 (not (tablist-column-property n :read-only)))) 1115 1116 (defun tablist-assert-column-editable (n) 1117 (unless (and (>= n 0) 1118 (< n (length tabulated-list-format))) 1119 (error "Invalid column number: %s" n)) 1120 (unless (tablist-operation-available-p 'edit-column) 1121 (error "Editing columns not enabled in this buffer")) 1122 (when (tablist-column-property n :read-only) 1123 (error "This column is read-only"))) 1124 1125 (defun tablist-edit-column-constrain-point () 1126 (unless tablist-edit-column-minor-mode 1127 (error "Not editing a column")) 1128 (unless (get-text-property (point) 'tablist-edit) 1129 (let ((bounds (tablist-edit-column-bounds))) 1130 (when bounds 1131 (if (> (point) (cdr bounds)) 1132 (goto-char (1- (cdr bounds))) 1133 (goto-char (car bounds))) 1134 (point))))) 1135 1136 (defun tablist-edit-column-bounds (&optional skip-final-space) 1137 (unless tablist-edit-column-minor-mode 1138 (error "Not editing a column")) 1139 (let ((pos (next-single-property-change 1140 (point) 'tablist-edit)) 1141 beg end) 1142 (cond 1143 ((null pos) 1144 (setq end (previous-single-property-change 1145 (point-max) 'tablist-edit) 1146 beg (previous-single-property-change 1147 end 'tablist-edit))) 1148 ((get-text-property pos 'tablist-edit) 1149 (setq beg pos 1150 end (next-single-property-change 1151 pos 'tablist-edit))) 1152 (pos 1153 (setq end pos 1154 beg (previous-single-property-change 1155 pos 'tablist-edit)))) 1156 1157 (unless (and beg end (get-text-property beg 'tablist-edit)) 1158 (error "Unable to locate edited text")) 1159 (cons beg (if skip-final-space (1- end) end)))) 1160 1161 (defun tablist-edit-column-mark-field () 1162 (interactive) 1163 (push-mark (field-beginning)) 1164 (push-mark (field-end) nil t) 1165 (goto-char (field-beginning))) 1166 1167 (defun tablist-find-entry (&optional id) 1168 (interactive) 1169 (unless (tablist-operation-available-p 'find-entry) 1170 (error "Finding entries not supported in this buffer")) 1171 (funcall tablist-operations-function 1172 'find-entry 1173 (or id (tabulated-list-get-id)))) 1174 1175 ;; 1176 ;; *Utility 1177 ;; 1178 1179 (defun tablist-column-property (n prop) 1180 (plist-get 1181 (nthcdr 3 (aref tabulated-list-format n)) 1182 prop)) 1183 1184 (defun tablist-current-column () 1185 "Return the column number at point. 1186 1187 Returns nil, if point is before the first column." 1188 (let ((column 1189 (1- (cl-position 1190 (current-column) 1191 (append (tablist-column-offsets) 1192 (list most-positive-fixnum)) 1193 :test (lambda (column offset) (> offset column)))))) 1194 (when (>= column 0) 1195 column))) 1196 1197 (defun tablist-column-offsets () 1198 "Return a list of column positions. 1199 1200 This is a list of offsets from the beginning of the line." 1201 (let ((cc tabulated-list-padding) 1202 columns) 1203 (dotimes (i (length tabulated-list-format)) 1204 (let* ((c (aref tabulated-list-format i)) 1205 (len (nth 1 c)) 1206 (pad (or (plist-get (nthcdr 3 c) :pad-right) 1207 1))) 1208 (push cc columns) 1209 (when (numberp len) 1210 (cl-incf cc len)) 1211 (when pad 1212 (cl-incf cc pad)))) 1213 (nreverse columns))) 1214 1215 (defun tablist-pretty-print-entry (item) 1216 (mapconcat (lambda (i) 1217 (tablist-nth-entry i item)) 1218 (tablist-major-columns) " ")) 1219 1220 (defun tablist--save-face-property (beg end) 1221 ;; We need to distinguish ,,not set'' from ''no face''. 1222 (unless (and (text-property-not-all beg end 'face nil) 1223 (< beg end)) 1224 (put-text-property beg (1+ beg) 'face 'default)) 1225 (unless (text-property-not-all beg end 'tablist-saved-face nil) 1226 (tablist-copy-text-property beg end 'face 'tablist-saved-face))) 1227 1228 (defun tablist--restore-face-property (beg end) 1229 (when (text-property-not-all beg end 'tablist-saved-face nil) 1230 (tablist-copy-text-property beg end 'tablist-saved-face 'face))) 1231 1232 (defun tablist-copy-text-property (beg end from to) 1233 "Copy text property FROM to TO in region BEG to END." 1234 (let ((inhibit-read-only t)) 1235 (save-excursion 1236 (while (< beg end) 1237 (goto-char beg) 1238 (put-text-property 1239 (point) 1240 (setq beg (next-single-property-change 1241 (point) from nil end)) 1242 to 1243 (get-text-property (point) from)))))) 1244 1245 ;; 1246 (defun tablist-read-column-name (arg &optional prompt default) 1247 "Read the name of a column using ARG. 1248 1249 If ARG is a number, return column ARG. 1250 If ARG is nil, return DEFAULT or the current column. 1251 Else ask the user, using PROMPT and DEFAULT." 1252 (cond 1253 ((numberp arg) 1254 (or (tablist-column-name 1255 (prefix-numeric-value arg)) 1256 (error "No such column: %d" (prefix-numeric-value arg)))) 1257 ((null arg) 1258 (or default 1259 (tablist-column-name 1260 (or (tablist-current-column) 1261 (car (tablist-major-columns)) 1262 0)))) 1263 (t 1264 (let* ((default (or default 1265 (tablist-column-name 1266 (car (tablist-major-columns))))) 1267 (result 1268 (completing-read 1269 (format "%s %s: " 1270 (or prompt "Use column") 1271 (if default 1272 (format "(default %s)" 1273 default) 1274 "")) 1275 (tablist-column-names) 1276 nil t nil 'tablist-column-name-history))) 1277 (if (> (length result) 0) 1278 result 1279 (if (not default) 1280 (error "No column selected") 1281 default)))))) 1282 1283 (defun tablist-column-name (n) 1284 "Return the name of column N." 1285 (when (and n 1286 (>= n 0) 1287 (< n (length tabulated-list-format))) 1288 (substring-no-properties 1289 (car (elt tabulated-list-format n)) 0))) 1290 1291 (defun tablist-column-names () 1292 "Return a list of all column-names." 1293 (mapcar 'tablist-column-name 1294 (number-sequence 0 (1- (length tabulated-list-format))))) 1295 1296 (defun tablist-nth-entry (n &optional entry) 1297 (unless entry (setq entry (tabulated-list-get-entry))) 1298 (when (and entry 1299 (>= n 0) 1300 (< n (length entry))) 1301 (let ((str (elt entry n))) 1302 (if (stringp str) 1303 str 1304 (car str))))) 1305 1306 (defun tablist-major-column-name () 1307 "Return a list of the major column names." 1308 (tablist-column-name (car (tablist-major-columns)))) 1309 1310 (defun tablist-export-csv (&optional separator always-quote-p 1311 invisible-p out-buffer display-p) 1312 "Export a tabulated list to a CSV format. 1313 1314 Use SEPARATOR (or ;) and quote if necessary (or always if 1315 ALWAYS-QUOTE-P is non-nil). Only consider non-filtered entries, 1316 unless invisible-p is non-nil. Create a buffer for the output or 1317 insert it after point in OUT-BUFFER. Finally if DISPLAY-P is 1318 non-nil, display this buffer. 1319 1320 Return the output buffer." 1321 1322 (interactive (list nil t nil nil t)) 1323 (unless (derived-mode-p 'tabulated-list-mode) 1324 (error "Not in Tabulated List Mode")) 1325 (unless (stringp separator) 1326 (setq separator (string (or separator ?\;)))) 1327 (let* ((outb (or out-buffer 1328 (get-buffer-create 1329 (format "%s.csv" (buffer-name))))) 1330 (escape-re (format "[%s\"\n]" separator)) 1331 (header (tablist-column-names))) 1332 (unless (buffer-live-p outb) 1333 (error "Expected a live buffer: %s" outb)) 1334 (cl-labels 1335 ((printit (entry) 1336 (insert 1337 (mapconcat 1338 (lambda (e) 1339 (unless (stringp e) 1340 (setq e (car e))) 1341 (if (or always-quote-p 1342 (string-match escape-re e)) 1343 (concat "\"" 1344 (replace-regexp-in-string "\"" "\"\"" e t t) 1345 "\"") 1346 e)) 1347 entry separator)) 1348 (insert ?\n))) 1349 (with-current-buffer outb 1350 (let ((inhibit-read-only t)) 1351 (erase-buffer) 1352 (printit header))) 1353 (save-excursion 1354 (goto-char (point-min)) 1355 (unless invisible-p 1356 (tablist-skip-invisible-entries)) 1357 (while (not (eobp)) 1358 (let* ((entry (tabulated-list-get-entry))) 1359 (with-current-buffer outb 1360 (let ((inhibit-read-only t)) 1361 (printit entry))) 1362 (if invisible-p 1363 (forward-line) 1364 (tablist-forward-entry))))) 1365 (if display-p 1366 (display-buffer outb)) 1367 outb))) 1368 1369 ;; 1370 1371 (defun tablist-enlarge-column (&optional column width) 1372 "Enlarge column COLUMN by WIDTH. 1373 1374 This function is lazy and therefore pretty slow." 1375 (interactive 1376 (list nil (* (prefix-numeric-value current-prefix-arg) 1377 3))) 1378 (unless column (setq column (tablist-current-column))) 1379 (unless column 1380 (error "No column given and no entry at point")) 1381 (unless width (setq width 1)) 1382 (when (or (not (numberp column)) 1383 (< column 0) 1384 (>= column (length tabulated-list-format))) 1385 (error "No such column: %d" column)) 1386 (when (= column (1- (length tabulated-list-format))) 1387 (error "Can't resize last column")) 1388 1389 (let* ((cur-width (cadr (elt tabulated-list-format column)))) 1390 (setcar (cdr (elt tabulated-list-format column)) 1391 (max 3 (+ cur-width width))) 1392 (tablist-with-remembering-entry 1393 (tablist-save-marks 1394 (tabulated-list-init-header) 1395 (tabulated-list-print))))) 1396 1397 (defun tablist-shrink-column (&optional column width) 1398 (interactive 1399 (list nil (* (prefix-numeric-value current-prefix-arg) 1400 3))) 1401 (tablist-enlarge-column column (- (or width 1)))) 1402 1403 ;; *Sorting 1404 ;; 1405 1406 (defun tablist-sort (&optional column) 1407 "Sort the tabulated-list by COLUMN. 1408 1409 COLUMN may be either a name or an index. The default compare 1410 function is given by the `tabulated-list-format', which see. 1411 1412 This function saves the current sort column and the inverse 1413 sort-direction in the variable `tabulated-list-sort-key', which 1414 also determines the default COLUMN and direction. 1415 1416 The main difference to `tabulated-list-sort' is, that this 1417 function sorts the buffer in-place and it ignores a nil sort 1418 entry in `tabulated-list-format' and sorts on the column 1419 anyway (why not ?)." 1420 1421 (interactive 1422 (list 1423 (if (null current-prefix-arg) 1424 (tablist-column-name 1425 (or (tablist-current-column) 1426 (car (tablist-major-columns)) 1427 0)) 1428 (tablist-read-column-name 1429 '(4) "Sort by column" 1430 (tablist-column-name (car (tablist-major-columns))))))) 1431 1432 (unless column 1433 (setq column (or (car tabulated-list-sort-key) 1434 (tablist-column-name (car (tablist-major-columns))) 1435 (tablist-column-name 0)))) 1436 (when (numberp column) 1437 (let ((column-name (tablist-column-name column))) 1438 (unless column-name 1439 (error "No such column: %d" column)) 1440 (setq column column-name))) 1441 1442 (setq tabulated-list-sort-key 1443 (cons column 1444 (if (equal column (car tabulated-list-sort-key)) 1445 (cdr tabulated-list-sort-key)))) 1446 1447 (let* ((entries (if (functionp tabulated-list-entries) 1448 (funcall tabulated-list-entries) 1449 tabulated-list-entries)) 1450 (reverse (cdr tabulated-list-sort-key)) 1451 (n (tabulated-list--column-number ;;errors if column is n/a 1452 (car tabulated-list-sort-key))) 1453 (compare-fn (nth 2 (aref tabulated-list-format n)))) 1454 1455 (when (or (null compare-fn) 1456 (eq compare-fn t)) 1457 (setq compare-fn 1458 (lambda (a b) 1459 (setq a (aref (cadr a) n)) 1460 (setq b (aref (cadr b) n)) 1461 (string< (if (stringp a) a (car a)) 1462 (if (stringp b) b (car b)))))) 1463 1464 (unless compare-fn 1465 (error "This column cannot be sorted: %s" column)) 1466 1467 (setcdr tabulated-list-sort-key (not reverse)) 1468 ;; Presort the entries and hash the result and sort the buffer. 1469 (setq entries (sort (copy-sequence entries) compare-fn)) 1470 (let ((hash (make-hash-table :test 'equal))) 1471 (dotimes (i (length entries)) 1472 (puthash (caar entries) i hash) 1473 (setq entries (cdr entries))) 1474 (tablist-with-remembering-entry 1475 (goto-char (point-min)) 1476 (tablist-skip-invisible-entries) 1477 (let ((inhibit-read-only t)) 1478 (sort-subr 1479 nil 'tablist-forward-entry 'end-of-line 1480 (lambda () 1481 (gethash (tabulated-list-get-id) hash 0)) 1482 nil (if reverse '< '>)))) 1483 (tablist-move-to-column n) 1484 ;; Make the sort arrows display. 1485 (tabulated-list-init-header)))) 1486 1487 ;; 1488 ;; *Filter 1489 ;; 1490 1491 (defun tablist-read-filter-name (prompt) 1492 (let ((filter (cdr (assq major-mode tablist-named-filter)))) 1493 (unless filter 1494 (error "No filter defined for %s mode" mode-name)) 1495 (let ((name (completing-read 1496 (format "%s: " prompt) 1497 filter 1498 nil t))) 1499 (unless (> (length name) 0) 1500 (error "No filter selected")) 1501 name))) 1502 1503 (defun tablist-apply-filter (&optional filter) 1504 "Apply FILTER to the current tabulated list. 1505 1506 FILTER defaults to `tablist-current-filter'." 1507 (unless filter (setq filter tablist-current-filter)) 1508 (tablist-filter-unhide-buffer) 1509 (when (and filter 1510 (null tablist-filter-suspended)) 1511 (tablist-with-remembering-entry 1512 (tablist-map-with-filter 1513 (lambda nil 1514 (if tablist-umark-filtered-entries 1515 (save-excursion (tablist-unmark-forward))) 1516 (tablist-filter-hide-entry)) 1517 (tablist-filter-negate filter)))) 1518 (force-mode-line-update)) 1519 1520 (defadvice tabulated-list-print (after tabulated-list activate) 1521 "Reapply the filter." 1522 (when (or tablist-minor-mode 1523 (derived-mode-p 'tablist-mode)) 1524 (tablist-apply-filter))) 1525 1526 (defun tablist-eval-filter (filter) 1527 (tablist-filter-eval 1528 filter 1529 (tabulated-list-get-id) 1530 (tabulated-list-get-entry) 1531 (cdr (assq major-mode tablist-named-filter)))) 1532 1533 (defun tablist-map-with-filter (fn filter &optional show-progress 1534 distinguish-one-marked) 1535 "Call FN for every unfiltered entry matching FILTER." 1536 (prog1 1537 (cl-labels ((search () 1538 (tablist-skip-invisible-entries) 1539 (while (and (not (eobp)) 1540 (not (tablist-eval-filter filter))) 1541 (tablist-forward-entry)) 1542 (unless (eobp) 1543 (point-marker)))) 1544 (let (next-position results) 1545 (save-excursion 1546 (goto-char (point-min)) 1547 (setq next-position (search)) 1548 (while next-position 1549 (goto-char next-position) 1550 (if show-progress (sit-for 0)) 1551 (push (funcall fn) results) 1552 ;; move after last match 1553 (goto-char next-position) 1554 (forward-line 1) 1555 (set-marker next-position nil) 1556 (setq next-position (search))) 1557 (if (and distinguish-one-marked (= (length results) 1)) 1558 (setq results (cons t results)))))))) 1559 1560 ;; 1561 ;; **Filter Commands 1562 ;; 1563 (defun tablist-push-filter (filter &optional interactive or-p) 1564 (setq tablist-current-filter 1565 (tablist-filter-push 1566 tablist-current-filter 1567 filter or-p)) 1568 (tablist-apply-filter) 1569 (when interactive 1570 (tablist-display-filter-temporarily))) 1571 1572 (defun tablist-pop-filter (&optional n interactive) 1573 "Remove the first N filter components." 1574 (interactive (list (prefix-numeric-value current-prefix-arg) t)) 1575 (while (and tablist-current-filter 1576 (> n 0)) 1577 (setq tablist-current-filter 1578 (tablist-filter-pop 1579 tablist-current-filter)) 1580 (cl-decf n)) 1581 (tablist-apply-filter) 1582 (when interactive 1583 (when (> n 0) 1584 (message "The filter is empty.")) 1585 (tablist-display-filter-temporarily)) 1586 n) 1587 1588 (defun tablist-negate-filter (&optional interactive) 1589 "Negate the current filter." 1590 (interactive (list t)) 1591 (setq tablist-current-filter 1592 (tablist-filter-negate 1593 tablist-current-filter)) 1594 (tablist-apply-filter) 1595 (when interactive 1596 (tablist-display-filter-temporarily))) 1597 1598 (defun tablist-toggle-first-filter-logic () 1599 "Toggle between and/or for the first filter operand." 1600 (interactive) 1601 (setq tablist-current-filter 1602 (pcase tablist-current-filter 1603 (`(or ,x1 ,x2) 1604 `(and ,x1 ,x2)) 1605 (`(and ,x1 ,x2) 1606 `(or ,x1 ,x2)) 1607 (`(not ,x) x) 1608 (x `(not ,x)))) 1609 (tablist-apply-filter) 1610 (tablist-display-filter-temporarily)) 1611 1612 (defun tablist-suspend-filter (&optional flag) 1613 "Temporarily disable filtering according to FLAG. 1614 1615 Interactively, this command toggles filtering." 1616 (interactive 1617 (list (not tablist-filter-suspended))) 1618 (let ((state tablist-filter-suspended)) 1619 (unless (eq (not (not state)) 1620 (not (not flag))) 1621 (set (make-local-variable 'tablist-filter-suspended) flag) 1622 (tablist-apply-filter)))) 1623 1624 (defun tablist-read-regexp-filter (operation arg) 1625 (let ((column-name (tablist-read-column-name arg))) 1626 (list 1627 column-name 1628 (let ((re 1629 (read-regexp (format "%s where %s matches: " operation column-name)))) 1630 (unless (> (length re) 0) 1631 (error "No regexp given")) 1632 re)))) 1633 1634 (defun tablist-read-equal-filter (operation arg) 1635 (let ((column-name (tablist-read-column-name arg))) 1636 (list 1637 column-name 1638 (read-string (format "%s where %s equals: " operation column-name))))) 1639 1640 (defun tablist-read-numeric-filter (operation arg) 1641 (let* ((entry (tabulated-list-get-entry 1)) 1642 (default (cl-some 1643 (lambda (idx) 1644 (let ((value (tablist-nth-entry idx entry))) 1645 (when (or (not (eq 0 (string-to-number value))) 1646 (equal "0" value)) 1647 (tablist-column-name idx)))) 1648 (number-sequence 0 (length entry)))) 1649 (column-name (tablist-read-column-name arg nil default)) 1650 (op (completing-read 1651 (format "%s %s matching binary op: " operation column-name) 1652 '("=" "<" ">" "<=" ">=") nil t)) 1653 oper) 1654 1655 (when (equal "" op) 1656 (error "No operation selected")) 1657 (setq op (intern op)) 1658 (setq oper (number-to-string 1659 (read-number 1660 (format "%s where %s %s " operation column-name op)))) 1661 1662 (list op column-name oper))) 1663 1664 (defun tablist-push-regexp-filter (column-name regexp) 1665 "Add a new filter matching REGEXP in COLUMN-NAME. 1666 1667 The filter is and'ed with the current filter. Use 1668 `tablist-toggle-first-filter-logic' to change this." 1669 (interactive 1670 (tablist-with-filter-displayed 1671 (tablist-read-regexp-filter "Filter" current-prefix-arg))) 1672 (tablist-push-filter 1673 `(=~ ,column-name ,regexp) 1674 (called-interactively-p 'any))) 1675 1676 (defun tablist-push-equal-filter (column-name string) 1677 "Add a new filter whre string equals COLUMN-NAME's value. 1678 1679 The filter is and'ed with the current filter. Use 1680 `tablist-toggle-first-filter-logic' to change this." 1681 (interactive 1682 (tablist-with-filter-displayed 1683 (tablist-read-equal-filter "Filter" current-prefix-arg))) 1684 (tablist-push-filter 1685 `(== ,column-name ,string) 1686 (called-interactively-p 'any))) 1687 1688 (defun tablist-push-numeric-filter (op column-name 2nd-arg) 1689 "Add a new filter matching a numeric predicate. 1690 1691 The filter is and'ed with the current filter. Use 1692 `tablist-toggle-first-filter-logic' to change this." 1693 (interactive 1694 (tablist-with-filter-displayed 1695 (tablist-read-numeric-filter "Filter" current-prefix-arg))) 1696 (tablist-push-filter 1697 `(,op ,column-name ,2nd-arg) 1698 (called-interactively-p 'any))) 1699 1700 (defun tablist-push-named-filter (name) 1701 "Add a named filter called NAME. 1702 1703 Named filter are saved in the variable `tablist-named-filter'." 1704 (interactive 1705 (tablist-with-filter-displayed 1706 (list 1707 (tablist-read-filter-name "Add filter")))) 1708 (when (and name (symbolp name)) 1709 (setq name (symbol-name name))) 1710 (tablist-push-filter name (called-interactively-p 'any))) 1711 1712 (defun tablist-delete-named-filter (name &optional mode) 1713 (interactive 1714 (tablist-with-filter-displayed 1715 (list 1716 (tablist-read-filter-name "Delete filter")))) 1717 (setq tablist-current-filter 1718 (tablist-filter-map 1719 (lambda (f) 1720 (when (equal f name) 1721 (setq f (tablist-get-named-filter f))) 1722 f) 1723 tablist-current-filter)) 1724 (unless mode (setq mode major-mode)) 1725 (let ((mode-filter 1726 (assq mode tablist-named-filter))) 1727 (when mode-filter 1728 (setcdr mode-filter 1729 (cl-remove name (cdr mode-filter) 1730 :test 'equal :key 'car))))) 1731 1732 (defun tablist-name-current-filter (name) 1733 (interactive 1734 (list (tablist-with-filter-displayed 1735 (read-string 1736 "Add name for current filter: ")))) 1737 (unless tablist-current-filter 1738 (error "Filter is empty")) 1739 (unless (> (length name) 0) 1740 (error "No name given")) 1741 (tablist-put-named-filter 1742 name (if (stringp tablist-current-filter) 1743 (tablist-get-named-filter 1744 tablist-current-filter) 1745 tablist-current-filter)) 1746 (setq tablist-current-filter name) 1747 (force-mode-line-update)) 1748 1749 (defun tablist-deconstruct-named-filter () 1750 (interactive) 1751 (let (found) 1752 (setq tablist-current-filter 1753 (tablist-filter-map 1754 (lambda (f) 1755 (when (and (not found) 1756 (stringp f)) 1757 (setq found t) 1758 (let ((df (tablist-get-named-filter f))) 1759 (unless df 1760 (error "Filter is not defined: %s" f)) 1761 (setq f df))) 1762 f) 1763 tablist-current-filter)) 1764 (unless found 1765 (error "No named filter found")) 1766 (force-mode-line-update))) 1767 1768 (defun tablist-filter-names (&optional mode) 1769 (mapcar 'car (cdr (assq (or mode major-mode) 1770 tablist-named-filter)))) 1771 1772 (defun tablist-get-named-filter (name &optional mode) 1773 (cdr (assoc name 1774 (cdr (assq (or mode major-mode) 1775 tablist-named-filter))))) 1776 1777 (defun tablist-put-named-filter (name filter &optional mode) 1778 (unless mode (setq mode major-mode)) 1779 (let ((mode-filter 1780 (assq mode tablist-named-filter))) 1781 (unless mode-filter 1782 (setq mode-filter (cons mode nil)) 1783 (push mode-filter tablist-named-filter)) 1784 (let ((entry (assoc name mode-filter))) 1785 (if entry 1786 (setcdr entry filter) 1787 (setcdr mode-filter 1788 (list (cons name filter))))))) 1789 1790 (defun tablist-validate-named-filter (filter) 1791 (tablist-filter-map 1792 (lambda (f) 1793 (when (and (stringp f) 1794 (null (tablist-get-named-filter f))) 1795 (error "Undefined named filter: %s (defined: %s)" f 1796 (mapconcat 'identity (tablist-filter-names) ",")))) 1797 filter)) 1798 1799 (defun tablist-edit-filter () 1800 (interactive) 1801 (setq tablist-current-filter 1802 (tablist-with-filter-displayed 1803 (tablist-filter-edit-filter 1804 "Edit filter: " 1805 tablist-current-filter 1806 nil 1807 'tablist-validate-named-filter))) 1808 (tablist-apply-filter)) 1809 1810 (defun tablist-clear-filter () 1811 (interactive) 1812 (setq tablist-current-filter nil) 1813 (tablist-apply-filter)) 1814 1815 ;; **Displaying filter 1816 ;; 1817 1818 (defconst tablist-display-filter-mode-line-tag nil) 1819 1820 (defun tablist-display-filter (&optional flag) 1821 "Display the current filter according to FLAG. 1822 1823 If FLAG has the value 'toggle, toggle it's visibility. 1824 If FLAG has the 'state, then do nothing but return the current 1825 visibility." 1826 (interactive (list 'toggle)) 1827 (let* ((tag 'tablist-display-filter-mode-line-tag) 1828 (displayed-p (not (not (assq tag mode-line-format))))) 1829 (if (eq flag 'state) 1830 displayed-p 1831 (let ((display-p (not (not (if (eq flag 'toggle) 1832 (not displayed-p) 1833 flag))))) 1834 (unless (eq displayed-p display-p) 1835 (setq mode-line-format 1836 (if display-p 1837 (list (cons tag mode-line-format) 1838 '(:eval 1839 (replace-regexp-in-string 1840 "%" "%%" 1841 (concat 1842 (propertize "Filter: " 'face 'minibuffer-prompt) 1843 (and tablist-filter-suspended 1844 "[suspended] ") 1845 (if tablist-current-filter 1846 (tablist-filter-unparse 1847 tablist-current-filter t) 1848 "[none]"))))) 1849 (cdr (assq tag mode-line-format))))) 1850 (force-mode-line-update) 1851 display-p)))) 1852 1853 (defun tablist-display-filter-temporarily () 1854 (tablist-with-filter-displayed 1855 (sit-for 9999))) 1856 1857 ;; 1858 ;; **Hiding/Unhiding Entries 1859 ;; 1860 (defun tablist-filter-set-entry-hidden (flag &optional pos) 1861 (save-excursion 1862 (when pos (goto-char pos)) 1863 (beginning-of-line) 1864 (let ((inhibit-read-only t)) 1865 (add-text-properties 1866 (point-at-bol) 1867 (1+ (point-at-eol)) 1868 `(invisible ,flag))))) 1869 1870 (defun tablist-filter-hide-entry (&optional pos) 1871 (interactive) 1872 (tablist-filter-set-entry-hidden t pos)) 1873 1874 (defun tablist-filter-unhide-entry (&optional pos) 1875 (tablist-filter-set-entry-hidden nil pos)) 1876 1877 (defun tablist-filter-unhide-buffer () 1878 (let ((inhibit-read-only t)) 1879 (remove-text-properties 1880 (point-min) (point-max) 1881 '(invisible)))) 1882 1883 (defun tablist-window-attach (awindow &optional window) 1884 "Attach AWINDOW to WINDOW. 1885 1886 This has the following effect. Whenever WINDOW, defaulting to 1887 the selected window, stops displaying the buffer it currently 1888 displays (e.g., by switching buffers or because it was deleted) 1889 AWINDOW is deleted." 1890 (unless window (setq window (selected-window))) 1891 (let ((buffer (window-buffer window)) 1892 (hook (make-symbol "window-attach-hook"))) 1893 (fset hook 1894 (lambda () 1895 (when (or (not (window-live-p window)) 1896 (not (eq buffer (window-buffer window)))) 1897 (remove-hook 'window-configuration-change-hook 1898 hook) 1899 ;; Deleting windows inside wcch may cause errors in 1900 ;; windows.el . 1901 (run-with-timer 1902 0 nil (lambda (win) 1903 (when (and (window-live-p win) 1904 (not (eq win (selected-window)))) 1905 (delete-window win))) 1906 awindow)))) 1907 (add-hook 'window-configuration-change-hook hook))) 1908 1909 (defun tablist-display-buffer-split-below-and-attach (buf alist) 1910 "Display buffer action using `tablist-window-attach'." 1911 (let ((window (selected-window)) 1912 (height (cdr (assq 'window-height alist))) 1913 newwin) 1914 (when height 1915 (when (floatp height) 1916 (setq height (round (* height (frame-height))))) 1917 (setq height (- (max height window-min-height)))) 1918 (setq newwin (window--display-buffer 1919 buf 1920 (split-window-below height) 1921 'window alist)) 1922 (tablist-window-attach newwin window) 1923 newwin)) 1924 1925 (defun tablist-generate-sorter (column compare-fn &optional read-fn) 1926 "Generate a sort function for `tabulated-list' entries. 1927 1928 Example: 1929 1930 \(tablist-generate-sorter 0 '< 'string-to-number\) 1931 1932 would create a sort function sorting `tabulated-list-entries' on 1933 the 0-th column as numbers by the less-than relation." 1934 1935 (lambda (e1 e2) 1936 (funcall compare-fn 1937 (funcall (or read-fn 'identity) 1938 (aref (cadr e1) column)) 1939 (funcall (or read-fn 'identity) 1940 (aref (cadr e2) column))))) 1941 1942 (provide 'tablist) 1943 ;; Local Variables: 1944 ;; outline-regexp: ";;\\(\\(?:[;*]+ \\| \\*+\\)[^\s\t\n]\\|###autoload\\)\\|(" 1945 ;; indent-tabs-mode: nil 1946 ;; End: 1947 ;;; tablist.el ends here