(mac_system_script_code): Use EMACS_INT for DEFVAR_INT variable.
[emacs.git] / lisp / smerge-mode.el
blob451b96a261bc90af3ac145c948e9d426633d4d1b
1 ;;; smerge-mode.el --- Minor mode to resolve diff3 conflicts
3 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003,
4 ;; 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
6 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
7 ;; Keywords: tools revision-control merge diff3 cvs conflict
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
26 ;;; Commentary:
28 ;; Provides a lightweight alternative to emerge/ediff.
29 ;; To use it, simply add to your .emacs the following lines:
31 ;; (autoload 'smerge-mode "smerge-mode" nil t)
33 ;; you can even have it turned on automatically with the following
34 ;; piece of code in your .emacs:
36 ;; (defun sm-try-smerge ()
37 ;; (save-excursion
38 ;; (goto-char (point-min))
39 ;; (when (re-search-forward "^<<<<<<< " nil t)
40 ;; (smerge-mode 1))))
41 ;; (add-hook 'find-file-hook 'sm-try-smerge t)
43 ;;; Todo:
45 ;; - if requested, ask the user whether he wants to call ediff right away
47 ;;; Code:
49 (eval-when-compile (require 'cl))
52 ;;; The real definition comes later.
53 (defvar smerge-mode)
55 (defgroup smerge ()
56 "Minor mode to highlight and resolve diff3 conflicts."
57 :group 'tools
58 :prefix "smerge-")
60 (defcustom smerge-diff-buffer-name "*vc-diff*"
61 "Buffer name to use for displaying diffs."
62 :group 'smerge
63 :type '(choice
64 (const "*vc-diff*")
65 (const "*cvs-diff*")
66 (const "*smerge-diff*")
67 string))
69 (defcustom smerge-diff-switches
70 (append '("-d" "-b")
71 (if (listp diff-switches) diff-switches (list diff-switches)))
72 "A list of strings specifying switches to be passed to diff.
73 Used in `smerge-diff-base-mine' and related functions."
74 :group 'smerge
75 :type '(repeat string))
77 (defcustom smerge-auto-leave t
78 "Non-nil means to leave `smerge-mode' when the last conflict is resolved."
79 :group 'smerge
80 :type 'boolean)
82 (defface smerge-mine
83 '((((min-colors 88) (background light))
84 (:foreground "blue1"))
85 (((background light))
86 (:foreground "blue"))
87 (((min-colors 88) (background dark))
88 (:foreground "cyan1"))
89 (((background dark))
90 (:foreground "cyan")))
91 "Face for your code."
92 :group 'smerge)
93 ;; backward-compatibility alias
94 (put 'smerge-mine-face 'face-alias 'smerge-mine)
95 (defvar smerge-mine-face 'smerge-mine)
97 (defface smerge-other
98 '((((background light))
99 (:foreground "darkgreen"))
100 (((background dark))
101 (:foreground "lightgreen")))
102 "Face for the other code."
103 :group 'smerge)
104 ;; backward-compatibility alias
105 (put 'smerge-other-face 'face-alias 'smerge-other)
106 (defvar smerge-other-face 'smerge-other)
108 (defface smerge-base
109 '((((min-colors 88) (background light))
110 (:foreground "red1"))
111 (((background light))
112 (:foreground "red"))
113 (((background dark))
114 (:foreground "orange")))
115 "Face for the base code."
116 :group 'smerge)
117 ;; backward-compatibility alias
118 (put 'smerge-base-face 'face-alias 'smerge-base)
119 (defvar smerge-base-face 'smerge-base)
121 (defface smerge-markers
122 '((((background light))
123 (:background "grey85"))
124 (((background dark))
125 (:background "grey30")))
126 "Face for the conflict markers."
127 :group 'smerge)
128 ;; backward-compatibility alias
129 (put 'smerge-markers-face 'face-alias 'smerge-markers)
130 (defvar smerge-markers-face 'smerge-markers)
132 (defface smerge-refined-change
133 '((t :background "yellow"))
134 "Face used for char-based changes shown by `smerge-refine'."
135 :group 'smerge)
137 (easy-mmode-defmap smerge-basic-map
138 `(("n" . smerge-next)
139 ("p" . smerge-prev)
140 ("r" . smerge-resolve)
141 ("a" . smerge-keep-all)
142 ("b" . smerge-keep-base)
143 ("o" . smerge-keep-other)
144 ("m" . smerge-keep-mine)
145 ("E" . smerge-ediff)
146 ("C" . smerge-combine-with-next)
147 ("R" . smerge-refine)
148 ("\C-m" . smerge-keep-current)
149 ("=" . ,(make-sparse-keymap "Diff"))
150 ("=<" "base-mine" . smerge-diff-base-mine)
151 ("=>" "base-other" . smerge-diff-base-other)
152 ("==" "mine-other" . smerge-diff-mine-other))
153 "The base keymap for `smerge-mode'.")
155 (defcustom smerge-command-prefix "\C-c^"
156 "Prefix for `smerge-mode' commands."
157 :group 'smerge
158 :type '(choice (const :tag "ESC" "\e")
159 (const :tag "C-c ^" "\C-c^" )
160 (const :tag "none" "")
161 string))
163 (easy-mmode-defmap smerge-mode-map
164 `((,smerge-command-prefix . ,smerge-basic-map))
165 "Keymap for `smerge-mode'.")
167 (defvar smerge-check-cache nil)
168 (make-variable-buffer-local 'smerge-check-cache)
169 (defun smerge-check (n)
170 (condition-case nil
171 (let ((state (cons (point) (buffer-modified-tick))))
172 (unless (equal (cdr smerge-check-cache) state)
173 (smerge-match-conflict)
174 (setq smerge-check-cache (cons (match-data) state)))
175 (nth (* 2 n) (car smerge-check-cache)))
176 (error nil)))
178 (easy-menu-define smerge-mode-menu smerge-mode-map
179 "Menu for `smerge-mode'."
180 '("SMerge"
181 ["Next" smerge-next :help "Go to next conflict"]
182 ["Previous" smerge-prev :help "Go to previous conflict"]
183 "--"
184 ["Keep All" smerge-keep-all :help "Keep all three versions"
185 :active (smerge-check 1)]
186 ["Keep Current" smerge-keep-current :help "Use current (at point) version"
187 :active (and (smerge-check 1) (> (smerge-get-current) 0))]
188 "--"
189 ["Revert to Base" smerge-keep-base :help "Revert to base version"
190 :active (smerge-check 2)]
191 ["Keep Other" smerge-keep-other :help "Keep `other' version"
192 :active (smerge-check 3)]
193 ["Keep Yours" smerge-keep-mine :help "Keep your version"
194 :active (smerge-check 1)]
195 "--"
196 ["Diff Base/Mine" smerge-diff-base-mine
197 :help "Diff `base' and `mine' for current conflict"
198 :active (smerge-check 2)]
199 ["Diff Base/Other" smerge-diff-base-other
200 :help "Diff `base' and `other' for current conflict"
201 :active (smerge-check 2)]
202 ["Diff Mine/Other" smerge-diff-mine-other
203 :help "Diff `mine' and `other' for current conflict"
204 :active (smerge-check 1)]
205 "--"
206 ["Invoke Ediff" smerge-ediff
207 :help "Use Ediff to resolve the conflicts"
208 :active (smerge-check 1)]
209 ["Auto Resolve" smerge-resolve
210 :help "Try auto-resolution heuristics"
211 :active (smerge-check 1)]
212 ["Combine" smerge-combine-with-next
213 :help "Combine current conflict with next"
214 :active (smerge-check 1)]
217 (easy-menu-define smerge-context-menu nil
218 "Context menu for mine area in `smerge-mode'."
219 '(nil
220 ["Keep Current" smerge-keep-current :help "Use current (at point) version"]
221 ["Kill Current" smerge-kill-current :help "Remove current (at point) version"]
222 ["Keep All" smerge-keep-all :help "Keep all three versions"]
223 "---"
224 ["More..." (popup-menu smerge-mode-menu) :help "Show full SMerge mode menu"]
227 (defconst smerge-font-lock-keywords
228 '((smerge-find-conflict
229 (1 smerge-mine-face prepend t)
230 (2 smerge-base-face prepend t)
231 (3 smerge-other-face prepend t)
232 ;; FIXME: `keep' doesn't work right with syntactic fontification.
233 (0 smerge-markers-face keep)
234 (4 nil t t)
235 (5 nil t t)))
236 "Font lock patterns for `smerge-mode'.")
238 (defconst smerge-begin-re "^<<<<<<< \\(.*\\)\n")
239 (defconst smerge-end-re "^>>>>>>> .*\n")
240 (defconst smerge-base-re "^||||||| .*\n")
241 (defconst smerge-other-re "^=======\n")
243 (defvar smerge-conflict-style nil
244 "Keep track of which style of conflict is in use.
245 Can be nil if the style is undecided, or else:
246 - `diff3-E'
247 - `diff3-A'")
249 ;; Compiler pacifiers
250 (defvar font-lock-mode)
251 (defvar font-lock-keywords)
253 ;;;;
254 ;;;; Actual code
255 ;;;;
257 ;; Define smerge-next and smerge-prev
258 (easy-mmode-define-navigation smerge smerge-begin-re "conflict")
260 (defconst smerge-match-names ["conflict" "mine" "base" "other"])
262 (defun smerge-ensure-match (n)
263 (unless (match-end n)
264 (error "No `%s'" (aref smerge-match-names n))))
266 (defun smerge-auto-leave ()
267 (when (and smerge-auto-leave
268 (save-excursion (goto-char (point-min))
269 (not (re-search-forward smerge-begin-re nil t))))
270 (when (and (listp buffer-undo-list) smerge-mode)
271 (push (list 'apply 'smerge-mode 1) buffer-undo-list))
272 (smerge-mode -1)))
275 (defun smerge-keep-all ()
276 "Concatenate all versions."
277 (interactive)
278 (smerge-match-conflict)
279 (let ((mb2 (or (match-beginning 2) (point-max)))
280 (me2 (or (match-end 2) (point-min))))
281 (delete-region (match-end 3) (match-end 0))
282 (delete-region (max me2 (match-end 1)) (match-beginning 3))
283 (if (and (match-end 2) (/= (match-end 1) (match-end 3)))
284 (delete-region (match-end 1) (match-beginning 2)))
285 (delete-region (match-beginning 0) (min (match-beginning 1) mb2))
286 (smerge-auto-leave)))
288 (defun smerge-keep-n (n)
289 (smerge-remove-props (match-beginning 0) (match-end 0))
290 ;; We used to use replace-match, but that did not preserve markers so well.
291 (delete-region (match-end n) (match-end 0))
292 (delete-region (match-beginning 0) (match-beginning n)))
294 (defun smerge-combine-with-next ()
295 "Combine the current conflict with the next one."
296 (interactive)
297 (smerge-match-conflict)
298 (let ((ends nil))
299 (dolist (i '(3 2 1 0))
300 (push (if (match-end i) (copy-marker (match-end i) t)) ends))
301 (setq ends (apply 'vector ends))
302 (goto-char (aref ends 0))
303 (if (not (re-search-forward smerge-begin-re nil t))
304 (error "No next conflict")
305 (smerge-match-conflict)
306 (let ((match-data (mapcar (lambda (m) (if m (copy-marker m)))
307 (match-data))))
308 ;; First copy the in-between text in each alternative.
309 (dolist (i '(1 2 3))
310 (when (aref ends i)
311 (goto-char (aref ends i))
312 (insert-buffer-substring (current-buffer)
313 (aref ends 0) (car match-data))))
314 (delete-region (aref ends 0) (car match-data))
315 ;; Then move the second conflict's alternatives into the first.
316 (dolist (i '(1 2 3))
317 (set-match-data match-data)
318 (when (and (aref ends i) (match-end i))
319 (goto-char (aref ends i))
320 (insert-buffer-substring (current-buffer)
321 (match-beginning i) (match-end i))))
322 (delete-region (car match-data) (cadr match-data))
323 ;; Free the markers.
324 (dolist (m match-data) (if m (move-marker m nil)))
325 (mapc (lambda (m) (if m (move-marker m nil))) ends)))))
327 (defvar smerge-resolve-function
328 (lambda () (error "Don't know how to resolve"))
329 "Mode-specific merge function.
330 The function is called with zero or one argument (non-nil if the resolution
331 function should only apply safe heuristics) and with the match data set
332 according to `smerge-match-conflict'.")
333 (add-to-list 'debug-ignored-errors "Don't know how to resolve")
335 (defvar smerge-text-properties
336 `(help-echo "merge conflict: mouse-3 shows a menu"
337 ;; mouse-face highlight
338 keymap (keymap (down-mouse-3 . smerge-popup-context-menu))))
340 (defun smerge-remove-props (beg end)
341 (remove-overlays beg end 'smerge 'refine)
342 (remove-overlays beg end 'smerge 'conflict)
343 ;; Now that we use overlays rather than text-properties, this function
344 ;; does not cause refontification any more. It can be seen very clearly
345 ;; in buffers where jit-lock-contextually is not t, in which case deleting
346 ;; the "<<<<<<< foobar" leading line leaves the rest of the conflict
347 ;; highlighted as if it were still a valid conflict. Note that in many
348 ;; important cases (such as the previous example) we're actually called
349 ;; during font-locking so inhibit-modification-hooks is non-nil, so we
350 ;; can't just modify the buffer and expect font-lock to be triggered as in:
351 ;; (put-text-property beg end 'smerge-force-highlighting nil)
352 (let ((modified (buffer-modified-p)))
353 (remove-text-properties beg end '(fontified nil))
354 (restore-buffer-modified-p modified)))
356 (defun smerge-popup-context-menu (event)
357 "Pop up the Smerge mode context menu under mouse."
358 (interactive "e")
359 (if (and smerge-mode
360 (save-excursion (posn-set-point (event-end event)) (smerge-check 1)))
361 (progn
362 (posn-set-point (event-end event))
363 (smerge-match-conflict)
364 (let ((i (smerge-get-current))
366 (if (<= i 0)
367 ;; Out of range
368 (popup-menu smerge-mode-menu)
369 ;; Install overlay.
370 (setq o (make-overlay (match-beginning i) (match-end i)))
371 (unwind-protect
372 (progn
373 (overlay-put o 'face 'highlight)
374 (sit-for 0) ;Display the new highlighting.
375 (popup-menu smerge-context-menu))
376 ;; Delete overlay.
377 (delete-overlay o)))))
378 ;; There's no conflict at point, the text-props are just obsolete.
379 (save-excursion
380 (let ((beg (re-search-backward smerge-end-re nil t))
381 (end (re-search-forward smerge-begin-re nil t)))
382 (smerge-remove-props (or beg (point-min)) (or end (point-max)))
383 (push event unread-command-events)))))
385 (defun smerge-resolve (&optional safe)
386 "Resolve the conflict at point intelligently.
387 This relies on mode-specific knowledge and thus only works in
388 some major modes. Uses `smerge-resolve-function' to do the actual work."
389 (interactive)
390 (smerge-match-conflict)
391 (smerge-remove-props (match-beginning 0) (match-end 0))
392 (cond
393 ;; Trivial diff3 -A non-conflicts.
394 ((and (eq (match-end 1) (match-end 3))
395 (eq (match-beginning 1) (match-beginning 3)))
396 (smerge-keep-n 3))
397 ;; Mode-specific conflict resolution.
398 ((condition-case nil
399 (atomic-change-group
400 (if safe
401 (funcall smerge-resolve-function safe)
402 (funcall smerge-resolve-function))
404 (error nil))
405 ;; Nothing to do: the resolution function has done it already.
406 nil)
407 ;; FIXME: Add "if [ diff -b MINE OTHER ]; then select OTHER; fi"
408 ((and (match-end 2)
409 ;; FIXME: Add "diff -b BASE MINE | patch OTHER".
410 ;; FIXME: Add "diff -b BASE OTHER | patch MINE".
411 nil)
413 ((and (not (match-end 2))
414 ;; FIXME: Add "diff -b"-based refinement.
415 nil)
418 (error "Don't know how to resolve")))
419 (smerge-auto-leave))
421 (defun smerge-resolve-all ()
422 "Perform automatic resolution on all conflicts."
423 (interactive)
424 (save-excursion
425 (goto-char (point-min))
426 (while (re-search-forward smerge-begin-re nil t)
427 (condition-case nil
428 (progn
429 (smerge-match-conflict)
430 (smerge-resolve 'safe))
431 (error nil)))))
433 (defun smerge-batch-resolve ()
434 ;; command-line-args-left is what is left of the command line.
435 (if (not noninteractive)
436 (error "`smerge-batch-resolve' is to be used only with -batch"))
437 (while command-line-args-left
438 (let ((file (pop command-line-args-left)))
439 (message "Resolving conflicts in %s..." file)
440 (when (file-readable-p file)
441 (with-current-buffer (find-file-noselect file)
442 (smerge-resolve-all)
443 (save-buffer)
444 (kill-buffer (current-buffer)))))))
446 (defun smerge-keep-base ()
447 "Revert to the base version."
448 (interactive)
449 (smerge-match-conflict)
450 (smerge-ensure-match 2)
451 (smerge-keep-n 2)
452 (smerge-auto-leave))
454 (defun smerge-keep-other ()
455 "Use \"other\" version."
456 (interactive)
457 (smerge-match-conflict)
458 ;;(smerge-ensure-match 3)
459 (smerge-keep-n 3)
460 (smerge-auto-leave))
462 (defun smerge-keep-mine ()
463 "Keep your version."
464 (interactive)
465 (smerge-match-conflict)
466 ;;(smerge-ensure-match 1)
467 (smerge-keep-n 1)
468 (smerge-auto-leave))
470 (defun smerge-get-current ()
471 (let ((i 3))
472 (while (or (not (match-end i))
473 (< (point) (match-beginning i))
474 (>= (point) (match-end i)))
475 (decf i))
478 (defun smerge-keep-current ()
479 "Use the current (under the cursor) version."
480 (interactive)
481 (smerge-match-conflict)
482 (let ((i (smerge-get-current)))
483 (if (<= i 0) (error "Not inside a version")
484 (smerge-keep-n i)
485 (smerge-auto-leave))))
487 (defun smerge-kill-current ()
488 "Remove the current (under the cursor) version."
489 (interactive)
490 (smerge-match-conflict)
491 (let ((i (smerge-get-current)))
492 (if (<= i 0) (error "Not inside a version")
493 (let ((left nil))
494 (dolist (n '(3 2 1))
495 (if (and (match-end n) (/= (match-end n) (match-end i)))
496 (push n left)))
497 (if (and (cdr left)
498 (/= (match-end (car left)) (match-end (cadr left))))
499 (ding) ;We don't know how to do that.
500 (smerge-keep-n (car left))
501 (smerge-auto-leave))))))
503 (defun smerge-diff-base-mine ()
504 "Diff 'base' and 'mine' version in current conflict region."
505 (interactive)
506 (smerge-diff 2 1))
508 (defun smerge-diff-base-other ()
509 "Diff 'base' and 'other' version in current conflict region."
510 (interactive)
511 (smerge-diff 2 3))
513 (defun smerge-diff-mine-other ()
514 "Diff 'mine' and 'other' version in current conflict region."
515 (interactive)
516 (smerge-diff 1 3))
518 (defun smerge-match-conflict ()
519 "Get info about the conflict. Puts the info in the `match-data'.
520 The submatches contain:
521 0: the whole conflict.
522 1: your code.
523 2: the base code.
524 3: other code.
525 An error is raised if not inside a conflict."
526 (save-excursion
527 (condition-case nil
528 (let* ((orig-point (point))
530 (_ (forward-line 1))
531 (_ (re-search-backward smerge-begin-re))
533 (start (match-beginning 0))
534 (mine-start (match-end 0))
535 (filename (or (match-string 1) ""))
537 (_ (re-search-forward smerge-end-re))
538 (_ (assert (< orig-point (match-end 0))))
540 (other-end (match-beginning 0))
541 (end (match-end 0))
543 (_ (re-search-backward smerge-other-re start))
545 (mine-end (match-beginning 0))
546 (other-start (match-end 0))
548 base-start base-end)
550 ;; handle the various conflict styles
551 (cond
552 ((save-excursion
553 (goto-char mine-start)
554 (re-search-forward smerge-begin-re end t))
555 ;; There's a nested conflict and we're after the the beginning
556 ;; of the outer one but before the beginning of the inner one.
557 ;; Of course, maybe this is not a nested conflict but in that
558 ;; case it can only be something nastier that we don't know how
559 ;; to handle, so may as well arbitrarily decide to treat it as
560 ;; a nested conflict. --Stef
561 (error "There is a nested conflict"))
563 ((re-search-backward smerge-base-re start t)
564 ;; a 3-parts conflict
565 (set (make-local-variable 'smerge-conflict-style) 'diff3-A)
566 (setq base-end mine-end)
567 (setq mine-end (match-beginning 0))
568 (setq base-start (match-end 0)))
570 ((string= filename (file-name-nondirectory
571 (or buffer-file-name "")))
572 ;; a 2-parts conflict
573 (set (make-local-variable 'smerge-conflict-style) 'diff3-E))
575 ((and (not base-start)
576 (or (eq smerge-conflict-style 'diff3-A)
577 (equal filename "ANCESTOR")
578 (string-match "\\`[.0-9]+\\'" filename)))
579 ;; a same-diff conflict
580 (setq base-start mine-start)
581 (setq base-end mine-end)
582 (setq mine-start other-start)
583 (setq mine-end other-end)))
585 (store-match-data (list start end
586 mine-start mine-end
587 base-start base-end
588 other-start other-end
589 (when base-start (1- base-start)) base-start
590 (1- other-start) other-start))
592 (search-failed (error "Point not in conflict region")))))
594 (add-to-list 'debug-ignored-errors "Point not in conflict region")
596 (defun smerge-conflict-overlay (pos)
597 "Return the conflict overlay at POS if any."
598 (let ((ols (overlays-at pos))
599 conflict)
600 (dolist (ol ols)
601 (if (and (eq (overlay-get ol 'smerge) 'conflict)
602 (> (overlay-end ol) pos))
603 (setq conflict ol)))
604 conflict))
606 (defun smerge-find-conflict (&optional limit)
607 "Find and match a conflict region. Intended as a font-lock MATCHER.
608 The submatches are the same as in `smerge-match-conflict'.
609 Returns non-nil if a match is found between point and LIMIT.
610 Point is moved to the end of the conflict."
611 (let ((found nil)
612 (pos (point))
613 conflict)
614 ;; First check to see if point is already inside a conflict, using
615 ;; the conflict overlays.
616 (while (and (not found) (setq conflict (smerge-conflict-overlay pos)))
617 ;; Check the overlay's validity and kill it if it's out of date.
618 (condition-case nil
619 (progn
620 (goto-char (overlay-start conflict))
621 (smerge-match-conflict)
622 (goto-char (match-end 0))
623 (if (<= (point) pos)
624 (error "Matching backward!")
625 (setq found t)))
626 (error (smerge-remove-props
627 (overlay-start conflict) (overlay-end conflict))
628 (goto-char pos))))
629 ;; If we're not already inside a conflict, look for the next conflict
630 ;; and add/update its overlay.
631 (while (and (not found) (re-search-forward smerge-begin-re limit t))
632 (condition-case nil
633 (progn
634 (smerge-match-conflict)
635 (goto-char (match-end 0))
636 (let ((conflict (smerge-conflict-overlay (1- (point)))))
637 (if conflict
638 ;; Update its location, just in case it got messed up.
639 (move-overlay conflict (match-beginning 0) (match-end 0))
640 (setq conflict (make-overlay (match-beginning 0) (match-end 0)
641 nil 'front-advance nil))
642 (overlay-put conflict 'evaporate t)
643 (overlay-put conflict 'smerge 'conflict)
644 (let ((props smerge-text-properties))
645 (while props
646 (overlay-put conflict (pop props) (pop props))))))
647 (setq found t))
648 (error nil)))
649 found))
651 (defun smerge-refine-chopup-region (beg end file)
652 "Chopup the region into small elements, one per line."
653 ;; ediff chops up into words, where the definition of a word is
654 ;; customizable. Instead we here keep only one char per line.
655 ;; The advantages are that there's nothing to configure, that we get very
656 ;; fine results, and that it's trivial to map the line numbers in the
657 ;; output of diff back into buffer positions. The disadvantage is that it
658 ;; can take more time to compute the diff and that the result is sometimes
659 ;; too fine. I'm not too concerned about the slowdown because conflicts
660 ;; are usually significantly smaller than the whole file. As for the
661 ;; problem of too-fine-refinement, I have found it to be unimportant
662 ;; especially when you consider the cases where the fine-grain is just
663 ;; what you want.
664 (let ((buf (current-buffer)))
665 (with-temp-buffer
666 (insert-buffer-substring buf beg end)
667 (goto-char (point-min))
668 (while (not (eobp))
669 (forward-char 1)
670 (unless (eq (char-before) ?\n) (insert ?\n)))
671 (let ((coding-system-for-write 'emacs-mule))
672 (write-region (point-min) (point-max) file nil 'nomessage)))))
674 (defun smerge-refine-highlight-change (buf beg match-num1 match-num2)
675 (let* ((startline (string-to-number (match-string match-num1)))
676 (ol (make-overlay
677 (+ beg startline -1)
678 (+ beg (if (match-end match-num2)
679 (string-to-number (match-string match-num2))
680 startline))
682 'front-advance nil)))
683 (overlay-put ol 'smerge 'refine)
684 (overlay-put ol 'evaporate t)
685 (overlay-put ol 'face 'smerge-refined-change)))
688 (defun smerge-refine ()
689 "Highlight the parts of the conflict that are different."
690 (interactive)
691 ;; FIXME: make it work with 3-way conflicts.
692 (smerge-match-conflict)
693 (remove-overlays (match-beginning 0) (match-end 0) 'smerge 'refine)
694 (smerge-ensure-match 1)
695 (smerge-ensure-match 3)
696 (let ((buf (current-buffer))
697 ;; Read them before the match-data gets clobbered.
698 (beg1 (match-beginning 1)) (end1 (match-end 1))
699 (beg2 (match-beginning 3)) (end2 (match-end 3))
700 (file1 (make-temp-file "smerge1"))
701 (file2 (make-temp-file "smerge2")))
703 ;; Chop up regions into smaller elements and save into files.
704 (smerge-refine-chopup-region beg1 end1 file1)
705 (smerge-refine-chopup-region beg2 end2 file2)
707 ;; Call diff on those files.
708 (unwind-protect
709 (with-temp-buffer
710 (let ((coding-system-for-read 'emacs-mule))
711 ;; Don't forget -a to make sure diff treats it as a text file
712 ;; even if it contains \0 and such.
713 (call-process diff-command nil t nil "-a" file1 file2))
714 ;; Process diff's output.
715 (goto-char (point-min))
716 (while (not (eobp))
717 (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$"))
718 (error "Unexpected patch hunk header: %s"
719 (buffer-substring (point) (line-end-position)))
720 (let ((op (char-after (match-beginning 3))))
721 (when (memq op '(?d ?c))
722 (smerge-refine-highlight-change buf beg1 1 2))
723 (when (memq op '(?a ?c))
724 (smerge-refine-highlight-change buf beg2 4 5)))
725 (forward-line 1) ;Skip hunk header.
726 (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body.
727 (goto-char (match-beginning 0))))))
728 (delete-file file1)
729 (delete-file file2))))
731 (defun smerge-diff (n1 n2)
732 (smerge-match-conflict)
733 (smerge-ensure-match n1)
734 (smerge-ensure-match n2)
735 (let ((name1 (aref smerge-match-names n1))
736 (name2 (aref smerge-match-names n2))
737 ;; Read them before the match-data gets clobbered.
738 (beg1 (match-beginning n1))
739 (end1 (match-end n1))
740 (beg2 (match-beginning n2))
741 (end2 (match-end n2))
742 (file1 (make-temp-file "smerge1"))
743 (file2 (make-temp-file "smerge2"))
744 (dir default-directory)
745 (file (if buffer-file-name (file-relative-name buffer-file-name)))
746 ;; We would want to use `emacs-mule-unix' for read&write, but we
747 ;; bump into problems with the coding-system used by diff to write
748 ;; the file names and the time stamps in the header.
749 ;; `buffer-file-coding-system' is not always correct either, but if
750 ;; the OS/user uses only one coding-system, then it works.
751 (coding-system-for-read buffer-file-coding-system))
752 (write-region beg1 end1 file1 nil 'nomessage)
753 (write-region beg2 end2 file2 nil 'nomessage)
754 (unwind-protect
755 (with-current-buffer (get-buffer-create smerge-diff-buffer-name)
756 (setq default-directory dir)
757 (let ((inhibit-read-only t))
758 (erase-buffer)
759 (let ((status
760 (apply 'call-process diff-command nil t nil
761 (append smerge-diff-switches
762 (list "-L" (concat name1 "/" file)
763 "-L" (concat name2 "/" file)
764 file1 file2)))))
765 (if (eq status 0) (insert "No differences found.\n"))))
766 (goto-char (point-min))
767 (diff-mode)
768 (display-buffer (current-buffer) t))
769 (delete-file file1)
770 (delete-file file2))))
772 ;; compiler pacifiers
773 (defvar smerge-ediff-windows)
774 (defvar smerge-ediff-buf)
775 (defvar ediff-buffer-A)
776 (defvar ediff-buffer-B)
777 (defvar ediff-buffer-C)
778 (defvar ediff-ancestor-buffer)
779 (defvar ediff-quit-hook)
781 ;;;###autoload
782 (defun smerge-ediff (&optional name-mine name-other name-base)
783 "Invoke ediff to resolve the conflicts.
784 NAME-MINE, NAME-OTHER, and NAME-BASE, if non-nil, are used for the
785 buffer names."
786 (interactive)
787 (let* ((buf (current-buffer))
788 (mode major-mode)
789 ;;(ediff-default-variant 'default-B)
790 (config (current-window-configuration))
791 (filename (file-name-nondirectory buffer-file-name))
792 (mine (generate-new-buffer
793 (or name-mine (concat "*" filename " MINE*"))))
794 (other (generate-new-buffer
795 (or name-other (concat "*" filename " OTHER*"))))
796 base)
797 (with-current-buffer mine
798 (buffer-disable-undo)
799 (insert-buffer-substring buf)
800 (goto-char (point-min))
801 (while (smerge-find-conflict)
802 (when (match-beginning 2) (setq base t))
803 (smerge-keep-n 1))
804 (buffer-enable-undo)
805 (set-buffer-modified-p nil)
806 (funcall mode))
808 (with-current-buffer other
809 (buffer-disable-undo)
810 (insert-buffer-substring buf)
811 (goto-char (point-min))
812 (while (smerge-find-conflict)
813 (smerge-keep-n 3))
814 (buffer-enable-undo)
815 (set-buffer-modified-p nil)
816 (funcall mode))
818 (when base
819 (setq base (generate-new-buffer
820 (or name-base (concat "*" filename " BASE*"))))
821 (with-current-buffer base
822 (buffer-disable-undo)
823 (insert-buffer-substring buf)
824 (goto-char (point-min))
825 (while (smerge-find-conflict)
826 (if (match-end 2)
827 (smerge-keep-n 2)
828 (delete-region (match-beginning 0) (match-end 0))))
829 (buffer-enable-undo)
830 (set-buffer-modified-p nil)
831 (funcall mode)))
833 ;; the rest of the code is inspired from vc.el
834 ;; Fire up ediff.
835 (set-buffer
836 (if base
837 (ediff-merge-buffers-with-ancestor mine other base)
838 ;; nil 'ediff-merge-revisions-with-ancestor buffer-file-name)
839 (ediff-merge-buffers mine other)))
840 ;; nil 'ediff-merge-revisions buffer-file-name)))
842 ;; Ediff is now set up, and we are in the control buffer.
843 ;; Do a few further adjustments and take precautions for exit.
844 (set (make-local-variable 'smerge-ediff-windows) config)
845 (set (make-local-variable 'smerge-ediff-buf) buf)
846 (set (make-local-variable 'ediff-quit-hook)
847 (lambda ()
848 (let ((buffer-A ediff-buffer-A)
849 (buffer-B ediff-buffer-B)
850 (buffer-C ediff-buffer-C)
851 (buffer-Ancestor ediff-ancestor-buffer)
852 (buf smerge-ediff-buf)
853 (windows smerge-ediff-windows))
854 (ediff-cleanup-mess)
855 (with-current-buffer buf
856 (erase-buffer)
857 (insert-buffer-substring buffer-C)
858 (kill-buffer buffer-A)
859 (kill-buffer buffer-B)
860 (kill-buffer buffer-C)
861 (when (bufferp buffer-Ancestor) (kill-buffer buffer-Ancestor))
862 (set-window-configuration windows)
863 (message "Conflict resolution finished; you may save the buffer")))))
864 (message "Please resolve conflicts now; exit ediff when done")))
867 (defconst smerge-parsep-re
868 (concat smerge-begin-re "\\|" smerge-end-re "\\|"
869 smerge-base-re "\\|" smerge-other-re "\\|"))
871 ;;;###autoload
872 (define-minor-mode smerge-mode
873 "Minor mode to simplify editing output from the diff3 program.
874 \\{smerge-mode-map}"
875 :group 'smerge :lighter " SMerge"
876 (when (and (boundp 'font-lock-mode) font-lock-mode)
877 (save-excursion
878 (if smerge-mode
879 (font-lock-add-keywords nil smerge-font-lock-keywords 'append)
880 (font-lock-remove-keywords nil smerge-font-lock-keywords))
881 (goto-char (point-min))
882 (while (smerge-find-conflict)
883 (save-excursion
884 (font-lock-fontify-region (match-beginning 0) (match-end 0) nil)))))
885 (if (string-match (regexp-quote smerge-parsep-re) paragraph-separate)
886 (unless smerge-mode
887 (set (make-local-variable 'paragraph-separate)
888 (replace-match "" t t paragraph-separate)))
889 (when smerge-mode
890 (set (make-local-variable 'paragraph-separate)
891 (concat smerge-parsep-re paragraph-separate))))
892 (unless smerge-mode
893 (smerge-remove-props (point-min) (point-max))))
896 (provide 'smerge-mode)
898 ;; arch-tag: 605c8d1e-e43d-4943-a6f3-1bcc4333e690
899 ;;; smerge-mode.el ends here