config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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