lisp/ibuf-ext.el (ibuffer-switch-to-saved-filter-groups): Shortcut completing-read...
[bpt/emacs.git] / lisp / proced.el
CommitLineData
e6854b3f 1;;; proced.el --- operate on system processes like dired
37e4d8ed 2
acaf905b 3;; Copyright (C) 2008-2012 Free Software Foundation, Inc.
1ba1a8b9 4
1e05f387 5;; Author: Roland Winkler <winkler@gnu.org>
37e4d8ed
RW
6;; Keywords: Processes, Unix
7
1ba1a8b9
RW
8;; This file is part of GNU Emacs.
9
eb3fa2cf 10;; GNU Emacs is free software: you can redistribute it and/or modify
1ba1a8b9 11;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
37e4d8ed 14
1ba1a8b9
RW
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.
37e4d8ed
RW
19
20;; You should have received a copy of the GNU General Public License
eb3fa2cf 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
37e4d8ed
RW
22
23;;; Commentary:
24
d74d0c42
RW
25;; Proced makes an Emacs buffer containing a listing of the current
26;; system processes. You can use the normal Emacs commands to move around
27;; in this buffer, and special Proced commands to operate on the processes
da643190 28;; listed. See `proced-mode' for getting started.
37e4d8ed 29;;
e6854b3f 30;; To do:
da643190 31;; - interactive temporary customizability of flags in `proced-grammar-alist'
9f583d14 32;; - allow "sudo kill PID", "renice PID"
da643190 33;;
413e65fe 34;; Thoughts and Ideas
a20878b6 35;; - Currently, `process-attributes' returns the list of
413e65fe
RW
36;; command-line arguments of a process as one concatenated string.
37;; This format is compatible with `shell-command'. Also, under
38;; MS-Windows, the command-line arguments are actually stored as a
39;; single string, so that it is impossible to reverse-engineer it back
a20878b6 40;; into separate arguments. Alternatively, `process-attributes'
413e65fe
RW
41;; could (try to) return a list of strings that correspond to individual
42;; command-line arguments. Then one could feed such a list of
43;; command-line arguments into `call-process' or `start-process'.
44;; Are there real-world applications when such a feature would be useful?
45;; What about something like `proced-restart-pid'?
37e4d8ed
RW
46
47;;; Code:
48
d74d0c42
RW
49(require 'time-date) ; for `with-decoded-time-value'
50
37e4d8ed
RW
51(defgroup proced nil
52 "Proced mode."
53 :group 'processes
54 :group 'unix
55 :prefix "proced-")
56
e6854b3f
RW
57(defcustom proced-signal-function 'signal-process
58 "Name of signal function.
59It can be an elisp function (usually `signal-process') or a string specifying
60the external command (usually \"kill\")."
37e4d8ed 61 :group 'proced
e6854b3f
RW
62 :type '(choice (function :tag "function")
63 (string :tag "command")))
37e4d8ed
RW
64
65(defcustom proced-signal-list
d74d0c42 66 '( ;; signals supported on all POSIX compliant systems
9a9af856
SM
67 ("HUP" . " (1. Hangup)")
68 ("INT" . " (2. Terminal interrupt)")
69 ("QUIT" . " (3. Terminal quit)")
70 ("ABRT" . " (6. Process abort)")
71 ("KILL" . " (9. Kill - cannot be caught or ignored)")
72 ("ALRM" . " (14. Alarm Clock)")
73 ("TERM" . " (15. Termination)")
9f583d14
RW
74 ;; POSIX 1003.1-2001
75 ;; Which systems do not support these signals so that we can
76 ;; exclude them from `proced-signal-list'?
9a9af856
SM
77 ("CONT" . " (Continue executing)")
78 ("STOP" . " (Stop executing / pause - cannot be caught or ignored)")
79 ("TSTP" . " (Terminal stop / pause)"))
37e4d8ed
RW
80 "List of signals, used for minibuffer completion."
81 :group 'proced
9a9af856
SM
82 :type '(repeat (cons (string :tag "signal name")
83 (string :tag "description"))))
37e4d8ed 84
d74d0c42
RW
85;; For which attributes can we use a fixed width of the output field?
86;; A fixed width speeds up formatting, yet it can make
87;; `proced-grammar-alist' system-dependent.
88;; (If proced runs like top(1) we want it to be fast.)
89;;
90;; If it is impossible / unlikely that an attribute has the same value
91;; for two processes, then sorting can be based on one ordinary (fast)
92;; predicate like `<'. Otherwise, a list of proced predicates can be used
93;; to refine the sort.
94;;
95;; It would be neat if one could temporarily override the following
96;; predefined rules.
204ebc5b 97(defcustom proced-grammar-alist
a20878b6 98 '( ;; attributes defined in `process-attributes'
d74d0c42 99 (euid "EUID" "%d" right proced-< nil (euid pid) (nil t nil))
9a9af856 100 (user "User" nil left proced-string-lessp nil (user pid) (nil t nil))
d74d0c42 101 (egid "EGID" "%d" right proced-< nil (egid euid pid) (nil t nil))
9a9af856
SM
102 (group "Group" nil left proced-string-lessp nil (group user pid) (nil t nil))
103 (comm "Command" nil left proced-string-lessp nil (comm pid) (nil t nil))
104 (state "Stat" nil left proced-string-lessp nil (state pid) (nil t nil))
b4f671ce 105 (ppid "PPID" "%d" right proced-< nil (ppid pid)
3ac09bb4 106 ((lambda (ppid) (proced-filter-parents proced-process-alist ppid))
b4f671ce 107 "refine to process parents"))
9a9af856
SM
108 (pgrp "PGrp" "%d" right proced-< nil (pgrp euid pid) (nil t nil))
109 (sess "Sess" "%d" right proced-< nil (sess pid) (nil t nil))
d74d0c42
RW
110 (ttname "TTY" proced-format-ttname left proced-string-lessp nil (ttname pid) (nil t nil))
111 (tpgid "TPGID" "%d" right proced-< nil (tpgid pid) (nil t nil))
9a9af856
SM
112 (minflt "MinFlt" "%d" right proced-< nil (minflt pid) (nil t t))
113 (majflt "MajFlt" "%d" right proced-< nil (majflt pid) (nil t t))
114 (cminflt "CMinFlt" "%d" right proced-< nil (cminflt pid) (nil t t))
115 (cmajflt "CMajFlt" "%d" right proced-< nil (cmajflt pid) (nil t t))
116 (utime "UTime" proced-format-time right proced-time-lessp t (utime pid) (nil t t))
117 (stime "STime" proced-format-time right proced-time-lessp t (stime pid) (nil t t))
118 (time "Time" proced-format-time right proced-time-lessp t (time pid) (nil t t))
119 (cutime "CUTime" proced-format-time right proced-time-lessp t (cutime pid) (nil t t))
120 (cstime "CSTime" proced-format-time right proced-time-lessp t (cstime pid) (nil t t))
121 (ctime "CTime" proced-format-time right proced-time-lessp t (ctime pid) (nil t t))
122 (pri "Pr" "%d" right proced-< t (pri pid) (nil t t))
123 (nice "Ni" "%3d" 3 proced-< t (nice pid) (t t nil))
124 (thcount "THCount" "%d" right proced-< t (thcount pid) (nil t t))
125 (start "Start" proced-format-start 6 proced-time-lessp nil (start pid) (t t nil))
126 (vsize "VSize" "%d" right proced-< t (vsize pid) (nil t t))
d74d0c42 127 (rss "RSS" "%d" right proced-< t (rss pid) (nil t t))
9a9af856 128 (etime "ETime" proced-format-time right proced-time-lessp t (etime pid) (nil t t))
d74d0c42 129 (pcpu "%CPU" "%.1f" right proced-< t (pcpu pid) (nil t t))
9a9af856
SM
130 (pmem "%Mem" "%.1f" right proced-< t (pmem pid) (nil t t))
131 (args "Args" proced-format-args left proced-string-lessp nil (args pid) (nil t nil))
d74d0c42
RW
132 ;;
133 ;; attributes defined by proced (see `proced-process-attributes')
b4f671ce 134 (pid "PID" "%d" right proced-< nil (pid)
3ac09bb4 135 ((lambda (ppid) (proced-filter-children proced-process-alist ppid))
b4f671ce 136 "refine to process children"))
f1d27653 137 ;; process tree
9a9af856 138 (tree "Tree" proced-format-tree left nil nil nil nil))
d74d0c42
RW
139 "Alist of rules for handling Proced attributes.
140
141Each element has the form
142
b4f671ce 143 (KEY NAME FORMAT JUSTIFY PREDICATE REVERSE SORT-SCHEME REFINER).
d74d0c42 144
204ebc5b 145Symbol KEY is the car of a process attribute.
d74d0c42 146
204ebc5b 147String NAME appears in the header line.
d74d0c42 148
d0482e4e
JB
149FORMAT specifies the format for displaying the attribute values. It can
150be a string passed to `format'. It can be a function called with one
151argument, the value of the attribute. The value nil means take as is.
d74d0c42
RW
152
153If JUSTIFY is an integer, its modulus gives the width of the attribute
da643190 154values formatted with FORMAT. If JUSTIFY is positive, NAME appears
d74d0c42
RW
155right-justified, otherwise it appears left-justified. If JUSTIFY is 'left
156or 'right, the field width is calculated from all field values in the listing.
157If JUSTIFY is 'left, the field values are formatted left-justified and
158right-justified otherwise.
159
160PREDICATE is the predicate for sorting and filtering the process listing
161based on attribute KEY. PREDICATE takes two arguments P1 and P2,
162the corresponding attribute values of two processes. PREDICATE should
163return 'equal if P1 has same rank like P2. Any other non-nil value says
164that P1 is \"less than\" P2, or nil if not.
f1d27653 165If PREDICATE is nil the attribute cannot be sorted.
d74d0c42 166
b4f671ce
RW
167PREDICATE defines an ascending sort order. REVERSE is non-nil if the sort
168order is descending.
d74d0c42 169
da643190 170SORT-SCHEME is a list (KEY1 KEY2 ...) defining a hierarchy of rules
d74d0c42
RW
171for sorting the process listing. KEY1, KEY2, ... are KEYs appearing as cars
172of `proced-grammar-alist'. First the PREDICATE of KEY1 is evaluated.
da643190 173If it yields non-equal, it defines the sort order for the corresponding
d74d0c42
RW
174processes. If it evaluates to 'equal the PREDICATE of KEY2 is evaluated, etc.
175
b4f671ce 176REFINER can be a list of flags (LESS-B EQUAL-B LARGER-B) used by the command
da643190
RW
177`proced-refine' (see there) to refine the listing based on attribute KEY.
178This command compares the value of attribute KEY of every process with
179the value of attribute KEY of the process at the position of point
180using PREDICATE.
d74d0c42
RW
181If PREDICATE yields non-nil, the process is accepted if LESS-B is non-nil.
182If PREDICATE yields 'equal, the process is accepted if EQUAL-B is non-nil.
b4f671ce
RW
183If PREDICATE yields nil, the process is accepted if LARGER-B is non-nil.
184
3ac09bb4 185REFINER can also be a list (FUNCTION HELP-ECHO).
b4f671ce
RW
186FUNCTION is called with one argument, the PID of the process at the position
187of point. The function must return a list of PIDs that is used for the refined
188listing. HELP-ECHO is a string that is shown when mouse is over this field.
189
190If REFINER is nil no refinement is done."
204ebc5b
RW
191 :group 'proced
192 :type '(repeat (list :tag "Attribute"
193 (symbol :tag "Key")
194 (string :tag "Header")
195 (choice :tag "Format"
196 (const :tag "None" nil)
197 (string :tag "Format String")
198 (function :tag "Formatting Function"))
199 (choice :tag "Justification"
200 (const :tag "left" left)
201 (const :tag "right" right)
202 (integer :tag "width"))
f1d27653
RW
203 (choice :tag "Predicate"
204 (const :tag "None" nil)
205 (function :tag "Function"))
b4f671ce 206 (boolean :tag "Descending Sort Order")
204ebc5b 207 (repeat :tag "Sort Scheme" (symbol :tag "Key"))
b4f671ce 208 (choice :tag "Refiner"
f1d27653 209 (const :tag "None" nil)
3ac09bb4
RW
210 (list (function :tag "Refinement Function")
211 (string :tag "Help echo"))
b4f671ce
RW
212 (list :tag "Refine Flags"
213 (boolean :tag "Less")
214 (boolean :tag "Equal")
3ac09bb4 215 (boolean :tag "Larger"))))))
204ebc5b
RW
216
217(defcustom proced-custom-attributes nil
d74d0c42
RW
218 "List of functions defining custom attributes.
219This variable extends the functionality of `proced-process-attributes'.
220Each function is called with one argument, the list of attributes
221of a system process. It returns a cons cell of the form (KEY . VALUE)
a20878b6 222like `process-attributes'. This cons cell is appended to the list
204ebc5b
RW
223returned by `proced-process-attributes'.
224If the function returns nil, the value is ignored."
225 :group 'proced
226 :type '(repeat (function :tag "Attribute")))
d74d0c42
RW
227
228;; Formatting and sorting rules are defined "per attribute". If formatting
229;; and / or sorting should use more than one attribute, it appears more
230;; transparent to define a new derived attribute, so that formatting and
231;; sorting can use them consistently. (Are there exceptions to this rule?
232;; Would it be advantageous to have yet more general methods available?)
233;; Sorting can also be based on attributes that are invisible in the listing.
234
204ebc5b 235(defcustom proced-format-alist
f1d27653
RW
236 '((short user pid tree pcpu pmem start time (args comm))
237 (medium user pid tree pcpu pmem vsize rss ttname state start time (args comm))
238 (long user euid group pid tree pri nice pcpu pmem vsize rss ttname state
b4f671ce 239 start time (args comm))
f1d27653 240 (verbose user euid group egid pid ppid tree pgrp sess pri nice pcpu pmem
d74d0c42 241 state thcount vsize rss ttname tpgid minflt majflt cminflt cmajflt
b4f671ce 242 start time utime stime ctime cutime cstime etime (args comm)))
d74d0c42
RW
243 "Alist of formats of listing.
244The car of each element is a symbol, the name of the format.
b4f671ce
RW
245The cdr is a list of attribute keys appearing in `proced-grammar-alist'.
246An element of this list may also be a list of attribute keys that specifies
247alternatives. If the first attribute is absent for a process, use the second
248one, etc."
204ebc5b
RW
249 :group 'proced
250 :type '(alist :key-type (symbol :tag "Format Name")
b4f671ce
RW
251 :value-type (repeat :tag "Keys"
252 (choice (symbol :tag "")
253 (repeat :tag "Alternative Keys"
254 (symbol :tag ""))))))
d74d0c42 255
204ebc5b 256(defcustom proced-format 'short
d74d0c42
RW
257 "Current format of Proced listing.
258It can be the car of an element of `proced-format-alist'.
204ebc5b
RW
259It can also be a list of keys appearing in `proced-grammar-alist'."
260 :group 'proced
261 :type '(choice (symbol :tag "Format Name")
262 (repeat :tag "Keys" (symbol :tag ""))))
d74d0c42
RW
263(make-variable-buffer-local 'proced-format)
264
265;; FIXME: is there a better name for filter `user' that does not coincide
266;; with an attribute key?
204ebc5b 267(defcustom proced-filter-alist
0d4dc442
RW
268 `((user (user . ,(concat "\\`" (regexp-quote (user-real-login-name)) "\\'")))
269 (user-running (user . ,(concat "\\`" (regexp-quote (user-real-login-name)) "\\'"))
d74d0c42
RW
270 (state . "\\`[Rr]\\'"))
271 (all)
272 (all-running (state . "\\`[Rr]\\'"))
273 (emacs (fun-all . (lambda (list)
274 (proced-filter-children list ,(emacs-pid))))))
275 "Alist of process filters.
276The car of each element is a symbol, the name of the filter.
277The cdr is a list of elementary filters that are applied to every process.
278A process is displayed if it passes all elementary filters of a selected
279filter.
280
281An elementary filter can be one of the following:
282\(KEY . REGEXP) If value of attribute KEY matches REGEXP,
283 accept this process.
284\(KEY . FUN) Apply function FUN to attribute KEY. Accept this process,
285 if FUN returns non-nil.
286\(function . FUN) For each process, apply function FUN to list of attributes
287 of each. Accept the process if FUN returns non-nil.
288\(fun-all . FUN) Apply function FUN to entire process list.
204ebc5b
RW
289 FUN must return the filtered list."
290 :group 'proced
291 :type '(repeat (cons :tag "Filter"
292 (symbol :tag "Filter Name")
293 (repeat :tag "Filters"
294 (choice (cons :tag "Key . Regexp" (symbol :tag "Key") regexp)
295 (cons :tag "Key . Function" (symbol :tag "Key") function)
296 (cons :tag "Function" (const :tag "Key: function" function) function)
297 (cons :tag "Fun-all" (const :tag "Key: fun-all" fun-all) function))))))
298
299(defcustom proced-filter 'user
d74d0c42
RW
300 "Current filter of proced listing.
301It can be the car of an element of `proced-filter-alist'.
302It can also be a list of elementary filters as in the cdrs of the elements
204ebc5b
RW
303of `proced-filter-alist'."
304 :group 'proced
305 :type '(choice (symbol :tag "Filter Name")
306 (repeat :tag "Filters"
307 (choice (cons :tag "Key . Regexp" (symbol :tag "Key") regexp)
308 (cons :tag "Key . Function" (symbol :tag "Key") function)
309 (cons :tag "Function" (const :tag "Key: function" function) function)
310 (cons :tag "Fun-all" (const :tag "Key: fun-all" fun-all) function)))))
d74d0c42
RW
311(make-variable-buffer-local 'proced-filter)
312
204ebc5b 313(defcustom proced-sort 'pcpu
da643190 314 "Current sort scheme for proced listing.
d74d0c42
RW
315It must be the KEY of an element of `proced-grammar-alist'.
316It can also be a list of KEYs as in the SORT-SCHEMEs of the elements
204ebc5b
RW
317of `proced-grammar-alist'."
318 :group 'proced
319 :type '(choice (symbol :tag "Sort Scheme")
320 (repeat :tag "Key List" (symbol :tag "Key"))))
f67cf064 321(make-variable-buffer-local 'proced-sort)
d74d0c42 322
b4f671ce
RW
323(defcustom proced-descend t
324 "Non-nil if proced listing is sorted in descending order."
325 :group 'proced
326 :type '(boolean :tag "Descending Sort Order"))
327(make-variable-buffer-local 'proced-descend)
328
d74d0c42
RW
329(defcustom proced-goal-attribute 'args
330 "If non-nil, key of the attribute that defines the `goal-column'."
331 :group 'proced
332 :type '(choice (const :tag "none" nil)
333 (symbol :tag "key")))
334
413e65fe 335(defcustom proced-auto-update-interval 5
204ebc5b 336 "Time interval in seconds for auto updating Proced buffers."
d74d0c42
RW
337 :group 'proced
338 :type 'integer)
339
413e65fe 340(defcustom proced-auto-update-flag nil
da643190 341 "Non-nil for auto update of a Proced buffer.
413e65fe 342Can be changed interactively via `proced-toggle-auto-update'."
d74d0c42
RW
343 :group 'proced
344 :type 'boolean)
413e65fe 345(make-variable-buffer-local 'proced-auto-update-flag)
d74d0c42 346
f1d27653 347(defcustom proced-tree-flag nil
744c85fb 348 "Non-nil for display of Proced buffer as process tree."
f1d27653
RW
349 :group 'proced
350 :type 'boolean)
351(make-variable-buffer-local 'proced-tree-flag)
352
3ac09bb4
RW
353(defcustom proced-post-display-hook nil
354 "Normal hook run after displaying or updating a Proced buffer.
355May be used to adapt the window size via `fit-window-to-buffer'."
356 :type 'hook
357 :options '(fit-window-to-buffer)
358 :group 'proced)
359
016151bb
RW
360(defcustom proced-after-send-signal-hook nil
361 "Normal hook run after sending a signal to processes by `proced-send-signal'.
362May be used to revert the process listing."
363 :type 'hook
364 :options '(proced-revert)
365 :group 'proced)
366
92d9ce48 367;; Internal variables
d74d0c42 368
4ed46aef
RW
369(defvar proced-available (not (null (list-system-processes)))
370 "Non-nil means Proced is known to work on this system.")
371
d74d0c42 372(defvar proced-process-alist nil
da643190
RW
373 "Alist of processes displayed by Proced.
374The car of each element is the PID, and the cdr is a list of
375cons pairs, see `proced-process-attributes'.")
d74d0c42
RW
376(make-variable-buffer-local 'proced-process-alist)
377
378(defvar proced-sort-internal nil
b4f671ce
RW
379 "Sort scheme for listing (internal format).
380It is a list of lists (KEY PREDICATE REVERSE).")
d74d0c42 381
37e4d8ed 382(defvar proced-marker-char ?* ; the answer is 42
d0482e4e 383 "In Proced, the current mark character.")
37e4d8ed 384
aa5fecb5
RW
385;; Faces and font-lock code taken from dired,
386;; but face variables are deprecated for new code.
37e4d8ed
RW
387(defgroup proced-faces nil
388 "Faces used by Proced."
389 :group 'proced
390 :group 'faces)
391
37e4d8ed
RW
392(defface proced-mark
393 '((t (:inherit font-lock-constant-face)))
d0482e4e 394 "Face used for Proced marks."
37e4d8ed 395 :group 'proced-faces)
37e4d8ed
RW
396
397(defface proced-marked
f22f4808 398 '((t (:inherit error)))
37e4d8ed
RW
399 "Face used for marked processes."
400 :group 'proced-faces)
37e4d8ed 401
da643190
RW
402(defface proced-sort-header
403 '((t (:inherit font-lock-keyword-face)))
404 "Face used for header of attribute used for sorting."
405 :group 'proced-faces)
da643190 406
37e4d8ed
RW
407(defvar proced-re-mark "^[^ \n]"
408 "Regexp matching a marked line.
409Important: the match ends just after the marker.")
410
d74d0c42
RW
411(defvar proced-header-line nil
412 "Headers in Proced buffer as a string.")
413(make-variable-buffer-local 'proced-header-line)
414
48152a70
RW
415(defvar proced-temp-alist nil
416 "Temporary alist (internal variable).")
f1d27653 417
d74d0c42 418(defvar proced-process-tree nil
f1d27653
RW
419 "Proced process tree (internal variable).")
420
3ac09bb4
RW
421(defvar proced-tree-depth nil
422 "Internal variable for depth of Proced process tree.")
d74d0c42 423
413e65fe
RW
424(defvar proced-auto-update-timer nil
425 "Stores if Proced auto update timer is already installed.")
d74d0c42 426
204ebc5b
RW
427(defvar proced-log-buffer "*Proced log*"
428 "Name of Proced Log buffer.")
429
d74d0c42
RW
430(defconst proced-help-string
431 "(n)ext, (p)revious, (m)ark, (u)nmark, (k)ill, (q)uit (type ? for more help)"
d0482e4e 432 "Help string for Proced.")
d74d0c42
RW
433
434(defconst proced-header-help-echo
da643190 435 "mouse-1, mouse-2: sort by attribute %s%s (%s)"
d74d0c42
RW
436 "Help string shown when mouse is over a sortable header.")
437
438(defconst proced-field-help-echo
da643190
RW
439 "mouse-2, RET: refine by attribute %s %s"
440 "Help string shown when mouse is over a refinable field.")
37e4d8ed
RW
441
442(defvar proced-font-lock-keywords
aa5fecb5
RW
443 `(;; (Any) proced marks.
444 (,proced-re-mark . 'proced-mark)
445 ;; Processes marked with `proced-marker-char'
446 ;; Should we make sure that only certain attributes are font-locked?
447 (,(concat "^[" (char-to-string proced-marker-char) "]")
448 ".+" (proced-move-to-goal-column) nil (0 'proced-marked))))
37e4d8ed
RW
449
450(defvar proced-mode-map
451 (let ((km (make-sparse-keymap)))
92d9ce48 452 ;; moving
5adfa483 453 (define-key km " " 'next-line)
d74d0c42
RW
454 (define-key km "n" 'next-line)
455 (define-key km "p" 'previous-line)
456 (define-key km "\C-n" 'next-line)
457 (define-key km "\C-p" 'previous-line)
458 (define-key km "\C-?" 'previous-line)
459 (define-key km [down] 'next-line)
460 (define-key km [up] 'previous-line)
92d9ce48 461 ;; marking
d6549da4 462 (define-key km "d" 'proced-mark) ; Dired compatibility ("delete")
37e4d8ed 463 (define-key km "m" 'proced-mark)
2efb64a8 464 (put 'proced-mark :advertised-binding "m")
37e4d8ed 465 (define-key km "u" 'proced-unmark)
e6854b3f 466 (define-key km "\177" 'proced-unmark-backward)
92d9ce48 467 (define-key km "M" 'proced-mark-all)
37e4d8ed 468 (define-key km "U" 'proced-unmark-all)
e6854b3f 469 (define-key km "t" 'proced-toggle-marks)
d74d0c42
RW
470 (define-key km "C" 'proced-mark-children)
471 (define-key km "P" 'proced-mark-parents)
472 ;; filtering
473 (define-key km "f" 'proced-filter-interactive)
da643190
RW
474 (define-key km [mouse-2] 'proced-refine)
475 (define-key km "\C-m" 'proced-refine)
92d9ce48 476 ;; sorting
61548252
RW
477 (define-key km "sc" 'proced-sort-pcpu)
478 (define-key km "sm" 'proced-sort-pmem)
479 (define-key km "sp" 'proced-sort-pid)
480 (define-key km "ss" 'proced-sort-start)
d74d0c42 481 (define-key km "sS" 'proced-sort-interactive)
61548252 482 (define-key km "st" 'proced-sort-time)
d74d0c42 483 (define-key km "su" 'proced-sort-user)
da643190
RW
484 ;; similar to `Buffer-menu-sort-by-column'
485 (define-key km [header-line mouse-1] 'proced-sort-header)
d74d0c42 486 (define-key km [header-line mouse-2] 'proced-sort-header)
f1d27653 487 (define-key km "T" 'proced-toggle-tree)
d74d0c42
RW
488 ;; formatting
489 (define-key km "F" 'proced-format-interactive)
92d9ce48 490 ;; operate
9f583d14 491 (define-key km "o" 'proced-omit-processes)
92d9ce48
RW
492 (define-key km "x" 'proced-send-signal) ; Dired compatibility
493 (define-key km "k" 'proced-send-signal) ; kill processes
494 ;; misc
92d9ce48
RW
495 (define-key km "h" 'describe-mode)
496 (define-key km "?" 'proced-help)
37e4d8ed
RW
497 (define-key km [remap undo] 'proced-undo)
498 (define-key km [remap advertised-undo] 'proced-undo)
3ac09bb4 499 ;; Additional keybindings are inherited from `special-mode-map'
37e4d8ed 500 km)
d0482e4e 501 "Keymap for Proced commands.")
37e4d8ed
RW
502
503(easy-menu-define
504 proced-menu proced-mode-map "Proced Menu"
9f583d14
RW
505 `("Proced"
506 ["Mark" proced-mark
507 :help "Mark Current Process"]
508 ["Unmark" proced-unmark
509 :help "Unmark Current Process"]
510 ["Mark All" proced-mark-all
511 :help "Mark All Processes"]
512 ["Unmark All" proced-unmark-all
513 :help "Unmark All Process"]
514 ["Toggle Marks" proced-toggle-marks
515 :help "Marked Processes Become Unmarked, and Vice Versa"]
d74d0c42
RW
516 ["Mark Children" proced-mark-children
517 :help "Mark Current Process and its Children"]
518 ["Mark Parents" proced-mark-parents
519 :help "Mark Current Process and its Parents"]
e6854b3f 520 "--"
d74d0c42
RW
521 ("Filters"
522 :help "Select Filter for Process Listing"
523 ,@(mapcar (lambda (el)
524 (let ((filter (car el)))
525 `[,(symbol-name filter)
526 (proced-filter-interactive ',filter)
527 :style radio
528 :selected (eq proced-filter ',filter)]))
529 proced-filter-alist))
530 ("Sorting"
da643190 531 :help "Select Sort Scheme"
d74d0c42
RW
532 ["Sort..." proced-sort-interactive
533 :help "Sort Process List"]
534 "--"
535 ["Sort by %CPU" proced-sort-pcpu]
536 ["Sort by %MEM" proced-sort-pmem]
537 ["Sort by PID" proced-sort-pid]
538 ["Sort by START" proced-sort-start]
539 ["Sort by TIME" proced-sort-time]
540 ["Sort by USER" proced-sort-user])
541 ("Formats"
542 :help "Select Format for Process Listing"
543 ,@(mapcar (lambda (el)
544 (let ((format (car el)))
545 `[,(symbol-name format)
546 (proced-format-interactive ',format)
547 :style radio
548 :selected (eq proced-format ',format)]))
549 proced-format-alist))
f1d27653
RW
550 ["Tree Display" proced-toggle-tree
551 :style toggle
552 :selected (eval proced-tree-flag)
553 :help "Display Proced Buffer as Process Tree"]
61548252 554 "--"
9f583d14
RW
555 ["Omit Marked Processes" proced-omit-processes
556 :help "Omit Marked Processes in Process Listing."]
37e4d8ed 557 "--"
9f583d14
RW
558 ["Revert" revert-buffer
559 :help "Revert Process Listing"]
413e65fe 560 ["Auto Update" proced-toggle-auto-update
b4f671ce 561 :style toggle
413e65fe 562 :selected (eval proced-auto-update-flag)
da643190 563 :help "Auto Update of Proced Buffer"]
9f583d14 564 ["Send signal" proced-send-signal
d74d0c42 565 :help "Send Signal to Marked Processes"]))
9f583d14 566
92d9ce48 567;; helper functions
e6854b3f 568(defun proced-marker-regexp ()
61548252 569 "Return regexp matching `proced-marker-char'."
92d9ce48 570 ;; `proced-marker-char' must appear in column zero
e6854b3f
RW
571 (concat "^" (regexp-quote (char-to-string proced-marker-char))))
572
573(defun proced-success-message (action count)
61548252 574 "Display success message for ACTION performed for COUNT processes."
e6854b3f
RW
575 (message "%s %s process%s" action count (if (= 1 count) "" "es")))
576
d74d0c42
RW
577;; Unlike dired, we do not define our own commands for vertical motion.
578;; If `goal-column' is set, `next-line' and `previous-line' are fancy
579;; commands to satisfy our modest needs. If `proced-goal-attribute'
580;; and/or `goal-column' are not set, `next-line' and `previous-line'
581;; are really what we need to preserve the column of point.
582;; We use `proced-move-to-goal-column' for "non-interactive" cases only
583;; to get a well-defined position of point.
584
92d9ce48 585(defun proced-move-to-goal-column ()
da643190 586 "Move to `goal-column' if non-nil. Return position of point."
e6854b3f 587 (beginning-of-line)
d74d0c42
RW
588 (unless (eobp)
589 (if goal-column
590 (forward-char goal-column)
da643190
RW
591 (forward-char 2)))
592 (point))
d74d0c42
RW
593
594(defun proced-header-line ()
595 "Return header line for Proced buffer."
596 (list (propertize " " 'display '(space :align-to 0))
016151bb
RW
597 (if (<= (window-hscroll) (length proced-header-line))
598 (replace-regexp-in-string ;; preserve text properties
599 "\\(%\\)" "\\1\\1"
600 (substring proced-header-line (window-hscroll))))))
d74d0c42
RW
601
602(defun proced-pid-at-point ()
603 "Return pid of system process at point.
604Return nil if point is not on a process line."
605 (save-excursion
606 (beginning-of-line)
607 (if (looking-at "^. .")
608 (get-text-property (match-end 0) 'proced-pid))))
609
610;; proced mode
e6854b3f 611
3ac09bb4 612(define-derived-mode proced-mode special-mode "Proced"
943c8b75 613 "Mode for displaying system processes and sending signals to them.
204ebc5b
RW
614Type \\[proced] to start a Proced session. In a Proced buffer
615type \\<proced-mode-map>\\[proced-mark] to mark a process for later commands.
079ba9b7
SM
616Type \\[proced-send-signal] to send signals to marked processes.
617
da643190
RW
618The initial content of a listing is defined by the variable `proced-filter'
619and the variable `proced-format'.
620The variable `proced-filter' specifies which system processes are displayed.
621The variable `proced-format' specifies which attributes are displayed for
622each process. Type \\[proced-filter-interactive] and \\[proced-format-interactive]
623to change the values of `proced-filter' and `proced-format'.
624The current value of the variable `proced-filter' is indicated in the
625mode line.
626
627The sort order of Proced listings is defined by the variable `proced-sort'.
628Type \\[proced-sort-interactive] or click on a header in the header line
629to change the sort scheme. The current sort scheme is indicated in the
630mode line, using \"+\" or \"-\" for ascending or descending sort order.
631
f1d27653
RW
632Type \\[proced-toggle-tree] to toggle whether the listing is
633displayed as process tree.
634
4ed46aef 635An existing Proced listing can be refined by typing \\[proced-refine].
da643190
RW
636Refining an existing listing does not update the variable `proced-filter'.
637
638The attribute-specific rules for formatting, filtering, sorting, and refining
639are defined in `proced-grammar-alist'.
640
3ac09bb4
RW
641After displaying or updating a Proced buffer, Proced runs the normal hook
642`proced-post-display-hook'.
643
079ba9b7
SM
644\\{proced-mode-map}"
645 (abbrev-mode 0)
646 (auto-fill-mode 0)
647 (setq buffer-read-only t
b9df5969
RW
648 truncate-lines t
649 header-line-format '(:eval (proced-header-line)))
650 (add-hook 'post-command-hook 'force-mode-line-update nil t)
079ba9b7
SM
651 (set (make-local-variable 'revert-buffer-function) 'proced-revert)
652 (set (make-local-variable 'font-lock-defaults)
d74d0c42 653 '(proced-font-lock-keywords t nil nil beginning-of-line))
413e65fe
RW
654 (if (and (not proced-auto-update-timer) proced-auto-update-interval)
655 (setq proced-auto-update-timer
656 (run-at-time t proced-auto-update-interval
657 'proced-auto-update-timer))))
216d81a1 658
079ba9b7
SM
659;;;###autoload
660(defun proced (&optional arg)
da643190 661 "Generate a listing of UNIX system processes.
777fe95e
CY
662\\<proced-mode-map>
663If invoked with optional ARG, do not select the window displaying
664the process information.
37e4d8ed 665
777fe95e
CY
666This function runs the normal hook `proced-post-display-hook'.
667
668See `proced-mode' for a description of features available in
669Proced buffers."
37e4d8ed 670 (interactive "P")
413e65fe
RW
671 (unless proced-available
672 (error "Proced is not available on this system"))
92d9ce48 673 (let ((buffer (get-buffer-create "*Proced*")) new)
61548252 674 (set-buffer buffer)
37e4d8ed 675 (setq new (zerop (buffer-size)))
3ac09bb4
RW
676 (when new
677 (proced-mode)
678 ;; `proced-update' runs `proced-post-display-hook' only if the
679 ;; Proced buffer has been selected. Yet the following call of
680 ;; `proced-update' is for an empty Proced buffer that has not
681 ;; yet been selected. Therefore we need to call
682 ;; `proced-post-display-hook' below.
683 (proced-update t))
37e4d8ed 684 (if arg
3ac09bb4
RW
685 (progn
686 (display-buffer buffer)
687 (with-current-buffer buffer
8b78760b 688 (proced-update t)))
61548252 689 (pop-to-buffer buffer)
8b78760b 690 (proced-update t)
008c22f2
JL
691 (message
692 (substitute-command-keys
693 "Type \\<proced-mode-map>\\[quit-window] to quit, \\[proced-help] for help")))))
37e4d8ed 694
413e65fe 695(defun proced-auto-update-timer ()
da643190 696 "Auto-update Proced buffers using `run-at-time'."
d74d0c42
RW
697 (dolist (buf (buffer-list))
698 (with-current-buffer buf
699 (if (and (eq major-mode 'proced-mode)
413e65fe 700 proced-auto-update-flag)
d74d0c42
RW
701 (proced-update t t)))))
702
413e65fe 703(defun proced-toggle-auto-update (arg)
da643190
RW
704 "Change whether this Proced buffer is updated automatically.
705With prefix ARG, update this buffer automatically if ARG is positive,
413e65fe
RW
706otherwise do not update. Sets the variable `proced-auto-update-flag'.
707The time interval for updates is specified via `proced-auto-update-interval'."
d74d0c42 708 (interactive (list (or current-prefix-arg 'toggle)))
413e65fe
RW
709 (setq proced-auto-update-flag
710 (cond ((eq arg 'toggle) (not proced-auto-update-flag))
d74d0c42 711 (arg (> (prefix-numeric-value arg) 0))
413e65fe
RW
712 (t (not proced-auto-update-flag))))
713 (message "Proced auto update %s"
714 (if proced-auto-update-flag "enabled" "disabled")))
92d9ce48 715
3ac09bb4
RW
716;;; Mark
717
37e4d8ed
RW
718(defun proced-mark (&optional count)
719 "Mark the current (or next COUNT) processes."
720 (interactive "p")
721 (proced-do-mark t count))
722
723(defun proced-unmark (&optional count)
724 "Unmark the current (or next COUNT) processes."
725 (interactive "p")
726 (proced-do-mark nil count))
727
e6854b3f
RW
728(defun proced-unmark-backward (&optional count)
729 "Unmark the previous (or COUNT previous) processes."
61548252
RW
730 ;; Analogous to `dired-unmark-backward',
731 ;; but `ibuffer-unmark-backward' behaves different.
e6854b3f
RW
732 (interactive "p")
733 (proced-do-mark nil (- (or count 1))))
734
37e4d8ed 735(defun proced-do-mark (mark &optional count)
9f583d14 736 "Mark the current (or next COUNT) processes using MARK."
37e4d8ed 737 (or count (setq count 1))
e6854b3f 738 (let ((backward (< count 0))
37e4d8ed 739 buffer-read-only)
92d9ce48
RW
740 (setq count (1+ (if (<= 0 count) count
741 (min (1- (line-number-at-pos)) (abs count)))))
742 (beginning-of-line)
743 (while (not (or (zerop (setq count (1- count))) (eobp)))
744 (proced-insert-mark mark backward))
745 (proced-move-to-goal-column)))
37e4d8ed 746
3ac09bb4
RW
747(defun proced-toggle-marks ()
748 "Toggle marks: marked processes become unmarked, and vice versa."
749 (interactive)
750 (let ((mark-re (proced-marker-regexp))
751 buffer-read-only)
752 (save-excursion
753 (goto-char (point-min))
754 (while (not (eobp))
755 (cond ((looking-at mark-re)
756 (proced-insert-mark nil))
757 ((looking-at " ")
758 (proced-insert-mark t))
759 (t
760 (forward-line 1)))))))
761
762(defun proced-insert-mark (mark &optional backward)
763 "If MARK is non-nil, insert `proced-marker-char'.
764If BACKWARD is non-nil, move one line backwards before inserting the mark.
765Otherwise move one line forward after inserting the mark."
766 (if backward (forward-line -1))
767 (insert (if mark proced-marker-char ?\s))
768 (delete-char 1)
769 (unless backward (forward-line)))
770
37e4d8ed 771(defun proced-mark-all ()
9f583d14
RW
772 "Mark all processes.
773If `transient-mark-mode' is turned on and the region is active,
774mark the region."
37e4d8ed
RW
775 (interactive)
776 (proced-do-mark-all t))
777
778(defun proced-unmark-all ()
9f583d14
RW
779 "Unmark all processes.
780If `transient-mark-mode' is turned on and the region is active,
781unmark the region."
37e4d8ed
RW
782 (interactive)
783 (proced-do-mark-all nil))
784
785(defun proced-do-mark-all (mark)
9f583d14
RW
786 "Mark all processes using MARK.
787If `transient-mark-mode' is turned on and the region is active,
788mark the region."
3ac09bb4
RW
789 (let* ((count 0)
790 (proced-marker-char (if mark proced-marker-char ?\s))
791 (marker-re (proced-marker-regexp))
792 end buffer-read-only)
e6854b3f 793 (save-excursion
d74d0c42 794 (if (use-region-p)
9f583d14
RW
795 ;; Operate even on those lines that are only partially a part
796 ;; of region. This appears most consistent with
797 ;; `proced-move-to-goal-column'.
d74d0c42
RW
798 (progn (setq end (save-excursion
799 (goto-char (region-end))
800 (unless (looking-at "^") (forward-line))
801 (point)))
802 (goto-char (region-beginning))
803 (unless (looking-at "^") (beginning-of-line)))
9f583d14 804 (goto-char (point-min))
d74d0c42
RW
805 (setq end (point-max)))
806 (while (< (point) end)
3ac09bb4
RW
807 (unless (looking-at marker-re)
808 (setq count (1+ count))
809 (insert proced-marker-char)
810 (delete-char 1))
811 (forward-line))
812 (proced-success-message (if mark "Marked" "Unmarked") count))))
e6854b3f 813
d74d0c42
RW
814(defun proced-mark-children (ppid &optional omit-ppid)
815 "Mark child processes of process PPID.
816Also mark process PPID unless prefix OMIT-PPID is non-nil."
817 (interactive (list (proced-pid-at-point) current-prefix-arg))
818 (proced-mark-process-alist
819 (proced-filter-children proced-process-alist ppid omit-ppid)))
820
821(defun proced-mark-parents (cpid &optional omit-cpid)
822 "Mark parent processes of process CPID.
823Also mark CPID unless prefix OMIT-CPID is non-nil."
824 (interactive (list (proced-pid-at-point) current-prefix-arg))
825 (proced-mark-process-alist
826 (proced-filter-parents proced-process-alist cpid omit-cpid)))
827
828(defun proced-mark-process-alist (process-alist &optional quiet)
f1d27653
RW
829 "Mark processes in PROCESS-ALIST.
830If QUIET is non-nil suppress status message."
d74d0c42
RW
831 (let ((count 0))
832 (if process-alist
833 (let (buffer-read-only)
834 (save-excursion
835 (goto-char (point-min))
836 (while (not (eobp))
837 (when (assq (proced-pid-at-point) process-alist)
838 (insert proced-marker-char)
839 (delete-char 1)
840 (setq count (1+ count)))
841 (forward-line)))))
842 (unless quiet
843 (proced-success-message "Marked" count))))
844
e6854b3f
RW
845;; Mostly analog of `dired-do-kill-lines'.
846;; However, for negative args the target lines of `dired-do-kill-lines'
847;; include the current line, whereas `dired-mark' for negative args operates
d74d0c42 848;; on the preceding lines. Here we are consistent with `dired-mark'.
9f583d14
RW
849(defun proced-omit-processes (&optional arg quiet)
850 "Omit marked processes.
851With prefix ARG, omit that many lines starting with the current line.
852\(A negative argument omits backward.)
d74d0c42
RW
853If `transient-mark-mode' is turned on and the region is active,
854omit the processes in region.
e6854b3f 855If QUIET is non-nil suppress status message.
9f583d14 856Returns count of omitted lines."
e6854b3f
RW
857 (interactive "P")
858 (let ((mark-re (proced-marker-regexp))
859 (count 0)
860 buffer-read-only)
d74d0c42
RW
861 (cond ((use-region-p) ;; Omit active region
862 (let ((lines (count-lines (region-beginning) (region-end))))
863 (save-excursion
864 (goto-char (region-beginning))
865 (while (< count lines)
866 (proced-omit-process)
867 (setq count (1+ count))))))
868 ((not arg) ;; Omit marked lines
869 (save-excursion
870 (goto-char (point-min))
871 (while (and (not (eobp))
872 (re-search-forward mark-re nil t))
873 (proced-omit-process)
874 (setq count (1+ count)))))
875 ((< 0 arg) ;; Omit forward
876 (while (and (not (eobp)) (< count arg))
877 (proced-omit-process)
878 (setq count (1+ count))))
879 ((< arg 0) ;; Omit backward
880 (while (and (not (bobp)) (< count (- arg)))
881 (forward-line -1)
882 (proced-omit-process)
883 (setq count (1+ count)))))
92d9ce48 884 (unless (zerop count) (proced-move-to-goal-column))
9f583d14 885 (unless quiet (proced-success-message "Omitted" count))
e6854b3f 886 count))
37e4d8ed 887
d74d0c42
RW
888(defun proced-omit-process ()
889 "Omit process from listing point is on.
890Update `proced-process-alist' accordingly."
891 (setq proced-process-alist
892 (assq-delete-all (proced-pid-at-point) proced-process-alist))
893 (delete-region (line-beginning-position)
894 (save-excursion (forward-line) (point))))
895
896;;; Filtering
897
898(defun proced-filter (process-alist filter-list)
da643190
RW
899 "Apply FILTER-LIST to PROCESS-ALIST.
900Return the filtered process list."
d74d0c42
RW
901 (if (symbolp filter-list)
902 (setq filter-list (cdr (assq filter-list proced-filter-alist))))
903 (dolist (filter filter-list)
904 (let (new-alist)
905 (cond ( ;; apply function to entire process list
906 (eq (car filter) 'fun-all)
907 (setq new-alist (funcall (cdr filter) process-alist)))
908 ( ;; apply predicate to each list of attributes
909 (eq (car filter) 'function)
910 (dolist (process process-alist)
911 (if (funcall (car filter) (cdr process))
912 (push process new-alist))))
913 (t ;; apply predicate to specified attribute
914 (let ((fun (if (stringp (cdr filter))
915 `(lambda (val)
916 (string-match ,(cdr filter) val))
917 (cdr filter)))
918 value)
919 (dolist (process process-alist)
920 (setq value (cdr (assq (car filter) (cdr process))))
921 (if (and value (funcall fun value))
922 (push process new-alist))))))
923 (setq process-alist new-alist)))
924 process-alist)
925
da643190 926(defun proced-filter-interactive (scheme)
d74d0c42
RW
927 "Filter Proced buffer using SCHEME.
928When called interactively, an empty string means nil, i.e., no filtering.
da643190 929Set variable `proced-filter' to SCHEME. Revert listing."
37e4d8ed 930 (interactive
d74d0c42
RW
931 (let ((scheme (completing-read "Filter: "
932 proced-filter-alist nil t)))
da643190 933 (list (if (string= "" scheme) nil (intern scheme)))))
aa5fecb5
RW
934 ;; only update if necessary
935 (unless (eq proced-filter scheme)
936 (setq proced-filter scheme)
937 (proced-update t)))
d74d0c42 938
48152a70
RW
939(defun proced-filter-parents (process-alist pid &optional omit-pid)
940 "For PROCESS-ALIST return list of parent processes of PID.
941This list includes PID unless OMIT-PID is non-nil."
942 (let ((parent-list (unless omit-pid (list (assq pid process-alist))))
943 (process (assq pid process-alist))
944 ppid)
945 (while (and (setq ppid (cdr (assq 'ppid (cdr process))))
946 ;; Ignore a PPID that equals PID.
947 (/= ppid pid)
948 ;; Accept only PPIDs that correspond to members in PROCESS-ALIST.
949 (setq process (assq ppid process-alist)))
950 (setq pid ppid)
951 (push process parent-list))
952 parent-list))
953
954(defun proced-filter-children (process-alist ppid &optional omit-ppid)
955 "For PROCESS-ALIST return list of child processes of PPID.
956This list includes PPID unless OMIT-PPID is non-nil."
957 (let ((proced-temp-alist (proced-children-alist process-alist))
958 new-alist)
959 (dolist (pid (proced-children-pids ppid))
960 (push (assq pid process-alist) new-alist))
961 (if omit-ppid
962 (assq-delete-all ppid new-alist)
963 new-alist)))
964
965;;; Process tree
966
f1d27653
RW
967(defun proced-children-alist (process-alist)
968 "Return children alist for PROCESS-ALIST.
969The children alist has elements (PPID PID1 PID2 ...).
d74d0c42 970PPID is a parent PID. PID1, PID2, ... are the child processes of PPID.
48152a70 971The children alist inherits the sorting order of PROCESS-ALIST.
d74d0c42 972The list of children does not include grandchildren."
f1d27653
RW
973 ;; The PPIDs inherit the sorting order of PROCESS-ALIST.
974 (let ((process-tree (mapcar (lambda (a) (list (car a))) process-alist))
975 ppid)
976 (dolist (process process-alist)
d74d0c42 977 (setq ppid (cdr (assq 'ppid (cdr process))))
f1d27653
RW
978 (if (and ppid
979 ;; Ignore a PPID that equals PID.
980 (/= ppid (car process))
981 ;; Accept only PPIDs that correspond to members in PROCESS-ALIST.
982 (assq ppid process-alist))
983 (let ((temp-alist process-tree) elt)
984 (while (setq elt (pop temp-alist))
985 (when (eq ppid (car elt))
986 (setq temp-alist nil)
987 (setcdr elt (cons (car process) (cdr elt))))))))
988 ;; The child processes inherit the sorting order of PROCESS-ALIST.
989 (setq process-tree
990 (mapcar (lambda (a) (cons (car a) (nreverse (cdr a))))
991 process-tree))))
992
48152a70
RW
993(defun proced-children-pids (ppid)
994 "Return list of children PIDs of PPID (including PPID)."
995 (let ((cpids (cdr (assq ppid proced-temp-alist))))
996 (if cpids
997 (cons ppid (apply 'append (mapcar 'proced-children-pids cpids)))
998 (list ppid))))
999
f1d27653 1000(defun proced-process-tree (process-alist)
48152a70
RW
1001 "Return process tree for PROCESS-ALIST.
1002It is an alist of alists where the car of each alist is a parent process
1003and the cdr is a list of child processes according to the ppid attribute
1004of these processes.
1005The process tree inherits the sorting order of PROCESS-ALIST."
1006 (let ((proced-temp-alist (proced-children-alist process-alist))
f1d27653 1007 pid-alist proced-process-tree)
48152a70 1008 (while (setq pid-alist (pop proced-temp-alist))
f1d27653
RW
1009 (push (proced-process-tree-internal pid-alist) proced-process-tree))
1010 (nreverse proced-process-tree)))
1011
1012(defun proced-process-tree-internal (pid-alist)
1013 "Helper function for `proced-process-tree'."
1014 (let ((cpid-list (cdr pid-alist)) cpid-alist cpid)
1015 (while (setq cpid (car cpid-list))
48152a70 1016 (if (setq cpid-alist (assq cpid proced-temp-alist))
f1d27653
RW
1017 ;; Unprocessed part of process tree that needs to be
1018 ;; analyzed recursively.
1019 (progn
48152a70
RW
1020 (setq proced-temp-alist
1021 (assq-delete-all cpid proced-temp-alist))
f1d27653
RW
1022 (setcar cpid-list (proced-process-tree-internal cpid-alist)))
1023 ;; We already processed this subtree and take it "as is".
1024 (setcar cpid-list (assq cpid proced-process-tree))
1025 (setq proced-process-tree
1026 (assq-delete-all cpid proced-process-tree)))
1027 (pop cpid-list)))
1028 pid-alist)
1029
1030(defun proced-toggle-tree (arg)
48152a70 1031 "Toggle the display of the process listing as process tree.
f1d27653 1032With prefix ARG, display as process tree if ARG is positive, otherwise
48152a70
RW
1033do not display as process tree. Sets the variable `proced-tree-flag'.
1034
1035The process tree is generated from the selected processes in the
1036Proced buffer (that is, the processes in `proced-process-alist').
1037All processes that do not have a parent process in this list
1038according to their ppid attribute become the root of a process tree.
1039Each parent process is followed by its child processes.
1040The process tree inherits the chosen sorting order of the process listing,
1041that is, child processes of the same parent process are sorted using
1042the selected sorting order."
f1d27653
RW
1043 (interactive (list (or current-prefix-arg 'toggle)))
1044 (setq proced-tree-flag
1045 (cond ((eq arg 'toggle) (not proced-tree-flag))
1046 (arg (> (prefix-numeric-value arg) 0))
1047 (t (not proced-tree-flag))))
1048 (proced-update)
1049 (message "Proced process tree display %s"
1050 (if proced-tree-flag "enabled" "disabled")))
1051
1052(defun proced-tree (process-alist)
48152a70
RW
1053 "Rearrange PROCESS-ALIST as process tree.
1054If `proced-tree-flag' is non-nil, rearrange PROCESS-ALIST such that
1055every processes is followed by its child processes. Each process
1056gets a tree attribute that specifies the depth of the process in the tree.
1057A root process is a process with no parent within PROCESS-ALIST according
1058to its value of the ppid attribute. It has depth 0.
1059
1060If `proced-tree-flag' is nil, remove the tree attribute.
1061Return the rearranged process list."
f1d27653
RW
1062 (if proced-tree-flag
1063 ;; add tree attribute
1064 (let ((process-tree (proced-process-tree process-alist))
3ac09bb4 1065 (proced-tree-depth 0)
48152a70 1066 (proced-temp-alist process-alist)
f1d27653
RW
1067 proced-process-tree pt)
1068 (while (setq pt (pop process-tree))
1069 (proced-tree-insert pt))
1070 (nreverse proced-process-tree))
48152a70
RW
1071 ;; remove tree attribute
1072 (let ((process-alist process-alist))
1073 (while process-alist
1074 (setcar process-alist
1075 (assq-delete-all 'tree (car process-alist)))
1076 (pop process-alist)))
1077 process-alist))
f1d27653
RW
1078
1079(defun proced-tree-insert (process-tree)
1080 "Helper function for `proced-tree'."
48152a70 1081 (let ((pprocess (assq (car process-tree) proced-temp-alist)))
f1d27653 1082 (push (append (list (car pprocess))
3ac09bb4 1083 (list (cons 'tree proced-tree-depth))
f1d27653
RW
1084 (cdr pprocess))
1085 proced-process-tree)
1086 (if (cdr process-tree)
3ac09bb4 1087 (let ((proced-tree-depth (1+ proced-tree-depth)))
f1d27653 1088 (mapc 'proced-tree-insert (cdr process-tree))))))
d74d0c42 1089
da643190
RW
1090;; Refining
1091
1092;; Filters are used to select the processes in a new listing.
1093;; Refiners are used to narrow down further (interactively) the processes
1094;; in an existing listing.
1095
1096(defun proced-refine (&optional event)
1097 "Refine Proced listing by comparing with the attribute value at point.
1098Optional EVENT is the location of the Proced field.
1099
b4f671ce
RW
1100Refinement is controlled by the REFINER defined for each attribute ATTR
1101in `proced-grammar-alist'.
1102
4ed46aef
RW
1103If REFINER is a list of flags and point is on a process's value of ATTR,
1104this command compares the value of ATTR of every process with the value
1105of ATTR of the process at the position of point.
da643190
RW
1106
1107The predicate for the comparison of two ATTR values is defined
1108in `proced-grammar-alist'. For each return value of the predicate
b4f671ce
RW
1109a refine flag is defined in `proced-grammar-alist'. One can select
1110processes for which the value of ATTR is \"less than\", \"equal\",
1111and / or \"larger\" than ATTR of the process point is on. A process
1112is included in the new listing if the refine flag for the corresponding
1113return value of the predicate is non-nil.
da643190 1114The help-echo string for `proced-refine' uses \"+\" or \"-\" to indicate
b4f671ce
RW
1115the current values of these refine flags.
1116
1117If REFINER is a cons pair (FUNCTION . HELP-ECHO), FUNCTION is called
1118with one argument, the PID of the process at the position of point.
1119The function must return a list of PIDs that is used for the refined
1120listing. HELP-ECHO is a string that is shown when mouse is over this field.
da643190 1121
b4f671ce
RW
1122This command refines an already existing process listing generated initially
1123based on the value of the variable `proced-filter'. It does not change
1124this variable. It does not revert the listing. If you frequently need
1125a certain refinement, consider defining a new filter in `proced-filter-alist'."
d74d0c42
RW
1126 (interactive (list last-input-event))
1127 (if event (posn-set-point (event-end event)))
1128 (let ((key (get-text-property (point) 'proced-key))
1129 (pid (get-text-property (point) 'proced-pid)))
1130 (if (and key pid)
1131 (let* ((grammar (assq key proced-grammar-alist))
b4f671ce
RW
1132 (refiner (nth 7 grammar)))
1133 (when refiner
1134 (cond ((functionp (car refiner))
1135 (setq proced-process-alist (funcall (car refiner) pid)))
1136 ((consp refiner)
1137 (let ((predicate (nth 4 grammar))
1138 (ref (cdr (assq key (cdr (assq pid proced-process-alist)))))
1139 val new-alist)
1140 (dolist (process proced-process-alist)
1141 (setq val (funcall predicate (cdr (assq key (cdr process))) ref))
1142 (if (cond ((not val) (nth 2 refiner))
1143 ((eq val 'equal) (nth 1 refiner))
1144 (val (car refiner)))
1145 (push process new-alist)))
1146 (setq proced-process-alist new-alist))))
da643190 1147 ;; Do not revert listing.
d74d0c42 1148 (proced-update)))
da643190 1149 (message "No refiner defined here."))))
d74d0c42
RW
1150
1151;; Proced predicates for sorting and filtering are based on a three-valued
1152;; logic:
da643190
RW
1153;; Predicates take two arguments P1 and P2, the corresponding attribute
1154;; values of two processes. Predicates should return 'equal if P1 has
d74d0c42
RW
1155;; same rank like P2. Any other non-nil value says that P1 is "less than" P2,
1156;; or nil if not.
1157
1158(defun proced-< (num1 num2)
1159 "Return t if NUM1 less than NUM2.
1160Return `equal' if NUM1 equals NUM2. Return nil if NUM1 greater than NUM2."
1161 (if (= num1 num2)
1162 'equal
1163 (< num1 num2)))
1164
1165(defun proced-string-lessp (s1 s2)
1166 "Return t if string S1 is less than S2 in lexicographic order.
1167Return `equal' if S1 and S2 have identical contents.
1168Return nil otherwise."
1169 (if (string= s1 s2)
1170 'equal
1171 (string-lessp s1 s2)))
1172
1173(defun proced-time-lessp (t1 t2)
1174 "Return t if time value T1 is less than time value T2.
1175Return `equal' if T1 equals T2. Return nil otherwise."
d35af63c
PE
1176 (with-decoded-time-value ((high1 low1 micro1 pico1 type1 t1)
1177 (high2 low2 micro2 pico2 type2 t2))
d74d0c42
RW
1178 (cond ((< high1 high2))
1179 ((< high2 high1) nil)
1180 ((< low1 low2))
1181 ((< low2 low1) nil)
1182 ((< micro1 micro2))
1183 ((< micro2 micro1) nil)
d35af63c
PE
1184 ((< pico1 pico2))
1185 ((< pico2 pico1) nil)
d74d0c42 1186 (t 'equal))))
37e4d8ed 1187
d74d0c42
RW
1188;;; Sorting
1189
1190(defsubst proced-xor (b1 b2)
1191 "Return the logical exclusive or of args B1 and B2."
1192 (and (or b1 b2)
1193 (not (and b1 b2))))
1194
1195(defun proced-sort-p (p1 p2)
1196 "Predicate for sorting processes P1 and P2."
1197 (if (not (cdr proced-sort-internal))
1198 ;; only one predicate: fast scheme
1199 (let* ((sorter (car proced-sort-internal))
1200 (k1 (cdr (assq (car sorter) (cdr p1))))
1201 (k2 (cdr (assq (car sorter) (cdr p2)))))
1202 ;; if the attributes are undefined, we should really abort sorting
1203 (if (and k1 k2)
1204 (proced-xor (funcall (nth 1 sorter) k1 k2)
1205 (nth 2 sorter))))
1206 (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
1207 (catch 'done
1208 (while (setq sorter (pop sort-list))
1209 (setq k1 (cdr (assq (car sorter) (cdr p1)))
1210 k2 (cdr (assq (car sorter) (cdr p2)))
1211 predicate
1212 (if (and k1 k2)
1213 (funcall (nth 1 sorter) k1 k2)))
1214 (if (not (eq predicate 'equal))
1215 (throw 'done (proced-xor predicate (nth 2 sorter)))))
1216 (eq t predicate)))))
1217
b4f671ce 1218(defun proced-sort (process-alist sorter descend)
d74d0c42 1219 "Sort PROCESS-ALIST using scheme SORTER.
b4f671ce
RW
1220SORTER is a scheme like `proced-sort'.
1221DESCEND is non-nil if the first element of SORTER is sorted
1222in descending order.
da643190 1223Return the sorted process list."
d74d0c42
RW
1224 ;; translate SORTER into a list of lists (KEY PREDICATE REVERSE)
1225 (setq proced-sort-internal
1226 (mapcar (lambda (arg)
1227 (let ((grammar (assq arg proced-grammar-alist)))
f1d27653
RW
1228 (unless (nth 4 grammar)
1229 (error "Attribute %s not sortable" (car grammar)))
d74d0c42
RW
1230 (list arg (nth 4 grammar) (nth 5 grammar))))
1231 (cond ((listp sorter) sorter)
1232 ((and (symbolp sorter)
1233 (nth 6 (assq sorter proced-grammar-alist))))
1234 ((symbolp sorter) (list sorter))
1235 (t (error "Sorter undefined %s" sorter)))))
1236 (if proced-sort-internal
b4f671ce
RW
1237 (progn
1238 ;; splice DESCEND into the list
1239 (setcar proced-sort-internal
1240 (list (caar proced-sort-internal)
1241 (nth 1 (car proced-sort-internal)) descend))
1242 (sort process-alist 'proced-sort-p))
d74d0c42
RW
1243 process-alist))
1244
4ed46aef 1245(defun proced-sort-interactive (scheme &optional arg)
d74d0c42
RW
1246 "Sort Proced buffer using SCHEME.
1247When called interactively, an empty string means nil, i.e., no sorting.
da643190 1248
4ed46aef
RW
1249Prefix ARG controls sort order:
1250- If prefix ARG is positive (negative), sort in ascending (descending) order.
1251- If ARG is nil or 'no-arg and SCHEME is equal to the previous sorting scheme,
1252 reverse the sorting order.
1253- If ARG is nil or 'no-arg and SCHEME differs from the previous sorting scheme,
1254 adopt the sorting order defined for SCHEME in `proced-grammar-alist'.
b4f671ce 1255
da643190
RW
1256Set variable `proced-sort' to SCHEME. The current sort scheme is displayed
1257in the mode line, using \"+\" or \"-\" for ascending or descending order."
d74d0c42 1258 (interactive
f1d27653
RW
1259 (let* (choices
1260 (scheme (completing-read "Sort attribute: "
1261 (dolist (grammar proced-grammar-alist choices)
1262 (if (nth 4 grammar)
1263 (push (list (car grammar)) choices)))
1264 nil t)))
d74d0c42 1265 (list (if (string= "" scheme) nil (intern scheme))
4ed46aef
RW
1266 ;; like 'toggle in `define-derived-mode'
1267 (or current-prefix-arg 'no-arg))))
1268
b4f671ce 1269 (setq proced-descend
4ed46aef
RW
1270 ;; If `proced-sort-interactive' is called repeatedly for the same
1271 ;; sort key, the sort order is reversed.
1272 (cond ((and (eq arg 'no-arg) (equal proced-sort scheme))
1273 (not proced-descend))
1274 ((eq arg 'no-arg)
1275 (nth 5 (assq (if (consp scheme) (car scheme) scheme)
1276 proced-grammar-alist)))
1277 (arg (< (prefix-numeric-value arg) 0))
1278 ((equal proced-sort scheme)
1279 (not proced-descend))
1280 (t (nth 5 (assq (if (consp scheme) (car scheme) scheme)
1281 proced-grammar-alist))))
b4f671ce 1282 proced-sort scheme)
4ed46aef 1283 (proced-update))
d74d0c42 1284
4ed46aef 1285(defun proced-sort-pcpu (&optional arg)
b4f671ce 1286 "Sort Proced buffer by percentage CPU time (%CPU).
4ed46aef
RW
1287Prefix ARG controls sort order, see `proced-sort-interactive'."
1288 (interactive (list (or current-prefix-arg 'no-arg)))
1289 (proced-sort-interactive 'pcpu arg))
d74d0c42 1290
4ed46aef 1291(defun proced-sort-pmem (&optional arg)
b4f671ce 1292 "Sort Proced buffer by percentage memory usage (%MEM).
4ed46aef
RW
1293Prefix ARG controls sort order, see `proced-sort-interactive'."
1294 (interactive (list (or current-prefix-arg 'no-arg)))
1295 (proced-sort-interactive 'pmem arg))
d74d0c42 1296
4ed46aef 1297(defun proced-sort-pid (&optional arg)
b4f671ce 1298 "Sort Proced buffer by PID.
4ed46aef
RW
1299Prefix ARG controls sort order, see `proced-sort-interactive'."
1300 (interactive (list (or current-prefix-arg 'no-arg)))
1301 (proced-sort-interactive 'pid arg))
d74d0c42 1302
4ed46aef 1303(defun proced-sort-start (&optional arg)
b4f671ce 1304 "Sort Proced buffer by time the command started (START).
4ed46aef
RW
1305Prefix ARG controls sort order, see `proced-sort-interactive'."
1306 (interactive (list (or current-prefix-arg 'no-arg)))
1307 (proced-sort-interactive 'start arg))
d74d0c42 1308
4ed46aef 1309(defun proced-sort-time (&optional arg)
b4f671ce 1310 "Sort Proced buffer by CPU time (TIME).
4ed46aef
RW
1311Prefix ARG controls sort order, see `proced-sort-interactive'."
1312 (interactive (list (or current-prefix-arg 'no-arg)))
1313 (proced-sort-interactive 'time arg))
d74d0c42 1314
4ed46aef 1315(defun proced-sort-user (&optional arg)
b4f671ce 1316 "Sort Proced buffer by USER.
4ed46aef
RW
1317Prefix ARG controls sort order, see `proced-sort-interactive'."
1318 (interactive (list (or current-prefix-arg 'no-arg)))
1319 (proced-sort-interactive 'user arg))
d74d0c42 1320
4ed46aef 1321(defun proced-sort-header (event &optional arg)
d74d0c42
RW
1322 "Sort Proced listing based on an attribute.
1323EVENT is a mouse event with starting position in the header line.
d0482e4e 1324It is converted to the corresponding attribute key.
b4f671ce 1325This command updates the variable `proced-sort'.
4ed46aef
RW
1326Prefix ARG controls sort order, see `proced-sort-interactive'."
1327 (interactive (list last-input-event (or last-prefix-arg 'no-arg)))
d74d0c42
RW
1328 (let ((start (event-start event))
1329 col key)
1330 (save-selected-window
1331 (select-window (posn-window start))
aa5fecb5 1332 (setq col (+ (1- (car (posn-actual-col-row start)))
d74d0c42
RW
1333 (window-hscroll)))
1334 (when (and (<= 0 col) (< col (length proced-header-line)))
1335 (setq key (get-text-property col 'proced-key proced-header-line))
1336 (if key
4ed46aef 1337 (proced-sort-interactive key arg)
d74d0c42
RW
1338 (message "No sorter defined here."))))))
1339
333f9019 1340;;; Formatting
d74d0c42
RW
1341
1342(defun proced-format-time (time)
667df88c 1343 "Format time interval TIME."
d74d0c42
RW
1344 (let* ((ftime (float-time time))
1345 (days (truncate ftime 86400))
1346 (ftime (mod ftime 86400))
1347 (hours (truncate ftime 3600))
1348 (ftime (mod ftime 3600))
1349 (minutes (truncate ftime 60))
1350 (seconds (mod ftime 60)))
1351 (cond ((< 0 days)
1352 (format "%d-%02d:%02d:%02d" days hours minutes seconds))
1353 ((< 0 hours)
1354 (format "%02d:%02d:%02d" hours minutes seconds))
1355 (t
1356 (format "%02d:%02d" minutes seconds)))))
1357
1358(defun proced-format-start (start)
1359 "Format time START.
1360The return string is always 6 characters wide."
1361 (let ((d-start (decode-time start))
1362 (d-current (decode-time)))
1363 (cond ( ;; process started in previous years
1364 (< (nth 5 d-start) (nth 5 d-current))
1365 (format-time-string " %Y" start))
1366 ;; process started today
1367 ((and (= (nth 3 d-start) (nth 3 d-current))
1368 (= (nth 4 d-start) (nth 4 d-current)))
1369 (format-time-string " %H:%M" start))
1370 (t ;; process started this year
1371 (format-time-string "%b %e" start)))))
1372
1373(defun proced-format-ttname (ttname)
da643190 1374 "Format attribute TTNAME, omitting path \"/dev/\"."
d74d0c42 1375 ;; Does this work for all systems?
da643190
RW
1376 (substring ttname (if (string-match "\\`/dev/" ttname)
1377 (match-end 0) 0)))
1378
f1d27653
RW
1379(defun proced-format-tree (tree)
1380 "Format attribute TREE."
1381 (concat (make-string tree ?\s) (number-to-string tree)))
1382
b4f671ce 1383;; Proced assumes that every process occupies only one line in the listing.
da643190
RW
1384(defun proced-format-args (args)
1385 "Format attribute ARGS.
1386Replace newline characters by \"^J\" (two characters)."
1387 (replace-regexp-in-string "\n" "^J" args))
d74d0c42
RW
1388
1389(defun proced-format (process-alist format)
1390 "Display PROCESS-ALIST using FORMAT."
1391 (if (symbolp format)
1392 (setq format (cdr (assq format proced-format-alist))))
b4f671ce
RW
1393
1394 ;; Not all systems give us all attributes. We take `emacs-pid' as a
1395 ;; representative process PID. If FORMAT contains a list of alternative
1396 ;; attributes, we take the first attribute that is non-nil for `emacs-pid'.
1397 ;; If none of the alternatives is non-nil, the attribute is ignored
1398 ;; in the listing.
1399 (let ((standard-attributes
1400 (car (proced-process-attributes (list (emacs-pid)))))
1401 new-format fmi)
3ac09bb4
RW
1402 (if (and proced-tree-flag
1403 (assq 'ppid standard-attributes))
1404 (push (cons 'tree 0) standard-attributes))
b4f671ce
RW
1405 (dolist (fmt format)
1406 (if (symbolp fmt)
1407 (if (assq fmt standard-attributes)
1408 (push fmt new-format))
1409 (while (setq fmi (pop fmt))
1410 (when (assq fmi standard-attributes)
1411 (push fmi new-format)
1412 (setq fmt nil)))))
1413 (setq format (nreverse new-format)))
1414
d74d0c42 1415 (insert (make-string (length process-alist) ?\n))
b4f671ce
RW
1416 (let ((whitespace " ") (unknown "?")
1417 (sort-key (if (consp proced-sort) (car proced-sort) proced-sort))
1418 header-list grammar)
d74d0c42 1419 ;; Loop over all attributes
b4f671ce 1420 (while (setq grammar (assq (pop format) proced-grammar-alist))
d74d0c42 1421 (let* ((key (car grammar))
da643190
RW
1422 (fun (cond ((stringp (nth 2 grammar))
1423 `(lambda (arg) (format ,(nth 2 grammar) arg)))
1424 ((not (nth 2 grammar)) 'identity)
1425 ( t (nth 2 grammar))))
d74d0c42
RW
1426 (whitespace (if format whitespace ""))
1427 ;; Text properties:
1428 ;; We use the text property `proced-key' to store in each
1429 ;; field the corresponding key.
1430 ;; Of course, the sort predicate appearing in help-echo
1431 ;; is only part of the story. But it gives the main idea.
f1d27653
RW
1432 (hprops
1433 (if (nth 4 grammar)
1434 (let ((descend (if (eq key sort-key) proced-descend (nth 5 grammar))))
1435 `(proced-key ,key mouse-face highlight
1436 help-echo ,(format proced-header-help-echo
1437 (if descend "-" "+")
1438 (nth 1 grammar)
1439 (if descend "descending" "ascending"))))))
b4f671ce
RW
1440 (refiner (nth 7 grammar))
1441 (fprops
1442 (cond ((functionp (car refiner))
1443 `(proced-key ,key mouse-face highlight
1444 help-echo ,(format "mouse-2, RET: %s"
3ac09bb4 1445 (nth 1 refiner))))
b4f671ce
RW
1446 ((consp refiner)
1447 `(proced-key ,key mouse-face highlight
1448 help-echo ,(format "mouse-2, RET: refine by attribute %s %s"
d74d0c42
RW
1449 (nth 1 grammar)
1450 (mapconcat (lambda (s)
1451 (if s "+" "-"))
b4f671ce 1452 refiner ""))))))
d74d0c42
RW
1453 value)
1454
da643190 1455 ;; highlight the header of the sort column
b4f671ce 1456 (if (eq key sort-key)
aa5fecb5 1457 (setq hprops (append '(face proced-sort-header) hprops)))
d74d0c42
RW
1458 (goto-char (point-min))
1459 (cond ( ;; fixed width of output field
1460 (numberp (nth 3 grammar))
1461 (dolist (process process-alist)
1462 (end-of-line)
1463 (setq value (cdr (assq key (cdr process))))
1464 (insert (if value
1465 (apply 'propertize (funcall fun value) fprops)
b4f671ce
RW
1466 (format (concat "%" (number-to-string (nth 3 grammar)) "s")
1467 unknown))
d74d0c42
RW
1468 whitespace)
1469 (forward-line))
1470 (push (format (concat "%" (number-to-string (nth 3 grammar)) "s")
1471 (apply 'propertize (nth 1 grammar) hprops))
1472 header-list))
1473
1474 ( ;; last field left-justified
1475 (and (not format) (eq 'left (nth 3 grammar)))
1476 (dolist (process process-alist)
1477 (end-of-line)
1478 (setq value (cdr (assq key (cdr process))))
b4f671ce
RW
1479 (insert (if value (apply 'propertize (funcall fun value) fprops)
1480 unknown))
d74d0c42
RW
1481 (forward-line))
1482 (push (apply 'propertize (nth 1 grammar) hprops) header-list))
1483
1484 (t ;; calculated field width
1485 (let ((width (length (nth 1 grammar)))
1486 field-list value)
1487 (dolist (process process-alist)
1488 (setq value (cdr (assq key (cdr process))))
1489 (if value
1490 (setq value (apply 'propertize (funcall fun value) fprops)
1491 width (max width (length value))
1492 field-list (cons value field-list))
b4f671ce
RW
1493 (push unknown field-list)
1494 (setq width (max width (length unknown)))))
d74d0c42
RW
1495 (let ((afmt (concat "%" (if (eq 'left (nth 3 grammar)) "-" "")
1496 (number-to-string width) "s")))
1497 (push (format afmt (apply 'propertize (nth 1 grammar) hprops))
1498 header-list)
1499 (dolist (value (nreverse field-list))
1500 (end-of-line)
1501 (insert (format afmt value) whitespace)
1502 (forward-line))))))))
1503
1504 ;; final cleanup
1505 (goto-char (point-min))
1506 (dolist (process process-alist)
1507 ;; We use the text property `proced-pid' to store in each line
1508 ;; the corresponding pid
1509 (put-text-property (point) (line-end-position) 'proced-pid (car process))
1510 (forward-line))
1511 ;; Set header line
1512 (setq proced-header-line
1513 (mapconcat 'identity (nreverse header-list) whitespace))
1514 (if (string-match "[ \t]+$" proced-header-line)
1515 (setq proced-header-line (substring proced-header-line 0
1516 (match-beginning 0))))
1517 ;; (delete-trailing-whitespace)
1518 (goto-char (point-min))
1519 (while (re-search-forward "[ \t\r]+$" nil t)
1520 (delete-region (match-beginning 0) (match-end 0)))))
b9df5969 1521
d74d0c42
RW
1522(defun proced-format-interactive (scheme &optional revert)
1523 "Format Proced buffer using SCHEME.
1524When called interactively, an empty string means nil, i.e., no formatting.
da643190 1525Set variable `proced-format' to SCHEME.
d74d0c42
RW
1526With prefix REVERT non-nil revert listing."
1527 (interactive
1528 (let ((scheme (completing-read "Format: "
1529 proced-format-alist nil t)))
1530 (list (if (string= "" scheme) nil (intern scheme))
1531 current-prefix-arg)))
aa5fecb5
RW
1532 ;; only update if necessary
1533 (when (or (not (eq proced-format scheme)) revert)
1534 (setq proced-format scheme)
1535 (proced-update revert)))
d74d0c42
RW
1536
1537;; generate listing
1538
b4f671ce 1539(defun proced-process-attributes (&optional pid-list)
d74d0c42 1540 "Return alist of attributes for each system process.
b4f671ce
RW
1541This alist can be customized via `proced-custom-attributes'.
1542Optional arg PID-LIST is a list of PIDs of system process that are analyzed.
1543If no attributes are known for a process (possibly because it already died)
1544the process is ignored."
1545 ;; Should we make it customizable whether processes with empty attribute
1546 ;; lists are ignored? When would such processes be of interest?
3ac09bb4 1547 (let (process-alist attributes attr)
b4f671ce 1548 (dolist (pid (or pid-list (list-system-processes)) process-alist)
a20878b6 1549 (when (setq attributes (process-attributes pid))
3ac09bb4
RW
1550 (setq attributes (cons (cons 'pid pid) attributes))
1551 (dolist (fun proced-custom-attributes)
1552 (if (setq attr (funcall fun attributes))
1553 (push attr attributes)))
1554 (push (cons pid attributes) process-alist)))))
d74d0c42
RW
1555
1556(defun proced-update (&optional revert quiet)
d0482e4e 1557 "Update the Proced process information. Preserves point and marks.
d74d0c42 1558With prefix REVERT non-nil, revert listing.
3ac09bb4
RW
1559Suppress status information if QUIET is nil.
1560After updating a displayed Proced buffer run the normal hook
1561`proced-post-display-hook'."
e6854b3f 1562 ;; This is the main function that generates and updates the process listing.
d74d0c42
RW
1563 (interactive "P")
1564 (setq revert (or revert (not proced-process-alist)))
1565 (or quiet (message (if revert "Updating process information..."
1566 "Updating process display...")))
da643190
RW
1567 (if revert ;; evaluate all processes
1568 (setq proced-process-alist (proced-process-attributes)))
1569 ;; filtering and sorting
1570 (setq proced-process-alist
b4f671ce
RW
1571 (proced-sort (proced-filter proced-process-alist proced-filter)
1572 proced-sort proced-descend))
da643190 1573
f1d27653
RW
1574 ;; display as process tree?
1575 (setq proced-process-alist
1576 (proced-tree proced-process-alist))
1577
da643190
RW
1578 ;; It is useless to keep undo information if we revert, filter, or
1579 ;; refine the listing so that `proced-process-alist' has changed.
1580 ;; We could keep the undo information if we only re-sort the buffer.
1581 ;; Would that be useful? Re-re-sorting is easy, too.
1582 (if (consp buffer-undo-list)
1583 (setq buffer-undo-list nil))
1584 (let ((buffer-undo-list t)
1585 ;; If point is on a field, we try to return point to that field.
1586 ;; Otherwise we try to return to the same column
1587 (old-pos (let ((pid (proced-pid-at-point))
1588 (key (get-text-property (point) 'proced-key)))
1589 (list pid key ; can both be nil
d74d0c42
RW
1590 (if key
1591 (if (get-text-property (1- (point)) 'proced-key)
1592 (- (point) (previous-single-property-change
1593 (point) 'proced-key))
1594 0)
1595 (current-column)))))
1596 buffer-read-only mp-list)
37e4d8ed 1597 ;; remember marked processes (whatever the mark was)
d74d0c42
RW
1598 (goto-char (point-min))
1599 (while (re-search-forward "^\\(\\S-\\)" nil t)
1600 (push (cons (save-match-data (proced-pid-at-point))
92d9ce48 1601 (match-string-no-properties 1)) mp-list))
da643190 1602
d74d0c42 1603 ;; generate listing
37e4d8ed 1604 (erase-buffer)
d74d0c42 1605 (proced-format proced-process-alist proced-format)
37e4d8ed
RW
1606 (goto-char (point-min))
1607 (while (not (eobp))
1608 (insert " ")
1609 (forward-line))
8ca42262 1610 (setq proced-header-line (concat " " proced-header-line))
d74d0c42 1611 (if revert (set-buffer-modified-p nil))
da643190 1612
d74d0c42
RW
1613 ;; set `goal-column'
1614 (let ((grammar (assq proced-goal-attribute proced-grammar-alist)))
1615 (setq goal-column ;; set to nil if no match
1616 (if (and grammar
1617 (not (zerop (buffer-size)))
1618 (string-match (regexp-quote (nth 1 grammar))
1619 proced-header-line))
1620 (if (nth 3 grammar)
1621 (match-beginning 0)
1622 (match-end 0)))))
da643190 1623
204ebc5b
RW
1624 ;; Restore process marks and buffer position (if possible).
1625 ;; Sometimes this puts point in the middle of the proced buffer
da643190 1626 ;; where it is not interesting. Is there a better / more flexible solution?
92d9ce48 1627 (goto-char (point-min))
da643190
RW
1628 (let (pid mark new-pos)
1629 (if (or mp-list (car old-pos))
d74d0c42
RW
1630 (while (not (eobp))
1631 (setq pid (proced-pid-at-point))
1632 (when (setq mark (assq pid mp-list))
1633 (insert (cdr mark))
1634 (delete-char 1)
1635 (beginning-of-line))
1636 (when (eq (car old-pos) pid)
1637 (if (nth 1 old-pos)
1638 (let ((limit (line-end-position)) pos)
1639 (while (and (not new-pos)
1640 (setq pos (next-property-change (point) nil limit)))
1641 (goto-char pos)
1642 (when (eq (nth 1 old-pos)
1643 (get-text-property (point) 'proced-key))
1644 (forward-char (min (nth 2 old-pos)
1645 (- (next-property-change (point))
1646 (point))))
1647 (setq new-pos (point))))
1648 (unless new-pos
da643190
RW
1649 ;; we found the process, but the field of point
1650 ;; is not listed anymore
1651 (setq new-pos (proced-move-to-goal-column))))
d74d0c42
RW
1652 (setq new-pos (min (+ (line-beginning-position) (nth 2 old-pos))
1653 (line-end-position)))))
da643190
RW
1654 (forward-line)))
1655 (if new-pos
1656 (goto-char new-pos)
1657 (goto-char (point-min))
1658 (proced-move-to-goal-column)))
37269466
CY
1659 ;; update mode line
1660 ;; Does the long `mode-name' clutter the mode line? It would be nice
da643190
RW
1661 ;; to have some other location for displaying the values of the various
1662 ;; flags that affect the behavior of proced (flags one might want
1663 ;; to change on the fly). Where??
d74d0c42
RW
1664 (setq mode-name
1665 (concat "Proced"
1666 (if proced-filter
1667 (concat ": " (symbol-name proced-filter))
1668 "")
1669 (if proced-sort
b4f671ce 1670 (let* ((key (if (consp proced-sort) (car proced-sort)
d74d0c42
RW
1671 proced-sort))
1672 (grammar (assq key proced-grammar-alist)))
b4f671ce 1673 (concat " by " (if proced-descend "-" "+")
d74d0c42
RW
1674 (nth 1 grammar)))
1675 "")))
61548252 1676 (force-mode-line-update)
3ac09bb4
RW
1677 ;; run `proced-post-display-hook' only for a displayed buffer.
1678 (if (get-buffer-window) (run-hooks 'proced-post-display-hook))
61548252 1679 ;; done
37e4d8ed 1680 (or quiet (input-pending-p)
d74d0c42
RW
1681 (message (if revert "Updating process information...done."
1682 "Updating process display...done.")))))
37e4d8ed 1683
06b60517 1684(defun proced-revert (&rest _args)
4ed46aef
RW
1685 "Reevaluate the process listing based on the currently running processes.
1686Preserves point and marks."
d74d0c42 1687 (proced-update t))
37e4d8ed 1688
37e4d8ed
RW
1689(defun proced-send-signal (&optional signal)
1690 "Send a SIGNAL to the marked processes.
9f583d14 1691If no process is marked, operate on current process.
37e4d8ed 1692SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
3ac09bb4
RW
1693If SIGNAL is nil display marked processes and query interactively for SIGNAL.
1694After sending the signal, this command runs the normal hook
1695`proced-after-send-signal-hook'."
37e4d8ed 1696 (interactive)
d74d0c42
RW
1697 (let ((regexp (proced-marker-regexp))
1698 process-alist)
37e4d8ed
RW
1699 ;; collect marked processes
1700 (save-excursion
1701 (goto-char (point-min))
1702 (while (re-search-forward regexp nil t)
d74d0c42
RW
1703 (push (cons (proced-pid-at-point)
1704 ;; How much info should we collect here?
3ac09bb4
RW
1705 (buffer-substring-no-properties
1706 (+ 2 (line-beginning-position))
1707 (line-end-position)))
d74d0c42
RW
1708 process-alist)))
1709 (setq process-alist
1710 (if process-alist
1711 (nreverse process-alist)
9f583d14 1712 ;; take current process
d74d0c42
RW
1713 (list (cons (proced-pid-at-point)
1714 (buffer-substring-no-properties
1715 (+ 2 (line-beginning-position))
1716 (line-end-position))))))
9f583d14
RW
1717 (unless signal
1718 ;; Display marked processes (code taken from `dired-mark-pop-up').
8b78760b
JL
1719 (let ((bufname " *Marked Processes*") ; use leading space in buffer name
1720 ; to make this buffer ephemeral
d74d0c42 1721 (header-line (substring-no-properties proced-header-line)))
9f583d14
RW
1722 (with-current-buffer (get-buffer-create bufname)
1723 (setq truncate-lines t
d74d0c42 1724 proced-header-line header-line ; inherit header line
9f583d14
RW
1725 header-line-format '(:eval (proced-header-line)))
1726 (add-hook 'post-command-hook 'force-mode-line-update nil t)
9a9af856
SM
1727 (let ((inhibit-read-only t))
1728 (erase-buffer)
1729 (buffer-disable-undo)
1730 (setq buffer-read-only t)
1731 (dolist (process process-alist)
5db2afd2
RW
1732 (insert " " (cdr process) "\n"))
1733 (delete-char -1)
1734 (goto-char (point-min)))
9f583d14 1735 (save-window-excursion
016151bb
RW
1736 ;; Analogous to `dired-pop-to-buffer'
1737 ;; Don't split window horizontally. (Bug#1806)
1738 (let (split-width-threshold)
1739 (pop-to-buffer (current-buffer)))
3ac09bb4 1740 (fit-window-to-buffer (get-buffer-window) nil 1)
9f583d14 1741 (let* ((completion-ignore-case t)
d74d0c42 1742 (pnum (if (= 1 (length process-alist))
9f583d14 1743 "1 process"
d74d0c42 1744 (format "%d processes" (length process-alist))))
2403c841
SM
1745 (completion-extra-properties
1746 '(:annotation-function
1747 (lambda (s) (cdr (assoc s proced-signal-list))))))
9a9af856
SM
1748 (setq signal
1749 (completing-read (concat "Send signal [" pnum
1750 "] (default TERM): ")
1751 proced-signal-list
1752 nil nil nil nil "TERM")))))))
f67cf064
RW
1753 ;; send signal
1754 (let ((count 0)
1755 failures)
1756 ;; Why not always use `signal-process'? See
1757 ;; http://lists.gnu.org/archive/html/emacs-devel/2008-03/msg02955.html
1758 (if (functionp proced-signal-function)
1759 ;; use built-in `signal-process'
1760 (let ((signal (if (stringp signal)
1761 (if (string-match "\\`[0-9]+\\'" signal)
1762 (string-to-number signal)
1763 (make-symbol signal))
1764 signal))) ; number
d74d0c42 1765 (dolist (process process-alist)
f67cf064
RW
1766 (condition-case err
1767 (if (zerop (funcall
1768 proced-signal-function (car process) signal))
1769 (setq count (1+ count))
1770 (proced-log "%s\n" (cdr process))
1771 (push (cdr process) failures))
1772 (error ; catch errors from failed signals
1773 (proced-log "%s\n" err)
1774 (proced-log "%s\n" (cdr process))
1775 (push (cdr process) failures)))))
1776 ;; use external system call
1777 (let ((signal (concat "-" (if (numberp signal)
1778 (number-to-string signal) signal))))
1779 (dolist (process process-alist)
1780 (with-temp-buffer
06b60517 1781 (condition-case nil
f67cf064
RW
1782 (if (zerop (call-process
1783 proced-signal-function nil t nil
1784 signal (number-to-string (car process))))
1785 (setq count (1+ count))
1786 (proced-log (current-buffer))
1787 (proced-log "%s\n" (cdr process))
1788 (push (cdr process) failures))
1789 (error ; catch errors from failed signals
1790 (proced-log (current-buffer))
1791 (proced-log "%s\n" (cdr process))
1792 (push (cdr process) failures)))))))
1793 (if failures
1794 ;; Proced error message are not always very precise.
1795 ;; Can we issue a useful one-line summary in the
1796 ;; message area (using FAILURES) if only one signal failed?
1797 (proced-log-summary
1798 signal
1799 (format "%d of %d signal%s failed"
1800 (length failures) (length process-alist)
1801 (if (= 1 (length process-alist)) "" "s")))
1802 (proced-success-message "Sent signal to" count)))
1803 ;; final clean-up
1804 (run-hooks 'proced-after-send-signal-hook)))
9f583d14 1805
d74d0c42 1806;; similar to `dired-why'
9f583d14
RW
1807(defun proced-why ()
1808 "Pop up a buffer with error log output from Proced.
1809A group of errors from a single command ends with a formfeed.
1810Thus, use \\[backward-page] to find the beginning of a group of errors."
1811 (interactive)
1812 (if (get-buffer proced-log-buffer)
d74d0c42
RW
1813 (save-selected-window
1814 ;; move `proced-log-buffer' to the front of the buffer list
1815 (select-window (display-buffer (get-buffer proced-log-buffer)))
1816 (setq truncate-lines t)
1817 (set-buffer-modified-p nil)
1818 (setq buffer-read-only t)
1819 (goto-char (point-max))
1820 (forward-line -1)
1821 (backward-page 1)
1822 (recenter 0))))
9f583d14
RW
1823
1824;; similar to `dired-log'
1825(defun proced-log (log &rest args)
1826 "Log a message or the contents of a buffer.
1827If LOG is a string and there are more args, it is formatted with
1828those ARGS. Usually the LOG string ends with a \\n.
1829End each bunch of errors with (proced-log t signal):
1830this inserts the current time, buffer and signal at the start of the page,
1831and \f (formfeed) at the end."
1832 (let ((obuf (current-buffer)))
1833 (with-current-buffer (get-buffer-create proced-log-buffer)
1834 (goto-char (point-max))
d74d0c42 1835 (let (buffer-read-only)
9f583d14
RW
1836 (cond ((stringp log)
1837 (insert (if args
1838 (apply 'format log args)
1839 log)))
1840 ((bufferp log)
1841 (insert-buffer-substring log))
1842 ((eq t log)
1843 (backward-page 1)
1844 (unless (bolp)
1845 (insert "\n"))
1846 (insert (current-time-string)
1847 "\tBuffer `" (buffer-name obuf) "', "
1848 (format "signal `%s'\n" (car args)))
1849 (goto-char (point-max))
1850 (insert "\f\n")))))))
1851
1852;; similar to `dired-log-summary'
1853(defun proced-log-summary (signal string)
1854 "State a summary of SIGNAL's failures, in echo area and log buffer.
1855STRING is an overall summary of the failures."
1856 (message "Signal %s: %s--type ? for details" signal string)
1857 ;; Log a summary describing a bunch of errors.
1858 (proced-log (concat "\n" string "\n"))
1859 (proced-log t signal))
37e4d8ed
RW
1860
1861(defun proced-help ()
d0482e4e 1862 "Provide help for the Proced user."
37e4d8ed 1863 (interactive)
9f583d14 1864 (proced-why)
37e4d8ed
RW
1865 (if (eq last-command 'proced-help)
1866 (describe-mode)
1867 (message proced-help-string)))
1868
1869(defun proced-undo ()
d0482e4e
JB
1870 "Undo in a Proced buffer.
1871This doesn't recover killed processes, it just undoes changes in the Proced
37e4d8ed
RW
1872buffer. You can use it to recover marks."
1873 (interactive)
1874 (let (buffer-read-only)
1875 (undo))
92d9ce48 1876 (message "Change in Proced buffer undone.
37e4d8ed
RW
1877Killed processes cannot be recovered by Emacs."))
1878
1879(provide 'proced)
1880
9f583d14 1881;;; proced.el ends here