1 ;;; proced.el --- operate on 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 processes
28 ;; (using ps(1)). You can use the normal Emacs commands to move around in
29 ;; this buffer, and special Proced commands to operate on the processes listed.
32 ;; (autoload 'proced "proced" nil t)
33 ;; in your .emacs file.
35 ;; Is there a need for additional features like:
36 ;; - automatic update of process list
37 ;; - sort by CPU time or other criteria
38 ;; - filter by user name or other criteria
48 (defcustom proced-procname-column-regexp
"\\b\\(CMD\\|COMMAND\\)\\b"
49 "If non-nil, regexp that defines the `proced-procname-column'."
51 :type
'(choice (const :tag
"none" nil
)
52 (regexp :tag
"regexp")))
54 (defcustom proced-command-alist
55 (cond ((memq system-type
'(berkeley-unix netbsd
))
56 '(("user" ("ps" "-uxgww") 2)
57 ("user-running" ("ps" "-uxrgww") 2)
58 ("all" ("ps" "-auxgww") 2)
59 ("all-running" ("ps" "-auxrgww") 2)))
60 ((memq system-type
'(linux lignux gnu
/linux
))
61 `(("user" ("ps" "uxwww") 2)
62 ("user-running" ("ps" "uxrwww") 2)
63 ("all" ("ps" "auxwww") 2)
64 ("all-running" ("ps" "auxrwww") 2)
65 ("emacs" ("ps" "--pid" ,(number-to-string (emacs-pid))
66 "--ppid" ,(number-to-string (emacs-pid))
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 SORT-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.
79 SORT-COLUMN is the column number used for sorting the process listing
80 \(must be a numeric field). If nil, the process listing is not sorted."
82 :type
'(repeat (group (string :tag
"name")
83 (cons (string :tag
"command")
84 (repeat (string :tag
"option")))
85 (integer :tag
"PID column")
86 (option (integer :tag
"sort column")))))
88 (defcustom proced-command
(if (zerop (user-real-uid)) "all" "user")
89 "Name of process listing.
90 Must be the car of an element of `proced-command-alist'."
92 :type
'(string :tag
"name"))
94 (defcustom proced-kill-program
"kill"
95 "Name of kill command (usually `kill')."
97 :type
'(string :tag
"command"))
99 (defcustom proced-signal-list
100 '(("HUP (1. Hangup)")
101 ("INT (2. Terminal interrupt)")
102 ("QUIT (3. Terminal quit)")
103 ("ABRT (6. Process abort)")
104 ("KILL (9. Kill -- cannot be caught or ignored)")
105 ("ALRM (14. Alarm Clock)")
106 ("TERM (15. Termination)"))
107 "List of signals, used for minibuffer completion."
109 :type
'(repeat (string :tag
"signal")))
111 (defvar proced-marker-char ?
* ; the answer is 42
112 "In proced, the current mark character.")
114 ;; face and font-lock code taken from dired
115 (defgroup proced-faces nil
116 "Faces used by Proced."
120 (defface proced-header
121 '((t (:inherit font-lock-type-face
)))
122 "Face used for proced headers."
123 :group
'proced-faces
)
124 (defvar proced-header-face
'proced-header
125 "Face name used for proced headers.")
128 '((t (:inherit font-lock-constant-face
)))
129 "Face used for proced marks."
130 :group
'proced-faces
)
131 (defvar proced-mark-face
'proced-mark
132 "Face name used for proced marks.")
134 (defface proced-marked
135 '((t (:inherit font-lock-warning-face
)))
136 "Face used for marked processes."
137 :group
'proced-faces
)
138 (defvar proced-marked-face
'proced-marked
139 "Face name used for marked processes.")
141 (defvar proced-re-mark
"^[^ \n]"
142 "Regexp matching a marked line.
143 Important: the match ends just after the marker.")
145 (defvar proced-header-regexp
"\\`.*$"
146 "Regexp matching a header line.")
148 (defvar proced-procname-column nil
149 "Proced command column.
150 Initialized based on `proced-procname-column-regexp'.")
152 (defvar proced-font-lock-keywords
155 ;; Process listing headers.
156 (list proced-header-regexp
'(0 proced-header-face
))
159 (list proced-re-mark
'(0 proced-mark-face
))
162 (list (concat "^[" (char-to-string proced-marker-char
) "]")
163 '(".+" (proced-move-to-procname) nil
(0 proced-marked-face
)))))
165 (defvar proced-mode-map
166 (let ((km (make-sparse-keymap)))
167 (define-key km
" " 'next-line
)
168 (define-key km
"n" 'next-line
)
169 (define-key km
"p" 'previous-line
)
170 (define-key km
"\C-?" 'previous-line
)
171 (define-key km
"h" 'describe-mode
)
172 (define-key km
"?" 'proced-help
)
173 (define-key km
"d" 'proced-mark
) ; Dired compatibility
174 (define-key km
"m" 'proced-mark
)
175 (define-key km
"M" 'proced-mark-all
)
176 (define-key km
"g" 'revert-buffer
) ; Dired compatibility
177 (define-key km
"q" 'quit-window
)
178 (define-key km
"u" 'proced-unmark
)
179 (define-key km
"U" 'proced-unmark-all
)
180 (define-key km
"x" 'proced-send-signal
) ; Dired compatibility
181 (define-key km
"k" 'proced-send-signal
) ; kill processes
182 (define-key km
"l" 'proced-listing-type
)
183 (define-key km
[remap undo
] 'proced-undo
)
184 (define-key km
[remap advertised-undo
] 'proced-undo
)
186 "Keymap for proced commands")
189 proced-menu proced-mode-map
"Proced Menu"
191 ["Mark" proced-mark t
]
192 ["Unmark" proced-unmark t
]
193 ["Mark All" proced-mark-all t
]
194 ["Unmark All" proced-unmark-all t
]
196 ["Revert" revert-buffer t
]
197 ["Send signal" proced-send-signal t
]
198 ["Change listing" proced-listing-type t
]))
200 (defconst proced-help-string
201 "(n)ext, (p)revious, (m)ark, (u)nmark, (k)ill, (q)uit (type ? for more help)"
202 "Help string for proced.")
204 (defun proced-mode (&optional arg
)
205 "Mode for displaying UNIX processes and sending signals to them.
206 Type \\[proced-mark-process] to mark a process for later commands.
207 Type \\[proced-send-signal] to send signals to marked processes.
209 If invoked with optional ARG the window displaying the process
210 information will be displayed but not selected.
214 (let ((proced-buffer (get-buffer-create "*Process Info*")) new
)
215 (set-buffer proced-buffer
)
216 (setq new
(zerop (buffer-size)))
218 (kill-all-local-variables)
219 (use-local-map proced-mode-map
)
222 (setq buffer-read-only t
224 major-mode
'proced-mode
226 (set (make-local-variable 'revert-buffer-function
) 'proced-revert
)
227 (set (make-local-variable 'font-lock-defaults
)
228 '(proced-font-lock-keywords t nil nil beginning-of-line
)))
234 (display-buffer proced-buffer
)
235 (pop-to-buffer proced-buffer
)
236 (message (substitute-command-keys
237 "type \\[quit-window] to quit, \\[proced-help] for help")))
238 (if new
(run-mode-hooks 'proced-mode-hook
))))
240 ;; Proced mode is suitable only for specially formatted data.
241 (put 'proced-mode
'mode-class
'special
)
243 (fset 'proced
'proced-mode
)
245 (defun proced-move-to-procname ()
246 "Move to the beginning of the process name on the current line.
247 Return the position of the beginning of the process name, or nil if none found."
249 (if proced-procname-column
250 (forward-char proced-procname-column
)
253 (defun proced-mark (&optional count
)
254 "Mark the current (or next COUNT) processes."
256 (proced-do-mark t count
))
258 (defun proced-unmark (&optional count
)
259 "Unmark the current (or next COUNT) processes."
261 (proced-do-mark nil count
))
263 (defun proced-do-mark (mark &optional count
)
264 "Mark the current (or next ARG) processes using MARK."
265 (or count
(setq count
1))
266 (let ((n (if (<= 0 count
) 1 -
1))
267 (line (line-number-at-pos))
269 ;; do nothing in the first line
271 (setq count
(1+ (cond ((<= 0 count
) count
)
272 ((< (abs count
) line
) (abs count
))
275 (while (not (or (zerop (setq count
(1- count
))) (eobp)))
276 (proced-insert-mark mark n
))
277 (proced-move-to-procname))))
279 (defun proced-mark-all ()
280 "Mark all processes."
282 (proced-do-mark-all t
))
284 (defun proced-unmark-all ()
285 "Unmark all processes."
287 (proced-do-mark-all nil
))
289 (defun proced-do-mark-all (mark)
290 "Mark all processes using MARK."
292 (let (buffer-read-only)
295 (proced-insert-mark mark
1)))))
297 (defun proced-insert-mark (mark n
)
298 "If MARK is non-nil, insert `proced-marker-char', move N lines."
299 ;; Do we need other marks besides `proced-marker-char'?
300 (insert (if mark proced-marker-char ?\s
))
304 (defun proced-listing-type (command)
305 "Select `proced' listing type COMMAND from `proced-command-alist'."
307 (list (completing-read "Listing type: " proced-command-alist nil t
)))
308 (setq proced-command command
)
311 (defsubst proced-skip-regexp
()
312 "Regexp to skip in process listing."
313 (apply 'concat
(make-list (1- (nth 2 (assoc proced-command
314 proced-command-alist
)))
317 (defun proced-update (&optional quiet
)
318 "Update the `proced' process information. Preserves point and marks."
320 (or quiet
(message "Updating process information..."))
321 (let* ((command (cdr (assoc proced-command proced-command-alist
)))
322 (regexp (concat (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\)"))
323 (old-pos (if (save-excursion
325 (looking-at (concat "^[* ]" regexp
)))
326 (cons (match-string-no-properties 1)
328 buffer-read-only plist
)
329 (goto-char (point-min))
330 ;; remember marked processes (whatever the mark was)
331 (while (re-search-forward (concat "^\\(\\S-\\)" regexp
) nil t
)
332 (push (cons (match-string-no-properties 2)
333 (match-string-no-properties 1)) plist
))
334 ;; generate new listing
336 (apply 'call-process
(caar command
) nil t nil
(cdar command
))
337 (goto-char (point-min))
341 ;; (delete-trailing-whitespace)
342 (goto-char (point-min))
343 (while (re-search-forward "[ \t\r]+$" nil t
)
344 (delete-region (match-beginning 0) (match-end 0)))
345 ;; set `proced-procname-column'
346 (goto-char (point-min))
347 (and proced-procname-column-regexp
348 (re-search-forward proced-procname-column-regexp nil t
)
349 (setq proced-procname-column
(1- (match-beginning 0))))
353 (sort-numeric-fields (nth 2 command
) (point) (point-max)))
354 (set-buffer-modified-p nil
)
355 ;; restore process marks
360 (while (re-search-forward (concat "^" regexp
) nil t
)
361 (if (setq mark
(assoc (match-string-no-properties 1) plist
))
365 (delete-char 1)))))))
366 ;; restore buffer position (if possible)
370 (concat "^[* ]" (proced-skip-regexp) "\\s-+" (car old-pos
) "\\>")
374 (forward-char (cdr old-pos
)))
375 (proced-move-to-procname))
376 (or quiet
(input-pending-p)
377 (message "Updating process information...done."))))
379 (defun proced-revert (&rest args
)
380 "Analog of `revert-buffer'."
383 ;; I do not want to reinvent the wheel
384 (autoload 'dired-pop-to-buffer
"dired")
386 (defun proced-send-signal (&optional signal
)
387 "Send a SIGNAL to the marked processes.
388 SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
389 If SIGNAL is nil display marked processes and query interactively for SIGNAL."
391 (let ((regexp (concat "^\\*" (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\).*$"))
393 ;; collect marked processes
395 (goto-char (point-min))
396 (while (re-search-forward regexp nil t
)
397 (push (cons (match-string-no-properties 1)
398 (substring (match-string-no-properties 0) 2))
401 (message "No processes marked")
403 ;; Display marked processes (code taken from `dired-mark-pop-up').
404 ;; We include all process information to distinguish multiple
405 ;; instances of the same program.
406 (let ((bufname " *Marked Processes*")
407 (header (save-excursion
408 (goto-char (+ 2 (point-min)))
409 (buffer-substring-no-properties
410 (point) (line-end-position)))))
411 (with-current-buffer (get-buffer-create bufname
)
412 (setq truncate-lines t
)
416 (insert (cdr proc
) "\n"))
417 (save-window-excursion
418 (dired-pop-to-buffer bufname
) ; all we need
419 (let* ((completion-ignore-case t
)
420 ;; The following is an ugly hack. Is there a better way
421 ;; to help people like me to remember the signals and
423 (tmp (completing-read "Signal (default TERM): "
425 nil nil nil nil
"TERM")))
426 (setq signal
(if (string-match "^\\(\\S-+\\)\\s-" tmp
)
427 (match-string 1 tmp
) tmp
))))))
429 (apply 'call-process proced-kill-program nil
0 nil
430 (concat "-" (if (numberp signal
)
431 (number-to-string signal
) signal
))
433 (run-hooks 'proced-after-send-signal-hook
)))))
435 (defun proced-help ()
436 "Provide help for the `proced' user."
438 (if (eq last-command
'proced-help
)
440 (message proced-help-string
)))
442 (defun proced-undo ()
443 "Undo in a proced buffer.
444 This doesn't recover killed processes, it just undoes changes in the proced
445 buffer. You can use it to recover marks."
447 (let (buffer-read-only)
449 (message "Change in proced buffer undone.
450 Killed processes cannot be recovered by Emacs."))
454 ;; arch-tag: a6e312ad-9032-45aa-972d-31a8cfc545af
455 ;;; proced.el ends here.