aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoland Winkler2008-09-06 23:05:49 +0000
committerRoland Winkler2008-09-06 23:05:49 +0000
commitd74d0c4266e503337d52b5f2abeb45757af54c3a (patch)
tree8fde96bd8df166bad3e7c53966c7d2aec9060223
parentea92add1fe600acbf03ab1bf80094846d52624ef (diff)
downloademacs-d74d0c4266e503337d52b5f2abeb45757af54c3a.tar.gz
emacs-d74d0c4266e503337d52b5f2abeb45757af54c3a.zip
Require time-date.
(proced-command-alist, proced-command, proced-goal-header-re) (proced-sorting-schemes-alist, proced-sorting-scheme) (proced-header-alist, proced-sorting-schemes-re) (proced-skip-regexp, proced-next-line, proced-previous-line) (proced-listing-type, proced-sorting-scheme-p): Removed. (proced-grammar-alist, proced-custom-attributes) (proced-format-alist, proced-format, proced-filter-alist) (proced-filter, proced-sort, proced-goal-attribute) (proced-timer-interval, proced-timer-flag, proced-timer) (proced-process-alist, proced-sort-internal, proced-process-tree) (proced-header-help-echo, proced-field-help-echo): New variables. (proced-pid-at-point, proced-timer, proced-mark-process-alist) (proced-omit-process, proced-filter, proced-process-tree) (proced-filter-children, proced-children-pids) (proced-filter-parents, proced-<, proced-string-lessp) (proced-time-lessp, proced-xor, proced-sort-p) (proced-format-time, proced-format-start, proced-format-ttname) (proced-format, proced-process-attributes): New functions. (proced-toggle-timer-flag, proced-mark-children) (proced-mark-parents, proced-filter-interactive) (proced-filter-attribute, proced-sort-interactive) (proced-sort-header, proced-format-interactive): New commands. (proced-move-to-goal-column): Use goal-column. (proced-mode): Use proced-timer. (proced-do-mark-all): Display process count. Use use-region-p. Simplify. (proced-omit-processes): Use use-region-p. (proced-sort-pcpu, proced-sort-pmem, proced-sort-pid) (proced-sort-start, proced-sort-time, proced-sort-user): Use proced-sort-interactive. (proced-sort): Make it a function that performs the actual sort. (proced-update): New arg revert. Use proced-process-alist, proced-filter, proced-sort, proced-format, and proced-grammar-alist. Preserve position of point based on fields. Make header line and fields clickable. (proced-send-signal): Use proced-pid-at-point and proced-process-alist. (proced-why): Use save-selected-window. (proced-log): Use buffer-read-only.
-rw-r--r--lisp/ChangeLog43
-rw-r--r--lisp/proced.el1355
2 files changed, 999 insertions, 399 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 75ed212df39..e99988e0b7a 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,46 @@
12008-09-06 Roland Winkler <Roland.Winkler@physik.uni-erlangen.de>
2
3 * proced.el: Require time-date.
4 (proced-command-alist, proced-command, proced-goal-header-re)
5 (proced-sorting-schemes-alist, proced-sorting-scheme)
6 (proced-header-alist, proced-sorting-schemes-re)
7 (proced-skip-regexp, proced-next-line, proced-previous-line)
8 (proced-listing-type, proced-sorting-scheme-p): Removed.
9 (proced-grammar-alist, proced-custom-attributes)
10 (proced-format-alist, proced-format, proced-filter-alist)
11 (proced-filter, proced-sort, proced-goal-attribute)
12 (proced-timer-interval, proced-timer-flag, proced-timer)
13 (proced-process-alist, proced-sort-internal, proced-process-tree)
14 (proced-header-help-echo, proced-field-help-echo): New variables.
15 (proced-pid-at-point, proced-timer, proced-mark-process-alist)
16 (proced-omit-process, proced-filter, proced-process-tree)
17 (proced-filter-children, proced-children-pids)
18 (proced-filter-parents, proced-<, proced-string-lessp)
19 (proced-time-lessp, proced-xor, proced-sort-p)
20 (proced-format-time, proced-format-start, proced-format-ttname)
21 (proced-format, proced-process-attributes): New functions.
22 (proced-toggle-timer-flag, proced-mark-children)
23 (proced-mark-parents, proced-filter-interactive)
24 (proced-filter-attribute, proced-sort-interactive)
25 (proced-sort-header, proced-format-interactive): New commands.
26 (proced-move-to-goal-column): Use goal-column.
27 (proced-mode): Use proced-timer.
28 (proced-do-mark-all): Display process count. Use use-region-p.
29 Simplify.
30 (proced-omit-processes): Use use-region-p.
31 (proced-sort-pcpu, proced-sort-pmem, proced-sort-pid)
32 (proced-sort-start, proced-sort-time, proced-sort-user): Use
33 proced-sort-interactive.
34 (proced-sort): Make it a function that performs the actual sort.
35 (proced-update): New arg revert. Use proced-process-alist,
36 proced-filter, proced-sort, proced-format, and
37 proced-grammar-alist. Preserve position of point based on fields.
38 Make header line and fields clickable.
39 (proced-send-signal): Use proced-pid-at-point and
40 proced-process-alist.
41 (proced-why): Use save-selected-window.
42 (proced-log): Use buffer-read-only.
43
12008-09-06 Chong Yidong <cyd@stupidchicken.com> 442008-09-06 Chong Yidong <cyd@stupidchicken.com>
2 45
3 * tooltip.el (tooltip-mode): Initialize unconditionally to t. 46 * tooltip.el (tooltip-mode): Initialize unconditionally to t.
diff --git a/lisp/proced.el b/lisp/proced.el
index afc11e4c88b..007fe20b517 100644
--- a/lisp/proced.el
+++ b/lisp/proced.el
@@ -22,119 +22,26 @@
22 22
23;;; Commentary: 23;;; Commentary:
24 24
25;; Proced makes an Emacs buffer containing a listing of the current system 25;; Proced makes an Emacs buffer containing a listing of the current
26;; processes (using ps(1)). You can use the normal Emacs commands 26;; system processes. You can use the normal Emacs commands to move around
27;; to move around in this buffer, and special Proced commands to operate 27;; in this buffer, and special Proced commands to operate on the processes
28;; on the processes listed. 28;; listed.
29;; 29;;
30;; To do: 30;; To do:
31;; - use list-system-processes and system-process-attributes 31;; - use defcustom where appropriate
32;; - sort and filter by user name or other criteria 32;; - interactive temporary customizability of `proced-grammar-alist'
33;; - make fields clickable for marking / filtering / sorting:
34;; clicking on a USER field marks all processes of this user etc
35;; clicking on a %MEM field marks all processes with at least this %MEM.
36;; clicking on a header field sorts according to this header
37;; - mark parent and children PIDs (or both)
38;; - automatic update of process list
39;; - allow "sudo kill PID", "renice PID" 33;; - allow "sudo kill PID", "renice PID"
40 34
41;;; Code: 35;;; Code:
42 36
37(require 'time-date) ; for `with-decoded-time-value'
38
43(defgroup proced nil 39(defgroup proced nil
44 "Proced mode." 40 "Proced mode."
45 :group 'processes 41 :group 'processes
46 :group 'unix 42 :group 'unix
47 :prefix "proced-") 43 :prefix "proced-")
48 44
49;; FIXME: a better approach instead of PID-COLUMN would be based
50;; on `proced-header-alist' once we have a reliable scheme to set this variable
51(defcustom proced-command-alist
52 (cond ((memq system-type '(berkeley-unix))
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 '(gnu gnu/linux)) ; BSD syntax
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))
64 "uwww") 2)))
65 ((memq system-type '(darwin))
66 `(("user" ("ps" "-u" ,(number-to-string (user-uid))) 2)
67 ("all" ("ps" "-Au") 2)))
68 (t ; standard UNIX syntax; doesn't allow 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.
72Each element has the form (NAME COMMAND PID-COLUMN).
73NAME is a shorthand name to select the type of listing.
74COMMAND is a list (COMMAND-NAME ARG1 ARG2 ...),
75where COMMAND-NAME is the command to generate the listing (usually \"ps\").
76ARG1, ARG2, ... are arguments passed to COMMAND-NAME to generate
77a particular listing. These arguments differ under various operating systems.
78PID-COLUMN is the column number (starting from 1) of the process ID."
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
85(defcustom proced-command (if (zerop (user-real-uid)) "all" "user")
86 "Name of process listing.
87Must be the car of an element of `proced-command-alist'."
88 :group 'proced
89 :type '(string :tag "name"))
90(make-variable-buffer-local 'proced-command)
91
92;; Should we incorporate in NAME that sorting can be done in ascending
93;; or descending order? Then we couldn't associate NAME anymore with one
94;; of the headers in the output of ps(1).
95;; FIXME: A sorting scheme without options or with an option being a symbol
96;; should be implemented in elisp
97(defcustom proced-sorting-schemes-alist
98 (cond ((memq system-type '(gnu gnu/linux)) ; GNU long options
99 '(("%CPU" "--sort" "-pcpu") ; descending order
100 ("%MEM" "--sort" "-pmem") ; descending order
101 ("COMMAND" "--sort" "args")
102 ("PID" "--sort" "pid")
103 ("PGID,PID" "--sort" "pgid,pid")
104 ("PPID,PID" "--sort" "ppid,pid")
105 ("RSS" "--sort" "rss,pid") ; equal RSS's are rare
106 ("STAT,PID" "--sort" "stat,pid")
107 ("START" "--sort" "start_time")
108 ("TIME" "--sort" "cputime")
109 ("TTY,PID" "--sort" "tty,pid")
110 ("UID,PID" "--sort" "uid,pid")
111 ("USER,PID" "--sort" "user,pid")
112 ("VSZ,PID" "--sort" "vsz,pid"))))
113 "Alist of sorting schemes.
114Each element is a list (NAME OPTION1 OPTION2 ...).
115NAME denotes the sorting scheme. It is the name of a header or a
116comma-separated sequence of headers in the output of ps(1).
117OPTION1, OPTION2, ... are options defining the sorting scheme."
118 :group 'proced
119 :type '(repeat (cons (string :tag "name")
120 (repeat (string :tag "option")))))
121
122(defcustom proced-sorting-scheme nil
123 "Proced sorting type.
124Must be the car of an element of `proced-sorting-schemes-alist' or nil."
125 :group 'proced
126 :type `(choice ,@(append '((const nil)) ; sorting type may be nil
127 (mapcar (lambda (item)
128 (list 'const (car item)))
129 proced-sorting-schemes-alist))))
130(make-variable-buffer-local 'proced-sorting-scheme)
131
132(defcustom proced-goal-header-re "\\b\\(CMD\\|COMMAND\\)\\b"
133 "If non-nil, regexp that defines the `proced-goal-column'."
134 :group 'proced
135 :type '(choice (const :tag "none" nil)
136 (regexp :tag "regexp")))
137
138(defcustom proced-signal-function 'signal-process 45(defcustom proced-signal-function 'signal-process
139 "Name of signal function. 46 "Name of signal function.
140It can be an elisp function (usually `signal-process') or a string specifying 47It can be an elisp function (usually `signal-process') or a string specifying
@@ -144,7 +51,7 @@ the external command (usually \"kill\")."
144 (string :tag "command"))) 51 (string :tag "command")))
145 52
146(defcustom proced-signal-list 53(defcustom proced-signal-list
147 '(;; signals supported on all POSIX compliant systems 54 '( ;; signals supported on all POSIX compliant systems
148 ("HUP (1. Hangup)") 55 ("HUP (1. Hangup)")
149 ("INT (2. Terminal interrupt)") 56 ("INT (2. Terminal interrupt)")
150 ("QUIT (3. Terminal quit)") 57 ("QUIT (3. Terminal quit)")
@@ -162,7 +69,199 @@ the external command (usually \"kill\")."
162 :group 'proced 69 :group 'proced
163 :type '(repeat (string :tag "signal"))) 70 :type '(repeat (string :tag "signal")))
164 71
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.)
76;;
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.
81;;
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))
115 ;;
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.
123
124Each element has the form
125
126 (KEY NAME FORMAT JUSTIFY PREDICATE REVERSE SORT-SCHEME FILTER-SCHEME).
127
128KEY is the car of a process attribute.
129
130NAME appears in the header line.
131
132FORMAT specifies the format for displaying the attribute values.
133It is either a string passed to `format' or a function called with one
134argument, the value of the attribute.
135
136If JUSTIFY is an integer, its modulus gives the width of the attribute
137vales formatted with FORMAT. If JUSTIFY is positive, NAME appears
138right-justified, otherwise it appears left-justified. If JUSTIFY is 'left
139or 'right, the field width is calculated from all field values in the listing.
140If JUSTIFY is 'left, the field values are formatted left-justified and
141right-justified otherwise.
142
143PREDICATE is the predicate for sorting and filtering the process listing
144based on attribute KEY. PREDICATE takes two arguments P1 and P2,
145the corresponding attribute values of two processes. PREDICATE should
146return 'equal if P1 has same rank like P2. Any other non-nil value says
147that P1 is \"less than\" P2, or nil if not.
148
149REVERSE is non-nil if the sort order is opposite to the order defined
150by PREDICATE.
151
152SORT-SCHEME is a list (KEY1 KEY2 ...) defing a hierarchy of rules
153for sorting the process listing. KEY1, KEY2, ... are KEYs appearing as cars
154of `proced-grammar-alist'. First the PREDICATE of KEY1 is evaluated.
155If it yields non-equal, it defines the sorting order for the corresponding
156processes. If it evaluates to 'equal the PREDICATE of KEY2 is evaluated, etc.
157
158FILTER-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
160compares the value of attribute KEY of every process with the value
161of attribute KEY of the process at the position of point using PREDICATE.
162If PREDICATE yields non-nil, the process is accepted if LESS-B is non-nil.
163If PREDICATE yields 'equal, the process is accepted if EQUAL-B is non-nil.
164If PREDICATE yields nil, the process is accepted if LARGER-B is non-nil.")
165
166(defvar proced-custom-attributes nil
167 "List of functions defining custom attributes.
168This variable extends the functionality of `proced-process-attributes'.
169Each function is called with one argument, the list of attributes
170of a system process. It returns a cons cell of the form (KEY . VALUE)
171like `system-process-attributes'.")
172
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.
179
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.
189The car of each element is a symbol, the name of the format.
190The cdr is a list of keys appearing in `proced-grammar-alist'.")
191
192(defvar proced-format 'short
193 "Current format of Proced listing.
194It can be the car of an element of `proced-format-alist'.
195It can also be a list of keys appearing in `proced-grammar-alist'.")
196(make-variable-buffer-local 'proced-format)
197
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.
209The car of each element is a symbol, the name of the filter.
210The cdr is a list of elementary filters that are applied to every process.
211A process is displayed if it passes all elementary filters of a selected
212filter.
213
214An 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.")
223
224(defvar proced-filter 'user
225 "Current filter of proced listing.
226It can be the car of an element of `proced-filter-alist'.
227It can also be a list of elementary filters as in the cdrs of the elements
228of `proced-filter-alist'.")
229(make-variable-buffer-local 'proced-filter)
230
231(defvar proced-sort 'pcpu
232 "Current sorting scheme for proced listing.
233It must be the KEY of an element of `proced-grammar-alist'.
234It can also be a list of KEYs as in the SORT-SCHEMEs of the elements
235of `proced-grammar-alist'.")
236(make-variable-buffer-local 'proced-format)
237
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")))
243
244(defcustom proced-timer-interval 5
245 "Time interval in seconds for updating Proced buffers."
246 :group 'proced
247 :type 'integer)
248
249(defcustom proced-timer-flag nil
250 "Non-nil for regular update of a Proced buffer.
251Can be changed interactively via `proced-toggle-timer-flag'."
252 :group 'proced
253 :type 'boolean)
254(make-variable-buffer-local 'proced-timer-flag)
255
165;; Internal variables 256;; Internal variables
257
258(defvar proced-process-alist nil
259 "Alist of PIDs displayed by Proced.")
260(make-variable-buffer-local 'proced-process-alist)
261
262(defvar proced-sort-internal nil
263 "Sorting scheme for listing (internal format).")
264
166(defvar proced-marker-char ?* ; the answer is 42 265(defvar proced-marker-char ?* ; the answer is 42
167 "In proced, the current mark character.") 266 "In proced, the current mark character.")
168 267
@@ -190,9 +289,30 @@ the external command (usually \"kill\")."
190 "Regexp matching a marked line. 289 "Regexp matching a marked line.
191Important: the match ends just after the marker.") 290Important: the match ends just after the marker.")
192 291
193(defvar proced-goal-column nil 292(defvar proced-header-line nil
194 "Proced goal column. Initialized based on `proced-goal-header-re'.") 293 "Headers in Proced buffer as a string.")
195(make-variable-buffer-local 'proced-goal-column) 294(make-variable-buffer-local 'proced-header-line)
295
296(defvar proced-log-buffer "*Proced log*"
297 "Name of Proced Log buffer.")
298
299(defvar proced-process-tree nil
300 "Process tree of listing (internal variable).")
301
302(defvar proced-timer nil
303 "Stores if Proced timer is already installed.")
304
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.")
308
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.")
312
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.")
196 316
197(defvar proced-font-lock-keywords 317(defvar proced-font-lock-keywords
198 (list 318 (list
@@ -208,35 +328,44 @@ Important: the match ends just after the marker.")
208 (let ((km (make-sparse-keymap))) 328 (let ((km (make-sparse-keymap)))
209 ;; moving 329 ;; moving
210 (define-key km " " 'proced-next-line) 330 (define-key km " " 'proced-next-line)
211 (define-key km "n" 'proced-next-line) 331 (define-key km "n" 'next-line)
212 (define-key km "p" 'proced-previous-line) 332 (define-key km "p" 'previous-line)
213 (define-key km "\C-n" 'proced-next-line) 333 (define-key km "\C-n" 'next-line)
214 (define-key km "\C-p" 'proced-previous-line) 334 (define-key km "\C-p" 'previous-line)
215 (define-key km "\C-?" 'proced-previous-line) 335 (define-key km "\C-?" 'previous-line)
216 (define-key km [down] 'proced-next-line) 336 (define-key km [down] 'next-line)
217 (define-key km [up] 'proced-previous-line) 337 (define-key km [up] 'previous-line)
218 ;; marking 338 ;; marking
219 (define-key km "d" 'proced-mark) ; Dired compatibility 339 (define-key km "d" 'proced-mark) ; Dired compatibility ("delete")
220 (define-key km "m" 'proced-mark) 340 (define-key km "m" 'proced-mark)
221 (define-key km "u" 'proced-unmark) 341 (define-key km "u" 'proced-unmark)
222 (define-key km "\177" 'proced-unmark-backward) 342 (define-key km "\177" 'proced-unmark-backward)
223 (define-key km "M" 'proced-mark-all) 343 (define-key km "M" 'proced-mark-all)
224 (define-key km "U" 'proced-unmark-all) 344 (define-key km "U" 'proced-unmark-all)
225 (define-key km "t" 'proced-toggle-marks) 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)
226 ;; sorting 352 ;; sorting
227 (define-key km "sc" 'proced-sort-pcpu) 353 (define-key km "sc" 'proced-sort-pcpu)
228 (define-key km "sm" 'proced-sort-pmem) 354 (define-key km "sm" 'proced-sort-pmem)
229 (define-key km "sp" 'proced-sort-pid) 355 (define-key km "sp" 'proced-sort-pid)
230 (define-key km "ss" 'proced-sort-start) 356 (define-key km "ss" 'proced-sort-start)
231 (define-key km "sS" 'proced-sort) 357 (define-key km "sS" 'proced-sort-interactive)
232 (define-key km "st" 'proced-sort-time) 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)
233 ;; operate 363 ;; operate
234 (define-key km "o" 'proced-omit-processes) 364 (define-key km "o" 'proced-omit-processes)
235 (define-key km "x" 'proced-send-signal) ; Dired compatibility 365 (define-key km "x" 'proced-send-signal) ; Dired compatibility
236 (define-key km "k" 'proced-send-signal) ; kill processes 366 (define-key km "k" 'proced-send-signal) ; kill processes
237 ;; misc 367 ;; misc
238 (define-key km "l" 'proced-listing-type) 368 (define-key km "g" 'revert-buffer) ; Dired compatibility
239 (define-key km "g" 'revert-buffer) ; Dired compatibility
240 (define-key km "h" 'describe-mode) 369 (define-key km "h" 'describe-mode)
241 (define-key km "?" 'proced-help) 370 (define-key km "?" 'proced-help)
242 (define-key km "q" 'quit-window) 371 (define-key km "q" 'quit-window)
@@ -258,54 +387,52 @@ Important: the match ends just after the marker.")
258 :help "Unmark All Process"] 387 :help "Unmark All Process"]
259 ["Toggle Marks" proced-toggle-marks 388 ["Toggle Marks" proced-toggle-marks
260 :help "Marked Processes Become Unmarked, and Vice Versa"] 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"]
261 "--" 394 "--"
262 ["Sort..." proced-sort 395 ("Filters"
263 :help "Sort Process List"] 396 :help "Select Filter for Process Listing"
264 ["Sort by %CPU" proced-sort-pcpu (proced-sorting-scheme-p "%CPU")] 397 ,@(mapcar (lambda (el)
265 ["Sort by %MEM" proced-sort-pmem (proced-sorting-scheme-p "%MEM")] 398 (let ((filter (car el)))
266 ["Sort by PID" proced-sort-pid (proced-sorting-scheme-p "PID")] 399 `[,(symbol-name filter)
267 ["Sort by START" proced-sort-start (proced-sorting-scheme-p "START")] 400 (proced-filter-interactive ',filter)
268 ["Sort by TIME" proced-sort-time (proced-sorting-scheme-p "TIME")] 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))
269 "--" 424 "--"
270 ["Omit Marked Processes" proced-omit-processes 425 ["Omit Marked Processes" proced-omit-processes
271 :help "Omit Marked Processes in Process Listing."] 426 :help "Omit Marked Processes in Process Listing."]
272 "--" 427 "--"
273 ["Revert" revert-buffer 428 ["Revert" revert-buffer
274 :help "Revert Process Listing"] 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"]
275 ["Send signal" proced-send-signal 434 ["Send signal" proced-send-signal
276 :help "Send Signal to Marked Processes"] 435 :help "Send Signal to Marked Processes"]))
277 ("Listing Type"
278 :help "Select Type of Process Listing"
279 ,@(mapcar (lambda (el)
280 (let ((command (car el)))
281 `[,command (proced-listing-type ,command)
282 :style radio
283 :selected (string= proced-command ,command)]))
284 proced-command-alist))))
285
286(defconst proced-help-string
287 "(n)ext, (p)revious, (m)ark, (u)nmark, (k)ill, (q)uit (type ? for more help)"
288 "Help string for proced.")
289
290(defvar proced-header-line nil
291 "Headers in Proced buffer as a string.")
292(make-variable-buffer-local 'proced-header-line)
293
294(defvar proced-header-alist nil
295 "Alist of headers in Proced buffer.
296Each element is of the form (NAME START END JUSTIFY).
297NAME is name of header in the output of ps(1).
298START and END are column numbers starting from 0.
299END is t if there is no end column for that field.
300JUSTIFY is 'left or 'right for left or right-justified output of ps(1).")
301(make-variable-buffer-local 'proced-header-alist)
302
303(defvar proced-sorting-schemes-re nil
304 "Regexp to match valid sorting schemes.")
305(make-variable-buffer-local 'proced-sorting-schemes-re)
306
307(defvar proced-log-buffer "*Proced log*"
308 "Name of Proced Log buffer.")
309 436
310;; helper functions 437;; helper functions
311(defun proced-marker-regexp () 438(defun proced-marker-regexp ()
@@ -317,20 +444,37 @@ JUSTIFY is 'left or 'right for left or right-justified output of ps(1).")
317 "Display success message for ACTION performed for COUNT processes." 444 "Display success message for ACTION performed for COUNT processes."
318 (message "%s %s process%s" action count (if (= 1 count) "" "es"))) 445 (message "%s %s process%s" action count (if (= 1 count) "" "es")))
319 446
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.
454
320(defun proced-move-to-goal-column () 455(defun proced-move-to-goal-column ()
321 "Move to `proced-goal-column' if non-nil." 456 "Move to `goal-column' if non-nil."
322 (beginning-of-line) 457 (beginning-of-line)
323 (if proced-goal-column 458 (unless (eobp)
324 (forward-char proced-goal-column) 459 (if goal-column
325 (forward-char 2))) 460 (forward-char goal-column)
326 461 (forward-char 2))))
327;; FIXME: a better approach would be based on `proced-header-alist' 462
328;; once we have a reliable scheme to set this variable 463(defun proced-header-line ()
329(defsubst proced-skip-regexp () 464 "Return header line for Proced buffer."
330 "Regexp to skip in process listing to find PID column." 465 (list (propertize " " 'display '(space :align-to 0))
331 (apply 'concat (make-list (1- (nth 2 (assoc proced-command 466 (replace-regexp-in-string ;; preserve text properties
332 proced-command-alist))) 467 "\\(%\\)" "\\1\\1" (substring proced-header-line (window-hscroll)))))
333 "\\s-+\\S-+"))) 468
469(defun proced-pid-at-point ()
470 "Return pid of system process at point.
471Return 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))))
476
477;; proced mode
334 478
335(define-derived-mode proced-mode nil "Proced" 479(define-derived-mode proced-mode nil "Proced"
336 "Mode for displaying UNIX system processes and sending signals to them. 480 "Mode for displaying UNIX system processes and sending signals to them.
@@ -346,7 +490,10 @@ Type \\[proced-send-signal] to send signals to marked processes.
346 (add-hook 'post-command-hook 'force-mode-line-update nil t) 490 (add-hook 'post-command-hook 'force-mode-line-update nil t)
347 (set (make-local-variable 'revert-buffer-function) 'proced-revert) 491 (set (make-local-variable 'revert-buffer-function) 'proced-revert)
348 (set (make-local-variable 'font-lock-defaults) 492 (set (make-local-variable 'font-lock-defaults)
349 '(proced-font-lock-keywords t nil nil beginning-of-line))) 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))))
350 497
351;; Proced mode is suitable only for specially formatted data. 498;; Proced mode is suitable only for specially formatted data.
352(put 'proced-mode 'mode-class 'special) 499(put 'proced-mode 'mode-class 'special)
@@ -367,7 +514,7 @@ information will be displayed but not selected.
367 (setq new (zerop (buffer-size))) 514 (setq new (zerop (buffer-size)))
368 (if new (proced-mode)) 515 (if new (proced-mode))
369 (if (or new arg) 516 (if (or new arg)
370 (proced-update)) 517 (proced-update t))
371 (if arg 518 (if arg
372 (display-buffer buffer) 519 (display-buffer buffer)
373 (pop-to-buffer buffer) 520 (pop-to-buffer buffer)
@@ -375,19 +522,25 @@ information will be displayed but not selected.
375 (substitute-command-keys 522 (substitute-command-keys
376 "Type \\<proced-mode-map>\\[quit-window] to quit, \\[proced-help] for help"))))) 523 "Type \\<proced-mode-map>\\[quit-window] to quit, \\[proced-help] for help")))))
377 524
378(defun proced-next-line (arg) 525(defun proced-timer ()
379 "Move down lines then position at `proced-goal-column'. 526 "Update Proced buffers regularly using `run-at-time'."
380Optional prefix ARG says how many lines to move; default is one line." 527 (dolist (buf (buffer-list))
381 (interactive "p") 528 (with-current-buffer buf
382 (forward-line arg) 529 (if (and (eq major-mode 'proced-mode)
383 (proced-move-to-goal-column)) 530 proced-timer-flag)
384 531 (proced-update t t)))))
385(defun proced-previous-line (arg) 532
386 "Move up lines then position at `proced-goal-column'. 533(defun proced-toggle-timer-flag (arg)
387Optional prefix ARG says how many lines to move; default is one line." 534 "Change whether this Proced buffer is updated regularly.
388 (interactive "p") 535With prefix ARG, update this buffer regularly if ARG is positive,
389 (forward-line (- arg)) 536otherwise do not update. Sets the variable `proced-timer-flag'.
390 (proced-move-to-goal-column)) 537The 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))
391 544
392(defun proced-mark (&optional count) 545(defun proced-mark (&optional count)
393 "Mark the current (or next COUNT) processes." 546 "Mark the current (or next COUNT) processes."
@@ -436,23 +589,24 @@ unmark the region."
436 "Mark all processes using MARK. 589 "Mark all processes using MARK.
437If `transient-mark-mode' is turned on and the region is active, 590If `transient-mark-mode' is turned on and the region is active,
438mark the region." 591mark the region."
439 (let (buffer-read-only) 592 (let ((count 0) end buffer-read-only)
440 (save-excursion 593 (save-excursion
441 (if (and transient-mark-mode mark-active) 594 (if (use-region-p)
442 ;; Operate even on those lines that are only partially a part 595 ;; Operate even on those lines that are only partially a part
443 ;; of region. This appears most consistent with 596 ;; of region. This appears most consistent with
444 ;; `proced-move-to-goal-column'. 597 ;; `proced-move-to-goal-column'.
445 (let ((end (save-excursion 598 (progn (setq end (save-excursion
446 (goto-char (region-end)) 599 (goto-char (region-end))
447 (unless (looking-at "^") (forward-line)) 600 (unless (looking-at "^") (forward-line))
448 (point)))) 601 (point)))
449 (goto-char (region-beginning)) 602 (goto-char (region-beginning))
450 (unless (looking-at "^") (beginning-of-line)) 603 (unless (looking-at "^") (beginning-of-line)))
451 (while (< (point) end)
452 (proced-insert-mark mark)))
453 (goto-char (point-min)) 604 (goto-char (point-min))
454 (while (not (eobp)) 605 (setq end (point-max)))
455 (proced-insert-mark mark)))))) 606 (while (< (point) end)
607 (setq count (1+ count))
608 (proced-insert-mark mark))
609 (proced-success-message "Marked" count))))
456 610
457(defun proced-toggle-marks () 611(defun proced-toggle-marks ()
458 "Toggle marks: marked processes become unmarked, and vice versa." 612 "Toggle marks: marked processes become unmarked, and vice versa."
@@ -478,152 +632,613 @@ Otherwise move one line forward after inserting the mark."
478 (delete-char 1) 632 (delete-char 1)
479 (unless backward (forward-line))) 633 (unless backward (forward-line)))
480 634
635(defun proced-mark-children (ppid &optional omit-ppid)
636 "Mark child processes of process PPID.
637Also 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)))
641
642(defun proced-mark-parents (cpid &optional omit-cpid)
643 "Mark parent processes of process CPID.
644Also 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)))
648
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))))
663
481;; Mostly analog of `dired-do-kill-lines'. 664;; Mostly analog of `dired-do-kill-lines'.
482;; However, for negative args the target lines of `dired-do-kill-lines' 665;; However, for negative args the target lines of `dired-do-kill-lines'
483;; include the current line, whereas `dired-mark' for negative args operates 666;; include the current line, whereas `dired-mark' for negative args operates
484;; on the preceding lines. Here we are consistent with `dired-mark'. 667;; on the preceding lines. Here we are consistent with `dired-mark'.
485(defun proced-omit-processes (&optional arg quiet) 668(defun proced-omit-processes (&optional arg quiet)
486 "Omit marked processes. 669 "Omit marked processes.
487With prefix ARG, omit that many lines starting with the current line. 670With prefix ARG, omit that many lines starting with the current line.
488\(A negative argument omits backward.) 671\(A negative argument omits backward.)
672If `transient-mark-mode' is turned on and the region is active,
673omit the processes in region.
489If QUIET is non-nil suppress status message. 674If QUIET is non-nil suppress status message.
490Returns count of omitted lines." 675Returns count of omitted lines."
491 (interactive "P") 676 (interactive "P")
492 (let ((mark-re (proced-marker-regexp)) 677 (let ((mark-re (proced-marker-regexp))
493 (count 0) 678 (count 0)
494 buffer-read-only) 679 buffer-read-only)
495 (if arg 680 (cond ((use-region-p) ;; Omit active region
496 ;; Omit ARG lines starting with the current line. 681 (let ((lines (count-lines (region-beginning) (region-end))))
497 (delete-region (line-beginning-position) 682 (save-excursion
498 (save-excursion 683 (goto-char (region-beginning))
499 (if (<= 0 arg) 684 (while (< count lines)
500 (setq count (- arg (forward-line arg))) 685 (proced-omit-process)
501 (setq count (min (1- (line-number-at-pos)) 686 (setq count (1+ count))))))
502 (abs arg))) 687 ((not arg) ;; Omit marked lines
503 (forward-line (- count))) 688 (save-excursion
504 (point))) 689 (goto-char (point-min))
505 ;; Omit marked lines 690 (while (and (not (eobp))
506 (save-excursion 691 (re-search-forward mark-re nil t))
507 (goto-char (point-min)) 692 (proced-omit-process)
508 (while (and (not (eobp)) 693 (setq count (1+ count)))))
509 (re-search-forward mark-re nil t)) 694 ((< 0 arg) ;; Omit forward
510 (delete-region (match-beginning 0) 695 (while (and (not (eobp)) (< count arg))
511 (save-excursion (forward-line) (point))) 696 (proced-omit-process)
512 (setq count (1+ count))))) 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)))))
513 (unless (zerop count) (proced-move-to-goal-column)) 703 (unless (zerop count) (proced-move-to-goal-column))
514 (unless quiet (proced-success-message "Omitted" count)) 704 (unless quiet (proced-success-message "Omitted" count))
515 count)) 705 count))
516 706
517(defun proced-listing-type (command) 707(defun proced-omit-process ()
518 "Select `proced' listing type COMMAND from `proced-command-alist'." 708 "Omit process from listing point is on.
709Update `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))))
714
715;;; Filtering
716
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)
743
744(defun proced-filter-interactive (scheme &optional revert)
745 "Filter Proced buffer using SCHEME.
746When called interactively, an empty string means nil, i.e., no filtering.
747With prefix REVERT non-nil revert listing."
519 (interactive 748 (interactive
520 (list (completing-read "Listing type: " proced-command-alist nil t))) 749 (let ((scheme (completing-read "Filter: "
521 (setq proced-command command) 750 proced-filter-alist nil t)))
522 (proced-update)) 751 (list (if (string= "" scheme) nil (intern scheme))
752 current-prefix-arg)))
753 (setq proced-filter scheme)
754 (proced-update revert))
755
756(defun proced-process-tree (process-alist)
757 "Return process tree for PROCESS-ALIST.
758The process tree is an alist with elements (PPID PID1 PID2 ...).
759PPID is a parent PID. PID1, PID2, ... are the child processes of PPID.
760The 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)))))))
771
772(defun proced-filter-children (process-alist ppid &optional omit-ppid)
773 "For PROCESS-ALIST return list of child processes of PPID.
774This 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)))
782
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))))
790
791(defun proced-filter-parents (process-alist pid &optional omit-pid)
792 "For PROCESS-ALIST return list of parent processes of PID.
793This 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))
798
799(defun proced-filter-attribute (&optional event)
800 "Filter Proced listing based on the attribute at point.
801Optional 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."))))
822
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.
829
830(defun proced-< (num1 num2)
831 "Return t if NUM1 less than NUM2.
832Return `equal' if NUM1 equals NUM2. Return nil if NUM1 greater than NUM2."
833 (if (= num1 num2)
834 'equal
835 (< num1 num2)))
836
837(defun proced-string-lessp (s1 s2)
838 "Return t if string S1 is less than S2 in lexicographic order.
839Return `equal' if S1 and S2 have identical contents.
840Return nil otherwise."
841 (if (string= s1 s2)
842 'equal
843 (string-lessp s1 s2)))
844
845(defun proced-time-lessp (t1 t2)
846 "Return t if time value T1 is less than time value T2.
847Return `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))))
523 857
524(defun proced-header-line () 858;;; Sorting
525 "Return header line for Proced buffer." 859
526 (list (propertize " " 'display '(space :align-to 0)) 860(defsubst proced-xor (b1 b2)
527 (replace-regexp-in-string 861 "Return the logical exclusive or of args B1 and B2."
528 "%" "%%" (substring proced-header-line (window-hscroll))))) 862 (and (or b1 b2)
863 (not (and b1 b2))))
864
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)))))
887
888(defun proced-sort (process-alist sorter)
889 "Sort PROCESS-ALIST using scheme SORTER.
890Return 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))
904
905(defun proced-sort-interactive (scheme &optional revert)
906 "Sort Proced buffer using SCHEME.
907When called interactively, an empty string means nil, i.e., no sorting.
908With 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))
916
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))
921
922(defun proced-sort-pmem (&optional revert)
923 "Sort Proced buffer by percentage memory usage (%MEM)."
924 (interactive "P")
925 (proced-sort-interactive 'pmem))
926
927(defun proced-sort-pid (&optional revert)
928 "Sort Proced buffer by PID."
929 (interactive "P")
930 (proced-sort-interactive 'pid revert))
931
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))
936
937(defun proced-sort-time (&optional revert)
938 "Sort Proced buffer by CPU time (TIME)."
939 (interactive "P")
940 (proced-sort-interactive 'time revert))
941
942(defun proced-sort-user (&optional revert)
943 "Sort Proced buffer by USER."
944 (interactive "P")
945 (proced-sort-interactive 'user revert))
946
947(defun proced-sort-header (event &optional revert)
948 "Sort Proced listing based on an attribute.
949EVENT is a mouse event with starting position in the header line.
950It 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."))))))
963
964;;; Formating
965
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))
979 (t
980 (format "%02d:%02d" minutes seconds)))))
981
982(defun proced-format-start (start)
983 "Format time START.
984The 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)))))
996
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))))
1003
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)
1035
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))
1050
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))
1059
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))))))))
1078
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)))))
529 1096
530(defun proced-update (&optional quiet) 1097(defun proced-format-interactive (scheme &optional revert)
1098 "Format Proced buffer using SCHEME.
1099When called interactively, an empty string means nil, i.e., no formatting.
1100With 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))
1108
1109;; generate listing
1110
1111(defun proced-process-attributes ()
1112 "Return alist of attributes for each system process.
1113This 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)))
1131
1132(defun proced-update (&optional revert quiet)
531 "Update the `proced' process information. Preserves point and marks. 1133 "Update the `proced' process information. Preserves point and marks.
1134With prefix REVERT non-nil, revert listing.
532Suppress status information if QUIET is nil." 1135Suppress status information if QUIET is nil."
533 ;; This is the main function that generates and updates the process listing. 1136 ;; This is the main function that generates and updates the process listing.
534 (interactive) 1137 (interactive "P")
535 (or quiet (message "Updating process information...")) 1138 (setq revert (or revert (not proced-process-alist)))
536 (let* ((command (cadr (assoc proced-command proced-command-alist))) 1139 (or quiet (message (if revert "Updating process information..."
537 (regexp (concat (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\)")) 1140 "Updating process display...")))
538 (old-pos (if (save-excursion 1141 ;; If point is on a field, we try to return point to that field.
539 (beginning-of-line) 1142 ;; Otherwise we try to return to the same column
540 (looking-at (concat "^[* ]" regexp))) 1143 (let ((old-pos (let ((key (get-text-property (point) 'proced-key)))
541 (cons (match-string-no-properties 1) 1144 (list (proced-pid-at-point) key
542 (current-column)))) 1145 (if key
543 buffer-read-only mp-list) 1146 (if (get-text-property (1- (point)) 'proced-key)
544 (goto-char (point-min)) 1147 (- (point) (previous-single-property-change
1148 (point) 'proced-key))
1149 0)
1150 (current-column)))))
1151 buffer-read-only mp-list)
545 ;; remember marked processes (whatever the mark was) 1152 ;; remember marked processes (whatever the mark was)
546 (while (re-search-forward (concat "^\\(\\S-\\)" regexp) nil t) 1153 (goto-char (point-min))
547 (push (cons (match-string-no-properties 2) 1154 (while (re-search-forward "^\\(\\S-\\)" nil t)
1155 (push (cons (save-match-data (proced-pid-at-point))
548 (match-string-no-properties 1)) mp-list)) 1156 (match-string-no-properties 1)) mp-list))
549 ;; generate new listing 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
550 (erase-buffer) 1168 (erase-buffer)
551 (apply 'call-process (car command) nil t nil 1169 (proced-format proced-process-alist proced-format)
552 (append (cdr command) (cdr (assoc proced-sorting-scheme
553 proced-sorting-schemes-alist))))
554 (goto-char (point-min)) 1170 (goto-char (point-min))
555 (while (not (eobp)) 1171 (while (not (eobp))
556 (insert " ") 1172 (insert " ")
557 (forward-line)) 1173 (forward-line))
558 ;; (delete-trailing-whitespace) 1174 (setq proced-header-line (concat " " proced-header-line))
559 (goto-char (point-min)) 1175 (if revert (set-buffer-modified-p nil))
560 (while (re-search-forward "[ \t\r]+$" nil t) 1176 ;; set `goal-column'
561 (delete-region (match-beginning 0) (match-end 0))) 1177 (let ((grammar (assq proced-goal-attribute proced-grammar-alist)))
562 (goto-char (point-min)) 1178 (setq goal-column ;; set to nil if no match
563 (let ((lep (line-end-position))) 1179 (if (and grammar
564 (setq proced-header-line (buffer-substring-no-properties (point) lep)) 1180 (not (zerop (buffer-size)))
565 (setq proced-header-alist nil) 1181 (string-match (regexp-quote (nth 1 grammar))
566 ;; FIXME: handle left/right justification properly 1182 proced-header-line))
567 (while (re-search-forward "\\([^ \t\n]+\\)[ \t]*\\($\\)?" lep t) 1183 (if (nth 3 grammar)
568 (push (list (match-string-no-properties 1) 1184 (match-beginning 0)
569 ;; take the column number starting from zero 1185 (match-end 0)))))
570 (- (match-beginning 0) (point-min)) 1186 ;; restore process marks and buffer position (if possible)
571 (or (not (not (match-beginning 2)))
572 (- (match-end 0) (point-min)))
573 'left)
574 proced-header-alist)))
575 (let ((temp (regexp-opt (mapcar 'car proced-header-alist) t)))
576 (setq proced-sorting-schemes-re
577 (concat "\\`" temp "\\(," temp "\\)*\\'")))
578 ;; remove header line from ps(1) output
579 (goto-char (point-min)) 1187 (goto-char (point-min))
580 (delete-region (point) 1188 (if (or mp-list old-pos)
581 (save-excursion (forward-line) (point))) 1189 (let (pid mark new-pos)
582 (set-buffer-modified-p nil) 1190 (while (not (eobp))
583 ;; set `proced-goal-column' 1191 (setq pid (proced-pid-at-point))
584 (if proced-goal-header-re 1192 (when (setq mark (assq pid mp-list))
585 (let ((hlist proced-header-alist) header) 1193 (insert (cdr mark))
586 (while (setq header (pop hlist)) 1194 (delete-char 1)
587 (if (string-match proced-goal-header-re (car header)) 1195 (beginning-of-line))
588 (setq proced-goal-column 1196 (when (eq (car old-pos) pid)
589 (if (eq 'left (nth 3 header)) 1197 (if (nth 1 old-pos)
590 (nth 1 header) (nth 2 header)) 1198 (let ((limit (line-end-position)) pos)
591 hlist nil))))) 1199 (while (and (not new-pos)
592 ;; restore process marks 1200 (setq pos (next-property-change (point) nil limit)))
593 (if mp-list 1201 (goto-char pos)
594 (save-excursion 1202 (when (eq (nth 1 old-pos)
595 (goto-char (point-min)) 1203 (get-text-property (point) 'proced-key))
596 (let (mark) 1204 (forward-char (min (nth 2 old-pos)
597 (while (re-search-forward (concat "^" regexp) nil t) 1205 (- (next-property-change (point))
598 (if (setq mark (assoc (match-string-no-properties 1) mp-list)) 1206 (point))))
599 (save-excursion 1207 (setq new-pos (point))))
600 (beginning-of-line) 1208 (unless new-pos
601 (insert (cdr mark)) 1209 (setq new-pos (if goal-column
602 (delete-char 1))))))) 1210 (+ (line-beginning-position) goal-column)
603 ;; restore buffer position (if possible) 1211 (line-beginning-position)))))
604 (goto-char (point-min)) 1212 (setq new-pos (min (+ (line-beginning-position) (nth 2 old-pos))
605 (if (and old-pos 1213 (line-end-position)))))
606 (re-search-forward 1214 (forward-line))
607 (concat "^[* ]" (proced-skip-regexp) "\\s-+" (car old-pos) "\\>") 1215 (if new-pos
608 nil t)) 1216 (goto-char new-pos)
609 (progn 1217 (proced-move-to-goal-column)))
610 (beginning-of-line)
611 (forward-char (cdr old-pos)))
612 (proced-move-to-goal-column)) 1218 (proced-move-to-goal-column))
613 ;; update modeline 1219 ;; update modeline
614 ;; Does the long mode-name clutter the modeline? 1220 ;; Does the long mode-name clutter the modeline?
615 (setq mode-name (concat "Proced: " proced-command 1221 (setq mode-name
616 (if proced-sorting-scheme 1222 (concat "Proced"
617 (concat " by " proced-sorting-scheme) 1223 (if proced-filter
618 ""))) 1224 (concat ": " (symbol-name proced-filter))
1225 "")
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 "")))
619 (force-mode-line-update) 1233 (force-mode-line-update)
620 ;; done 1234 ;; done
621 (or quiet (input-pending-p) 1235 (or quiet (input-pending-p)
622 (message "Updating process information...done.")))) 1236 (message (if revert "Updating process information...done."
1237 "Updating process display...done.")))))
623 1238
624(defun proced-revert (&rest args) 1239(defun proced-revert (&rest args)
625 "Analog of `revert-buffer'." 1240 "Analog of `revert-buffer'."
626 (proced-update)) 1241 (proced-update t))
627 1242
628;; I do not want to reinvent the wheel. Should we rename `dired-pop-to-buffer' 1243;; I do not want to reinvent the wheel. Should we rename `dired-pop-to-buffer'
629;; and move it to window.el so that proced and ibuffer can easily use it, too? 1244;; and move it to window.el so that proced and ibuffer can easily use it, too?
@@ -637,48 +1252,43 @@ If no process is marked, operate on current process.
637SIGNAL may be a string (HUP, INT, TERM, etc.) or a number. 1252SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
638If SIGNAL is nil display marked processes and query interactively for SIGNAL." 1253If SIGNAL is nil display marked processes and query interactively for SIGNAL."
639 (interactive) 1254 (interactive)
640 (let ((regexp (concat (proced-marker-regexp) 1255 (let ((regexp (proced-marker-regexp))
641 (proced-skip-regexp) "\\s-+\\([0-9]+\\>\\).*$")) 1256 process-alist)
642 process-list)
643 ;; collect marked processes 1257 ;; collect marked processes
644 (save-excursion 1258 (save-excursion
645 (goto-char (point-min)) 1259 (goto-char (point-min))
646 (while (re-search-forward regexp nil t) 1260 (while (re-search-forward regexp nil t)
647 (push (cons (match-string-no-properties 1) 1261 (push (cons (proced-pid-at-point)
648 ;; How much info should we collect here? Would it be 1262 ;; How much info should we collect here?
649 ;; better to collect only the PID (to avoid ambiguities)
650 ;; and the command name?
651 (substring (match-string-no-properties 0) 2)) 1263 (substring (match-string-no-properties 0) 2))
652 process-list))) 1264 process-alist)))
653 (setq process-list 1265 (setq process-alist
654 (if process-list 1266 (if process-alist
655 (nreverse process-list) 1267 (nreverse process-alist)
656 ;; take current process 1268 ;; take current process
657 (save-excursion 1269 (list (cons (proced-pid-at-point)
658 (beginning-of-line) 1270 (buffer-substring-no-properties
659 (looking-at (concat "^" (proced-skip-regexp) 1271 (+ 2 (line-beginning-position))
660 "\\s-+\\([0-9]+\\>\\).*$")) 1272 (line-end-position))))))
661 (list (cons (match-string-no-properties 1)
662 (substring (match-string-no-properties 0) 2))))))
663 (unless signal 1273 (unless signal
664 ;; Display marked processes (code taken from `dired-mark-pop-up'). 1274 ;; Display marked processes (code taken from `dired-mark-pop-up').
665 (let ((bufname " *Marked Processes*") 1275 (let ((bufname " *Marked Processes*")
666 (header proced-header-line)) ; inherit header line 1276 (header-line (substring-no-properties proced-header-line)))
667 (with-current-buffer (get-buffer-create bufname) 1277 (with-current-buffer (get-buffer-create bufname)
668 (setq truncate-lines t 1278 (setq truncate-lines t
669 proced-header-line header 1279 proced-header-line header-line ; inherit header line
670 header-line-format '(:eval (proced-header-line))) 1280 header-line-format '(:eval (proced-header-line)))
671 (add-hook 'post-command-hook 'force-mode-line-update nil t) 1281 (add-hook 'post-command-hook 'force-mode-line-update nil t)
672 (erase-buffer) 1282 (erase-buffer)
673 (dolist (process process-list) 1283 (dolist (process process-alist)
674 (insert " " (cdr process) "\n")) 1284 (insert " " (cdr process) "\n"))
675 (save-window-excursion 1285 (save-window-excursion
676 (dired-pop-to-buffer bufname) ; all we need 1286 (dired-pop-to-buffer bufname) ; all we need
677 (let* ((completion-ignore-case t) 1287 (let* ((completion-ignore-case t)
678 (pnum (if (= 1 (length process-list)) 1288 (pnum (if (= 1 (length process-alist))
679 "1 process" 1289 "1 process"
680 (format "%d processes" (length process-list)))) 1290 (format "%d processes" (length process-alist))))
681 ;; The following is an ugly hack. Is there a better way 1291 ;; The following is an ugly hack. Is there a better way
682 ;; to help people like me to remember the signals and 1292 ;; to help people like me to remember the signals and
683 ;; their meanings? 1293 ;; their meanings?
684 (tmp (completing-read (concat "Send signal [" pnum 1294 (tmp (completing-read (concat "Send signal [" pnum
@@ -698,12 +1308,11 @@ If SIGNAL is nil display marked processes and query interactively for SIGNAL."
698 (if (string-match "\\`[0-9]+\\'" signal) 1308 (if (string-match "\\`[0-9]+\\'" signal)
699 (string-to-number signal) 1309 (string-to-number signal)
700 (make-symbol signal)) 1310 (make-symbol signal))
701 signal))) ; number 1311 signal))) ; number
702 (dolist (process process-list) 1312 (dolist (process process-alist)
703 (condition-case err 1313 (condition-case err
704 (if (zerop (funcall 1314 (if (zerop (funcall
705 proced-signal-function 1315 proced-signal-function (car process) signal))
706 (string-to-number (car process)) signal))
707 (setq count (1+ count)) 1316 (setq count (1+ count))
708 (proced-log "%s\n" (cdr process)) 1317 (proced-log "%s\n" (cdr process))
709 (push (cdr process) failures)) 1318 (push (cdr process) failures))
@@ -714,12 +1323,12 @@ If SIGNAL is nil display marked processes and query interactively for SIGNAL."
714 ;; use external system call 1323 ;; use external system call
715 (let ((signal (concat "-" (if (numberp signal) 1324 (let ((signal (concat "-" (if (numberp signal)
716 (number-to-string signal) signal)))) 1325 (number-to-string signal) signal))))
717 (dolist (process process-list) 1326 (dolist (process process-alist)
718 (with-temp-buffer 1327 (with-temp-buffer
719 (condition-case err 1328 (condition-case err
720 (if (zerop (call-process 1329 (if (zerop (call-process
721 proced-signal-function nil t nil 1330 proced-signal-function nil t nil
722 signal (car process))) 1331 signal (number-to-string (car process))))
723 (setq count (1+ count)) 1332 (setq count (1+ count))
724 (proced-log (current-buffer)) 1333 (proced-log (current-buffer))
725 (proced-log "%s\n" (cdr process)) 1334 (proced-log "%s\n" (cdr process))
@@ -729,32 +1338,35 @@ If SIGNAL is nil display marked processes and query interactively for SIGNAL."
729 (proced-log "%s\n" (cdr process)) 1338 (proced-log "%s\n" (cdr process))
730 (push (cdr process) failures))))))) 1339 (push (cdr process) failures)))))))
731 (if 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?
732 (proced-log-summary 1344 (proced-log-summary
733 signal 1345 signal
734 (format "%d of %d signal%s failed" 1346 (format "%d of %d signal%s failed"
735 (length failures) (length process-list) 1347 (length failures) (length process-alist)
736 (if (= 1 (length process-list)) "" "s"))) 1348 (if (= 1 (length process-alist)) "" "s")))
737 (proced-success-message "Sent signal to" count))) 1349 (proced-success-message "Sent signal to" count)))
738 ;; final clean-up 1350 ;; final clean-up
739 (run-hooks 'proced-after-send-signal-hook)))) 1351 (run-hooks 'proced-after-send-signal-hook))))
740 1352
741;; just like `dired-why' 1353;; similar to `dired-why'
742(defun proced-why () 1354(defun proced-why ()
743 "Pop up a buffer with error log output from Proced. 1355 "Pop up a buffer with error log output from Proced.
744A group of errors from a single command ends with a formfeed. 1356A group of errors from a single command ends with a formfeed.
745Thus, use \\[backward-page] to find the beginning of a group of errors." 1357Thus, use \\[backward-page] to find the beginning of a group of errors."
746 (interactive) 1358 (interactive)
747 (if (get-buffer proced-log-buffer) 1359 (if (get-buffer proced-log-buffer)
748 (let ((owindow (selected-window)) 1360 (save-selected-window
749 (window (display-buffer (get-buffer proced-log-buffer)))) 1361 ;; move `proced-log-buffer' to the front of the buffer list
750 (unwind-protect 1362 (select-window (display-buffer (get-buffer proced-log-buffer)))
751 (progn 1363 (setq truncate-lines t)
752 (select-window window) 1364 (set-buffer-modified-p nil)
753 (goto-char (point-max)) 1365 (setq buffer-read-only t)
754 (forward-line -1) 1366 (goto-char (point-max))
755 (backward-page 1) 1367 (forward-line -1)
756 (recenter 0)) 1368 (backward-page 1)
757 (select-window owindow))))) 1369 (recenter 0))))
758 1370
759;; similar to `dired-log' 1371;; similar to `dired-log'
760(defun proced-log (log &rest args) 1372(defun proced-log (log &rest args)
@@ -767,7 +1379,7 @@ and \f (formfeed) at the end."
767 (let ((obuf (current-buffer))) 1379 (let ((obuf (current-buffer)))
768 (with-current-buffer (get-buffer-create proced-log-buffer) 1380 (with-current-buffer (get-buffer-create proced-log-buffer)
769 (goto-char (point-max)) 1381 (goto-char (point-max))
770 (let ((inhibit-read-only t)) 1382 (let (buffer-read-only)
771 (cond ((stringp log) 1383 (cond ((stringp log)
772 (insert (if args 1384 (insert (if args
773 (apply 'format log args) 1385 (apply 'format log args)
@@ -811,61 +1423,6 @@ buffer. You can use it to recover marks."
811 (message "Change in Proced buffer undone. 1423 (message "Change in Proced buffer undone.
812Killed processes cannot be recovered by Emacs.")) 1424Killed processes cannot be recovered by Emacs."))
813 1425
814;;; Sorting
815(defun proced-sort (scheme)
816 "Sort Proced buffer using SCHEME.
817When called interactively, an empty string means nil, i.e., no sorting."
818 (interactive
819 (list (let* ((completion-ignore-case t)
820 ;; restrict completion list to applicable sorting schemes
821 (completion-list
822 (apply 'append
823 (mapcar (lambda (x)
824 (if (string-match proced-sorting-schemes-re
825 (car x))
826 (list (car x))))
827 proced-sorting-schemes-alist)))
828 (scheme (completing-read "Sorting type: "
829 completion-list nil t)))
830 (if (string= "" scheme) nil scheme))))
831 (if (proced-sorting-scheme-p scheme)
832 (progn
833 (setq proced-sorting-scheme scheme)
834 (proced-update))
835 (error "Proced sorting scheme %s not applicable" scheme)))
836
837(defun proced-sorting-scheme-p (scheme)
838 "Return non-nil if SCHEME is an applicable sorting scheme.
839SCHEME must be a string or nil."
840 (or (not scheme)
841 (and (string-match proced-sorting-schemes-re scheme)
842 (assoc scheme proced-sorting-schemes-alist))))
843
844(defun proced-sort-pcpu ()
845 "Sort Proced buffer by percentage CPU time (%CPU)."
846 (interactive)
847 (proced-sort "%CPU"))
848
849(defun proced-sort-pmem ()
850 "Sort Proced buffer by percentage memory usage (%MEM)."
851 (interactive)
852 (proced-sort "%MEM"))
853
854(defun proced-sort-pid ()
855 "Sort Proced buffer by PID."
856 (interactive)
857 (proced-sort "PID"))
858
859(defun proced-sort-start ()
860 "Sort Proced buffer by time the command started (START)."
861 (interactive)
862 (proced-sort "START"))
863
864(defun proced-sort-time ()
865 "Sort Proced buffer by cumulative CPU time (TIME)."
866 (interactive)
867 (proced-sort "TIME"))
868
869(provide 'proced) 1426(provide 'proced)
870 1427
871;; arch-tag: a6e312ad-9032-45aa-972d-31a8cfc545af 1428;; arch-tag: a6e312ad-9032-45aa-972d-31a8cfc545af