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