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