##// END OF EJS Templates
mq.el: define keymaps as convensions
NIIMI Satoshi -
r5468:24eb1bf8 default
parent child Browse files
Show More
@@ -1,414 +1,418
1 1 ;;; mq.el --- Emacs support for Mercurial Queues
2 2
3 3 ;; Copyright (C) 2006 Bryan O'Sullivan
4 4
5 5 ;; Author: Bryan O'Sullivan <bos@serpentine.com>
6 6
7 7 ;; mq.el is free software; you can redistribute it and/or modify it
8 8 ;; under the terms of version 2 of the GNU General Public License as
9 9 ;; published by the Free Software Foundation.
10 10
11 11 ;; mq.el is distributed in the hope that it will be useful, but
12 12 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 14 ;; General Public License for more details.
15 15
16 16 ;; You should have received a copy of the GNU General Public License
17 17 ;; along with mq.el, GNU Emacs, or XEmacs; see the file COPYING (`C-h
18 18 ;; C-l'). If not, write to the Free Software Foundation, Inc., 59
19 19 ;; Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 20
21 21 (eval-when-compile (require 'cl))
22 22 (require 'mercurial)
23 23
24 24
25 25 (defcustom mq-mode-hook nil
26 26 "Hook run when a buffer enters mq-mode."
27 27 :type 'sexp
28 28 :group 'mercurial)
29 29
30 30 (defcustom mq-global-prefix "\C-cq"
31 31 "The global prefix for Mercurial Queues keymap bindings."
32 32 :type 'sexp
33 33 :group 'mercurial)
34 34
35 35 (defcustom mq-edit-mode-hook nil
36 36 "Hook run after a buffer is populated to edit a patch description."
37 37 :type 'sexp
38 38 :group 'mercurial)
39 39
40 40 (defcustom mq-edit-finish-hook nil
41 41 "Hook run before a patch description is finished up with."
42 42 :type 'sexp
43 43 :group 'mercurial)
44 44
45 45 (defcustom mq-signoff-address nil
46 46 "Address with which to sign off on a patch."
47 47 :type 'string
48 48 :group 'mercurial)
49 49
50 50
51 51 ;;; Internal variables.
52 52
53 53 (defvar mq-mode nil
54 54 "Is this file managed by MQ?")
55 55 (make-variable-buffer-local 'mq-mode)
56 56 (put 'mq-mode 'permanent-local t)
57 57
58 58 (defvar mq-patch-history nil)
59 59
60 60 (defvar mq-top-patch '(nil))
61 61
62 62 (defvar mq-prev-buffer nil)
63 63 (make-variable-buffer-local 'mq-prev-buffer)
64 64 (put 'mq-prev-buffer 'permanent-local t)
65 65
66 66 (defvar mq-top nil)
67 67 (make-variable-buffer-local 'mq-top)
68 68 (put 'mq-top 'permanent-local t)
69 69
70 70 ;;; Global keymap.
71 71
72 (defvar mq-global-map (make-sparse-keymap))
73 (fset 'mq-global-map mq-global-map)
74 (global-set-key mq-global-prefix 'mq-global-map)
75 (define-key mq-global-map "." 'mq-push)
76 (define-key mq-global-map ">" 'mq-push-all)
77 (define-key mq-global-map "," 'mq-pop)
78 (define-key mq-global-map "<" 'mq-pop-all)
79 (define-key mq-global-map "=" 'mq-diff)
80 (define-key mq-global-map "r" 'mq-refresh)
81 (define-key mq-global-map "e" 'mq-refresh-edit)
82 (define-key mq-global-map "i" 'mq-new)
83 (define-key mq-global-map "n" 'mq-next)
84 (define-key mq-global-map "o" 'mq-signoff)
85 (define-key mq-global-map "p" 'mq-previous)
86 (define-key mq-global-map "s" 'mq-edit-series)
87 (define-key mq-global-map "t" 'mq-top)
72 (defvar mq-global-map
73 (let ((map (make-sparse-keymap)))
74 (define-key map "." 'mq-push)
75 (define-key map ">" 'mq-push-all)
76 (define-key map "," 'mq-pop)
77 (define-key map "<" 'mq-pop-all)
78 (define-key map "=" 'mq-diff)
79 (define-key map "r" 'mq-refresh)
80 (define-key map "e" 'mq-refresh-edit)
81 (define-key map "i" 'mq-new)
82 (define-key map "n" 'mq-next)
83 (define-key map "o" 'mq-signoff)
84 (define-key map "p" 'mq-previous)
85 (define-key map "s" 'mq-edit-series)
86 (define-key map "t" 'mq-top)
87 map))
88
89 (global-set-key mq-global-prefix mq-global-map)
88 90
89 91 (add-minor-mode 'mq-mode 'mq-mode)
90 92
91 93
92 94 ;;; Refresh edit mode keymap.
93 95
94 (defvar mq-edit-mode-map (make-sparse-keymap))
95 (define-key mq-edit-mode-map "\C-c\C-c" 'mq-edit-finish)
96 (define-key mq-edit-mode-map "\C-c\C-k" 'mq-edit-kill)
97 (define-key mq-edit-mode-map "\C-c\C-s" 'mq-signoff)
96 (defvar mq-edit-mode-map
97 (let ((map (make-sparse-keymap)))
98 (define-key map "\C-c\C-c" 'mq-edit-finish)
99 (define-key map "\C-c\C-k" 'mq-edit-kill)
100 (define-key map "\C-c\C-s" 'mq-signoff)
101 map))
98 102
99 103
100 104 ;;; Helper functions.
101 105
102 106 (defun mq-read-patch-name (&optional source prompt force)
103 107 "Read a patch name to use with a command.
104 108 May return nil, meaning \"use the default\"."
105 109 (let ((patches (split-string
106 110 (hg-chomp (hg-run0 (or source "qseries"))) "\n")))
107 111 (when force
108 112 (completing-read (format "Patch%s: " (or prompt ""))
109 113 (mapcar (lambda (x) (cons x x)) patches)
110 114 nil
111 115 nil
112 116 nil
113 117 'mq-patch-history))))
114 118
115 119 (defun mq-refresh-buffers (root)
116 120 (save-excursion
117 121 (dolist (buf (hg-buffers-visiting-repo root))
118 122 (when (not (verify-visited-file-modtime buf))
119 123 (set-buffer buf)
120 124 (let ((ctx (hg-buffer-context)))
121 125 (message "Refreshing %s..." (buffer-name))
122 126 (revert-buffer t t t)
123 127 (hg-restore-context ctx)
124 128 (message "Refreshing %s...done" (buffer-name))))))
125 129 (hg-update-mode-lines root)
126 130 (mq-update-mode-lines root))
127 131
128 132 (defun mq-last-line ()
129 133 (goto-char (point-max))
130 134 (beginning-of-line)
131 135 (when (looking-at "^$")
132 136 (forward-line -1))
133 137 (let ((bol (point)))
134 138 (end-of-line)
135 139 (let ((line (buffer-substring bol (point))))
136 140 (when (> (length line) 0)
137 141 line))))
138 142
139 143 (defun mq-push (&optional patch)
140 144 "Push patches until PATCH is reached.
141 145 If PATCH is nil, push at most one patch."
142 146 (interactive (list (mq-read-patch-name "qunapplied" " to push"
143 147 current-prefix-arg)))
144 148 (let ((root (hg-root))
145 149 (prev-buf (current-buffer))
146 150 last-line ok)
147 151 (unless root
148 152 (error "Cannot push outside a repository!"))
149 153 (hg-sync-buffers root)
150 154 (let ((buf-name (format "MQ: Push %s" (or patch "next patch"))))
151 155 (kill-buffer (get-buffer-create buf-name))
152 156 (split-window-vertically)
153 157 (other-window 1)
154 158 (switch-to-buffer (get-buffer-create buf-name))
155 159 (cd root)
156 160 (message "Pushing...")
157 161 (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpush"
158 162 (if patch (list patch))))
159 163 last-line (mq-last-line))
160 164 (let ((lines (count-lines (point-min) (point-max))))
161 165 (if (or (<= lines 1)
162 166 (and (equal lines 2) (string-match "Now at:" last-line)))
163 167 (progn
164 168 (kill-buffer (current-buffer))
165 169 (delete-window))
166 170 (hg-view-mode prev-buf))))
167 171 (mq-refresh-buffers root)
168 172 (sit-for 0)
169 173 (when last-line
170 174 (if ok
171 175 (message "Pushing... %s" last-line)
172 176 (error "Pushing... %s" last-line)))))
173 177
174 178 (defun mq-push-all ()
175 179 "Push patches until all are applied."
176 180 (interactive)
177 181 (mq-push "-a"))
178 182
179 183 (defun mq-pop (&optional patch)
180 184 "Pop patches until PATCH is reached.
181 185 If PATCH is nil, pop at most one patch."
182 186 (interactive (list (mq-read-patch-name "qapplied" " to pop to"
183 187 current-prefix-arg)))
184 188 (let ((root (hg-root))
185 189 last-line ok)
186 190 (unless root
187 191 (error "Cannot pop outside a repository!"))
188 192 (hg-sync-buffers root)
189 193 (set-buffer (generate-new-buffer "qpop"))
190 194 (cd root)
191 195 (message "Popping...")
192 196 (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpop"
193 197 (if patch (list patch))))
194 198 last-line (mq-last-line))
195 199 (kill-buffer (current-buffer))
196 200 (mq-refresh-buffers root)
197 201 (sit-for 0)
198 202 (when last-line
199 203 (if ok
200 204 (message "Popping... %s" last-line)
201 205 (error "Popping... %s" last-line)))))
202 206
203 207 (defun mq-pop-all ()
204 208 "Push patches until none are applied."
205 209 (interactive)
206 210 (mq-pop "-a"))
207 211
208 212 (defun mq-refresh-internal (root &rest args)
209 213 (hg-sync-buffers root)
210 214 (let ((patch (mq-patch-info "qtop")))
211 215 (message "Refreshing %s..." patch)
212 216 (let ((ret (apply 'hg-run "qrefresh" args)))
213 217 (if (equal (car ret) 0)
214 218 (message "Refreshing %s... done." patch)
215 219 (error "Refreshing %s... %s" patch (hg-chomp (cdr ret)))))))
216 220
217 221 (defun mq-refresh (&optional git)
218 222 "Refresh the topmost applied patch.
219 223 With a prefix argument, generate a git-compatible patch."
220 224 (interactive "P")
221 225 (let ((root (hg-root)))
222 226 (unless root
223 227 (error "Cannot refresh outside of a repository!"))
224 228 (apply 'mq-refresh-internal root (if git '("--git")))))
225 229
226 230 (defun mq-patch-info (cmd &optional msg)
227 231 (let* ((ret (hg-run cmd))
228 232 (info (hg-chomp (cdr ret))))
229 233 (if (equal (car ret) 0)
230 234 (if msg
231 235 (message "%s patch: %s" msg info)
232 236 info)
233 237 (error "%s" info))))
234 238
235 239 (defun mq-top ()
236 240 "Print the name of the topmost applied patch."
237 241 (interactive)
238 242 (mq-patch-info "qtop" "Top"))
239 243
240 244 (defun mq-next ()
241 245 "Print the name of the next patch to be pushed."
242 246 (interactive)
243 247 (mq-patch-info "qnext" "Next"))
244 248
245 249 (defun mq-previous ()
246 250 "Print the name of the first patch below the topmost applied patch.
247 251 This would become the active patch if popped to."
248 252 (interactive)
249 253 (mq-patch-info "qprev" "Previous"))
250 254
251 255 (defun mq-edit-finish ()
252 256 "Finish editing the description of this patch, and refresh the patch."
253 257 (interactive)
254 258 (unless (equal (mq-patch-info "qtop") mq-top)
255 259 (error "Topmost patch has changed!"))
256 260 (hg-sync-buffers hg-root)
257 261 (run-hooks 'mq-edit-finish-hook)
258 262 (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max)))
259 263 (let ((buf mq-prev-buffer))
260 264 (kill-buffer nil)
261 265 (switch-to-buffer buf)))
262 266
263 267 (defun mq-edit-kill ()
264 268 "Kill the edit currently being prepared."
265 269 (interactive)
266 270 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this edit? "))
267 271 (let ((buf mq-prev-buffer))
268 272 (kill-buffer nil)
269 273 (switch-to-buffer buf))))
270 274
271 275 (defun mq-get-top (root)
272 276 (let ((entry (assoc root mq-top-patch)))
273 277 (if entry
274 278 (cdr entry))))
275 279
276 280 (defun mq-set-top (root patch)
277 281 (let ((entry (assoc root mq-top-patch)))
278 282 (if entry
279 283 (if patch
280 284 (setcdr entry patch)
281 285 (setq mq-top-patch (delq entry mq-top-patch)))
282 286 (setq mq-top-patch (cons (cons root patch) mq-top-patch)))))
283 287
284 288 (defun mq-update-mode-lines (root)
285 289 (let ((cwd default-directory))
286 290 (cd root)
287 291 (condition-case nil
288 292 (mq-set-top root (mq-patch-info "qtop"))
289 293 (error (mq-set-top root nil)))
290 294 (cd cwd))
291 295 (let ((patch (mq-get-top root)))
292 296 (save-excursion
293 297 (dolist (buf (hg-buffers-visiting-repo root))
294 298 (set-buffer buf)
295 299 (if mq-mode
296 300 (setq mq-mode (or (and patch (concat " MQ:" patch)) " MQ")))))))
297 301
298 302 (defun mq-mode (&optional arg)
299 303 "Minor mode for Mercurial repositories with an MQ patch queue"
300 304 (interactive "i")
301 305 (cond ((hg-root)
302 306 (setq mq-mode (if (null arg) (not mq-mode)
303 307 arg))
304 308 (mq-update-mode-lines (hg-root))))
305 309 (run-hooks 'mq-mode-hook))
306 310
307 311 (defun mq-edit-mode ()
308 312 "Mode for editing the description of a patch.
309 313
310 314 Key bindings
311 315 ------------
312 316 \\[mq-edit-finish] use this description
313 317 \\[mq-edit-kill] abandon this description"
314 318 (interactive)
315 319 (use-local-map mq-edit-mode-map)
316 320 (set-syntax-table text-mode-syntax-table)
317 321 (setq local-abbrev-table text-mode-abbrev-table
318 322 major-mode 'mq-edit-mode
319 323 mode-name "MQ-Edit")
320 324 (set-buffer-modified-p nil)
321 325 (setq buffer-undo-list nil)
322 326 (run-hooks 'text-mode-hook 'mq-edit-mode-hook))
323 327
324 328 (defun mq-refresh-edit ()
325 329 "Refresh the topmost applied patch, editing the patch description."
326 330 (interactive)
327 331 (while mq-prev-buffer
328 332 (set-buffer mq-prev-buffer))
329 333 (let ((root (hg-root))
330 334 (prev-buffer (current-buffer))
331 335 (patch (mq-patch-info "qtop")))
332 336 (hg-sync-buffers root)
333 337 (let ((buf-name (format "*MQ: Edit description of %s*" patch)))
334 338 (switch-to-buffer (get-buffer-create buf-name))
335 339 (when (= (point-min) (point-max))
336 340 (set (make-local-variable 'hg-root) root)
337 341 (set (make-local-variable 'mq-top) patch)
338 342 (setq mq-prev-buffer prev-buffer)
339 343 (insert (hg-run0 "qheader"))
340 344 (goto-char (point-min)))
341 345 (mq-edit-mode)
342 346 (cd root)))
343 347 (message "Type `C-c C-c' to finish editing and refresh the patch."))
344 348
345 349 (defun mq-new (name)
346 350 "Create a new empty patch named NAME.
347 351 The patch is applied on top of the current topmost patch.
348 352 With a prefix argument, forcibly create the patch even if the working
349 353 directory is modified."
350 354 (interactive (list (mq-read-patch-name "qseries" " to create" t)))
351 355 (message "Creating patch...")
352 356 (let ((ret (if current-prefix-arg
353 357 (hg-run "qnew" "-f" name)
354 358 (hg-run "qnew" name))))
355 359 (if (equal (car ret) 0)
356 360 (progn
357 361 (hg-update-mode-lines (buffer-file-name))
358 362 (message "Creating patch... done."))
359 363 (error "Creating patch... %s" (hg-chomp (cdr ret))))))
360 364
361 365 (defun mq-edit-series ()
362 366 "Edit the MQ series file directly."
363 367 (interactive)
364 368 (let ((root (hg-root)))
365 369 (unless root
366 370 (error "Not in an MQ repository!"))
367 371 (find-file (concat root ".hg/patches/series"))))
368 372
369 373 (defun mq-diff (&optional git)
370 374 "Display a diff of the topmost applied patch.
371 375 With a prefix argument, display a git-compatible diff."
372 376 (interactive "P")
373 377 (hg-view-output ((format "MQ: Diff of %s" (mq-patch-info "qtop")))
374 378 (if git
375 379 (call-process (hg-binary) nil t nil "qdiff" "--git")
376 380 (call-process (hg-binary) nil t nil "qdiff"))
377 381 (diff-mode)
378 382 (font-lock-fontify-buffer)))
379 383
380 384 (defun mq-signoff ()
381 385 "Sign off on the current patch, in the style used by the Linux kernel.
382 386 If the variable mq-signoff-address is non-nil, it will be used, otherwise
383 387 the value of the ui.username item from your hgrc will be used."
384 388 (interactive)
385 389 (let ((was-editing (eq major-mode 'mq-edit-mode))
386 390 signed)
387 391 (unless was-editing
388 392 (mq-refresh-edit))
389 393 (save-excursion
390 394 (let* ((user (or mq-signoff-address
391 395 (hg-run0 "debugconfig" "ui.username")))
392 396 (signoff (concat "Signed-off-by: " user)))
393 397 (if (search-forward signoff nil t)
394 398 (message "You have already signed off on this patch.")
395 399 (goto-char (point-max))
396 400 (let ((case-fold-search t))
397 401 (if (re-search-backward "^Signed-off-by: " nil t)
398 402 (forward-line 1)
399 403 (insert "\n")))
400 404 (insert signoff)
401 405 (message "%s" signoff)
402 406 (setq signed t))))
403 407 (unless was-editing
404 408 (if signed
405 409 (mq-edit-finish)
406 410 (mq-edit-kill)))))
407 411
408 412
409 413 (provide 'mq)
410 414
411 415
412 416 ;;; Local Variables:
413 417 ;;; prompt-to-byte-compile: nil
414 418 ;;; end:
General Comments 0
You need to be logged in to leave comments. Login now