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