comparison lisp/proced.el @ 93212:134b2dc25692

proced.el: New file.
author Roland Winkler <Roland.Winkler@physik.uni-erlangen.de>
date Wed, 26 Mar 2008 02:15:28 +0000
parents
children f51aae2fc6a4
comparison
equal deleted inserted replaced
93211:844981a78ea6 93212:134b2dc25692
1 ;;; proced.el --- operate on processes like dired
2
3 ;; Copyright (C) 2008 Roland Winkler
4 ;; Author: Roland Winkler <Roland.Winkler@physik.uni-erlangen.de>
5 ;; Version: 0.5
6 ;; Keywords: Processes, Unix
7
8 ;; This program is free software; you can redistribute it and/or
9 ;; modify it under the terms of the GNU General Public License as
10 ;; published by the Free Software Foundation; either version 2, or (at
11 ;; your option) any later version.
12
13 ;; This program is distributed in the hope that it will be useful, but
14 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 ;; General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program ; see the file COPYING. If not, write to
20 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 ;; Boston, MA 02110-1301, USA.
22
23 ;;; Commentary:
24
25 ;; Proced makes an Emacs buffer containing a listing of the current processes
26 ;; (using ps(1)). You can use the normal Emacs commands to move around in
27 ;; this buffer, and special Proced commands to operate on the processes listed.
28 ;;
29 ;; To autoload, use
30 ;; (autoload 'proced "proced" nil t)
31 ;; in your .emacs file.
32 ;;
33 ;; Is there a need for additional features like:
34 ;; - automatic update of process list
35 ;; - sort by CPU time or other criteria
36 ;; - filter by user name or other criteria
37
38 ;;; Code:
39
40 (defgroup proced nil
41 "Proced mode."
42 :group 'processes
43 :group 'unix
44 :prefix "proced-")
45
46 (defcustom proced-procname-column-regexp "\\b\\(CMD\\|COMMAND\\)\\b"
47 "If non-nil, regexp that defines the `proced-procname-column'."
48 :group 'proced
49 :type '(choice (const :tag "none" nil)
50 (regexp :tag "regexp")))
51
52 (defcustom proced-command-alist
53 (cond ((memq system-type '(berkeley-unix netbsd))
54 '(("user" ("ps" "-uxgww") 2)
55 ("user-running" ("ps" "-uxrgww") 2)
56 ("all" ("ps" "-auxgww") 2)
57 ("all-running" ("ps" "-auxrgww") 2)))
58 ((memq system-type '(linux lignux gnu/linux))
59 `(("user" ("ps" "uxwww") 2)
60 ("user-running" ("ps" "uxrwww") 2)
61 ("all" ("ps" "auxwww") 2)
62 ("all-running" ("ps" "auxrwww") 2)
63 ("emacs" ("ps" "--pid" ,(number-to-string (emacs-pid))
64 "--ppid" ,(number-to-string (emacs-pid))
65 "uwww") 2)))
66 (t ; standard syntax doesn't allow us to list running processes only
67 `(("user" ("ps" "-fu" ,(number-to-string (user-uid))) 2)
68 ("all" ("ps" "-ef") 2))))
69 "Alist of commands to get list of processes.
70 Each element has the form (NAME COMMAND PID-COLUMN SORT-COLUMN).
71 NAME is a shorthand name to select the type of listing.
72 COMMAND is a list (COMMAND-NAME ARG1 ARG2 ...),
73 where COMMAND-NAME is the command to generate the listing (usually \"ps\").
74 ARG1, ARG2, ... are arguments passed to COMMAND-NAME to generate
75 a particular listing. These arguments differ under various operating systems.
76 PID-COLUMN is the column number (starting from 1) of the process ID.
77 SORT-COLUMN is the column number used for sorting the process listing
78 \(must be a numeric field). If nil, the process listing is not sorted."
79 :group 'proced
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")))))
85
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'."
89 :group 'proced
90 :type '(string :tag "name"))
91
92 (defcustom proced-kill-program "kill"
93 "Name of kill command (usually `kill')."
94 :group 'proced
95 :type '(string :tag "command"))
96
97 (defcustom proced-signal-list
98 '(("HUP (1. Hangup)")
99 ("INT (2. Terminal interrupt)")
100 ("QUIT (3. Terminal quit)")
101 ("ABRT (6. Process abort)")
102 ("KILL (9. Kill -- cannot be caught or ignored)")
103 ("ALRM (14. Alarm Clock)")
104 ("TERM (15. Termination)"))
105 "List of signals, used for minibuffer completion."
106 :group 'proced
107 :type '(repeat (string :tag "signal")))
108
109 (defvar proced-marker-char ?* ; the answer is 42
110 "In proced, the current mark character.")
111
112 ;; face and font-lock code taken from dired
113 (defgroup proced-faces nil
114 "Faces used by Proced."
115 :group 'proced
116 :group 'faces)
117
118 (defface proced-header
119 '((t (:inherit font-lock-type-face)))
120 "Face used for proced headers."
121 :group 'proced-faces)
122 (defvar proced-header-face 'proced-header
123 "Face name used for proced headers.")
124
125 (defface proced-mark
126 '((t (:inherit font-lock-constant-face)))
127 "Face used for proced marks."
128 :group 'proced-faces)
129 (defvar proced-mark-face 'proced-mark
130 "Face name used for proced marks.")
131
132 (defface proced-marked
133 '((t (:inherit font-lock-warning-face)))
134 "Face used for marked processes."
135 :group 'proced-faces)
136 (defvar proced-marked-face 'proced-marked
137 "Face name used for marked processes.")
138
139 (defvar proced-re-mark "^[^ \n]"
140 "Regexp matching a marked line.
141 Important: the match ends just after the marker.")
142
143 (defvar proced-header-regexp "\\`.*$"
144 "Regexp matching a header line.")
145
146 (defvar proced-procname-column nil
147 "Proced command column.
148 Initialized based on `proced-procname-column-regexp'.")
149
150 (defvar proced-font-lock-keywords
151 (list
152 ;;
153 ;; Process listing headers.
154 (list proced-header-regexp '(0 proced-header-face))
155 ;;
156 ;; Proced marks.
157 (list proced-re-mark '(0 proced-mark-face))
158 ;;
159 ;; Marked files.
160 (list (concat "^[" (char-to-string proced-marker-char) "]")
161 '(".+" (proced-move-to-procname) nil (0 proced-marked-face)))))
162
163 (defvar proced-mode-map
164 (let ((km (make-sparse-keymap)))
165 (define-key km " " 'next-line)
166 (define-key km "n" 'next-line)
167 (define-key km "p" 'previous-line)
168 (define-key km "\C-?" 'previous-line)
169 (define-key km "h" 'describe-mode)
170 (define-key km "?" 'proced-help)
171 (define-key km "d" 'proced-mark) ; Dired compatibility
172 (define-key km "m" 'proced-mark)
173 (define-key km "M" 'proced-mark-all)
174 (define-key km "g" 'revert-buffer) ; Dired compatibility
175 (define-key km "q" 'quit-window)
176 (define-key km "u" 'proced-unmark)
177 (define-key km "U" 'proced-unmark-all)
178 (define-key km "x" 'proced-send-signal) ; Dired compatibility
179 (define-key km "k" 'proced-send-signal) ; kill processes
180 (define-key km "l" 'proced-listing-type)
181 (define-key km [remap undo] 'proced-undo)
182 (define-key km [remap advertised-undo] 'proced-undo)
183 km)
184 "Keymap for proced commands")
185
186 (easy-menu-define
187 proced-menu proced-mode-map "Proced Menu"
188 '("Proced"
189 ["Mark" proced-mark t]
190 ["Unmark" proced-unmark t]
191 ["Mark All" proced-mark-all t]
192 ["Unmark All" proced-unmark-all t]
193 "--"
194 ["Revert" revert-buffer t]
195 ["Send signal" proced-send-signal t]
196 ["Change listing" proced-listing-type t]))
197
198 (defconst proced-help-string
199 "(n)ext, (p)revious, (m)ark, (u)nmark, (k)ill, (q)uit (type ? for more help)"
200 "Help string for proced.")
201
202 (defun proced-mode (&optional arg)
203 "Mode for displaying UNIX processes and sending signals to them.
204 Type \\[proced-mark-process] to mark a process for later commands.
205 Type \\[proced-send-signal] to send signals to marked processes.
206
207 If invoked with optional ARG the window displaying the process
208 information will be displayed but not selected.
209
210 \\{proced-mode-map}"
211 (interactive "P")
212 (let ((proced-buffer (get-buffer-create "*Process Info*")) new)
213 (set-buffer proced-buffer)
214 (setq new (zerop (buffer-size)))
215 (when new
216 (kill-all-local-variables)
217 (use-local-map proced-mode-map)
218 (abbrev-mode 0)
219 (auto-fill-mode 0)
220 (setq buffer-read-only t
221 truncate-lines t
222 major-mode 'proced-mode
223 mode-name "Proced")
224 (set (make-local-variable 'revert-buffer-function) 'proced-revert)
225 (set (make-local-variable 'font-lock-defaults)
226 '(proced-font-lock-keywords t nil nil beginning-of-line)))
227
228 (if (or new arg)
229 (proced-update))
230
231 (if arg
232 (display-buffer proced-buffer)
233 (pop-to-buffer proced-buffer)
234 (message (substitute-command-keys
235 "type \\[quit-window] to quit, \\[proced-help] for help")))
236 (if new (run-mode-hooks 'proced-mode-hook))))
237
238 ;; Proced mode is suitable only for specially formatted data.
239 (put 'proced-mode 'mode-class 'special)
240
241 (fset 'proced 'proced-mode)
242
243 (defun proced-move-to-procname ()
244 "Move to the beginning of the process name on the current line.
245 Return the position of the beginning of the process name, or nil if none found."
246 (beginning-of-line)
247 (if proced-procname-column
248 (forward-char proced-procname-column)
249 (forward-char 2)))
250
251 (defun proced-mark (&optional count)
252 "Mark the current (or next COUNT) processes."
253 (interactive "p")
254 (proced-do-mark t count))
255
256 (defun proced-unmark (&optional count)
257 "Unmark the current (or next COUNT) processes."
258 (interactive "p")
259 (proced-do-mark nil count))
260
261 (defun proced-do-mark (mark &optional count)
262 "Mark the current (or next ARG) processes using MARK."
263 (or count (setq count 1))
264 (let ((n (if (<= 0 count) 1 -1))
265 (line (line-number-at-pos))
266 buffer-read-only)
267 ;; do nothing in the first line
268 (unless (= line 1)
269 (setq count (1+ (cond ((<= 0 count) count)
270 ((< (abs count) line) (abs count))
271 (t (1- line)))))
272 (beginning-of-line)
273 (while (not (or (zerop (setq count (1- count))) (eobp)))
274 (proced-insert-mark mark n))
275 (proced-move-to-procname))))
276
277 (defun proced-mark-all ()
278 "Mark all processes."
279 (interactive)
280 (proced-do-mark-all t))
281
282 (defun proced-unmark-all ()
283 "Unmark all processes."
284 (interactive)
285 (proced-do-mark-all nil))
286
287 (defun proced-do-mark-all (mark)
288 "Mark all processes using MARK."
289 (save-excursion
290 (let (buffer-read-only)
291 (goto-line 2)
292 (while (not (eobp))
293 (proced-insert-mark mark 1)))))
294
295 (defun proced-insert-mark (mark n)
296 "If MARK is non-nil, insert `proced-marker-char', move N lines."
297 ;; Do we need other marks besides `proced-marker-char'?
298 (insert (if mark proced-marker-char ?\s))
299 (delete-char 1)
300 (forward-line n))
301
302 (defun proced-listing-type (command)
303 "Select `proced' listing type COMMAND from `proced-command-alist'."
304 (interactive
305 (list (completing-read "Listing type: " proced-command-alist nil t)))
306 (setq proced-command command)
307 (proced-update))
308
309 (defsubst proced-skip-regexp ()
310 "Regexp to skip in process listing."
311 (apply 'concat (make-list (1- (nth 2 (assoc proced-command
312 proced-command-alist)))
313 "\\s-+\\S-+")))
314
315 (defun proced-update (&optional quiet)
316 "Update the `proced' process information. Preserves point and marks."
317 (interactive)
318 (or quiet (message "Updating process information..."))
319 (let* ((command (cdr (assoc proced-command proced-command-alist)))
320 (regexp (concat (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\)"))
321 (old-pos (if (save-excursion
322 (beginning-of-line)
323 (looking-at (concat "^[* ]" regexp)))
324 (cons (match-string-no-properties 1)
325 (current-column))))
326 buffer-read-only plist)
327 (goto-char (point-min))
328 ;; remember marked processes (whatever the mark was)
329 (while (re-search-forward (concat "^\\(\\S-\\)" regexp) nil t)
330 (push (cons (match-string-no-properties 2)
331 (match-string-no-properties 1)) plist))
332 ;; generate new listing
333 (erase-buffer)
334 (apply 'call-process (caar command) nil t nil (cdar command))
335 (goto-char (point-min))
336 (while (not (eobp))
337 (insert " ")
338 (forward-line))
339 ;; (delete-trailing-whitespace)
340 (goto-char (point-min))
341 (while (re-search-forward "[ \t\r]+$" nil t)
342 (delete-region (match-beginning 0) (match-end 0)))
343 ;; set `proced-procname-column'
344 (goto-char (point-min))
345 (and proced-procname-column-regexp
346 (re-search-forward proced-procname-column-regexp nil t)
347 (setq proced-procname-column (1- (match-beginning 0))))
348 ;; sort fields
349 (goto-line 2)
350 (if (nth 2 command)
351 (sort-numeric-fields (nth 2 command) (point) (point-max)))
352 (set-buffer-modified-p nil)
353 ;; restore process marks
354 (if plist
355 (save-excursion
356 (goto-line 2)
357 (let (mark)
358 (while (re-search-forward (concat "^" regexp) nil t)
359 (if (setq mark (assoc (match-string-no-properties 1) plist))
360 (save-excursion
361 (beginning-of-line)
362 (insert (cdr mark))
363 (delete-char 1)))))))
364 ;; restore buffer position (if possible)
365 (goto-line 2)
366 (if (and old-pos
367 (re-search-forward
368 (concat "^[* ]" (proced-skip-regexp) "\\s-+" (car old-pos) "\\>")
369 nil t))
370 (progn
371 (beginning-of-line)
372 (forward-char (cdr old-pos)))
373 (proced-move-to-procname))
374 (or quiet (input-pending-p)
375 (message "Updating process information...done."))))
376
377 (defun proced-revert (&rest args)
378 "Analog of `revert-buffer'."
379 (proced-update))
380
381 ;; I do not want to reinvent the wheel
382 (autoload 'dired-pop-to-buffer "dired")
383
384 (defun proced-send-signal (&optional signal)
385 "Send a SIGNAL to the marked processes.
386 SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
387 If SIGNAL is nil display marked processes and query interactively for SIGNAL."
388 (interactive)
389 (let ((regexp (concat "^\\*" (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\).*$"))
390 plist)
391 ;; collect marked processes
392 (save-excursion
393 (goto-char (point-min))
394 (while (re-search-forward regexp nil t)
395 (push (cons (match-string-no-properties 1)
396 (substring (match-string-no-properties 0) 2))
397 plist)))
398 (if (not plist)
399 (message "No processes marked")
400 (unless signal
401 ;; Display marked processes (code taken from `dired-mark-pop-up').
402 ;; We include all process information to distinguish multiple
403 ;; instances of the same program.
404 (let ((bufname " *Marked Processes*")
405 (header (save-excursion
406 (goto-char (+ 2 (point-min)))
407 (buffer-substring-no-properties
408 (point) (line-end-position)))))
409 (with-current-buffer (get-buffer-create bufname)
410 (setq truncate-lines t)
411 (erase-buffer)
412 (insert header "\n")
413 (dolist (proc plist)
414 (insert (cdr proc) "\n"))
415 (save-window-excursion
416 (dired-pop-to-buffer bufname) ; all we need
417 (let* ((completion-ignore-case t)
418 ;; The following is an ugly hack. Is there a better way
419 ;; to help people like me to remember the signals and
420 ;; their meanings?
421 (tmp (completing-read "Signal (default TERM): "
422 proced-signal-list
423 nil nil nil nil "TERM")))
424 (setq signal (if (string-match "^\\(\\S-+\\)\\s-" tmp)
425 (match-string 1 tmp) tmp))))))
426 ;; send signal
427 (apply 'call-process proced-kill-program nil 0 nil
428 (concat "-" (if (numberp signal)
429 (number-to-string signal) signal))
430 (mapcar 'car plist))
431 (run-hooks 'proced-after-send-signal-hook)))))
432
433 (defun proced-help ()
434 "Provide help for the `proced' user."
435 (interactive)
436 (if (eq last-command 'proced-help)
437 (describe-mode)
438 (message proced-help-string)))
439
440 (defun proced-undo ()
441 "Undo in a proced buffer.
442 This doesn't recover killed processes, it just undoes changes in the proced
443 buffer. You can use it to recover marks."
444 (interactive)
445 (let (buffer-read-only)
446 (undo))
447 (message "Change in proced buffer undone.
448 Killed processes cannot be recovered by Emacs."))
449
450 (provide 'proced)
451
452 ;;; proced.el ends here.