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