Commit | Line | Data |
---|---|---|
e8af40ee | 1 | ;;; ewoc.el --- utility to maintain a view of a list of objects in a buffer |
5b467bf4 | 2 | |
3731a850 | 3 | ;; Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, |
8b72699e | 4 | ;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. |
5b467bf4 SM |
5 | |
6 | ;; Author: Per Cederqvist <ceder@lysator.liu.se> | |
7 | ;; Inge Wallin <inge@lysator.liu.se> | |
8 | ;; Maintainer: monnier@gnu.org | |
9 | ;; Created: 3 Aug 1992 | |
10 | ;; Keywords: extensions, lisp | |
11 | ||
12 | ;; This file is part of GNU Emacs. | |
13 | ||
d6cba7ae | 14 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
5b467bf4 | 15 | ;; it under the terms of the GNU General Public License as published by |
d6cba7ae GM |
16 | ;; the Free Software Foundation, either version 3 of the License, or |
17 | ;; (at your option) any later version. | |
5b467bf4 SM |
18 | |
19 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
22 | ;; GNU General Public License for more details. | |
23 | ||
24 | ;; You should have received a copy of the GNU General Public License | |
d6cba7ae | 25 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
5b467bf4 SM |
26 | |
27 | ;;; Commentary: | |
28 | ||
29 | ;; Ewoc Was Once Cookie | |
30 | ;; But now it's Emacs' Widget for Object Collections | |
31 | ||
32 | ;; As the name implies this derives from the `cookie' package (part | |
10c471e6 | 33 | ;; of Elib). The changes are pervasive though mostly superficial: |
5b467bf4 | 34 | |
10c471e6 | 35 | ;; - uses CL (and its `defstruct') |
5b467bf4 SM |
36 | ;; - separate from Elib. |
37 | ;; - uses its own version of a doubly-linked list which allows us | |
38 | ;; to merge the elib-wrapper and the elib-node structures into ewoc-node | |
39 | ;; - dropping functions not used by PCL-CVS (the only client of ewoc at the | |
40 | ;; time of writing) | |
41 | ;; - removing unused arguments | |
42 | ;; - renaming: | |
43 | ;; elib-node ==> ewoc--node | |
44 | ;; collection ==> ewoc | |
45 | ;; tin ==> ewoc--node | |
46 | ;; cookie ==> data or element or elem | |
47 | ||
48 | ;; Introduction | |
49 | ;; ============ | |
50 | ;; | |
51 | ;; Ewoc is a package that implements a connection between an | |
52 | ;; dll (a doubly linked list) and the contents of a buffer. | |
53 | ;; Possible uses are dired (have all files in a list, and show them), | |
54 | ;; buffer-list, kom-prioritize (in the LysKOM elisp client) and | |
55 | ;; others. pcl-cvs.el uses ewoc.el. | |
56 | ;; | |
57 | ;; Ewoc can be considered as the `view' part of a model-view-controller. | |
58 | ;; | |
59 | ;; A `element' can be any lisp object. When you use the ewoc | |
60 | ;; package you specify a pretty-printer, a function that inserts | |
61 | ;; a printable representation of the element in the buffer. (The | |
62 | ;; pretty-printer should use "insert" and not | |
63 | ;; "insert-before-markers"). | |
64 | ;; | |
65 | ;; A `ewoc' consists of a doubly linked list of elements, a | |
66 | ;; header, a footer and a pretty-printer. It is displayed at a | |
67 | ;; certain point in a certain buffer. (The buffer and point are | |
68 | ;; fixed when the ewoc is created). The header and the footer | |
69 | ;; are constant strings. They appear before and after the elements. | |
5b467bf4 SM |
70 | ;; |
71 | ;; Ewoc does not affect the mode of the buffer in any way. It | |
72 | ;; merely makes it easy to connect an underlying data representation | |
73 | ;; to the buffer contents. | |
74 | ;; | |
75 | ;; A `ewoc--node' is an object that contains one element. There are | |
10c471e6 SM |
76 | ;; functions in this package that given an ewoc--node extract the data, or |
77 | ;; give the next or previous ewoc--node. (All ewoc--nodes are linked together | |
78 | ;; in a doubly linked list. The `previous' ewoc--node is the one that appears | |
5b467bf4 SM |
79 | ;; before the other in the buffer.) You should not do anything with |
80 | ;; an ewoc--node except pass it to the functions in this package. | |
81 | ;; | |
82 | ;; An ewoc is a very dynamic thing. You can easily add or delete elements. | |
83 | ;; You can apply a function to all elements in an ewoc, etc, etc. | |
84 | ;; | |
85 | ;; Remember that an element can be anything. Your imagination is the | |
86 | ;; limit! It is even possible to have another ewoc as an | |
87 | ;; element. In that way some kind of tree hierarchy can be created. | |
88 | ;; | |
0741626e | 89 | ;; The Emacs Lisp Reference Manual documents ewoc.el's "public interface". |
5b467bf4 SM |
90 | |
91 | ;; Coding conventions | |
92 | ;; ================== | |
93 | ;; | |
94 | ;; All functions of course start with `ewoc'. Functions and macros | |
95 | ;; starting with the prefix `ewoc--' are meant for internal use, | |
96 | ;; while those starting with `ewoc-' are exported for public use. | |
5b467bf4 SM |
97 | |
98 | ;;; Code: | |
99 | ||
7ece7aba | 100 | (eval-when-compile (require 'cl)) |
5b467bf4 | 101 | |
bb7a346f SM |
102 | ;; The doubly linked list is implemented as a circular list with a dummy |
103 | ;; node first and last. The dummy node is used as "the dll". | |
5b467bf4 | 104 | (defstruct (ewoc--node |
63910b23 | 105 | (:type vector) ;ewoc--node-nth needs this |
7f934f3d | 106 | (:constructor nil) |
5b467bf4 SM |
107 | (:constructor ewoc--node-create (start-marker data))) |
108 | left right data start-marker) | |
109 | ||
bb7a346f | 110 | (defun ewoc--node-next (dll node) |
5b467bf4 | 111 | "Return the node after NODE, or nil if NODE is the last node." |
7dd2e64c | 112 | (let ((R (ewoc--node-right node))) |
bb7a346f | 113 | (unless (eq dll R) R))) |
5b467bf4 | 114 | |
bb7a346f | 115 | (defun ewoc--node-prev (dll node) |
5b467bf4 | 116 | "Return the node before NODE, or nil if NODE is the first node." |
7dd2e64c | 117 | (let ((L (ewoc--node-left node))) |
bb7a346f | 118 | (unless (eq dll L) L))) |
7dd2e64c | 119 | |
bb7a346f SM |
120 | (defun ewoc--node-nth (dll n) |
121 | "Return the Nth node from the doubly linked list `dll'. | |
7dd2e64c TTN |
122 | N counts from zero. If N is negative, return the -(N+1)th last element. |
123 | If N is out of range, return nil. | |
bb7a346f SM |
124 | Thus, (ewoc--node-nth dll 0) returns the first node, |
125 | and (ewoc--node-nth dll -1) returns the last node." | |
63910b23 | 126 | ;; Presuming a node is ":type vector", starting with `left' and `right': |
5b467bf4 SM |
127 | ;; Branch 0 ("follow left pointer") is used when n is negative. |
128 | ;; Branch 1 ("follow right pointer") is used otherwise. | |
129 | (let* ((branch (if (< n 0) 0 1)) | |
bb7a346f | 130 | (node (aref dll branch))) |
5b467bf4 | 131 | (if (< n 0) (setq n (- -1 n))) |
bb7a346f | 132 | (while (and (not (eq dll node)) (> n 0)) |
63910b23 | 133 | (setq node (aref node branch)) |
5b467bf4 | 134 | (setq n (1- n))) |
bb7a346f | 135 | (unless (eq dll node) node))) |
5b467bf4 | 136 | |
10c471e6 SM |
137 | (defun ewoc-location (node) |
138 | "Return the start location of NODE." | |
139 | (ewoc--node-start-marker node)) | |
140 | ||
5b467bf4 SM |
141 | \f |
142 | ;;; The ewoc data type | |
143 | ||
144 | (defstruct (ewoc | |
145 | (:constructor nil) | |
f860b721 | 146 | (:constructor ewoc--create (buffer pretty-printer dll)) |
5b467bf4 | 147 | (:conc-name ewoc--)) |
2bbacc0e | 148 | buffer pretty-printer header footer dll last-node hf-pp) |
5b467bf4 SM |
149 | |
150 | (defmacro ewoc--set-buffer-bind-dll-let* (ewoc varlist &rest forms) | |
151 | "Execute FORMS with ewoc--buffer selected as current buffer, | |
bb7a346f SM |
152 | `dll' bound to the dll, and VARLIST bound as in a let*. |
153 | `dll' will be bound when VARLIST is initialized, but | |
7dd2e64c | 154 | the current buffer will *not* have been changed. |
5b467bf4 | 155 | Return value of last form in FORMS." |
b1c36c0f TTN |
156 | (let ((hnd (make-symbol "ewoc"))) |
157 | `(let* ((,hnd ,ewoc) | |
bb7a346f | 158 | (dll (ewoc--dll ,hnd)) |
8a946354 | 159 | ,@varlist) |
b1c36c0f TTN |
160 | (with-current-buffer (ewoc--buffer ,hnd) |
161 | ,@forms)))) | |
5b467bf4 SM |
162 | |
163 | (defmacro ewoc--set-buffer-bind-dll (ewoc &rest forms) | |
164 | `(ewoc--set-buffer-bind-dll-let* ,ewoc nil ,@forms)) | |
165 | ||
166 | (defsubst ewoc--filter-hf-nodes (ewoc node) | |
167 | "Evaluate NODE once and return it. | |
168 | BUT if it is the header or the footer in EWOC return nil instead." | |
169 | (unless (or (eq node (ewoc--header ewoc)) | |
170 | (eq node (ewoc--footer ewoc))) | |
171 | node)) | |
172 | ||
bb7a346f | 173 | (defun ewoc--adjust (beg end node dll) |
60eae434 TTN |
174 | ;; "Manually reseat" markers for NODE and its successors (including footer |
175 | ;; and dll), in the case where they originally shared start position with | |
176 | ;; BEG, to END. BEG and END are buffer positions describing NODE's left | |
177 | ;; neighbor. This operation is functionally equivalent to temporarily | |
178 | ;; setting these nodes' markers' insertion type to t around the pretty-print | |
7dd2e64c | 179 | ;; call that precedes the call to `ewoc--adjust', and then changing them back |
60eae434 TTN |
180 | ;; to nil. |
181 | (when (< beg end) | |
182 | (let (m) | |
183 | (while (and (= beg (setq m (ewoc--node-start-marker node))) | |
bb7a346f SM |
184 | ;; The "dummy" node `dll' actually holds the marker that |
185 | ;; points to the end of the footer, so we check `dll' | |
186 | ;; *after* reseating the marker. | |
60eae434 TTN |
187 | (progn |
188 | (set-marker m end) | |
bb7a346f | 189 | (not (eq dll node)))) |
60eae434 TTN |
190 | (setq node (ewoc--node-right node)))))) |
191 | ||
f718c2fc | 192 | (defun ewoc--insert-new-node (node data pretty-printer dll) |
7f0ea399 | 193 | "Insert before NODE a new node for DATA, displayed by PRETTY-PRINTER. |
f718c2fc | 194 | Fourth arg DLL -- from `(ewoc--dll EWOC)' -- is for internal purposes. |
7f0ea399 TTN |
195 | Call PRETTY-PRINTER with point at NODE's start, thus pushing back |
196 | NODE and leaving the new node's start there. Return the new node." | |
5b467bf4 | 197 | (save-excursion |
7ece7aba SM |
198 | (let ((elemnode (ewoc--node-create |
199 | (copy-marker (ewoc--node-start-marker node)) data))) | |
200 | (setf (ewoc--node-left elemnode) (ewoc--node-left node) | |
7f0ea399 TTN |
201 | (ewoc--node-right elemnode) node |
202 | (ewoc--node-right (ewoc--node-left node)) elemnode | |
203 | (ewoc--node-left node) elemnode) | |
bb7a346f | 204 | (ewoc--refresh-node pretty-printer elemnode dll) |
7f0ea399 | 205 | elemnode))) |
5b467bf4 | 206 | |
bb7a346f | 207 | (defun ewoc--refresh-node (pp node dll) |
cb3430a1 | 208 | "Redisplay the element represented by NODE using the pretty-printer PP." |
60eae434 TTN |
209 | (let ((inhibit-read-only t) |
210 | (m (ewoc--node-start-marker node)) | |
211 | (R (ewoc--node-right node))) | |
bfbdb5ca | 212 | ;; First, remove the string from the buffer: |
60eae434 | 213 | (delete-region m (ewoc--node-start-marker R)) |
bfbdb5ca | 214 | ;; Calculate and insert the string. |
60eae434 TTN |
215 | (goto-char m) |
216 | (funcall pp (ewoc--node-data node)) | |
bb7a346f | 217 | (ewoc--adjust m (point) R dll))) |
2bbacc0e TTN |
218 | |
219 | (defun ewoc--wrap (func) | |
220 | (lexical-let ((ewoc--user-pp func)) | |
221 | (lambda (data) | |
222 | (funcall ewoc--user-pp data) | |
223 | (insert "\n")))) | |
224 | ||
5b467bf4 SM |
225 | \f |
226 | ;;; =========================================================================== | |
227 | ;;; Public members of the Ewoc package | |
228 | ||
3fe35897 | 229 | ;;;###autoload |
2bbacc0e | 230 | (defun ewoc-create (pretty-printer &optional header footer nosep) |
5b467bf4 SM |
231 | "Create an empty ewoc. |
232 | ||
cb3430a1 | 233 | The ewoc will be inserted in the current buffer at the current position. |
5b467bf4 SM |
234 | |
235 | PRETTY-PRINTER should be a function that takes one argument, an | |
236 | element, and inserts a string representing it in the buffer (at | |
0111ab41 | 237 | point). The string PRETTY-PRINTER inserts may be empty or span |
60eae434 | 238 | several lines. The PRETTY-PRINTER should use `insert', and not |
0111ab41 JB |
239 | `insert-before-markers'. |
240 | ||
60eae434 TTN |
241 | Optional second and third arguments HEADER and FOOTER are strings, |
242 | possibly empty, that will always be present at the top and bottom, | |
2bbacc0e TTN |
243 | respectively, of the ewoc. |
244 | ||
245 | Normally, a newline is automatically inserted after the header, | |
246 | the footer and every node's printed representation. Optional | |
247 | fourth arg NOSEP non-nil inhibits this." | |
2c246c9f TTN |
248 | (let* ((dummy-node (ewoc--node-create 'DL-LIST 'DL-LIST)) |
249 | (dll (progn (setf (ewoc--node-right dummy-node) dummy-node) | |
250 | (setf (ewoc--node-left dummy-node) dummy-node) | |
251 | dummy-node)) | |
2bbacc0e | 252 | (wrap (if nosep 'identity 'ewoc--wrap)) |
f860b721 | 253 | (new-ewoc (ewoc--create (current-buffer) |
2bbacc0e | 254 | (funcall wrap pretty-printer) |
f860b721 | 255 | dll)) |
2bbacc0e | 256 | (hf-pp (funcall wrap 'insert)) |
7f0ea399 TTN |
257 | (pos (point)) |
258 | head foot) | |
5b467bf4 SM |
259 | (ewoc--set-buffer-bind-dll new-ewoc |
260 | ;; Set default values | |
261 | (unless header (setq header "")) | |
262 | (unless footer (setq footer "")) | |
7f0ea399 | 263 | (setf (ewoc--node-start-marker dll) (copy-marker pos) |
f718c2fc TTN |
264 | foot (ewoc--insert-new-node dll footer hf-pp dll) |
265 | head (ewoc--insert-new-node foot header hf-pp dll) | |
2bbacc0e | 266 | (ewoc--hf-pp new-ewoc) hf-pp |
7f0ea399 TTN |
267 | (ewoc--footer new-ewoc) foot |
268 | (ewoc--header new-ewoc) head)) | |
5b467bf4 SM |
269 | ;; Return the ewoc |
270 | new-ewoc)) | |
271 | ||
8d1bec8d TTN |
272 | (defalias 'ewoc-data 'ewoc--node-data |
273 | "Extract the data encapsulated by NODE and return it. | |
274 | ||
275 | \(fn NODE)") | |
5b467bf4 | 276 | |
bb8d35a2 TTN |
277 | (defun ewoc-set-data (node data) |
278 | "Set NODE to encapsulate DATA." | |
279 | (setf (ewoc--node-data node) data)) | |
280 | ||
5b467bf4 | 281 | (defun ewoc-enter-first (ewoc data) |
d4ddf783 TTN |
282 | "Enter DATA first in EWOC. |
283 | Return the new node." | |
5b467bf4 | 284 | (ewoc--set-buffer-bind-dll ewoc |
bb7a346f | 285 | (ewoc-enter-after ewoc (ewoc--node-nth dll 0) data))) |
5b467bf4 SM |
286 | |
287 | (defun ewoc-enter-last (ewoc data) | |
d4ddf783 TTN |
288 | "Enter DATA last in EWOC. |
289 | Return the new node." | |
5b467bf4 | 290 | (ewoc--set-buffer-bind-dll ewoc |
bb7a346f | 291 | (ewoc-enter-before ewoc (ewoc--node-nth dll -1) data))) |
5b467bf4 | 292 | |
5b467bf4 | 293 | (defun ewoc-enter-after (ewoc node data) |
10c471e6 | 294 | "Enter a new element DATA after NODE in EWOC. |
d4ddf783 | 295 | Return the new node." |
5b467bf4 | 296 | (ewoc--set-buffer-bind-dll ewoc |
bb7a346f | 297 | (ewoc-enter-before ewoc (ewoc--node-next dll node) data))) |
5b467bf4 SM |
298 | |
299 | (defun ewoc-enter-before (ewoc node data) | |
10c471e6 | 300 | "Enter a new element DATA before NODE in EWOC. |
d4ddf783 | 301 | Return the new node." |
5b467bf4 | 302 | (ewoc--set-buffer-bind-dll ewoc |
f718c2fc | 303 | (ewoc--insert-new-node node data (ewoc--pretty-printer ewoc) dll))) |
5b467bf4 SM |
304 | |
305 | (defun ewoc-next (ewoc node) | |
d4ddf783 TTN |
306 | "Return the node in EWOC that follows NODE. |
307 | Return nil if NODE is nil or the last element." | |
5b467bf4 SM |
308 | (when node |
309 | (ewoc--filter-hf-nodes | |
bb7a346f | 310 | ewoc (ewoc--node-next (ewoc--dll ewoc) node)))) |
5b467bf4 SM |
311 | |
312 | (defun ewoc-prev (ewoc node) | |
d4ddf783 TTN |
313 | "Return the node in EWOC that precedes NODE. |
314 | Return nil if NODE is nil or the first element." | |
5b467bf4 SM |
315 | (when node |
316 | (ewoc--filter-hf-nodes | |
bb7a346f | 317 | ewoc (ewoc--node-prev (ewoc--dll ewoc) node)))) |
5b467bf4 | 318 | |
5b467bf4 SM |
319 | (defun ewoc-nth (ewoc n) |
320 | "Return the Nth node. | |
f0529b5b | 321 | N counts from zero. Return nil if there is less than N elements. |
5b467bf4 | 322 | If N is negative, return the -(N+1)th last element. |
7dd2e64c TTN |
323 | Thus, (ewoc-nth ewoc 0) returns the first node, |
324 | and (ewoc-nth ewoc -1) returns the last node. | |
8d1bec8d | 325 | Use `ewoc-data' to extract the data from the node." |
5b467bf4 SM |
326 | ;; Skip the header (or footer, if n is negative). |
327 | (setq n (if (< n 0) (1- n) (1+ n))) | |
328 | (ewoc--filter-hf-nodes ewoc | |
bb7a346f | 329 | (ewoc--node-nth (ewoc--dll ewoc) n))) |
5b467bf4 SM |
330 | |
331 | (defun ewoc-map (map-function ewoc &rest args) | |
332 | "Apply MAP-FUNCTION to all elements in EWOC. | |
333 | MAP-FUNCTION is applied to the first element first. | |
334 | If MAP-FUNCTION returns non-nil the element will be refreshed (its | |
335 | pretty-printer will be called once again). | |
336 | ||
0111ab41 JB |
337 | Note that the buffer for EWOC will be the current buffer when |
338 | MAP-FUNCTION is called. MAP-FUNCTION must restore the current | |
339 | buffer before it returns, if it changes it. | |
5b467bf4 SM |
340 | |
341 | If more than two arguments are given, the remaining | |
342 | arguments will be passed to MAP-FUNCTION." | |
343 | (ewoc--set-buffer-bind-dll-let* ewoc | |
344 | ((footer (ewoc--footer ewoc)) | |
8433d470 | 345 | (pp (ewoc--pretty-printer ewoc)) |
bb7a346f | 346 | (node (ewoc--node-nth dll 1))) |
bfbdb5ca TTN |
347 | (save-excursion |
348 | (while (not (eq node footer)) | |
349 | (if (apply map-function (ewoc--node-data node) args) | |
bb7a346f SM |
350 | (ewoc--refresh-node pp node dll)) |
351 | (setq node (ewoc--node-next dll node)))))) | |
5b467bf4 | 352 | |
f569c26e TTN |
353 | (defun ewoc-delete (ewoc &rest nodes) |
354 | "Delete NODES from EWOC." | |
355 | (ewoc--set-buffer-bind-dll-let* ewoc | |
ec491f90 | 356 | ((L nil) (R nil) (last (ewoc--last-node ewoc))) |
f569c26e TTN |
357 | (dolist (node nodes) |
358 | ;; If we are about to delete the node pointed at by last-node, | |
359 | ;; set last-node to nil. | |
ec491f90 TTN |
360 | (when (eq last node) |
361 | (setf last nil (ewoc--last-node ewoc) nil)) | |
f569c26e | 362 | (delete-region (ewoc--node-start-marker node) |
bb7a346f | 363 | (ewoc--node-start-marker (ewoc--node-next dll node))) |
f569c26e TTN |
364 | (set-marker (ewoc--node-start-marker node) nil) |
365 | (setf L (ewoc--node-left node) | |
366 | R (ewoc--node-right node) | |
367 | ;; Link neighbors to each other. | |
368 | (ewoc--node-right L) R | |
369 | (ewoc--node-left R) L | |
370 | ;; Forget neighbors. | |
371 | (ewoc--node-left node) nil | |
372 | (ewoc--node-right node) nil)))) | |
373 | ||
5b467bf4 SM |
374 | (defun ewoc-filter (ewoc predicate &rest args) |
375 | "Remove all elements in EWOC for which PREDICATE returns nil. | |
a1506d29 | 376 | Note that the buffer for EWOC will be current-buffer when PREDICATE |
0111ab41 | 377 | is called. PREDICATE must restore the current buffer before it returns |
5b467bf4 | 378 | if it changes it. |
0111ab41 | 379 | The PREDICATE is called with the element as its first argument. If any |
5b467bf4 SM |
380 | ARGS are given they will be passed to the PREDICATE." |
381 | (ewoc--set-buffer-bind-dll-let* ewoc | |
bb7a346f | 382 | ((node (ewoc--node-nth dll 1)) |
5b467bf4 | 383 | (footer (ewoc--footer ewoc)) |
f569c26e | 384 | (goodbye nil) |
2c246c9f | 385 | (inhibit-read-only t)) |
5b467bf4 | 386 | (while (not (eq node footer)) |
5b467bf4 | 387 | (unless (apply predicate (ewoc--node-data node) args) |
f569c26e | 388 | (push node goodbye)) |
bb7a346f | 389 | (setq node (ewoc--node-next dll node))) |
f569c26e | 390 | (apply 'ewoc-delete ewoc goodbye))) |
5b467bf4 | 391 | |
44946a4c | 392 | (defun ewoc-locate (ewoc &optional pos guess) |
5b467bf4 | 393 | "Return the node that POS (a buffer position) is within. |
44946a4c | 394 | POS may be a marker or an integer. It defaults to point. |
0111ab41 | 395 | GUESS should be a node that it is likely to be near POS. |
5b467bf4 SM |
396 | |
397 | If POS points before the first element, the first node is returned. | |
398 | If POS points after the last element, the last node is returned. | |
399 | If the EWOC is empty, nil is returned." | |
44946a4c | 400 | (unless pos (setq pos (point))) |
bb7a346f | 401 | (ewoc--set-buffer-bind-dll ewoc |
5b467bf4 SM |
402 | |
403 | (cond | |
404 | ;; Nothing present? | |
bb7a346f | 405 | ((eq (ewoc--node-nth dll 1) (ewoc--node-nth dll -1)) |
5b467bf4 SM |
406 | nil) |
407 | ||
408 | ;; Before second elem? | |
bb7a346f SM |
409 | ((< pos (ewoc--node-start-marker (ewoc--node-nth dll 2))) |
410 | (ewoc--node-nth dll 1)) | |
5b467bf4 SM |
411 | |
412 | ;; After one-before-last elem? | |
bb7a346f SM |
413 | ((>= pos (ewoc--node-start-marker (ewoc--node-nth dll -2))) |
414 | (ewoc--node-nth dll -2)) | |
5b467bf4 SM |
415 | |
416 | ;; We now know that pos is within a elem. | |
417 | (t | |
418 | ;; Make an educated guess about which of the three known | |
419 | ;; node'es (the first, the last, or GUESS) is nearest. | |
bb7a346f | 420 | (let* ((best-guess (ewoc--node-nth dll 1)) |
5b467bf4 SM |
421 | (distance (abs (- pos (ewoc--node-start-marker best-guess))))) |
422 | (when guess | |
423 | (let ((d (abs (- pos (ewoc--node-start-marker guess))))) | |
424 | (when (< d distance) | |
425 | (setq distance d) | |
426 | (setq best-guess guess)))) | |
427 | ||
bb7a346f | 428 | (let* ((g (ewoc--node-nth dll -1)) ;Check the last elem |
5b467bf4 SM |
429 | (d (abs (- pos (ewoc--node-start-marker g))))) |
430 | (when (< d distance) | |
431 | (setq distance d) | |
432 | (setq best-guess g))) | |
433 | ||
7dd2e64c | 434 | (when (ewoc--last-node ewoc) ;Check "previous". |
5b467bf4 SM |
435 | (let* ((g (ewoc--last-node ewoc)) |
436 | (d (abs (- pos (ewoc--node-start-marker g))))) | |
437 | (when (< d distance) | |
438 | (setq distance d) | |
439 | (setq best-guess g)))) | |
440 | ||
441 | ;; best-guess is now a "best guess". | |
442 | ;; Find the correct node. First determine in which direction | |
443 | ;; it lies, and then move in that direction until it is found. | |
a1506d29 | 444 | |
5b467bf4 SM |
445 | (cond |
446 | ;; Is pos after the guess? | |
447 | ((>= pos | |
448 | (ewoc--node-start-marker best-guess)) | |
449 | ;; Loop until we are exactly one node too far down... | |
450 | (while (>= pos (ewoc--node-start-marker best-guess)) | |
bb7a346f | 451 | (setq best-guess (ewoc--node-next dll best-guess))) |
5b467bf4 | 452 | ;; ...and return the previous node. |
bb7a346f | 453 | (ewoc--node-prev dll best-guess)) |
5b467bf4 SM |
454 | |
455 | ;; Pos is before best-guess | |
456 | (t | |
457 | (while (< pos (ewoc--node-start-marker best-guess)) | |
bb7a346f | 458 | (setq best-guess (ewoc--node-prev dll best-guess))) |
5b467bf4 SM |
459 | best-guess))))))) |
460 | ||
461 | (defun ewoc-invalidate (ewoc &rest nodes) | |
d4ddf783 TTN |
462 | "Call EWOC's pretty-printer for each element in NODES. |
463 | Delete current text first, thus effecting a \"refresh\"." | |
8433d470 TTN |
464 | (ewoc--set-buffer-bind-dll-let* ewoc |
465 | ((pp (ewoc--pretty-printer ewoc))) | |
bfbdb5ca TTN |
466 | (save-excursion |
467 | (dolist (node nodes) | |
bb7a346f | 468 | (ewoc--refresh-node pp node dll))))) |
5b467bf4 | 469 | |
44946a4c | 470 | (defun ewoc-goto-prev (ewoc arg) |
d4ddf783 | 471 | "Move point to the ARGth previous element in EWOC. |
5b467bf4 | 472 | Don't move if we are at the first element, or if EWOC is empty. |
d4ddf783 | 473 | Return the node we moved to." |
5b467bf4 | 474 | (ewoc--set-buffer-bind-dll-let* ewoc |
6d84ac85 | 475 | ((node (ewoc-locate ewoc (point)))) |
5b467bf4 | 476 | (when node |
44946a4c SM |
477 | ;; If we were past the last element, first jump to it. |
478 | (when (>= (point) (ewoc--node-start-marker (ewoc--node-right node))) | |
479 | (setq arg (1- arg))) | |
5b467bf4 SM |
480 | (while (and node (> arg 0)) |
481 | (setq arg (1- arg)) | |
bb7a346f | 482 | (setq node (ewoc--node-prev dll node))) |
5b467bf4 SM |
483 | ;; Never step above the first element. |
484 | (unless (ewoc--filter-hf-nodes ewoc node) | |
bb7a346f | 485 | (setq node (ewoc--node-nth dll 1))) |
5b467bf4 SM |
486 | (ewoc-goto-node ewoc node)))) |
487 | ||
44946a4c | 488 | (defun ewoc-goto-next (ewoc arg) |
d4ddf783 TTN |
489 | "Move point to the ARGth next element in EWOC. |
490 | Return the node (or nil if we just passed the last node)." | |
5b467bf4 | 491 | (ewoc--set-buffer-bind-dll-let* ewoc |
6d84ac85 | 492 | ((node (ewoc-locate ewoc (point)))) |
5b467bf4 SM |
493 | (while (and node (> arg 0)) |
494 | (setq arg (1- arg)) | |
bb7a346f | 495 | (setq node (ewoc--node-next dll node))) |
5b467bf4 | 496 | ;; Never step below the first element. |
44946a4c | 497 | ;; (unless (ewoc--filter-hf-nodes ewoc node) |
bb7a346f | 498 | ;; (setq node (ewoc--node-nth dll -2))) |
5b467bf4 SM |
499 | (ewoc-goto-node ewoc node))) |
500 | ||
501 | (defun ewoc-goto-node (ewoc node) | |
d4ddf783 | 502 | "Move point to NODE in EWOC." |
5b467bf4 SM |
503 | (ewoc--set-buffer-bind-dll ewoc |
504 | (goto-char (ewoc--node-start-marker node)) | |
505 | (if goal-column (move-to-column goal-column)) | |
506 | (setf (ewoc--last-node ewoc) node))) | |
507 | ||
508 | (defun ewoc-refresh (ewoc) | |
509 | "Refresh all data in EWOC. | |
510 | The pretty-printer that was specified when the EWOC was created | |
511 | will be called for all elements in EWOC. | |
512 | Note that `ewoc-invalidate' is more efficient if only a small | |
513 | number of elements needs to be refreshed." | |
514 | (ewoc--set-buffer-bind-dll-let* ewoc | |
cb3430a1 | 515 | ((footer (ewoc--footer ewoc))) |
5b467bf4 | 516 | (let ((inhibit-read-only t)) |
bb7a346f | 517 | (delete-region (ewoc--node-start-marker (ewoc--node-nth dll 1)) |
5b467bf4 SM |
518 | (ewoc--node-start-marker footer)) |
519 | (goto-char (ewoc--node-start-marker footer)) | |
340d9945 | 520 | (let ((pp (ewoc--pretty-printer ewoc)) |
bb7a346f | 521 | (node (ewoc--node-nth dll 1))) |
5b467bf4 SM |
522 | (while (not (eq node footer)) |
523 | (set-marker (ewoc--node-start-marker node) (point)) | |
340d9945 | 524 | (funcall pp (ewoc--node-data node)) |
bb7a346f | 525 | (setq node (ewoc--node-next dll node))))) |
5b467bf4 SM |
526 | (set-marker (ewoc--node-start-marker footer) (point)))) |
527 | ||
528 | (defun ewoc-collect (ewoc predicate &rest args) | |
529 | "Select elements from EWOC using PREDICATE. | |
530 | Return a list of all selected data elements. | |
0111ab41 JB |
531 | PREDICATE is a function that takes a data element as its first |
532 | argument. The elements on the returned list will appear in the | |
533 | same order as in the buffer. You should not rely on the order of | |
534 | calls to PREDICATE. | |
535 | Note that the buffer the EWOC is displayed in is the current | |
536 | buffer when PREDICATE is called. PREDICATE must restore it if it | |
537 | changes it. | |
5b467bf4 SM |
538 | If more than two arguments are given the |
539 | remaining arguments will be passed to PREDICATE." | |
540 | (ewoc--set-buffer-bind-dll-let* ewoc | |
541 | ((header (ewoc--header ewoc)) | |
bb7a346f | 542 | (node (ewoc--node-nth dll -2)) |
5b467bf4 SM |
543 | result) |
544 | (while (not (eq node header)) | |
545 | (if (apply predicate (ewoc--node-data node) args) | |
546 | (push (ewoc--node-data node) result)) | |
bb7a346f | 547 | (setq node (ewoc--node-prev dll node))) |
d5337506 | 548 | result)) |
5b467bf4 SM |
549 | |
550 | (defun ewoc-buffer (ewoc) | |
551 | "Return the buffer that is associated with EWOC. | |
d4ddf783 | 552 | Return nil if the buffer has been deleted." |
5b467bf4 SM |
553 | (let ((buf (ewoc--buffer ewoc))) |
554 | (when (buffer-name buf) buf))) | |
555 | ||
cb3430a1 SM |
556 | (defun ewoc-get-hf (ewoc) |
557 | "Return a cons cell containing the (HEADER . FOOTER) of EWOC." | |
558 | (cons (ewoc--node-data (ewoc--header ewoc)) | |
559 | (ewoc--node-data (ewoc--footer ewoc)))) | |
560 | ||
561 | (defun ewoc-set-hf (ewoc header footer) | |
562 | "Set the HEADER and FOOTER of EWOC." | |
60eae434 TTN |
563 | (ewoc--set-buffer-bind-dll-let* ewoc |
564 | ((head (ewoc--header ewoc)) | |
2bbacc0e TTN |
565 | (foot (ewoc--footer ewoc)) |
566 | (hf-pp (ewoc--hf-pp ewoc))) | |
60eae434 TTN |
567 | (setf (ewoc--node-data head) header |
568 | (ewoc--node-data foot) footer) | |
569 | (save-excursion | |
bb7a346f SM |
570 | (ewoc--refresh-node hf-pp head dll) |
571 | (ewoc--refresh-node hf-pp foot dll)))) | |
cb3430a1 | 572 | |
5b467bf4 SM |
573 | \f |
574 | (provide 'ewoc) | |
575 | ||
7ece7aba SM |
576 | ;; Local Variables: |
577 | ;; eval: (put 'ewoc--set-buffer-bind-dll 'lisp-indent-hook 1) | |
578 | ;; eval: (put 'ewoc--set-buffer-bind-dll-let* 'lisp-indent-hook 2) | |
579 | ;; End: | |
5b467bf4 | 580 | |
7ece7aba | 581 | ;; arch-tag: d78915b9-9a07-44bf-aac6-04a1fc1bd6d4 |
5b467bf4 | 582 | ;;; ewoc.el ends here |