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