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, or (at your option)
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; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
27 ;; Proced makes an Emacs buffer containing a listing of the current system
28 ;; processes (using ps(1)). You can use the normal Emacs commands
29 ;; to move around in this buffer, and special Proced commands to operate
30 ;; on the processes listed.
33 ;; - sort by CPU time or other criteria
34 ;; - filter by user name or other criteria
35 ;; - automatic update of process list
45 (defcustom proced-procname-column-regexp
"\\b\\(CMD\\|COMMAND\\)\\b"
46 "If non-nil, regexp that defines the `proced-procname-column'."
48 :type
'(choice (const :tag
"none" nil
)
49 (regexp :tag
"regexp")))
51 (defcustom proced-command-alist
52 (cond ((memq system-type
'(berkeley-unix netbsd
))
53 '(("user" ("ps" "-uxgww") 2)
54 ("user-running" ("ps" "-uxrgww") 2)
55 ("all" ("ps" "-auxgww") 2)
56 ("all-running" ("ps" "-auxrgww") 2)))
57 ((memq system-type
'(linux lignux gnu
/linux
))
58 `(("user" ("ps" "uxwww") 2)
59 ("user-running" ("ps" "uxrwww") 2)
60 ("all" ("ps" "auxwww") 2)
61 ("all-running" ("ps" "auxrwww") 2)
62 ("emacs" ("ps" "--pid" ,(number-to-string (emacs-pid))
63 "--ppid" ,(number-to-string (emacs-pid))
65 ((memq system-type
'(darwin))
66 `(("user" ("ps" "-u" ,(number-to-string (user-uid))) 2)
67 ("all" ("ps" "-Au") 2)))
68 (t ; standard syntax doesn't allow us to list running processes only
69 `(("user" ("ps" "-fu" ,(number-to-string (user-uid))) 2)
70 ("all" ("ps" "-ef") 2))))
71 "Alist of commands to get list of processes.
72 Each element has the form (NAME COMMAND PID-COLUMN).
73 NAME is a shorthand name to select the type of listing.
74 COMMAND is a list (COMMAND-NAME ARG1 ARG2 ...),
75 where COMMAND-NAME is the command to generate the listing (usually \"ps\").
76 ARG1, ARG2, ... are arguments passed to COMMAND-NAME to generate
77 a particular listing. These arguments differ under various operating systems.
78 PID-COLUMN is the column number (starting from 1) of the process ID."
80 :type
'(repeat (group (string :tag
"name")
81 (cons (string :tag
"command")
82 (repeat (string :tag
"option")))
83 (integer :tag
"PID column")
84 (option (integer :tag
"sort column")))))
86 (defcustom proced-command
(if (zerop (user-real-uid)) "all" "user")
87 "Name of process listing.
88 Must be the car of an element of `proced-command-alist'."
90 :type
'(string :tag
"name"))
91 (make-variable-buffer-local 'proced-command
)
93 (defcustom proced-signal-function
'signal-process
94 "Name of signal function.
95 It can be an elisp function (usually `signal-process') or a string specifying
96 the external command (usually \"kill\")."
98 :type
'(choice (function :tag
"function")
99 (string :tag
"command")))
101 (defcustom proced-signal-list
102 '(("HUP (1. Hangup)")
103 ("INT (2. Terminal interrupt)")
104 ("QUIT (3. Terminal quit)")
105 ("ABRT (6. Process abort)")
106 ("KILL (9. Kill -- cannot be caught or ignored)")
107 ("ALRM (14. Alarm Clock)")
108 ("TERM (15. Termination)"))
109 "List of signals, used for minibuffer completion."
111 :type
'(repeat (string :tag
"signal")))
113 (defvar proced-marker-char ?
* ; the answer is 42
114 "In proced, the current mark character.")
116 ;; face and font-lock code taken from dired
117 (defgroup proced-faces nil
118 "Faces used by Proced."
122 (defface proced-header
123 '((t (:inherit font-lock-type-face
)))
124 "Face used for proced headers."
125 :group
'proced-faces
)
126 (defvar proced-header-face
'proced-header
127 "Face name used for proced headers.")
130 '((t (:inherit font-lock-constant-face
)))
131 "Face used for proced marks."
132 :group
'proced-faces
)
133 (defvar proced-mark-face
'proced-mark
134 "Face name used for proced marks.")
136 (defface proced-marked
137 '((t (:inherit font-lock-warning-face
)))
138 "Face used for marked processes."
139 :group
'proced-faces
)
140 (defvar proced-marked-face
'proced-marked
141 "Face name used for marked processes.")
143 (defvar proced-re-mark
"^[^ \n]"
144 "Regexp matching a marked line.
145 Important: the match ends just after the marker.")
147 (defvar proced-header-regexp
"\\`.*$"
148 "Regexp matching a header line.")
150 (defvar proced-procname-column nil
151 "Proced command column.
152 Initialized based on `proced-procname-column-regexp'.")
153 (make-variable-buffer-local 'proced-procname-column
)
155 (defvar proced-font-lock-keywords
158 ;; Process listing headers.
159 (list proced-header-regexp
'(0 proced-header-face
))
162 (list proced-re-mark
'(0 proced-mark-face
))
165 (list (concat "^[" (char-to-string proced-marker-char
) "]")
166 '(".+" (proced-move-to-procname) nil
(0 proced-marked-face
)))))
168 (defvar proced-mode-map
169 (let ((km (make-sparse-keymap)))
170 (define-key km
" " 'next-line
)
171 (define-key km
"n" 'next-line
)
172 (define-key km
"p" 'previous-line
)
173 (define-key km
"\C-?" 'previous-line
)
174 (define-key km
"h" 'describe-mode
)
175 (define-key km
"?" 'proced-help
)
176 (define-key km
"d" 'proced-mark
) ; Dired compatibility
177 (define-key km
"m" 'proced-mark
)
178 (define-key km
"M" 'proced-mark-all
)
179 (define-key km
"u" 'proced-unmark
)
180 (define-key km
"\177" 'proced-unmark-backward
)
181 (define-key km
"U" 'proced-unmark-all
)
182 (define-key km
"t" 'proced-toggle-marks
)
183 (define-key km
"h" 'proced-hide-processes
)
184 (define-key km
"x" 'proced-send-signal
) ; Dired compatibility
185 (define-key km
"k" 'proced-send-signal
) ; kill processes
186 (define-key km
"l" 'proced-listing-type
)
187 (define-key km
"g" 'revert-buffer
) ; Dired compatibility
188 (define-key km
"q" 'quit-window
)
189 (define-key km
[remap undo
] 'proced-undo
)
190 (define-key km
[remap advertised-undo
] 'proced-undo
)
192 "Keymap for proced commands")
195 proced-menu proced-mode-map
"Proced Menu"
197 ["Mark" proced-mark t
]
198 ["Unmark" proced-unmark t
]
199 ["Mark All" proced-mark-all t
]
200 ["Unmark All" proced-unmark-all t
]
201 ["Toggle Marks" proced-unmark-all t
]
203 ["Hide Marked Processes" proced-hide-processes t
]
205 ["Revert" revert-buffer t
]
206 ["Send signal" proced-send-signal t
]
207 ["Change listing" proced-listing-type t
]))
209 (defconst proced-help-string
210 "(n)ext, (p)revious, (m)ark, (u)nmark, (k)ill, (q)uit (type ? for more help)"
211 "Help string for proced.")
213 (defun proced-marker-regexp ()
214 (concat "^" (regexp-quote (char-to-string proced-marker-char
))))
216 (defun proced-success-message (action count
)
217 (message "%s %s process%s" action count
(if (= 1 count
) "" "es")))
219 (defun proced-move-to-procname ()
220 "Move to the beginning of the process name on the current line.
221 Return the position of the beginning of the process name, or nil if none found."
223 (if proced-procname-column
224 (forward-char proced-procname-column
)
227 (defsubst proced-skip-regexp
()
228 "Regexp to skip in process listing."
229 (apply 'concat
(make-list (1- (nth 2 (assoc proced-command
230 proced-command-alist
)))
233 (define-derived-mode proced-mode nil
"Proced"
234 "Mode for displaying UNIX system processes and sending signals to them.
235 Type \\[proced-mark-process] to mark a process for later commands.
236 Type \\[proced-send-signal] to send signals to marked processes.
241 (setq buffer-read-only t
243 (set (make-local-variable 'revert-buffer-function
) 'proced-revert
)
244 (set (make-local-variable 'font-lock-defaults
)
245 '(proced-font-lock-keywords t nil nil beginning-of-line
)))
247 ;; Proced mode is suitable only for specially formatted data.
248 (put 'proced-mode
'mode-class
'special
)
251 (defun proced (&optional arg
)
252 "Mode for displaying UNIX system processes and sending signals to them.
253 Type \\[proced-mark-process] to mark a process for later commands.
254 Type \\[proced-send-signal] to send signals to marked processes.
256 If invoked with optional ARG the window displaying the process
257 information will be displayed but not selected.
261 (let ((proced-buffer (get-buffer-create "*Process Info*")) new
)
262 (set-buffer proced-buffer
)
263 (setq new
(zerop (buffer-size)))
264 (when new
(proced-mode))
270 (display-buffer proced-buffer
)
271 (pop-to-buffer proced-buffer
)
272 (message (substitute-command-keys
273 "type \\[quit-window] to quit, \\[proced-help] for help")))))
276 (defun proced-mark (&optional count
)
277 "Mark the current (or next COUNT) processes."
279 (proced-do-mark t count
))
281 (defun proced-unmark (&optional count
)
282 "Unmark the current (or next COUNT) processes."
284 (proced-do-mark nil count
))
286 (defun proced-unmark-backward (&optional count
)
287 "Unmark the previous (or COUNT previous) processes."
289 (proced-do-mark nil
(- (or count
1))))
291 (defun proced-do-mark (mark &optional count
)
292 "Mark the current (or next ARG) processes using MARK."
293 (or count
(setq count
1))
294 (let ((backward (< count
0))
295 (line (line-number-at-pos))
297 ;; do nothing in the first line
299 (setq count
(1+ (if (<= 0 count
) count
300 (min (- line
2) (abs count
)))))
302 (while (not (or (zerop (setq count
(1- count
))) (eobp)))
303 (proced-insert-mark mark backward
))
304 (proced-move-to-procname))))
306 (defun proced-mark-all ()
307 "Mark all processes."
309 (proced-do-mark-all t
))
311 (defun proced-unmark-all ()
312 "Unmark all processes."
314 (proced-do-mark-all nil
))
316 (defun proced-do-mark-all (mark)
317 "Mark all processes using MARK."
318 (let (buffer-read-only)
322 (proced-insert-mark mark
)))))
324 (defun proced-toggle-marks ()
325 "Toggle marks: marked processes become unmarked, and vice versa."
327 (let ((mark-re (proced-marker-regexp))
332 (cond ((looking-at mark-re
)
333 (proced-insert-mark nil
))
335 (proced-insert-mark t
))
337 (forward-line 1)))))))
339 (defun proced-insert-mark (mark &optional backward
)
340 "If MARK is non-nil, insert `proced-marker-char'.
341 If BACKWARD is non-nil, move one line backwards before inserting the mark.
342 Otherwise move one line forward after inserting the mark."
343 (if backward
(forward-line -
1))
344 (insert (if mark proced-marker-char ?\s
))
346 (unless backward
(forward-line)))
348 ;; Mostly analog of `dired-do-kill-lines'.
349 ;; However, for negative args the target lines of `dired-do-kill-lines'
350 ;; include the current line, whereas `dired-mark' for negative args operates
351 ;; on the preceding lines. Here we are consistent with `dired-mark'.
352 (defun proced-hide-processes (&optional arg quiet
)
353 "Hide marked processes.
354 With prefix ARG, hide that many lines starting with the current line.
355 \(A negative argument hides backward.)
356 If QUIET is non-nil suppress status message.
357 Returns count of hidden lines."
359 (let ((mark-re (proced-marker-regexp))
364 ;; Hide ARG lines starting with the current line.
365 (let ((line (line-number-at-pos)))
366 ;; do nothing in the first line
368 (delete-region (line-beginning-position)
371 (setq count
(- arg
(forward-line arg
)))
372 (setq count
(min (- line
2) (abs arg
)))
373 (forward-line (- count
)))
377 (while (and (not (eobp))
378 (re-search-forward mark-re nil t
))
379 (delete-region (match-beginning 0)
380 (save-excursion (forward-line) (point)))
381 (setq count
(1+ count
)))))
382 (unless (zerop count
) (proced-move-to-procname))
384 (proced-success-message "Hid" count
))
387 (defun proced-listing-type (command)
388 "Select `proced' listing type COMMAND from `proced-command-alist'."
390 (list (completing-read "Listing type: " proced-command-alist nil t
)))
391 (setq proced-command command
)
394 (defun proced-update (&optional quiet
)
395 "Update the `proced' process information. Preserves point and marks."
396 ;; This is the main function that generates and updates the process listing.
398 (or quiet
(message "Updating process information..."))
399 (let* ((command (cdr (assoc proced-command proced-command-alist
)))
400 (regexp (concat (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\)"))
401 (old-pos (if (save-excursion
403 (looking-at (concat "^[* ]" regexp
)))
404 (cons (match-string-no-properties 1)
406 buffer-read-only plist
)
407 (goto-char (point-min))
408 ;; remember marked processes (whatever the mark was)
409 (while (re-search-forward (concat "^\\(\\S-\\)" regexp
) nil t
)
410 (push (cons (match-string-no-properties 2)
411 (match-string-no-properties 1)) plist
))
412 ;; generate new listing
414 (apply 'call-process
(caar command
) nil t nil
(cdar command
))
415 (goto-char (point-min))
419 ;; (delete-trailing-whitespace)
420 (goto-char (point-min))
421 (while (re-search-forward "[ \t\r]+$" nil t
)
422 (delete-region (match-beginning 0) (match-end 0)))
423 (set-buffer-modified-p nil
)
424 ;; set `proced-procname-column'
425 (goto-char (point-min))
426 (and proced-procname-column-regexp
427 (re-search-forward proced-procname-column-regexp nil t
)
428 (setq proced-procname-column
(1- (match-beginning 0))))
429 ;; restore process marks
434 (while (re-search-forward (concat "^" regexp
) nil t
)
435 (if (setq mark
(assoc (match-string-no-properties 1) plist
))
439 (delete-char 1)))))))
440 ;; restore buffer position (if possible)
444 (concat "^[* ]" (proced-skip-regexp) "\\s-+" (car old-pos
) "\\>")
448 (forward-char (cdr old-pos
)))
449 (proced-move-to-procname))
450 (or quiet
(input-pending-p)
451 (message "Updating process information...done."))))
453 (defun proced-revert (&rest args
)
454 "Analog of `revert-buffer'."
457 ;; I do not want to reinvent the wheel. Should we rename `dired-pop-to-buffer'
458 ;; and move it to simple.el so that proced and ibuffer can easily use it, too?
459 (autoload 'dired-pop-to-buffer
"dired")
461 (defun proced-send-signal (&optional signal
)
462 "Send a SIGNAL to the marked processes.
463 SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
464 If SIGNAL is nil display marked processes and query interactively for SIGNAL."
466 (let ((regexp (concat (proced-marker-regexp)
467 (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\).*$"))
469 ;; collect marked processes
471 (goto-char (point-min))
472 (while (re-search-forward regexp nil t
)
473 (push (cons (match-string-no-properties 1)
474 ;; How much info should we collect here? Would it be
475 ;; better to collect only the PID (to avoid ambiguities)
476 ;; and the command name?
477 (substring (match-string-no-properties 0) 2))
480 (message "No processes marked")
482 ;; Display marked processes (code taken from `dired-mark-pop-up').
483 (let ((bufname " *Marked Processes*")
484 (header (save-excursion
485 (goto-char (+ 2 (point-min)))
486 (buffer-substring-no-properties
487 (point) (line-end-position)))))
488 (with-current-buffer (get-buffer-create bufname
)
489 (setq truncate-lines t
)
493 (insert (cdr proc
) "\n"))
494 (save-window-excursion
495 (dired-pop-to-buffer bufname
) ; all we need
496 (let* ((completion-ignore-case t
)
497 (pnum (if (= 1 (length plist
))
499 (format "%d processes" (length plist
))))
500 ;; The following is an ugly hack. Is there a better way
501 ;; to help people like me to remember the signals and
503 (tmp (completing-read (concat "Send signal [" pnum
504 "] (default TERM): ")
506 nil nil nil nil
"TERM")))
507 (setq signal
(if (string-match "^\\(\\S-+\\)\\s-" tmp
)
508 (match-string 1 tmp
) tmp
))))))
512 (if (functionp proced-signal-function
)
513 ;; use built-in `signal-process'
514 (let ((signal (if (stringp signal
)
515 (if (string-match "\\`[0-9]+\\'" signal
)
516 (string-to-number signal
)
517 (make-symbol signal
))
519 (dolist (process plist
)
521 proced-signal-function
522 (string-to-number (car process
)) signal
))
523 (setq count
(1+ count
))
524 (push (cdr process
) err-list
))))
525 ;; use external system call
526 (let ((signal (concat "-" (if (numberp signal
)
527 (number-to-string signal
) signal
))))
528 (dolist (process plist
)
529 (if (zerop (call-process
530 proced-signal-function nil
0 nil
531 signal
(car process
)))
532 (setq count
(1+ count
))
533 (push (cdr process
) err-list
)))))
535 ;; FIXME: that's not enough to display the errors.
536 (message "%s: %s" signal err-list
)
537 (proced-success-message "Sent signal to" count
)))
539 (run-hooks 'proced-after-send-signal-hook
)))))
541 (defun proced-help ()
542 "Provide help for the `proced' user."
544 (if (eq last-command
'proced-help
)
546 (message proced-help-string
)))
548 (defun proced-undo ()
549 "Undo in a proced buffer.
550 This doesn't recover killed processes, it just undoes changes in the proced
551 buffer. You can use it to recover marks."
553 (let (buffer-read-only)
555 (message "Change in proced buffer undone.
556 Killed processes cannot be recovered by Emacs."))
560 ;; arch-tag: a6e312ad-9032-45aa-972d-31a8cfc545af
561 ;;; proced.el ends here.