declare smobs in alloc.c
[bpt/emacs.git] / lisp / gnus / gnus-salt.el
CommitLineData
eec82323 1;;; gnus-salt.el --- alternate summary mode interfaces for Gnus
16409b0b 2
ba318903 3;; Copyright (C) 1996-1999, 2001-2014 Free Software Foundation, Inc.
eec82323 4
6748645f
LMI
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Keywords: news
eec82323
LMI
7
8;; This file is part of GNU Emacs.
9
5e809f55 10;; GNU Emacs is free software: you can redistribute it and/or modify
eec82323 11;; it under the terms of the GNU General Public License as published by
5e809f55
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
eec82323
LMI
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
5e809f55 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
22
23;;; Commentary:
24
25;;; Code:
26
5ab7173c 27(eval-when-compile (require 'cl))
d029b5d2
KY
28(eval-when-compile
29 (when (featurep 'xemacs)
30 (require 'easy-mmode))) ; for `define-minor-mode'
5ab7173c 31
eec82323
LMI
32(require 'gnus)
33(require 'gnus-sum)
23f87bed 34(require 'gnus-win)
eec82323
LMI
35
36;;;
37;;; gnus-pick-mode
38;;;
39
a8151ef7
LMI
40(defcustom gnus-pick-display-summary nil
41 "*Display summary while reading."
42 :type 'boolean
43 :group 'gnus-summary-pick)
44
45(defcustom gnus-pick-mode-hook nil
46 "Hook run in summary pick mode buffers."
47 :type 'hook
48 :group 'gnus-summary-pick)
49
23f87bed
MB
50(when (featurep 'xemacs)
51 (add-hook 'gnus-pick-mode-hook 'gnus-xmas-pick-menu-add))
52
a8151ef7
LMI
53(defcustom gnus-mark-unpicked-articles-as-read nil
54 "*If non-nil, mark all unpicked articles as read."
55 :type 'boolean
56 :group 'gnus-summary-pick)
57
58(defcustom gnus-pick-elegant-flow t
23f87bed
MB
59 "If non-nil, `gnus-pick-start-reading' runs
60 `gnus-summary-next-group' when no articles have been picked."
a8151ef7
LMI
61 :type 'boolean
62 :group 'gnus-summary-pick)
63
64(defcustom gnus-summary-pick-line-format
23f87bed 65 "%-5P %U\%R\%z\%I\%(%[%4L: %-23,23n%]%) %s\n"
eec82323 66 "*The format specification of the lines in pick buffers.
a8151ef7
LMI
67It accepts the same format specs that `gnus-summary-line-format' does."
68 :type 'string
69 :group 'gnus-summary-pick)
eec82323
LMI
70
71;;; Internal variables.
72
bbf52f1e
SM
73(defvar gnus-pick-mode-map
74 (let ((map (make-sparse-keymap)))
75 (gnus-define-keys map
76 " " gnus-pick-next-page
77 "u" gnus-pick-unmark-article-or-thread
78 "." gnus-pick-article-or-thread
79 gnus-down-mouse-2 gnus-pick-mouse-pick-region
80 "\r" gnus-pick-start-reading)
81 map))
eec82323
LMI
82
83(defun gnus-pick-make-menu-bar ()
84 (unless (boundp 'gnus-pick-menu)
85 (easy-menu-define
23f87bed
MB
86 gnus-pick-menu gnus-pick-mode-map ""
87 '("Pick"
88 ("Pick"
89 ["Article" gnus-summary-mark-as-processable t]
90 ["Thread" gnus-uu-mark-thread t]
91 ["Region" gnus-uu-mark-region t]
92 ["Regexp" gnus-uu-mark-by-regexp t]
93 ["Buffer" gnus-uu-mark-buffer t])
94 ("Unpick"
95 ["Article" gnus-summary-unmark-as-processable t]
96 ["Thread" gnus-uu-unmark-thread t]
97 ["Region" gnus-uu-unmark-region t]
98 ["Regexp" gnus-uu-unmark-by-regexp t]
99 ["Buffer" gnus-summary-unmark-all-processable t])
100 ["Start reading" gnus-pick-start-reading t]
101 ["Switch pick mode off" gnus-pick-mode gnus-pick-mode]))))
eec82323 102
765d4319
KY
103(eval-when-compile
104 (when (featurep 'xemacs)
105 (defvar gnus-pick-mode-on-hook)
106 (defvar gnus-pick-mode-off-hook)))
107
bbf52f1e 108(define-minor-mode gnus-pick-mode
eec82323
LMI
109 "Minor mode for providing a pick-and-read interface in Gnus summary buffers.
110
111\\{gnus-pick-mode-map}"
bbf52f1e
SM
112 :lighter " Pick" :keymap gnus-pick-mode-map
113 (cond
114 ((not (derived-mode-p 'gnus-summary-mode)) (setq gnus-pick-mode nil))
115 ((not gnus-pick-mode)
116 ;; FIXME: a buffer-local minor mode removing globally from a hook??
117 (remove-hook 'gnus-message-setup-hook 'gnus-pick-setup-message))
118 (t
119 ;; Make sure that we don't select any articles upon group entry.
120 (set (make-local-variable 'gnus-auto-select-first) nil)
121 ;; Change line format.
122 (setq gnus-summary-line-format gnus-summary-pick-line-format)
123 (setq gnus-summary-line-format-spec nil)
124 (gnus-update-format-specifications nil 'summary)
125 (gnus-update-summary-mark-positions)
126 ;; FIXME: a buffer-local minor mode adding globally to a hook??
127 (add-hook 'gnus-message-setup-hook 'gnus-pick-setup-message)
128 (set (make-local-variable 'gnus-summary-goto-unread) 'never)
129 ;; Set up the menu.
130 (when (gnus-visual-p 'pick-menu 'menu)
131 (gnus-pick-make-menu-bar)))))
eec82323
LMI
132
133(defun gnus-pick-setup-message ()
134 "Make Message do the right thing on exit."
135 (when (and (gnus-buffer-live-p gnus-summary-buffer)
67d0660b 136 (with-current-buffer gnus-summary-buffer
eec82323
LMI
137 gnus-pick-mode))
138 (message-add-action
67d0660b 139 `(gnus-configure-windows ,gnus-current-window-configuration t)
16409b0b 140 'send 'exit 'postpone 'kill)))
eec82323
LMI
141
142(defvar gnus-pick-line-number 1)
143(defun gnus-pick-line-number ()
144 "Return the current line number."
145 (if (bobp)
146 (setq gnus-pick-line-number 1)
147 (incf gnus-pick-line-number)))
148
149(defun gnus-pick-start-reading (&optional catch-up)
150 "Start reading the picked articles.
151If given a prefix, mark all unpicked articles as read."
152 (interactive "P")
153 (if gnus-newsgroup-processable
154 (progn
23f87bed
MB
155 (gnus-summary-limit-to-articles nil)
156 (when (or catch-up gnus-mark-unpicked-articles-as-read)
eec82323 157 (gnus-summary-limit-mark-excluded-as-read))
23f87bed
MB
158 (gnus-summary-first-article)
159 (gnus-configure-windows
eec82323
LMI
160 (if gnus-pick-display-summary 'article 'pick) t))
161 (if gnus-pick-elegant-flow
162 (progn
163 (when (or catch-up gnus-mark-unpicked-articles-as-read)
a8151ef7 164 (gnus-summary-catchup nil t))
eec82323
LMI
165 (if (gnus-group-quit-config gnus-newsgroup-name)
166 (gnus-summary-exit)
167 (gnus-summary-next-group)))
168 (error "No articles have been picked"))))
169
6748645f 170(defun gnus-pick-goto-article (arg)
16409b0b
GM
171 "Go to the article number indicated by ARG.
172If ARG is an invalid article number, then stay on current line."
6748645f
LMI
173 (let (pos)
174 (save-excursion
175 (goto-char (point-min))
176 (when (zerop (forward-line (1- (prefix-numeric-value arg))))
177 (setq pos (point))))
178 (if (not pos)
179 (gnus-error 2 "No such line: %s" arg)
180 (goto-char pos))))
16409b0b 181
eec82323 182(defun gnus-pick-article (&optional arg)
16409b0b 183 "Pick the article on the current line.
eec82323
LMI
184If ARG, pick the article on that line instead."
185 (interactive "P")
186 (when arg
6748645f 187 (gnus-pick-goto-article arg))
eec82323
LMI
188 (gnus-summary-mark-as-processable 1))
189
6748645f 190(defun gnus-pick-article-or-thread (&optional arg)
16409b0b 191 "If `gnus-thread-hide-subtree' is t, then pick the thread on the current line.
6748645f
LMI
192Otherwise pick the article on the current line.
193If ARG, pick the article/thread on that line instead."
194 (interactive "P")
195 (when arg
196 (gnus-pick-goto-article arg))
197 (if gnus-thread-hide-subtree
16409b0b
GM
198 (progn
199 (save-excursion
200 (gnus-uu-mark-thread))
201 (forward-line 1))
6748645f 202 (gnus-summary-mark-as-processable 1)))
16409b0b 203
6748645f 204(defun gnus-pick-unmark-article-or-thread (&optional arg)
16409b0b 205 "If `gnus-thread-hide-subtree' is t, then unmark the thread on current line.
6748645f
LMI
206Otherwise unmark the article on current line.
207If ARG, unmark thread/article on that line instead."
208 (interactive "P")
209 (when arg
210 (gnus-pick-goto-article arg))
211 (if gnus-thread-hide-subtree
16409b0b
GM
212 (save-excursion
213 (gnus-uu-unmark-thread))
6748645f 214 (gnus-summary-unmark-as-processable 1)))
16409b0b 215
eec82323
LMI
216(defun gnus-pick-mouse-pick (e)
217 (interactive "e")
218 (mouse-set-point e)
219 (save-excursion
220 (gnus-summary-mark-as-processable 1)))
221
222(defun gnus-pick-mouse-pick-region (start-event)
223 "Pick articles that the mouse is dragged over.
224This must be bound to a button-down mouse event."
225 (interactive "e")
226 (mouse-minibuffer-check start-event)
227 (let* ((echo-keystrokes 0)
228 (start-posn (event-start start-event))
229 (start-point (posn-point start-posn))
9b40100a 230 (start-line (1+ (count-lines (point-min) start-point)))
eec82323 231 (start-window (posn-window start-posn))
6748645f 232 (bounds (gnus-window-edges start-window))
eec82323
LMI
233 (top (nth 1 bounds))
234 (bottom (if (window-minibuffer-p start-window)
235 (nth 3 bounds)
236 ;; Don't count the mode line.
237 (1- (nth 3 bounds))))
238 (click-count (1- (event-click-count start-event))))
239 (setq mouse-selection-click-count click-count)
240 (setq mouse-selection-click-count-buffer (current-buffer))
241 (mouse-set-point start-event)
23f87bed 242 ;; In case the down click is in the middle of some intangible text,
eec82323
LMI
243 ;; use the end of that text, and put it in START-POINT.
244 (when (< (point) start-point)
245 (goto-char start-point))
246 (gnus-pick-article)
247 (setq start-point (point))
248 ;; end-of-range is used only in the single-click case.
249 ;; It is the place where the drag has reached so far
250 ;; (but not outside the window where the drag started).
6748645f 251 (let (event end end-point (end-of-range (point)))
eec82323 252 (track-mouse
23f87bed
MB
253 (while (progn
254 (setq event (cdr (gnus-read-event-char)))
255 (or (mouse-movement-p event)
256 (eq (car-safe event) 'switch-frame)))
257 (if (eq (car-safe event) 'switch-frame)
258 nil
259 (setq end (event-end event)
260 end-point (posn-point end))
261
262 (cond
263 ;; Are we moving within the original window?
264 ((and (eq (posn-window end) start-window)
265 (integer-or-marker-p end-point))
9b40100a 266 ;; Go to START-POINT first, so that when we move to END-POINT,
23f87bed
MB
267 ;; if it's in the middle of intangible text,
268 ;; point jumps in the direction away from START-POINT.
269 (goto-char start-point)
270 (goto-char end-point)
271 (gnus-pick-article)
272 ;; In case the user moved his mouse really fast, pick
9b40100a
SM
273 ;; articles on the line between this one and the last one.
274 (let* ((this-line (1+ (count-lines (point-min) end-point)))
23f87bed
MB
275 (min-line (min this-line start-line))
276 (max-line (max this-line start-line)))
277 (while (< min-line max-line)
7a4abdd6
GM
278 (goto-char (point-min))
279 (forward-line (1- min-line))
23f87bed
MB
280 (gnus-pick-article)
281 (setq min-line (1+ min-line)))
282 (setq start-line this-line))
283 (when (zerop (% click-count 3))
284 (setq end-of-range (point))))
285 (t
286 (let ((mouse-row (cdr (cdr (mouse-position)))))
287 (cond
288 ((null mouse-row))
289 ((< mouse-row top)
290 (mouse-scroll-subr start-window (- mouse-row top)))
291 ((>= mouse-row bottom)
292 (mouse-scroll-subr start-window
293 (1+ (- mouse-row bottom)))))))))))
eec82323 294 (when (consp event)
c2e9e9ef
SM
295 (let (;; (fun (key-binding (vector (car event))))
296 )
eec82323 297 ;; Run the binding of the terminating up-event, if possible.
c2e9e9ef 298 ;; In the case of a multiple click, it gives the wrong results,
eec82323
LMI
299 ;; because it would fail to set up a region.
300 (when nil
c2e9e9ef
SM
301 ;; (and (= (mod mouse-selection-click-count 3) 0) (fboundp fun))
302 ;; In this case, we can just let the up-event execute normally.
eec82323
LMI
303 (let ((end (event-end event)))
304 ;; Set the position in the event before we replay it,
305 ;; because otherwise it may have a position in the wrong
306 ;; buffer.
307 (setcar (cdr end) end-of-range)
308 ;; Delete the overlay before calling the function,
c2e9e9ef 309 ;; because delete-overlay increases buffer-modified-tick.
eec82323
LMI
310 (push event unread-command-events))))))))
311
c2e9e9ef
SM
312(defvar scroll-in-place)
313
eec82323
LMI
314(defun gnus-pick-next-page ()
315 "Go to the next page. If at the end of the buffer, start reading articles."
316 (interactive)
317 (let ((scroll-in-place nil))
318 (condition-case nil
319 (scroll-up)
320 (end-of-buffer (gnus-pick-start-reading)))))
321
322;;;
323;;; gnus-binary-mode
324;;;
325
eec82323
LMI
326(defvar gnus-binary-mode-hook nil
327 "Hook run in summary binary mode buffers.")
328
bbf52f1e
SM
329(defvar gnus-binary-mode-map
330 (let ((map (make-sparse-keymap)))
331 (gnus-define-keys map
332 "g" gnus-binary-show-article)
333 map))
eec82323
LMI
334
335(defun gnus-binary-make-menu-bar ()
336 (unless (boundp 'gnus-binary-menu)
337 (easy-menu-define
23f87bed
MB
338 gnus-binary-menu gnus-binary-mode-map ""
339 '("Pick"
340 ["Switch binary mode off" gnus-binary-mode t]))))
eec82323 341
765d4319
KY
342(eval-when-compile
343 (when (featurep 'xemacs)
344 (defvar gnus-binary-mode-on-hook)
345 (defvar gnus-binary-mode-off-hook)))
346
bbf52f1e 347(define-minor-mode gnus-binary-mode
eec82323 348 "Minor mode for providing a binary group interface in Gnus summary buffers."
bbf52f1e
SM
349 :lighter " Binary" :keymap gnus-binary-mode-map
350 (cond
351 ((not (derived-mode-p 'gnus-summary-mode)) (setq gnus-binary-mode nil))
352 (gnus-binary-mode
353 ;; Make sure that we don't select any articles upon group entry.
354 (make-local-variable 'gnus-auto-select-first)
355 (setq gnus-auto-select-first nil)
356 (make-local-variable 'gnus-summary-display-article-function)
357 (setq gnus-summary-display-article-function 'gnus-binary-display-article)
358 ;; Set up the menu.
359 (when (gnus-visual-p 'binary-menu 'menu)
360 (gnus-binary-make-menu-bar)))))
eec82323 361
c2e9e9ef 362(defun gnus-binary-display-article (article &optional _all-header)
eec82323
LMI
363 "Run ARTICLE through the binary decode functions."
364 (when (gnus-summary-goto-subject article)
23f87bed 365 (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic)))
eec82323
LMI
366 (gnus-uu-decode-uu))))
367
368(defun gnus-binary-show-article (&optional arg)
369 "Bypass the binary functions and show the article."
370 (interactive "P")
371 (let (gnus-summary-display-article-function)
372 (gnus-summary-show-article arg)))
373
374;;;
375;;; gnus-tree-mode
376;;;
377
a8151ef7
LMI
378(defcustom gnus-tree-line-format "%(%[%3,3n%]%)"
379 "Format of tree elements."
380 :type 'string
381 :group 'gnus-summary-tree)
eec82323 382
a8151ef7 383(defcustom gnus-tree-minimize-window t
eec82323
LMI
384 "If non-nil, minimize the tree buffer window.
385If a number, never let the tree buffer grow taller than that number of
a8151ef7 386lines."
6748645f
LMI
387 :type '(choice boolean
388 integer)
a8151ef7 389 :group 'gnus-summary-tree)
eec82323 390
a931698a 391(defcustom gnus-selected-tree-face 'mode-line
a8151ef7
LMI
392 "*Face used for highlighting selected articles in the thread tree."
393 :type 'face
394 :group 'gnus-summary-tree)
eec82323
LMI
395
396(defvar gnus-tree-brackets '((?\[ . ?\]) (?\( . ?\))
397 (?\{ . ?\}) (?< . ?>))
398 "Brackets used in tree nodes.")
399
400(defvar gnus-tree-parent-child-edges '(?- ?\\ ?|)
401 "Characters used to connect parents with children.")
402
a8151ef7
LMI
403(defcustom gnus-tree-mode-line-format "Gnus: %%b %S %Z"
404 "*The format specification for the tree mode line."
405 :type 'string
406 :group 'gnus-summary-tree)
eec82323 407
a8151ef7 408(defcustom gnus-generate-tree-function 'gnus-generate-vertical-tree
eec82323
LMI
409 "*Function for generating a thread tree.
410Two predefined functions are available:
a8151ef7
LMI
411`gnus-generate-horizontal-tree' and `gnus-generate-vertical-tree'."
412 :type '(radio (function-item gnus-generate-vertical-tree)
413 (function-item gnus-generate-horizontal-tree)
414 (function :tag "Other" nil))
415 :group 'gnus-summary-tree)
eec82323 416
a8151ef7
LMI
417(defcustom gnus-tree-mode-hook nil
418 "*Hook run in tree mode buffers."
419 :type 'hook
420 :group 'gnus-summary-tree)
eec82323 421
23f87bed
MB
422(when (featurep 'xemacs)
423 (add-hook 'gnus-tree-mode-hook 'gnus-xmas-tree-menu-add)
424 (add-hook 'gnus-tree-mode-hook 'gnus-xmas-switch-horizontal-scrollbar-off))
425
426
eec82323
LMI
427;;; Internal variables.
428
c2e9e9ef
SM
429(defvar gnus-tmp-name)
430(defvar gnus-tmp-from)
431(defvar gnus-tmp-number)
432(defvar gnus-tmp-open-bracket)
433(defvar gnus-tmp-close-bracket)
434(defvar gnus-tmp-subject)
435
eec82323
LMI
436(defvar gnus-tree-line-format-alist
437 `((?n gnus-tmp-name ?s)
438 (?f gnus-tmp-from ?s)
439 (?N gnus-tmp-number ?d)
440 (?\[ gnus-tmp-open-bracket ?c)
441 (?\] gnus-tmp-close-bracket ?c)
442 (?s gnus-tmp-subject ?s)))
443
444(defvar gnus-tree-mode-line-format-alist gnus-summary-mode-line-format-alist)
445
446(defvar gnus-tree-mode-line-format-spec nil)
447(defvar gnus-tree-line-format-spec nil)
448
449(defvar gnus-tree-node-length nil)
450(defvar gnus-selected-tree-overlay nil)
451
452(defvar gnus-tree-displayed-thread nil)
16409b0b 453(defvar gnus-tree-inhibit nil)
eec82323 454
c2e9e9ef
SM
455(defvar gnus-tree-mode-map
456 (let ((map (make-keymap)))
457 (suppress-keymap map)
458 (gnus-define-keys
459 map
460 "\r" gnus-tree-select-article
461 gnus-mouse-2 gnus-tree-pick-article
462 "\C-?" gnus-tree-read-summary-keys
463 "h" gnus-tree-show-summary
eec82323 464
c2e9e9ef 465 "\C-c\C-i" gnus-info-find-node)
eec82323 466
c2e9e9ef
SM
467 (substitute-key-definition
468 'undefined 'gnus-tree-read-summary-keys map)
469 map))
eec82323 470
c2e9e9ef 471(put 'gnus-tree-mode 'mode-class 'special)
eec82323
LMI
472
473(defun gnus-tree-make-menu-bar ()
474 (unless (boundp 'gnus-tree-menu)
475 (easy-menu-define
23f87bed
MB
476 gnus-tree-menu gnus-tree-mode-map ""
477 '("Tree"
478 ["Select article" gnus-tree-select-article t]))))
eec82323 479
c2e9e9ef 480(define-derived-mode gnus-tree-mode fundamental-mode "Tree"
eec82323 481 "Major mode for displaying thread trees."
6748645f
LMI
482 (gnus-set-format 'tree-mode)
483 (gnus-set-format 'tree t)
eec82323
LMI
484 (when (gnus-visual-p 'tree-menu 'menu)
485 (gnus-tree-make-menu-bar))
eec82323 486 (gnus-simplify-mode-line)
16409b0b 487 (buffer-disable-undo)
eec82323
LMI
488 (setq buffer-read-only t)
489 (setq truncate-lines t)
c2e9e9ef 490 (save-current-buffer
eec82323
LMI
491 (gnus-set-work-buffer)
492 (gnus-tree-node-insert (make-mail-header "") nil)
c2e9e9ef 493 (setq gnus-tree-node-length (1- (point)))))
eec82323
LMI
494
495(defun gnus-tree-read-summary-keys (&optional arg)
496 "Read a summary buffer key sequence and execute it."
497 (interactive "P")
16409b0b
GM
498 (unless gnus-tree-inhibit
499 (let ((buf (current-buffer))
500 (gnus-tree-inhibit t)
501 win)
502 (set-buffer gnus-article-buffer)
503 (gnus-article-read-summary-keys arg nil t)
504 (when (setq win (get-buffer-window buf))
505 (select-window win)
506 (when gnus-selected-tree-overlay
507 (goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1)))
508 (gnus-tree-minimize)))))
eec82323 509
a8151ef7
LMI
510(defun gnus-tree-show-summary ()
511 "Reconfigure windows to show summary buffer."
512 (interactive)
513 (if (not (gnus-buffer-live-p gnus-summary-buffer))
514 (error "There is no summary buffer for this tree buffer")
515 (gnus-configure-windows 'article)
516 (gnus-summary-goto-subject gnus-current-article)))
517
eec82323
LMI
518(defun gnus-tree-select-article (article)
519 "Select the article under point, if any."
520 (interactive (list (gnus-tree-article-number)))
521 (let ((buf (current-buffer)))
522 (when article
67d0660b 523 (with-current-buffer gnus-summary-buffer
eec82323
LMI
524 (gnus-summary-goto-article article))
525 (select-window (get-buffer-window buf)))))
526
527(defun gnus-tree-pick-article (e)
528 "Select the article under the mouse pointer."
529 (interactive "e")
530 (mouse-set-point e)
531 (gnus-tree-select-article (gnus-tree-article-number)))
532
533(defun gnus-tree-article-number ()
534 (get-text-property (point) 'gnus-number))
535
536(defun gnus-tree-article-region (article)
537 "Return a cons with BEG and END of the article region."
16409b0b
GM
538 (let ((pos (text-property-any
539 (point-min) (point-max) 'gnus-number article)))
eec82323
LMI
540 (when pos
541 (cons pos (next-single-property-change pos 'gnus-number)))))
542
eec82323
LMI
543(defun gnus-tree-recenter ()
544 "Center point in the tree window."
545 (let ((selected (selected-window))
23f87bed 546 (tree-window (gnus-get-buffer-window gnus-tree-buffer t)))
eec82323
LMI
547 (when tree-window
548 (select-window tree-window)
549 (when gnus-selected-tree-overlay
550 (goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1)))
551 (let* ((top (cond ((< (window-height) 4) 0)
552 ((< (window-height) 7) 1)
553 (t 2)))
554 (height (1- (window-height)))
555 (bottom (save-excursion (goto-char (point-max))
556 (forward-line (- height))
557 (point))))
23f87bed 558 ;; Set the window start to either `bottom', which is the biggest
eec82323
LMI
559 ;; possible valid number, or the second line from the top,
560 ;; whichever is the least.
561 (set-window-start
562 tree-window (min bottom (save-excursion
563 (forward-line (- top)) (point)))))
564 (select-window selected))))
565
566(defun gnus-get-tree-buffer ()
567 "Return the tree buffer properly initialized."
67d0660b 568 (with-current-buffer (gnus-get-buffer-create gnus-tree-buffer)
c2e9e9ef 569 (unless (derived-mode-p 'gnus-tree-mode)
eec82323
LMI
570 (gnus-tree-mode))
571 (current-buffer)))
572
573(defun gnus-tree-minimize ()
574 (when (and gnus-tree-minimize-window
575 (not (one-window-p)))
576 (let ((windows 0)
577 tot-win-height)
c2e9e9ef 578 (walk-windows (lambda (_window) (incf windows)))
eec82323
LMI
579 (setq tot-win-height
580 (- (frame-height)
581 (* window-min-height (1- windows))
582 2))
583 (let* ((window-min-height 2)
584 (height (count-lines (point-min) (point-max)))
585 (min (max (1- window-min-height) height))
586 (tot (if (numberp gnus-tree-minimize-window)
587 (min gnus-tree-minimize-window min)
588 min))
589 (win (get-buffer-window (current-buffer)))
590 (wh (and win (1- (window-height win)))))
591 (setq tot (min tot tot-win-height))
592 (when (and win
593 (not (eq tot wh)))
594 (let ((selected (selected-window)))
595 (when (ignore-errors (select-window win))
596 (enlarge-window (- tot wh))
597 (select-window selected))))))))
598
599;;; Generating the tree.
600
601(defun gnus-tree-node-insert (header sparse &optional adopted)
602 (let* ((dummy (stringp header))
603 (header (if (vectorp header) header
604 (progn
605 (setq header (make-mail-header "*****"))
606 (mail-header-set-number header 0)
607 (mail-header-set-lines header 0)
608 (mail-header-set-chars header 0)
609 header)))
610 (gnus-tmp-from (mail-header-from header))
611 (gnus-tmp-subject (mail-header-subject header))
612 (gnus-tmp-number (mail-header-number header))
613 (gnus-tmp-name
614 (cond
615 ((string-match "(.+)" gnus-tmp-from)
616 (substring gnus-tmp-from
617 (1+ (match-beginning 0)) (1- (match-end 0))))
618 ((string-match "<[^>]+> *$" gnus-tmp-from)
619 (let ((beg (match-beginning 0)))
620 (or (and (string-match "^\"[^\"]*\"" gnus-tmp-from)
621 (substring gnus-tmp-from (1+ (match-beginning 0))
622 (1- (match-end 0))))
623 (substring gnus-tmp-from 0 beg))))
624 ((memq gnus-tmp-number sparse)
625 "***")
626 (t gnus-tmp-from)))
627 (gnus-tmp-open-bracket
628 (cond ((memq gnus-tmp-number sparse)
629 (caadr gnus-tree-brackets))
630 (dummy (caaddr gnus-tree-brackets))
631 (adopted (car (nth 3 gnus-tree-brackets)))
632 (t (caar gnus-tree-brackets))))
633 (gnus-tmp-close-bracket
634 (cond ((memq gnus-tmp-number sparse)
635 (cdadr gnus-tree-brackets))
636 (adopted (cdr (nth 3 gnus-tree-brackets)))
637 (dummy
638 (cdaddr gnus-tree-brackets))
639 (t (cdar gnus-tree-brackets))))
640 (buffer-read-only nil)
641 beg end)
642 (gnus-add-text-properties
643 (setq beg (point))
644 (setq end (progn (eval gnus-tree-line-format-spec) (point)))
645 (list 'gnus-number gnus-tmp-number))
646 (when (or t (gnus-visual-p 'tree-highlight 'highlight))
647 (gnus-tree-highlight-node gnus-tmp-number beg end))))
648
c2e9e9ef
SM
649(defmacro gnus--let-eval (bindings evalsym &rest body)
650 "Build an environment in which to evaluate expressions.
651BINDINGS is a `let'-style list of bindings to use for the environment.
652EVALSYM is then bound in BODY to a function that takes a sexp and evaluates
653it in the environment specified by BINDINGS."
654 (declare (indent 2) (debug ((&rest (sym form)) sym body)))
655 (if (ignore-errors (let ((x 3)) (eq (eval '(- x 1) '((x . 4))) x)))
656 ;; Use lexical vars if possible.
657 `(let* ((env (list ,@(mapcar (lambda (binding)
658 `(cons ',(car binding) ,(cadr binding)))
659 bindings)))
660 (,evalsym (lambda (exp) (eval exp env))))
661 ,@body)
662 `(let (,@bindings (,evalsym #'eval)) ,@body)))
663
eec82323
LMI
664(defun gnus-tree-highlight-node (article beg end)
665 "Highlight current line according to `gnus-summary-highlight'."
666 (let ((list gnus-summary-highlight)
667 face)
67d0660b 668 (with-current-buffer gnus-summary-buffer
c2e9e9ef
SM
669 (let ((uncached (memq article gnus-newsgroup-undownloaded)))
670 (gnus--let-eval
671 ((score (or (cdr (assq article gnus-newsgroup-scored))
eec82323
LMI
672 gnus-summary-default-score 0))
673 (default gnus-summary-default-score)
23f87bed
MB
674 (default-high gnus-summary-default-high-score)
675 (default-low gnus-summary-default-low-score)
c2e9e9ef 676 (uncached uncached)
23f87bed 677 (downloaded (not uncached))
eec82323 678 (mark (or (gnus-summary-article-mark article) gnus-unread-mark)))
c2e9e9ef
SM
679 evalfun
680 ;; Eval the cars of the lists until we find a match.
681 (while (and list
682 (not (funcall evalfun (caar list))))
683 (setq list (cdr list))))))
cc21c235 684 (unless (eq (setq face (cdar list)) (gnus-get-text-property-excluding-characters-with-faces beg 'face))
1d43a35f
CY
685 (gnus-put-text-property-excluding-characters-with-faces
686 beg end 'face
687 (if (boundp face) (symbol-value face) face)))))
eec82323
LMI
688
689(defun gnus-tree-indent (level)
690 (insert (make-string (1- (* (1+ gnus-tree-node-length) level)) ? )))
691
692(defvar gnus-tmp-limit)
693(defvar gnus-tmp-sparse)
694(defvar gnus-tmp-indent)
695
696(defun gnus-generate-tree (thread)
697 "Generate a thread tree for THREAD."
67d0660b 698 (with-current-buffer (gnus-get-tree-buffer)
eec82323
LMI
699 (let ((buffer-read-only nil)
700 (gnus-tmp-indent 0))
701 (erase-buffer)
702 (funcall gnus-generate-tree-function thread 0)
703 (gnus-set-mode-line 'tree)
704 (goto-char (point-min))
705 (gnus-tree-minimize)
706 (gnus-tree-recenter)
707 (let ((selected (selected-window)))
23f87bed
MB
708 (when (gnus-get-buffer-window (set-buffer gnus-tree-buffer) t)
709 (select-window (gnus-get-buffer-window (set-buffer gnus-tree-buffer) t))
eec82323
LMI
710 (gnus-horizontal-recenter)
711 (select-window selected))))))
712
713(defun gnus-generate-horizontal-tree (thread level &optional dummyp adopted)
714 "Generate a horizontal tree."
715 (let* ((dummy (stringp (car thread)))
716 (do (or dummy
a8151ef7
LMI
717 (and (car thread)
718 (memq (mail-header-number (car thread))
719 gnus-tmp-limit))))
eec82323
LMI
720 col beg)
721 (if (not do)
722 ;; We don't want this article.
723 (setq thread (cdr thread))
724 (if (not (bolp))
725 ;; Not the first article on the line, so we insert a "-".
726 (insert (car gnus-tree-parent-child-edges))
727 ;; If the level isn't zero, then we insert some indentation.
728 (unless (zerop level)
729 (gnus-tree-indent level)
730 (insert (cadr gnus-tree-parent-child-edges))
01c52d31 731 (setq col (- (setq beg (point)) (point-at-bol) 1))
eec82323
LMI
732 ;; Draw "|" lines upwards.
733 (while (progn
734 (forward-line -1)
735 (forward-char col)
16409b0b 736 (eq (char-after) ? ))
eec82323
LMI
737 (delete-char 1)
738 (insert (caddr gnus-tree-parent-child-edges)))
739 (goto-char beg)))
740 (setq dummyp nil)
741 ;; Insert the article node.
742 (gnus-tree-node-insert (pop thread) gnus-tmp-sparse adopted))
743 (if (null thread)
744 ;; End of the thread, so we go to the next line.
745 (unless (bolp)
746 (insert "\n"))
747 ;; Recurse downwards in all children of this article.
748 (while thread
749 (gnus-generate-horizontal-tree
750 (pop thread) (if do (1+ level) level)
751 (or dummyp dummy) dummy)))))
752
753(defsubst gnus-tree-indent-vertical ()
754 (let ((len (- (* (1+ gnus-tree-node-length) gnus-tmp-indent)
01c52d31 755 (- (point) (point-at-bol)))))
eec82323
LMI
756 (when (> len 0)
757 (insert (make-string len ? )))))
758
759(defsubst gnus-tree-forward-line (n)
760 (while (>= (decf n) 0)
761 (unless (zerop (forward-line 1))
762 (end-of-line)
763 (insert "\n")))
764 (end-of-line))
765
766(defun gnus-generate-vertical-tree (thread level &optional dummyp adopted)
767 "Generate a vertical tree."
768 (let* ((dummy (stringp (car thread)))
769 (do (or dummy
770 (and (car thread)
771 (memq (mail-header-number (car thread))
772 gnus-tmp-limit))))
773 beg)
774 (if (not do)
775 ;; We don't want this article.
776 (setq thread (cdr thread))
777 (if (not (save-excursion (beginning-of-line) (bobp)))
778 ;; Not the first article on the line, so we insert a "-".
779 (progn
780 (gnus-tree-indent-vertical)
781 (insert (make-string (/ gnus-tree-node-length 2) ? ))
782 (insert (caddr gnus-tree-parent-child-edges))
783 (gnus-tree-forward-line 1))
784 ;; If the level isn't zero, then we insert some indentation.
785 (unless (zerop gnus-tmp-indent)
786 (gnus-tree-forward-line (1- (* 2 level)))
787 (gnus-tree-indent-vertical)
788 (delete-char -1)
789 (insert (cadr gnus-tree-parent-child-edges))
790 (setq beg (point))
a8151ef7 791 (forward-char -1)
eec82323 792 ;; Draw "-" lines leftwards.
9b40100a 793 (while (and (not (bobp))
16409b0b 794 (eq (char-after (1- (point))) ? ))
a8151ef7
LMI
795 (delete-char -1)
796 (insert (car gnus-tree-parent-child-edges))
797 (forward-char -1))
eec82323
LMI
798 (goto-char beg)
799 (gnus-tree-forward-line 1)))
800 (setq dummyp nil)
801 ;; Insert the article node.
802 (gnus-tree-indent-vertical)
803 (gnus-tree-node-insert (pop thread) gnus-tmp-sparse adopted)
804 (gnus-tree-forward-line 1))
805 (if (null thread)
806 ;; End of the thread, so we go to the next line.
807 (progn
808 (goto-char (point-min))
809 (end-of-line)
810 (incf gnus-tmp-indent))
811 ;; Recurse downwards in all children of this article.
812 (while thread
813 (gnus-generate-vertical-tree
814 (pop thread) (if do (1+ level) level)
815 (or dummyp dummy) dummy)))))
816
817;;; Interface functions.
818
819(defun gnus-possibly-generate-tree (article &optional force)
820 "Generate the thread tree for ARTICLE if it isn't displayed already."
67d0660b 821 (when (with-current-buffer gnus-summary-buffer
eec82323
LMI
822 (and gnus-use-trees
823 gnus-show-threads
824 (vectorp (gnus-summary-article-header article))))
825 (save-excursion
67d0660b 826 (let ((top (with-current-buffer gnus-summary-buffer
eec82323
LMI
827 (gnus-cut-thread
828 (gnus-remove-thread
829 (mail-header-id
830 (gnus-summary-article-header article))
831 t))))
832 (gnus-tmp-limit gnus-newsgroup-limit)
833 (gnus-tmp-sparse gnus-newsgroup-sparse))
834 (when (or force
835 (not (eq top gnus-tree-displayed-thread)))
836 (gnus-generate-tree top)
837 (setq gnus-tree-displayed-thread top))))))
838
c2e9e9ef 839(defun gnus-tree-open ()
eec82323
LMI
840 (gnus-get-tree-buffer))
841
c2e9e9ef 842(defun gnus-tree-close ()
6748645f 843 (gnus-kill-buffer gnus-tree-buffer))
eec82323 844
23f87bed
MB
845(defun gnus-tree-perhaps-minimize ()
846 (when (and gnus-tree-minimize-window
847 (get-buffer gnus-tree-buffer))
67d0660b 848 (with-current-buffer gnus-tree-buffer
23f87bed
MB
849 (gnus-tree-minimize))))
850
eec82323
LMI
851(defun gnus-highlight-selected-tree (article)
852 "Highlight the selected article in the tree."
cc21c235
G
853 (when (buffer-live-p gnus-tree-buffer)
854 (let ((buf (current-buffer))
855 region)
856 (set-buffer gnus-tree-buffer)
857 (when (setq region (gnus-tree-article-region article))
858 (when (or (not gnus-selected-tree-overlay)
859 (gnus-extent-detached-p gnus-selected-tree-overlay))
860 ;; Create a new overlay.
861 (gnus-overlay-put
862 (setq gnus-selected-tree-overlay
863 (gnus-make-overlay (point-min) (1+ (point-min))))
864 'face gnus-selected-tree-face))
865 ;; Move the overlay to the article.
866 (gnus-move-overlay
867 gnus-selected-tree-overlay (goto-char (car region)) (cdr region))
868 (gnus-tree-minimize)
869 (gnus-tree-recenter)
870 (let ((selected (selected-window)))
871 (when (gnus-get-buffer-window (set-buffer gnus-tree-buffer) t)
872 (select-window
873 (gnus-get-buffer-window (set-buffer gnus-tree-buffer) t))
874 (gnus-horizontal-recenter)
875 (select-window selected))))
876 ;; If we remove this save-excursion, it updates the wrong mode lines?!?
877 (with-current-buffer gnus-tree-buffer
878 (gnus-set-mode-line 'tree))
879 (set-buffer buf))))
eec82323
LMI
880
881(defun gnus-tree-highlight-article (article face)
4e2b87d8
G
882 ;; The save-excursion here is apparently necessary because
883 ;; `set-window-point' somehow manages to alter the buffer position.
884 (save-excursion
885 (with-current-buffer (gnus-get-tree-buffer)
886 (let (region)
887 (when (setq region (gnus-tree-article-region article))
888 (gnus-put-text-property (car region) (cdr region) 'face face)
889 (set-window-point
890 (gnus-get-buffer-window (current-buffer) t) (cdr region)))))))
eec82323 891
eec82323
LMI
892;;; Allow redefinition of functions.
893(gnus-ems-redefine)
894
895(provide 'gnus-salt)
896
897;;; gnus-salt.el ends here