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