##// END OF EJS Templates
mq.el: don't fill half the screen with a single line of output.
Bryan O'Sullivan -
r4428:fa51e661 default
parent child Browse files
Show More
@@ -1,374 +1,375 b''
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 (require 'mercurial)
22 22
23 23
24 24 (defcustom mq-mode-hook nil
25 25 "Hook run when a buffer enters mq-mode."
26 26 :type 'sexp
27 27 :group 'mercurial)
28 28
29 29 (defcustom mq-global-prefix "\C-cq"
30 30 "The global prefix for Mercurial Queues keymap bindings."
31 31 :type 'sexp
32 32 :group 'mercurial)
33 33
34 34 (defcustom mq-edit-mode-hook nil
35 35 "Hook run after a buffer is populated to edit a patch description."
36 36 :type 'sexp
37 37 :group 'mercurial)
38 38
39 39 (defcustom mq-edit-finish-hook nil
40 40 "Hook run before a patch description is finished up with."
41 41 :type 'sexp
42 42 :group 'mercurial)
43 43
44 44
45 45 ;;; Internal variables.
46 46
47 47 (defvar mq-mode nil
48 48 "Is this file managed by MQ?")
49 49 (make-variable-buffer-local 'mq-mode)
50 50 (put 'mq-mode 'permanent-local t)
51 51
52 52 (defvar mq-patch-history nil)
53 53
54 54 (defvar mq-top-patch '(nil))
55 55
56 56 (defvar mq-prev-buffer nil)
57 57 (make-variable-buffer-local 'mq-prev-buffer)
58 58 (put 'mq-prev-buffer 'permanent-local t)
59 59
60 60
61 61 ;;; Global keymap.
62 62
63 63 (defvar mq-global-map (make-sparse-keymap))
64 64 (fset 'mq-global-map mq-global-map)
65 65 (global-set-key mq-global-prefix 'mq-global-map)
66 66 (define-key mq-global-map "." 'mq-push)
67 67 (define-key mq-global-map ">" 'mq-push-all)
68 68 (define-key mq-global-map "," 'mq-pop)
69 69 (define-key mq-global-map "<" 'mq-pop-all)
70 70 (define-key mq-global-map "=" 'mq-diff)
71 71 (define-key mq-global-map "r" 'mq-refresh)
72 72 (define-key mq-global-map "e" 'mq-refresh-edit)
73 73 (define-key mq-global-map "i" 'mq-new)
74 74 (define-key mq-global-map "n" 'mq-next)
75 75 (define-key mq-global-map "p" 'mq-previous)
76 76 (define-key mq-global-map "s" 'mq-edit-series)
77 77 (define-key mq-global-map "t" 'mq-top)
78 78
79 79 (add-minor-mode 'mq-mode 'mq-mode)
80 80
81 81
82 82 ;;; Refresh edit mode keymap.
83 83
84 84 (defvar mq-edit-mode-map (make-sparse-keymap))
85 85 (define-key mq-edit-mode-map "\C-c\C-c" 'mq-edit-finish)
86 86 (define-key mq-edit-mode-map "\C-c\C-k" 'mq-edit-kill)
87 87
88 88
89 89 ;;; Helper functions.
90 90
91 91 (defun mq-read-patch-name (&optional source prompt force)
92 92 "Read a patch name to use with a command.
93 93 May return nil, meaning \"use the default\"."
94 94 (let ((patches (split-string
95 95 (hg-chomp (hg-run0 (or source "qseries"))) "\n")))
96 96 (when force
97 97 (completing-read (format "Patch%s: " (or prompt ""))
98 98 (map 'list 'cons patches patches)
99 99 nil
100 100 nil
101 101 nil
102 102 'mq-patch-history))))
103 103
104 104 (defun mq-refresh-buffers (root)
105 105 (save-excursion
106 106 (dolist (buf (hg-buffers-visiting-repo root))
107 107 (when (not (verify-visited-file-modtime buf))
108 108 (set-buffer buf)
109 109 (let ((ctx (hg-buffer-context)))
110 110 (message "Refreshing %s..." (buffer-name))
111 111 (revert-buffer t t t)
112 112 (hg-restore-context ctx)
113 113 (message "Refreshing %s...done" (buffer-name))))))
114 114 (hg-update-mode-lines root)
115 115 (mq-update-mode-lines root))
116 116
117 117 (defun mq-last-line ()
118 118 (goto-char (point-max))
119 119 (beginning-of-line)
120 120 (when (looking-at "^$")
121 121 (forward-line -1))
122 122 (let ((bol (point)))
123 123 (end-of-line)
124 124 (let ((line (buffer-substring bol (point))))
125 125 (when (> (length line) 0)
126 126 line))))
127 127
128 128 (defun mq-push (&optional patch)
129 129 "Push patches until PATCH is reached.
130 130 If PATCH is nil, push at most one patch."
131 131 (interactive (list (mq-read-patch-name "qunapplied" " to push"
132 132 current-prefix-arg)))
133 133 (let ((root (hg-root))
134 134 (prev-buf (current-buffer))
135 135 last-line ok)
136 136 (unless root
137 137 (error "Cannot push outside a repository!"))
138 138 (hg-sync-buffers root)
139 139 (let ((buf-name (format "MQ: Push %s" (or patch "next patch"))))
140 140 (kill-buffer (get-buffer-create buf-name))
141 141 (split-window-vertically)
142 142 (other-window 1)
143 143 (switch-to-buffer (get-buffer-create buf-name))
144 144 (cd root)
145 145 (message "Pushing...")
146 146 (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpush"
147 147 (if patch (list patch))))
148 148 last-line (mq-last-line))
149 149 (let ((lines (count-lines (point-min) (point-max))))
150 (if (and (equal lines 2) (string-match "Now at:" last-line))
150 (if (or (<= lines 1)
151 (and (equal lines 2) (string-match "Now at:" last-line)))
151 152 (progn
152 153 (kill-buffer (current-buffer))
153 154 (delete-window))
154 155 (hg-view-mode prev-buf))))
155 156 (mq-refresh-buffers root)
156 157 (sit-for 0)
157 158 (when last-line
158 159 (if ok
159 160 (message "Pushing... %s" last-line)
160 161 (error "Pushing... %s" last-line)))))
161 162
162 163 (defun mq-push-all ()
163 164 "Push patches until all are applied."
164 165 (interactive)
165 166 (mq-push "-a"))
166 167
167 168 (defun mq-pop (&optional patch)
168 169 "Pop patches until PATCH is reached.
169 170 If PATCH is nil, pop at most one patch."
170 171 (interactive (list (mq-read-patch-name "qapplied" " to pop to"
171 172 current-prefix-arg)))
172 173 (let ((root (hg-root))
173 174 last-line ok)
174 175 (unless root
175 176 (error "Cannot pop outside a repository!"))
176 177 (hg-sync-buffers root)
177 178 (set-buffer (generate-new-buffer "qpop"))
178 179 (cd root)
179 180 (message "Popping...")
180 181 (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpop"
181 182 (if patch (list patch))))
182 183 last-line (mq-last-line))
183 184 (kill-buffer (current-buffer))
184 185 (mq-refresh-buffers root)
185 186 (sit-for 0)
186 187 (when last-line
187 188 (if ok
188 189 (message "Popping... %s" last-line)
189 190 (error "Popping... %s" last-line)))))
190 191
191 192 (defun mq-pop-all ()
192 193 "Push patches until none are applied."
193 194 (interactive)
194 195 (mq-pop "-a"))
195 196
196 197 (defun mq-refresh-internal (root &rest args)
197 198 (hg-sync-buffers root)
198 199 (let ((patch (mq-patch-info "qtop")))
199 200 (message "Refreshing %s..." patch)
200 201 (let ((ret (apply 'hg-run "qrefresh" args)))
201 202 (if (equal (car ret) 0)
202 203 (message "Refreshing %s... done." patch)
203 204 (error "Refreshing %s... %s" patch (hg-chomp (cdr ret)))))))
204 205
205 206 (defun mq-refresh (&optional git)
206 207 "Refresh the topmost applied patch.
207 208 With a prefix argument, generate a git-compatible patch."
208 209 (interactive "P")
209 210 (let ((root (hg-root)))
210 211 (unless root
211 212 (error "Cannot refresh outside of a repository!"))
212 213 (apply 'mq-refresh-internal root (if git '("--git")))))
213 214
214 215 (defun mq-patch-info (cmd &optional msg)
215 216 (let* ((ret (hg-run cmd))
216 217 (info (hg-chomp (cdr ret))))
217 218 (if (equal (car ret) 0)
218 219 (if msg
219 220 (message "%s patch: %s" msg info)
220 221 info)
221 222 (error "%s" info))))
222 223
223 224 (defun mq-top ()
224 225 "Print the name of the topmost applied patch."
225 226 (interactive)
226 227 (mq-patch-info "qtop" "Top"))
227 228
228 229 (defun mq-next ()
229 230 "Print the name of the next patch to be pushed."
230 231 (interactive)
231 232 (mq-patch-info "qnext" "Next"))
232 233
233 234 (defun mq-previous ()
234 235 "Print the name of the first patch below the topmost applied patch.
235 236 This would become the active patch if popped to."
236 237 (interactive)
237 238 (mq-patch-info "qprev" "Previous"))
238 239
239 240 (defun mq-edit-finish ()
240 241 "Finish editing the description of this patch, and refresh the patch."
241 242 (interactive)
242 243 (unless (equal (mq-patch-info "qtop") mq-top)
243 244 (error "Topmost patch has changed!"))
244 245 (hg-sync-buffers hg-root)
245 246 (run-hooks 'mq-edit-finish-hook)
246 247 (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max)))
247 248 (let ((buf mq-prev-buffer))
248 249 (kill-buffer nil)
249 250 (switch-to-buffer buf)))
250 251
251 252 (defun mq-edit-kill ()
252 253 "Kill the edit currently being prepared."
253 254 (interactive)
254 255 (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this edit? "))
255 256 (let ((buf mq-prev-buffer))
256 257 (kill-buffer nil)
257 258 (switch-to-buffer buf))))
258 259
259 260 (defun mq-get-top (root)
260 261 (let ((entry (assoc root mq-top-patch)))
261 262 (if entry
262 263 (cdr entry))))
263 264
264 265 (defun mq-set-top (root patch)
265 266 (let ((entry (assoc root mq-top-patch)))
266 267 (if entry
267 268 (if patch
268 269 (setcdr entry patch)
269 270 (setq mq-top-patch (delq entry mq-top-patch)))
270 271 (setq mq-top-patch (cons (cons root patch) mq-top-patch)))))
271 272
272 273 (defun mq-update-mode-lines (root)
273 274 (let ((cwd default-directory))
274 275 (cd root)
275 276 (condition-case nil
276 277 (mq-set-top root (mq-patch-info "qtop"))
277 278 (error (mq-set-top root nil)))
278 279 (cd cwd))
279 280 (let ((patch (mq-get-top root)))
280 281 (save-excursion
281 282 (dolist (buf (hg-buffers-visiting-repo root))
282 283 (set-buffer buf)
283 284 (if mq-mode
284 285 (setq mq-mode (or (and patch (concat " MQ:" patch)) " MQ")))))))
285 286
286 287 (defun mq-mode (&optional arg)
287 288 "Minor mode for Mercurial repositories with an MQ patch queue"
288 289 (interactive "i")
289 290 (cond ((hg-root)
290 291 (setq mq-mode (if (null arg) (not mq-mode)
291 292 arg))
292 293 (mq-update-mode-lines (hg-root))))
293 294 (run-hooks 'mq-mode-hook))
294 295
295 296 (defun mq-edit-mode ()
296 297 "Mode for editing the description of a patch.
297 298
298 299 Key bindings
299 300 ------------
300 301 \\[mq-edit-finish] use this description
301 302 \\[mq-edit-kill] abandon this description"
302 303 (interactive)
303 304 (use-local-map mq-edit-mode-map)
304 305 (set-syntax-table text-mode-syntax-table)
305 306 (setq local-abbrev-table text-mode-abbrev-table
306 307 major-mode 'mq-edit-mode
307 308 mode-name "MQ-Edit")
308 309 (set-buffer-modified-p nil)
309 310 (setq buffer-undo-list nil)
310 311 (run-hooks 'text-mode-hook 'mq-edit-mode-hook))
311 312
312 313 (defun mq-refresh-edit ()
313 314 "Refresh the topmost applied patch, editing the patch description."
314 315 (interactive)
315 316 (while mq-prev-buffer
316 317 (set-buffer mq-prev-buffer))
317 318 (let ((root (hg-root))
318 319 (prev-buffer (current-buffer))
319 320 (patch (mq-patch-info "qtop")))
320 321 (hg-sync-buffers root)
321 322 (let ((buf-name (format "*MQ: Edit description of %s*" patch)))
322 323 (switch-to-buffer (get-buffer-create buf-name))
323 324 (when (= (point-min) (point-max))
324 325 (set (make-local-variable 'hg-root) root)
325 326 (set (make-local-variable 'mq-top) patch)
326 327 (setq mq-prev-buffer prev-buffer)
327 328 (insert (hg-run0 "qheader"))
328 329 (goto-char (point-min)))
329 330 (mq-edit-mode)
330 331 (cd root)))
331 332 (message "Type `C-c C-c' to finish editing and refresh the patch."))
332 333
333 334 (defun mq-new (name)
334 335 "Create a new empty patch named NAME.
335 336 The patch is applied on top of the current topmost patch.
336 337 With a prefix argument, forcibly create the patch even if the working
337 338 directory is modified."
338 339 (interactive (list (mq-read-patch-name "qseries" " to create" t)))
339 340 (message "Creating patch...")
340 341 (let ((ret (if current-prefix-arg
341 342 (hg-run "qnew" "-f" name)
342 343 (hg-run "qnew" name))))
343 344 (if (equal (car ret) 0)
344 345 (progn
345 346 (hg-update-mode-lines (buffer-file-name))
346 347 (message "Creating patch... done."))
347 348 (error "Creating patch... %s" (hg-chomp (cdr ret))))))
348 349
349 350 (defun mq-edit-series ()
350 351 "Edit the MQ series file directly."
351 352 (interactive)
352 353 (let ((root (hg-root)))
353 354 (unless root
354 355 (error "Not in an MQ repository!"))
355 356 (find-file (concat root ".hg/patches/series"))))
356 357
357 358 (defun mq-diff (&optional git)
358 359 "Display a diff of the topmost applied patch.
359 360 With a prefix argument, display a git-compatible diff."
360 361 (interactive "P")
361 362 (hg-view-output ((format "MQ: Diff of %s" (mq-patch-info "qtop")))
362 363 (if git
363 364 (call-process (hg-binary) nil t nil "qdiff" "--git")
364 365 (call-process (hg-binary) nil t nil "qdiff"))
365 366 (diff-mode)
366 367 (font-lock-fontify-buffer)))
367 368
368 369
369 370 (provide 'mq)
370 371
371 372
372 373 ;;; Local Variables:
373 374 ;;; prompt-to-byte-compile: nil
374 375 ;;; end:
General Comments 0
You need to be logged in to leave comments. Login now