* net/tramp.el (tramp-perl-file-attributes): Print "nil" when
[bpt/emacs.git] / lisp / proced.el
CommitLineData
e6854b3f 1;;; proced.el --- operate on system processes like dired
37e4d8ed 2
ae940284 3;; Copyright (C) 2008, 2009 Free Software Foundation, Inc.
1ba1a8b9 4
37e4d8ed 5;; Author: Roland Winkler <Roland.Winkler@physik.uni-erlangen.de>
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
d74d0c42
RW
268 `((user (user . ,(concat "\\`" (user-real-login-name) "\\'")))
269 (user-running (user . ,(concat "\\`" (user-real-login-name) "\\'"))
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
398 '((t (:inherit font-lock-warning-face)))
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
d74d0c42 462 (define-key km "d" 'proced-mark) ; Dired compatibility ("delete")
37e4d8ed 463 (define-key km "m" 'proced-mark)
37e4d8ed 464 (define-key km "u" 'proced-unmark)
e6854b3f 465 (define-key km "\177" 'proced-unmark-backward)
92d9ce48 466 (define-key km "M" 'proced-mark-all)
37e4d8ed 467 (define-key km "U" 'proced-unmark-all)
e6854b3f 468 (define-key km "t" 'proced-toggle-marks)
d74d0c42
RW
469 (define-key km "C" 'proced-mark-children)
470 (define-key km "P" 'proced-mark-parents)
471 ;; filtering
472 (define-key km "f" 'proced-filter-interactive)
da643190
RW
473 (define-key km [mouse-2] 'proced-refine)
474 (define-key km "\C-m" 'proced-refine)
92d9ce48 475 ;; sorting
61548252
RW
476 (define-key km "sc" 'proced-sort-pcpu)
477 (define-key km "sm" 'proced-sort-pmem)
478 (define-key km "sp" 'proced-sort-pid)
479 (define-key km "ss" 'proced-sort-start)
d74d0c42 480 (define-key km "sS" 'proced-sort-interactive)
61548252 481 (define-key km "st" 'proced-sort-time)
d74d0c42 482 (define-key km "su" 'proced-sort-user)
da643190
RW
483 ;; similar to `Buffer-menu-sort-by-column'
484 (define-key km [header-line mouse-1] 'proced-sort-header)
d74d0c42 485 (define-key km [header-line mouse-2] 'proced-sort-header)
f1d27653 486 (define-key km "T" 'proced-toggle-tree)
d74d0c42
RW
487 ;; formatting
488 (define-key km "F" 'proced-format-interactive)
92d9ce48 489 ;; operate
9f583d14 490 (define-key km "o" 'proced-omit-processes)
92d9ce48
RW
491 (define-key km "x" 'proced-send-signal) ; Dired compatibility
492 (define-key km "k" 'proced-send-signal) ; kill processes
493 ;; misc
92d9ce48
RW
494 (define-key km "h" 'describe-mode)
495 (define-key km "?" 'proced-help)
37e4d8ed
RW
496 (define-key km [remap undo] 'proced-undo)
497 (define-key km [remap advertised-undo] 'proced-undo)
3ac09bb4 498 ;; Additional keybindings are inherited from `special-mode-map'
37e4d8ed 499 km)
d0482e4e 500 "Keymap for Proced commands.")
37e4d8ed
RW
501
502(easy-menu-define
503 proced-menu proced-mode-map "Proced Menu"
9f583d14
RW
504 `("Proced"
505 ["Mark" proced-mark
506 :help "Mark Current Process"]
507 ["Unmark" proced-unmark
508 :help "Unmark Current Process"]
509 ["Mark All" proced-mark-all
510 :help "Mark All Processes"]
511 ["Unmark All" proced-unmark-all
512 :help "Unmark All Process"]
513 ["Toggle Marks" proced-toggle-marks
514 :help "Marked Processes Become Unmarked, and Vice Versa"]
d74d0c42
RW
515 ["Mark Children" proced-mark-children
516 :help "Mark Current Process and its Children"]
517 ["Mark Parents" proced-mark-parents
518 :help "Mark Current Process and its Parents"]
e6854b3f 519 "--"
d74d0c42
RW
520 ("Filters"
521 :help "Select Filter for Process Listing"
522 ,@(mapcar (lambda (el)
523 (let ((filter (car el)))
524 `[,(symbol-name filter)
525 (proced-filter-interactive ',filter)
526 :style radio
527 :selected (eq proced-filter ',filter)]))
528 proced-filter-alist))
529 ("Sorting"
da643190 530 :help "Select Sort Scheme"
d74d0c42
RW
531 ["Sort..." proced-sort-interactive
532 :help "Sort Process List"]
533 "--"
534 ["Sort by %CPU" proced-sort-pcpu]
535 ["Sort by %MEM" proced-sort-pmem]
536 ["Sort by PID" proced-sort-pid]
537 ["Sort by START" proced-sort-start]
538 ["Sort by TIME" proced-sort-time]
539 ["Sort by USER" proced-sort-user])
540 ("Formats"
541 :help "Select Format for Process Listing"
542 ,@(mapcar (lambda (el)
543 (let ((format (car el)))
544 `[,(symbol-name format)
545 (proced-format-interactive ',format)
546 :style radio
547 :selected (eq proced-format ',format)]))
548 proced-format-alist))
f1d27653
RW
549 ["Tree Display" proced-toggle-tree
550 :style toggle
551 :selected (eval proced-tree-flag)
552 :help "Display Proced Buffer as Process Tree"]
61548252 553 "--"
9f583d14
RW
554 ["Omit Marked Processes" proced-omit-processes
555 :help "Omit Marked Processes in Process Listing."]
37e4d8ed 556 "--"
9f583d14
RW
557 ["Revert" revert-buffer
558 :help "Revert Process Listing"]
413e65fe 559 ["Auto Update" proced-toggle-auto-update
b4f671ce 560 :style toggle
413e65fe 561 :selected (eval proced-auto-update-flag)
da643190 562 :help "Auto Update of Proced Buffer"]
9f583d14 563 ["Send signal" proced-send-signal
d74d0c42 564 :help "Send Signal to Marked Processes"]))
9f583d14 565
92d9ce48 566;; helper functions
e6854b3f 567(defun proced-marker-regexp ()
61548252 568 "Return regexp matching `proced-marker-char'."
92d9ce48 569 ;; `proced-marker-char' must appear in column zero
e6854b3f
RW
570 (concat "^" (regexp-quote (char-to-string proced-marker-char))))
571
572(defun proced-success-message (action count)
61548252 573 "Display success message for ACTION performed for COUNT processes."
e6854b3f
RW
574 (message "%s %s process%s" action count (if (= 1 count) "" "es")))
575
d74d0c42
RW
576;; Unlike dired, we do not define our own commands for vertical motion.
577;; If `goal-column' is set, `next-line' and `previous-line' are fancy
578;; commands to satisfy our modest needs. If `proced-goal-attribute'
579;; and/or `goal-column' are not set, `next-line' and `previous-line'
580;; are really what we need to preserve the column of point.
581;; We use `proced-move-to-goal-column' for "non-interactive" cases only
582;; to get a well-defined position of point.
583
92d9ce48 584(defun proced-move-to-goal-column ()
da643190 585 "Move to `goal-column' if non-nil. Return position of point."
e6854b3f 586 (beginning-of-line)
d74d0c42
RW
587 (unless (eobp)
588 (if goal-column
589 (forward-char goal-column)
da643190
RW
590 (forward-char 2)))
591 (point))
d74d0c42
RW
592
593(defun proced-header-line ()
594 "Return header line for Proced buffer."
595 (list (propertize " " 'display '(space :align-to 0))
016151bb
RW
596 (if (<= (window-hscroll) (length proced-header-line))
597 (replace-regexp-in-string ;; preserve text properties
598 "\\(%\\)" "\\1\\1"
599 (substring proced-header-line (window-hscroll))))))
d74d0c42
RW
600
601(defun proced-pid-at-point ()
602 "Return pid of system process at point.
603Return nil if point is not on a process line."
604 (save-excursion
605 (beginning-of-line)
606 (if (looking-at "^. .")
607 (get-text-property (match-end 0) 'proced-pid))))
608
609;; proced mode
e6854b3f 610
3ac09bb4 611(define-derived-mode proced-mode special-mode "Proced"
079ba9b7 612 "Mode for displaying UNIX system processes and sending signals to them.
204ebc5b
RW
613Type \\[proced] to start a Proced session. In a Proced buffer
614type \\<proced-mode-map>\\[proced-mark] to mark a process for later commands.
079ba9b7
SM
615Type \\[proced-send-signal] to send signals to marked processes.
616
da643190
RW
617The initial content of a listing is defined by the variable `proced-filter'
618and the variable `proced-format'.
619The variable `proced-filter' specifies which system processes are displayed.
620The variable `proced-format' specifies which attributes are displayed for
621each process. Type \\[proced-filter-interactive] and \\[proced-format-interactive]
622to change the values of `proced-filter' and `proced-format'.
623The current value of the variable `proced-filter' is indicated in the
624mode line.
625
626The sort order of Proced listings is defined by the variable `proced-sort'.
627Type \\[proced-sort-interactive] or click on a header in the header line
628to change the sort scheme. The current sort scheme is indicated in the
629mode line, using \"+\" or \"-\" for ascending or descending sort order.
630
f1d27653
RW
631Type \\[proced-toggle-tree] to toggle whether the listing is
632displayed as process tree.
633
4ed46aef 634An existing Proced listing can be refined by typing \\[proced-refine].
da643190
RW
635Refining an existing listing does not update the variable `proced-filter'.
636
637The attribute-specific rules for formatting, filtering, sorting, and refining
638are defined in `proced-grammar-alist'.
639
3ac09bb4
RW
640After displaying or updating a Proced buffer, Proced runs the normal hook
641`proced-post-display-hook'.
642
079ba9b7
SM
643\\{proced-mode-map}"
644 (abbrev-mode 0)
645 (auto-fill-mode 0)
646 (setq buffer-read-only t
b9df5969
RW
647 truncate-lines t
648 header-line-format '(:eval (proced-header-line)))
649 (add-hook 'post-command-hook 'force-mode-line-update nil t)
079ba9b7
SM
650 (set (make-local-variable 'revert-buffer-function) 'proced-revert)
651 (set (make-local-variable 'font-lock-defaults)
d74d0c42 652 '(proced-font-lock-keywords t nil nil beginning-of-line))
413e65fe
RW
653 (if (and (not proced-auto-update-timer) proced-auto-update-interval)
654 (setq proced-auto-update-timer
655 (run-at-time t proced-auto-update-interval
656 'proced-auto-update-timer))))
216d81a1 657
079ba9b7
SM
658;;;###autoload
659(defun proced (&optional arg)
da643190 660 "Generate a listing of UNIX system processes.
37e4d8ed
RW
661If invoked with optional ARG the window displaying the process
662information will be displayed but not selected.
3ac09bb4 663Runs the normal hook `proced-post-display-hook'.
37e4d8ed 664
667df88c 665See `proced-mode' for a description of features available in Proced buffers."
37e4d8ed 666 (interactive "P")
413e65fe
RW
667 (unless proced-available
668 (error "Proced is not available on this system"))
92d9ce48 669 (let ((buffer (get-buffer-create "*Proced*")) new)
61548252 670 (set-buffer buffer)
37e4d8ed 671 (setq new (zerop (buffer-size)))
3ac09bb4
RW
672 (when new
673 (proced-mode)
674 ;; `proced-update' runs `proced-post-display-hook' only if the
675 ;; Proced buffer has been selected. Yet the following call of
676 ;; `proced-update' is for an empty Proced buffer that has not
677 ;; yet been selected. Therefore we need to call
678 ;; `proced-post-display-hook' below.
679 (proced-update t))
37e4d8ed 680 (if arg
3ac09bb4
RW
681 (progn
682 (display-buffer buffer)
683 (with-current-buffer buffer
684 (run-hooks 'proced-post-display-hook)))
61548252 685 (pop-to-buffer buffer)
3ac09bb4 686 (run-hooks 'proced-post-display-hook)
008c22f2
JL
687 (message
688 (substitute-command-keys
689 "Type \\<proced-mode-map>\\[quit-window] to quit, \\[proced-help] for help")))))
37e4d8ed 690
413e65fe 691(defun proced-auto-update-timer ()
da643190 692 "Auto-update Proced buffers using `run-at-time'."
d74d0c42
RW
693 (dolist (buf (buffer-list))
694 (with-current-buffer buf
695 (if (and (eq major-mode 'proced-mode)
413e65fe 696 proced-auto-update-flag)
d74d0c42
RW
697 (proced-update t t)))))
698
413e65fe 699(defun proced-toggle-auto-update (arg)
da643190
RW
700 "Change whether this Proced buffer is updated automatically.
701With prefix ARG, update this buffer automatically if ARG is positive,
413e65fe
RW
702otherwise do not update. Sets the variable `proced-auto-update-flag'.
703The time interval for updates is specified via `proced-auto-update-interval'."
d74d0c42 704 (interactive (list (or current-prefix-arg 'toggle)))
413e65fe
RW
705 (setq proced-auto-update-flag
706 (cond ((eq arg 'toggle) (not proced-auto-update-flag))
d74d0c42 707 (arg (> (prefix-numeric-value arg) 0))
413e65fe
RW
708 (t (not proced-auto-update-flag))))
709 (message "Proced auto update %s"
710 (if proced-auto-update-flag "enabled" "disabled")))
92d9ce48 711
3ac09bb4
RW
712;;; Mark
713
37e4d8ed
RW
714(defun proced-mark (&optional count)
715 "Mark the current (or next COUNT) processes."
716 (interactive "p")
717 (proced-do-mark t count))
718
719(defun proced-unmark (&optional count)
720 "Unmark the current (or next COUNT) processes."
721 (interactive "p")
722 (proced-do-mark nil count))
723
e6854b3f
RW
724(defun proced-unmark-backward (&optional count)
725 "Unmark the previous (or COUNT previous) processes."
61548252
RW
726 ;; Analogous to `dired-unmark-backward',
727 ;; but `ibuffer-unmark-backward' behaves different.
e6854b3f
RW
728 (interactive "p")
729 (proced-do-mark nil (- (or count 1))))
730
37e4d8ed 731(defun proced-do-mark (mark &optional count)
9f583d14 732 "Mark the current (or next COUNT) processes using MARK."
37e4d8ed 733 (or count (setq count 1))
e6854b3f 734 (let ((backward (< count 0))
37e4d8ed 735 buffer-read-only)
92d9ce48
RW
736 (setq count (1+ (if (<= 0 count) count
737 (min (1- (line-number-at-pos)) (abs count)))))
738 (beginning-of-line)
739 (while (not (or (zerop (setq count (1- count))) (eobp)))
740 (proced-insert-mark mark backward))
741 (proced-move-to-goal-column)))
37e4d8ed 742
3ac09bb4
RW
743(defun proced-toggle-marks ()
744 "Toggle marks: marked processes become unmarked, and vice versa."
745 (interactive)
746 (let ((mark-re (proced-marker-regexp))
747 buffer-read-only)
748 (save-excursion
749 (goto-char (point-min))
750 (while (not (eobp))
751 (cond ((looking-at mark-re)
752 (proced-insert-mark nil))
753 ((looking-at " ")
754 (proced-insert-mark t))
755 (t
756 (forward-line 1)))))))
757
758(defun proced-insert-mark (mark &optional backward)
759 "If MARK is non-nil, insert `proced-marker-char'.
760If BACKWARD is non-nil, move one line backwards before inserting the mark.
761Otherwise move one line forward after inserting the mark."
762 (if backward (forward-line -1))
763 (insert (if mark proced-marker-char ?\s))
764 (delete-char 1)
765 (unless backward (forward-line)))
766
37e4d8ed 767(defun proced-mark-all ()
9f583d14
RW
768 "Mark all processes.
769If `transient-mark-mode' is turned on and the region is active,
770mark the region."
37e4d8ed
RW
771 (interactive)
772 (proced-do-mark-all t))
773
774(defun proced-unmark-all ()
9f583d14
RW
775 "Unmark all processes.
776If `transient-mark-mode' is turned on and the region is active,
777unmark the region."
37e4d8ed
RW
778 (interactive)
779 (proced-do-mark-all nil))
780
781(defun proced-do-mark-all (mark)
9f583d14
RW
782 "Mark all processes using MARK.
783If `transient-mark-mode' is turned on and the region is active,
784mark the region."
3ac09bb4
RW
785 (let* ((count 0)
786 (proced-marker-char (if mark proced-marker-char ?\s))
787 (marker-re (proced-marker-regexp))
788 end buffer-read-only)
e6854b3f 789 (save-excursion
d74d0c42 790 (if (use-region-p)
9f583d14
RW
791 ;; Operate even on those lines that are only partially a part
792 ;; of region. This appears most consistent with
793 ;; `proced-move-to-goal-column'.
d74d0c42
RW
794 (progn (setq end (save-excursion
795 (goto-char (region-end))
796 (unless (looking-at "^") (forward-line))
797 (point)))
798 (goto-char (region-beginning))
799 (unless (looking-at "^") (beginning-of-line)))
9f583d14 800 (goto-char (point-min))
d74d0c42
RW
801 (setq end (point-max)))
802 (while (< (point) end)
3ac09bb4
RW
803 (unless (looking-at marker-re)
804 (setq count (1+ count))
805 (insert proced-marker-char)
806 (delete-char 1))
807 (forward-line))
808 (proced-success-message (if mark "Marked" "Unmarked") count))))
e6854b3f 809
d74d0c42
RW
810(defun proced-mark-children (ppid &optional omit-ppid)
811 "Mark child processes of process PPID.
812Also mark process PPID unless prefix OMIT-PPID is non-nil."
813 (interactive (list (proced-pid-at-point) current-prefix-arg))
814 (proced-mark-process-alist
815 (proced-filter-children proced-process-alist ppid omit-ppid)))
816
817(defun proced-mark-parents (cpid &optional omit-cpid)
818 "Mark parent processes of process CPID.
819Also mark CPID unless prefix OMIT-CPID is non-nil."
820 (interactive (list (proced-pid-at-point) current-prefix-arg))
821 (proced-mark-process-alist
822 (proced-filter-parents proced-process-alist cpid omit-cpid)))
823
824(defun proced-mark-process-alist (process-alist &optional quiet)
f1d27653
RW
825 "Mark processes in PROCESS-ALIST.
826If QUIET is non-nil suppress status message."
d74d0c42
RW
827 (let ((count 0))
828 (if process-alist
829 (let (buffer-read-only)
830 (save-excursion
831 (goto-char (point-min))
832 (while (not (eobp))
833 (when (assq (proced-pid-at-point) process-alist)
834 (insert proced-marker-char)
835 (delete-char 1)
836 (setq count (1+ count)))
837 (forward-line)))))
838 (unless quiet
839 (proced-success-message "Marked" count))))
840
e6854b3f
RW
841;; Mostly analog of `dired-do-kill-lines'.
842;; However, for negative args the target lines of `dired-do-kill-lines'
843;; include the current line, whereas `dired-mark' for negative args operates
d74d0c42 844;; on the preceding lines. Here we are consistent with `dired-mark'.
9f583d14
RW
845(defun proced-omit-processes (&optional arg quiet)
846 "Omit marked processes.
847With prefix ARG, omit that many lines starting with the current line.
848\(A negative argument omits backward.)
d74d0c42
RW
849If `transient-mark-mode' is turned on and the region is active,
850omit the processes in region.
e6854b3f 851If QUIET is non-nil suppress status message.
9f583d14 852Returns count of omitted lines."
e6854b3f
RW
853 (interactive "P")
854 (let ((mark-re (proced-marker-regexp))
855 (count 0)
856 buffer-read-only)
d74d0c42
RW
857 (cond ((use-region-p) ;; Omit active region
858 (let ((lines (count-lines (region-beginning) (region-end))))
859 (save-excursion
860 (goto-char (region-beginning))
861 (while (< count lines)
862 (proced-omit-process)
863 (setq count (1+ count))))))
864 ((not arg) ;; Omit marked lines
865 (save-excursion
866 (goto-char (point-min))
867 (while (and (not (eobp))
868 (re-search-forward mark-re nil t))
869 (proced-omit-process)
870 (setq count (1+ count)))))
871 ((< 0 arg) ;; Omit forward
872 (while (and (not (eobp)) (< count arg))
873 (proced-omit-process)
874 (setq count (1+ count))))
875 ((< arg 0) ;; Omit backward
876 (while (and (not (bobp)) (< count (- arg)))
877 (forward-line -1)
878 (proced-omit-process)
879 (setq count (1+ count)))))
92d9ce48 880 (unless (zerop count) (proced-move-to-goal-column))
9f583d14 881 (unless quiet (proced-success-message "Omitted" count))
e6854b3f 882 count))
37e4d8ed 883
d74d0c42
RW
884(defun proced-omit-process ()
885 "Omit process from listing point is on.
886Update `proced-process-alist' accordingly."
887 (setq proced-process-alist
888 (assq-delete-all (proced-pid-at-point) proced-process-alist))
889 (delete-region (line-beginning-position)
890 (save-excursion (forward-line) (point))))
891
892;;; Filtering
893
894(defun proced-filter (process-alist filter-list)
da643190
RW
895 "Apply FILTER-LIST to PROCESS-ALIST.
896Return the filtered process list."
d74d0c42
RW
897 (if (symbolp filter-list)
898 (setq filter-list (cdr (assq filter-list proced-filter-alist))))
899 (dolist (filter filter-list)
900 (let (new-alist)
901 (cond ( ;; apply function to entire process list
902 (eq (car filter) 'fun-all)
903 (setq new-alist (funcall (cdr filter) process-alist)))
904 ( ;; apply predicate to each list of attributes
905 (eq (car filter) 'function)
906 (dolist (process process-alist)
907 (if (funcall (car filter) (cdr process))
908 (push process new-alist))))
909 (t ;; apply predicate to specified attribute
910 (let ((fun (if (stringp (cdr filter))
911 `(lambda (val)
912 (string-match ,(cdr filter) val))
913 (cdr filter)))
914 value)
915 (dolist (process process-alist)
916 (setq value (cdr (assq (car filter) (cdr process))))
917 (if (and value (funcall fun value))
918 (push process new-alist))))))
919 (setq process-alist new-alist)))
920 process-alist)
921
da643190 922(defun proced-filter-interactive (scheme)
d74d0c42
RW
923 "Filter Proced buffer using SCHEME.
924When called interactively, an empty string means nil, i.e., no filtering.
da643190 925Set variable `proced-filter' to SCHEME. Revert listing."
37e4d8ed 926 (interactive
d74d0c42
RW
927 (let ((scheme (completing-read "Filter: "
928 proced-filter-alist nil t)))
da643190 929 (list (if (string= "" scheme) nil (intern scheme)))))
aa5fecb5
RW
930 ;; only update if necessary
931 (unless (eq proced-filter scheme)
932 (setq proced-filter scheme)
933 (proced-update t)))
d74d0c42 934
48152a70
RW
935(defun proced-filter-parents (process-alist pid &optional omit-pid)
936 "For PROCESS-ALIST return list of parent processes of PID.
937This list includes PID unless OMIT-PID is non-nil."
938 (let ((parent-list (unless omit-pid (list (assq pid process-alist))))
939 (process (assq pid process-alist))
940 ppid)
941 (while (and (setq ppid (cdr (assq 'ppid (cdr process))))
942 ;; Ignore a PPID that equals PID.
943 (/= ppid pid)
944 ;; Accept only PPIDs that correspond to members in PROCESS-ALIST.
945 (setq process (assq ppid process-alist)))
946 (setq pid ppid)
947 (push process parent-list))
948 parent-list))
949
950(defun proced-filter-children (process-alist ppid &optional omit-ppid)
951 "For PROCESS-ALIST return list of child processes of PPID.
952This list includes PPID unless OMIT-PPID is non-nil."
953 (let ((proced-temp-alist (proced-children-alist process-alist))
954 new-alist)
955 (dolist (pid (proced-children-pids ppid))
956 (push (assq pid process-alist) new-alist))
957 (if omit-ppid
958 (assq-delete-all ppid new-alist)
959 new-alist)))
960
961;;; Process tree
962
f1d27653
RW
963(defun proced-children-alist (process-alist)
964 "Return children alist for PROCESS-ALIST.
965The children alist has elements (PPID PID1 PID2 ...).
d74d0c42 966PPID is a parent PID. PID1, PID2, ... are the child processes of PPID.
48152a70 967The children alist inherits the sorting order of PROCESS-ALIST.
d74d0c42 968The list of children does not include grandchildren."
f1d27653
RW
969 ;; The PPIDs inherit the sorting order of PROCESS-ALIST.
970 (let ((process-tree (mapcar (lambda (a) (list (car a))) process-alist))
971 ppid)
972 (dolist (process process-alist)
d74d0c42 973 (setq ppid (cdr (assq 'ppid (cdr process))))
f1d27653
RW
974 (if (and ppid
975 ;; Ignore a PPID that equals PID.
976 (/= ppid (car process))
977 ;; Accept only PPIDs that correspond to members in PROCESS-ALIST.
978 (assq ppid process-alist))
979 (let ((temp-alist process-tree) elt)
980 (while (setq elt (pop temp-alist))
981 (when (eq ppid (car elt))
982 (setq temp-alist nil)
983 (setcdr elt (cons (car process) (cdr elt))))))))
984 ;; The child processes inherit the sorting order of PROCESS-ALIST.
985 (setq process-tree
986 (mapcar (lambda (a) (cons (car a) (nreverse (cdr a))))
987 process-tree))))
988
48152a70
RW
989(defun proced-children-pids (ppid)
990 "Return list of children PIDs of PPID (including PPID)."
991 (let ((cpids (cdr (assq ppid proced-temp-alist))))
992 (if cpids
993 (cons ppid (apply 'append (mapcar 'proced-children-pids cpids)))
994 (list ppid))))
995
f1d27653 996(defun proced-process-tree (process-alist)
48152a70
RW
997 "Return process tree for PROCESS-ALIST.
998It is an alist of alists where the car of each alist is a parent process
999and the cdr is a list of child processes according to the ppid attribute
1000of these processes.
1001The process tree inherits the sorting order of PROCESS-ALIST."
1002 (let ((proced-temp-alist (proced-children-alist process-alist))
f1d27653 1003 pid-alist proced-process-tree)
48152a70 1004 (while (setq pid-alist (pop proced-temp-alist))
f1d27653
RW
1005 (push (proced-process-tree-internal pid-alist) proced-process-tree))
1006 (nreverse proced-process-tree)))
1007
1008(defun proced-process-tree-internal (pid-alist)
1009 "Helper function for `proced-process-tree'."
1010 (let ((cpid-list (cdr pid-alist)) cpid-alist cpid)
1011 (while (setq cpid (car cpid-list))
48152a70 1012 (if (setq cpid-alist (assq cpid proced-temp-alist))
f1d27653
RW
1013 ;; Unprocessed part of process tree that needs to be
1014 ;; analyzed recursively.
1015 (progn
48152a70
RW
1016 (setq proced-temp-alist
1017 (assq-delete-all cpid proced-temp-alist))
f1d27653
RW
1018 (setcar cpid-list (proced-process-tree-internal cpid-alist)))
1019 ;; We already processed this subtree and take it "as is".
1020 (setcar cpid-list (assq cpid proced-process-tree))
1021 (setq proced-process-tree
1022 (assq-delete-all cpid proced-process-tree)))
1023 (pop cpid-list)))
1024 pid-alist)
1025
1026(defun proced-toggle-tree (arg)
48152a70 1027 "Toggle the display of the process listing as process tree.
f1d27653 1028With prefix ARG, display as process tree if ARG is positive, otherwise
48152a70
RW
1029do not display as process tree. Sets the variable `proced-tree-flag'.
1030
1031The process tree is generated from the selected processes in the
1032Proced buffer (that is, the processes in `proced-process-alist').
1033All processes that do not have a parent process in this list
1034according to their ppid attribute become the root of a process tree.
1035Each parent process is followed by its child processes.
1036The process tree inherits the chosen sorting order of the process listing,
1037that is, child processes of the same parent process are sorted using
1038the selected sorting order."
f1d27653
RW
1039 (interactive (list (or current-prefix-arg 'toggle)))
1040 (setq proced-tree-flag
1041 (cond ((eq arg 'toggle) (not proced-tree-flag))
1042 (arg (> (prefix-numeric-value arg) 0))
1043 (t (not proced-tree-flag))))
1044 (proced-update)
1045 (message "Proced process tree display %s"
1046 (if proced-tree-flag "enabled" "disabled")))
1047
1048(defun proced-tree (process-alist)
48152a70
RW
1049 "Rearrange PROCESS-ALIST as process tree.
1050If `proced-tree-flag' is non-nil, rearrange PROCESS-ALIST such that
1051every processes is followed by its child processes. Each process
1052gets a tree attribute that specifies the depth of the process in the tree.
1053A root process is a process with no parent within PROCESS-ALIST according
1054to its value of the ppid attribute. It has depth 0.
1055
1056If `proced-tree-flag' is nil, remove the tree attribute.
1057Return the rearranged process list."
f1d27653
RW
1058 (if proced-tree-flag
1059 ;; add tree attribute
1060 (let ((process-tree (proced-process-tree process-alist))
3ac09bb4 1061 (proced-tree-depth 0)
48152a70 1062 (proced-temp-alist process-alist)
f1d27653
RW
1063 proced-process-tree pt)
1064 (while (setq pt (pop process-tree))
1065 (proced-tree-insert pt))
1066 (nreverse proced-process-tree))
48152a70
RW
1067 ;; remove tree attribute
1068 (let ((process-alist process-alist))
1069 (while process-alist
1070 (setcar process-alist
1071 (assq-delete-all 'tree (car process-alist)))
1072 (pop process-alist)))
1073 process-alist))
f1d27653
RW
1074
1075(defun proced-tree-insert (process-tree)
1076 "Helper function for `proced-tree'."
48152a70 1077 (let ((pprocess (assq (car process-tree) proced-temp-alist)))
f1d27653 1078 (push (append (list (car pprocess))
3ac09bb4 1079 (list (cons 'tree proced-tree-depth))
f1d27653
RW
1080 (cdr pprocess))
1081 proced-process-tree)
1082 (if (cdr process-tree)
3ac09bb4 1083 (let ((proced-tree-depth (1+ proced-tree-depth)))
f1d27653 1084 (mapc 'proced-tree-insert (cdr process-tree))))))
d74d0c42 1085
da643190
RW
1086;; Refining
1087
1088;; Filters are used to select the processes in a new listing.
1089;; Refiners are used to narrow down further (interactively) the processes
1090;; in an existing listing.
1091
1092(defun proced-refine (&optional event)
1093 "Refine Proced listing by comparing with the attribute value at point.
1094Optional EVENT is the location of the Proced field.
1095
b4f671ce
RW
1096Refinement is controlled by the REFINER defined for each attribute ATTR
1097in `proced-grammar-alist'.
1098
4ed46aef
RW
1099If REFINER is a list of flags and point is on a process's value of ATTR,
1100this command compares the value of ATTR of every process with the value
1101of ATTR of the process at the position of point.
da643190
RW
1102
1103The predicate for the comparison of two ATTR values is defined
1104in `proced-grammar-alist'. For each return value of the predicate
b4f671ce
RW
1105a refine flag is defined in `proced-grammar-alist'. One can select
1106processes for which the value of ATTR is \"less than\", \"equal\",
1107and / or \"larger\" than ATTR of the process point is on. A process
1108is included in the new listing if the refine flag for the corresponding
1109return value of the predicate is non-nil.
da643190 1110The help-echo string for `proced-refine' uses \"+\" or \"-\" to indicate
b4f671ce
RW
1111the current values of these refine flags.
1112
1113If REFINER is a cons pair (FUNCTION . HELP-ECHO), FUNCTION is called
1114with one argument, the PID of the process at the position of point.
1115The function must return a list of PIDs that is used for the refined
1116listing. HELP-ECHO is a string that is shown when mouse is over this field.
da643190 1117
b4f671ce
RW
1118This command refines an already existing process listing generated initially
1119based on the value of the variable `proced-filter'. It does not change
1120this variable. It does not revert the listing. If you frequently need
1121a certain refinement, consider defining a new filter in `proced-filter-alist'."
d74d0c42
RW
1122 (interactive (list last-input-event))
1123 (if event (posn-set-point (event-end event)))
1124 (let ((key (get-text-property (point) 'proced-key))
1125 (pid (get-text-property (point) 'proced-pid)))
1126 (if (and key pid)
1127 (let* ((grammar (assq key proced-grammar-alist))
b4f671ce
RW
1128 (refiner (nth 7 grammar)))
1129 (when refiner
1130 (cond ((functionp (car refiner))
1131 (setq proced-process-alist (funcall (car refiner) pid)))
1132 ((consp refiner)
1133 (let ((predicate (nth 4 grammar))
1134 (ref (cdr (assq key (cdr (assq pid proced-process-alist)))))
1135 val new-alist)
1136 (dolist (process proced-process-alist)
1137 (setq val (funcall predicate (cdr (assq key (cdr process))) ref))
1138 (if (cond ((not val) (nth 2 refiner))
1139 ((eq val 'equal) (nth 1 refiner))
1140 (val (car refiner)))
1141 (push process new-alist)))
1142 (setq proced-process-alist new-alist))))
da643190 1143 ;; Do not revert listing.
d74d0c42 1144 (proced-update)))
da643190 1145 (message "No refiner defined here."))))
d74d0c42
RW
1146
1147;; Proced predicates for sorting and filtering are based on a three-valued
1148;; logic:
da643190
RW
1149;; Predicates take two arguments P1 and P2, the corresponding attribute
1150;; values of two processes. Predicates should return 'equal if P1 has
d74d0c42
RW
1151;; same rank like P2. Any other non-nil value says that P1 is "less than" P2,
1152;; or nil if not.
1153
1154(defun proced-< (num1 num2)
1155 "Return t if NUM1 less than NUM2.
1156Return `equal' if NUM1 equals NUM2. Return nil if NUM1 greater than NUM2."
1157 (if (= num1 num2)
1158 'equal
1159 (< num1 num2)))
1160
1161(defun proced-string-lessp (s1 s2)
1162 "Return t if string S1 is less than S2 in lexicographic order.
1163Return `equal' if S1 and S2 have identical contents.
1164Return nil otherwise."
1165 (if (string= s1 s2)
1166 'equal
1167 (string-lessp s1 s2)))
1168
1169(defun proced-time-lessp (t1 t2)
1170 "Return t if time value T1 is less than time value T2.
1171Return `equal' if T1 equals T2. Return nil otherwise."
1172 (with-decoded-time-value ((high1 low1 micro1 t1)
1173 (high2 low2 micro2 t2))
1174 (cond ((< high1 high2))
1175 ((< high2 high1) nil)
1176 ((< low1 low2))
1177 ((< low2 low1) nil)
1178 ((< micro1 micro2))
1179 ((< micro2 micro1) nil)
1180 (t 'equal))))
37e4d8ed 1181
d74d0c42
RW
1182;;; Sorting
1183
1184(defsubst proced-xor (b1 b2)
1185 "Return the logical exclusive or of args B1 and B2."
1186 (and (or b1 b2)
1187 (not (and b1 b2))))
1188
1189(defun proced-sort-p (p1 p2)
1190 "Predicate for sorting processes P1 and P2."
1191 (if (not (cdr proced-sort-internal))
1192 ;; only one predicate: fast scheme
1193 (let* ((sorter (car proced-sort-internal))
1194 (k1 (cdr (assq (car sorter) (cdr p1))))
1195 (k2 (cdr (assq (car sorter) (cdr p2)))))
1196 ;; if the attributes are undefined, we should really abort sorting
1197 (if (and k1 k2)
1198 (proced-xor (funcall (nth 1 sorter) k1 k2)
1199 (nth 2 sorter))))
1200 (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
1201 (catch 'done
1202 (while (setq sorter (pop sort-list))
1203 (setq k1 (cdr (assq (car sorter) (cdr p1)))
1204 k2 (cdr (assq (car sorter) (cdr p2)))
1205 predicate
1206 (if (and k1 k2)
1207 (funcall (nth 1 sorter) k1 k2)))
1208 (if (not (eq predicate 'equal))
1209 (throw 'done (proced-xor predicate (nth 2 sorter)))))
1210 (eq t predicate)))))
1211
b4f671ce 1212(defun proced-sort (process-alist sorter descend)
d74d0c42 1213 "Sort PROCESS-ALIST using scheme SORTER.
b4f671ce
RW
1214SORTER is a scheme like `proced-sort'.
1215DESCEND is non-nil if the first element of SORTER is sorted
1216in descending order.
da643190 1217Return the sorted process list."
d74d0c42
RW
1218 ;; translate SORTER into a list of lists (KEY PREDICATE REVERSE)
1219 (setq proced-sort-internal
1220 (mapcar (lambda (arg)
1221 (let ((grammar (assq arg proced-grammar-alist)))
f1d27653
RW
1222 (unless (nth 4 grammar)
1223 (error "Attribute %s not sortable" (car grammar)))
d74d0c42
RW
1224 (list arg (nth 4 grammar) (nth 5 grammar))))
1225 (cond ((listp sorter) sorter)
1226 ((and (symbolp sorter)
1227 (nth 6 (assq sorter proced-grammar-alist))))
1228 ((symbolp sorter) (list sorter))
1229 (t (error "Sorter undefined %s" sorter)))))
1230 (if proced-sort-internal
b4f671ce
RW
1231 (progn
1232 ;; splice DESCEND into the list
1233 (setcar proced-sort-internal
1234 (list (caar proced-sort-internal)
1235 (nth 1 (car proced-sort-internal)) descend))
1236 (sort process-alist 'proced-sort-p))
d74d0c42
RW
1237 process-alist))
1238
4ed46aef 1239(defun proced-sort-interactive (scheme &optional arg)
d74d0c42
RW
1240 "Sort Proced buffer using SCHEME.
1241When called interactively, an empty string means nil, i.e., no sorting.
da643190 1242
4ed46aef
RW
1243Prefix ARG controls sort order:
1244- If prefix ARG is positive (negative), sort in ascending (descending) order.
1245- If ARG is nil or 'no-arg and SCHEME is equal to the previous sorting scheme,
1246 reverse the sorting order.
1247- If ARG is nil or 'no-arg and SCHEME differs from the previous sorting scheme,
1248 adopt the sorting order defined for SCHEME in `proced-grammar-alist'.
b4f671ce 1249
da643190
RW
1250Set variable `proced-sort' to SCHEME. The current sort scheme is displayed
1251in the mode line, using \"+\" or \"-\" for ascending or descending order."
d74d0c42 1252 (interactive
f1d27653
RW
1253 (let* (choices
1254 (scheme (completing-read "Sort attribute: "
1255 (dolist (grammar proced-grammar-alist choices)
1256 (if (nth 4 grammar)
1257 (push (list (car grammar)) choices)))
1258 nil t)))
d74d0c42 1259 (list (if (string= "" scheme) nil (intern scheme))
4ed46aef
RW
1260 ;; like 'toggle in `define-derived-mode'
1261 (or current-prefix-arg 'no-arg))))
1262
b4f671ce 1263 (setq proced-descend
4ed46aef
RW
1264 ;; If `proced-sort-interactive' is called repeatedly for the same
1265 ;; sort key, the sort order is reversed.
1266 (cond ((and (eq arg 'no-arg) (equal proced-sort scheme))
1267 (not proced-descend))
1268 ((eq arg 'no-arg)
1269 (nth 5 (assq (if (consp scheme) (car scheme) scheme)
1270 proced-grammar-alist)))
1271 (arg (< (prefix-numeric-value arg) 0))
1272 ((equal proced-sort scheme)
1273 (not proced-descend))
1274 (t (nth 5 (assq (if (consp scheme) (car scheme) scheme)
1275 proced-grammar-alist))))
b4f671ce 1276 proced-sort scheme)
4ed46aef 1277 (proced-update))
d74d0c42 1278
4ed46aef 1279(defun proced-sort-pcpu (&optional arg)
b4f671ce 1280 "Sort Proced buffer by percentage CPU time (%CPU).
4ed46aef
RW
1281Prefix ARG controls sort order, see `proced-sort-interactive'."
1282 (interactive (list (or current-prefix-arg 'no-arg)))
1283 (proced-sort-interactive 'pcpu arg))
d74d0c42 1284
4ed46aef 1285(defun proced-sort-pmem (&optional arg)
b4f671ce 1286 "Sort Proced buffer by percentage memory usage (%MEM).
4ed46aef
RW
1287Prefix ARG controls sort order, see `proced-sort-interactive'."
1288 (interactive (list (or current-prefix-arg 'no-arg)))
1289 (proced-sort-interactive 'pmem arg))
d74d0c42 1290
4ed46aef 1291(defun proced-sort-pid (&optional arg)
b4f671ce 1292 "Sort Proced buffer by PID.
4ed46aef
RW
1293Prefix ARG controls sort order, see `proced-sort-interactive'."
1294 (interactive (list (or current-prefix-arg 'no-arg)))
1295 (proced-sort-interactive 'pid arg))
d74d0c42 1296
4ed46aef 1297(defun proced-sort-start (&optional arg)
b4f671ce 1298 "Sort Proced buffer by time the command started (START).
4ed46aef
RW
1299Prefix ARG controls sort order, see `proced-sort-interactive'."
1300 (interactive (list (or current-prefix-arg 'no-arg)))
1301 (proced-sort-interactive 'start arg))
d74d0c42 1302
4ed46aef 1303(defun proced-sort-time (&optional arg)
b4f671ce 1304 "Sort Proced buffer by CPU time (TIME).
4ed46aef
RW
1305Prefix ARG controls sort order, see `proced-sort-interactive'."
1306 (interactive (list (or current-prefix-arg 'no-arg)))
1307 (proced-sort-interactive 'time arg))
d74d0c42 1308
4ed46aef 1309(defun proced-sort-user (&optional arg)
b4f671ce 1310 "Sort Proced buffer by USER.
4ed46aef
RW
1311Prefix ARG controls sort order, see `proced-sort-interactive'."
1312 (interactive (list (or current-prefix-arg 'no-arg)))
1313 (proced-sort-interactive 'user arg))
d74d0c42 1314
4ed46aef 1315(defun proced-sort-header (event &optional arg)
d74d0c42
RW
1316 "Sort Proced listing based on an attribute.
1317EVENT is a mouse event with starting position in the header line.
d0482e4e 1318It is converted to the corresponding attribute key.
b4f671ce 1319This command updates the variable `proced-sort'.
4ed46aef
RW
1320Prefix ARG controls sort order, see `proced-sort-interactive'."
1321 (interactive (list last-input-event (or last-prefix-arg 'no-arg)))
d74d0c42
RW
1322 (let ((start (event-start event))
1323 col key)
1324 (save-selected-window
1325 (select-window (posn-window start))
aa5fecb5 1326 (setq col (+ (1- (car (posn-actual-col-row start)))
d74d0c42
RW
1327 (window-hscroll)))
1328 (when (and (<= 0 col) (< col (length proced-header-line)))
1329 (setq key (get-text-property col 'proced-key proced-header-line))
1330 (if key
4ed46aef 1331 (proced-sort-interactive key arg)
d74d0c42
RW
1332 (message "No sorter defined here."))))))
1333
1334;;; Formating
1335
1336(defun proced-format-time (time)
667df88c 1337 "Format time interval TIME."
d74d0c42
RW
1338 (let* ((ftime (float-time time))
1339 (days (truncate ftime 86400))
1340 (ftime (mod ftime 86400))
1341 (hours (truncate ftime 3600))
1342 (ftime (mod ftime 3600))
1343 (minutes (truncate ftime 60))
1344 (seconds (mod ftime 60)))
1345 (cond ((< 0 days)
1346 (format "%d-%02d:%02d:%02d" days hours minutes seconds))
1347 ((< 0 hours)
1348 (format "%02d:%02d:%02d" hours minutes seconds))
1349 (t
1350 (format "%02d:%02d" minutes seconds)))))
1351
1352(defun proced-format-start (start)
1353 "Format time START.
1354The return string is always 6 characters wide."
1355 (let ((d-start (decode-time start))
1356 (d-current (decode-time)))
1357 (cond ( ;; process started in previous years
1358 (< (nth 5 d-start) (nth 5 d-current))
1359 (format-time-string " %Y" start))
1360 ;; process started today
1361 ((and (= (nth 3 d-start) (nth 3 d-current))
1362 (= (nth 4 d-start) (nth 4 d-current)))
1363 (format-time-string " %H:%M" start))
1364 (t ;; process started this year
1365 (format-time-string "%b %e" start)))))
1366
1367(defun proced-format-ttname (ttname)
da643190 1368 "Format attribute TTNAME, omitting path \"/dev/\"."
d74d0c42 1369 ;; Does this work for all systems?
da643190
RW
1370 (substring ttname (if (string-match "\\`/dev/" ttname)
1371 (match-end 0) 0)))
1372
f1d27653
RW
1373(defun proced-format-tree (tree)
1374 "Format attribute TREE."
1375 (concat (make-string tree ?\s) (number-to-string tree)))
1376
b4f671ce 1377;; Proced assumes that every process occupies only one line in the listing.
da643190
RW
1378(defun proced-format-args (args)
1379 "Format attribute ARGS.
1380Replace newline characters by \"^J\" (two characters)."
1381 (replace-regexp-in-string "\n" "^J" args))
d74d0c42
RW
1382
1383(defun proced-format (process-alist format)
1384 "Display PROCESS-ALIST using FORMAT."
1385 (if (symbolp format)
1386 (setq format (cdr (assq format proced-format-alist))))
b4f671ce
RW
1387
1388 ;; Not all systems give us all attributes. We take `emacs-pid' as a
1389 ;; representative process PID. If FORMAT contains a list of alternative
1390 ;; attributes, we take the first attribute that is non-nil for `emacs-pid'.
1391 ;; If none of the alternatives is non-nil, the attribute is ignored
1392 ;; in the listing.
1393 (let ((standard-attributes
1394 (car (proced-process-attributes (list (emacs-pid)))))
1395 new-format fmi)
3ac09bb4
RW
1396 (if (and proced-tree-flag
1397 (assq 'ppid standard-attributes))
1398 (push (cons 'tree 0) standard-attributes))
b4f671ce
RW
1399 (dolist (fmt format)
1400 (if (symbolp fmt)
1401 (if (assq fmt standard-attributes)
1402 (push fmt new-format))
1403 (while (setq fmi (pop fmt))
1404 (when (assq fmi standard-attributes)
1405 (push fmi new-format)
1406 (setq fmt nil)))))
1407 (setq format (nreverse new-format)))
1408
d74d0c42 1409 (insert (make-string (length process-alist) ?\n))
b4f671ce
RW
1410 (let ((whitespace " ") (unknown "?")
1411 (sort-key (if (consp proced-sort) (car proced-sort) proced-sort))
1412 header-list grammar)
d74d0c42 1413 ;; Loop over all attributes
b4f671ce 1414 (while (setq grammar (assq (pop format) proced-grammar-alist))
d74d0c42 1415 (let* ((key (car grammar))
da643190
RW
1416 (fun (cond ((stringp (nth 2 grammar))
1417 `(lambda (arg) (format ,(nth 2 grammar) arg)))
1418 ((not (nth 2 grammar)) 'identity)
1419 ( t (nth 2 grammar))))
d74d0c42
RW
1420 (whitespace (if format whitespace ""))
1421 ;; Text properties:
1422 ;; We use the text property `proced-key' to store in each
1423 ;; field the corresponding key.
1424 ;; Of course, the sort predicate appearing in help-echo
1425 ;; is only part of the story. But it gives the main idea.
f1d27653
RW
1426 (hprops
1427 (if (nth 4 grammar)
1428 (let ((descend (if (eq key sort-key) proced-descend (nth 5 grammar))))
1429 `(proced-key ,key mouse-face highlight
1430 help-echo ,(format proced-header-help-echo
1431 (if descend "-" "+")
1432 (nth 1 grammar)
1433 (if descend "descending" "ascending"))))))
b4f671ce
RW
1434 (refiner (nth 7 grammar))
1435 (fprops
1436 (cond ((functionp (car refiner))
1437 `(proced-key ,key mouse-face highlight
1438 help-echo ,(format "mouse-2, RET: %s"
3ac09bb4 1439 (nth 1 refiner))))
b4f671ce
RW
1440 ((consp refiner)
1441 `(proced-key ,key mouse-face highlight
1442 help-echo ,(format "mouse-2, RET: refine by attribute %s %s"
d74d0c42
RW
1443 (nth 1 grammar)
1444 (mapconcat (lambda (s)
1445 (if s "+" "-"))
b4f671ce 1446 refiner ""))))))
d74d0c42
RW
1447 value)
1448
da643190 1449 ;; highlight the header of the sort column
b4f671ce 1450 (if (eq key sort-key)
aa5fecb5 1451 (setq hprops (append '(face proced-sort-header) hprops)))
d74d0c42
RW
1452 (goto-char (point-min))
1453 (cond ( ;; fixed width of output field
1454 (numberp (nth 3 grammar))
1455 (dolist (process process-alist)
1456 (end-of-line)
1457 (setq value (cdr (assq key (cdr process))))
1458 (insert (if value
1459 (apply 'propertize (funcall fun value) fprops)
b4f671ce
RW
1460 (format (concat "%" (number-to-string (nth 3 grammar)) "s")
1461 unknown))
d74d0c42
RW
1462 whitespace)
1463 (forward-line))
1464 (push (format (concat "%" (number-to-string (nth 3 grammar)) "s")
1465 (apply 'propertize (nth 1 grammar) hprops))
1466 header-list))
1467
1468 ( ;; last field left-justified
1469 (and (not format) (eq 'left (nth 3 grammar)))
1470 (dolist (process process-alist)
1471 (end-of-line)
1472 (setq value (cdr (assq key (cdr process))))
b4f671ce
RW
1473 (insert (if value (apply 'propertize (funcall fun value) fprops)
1474 unknown))
d74d0c42
RW
1475 (forward-line))
1476 (push (apply 'propertize (nth 1 grammar) hprops) header-list))
1477
1478 (t ;; calculated field width
1479 (let ((width (length (nth 1 grammar)))
1480 field-list value)
1481 (dolist (process process-alist)
1482 (setq value (cdr (assq key (cdr process))))
1483 (if value
1484 (setq value (apply 'propertize (funcall fun value) fprops)
1485 width (max width (length value))
1486 field-list (cons value field-list))
b4f671ce
RW
1487 (push unknown field-list)
1488 (setq width (max width (length unknown)))))
d74d0c42
RW
1489 (let ((afmt (concat "%" (if (eq 'left (nth 3 grammar)) "-" "")
1490 (number-to-string width) "s")))
1491 (push (format afmt (apply 'propertize (nth 1 grammar) hprops))
1492 header-list)
1493 (dolist (value (nreverse field-list))
1494 (end-of-line)
1495 (insert (format afmt value) whitespace)
1496 (forward-line))))))))
1497
1498 ;; final cleanup
1499 (goto-char (point-min))
1500 (dolist (process process-alist)
1501 ;; We use the text property `proced-pid' to store in each line
1502 ;; the corresponding pid
1503 (put-text-property (point) (line-end-position) 'proced-pid (car process))
1504 (forward-line))
1505 ;; Set header line
1506 (setq proced-header-line
1507 (mapconcat 'identity (nreverse header-list) whitespace))
1508 (if (string-match "[ \t]+$" proced-header-line)
1509 (setq proced-header-line (substring proced-header-line 0
1510 (match-beginning 0))))
1511 ;; (delete-trailing-whitespace)
1512 (goto-char (point-min))
1513 (while (re-search-forward "[ \t\r]+$" nil t)
1514 (delete-region (match-beginning 0) (match-end 0)))))
b9df5969 1515
d74d0c42
RW
1516(defun proced-format-interactive (scheme &optional revert)
1517 "Format Proced buffer using SCHEME.
1518When called interactively, an empty string means nil, i.e., no formatting.
da643190 1519Set variable `proced-format' to SCHEME.
d74d0c42
RW
1520With prefix REVERT non-nil revert listing."
1521 (interactive
1522 (let ((scheme (completing-read "Format: "
1523 proced-format-alist nil t)))
1524 (list (if (string= "" scheme) nil (intern scheme))
1525 current-prefix-arg)))
aa5fecb5
RW
1526 ;; only update if necessary
1527 (when (or (not (eq proced-format scheme)) revert)
1528 (setq proced-format scheme)
1529 (proced-update revert)))
d74d0c42
RW
1530
1531;; generate listing
1532
b4f671ce 1533(defun proced-process-attributes (&optional pid-list)
d74d0c42 1534 "Return alist of attributes for each system process.
b4f671ce
RW
1535This alist can be customized via `proced-custom-attributes'.
1536Optional arg PID-LIST is a list of PIDs of system process that are analyzed.
1537If no attributes are known for a process (possibly because it already died)
1538the process is ignored."
1539 ;; Should we make it customizable whether processes with empty attribute
1540 ;; lists are ignored? When would such processes be of interest?
3ac09bb4 1541 (let (process-alist attributes attr)
b4f671ce 1542 (dolist (pid (or pid-list (list-system-processes)) process-alist)
a20878b6 1543 (when (setq attributes (process-attributes pid))
3ac09bb4
RW
1544 (setq attributes (cons (cons 'pid pid) attributes))
1545 (dolist (fun proced-custom-attributes)
1546 (if (setq attr (funcall fun attributes))
1547 (push attr attributes)))
1548 (push (cons pid attributes) process-alist)))))
d74d0c42
RW
1549
1550(defun proced-update (&optional revert quiet)
d0482e4e 1551 "Update the Proced process information. Preserves point and marks.
d74d0c42 1552With prefix REVERT non-nil, revert listing.
3ac09bb4
RW
1553Suppress status information if QUIET is nil.
1554After updating a displayed Proced buffer run the normal hook
1555`proced-post-display-hook'."
e6854b3f 1556 ;; This is the main function that generates and updates the process listing.
d74d0c42
RW
1557 (interactive "P")
1558 (setq revert (or revert (not proced-process-alist)))
1559 (or quiet (message (if revert "Updating process information..."
1560 "Updating process display...")))
da643190
RW
1561 (if revert ;; evaluate all processes
1562 (setq proced-process-alist (proced-process-attributes)))
1563 ;; filtering and sorting
1564 (setq proced-process-alist
b4f671ce
RW
1565 (proced-sort (proced-filter proced-process-alist proced-filter)
1566 proced-sort proced-descend))
da643190 1567
f1d27653
RW
1568 ;; display as process tree?
1569 (setq proced-process-alist
1570 (proced-tree proced-process-alist))
1571
da643190
RW
1572 ;; It is useless to keep undo information if we revert, filter, or
1573 ;; refine the listing so that `proced-process-alist' has changed.
1574 ;; We could keep the undo information if we only re-sort the buffer.
1575 ;; Would that be useful? Re-re-sorting is easy, too.
1576 (if (consp buffer-undo-list)
1577 (setq buffer-undo-list nil))
1578 (let ((buffer-undo-list t)
1579 ;; If point is on a field, we try to return point to that field.
1580 ;; Otherwise we try to return to the same column
1581 (old-pos (let ((pid (proced-pid-at-point))
1582 (key (get-text-property (point) 'proced-key)))
1583 (list pid key ; can both be nil
d74d0c42
RW
1584 (if key
1585 (if (get-text-property (1- (point)) 'proced-key)
1586 (- (point) (previous-single-property-change
1587 (point) 'proced-key))
1588 0)
1589 (current-column)))))
1590 buffer-read-only mp-list)
37e4d8ed 1591 ;; remember marked processes (whatever the mark was)
d74d0c42
RW
1592 (goto-char (point-min))
1593 (while (re-search-forward "^\\(\\S-\\)" nil t)
1594 (push (cons (save-match-data (proced-pid-at-point))
92d9ce48 1595 (match-string-no-properties 1)) mp-list))
da643190 1596
d74d0c42 1597 ;; generate listing
37e4d8ed 1598 (erase-buffer)
d74d0c42 1599 (proced-format proced-process-alist proced-format)
37e4d8ed
RW
1600 (goto-char (point-min))
1601 (while (not (eobp))
1602 (insert " ")
1603 (forward-line))
d74d0c42
RW
1604 (setq proced-header-line (concat " " proced-header-line))
1605 (if revert (set-buffer-modified-p nil))
da643190 1606
d74d0c42
RW
1607 ;; set `goal-column'
1608 (let ((grammar (assq proced-goal-attribute proced-grammar-alist)))
1609 (setq goal-column ;; set to nil if no match
1610 (if (and grammar
1611 (not (zerop (buffer-size)))
1612 (string-match (regexp-quote (nth 1 grammar))
1613 proced-header-line))
1614 (if (nth 3 grammar)
1615 (match-beginning 0)
1616 (match-end 0)))))
da643190 1617
204ebc5b
RW
1618 ;; Restore process marks and buffer position (if possible).
1619 ;; Sometimes this puts point in the middle of the proced buffer
da643190 1620 ;; where it is not interesting. Is there a better / more flexible solution?
92d9ce48 1621 (goto-char (point-min))
da643190
RW
1622 (let (pid mark new-pos)
1623 (if (or mp-list (car old-pos))
d74d0c42
RW
1624 (while (not (eobp))
1625 (setq pid (proced-pid-at-point))
1626 (when (setq mark (assq pid mp-list))
1627 (insert (cdr mark))
1628 (delete-char 1)
1629 (beginning-of-line))
1630 (when (eq (car old-pos) pid)
1631 (if (nth 1 old-pos)
1632 (let ((limit (line-end-position)) pos)
1633 (while (and (not new-pos)
1634 (setq pos (next-property-change (point) nil limit)))
1635 (goto-char pos)
1636 (when (eq (nth 1 old-pos)
1637 (get-text-property (point) 'proced-key))
1638 (forward-char (min (nth 2 old-pos)
1639 (- (next-property-change (point))
1640 (point))))
1641 (setq new-pos (point))))
1642 (unless new-pos
da643190
RW
1643 ;; we found the process, but the field of point
1644 ;; is not listed anymore
1645 (setq new-pos (proced-move-to-goal-column))))
d74d0c42
RW
1646 (setq new-pos (min (+ (line-beginning-position) (nth 2 old-pos))
1647 (line-end-position)))))
da643190
RW
1648 (forward-line)))
1649 (if new-pos
1650 (goto-char new-pos)
1651 (goto-char (point-min))
1652 (proced-move-to-goal-column)))
61548252 1653 ;; update modeline
da643190
RW
1654 ;; Does the long `mode-name' clutter the modeline? It would be nice
1655 ;; to have some other location for displaying the values of the various
1656 ;; flags that affect the behavior of proced (flags one might want
1657 ;; to change on the fly). Where??
d74d0c42
RW
1658 (setq mode-name
1659 (concat "Proced"
1660 (if proced-filter
1661 (concat ": " (symbol-name proced-filter))
1662 "")
1663 (if proced-sort
b4f671ce 1664 (let* ((key (if (consp proced-sort) (car proced-sort)
d74d0c42
RW
1665 proced-sort))
1666 (grammar (assq key proced-grammar-alist)))
b4f671ce 1667 (concat " by " (if proced-descend "-" "+")
d74d0c42
RW
1668 (nth 1 grammar)))
1669 "")))
61548252 1670 (force-mode-line-update)
3ac09bb4
RW
1671 ;; run `proced-post-display-hook' only for a displayed buffer.
1672 (if (get-buffer-window) (run-hooks 'proced-post-display-hook))
61548252 1673 ;; done
37e4d8ed 1674 (or quiet (input-pending-p)
d74d0c42
RW
1675 (message (if revert "Updating process information...done."
1676 "Updating process display...done.")))))
37e4d8ed
RW
1677
1678(defun proced-revert (&rest args)
4ed46aef
RW
1679 "Reevaluate the process listing based on the currently running processes.
1680Preserves point and marks."
d74d0c42 1681 (proced-update t))
37e4d8ed 1682
37e4d8ed
RW
1683(defun proced-send-signal (&optional signal)
1684 "Send a SIGNAL to the marked processes.
9f583d14 1685If no process is marked, operate on current process.
37e4d8ed 1686SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
3ac09bb4
RW
1687If SIGNAL is nil display marked processes and query interactively for SIGNAL.
1688After sending the signal, this command runs the normal hook
1689`proced-after-send-signal-hook'."
37e4d8ed 1690 (interactive)
d74d0c42
RW
1691 (let ((regexp (proced-marker-regexp))
1692 process-alist)
37e4d8ed
RW
1693 ;; collect marked processes
1694 (save-excursion
1695 (goto-char (point-min))
1696 (while (re-search-forward regexp nil t)
d74d0c42
RW
1697 (push (cons (proced-pid-at-point)
1698 ;; How much info should we collect here?
3ac09bb4
RW
1699 (buffer-substring-no-properties
1700 (+ 2 (line-beginning-position))
1701 (line-end-position)))
d74d0c42
RW
1702 process-alist)))
1703 (setq process-alist
1704 (if process-alist
1705 (nreverse process-alist)
9f583d14 1706 ;; take current process
d74d0c42
RW
1707 (list (cons (proced-pid-at-point)
1708 (buffer-substring-no-properties
1709 (+ 2 (line-beginning-position))
1710 (line-end-position))))))
9f583d14
RW
1711 (unless signal
1712 ;; Display marked processes (code taken from `dired-mark-pop-up').
9a9af856 1713 (let ((bufname "*Marked Processes*")
d74d0c42 1714 (header-line (substring-no-properties proced-header-line)))
9f583d14
RW
1715 (with-current-buffer (get-buffer-create bufname)
1716 (setq truncate-lines t
d74d0c42 1717 proced-header-line header-line ; inherit header line
9f583d14
RW
1718 header-line-format '(:eval (proced-header-line)))
1719 (add-hook 'post-command-hook 'force-mode-line-update nil t)
9a9af856
SM
1720 (let ((inhibit-read-only t))
1721 (erase-buffer)
1722 (buffer-disable-undo)
1723 (setq buffer-read-only t)
1724 (dolist (process process-alist)
1725 (insert " " (cdr process) "\n")))
9f583d14 1726 (save-window-excursion
016151bb
RW
1727 ;; Analogous to `dired-pop-to-buffer'
1728 ;; Don't split window horizontally. (Bug#1806)
1729 (let (split-width-threshold)
1730 (pop-to-buffer (current-buffer)))
3ac09bb4 1731 (fit-window-to-buffer (get-buffer-window) nil 1)
9f583d14 1732 (let* ((completion-ignore-case t)
d74d0c42 1733 (pnum (if (= 1 (length process-alist))
9f583d14 1734 "1 process"
d74d0c42 1735 (format "%d processes" (length process-alist))))
9a9af856
SM
1736 (completion-annotate-function
1737 (lambda (s) (cdr (assoc s proced-signal-list)))))
1738 (setq signal
1739 (completing-read (concat "Send signal [" pnum
1740 "] (default TERM): ")
1741 proced-signal-list
1742 nil nil nil nil "TERM")))))))
f67cf064
RW
1743 ;; send signal
1744 (let ((count 0)
1745 failures)
1746 ;; Why not always use `signal-process'? See
1747 ;; http://lists.gnu.org/archive/html/emacs-devel/2008-03/msg02955.html
1748 (if (functionp proced-signal-function)
1749 ;; use built-in `signal-process'
1750 (let ((signal (if (stringp signal)
1751 (if (string-match "\\`[0-9]+\\'" signal)
1752 (string-to-number signal)
1753 (make-symbol signal))
1754 signal))) ; number
d74d0c42 1755 (dolist (process process-alist)
f67cf064
RW
1756 (condition-case err
1757 (if (zerop (funcall
1758 proced-signal-function (car process) signal))
1759 (setq count (1+ count))
1760 (proced-log "%s\n" (cdr process))
1761 (push (cdr process) failures))
1762 (error ; catch errors from failed signals
1763 (proced-log "%s\n" err)
1764 (proced-log "%s\n" (cdr process))
1765 (push (cdr process) failures)))))
1766 ;; use external system call
1767 (let ((signal (concat "-" (if (numberp signal)
1768 (number-to-string signal) signal))))
1769 (dolist (process process-alist)
1770 (with-temp-buffer
1771 (condition-case err
1772 (if (zerop (call-process
1773 proced-signal-function nil t nil
1774 signal (number-to-string (car process))))
1775 (setq count (1+ count))
1776 (proced-log (current-buffer))
1777 (proced-log "%s\n" (cdr process))
1778 (push (cdr process) failures))
1779 (error ; catch errors from failed signals
1780 (proced-log (current-buffer))
1781 (proced-log "%s\n" (cdr process))
1782 (push (cdr process) failures)))))))
1783 (if failures
1784 ;; Proced error message are not always very precise.
1785 ;; Can we issue a useful one-line summary in the
1786 ;; message area (using FAILURES) if only one signal failed?
1787 (proced-log-summary
1788 signal
1789 (format "%d of %d signal%s failed"
1790 (length failures) (length process-alist)
1791 (if (= 1 (length process-alist)) "" "s")))
1792 (proced-success-message "Sent signal to" count)))
1793 ;; final clean-up
1794 (run-hooks 'proced-after-send-signal-hook)))
9f583d14 1795
d74d0c42 1796;; similar to `dired-why'
9f583d14
RW
1797(defun proced-why ()
1798 "Pop up a buffer with error log output from Proced.
1799A group of errors from a single command ends with a formfeed.
1800Thus, use \\[backward-page] to find the beginning of a group of errors."
1801 (interactive)
1802 (if (get-buffer proced-log-buffer)
d74d0c42
RW
1803 (save-selected-window
1804 ;; move `proced-log-buffer' to the front of the buffer list
1805 (select-window (display-buffer (get-buffer proced-log-buffer)))
1806 (setq truncate-lines t)
1807 (set-buffer-modified-p nil)
1808 (setq buffer-read-only t)
1809 (goto-char (point-max))
1810 (forward-line -1)
1811 (backward-page 1)
1812 (recenter 0))))
9f583d14
RW
1813
1814;; similar to `dired-log'
1815(defun proced-log (log &rest args)
1816 "Log a message or the contents of a buffer.
1817If LOG is a string and there are more args, it is formatted with
1818those ARGS. Usually the LOG string ends with a \\n.
1819End each bunch of errors with (proced-log t signal):
1820this inserts the current time, buffer and signal at the start of the page,
1821and \f (formfeed) at the end."
1822 (let ((obuf (current-buffer)))
1823 (with-current-buffer (get-buffer-create proced-log-buffer)
1824 (goto-char (point-max))
d74d0c42 1825 (let (buffer-read-only)
9f583d14
RW
1826 (cond ((stringp log)
1827 (insert (if args
1828 (apply 'format log args)
1829 log)))
1830 ((bufferp log)
1831 (insert-buffer-substring log))
1832 ((eq t log)
1833 (backward-page 1)
1834 (unless (bolp)
1835 (insert "\n"))
1836 (insert (current-time-string)
1837 "\tBuffer `" (buffer-name obuf) "', "
1838 (format "signal `%s'\n" (car args)))
1839 (goto-char (point-max))
1840 (insert "\f\n")))))))
1841
1842;; similar to `dired-log-summary'
1843(defun proced-log-summary (signal string)
1844 "State a summary of SIGNAL's failures, in echo area and log buffer.
1845STRING is an overall summary of the failures."
1846 (message "Signal %s: %s--type ? for details" signal string)
1847 ;; Log a summary describing a bunch of errors.
1848 (proced-log (concat "\n" string "\n"))
1849 (proced-log t signal))
37e4d8ed
RW
1850
1851(defun proced-help ()
d0482e4e 1852 "Provide help for the Proced user."
37e4d8ed 1853 (interactive)
9f583d14 1854 (proced-why)
37e4d8ed
RW
1855 (if (eq last-command 'proced-help)
1856 (describe-mode)
1857 (message proced-help-string)))
1858
1859(defun proced-undo ()
d0482e4e
JB
1860 "Undo in a Proced buffer.
1861This doesn't recover killed processes, it just undoes changes in the Proced
37e4d8ed
RW
1862buffer. You can use it to recover marks."
1863 (interactive)
1864 (let (buffer-read-only)
1865 (undo))
92d9ce48 1866 (message "Change in Proced buffer undone.
37e4d8ed
RW
1867Killed processes cannot be recovered by Emacs."))
1868
1869(provide 'proced)
1870
dfab9988 1871;; arch-tag: a6e312ad-9032-45aa-972d-31a8cfc545af
9f583d14 1872;;; proced.el ends here