Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1941 lines
65 KiB

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