]> git.donarmstrong.com Git - lib.git/blob - emacs_el/tiny-tools/tiny/tinydiff.el
add tiny-tools
[lib.git] / emacs_el / tiny-tools / tiny / tinydiff.el
1 ;;; tinydiff.el --- Diff and patch minor mode. Browsing, patching.
2
3 ;; This file is not part of Emacs
4
5 ;;{{{ Id
6
7 ;; Copyright (C)    1996-2007 Jari Aalto
8 ;; Keywords:        tools
9 ;; Author:          Jari Aalto
10 ;; Maintainer:      Jari Aalto
11 ;;
12 ;; To get information on this program use ident(1) or call M-x
13 ;; tinydiff-version Look at the code with folding.el
14
15 ;; COPYRIGHT NOTICE
16 ;;
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
20 ;; any later version.
21 ;;
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25 ;; for more details.
26 ;;
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
31 ;;
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
33
34 ;;}}}
35 ;;{{{ Install
36
37 ;; ....................................................... &t-install ...
38 ;;   Put this file on your Emacs-Lisp load path, add following into your
39 ;;   ~/.emacs startup file
40 ;;
41 ;;   Here is the very basic setup. You don't necessarily need to set
42 ;;   these variables, because they are determined at startup.
43 ;;
44 ;;      (setq tinydiff-:diff-program  "gdiff")      ;; GNU diff
45 ;;      (setq tinydiff-:patch-program "gpatch -t")  ;; GNU patch
46 ;;
47 ;;      (require 'tinydiff)
48 ;;
49 ;;   OR use this; your .emacs will load much  quicker
50 ;;
51 ;;      (autoload 'tinydiff-mode              "tinydiff" "" t)
52 ;;      (autoload 'turn-on-tinydiff-mode      "tinydiff" "" t)
53 ;;      (autoload 'tinydiff-diff              "tinydiff" "" t)
54 ;;      (autoload 'tinydiff-diff-show         "tinydiff" "" t)
55 ;;      (autoload 'tinydiff-diff-show-noask   "tinydiff" "" t)
56 ;;
57 ;;   Here are some suggested key bindings. Reserve The "d" for diff map.
58 ;;
59 ;;      (global-set-key "\C-cmd" nil)     ;; Initialize prefix key
60 ;;      (global-set-key "\C-cmdd"         'tinydiff-mode)
61 ;;      (global-set-key "\C-cmdd"         'tinydiff-diff-show)
62 ;;      (global-set-key "\C-cmdp"         'tinydiff-patch)
63 ;;
64 ;;   If you have any questions, use these functions
65 ;;
66 ;;      M-x tinydiff-debug-toggle         turn on debug
67 ;;      <... do as you did ...>
68 ;;      M-x tinydiff-submit-bug-report    send bug report
69
70 ;;}}}
71 ;;{{{ Documentation
72
73 ;; ..................................................... &t-commentary ...
74
75 ;;; Commentary:
76
77 ;;  Preface, jan 1996
78 ;;
79 ;;      Long ago there was set of simple functions lying around to generate
80 ;;      instant diffs for the file that was being edited, before it was
81 ;;      checked in with RCS. At the time *vc.el* was not in the Emacs
82 ;;      distribution. Looking at diffs and using "goto-line" command in
83 ;;      other buffer gave an idea to make a separate diff mode. The project
84 ;;      turned out to be a bit bigger than just taking simple diff. You may
85 ;;      wonder, why would you use this utility over ediff.el? If you like
86 ;;      working with "command line" diff interface, then you may want to
87 ;;      use this utility over *ediff.el*. There is a command prompt when
88 ;;      various diff options can be manipulated with key bindings. Lik:
89 ;;      Change rcsdiff to diff command, copy previous argument etc.
90 ;;
91 ;;  Overview of features
92 ;;
93 ;;      Taking diff
94 ;;
95 ;;      o   Buffer based simple diff/rcsdiff/patch package.
96 ;;      o   You can diff current buffer against last saved backup copy.
97 ;;      o   Give and manipulate diff command in echo-area: file name
98 ;;          completion; switching between rcsdiff/diff, guess rcs version
99 ;;          for buffer, various diff switches...
100 ;;
101 ;;      Browsing diff
102 ;;
103 ;;      o   Supported: normal diff, context diff, gnu diff -u, gnu diff -n
104 ;;      o   When you have diff output in buffer, turning on `tinydiff-mode'
105 ;;          allows you to browse source buffer by jumping diff blocks fwd/back
106 ;;          and showing the source location.
107 ;;      o   You can kill single diff block with `tinydiff-block-kill'
108 ;;      o   You can apply only the current diff block (patch) with
109 ;;          `tinydiff-block-apply-patch'
110 ;;
111 ;;      Sending or saving diff
112 ;;
113 ;;      o   In diff buffer, you can save the diff to a file with "W"; write
114 ;;      o   In diff buffer, you can attach the content as MIME diff to
115 ;;          the open mail buffer. Or if you don't have MIME-edit active,
116 ;;          the diff is added without MIME tags. Command "M" for Mime.
117 ;;
118 ;;      Patch
119 ;;
120 ;;      o   Easy patching. Finds file to patch (along user defined paths)
121 ;;          and applies the diff. You can receive patches by email
122 ;;          and apply them with one or two keystrokes.
123 ;;      o   Finds the file to patch through pre defined paths.
124 ;;      o   Can also patch compresses .gz file.
125 ;;      o   loads patch rejection file, if patch didn't succeed 100%
126 ;;      o   Re-evaluates patched lisp file if the file was used by Emacs
127 ;;      o   If you don't want to apply whole diff, use
128 ;;          `tinydiff-block-apply-patch' for individual sections.
129 ;;
130 ;;  Genrating diff -- parsing diff
131 ;;
132 ;;      Be in buffer where you have diff file and just turn on the
133 ;;
134 ;;          M-x tinydiff-mode
135 ;;
136 ;;      Then take a look at the bindings you have available:
137 ;;
138 ;;          C-x b
139 ;;
140 ;;      If you want to generate [rcs]diff for current buffer, call function
141 ;;
142 ;;          M-x tinydiff-diff-show
143 ;;
144 ;;      And it generates diff and puts you on `tinydiff-mode'. X window users and
145 ;;      those that have the highlighting capabitities can enjoy more about
146 ;;      this mode, because it marks line numbers in buffer with
147 ;;      `mouse-face'. You just click the point to jump to diff position
148 ;;
149 ;;  Taking diffs
150 ;;
151 ;;      The main purpose of this module is to help you taking "diff shots",
152 ;;      inside emacs. This means that the file must be loaded into
153 ;;      emacs and your cursor must be in the buffers, before you execute
154 ;;
155 ;;          M-x tinydiff-diff-show
156 ;;
157 ;;      o   If the file is not rcs controlled you're offered regular diff
158 ;;      o   if file is rcs controlled, your're offered rcsdiff prompt
159 ;;      o   if the buffer has changed, you're offered to diff against
160 ;;          last saved file to see recent changes you have done since you
161 ;;          saved the buffer.
162 ;;
163 ;;  Command prompt
164 ;;
165 ;;      The help key is on `?', press it to get summary of command while
166 ;;      you're in minibuffer prompt. The command prompt in minibuffer looks
167 ;;      like this for rcs controlled file.
168 ;;
169 ;;          > cd /users/foo/dir1/dir2; rcsdiff -c -r1.21 test.txt
170 ;;
171 ;;      You can edit this command as much as you like, but please leave `cd'
172 ;;      `XXX' alone because the minibuffer commands expect it it be
173 ;;      present. The hotkey command won't work without it.
174 ;;
175 ;;  Command prompt: rcsdiff and diff toggle
176 ;;
177 ;;      To conveniently construct diff command against another file, say
178 ;;      test2.txt, you can hit key `C-z' to chage the prompt immediately to
179 ;;
180 ;;          > cd /users/foo/dir1/dir2; diff -c test.txt
181 ;;
182 ;;      And add the `test2.txt' to the end of line. If you want to restore
183 ;;      the previous rcsdiff form, just hit `C-z' again. This `C-z'
184 ;;      facility works only if the initial command was rcsdiff. There is no
185 ;;      point of converting initial diff command to rcsdiff command though.
186 ;;
187 ;;  Command prompt: tab completes file name
188 ;;
189 ;;      While your're editing the command you can also use the TAB key to
190 ;;      complete filename in the 'cd' command directory. If you specify any
191 ;;      directories for the file, the directory's files are completed.
192 ;;      That feature should allow you to get filenames into the prompt
193 ;;      easily.
194 ;;
195 ;;  Command prompt: diffing between two Rcs revisions
196 ;;
197 ;;      There is also more commands, like `C-r' which changes
198 ;;
199 ;;          > cd /users/foo/dir1/dir2; rcsdiff -c -r1.21 test.txt
200 ;;
201 ;;      prompt so that it has now two -r commands. You can take diffs
202 ;;      between two versions easily with it. The `C-r' key is a toggle.
203 ;;
204 ;;          > cd /users/foo/dir1/dir2; rcsdiff -c -r1.21 -r1.21 test.txt
205 ;;
206 ;;       Case study:
207 ;;
208 ;;      You see nice package on the net. You download it ;; and notice that
209 ;;      it needs some fixes. You put the original version ;; to your
210 ;;      private rcstree with the same version number as what ;; the package
211 ;;      had; say 2.2. Then you CheckOut the original, make ;; changes, and
212 ;;      put it back to tree with version 2.2.1.1. You dont't ;; put it back
213 ;;      with 2.3, because that's not your file. You made the ;; correction
214 ;;      to 2.2, so you must make a branch.
215 ;;
216 ;;      Okay. You have the original 2.2 and you have the fixed version
217 ;;      2.2.1.1 and you want to send the diff to the author. Here is how
218 ;;      you do it
219 ;;
220 ;;      o   Be on the file buffer 2.2.1.1 and hit M-x tinydiff-dif
221 ;;      o   Hit `C-r' toggle second revision (previous) and edit the line
222 ;;          to look "-r2.2 -r2.2.1.1". You are usually comparing _old_ and
223 ;;          new_ versions.
224 ;;      o   Hit `C-s' to toggle between `-u' or `-c'. You normally want
225 ;;          to send `-u' gnu unified diff, because it is more readable.
226 ;;          Provided that the receiver has gnu patch to understand it.
227 ;;      o   Hit `C-f' to add option `tinydiff-:cl-user-option' which by
228 ;;          default is `-kk'. From the rcsdiff(1) man pages you will
229 ;;          see that it roughly means: "When you take diff between versions,
230 ;;          ignore the rcs tag differencies". Confused? It means that
231 ;;          the keywords that changed, like version, author, log ..
232 ;;          when you deposited 2.2 and 2.2.1.1 are ignored.
233 ;;
234 ;;      And hit enter. Then you get clean diff that you can send to author.
235 ;;      And when he responds back or sends you new version, say 2.5,
236 ;;      you repeat the whole process again if you intend to make more
237 ;;      changes 8put original 2.5 on ice and make branch 2.5.1.1 for your
238 ;;      changes)
239 ;;
240 ;;  Command prompt: autosave and backup file diff
241 ;;
242 ;;      Other helpfull commands insert he #autosaved# and backup~ filenames
243 ;;      into the current point. Remember to put the # or ~ file to the left
244 ;;      and not to the right. you do want to diff current file against the
245 ;;      saved one; right? The first one is original prompt. That second is
246 ;;      after `C-r' and latter after `C-v'
247 ;;
248 ;;          > cd /users/foo/dir1/dir2; diff -c test.txt
249 ;;                                            * point here
250 ;;
251 ;;          > cd /users/foo/dir1/dir2; diff -c #test.txt# test.txt
252 ;;          > cd /users/foo/dir1/dir2; diff -c ~/backup/test.txt~ test.txt
253 ;;
254 ;;      Notice that your backup file may not reside int he same directory.
255 ;;      The backupfilename is returned by function `make-backup-file'.
256 ;;
257 ;;  Generated diff: the Prereq tag
258 ;;
259 ;;      It is important that when you send diff, it is diff between two
260 ;;      rcs versions if possible (if you're author of program). In those
261 ;;      cases where revision information can be found, the diff data
262 ;;      is preceeded with this line:
263 ;;
264 ;;          Prereq: N.NN        e.g. Prereq: 1.76
265 ;;
266 ;;      If the receiving end has GNU patch, the patch program first checks
267 ;;      if the version that person has is exactly N.NN and aborts if
268 ;;      he had some other version. This prevent applying diffs that
269 ;;      are meant to other versions. Regular Unix *patch* program
270 ;;      does not notice the *Prereq:* tag, so consider getting more
271 ;;      safer GNU version as soon as possible.
272 ;;
273 ;;  Patching
274 ;;
275 ;;      There is also included little patching function.
276 ;;
277 ;;          M-x tinydiff-patch          non verbose
278 ;;          C-u M-x tinydiff-patch      verbose
279 ;;
280 ;;      For elisp (.el) files the `load-path' is automatically searched
281 ;;      for possible destination of the patch. You can set variable
282 ;;
283 ;;          tinydiff-:patch-list
284 ;;
285 ;;      To match files and their associated patch directories if you
286 ;;      receive patches for other files regularly. This function is most
287 ;;      useful for RCS diffs, because they can be easily detected and the
288 ;;      file information is also included in the diff.
289 ;;
290 ;;  Patch: general notes
291 ;;
292 ;;      Note: normally when `patch' program is called it always makes
293 ;;      backup with the suffix .orig. So if you have applied a patch,
294 ;;      then there is two file in the directory.
295 ;;
296 ;;          FILE.txt        -- patched file
297 ;;          FILE.txt.orig   -- original file, before the patch
298 ;;
299 ;;      It also creates rejections file if all dind't go as planned.
300 ;;
301 ;;          FILE.txt.rej
302 ;;
303 ;;  Patch: success or failure
304 ;;
305 ;;      When the patch has been applied, This package checks if all went
306 ;;      well. If rejection file was created; then the patch process's
307 ;;      output is shown and the rejection file is loaded so that you can
308 ;;      see what possibly went wrong and if you should be concerned.
309 ;;
310 ;;      If you get this rejection file, then there propably is potential
311 ;;      trouble. Please contact the sender of patch immediately and tell
312 ;;      about your troubles. There are few common reasons why patch failure
313 ;;      happened.
314 ;;
315 ;;      o   Sender forgot `-kk' switch when he run rcsdiff to the file
316 ;;          that was not owned by him (See RCS for details about `-kk')
317 ;;      o   Too few context, Sender should try increasing context with
318 ;;          `-C' switch (like `-C7')
319 ;;      o   The patch were modified during email transit. Ask
320 ;;          to send the patch with some encoded format: uuencode, base64,
321 ;;          PGP encrypted or PGP base64 signed (clearsig off) format.
322 ;;
323 ;;  Patch: what happens after success
324 ;;
325 ;;      When the patch succeeds, there is a bit special handling for Emacs
326 ;;      elisp packages. Say we recieve correction to the following module
327 ;;      and you have it loaded in emacs: (feature 'foo) returns true.
328 ;;
329 ;;          foo.el
330 ;;
331 ;;      After patch is applied, you're asked if you want to reload the
332 ;;      new release of *foo* module (just patched). You should answer
333 ;;      `Yes' to get the newest one running in your Emacs immediately.
334 ;;
335 ;;  Patch: after success, returning to original version
336 ;;
337 ;;      If the patched version, which is usually new version of the progrmam
338 ;;      doesn't work as it is supposed to, you can go back to the original
339 ;;      version by appluing the same patch again. You should report what
340 ;;      problems you had to the maintainer and inform that you wnet back
341 ;;      to previous version.
342 ;;
343 ;;      *IMPORTANT* If you did get the rejection file, you can't use that
344 ;;      patch to go back to original!! See next chapter how to go to
345 ;;      original version in that case
346 ;;
347 ;;  Patch: rejection file created -- what to do?
348 ;;
349 ;;      If you want to go back to original version, apply the same diff
350 ;;      again; this reverses just applied patch. Just call `M-x'
351 ;;      `tinydiff-patch' in the buffer where you have the diff.
352 ;;
353 ;;      When you do that, the function detects that there is already a
354 ;;      .orig file and prompts you to choose an appropriate action.
355 ;;      Here is the explanation what they do and what should you choose
356 ;;
357 ;;       Command _*o*_
358 ;;
359 ;;      Go back to (o)riginal. This copies the FILE.txt.orig over the
360 ;;      FILE.txt and deletes FILE.txt.orig and doesn't do _anything_
361 ;;      else (stops the patching process). You're back to starting
362 ;;      point as if you never patched anything.
363 ;;
364 ;;       Command _*r*_
365 ;;
366 ;;      (R)etry means that the FILE.txt.orig is copied over FILE.txt and the
367 ;;      pach is tried again for FILE.txt. You may have asked the author to
368 ;;      send more context with using the -C10 switch and after you received
369 ;;      this new patch you want to try if it now goes ok. The FILE.txt.orig
370 ;;      still remains as a backup
371 ;;
372 ;;       Command _*g*_
373 ;;
374 ;;      (G)o says that we should apply the diff again to FILE.txt. Do this
375 ;;      only if you did not get rejections last time. The intention
376 ;;      is that you apply the patch again, and this reverses the situation.
377 ;;      I mean 1) you patch; you get new version 2) you patch again: you
378 ;;      degrade to the version before patch (original file before patch)
379 ;;
380 ;;  Development note
381 ;;
382 ;;      There is `ediff.el', which is much more complete package than
383 ;;      this is. The aim was to develop a simple but handy package for
384 ;;      everyday diff'ing and easy package patching.
385 ;;
386 ;;  Bugs
387 ;;
388 ;;      The "f" key, which shows the function identifier in diff browse
389 ;;      mode `tinydiff-mode', can handle buffers which are narrowed, but if the
390 ;;      buffer is using folding.el or similar package where goto-line does
391 ;;      not work properly, the returned message shown to user is not
392 ;;      correct.
393 ;;
394 ;;      Please unfold the buffer and you get the correct result.
395 ;;
396 ;;  Example
397 ;;
398 ;;      This hook setup turns on the view mode for easy scrolling
399 ;;      of buffer.
400 ;;
401 ;;          (add-hook 'tinydiff-:diff-hook  'my-tinydiff-diff-hook)
402 ;;
403 ;;          (defun my-tinydiff-diff-hook ()
404 ;;            "Turn on view-mode in diff buffer."
405 ;;            ;; See tinydiff-:diff-buffer.
406 ;;            (view-mode 1))
407 ;;
408 ;;  Sending good bug reports
409 ;;
410 ;;      If you find anything funny happening in the command line prompt
411 ;;      while you use the tdi minibuffer commands. Immediately do
412 ;;      following.
413 ;;
414 ;;      o   Turn on debug: `M-x' `tinydiff-debug-toggle'
415 ;;      o   Turn on emacs debug: (setq debug-on-error t)
416 ;;      o   Clear the debug buffer *tinydiff-debug* if it exists
417 ;;      o   Start from original situation
418 ;;      o   Do what you did and when the weird condition is met
419 ;;          immediately go to *tinydiff-debug* buffer and save the
420 ;;          content and send it to the maintainer.
421
422 ;;}}}
423
424 ;;; Change Log:
425
426 ;;; Code:
427
428 ;;{{{ setup: require
429
430 ;;; ......................................................... &require ...
431
432 ;;  make sure this is loaded so that the `tinydiff-mode' map can redefine
433 ;;  keys "n" and "p"
434
435 (require 'tinylibm)
436
437 (eval-and-compile
438   (ti::package-use-dynamic-compilation)
439   (ti::package-require-view)
440   (defvar diff-command)        ;; Byte compiler silencer
441   (defvar ediff-diff-program)  ;; Byte compiler silencer
442   (autoload 'dired-get-filename   "dired"  "" t))
443
444 (ti::package-defgroup-tiny TinyDiff tinydiff-: tools
445   "Take buffer diffs easily, browse diff and apply patch.
446   Overview of features.
447
448       o   Buffer based simple diff/rcsdiff/patch package.
449       o   You cab diff current buffer against last saved backup copy.
450       o   Give and manipulate diff command in echo-area: file name
451             completion; switching between rcsdiff/diff, guess rcs version
452             for buffer ...
453       o   When you have diff output in buffer, turning on `tinydiff-mode'
454           allows you to browse source buffer by jumping diff blocks fwd/back
455           and showing the source location.
456       o   Supported: normal diff, context diff, gnu diff -u, gnu diff -n
457       o   Easy patching. Finds file to patch (along user defined paths)
458           and applies the diff. You can receive patches by email
459           and apply them with one or two keystrokes.
460       o   loads patch rejection file, if patch didn't succeed 100%")
461
462 ;;}}}
463 ;;{{{ setup: hooks
464
465 ;;; ......................................................... &v-hooks ...
466
467 (defcustom tinydiff-:load-hook nil
468   "*Hook that is run when package is loaded."
469   :type  'hook
470   :group 'TinyDiff)
471
472 (defcustom tinydiff-:diff-hook  nil
473   "*Hooks that run after successful diff run."
474   :type  'hook
475   :group 'TinyDiff)
476
477 (defcustom tinydiff-:mode-define-keys-minibuffer-hook
478   'tinydiff-mode-define-keys-minibuffer-default
479   "*Function to define the keys for the minibuffer."
480   :type  'hook
481   :group 'TinyDiff)
482
483 (defcustom tinydiff-:parse-buffer-hook  nil
484   "Function called when diff buffer has been parsed. (highight)."
485   :type  'hook
486   :group 'TinyDiff)
487
488 ;;}}}
489 ;;{{{ setup: variables
490
491 ;;; ........................................................ &v-public ...
492 ;;; User configurable
493
494 (defcustom tinydiff-:auto-mode-alist
495   '(("\\.diff\\'"    . turn-on-tinydiff-mode)
496     ("\\.patch\\'"   . turn-on-tinydiff-mode))
497   "Items to add to `auto-mode-alist' to activate `turn-on-tinydiff-mode'."
498   :type '(repeat
499           (list
500            (string :tag "File Regexp")
501            (const turn-on-tinydiff-mode)))
502   :group  'TinyDiff)
503
504 ;;  We need this function in defvar; so instantiate it for compiler
505 ;;  to use it.
506 ;;
507 (eval-and-compile
508
509   (defun tinydiff-find-program (program-list default opt seek-option)
510     "Try to use GNU program. Return program name.
511 Input:
512
513   PROGRAM-LIST      list of program binaries to try (gnu)
514   DEFAULT           if no gnu binary found, use DEFAULT binary
515   OPT               additional option for binary
516   SEEK-OPTION       When GNU product, it must know this option."
517     (let* ((exec-path exec-path)
518            (ret       default)
519            gnu
520            path)
521       ;;  Give precedence to GNU diff programs
522       (dolist (path '("/opt/local/bin/" "/usr/local/bin/"))
523         (if (file-directory-p path)
524             (push path exec-path )))
525       (if path
526           (setq path t))               ;XEmacs ByteCom silencer, no-op
527       (with-temp-buffer
528         ;;   Select gnu if possible
529         (dolist (prg program-list)
530           (message "TinyDiff: Please wait. Searching for binary `%s'" prg)
531           (when (setq path (executable-find prg))
532             (call-process prg
533                           nil
534                           (current-buffer)
535                           nil
536                           seek-option)
537             (when (ti::re-search-check seek-option)
538               (setq gnu path)
539               (return))))
540         (if gnu
541             (setq ret gnu)
542           (message "TinyDiff: Hm, no GNU %s, but using it anyway" default)
543           (sit-for 1))
544         (set-buffer-modified-p nil)
545         ret)))) ;; eval-end
546
547 ;;  Different users may want to set the keys differently.
548 ;;  You could say
549 ;;
550 ;;  (setq tinydiff-:diff-program (progn (my-diff-program-select)))
551 ;;
552 ;;  Which would return appropriate diff program: maybe you want to use
553 ;;  GNU diff for some files and normal diff other times.
554 ;;  GNU diff offers line exlude options that you may want
555 ;;  to set for RCS files.
556
557 (defcustom tinydiff-:diff-program
558   (cond
559    ((and (boundp 'diff-command)
560          diff-command)
561     diff-command)
562    ((and (boundp 'ediff-diff-program)
563          ediff-diff-program)
564     ediff-diff-program)
565    ((ti::os-check-gnu-support-p)
566     "diff")
567    (t
568     (or (tinydiff-find-program '("gdiff" "diff") "diff" nil "--help")
569         "diff")))
570   "*Program to generate diff.
571 It should print 'filename: FILE.XXX' tag which is read by function
572 `tinydiff-get-buffer-name'. The variable can contain a Lisp expression
573 which returns program name."
574   :type  '(string :tag "Shell command")
575   :group 'TinyDiff)
576
577 (defcustom tinydiff-:rcsdiff-program "rcsdiff"
578   "*Shell program to print RCS diff.
579 It should output the 'filename: FILE' which is read by `tinydiff-get-buffer-name'.
580 This variable is evaled to get the program name."
581   :type  '(string :tag "Shell program")
582   :group 'TinyDiff)
583
584 (defcustom tinydiff-:cvsdiff-program "cvs diff"
585   "*Shell command to print CVS diff."
586   :type  '(string :tag "Command")
587   :group 'TinyDiff)
588
589 ;; The diff-switches is defined at least in vc.el
590
591 (defcustom tinydiff-:diff-option
592   '(or (and (boundp 'diff-switches)
593             (stringp diff-switches)
594             diff-switches)
595        (and (ti::os-check-gnu-support-p)
596             "-u")
597        "-c")
598   "*Diff options as STRING.
599 This variable is evaled to get the options, so it can contain Lisp
600 FORM that returns option string."
601
602   :type  '(string :tag "Options")
603   :group 'TinyDiff)
604
605 (defcustom tinydiff-:cl-user-option  "-kk"
606   "The option inserted or removed when user presses C - s in command line.
607 The default option -kk is only meaningful on rcsdiff command where
608 it excludes the rcs tags from diff."
609   :type  'string
610   :group 'TinyDiff)
611
612 (defcustom tinydiff-:diff-tmp-file
613   (or (let ((temp (or (getenv "TEMPDIR")
614                       (getenv "TMP")))
615             (file "tinydiff.diff"))
616         (dolist (dir (list
617                       "~/tmp"
618                       "~/temp"
619                       "/tmp"
620                       temp ;; this may be nil
621                       "c:/tmp"
622                       "c:/temp"
623                       "c:/winnt/tmp"
624                       "c:/windows/temp"))
625           (when (and (stringp dir)
626                      (file-directory-p dir))
627             (return (concat (file-name-as-directory dir) file)))))
628       (error "TinyDiff: Please set tinydiff-:diff-tmp-file"))
629   "*Temporary file where the diff is stored for patching."
630   :type  'file
631   :group 'TinyDiff)
632
633 (defcustom tinydiff-:patch-program
634   (if (ti::os-check-gnu-support-p)
635       ;;  We Know this is GNU patch, do not search alternatives
636       ;; gnu patch : -t, --batch
637       ;; similar to -f, in that it suppresses questions,
638       ;; skip patches for which a file to patch can't be found
639       ;;
640       ;; Note: OLD patch command doesn not know -t switch!
641       "patch -t -N -F 3"
642     (tinydiff-find-program
643      '("gpatch" "patch")
644      "patch"
645      "-t -N -F 3"
646      "--help"))
647   "*Patch command and its options.
648 This variable is evaluated to get the program name and switches."
649   :type  '(string :tag "shell command")
650   :group 'TinyDiff)
651
652 (defcustom tinydiff-:patch-list
653   '(( "[.]el$"  load-path)
654     ( "."       '("~/txt" "~/elisp")))
655   "*List of item that control how patching is applied.
656 The list form is:
657
658    '((REGEXP EVAL-FORM) (REGEXP EVAL-FORM) ..)
659
660 Where REGEXP is tried against the filename that is found from the patch
661 itself. EVAL-FORM can be any form that return list of pathnames that can
662 be searched for the filename. The first file that is found from the path
663 is used."
664   :type  '(repeat
665            (list
666             (string :tag "Regexp")
667             directory))
668   :group 'TinyDiff)
669
670 (defcustom tinydiff-:font-lock-keywords
671   '(
672
673     ;; RCS diff
674
675     ("RCS file: +\\(.*\\)"           1 'highlight)
676     ("retrieving revision +\\(.*\\)" 1 'highlight)
677
678     ;;  Lisp: "defun NAME" etc.
679
680     ("(def[^(\n]+"                  0 font-lock-reference-face)
681     ("(interactive.*)"              0 font-lock-type-face)
682
683     ;;  Lisp: some tokens
684
685     ("(\\(let\\|cond\\|when\\|unless\\|save-.*\\|with-.*\\)"
686      1 font-lock-keyword-face))
687   "Font lock keywords."
688   :type 'sexp
689   :group  'TinyDiff)
690
691 ;;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. . misc . .
692
693 (defcustom tinydiff-:register-function-name  ?f
694   "*Register used to store the function name.
695 Only used if `tinydiff-:function-name-handle-function' is set to
696 'tinydiff-function-name-store."
697   :type  'character
698   :group 'TinyDiff)
699
700 ;;  The diff data is inserted into register automatically, because
701 ;;  many time the diff data is needed elswhere.
702
703 (defcustom tinydiff-:register-diff ?d
704   "*Register name where diff is inserted.
705 If this variable is nil, then no data is inserted into register.
706
707 Variable is evaled to get the register name."
708   :type  'character
709   :group 'TinyDiff)
710
711 ;;; ......................................................... &v-funcs ...
712
713 (defcustom tinydiff-:mail-buffer-function  'ti::mail-get-buffer
714   "Return some mail buffer for function `tinydiff-mime-compose'.
715 Default value is `ti::mail-get-buffer'."
716   :type 'function
717   :group 'TinyDiff)
718
719 (defcustom tinydiff-:find-ref-function 'beginning-of-defun
720   "*Elisp Function to find underlying code's function name around point.
721 The cursor is positioned in the source buffer and on the referenced
722 line before calling with no arguments. Function should move the point
723 in the line below where the associated reference is located.
724
725 If no reference is found, function _must_ call 'error'."
726   :type 'function
727   :group 'TinyDiff)
728
729 ;;  - It's great to paste the function name into buffer.
730 ;;  - C-x g REG put the name into current buffer...
731
732 (defcustom tinydiff-:function-name-handle-function 'tinydiff-function-name-store
733   "*Function which find the code's function name string.
734
735 Input args to function:
736
737   POINT where the `tinydiff-:find-ref-function' positioned the defun.
738   This function should store the function name into register
739   `tinydiff-:register-function-name'."
740   :type  'function
741   :group 'TinyDiff)
742
743 (defcustom tinydiff-:source-buffer-function 'tinydiff-get-buffer-name
744   "*Function which return filename for the diff buffer.
745 You shouldn't touch this function unless you're coping with very
746 strange diff format. Default function is `tinydiff-get-buffer-name'."
747   :type  'function
748   :group 'TinyDiff)
749
750 ;;}}}
751 ;;{{{ setup: private
752
753 ;;; ....................................................... &v-private ...
754
755 (defvar tinydiff-:patch-global-option nil
756   "Path options in effect. See `tinydiff-patch-set-option'.")
757
758 (defvar tinydiff-:patch-reject-buffer "*tinydiff-patch-rejects"
759   "Buffer where to display rejected patch parts.")
760
761 (defvar tinydiff-:diff-source-buffer nil
762   "Private. Source buffer for diff. See `tinydiff-:source-buffer-function'.")
763 (make-variable-buffer-local 'tinydiff-:diff-source-buffer)
764
765 (defvar tinydiff-:last-data nil
766   "Private. Data storage eg in `tinydiff-minibuffer--change-diff-command'.")
767
768 (defvar tinydiff-:version-list  nil
769   "All Version for file. Updated when diff command is being run.")
770
771 (defvar tinydiff-:version-branch-list  nil
772   "Branches. Updated when diff command is being run.")
773
774 (defvar tinydiff-:diff-tmp-buffer "*tinydiff-tmp*"
775   "Temporary work buffer, patch shell results.")
776
777 (defvar tinydiff-:patch-tmp-buffer " *tinydiff-tmp-patch*"
778   "Temporary work buffer, patch.")
779
780 (defvar tinydiff-:package-exist-tinymy  (locate-library  "tinymy.el")
781   "Private. Has load path for package tinymy.el if it exist.
782 It has some functions we may use from there.")
783
784 (defvar tinydiff-:diff-buffer "*diff*"
785   "Buffer where diff is inserted.")
786
787 (defvar tinydiff-:patch-to-file nil
788   "Name of the file to patch.
789 This variable is made local to current patch/diff buffer.")
790
791 (defvar tinydiff-:patch-hunk-count nil
792   "Counter how many patch hunks hvae been applied
793 This variable is made local to current patch/diff buffer.")
794
795 ;;}}}
796 ;;{{{ setup: version
797
798 ;;; ....................................................... &v-version ...
799
800 ;;;###autoload (autoload 'tinydiff-version "tinydiff" "Display commentary." t)
801
802 (eval-and-compile
803   (ti::macrof-version-bug-report
804    "tinydiff.el"
805    "tinydiff"
806    tinydiff-:version-id
807    "$Id: tinydiff.el,v 2.83 2007/05/01 17:20:43 jaalto Exp $"
808    '(tinydiff-:version-id
809      tinydiff-:debug
810      tinydiff-:load-hook
811      tinydiff-:mode-hook
812      tinydiff-:diff-hook
813      tinydiff-:mode-name
814      tinydiff-:diff-program
815      tinydiff-:rcsdiff-program
816      tinydiff-:cvsdiff-program
817      tinydiff-:source-buffer-function
818      tinydiff-:function-name-handle-function
819      tinydiff-:register-function-name
820      tinydiff-:find-ref-function
821      tinydiff-:source-buffer-function
822      tinydiff-:mode-define-keys-hook
823      tinydiff-:register-diff
824      tinydiff-:diff-buffer
825      tinydiff-:diff-option
826      tinydiff-:diff-tmp-file
827      tinydiff-:diff-tmp-buffer
828      tinydiff-:patch-global-option
829      tinydiff-:patch-program)
830    '(tinydiff-:debug-buffer)))
831
832 ;;}}}
833 ;;{{{ code: minor mode definition
834
835 ;;; ............................................................ &mode ...
836
837 (defvar tinydiff-:minibuffer-map nil
838   "Minibuffer key map when asked for the right diff command.")
839
840 ;;;###autoload (autoload 'tinydiff-mode          "tinydiff" "" t)
841 ;;;###autoload (autoload 'turn-on-tinydiff-mode  "tinydiff" "" t)
842 ;;;###autoload (autoload 'turn-off-tinydiff-mode "tinydiff" "" t)
843 ;;;###autoload (autoload 'tinydiff-commentary    "tinydiff" "" t)
844
845 (eval-and-compile
846   (ti::macrof-minor-mode-wizard
847    "tinydiff-" " Tdi" nil " Tdiff" 'TinyDiff "tinydiff-:" ;1-6
848
849    "Diff browsing minor mode.
850
851 Defined keys:
852
853 \\{tinydiff-:mode-map}"
854
855    "Diff minor mode"
856
857    nil
858
859    "Diff browsing mode"
860
861    (list
862     tinydiff-:mode-easymenu-name
863     ["goto current point"                tinydiff-goto-kbd               t]
864     ["block forward"                     tinydiff-goto-next              t]
865     ["block forward, no update"          tinydiff-goto-next-no-update    t]
866     ["block backward"                    tinydiff-goto-prev              t]
867     ["block backward, no update"         tinydiff-goto-prev-no-update    t]
868     ["Set patch option"                  tinydiff-patch-set-option       t]
869     "----"
870     ["Parse buffer"                      tinydiff-parse-buffer           t]
871     ["Set source buffer for diff"        tinydiff-set-source-buffer      t]
872     ["Show function name"                tinydiff-show-function-name     t]
873     "----"
874     ;; ["Keyboard menu"                     tinydiff-menu-main           t]
875     ["Package version"                   tinydiff-version                t]
876     ["Package commentary"                tinydiff-commentary             t]
877     ["Mode help"                         tinydiff-mode-help              t]
878     ["Mode off"                          tinydiff-mode                   t])
879
880    (progn
881
882      ;;  I first thought to put goto line into " " or "\C-m"
883      ;;  That is: SPACE or RETURN, but later realized that they were
884      ;;  used in view-mode to scroll buffer up-down.
885      ;;
886      ;;  Eg. in my setup whenever I turn on the read-only
887      ;;  with C-x C-q it also automatically turns on view-mode for
888      ;;  easy scrolling...
889      ;;
890
891      ;;  This happens to be unsifted in HP-UX, a top-leftmost button.
892      ;;  Select something that suit you more...
893
894      (define-key   root-map "\C-m"     'tinydiff-goto-kbd)
895
896      (define-key   root-map "e"        'tinydiff-parse-buffer)
897      (define-key   root-map "!"        'tinydiff-set-source-buffer)
898      (define-key   root-map "n"        'tinydiff-goto-next)
899      (define-key   root-map "p"        'tinydiff-goto-prev)
900
901      ;;  These are borrewd from unix more(1) and less(1)
902
903      (define-key   root-map "y"        'tinydiff-goto-prev-no-update)
904      (define-key   root-map "b"        'tinydiff-goto-next-no-update)
905
906      ;;  But perhaps user feels more comfortable with these.
907
908      (define-key   root-map "P"        'tinydiff-goto-prev-no-update)
909      (define-key   root-map "B"        'tinydiff-goto-next-no-update)
910
911      (define-key   root-map "f"        'tinydiff-show-function-name)
912
913      (define-key   root-map "W"        'tinydiff-write-file)
914      (define-key   root-map "M"        'tinydiff-mime-compose)
915      (define-key   root-map "O"        'tinydiff-patch-set-option)
916
917      (define-key   root-map "-k"       'tinydiff-block-kill)
918      (define-key   root-map "-\C-?"    'tinydiff-block-kill) ;; backspace
919      (define-key   root-map "--"       'tinydiff-block-apply-patch)
920
921      (define-key map  "?"  'tinydiff-mode-help)
922      (define-key map  "Hm" 'tinydiff-mode-help)
923      (define-key map  "Hc" 'tinydiff-commentary)
924      (define-key map  "Hv" 'tinydiff-version)
925
926      (if (ti::emacs-p)
927          (define-key   root-map [mouse-2]  'tinydiff-goto-mouse)
928        (define-key   root-map [(button2)]  'tinydiff-goto-mouse)))))
929
930 ;;; ----------------------------------------------------------------------
931 ;;;
932 (defun tinydiff-install (&optional uninstall)
933   "Install TinyDiff package, or optionally UNINSTALL.
934 A .diff or .patch file invokes `tinydiff-mode' in `automode-alist'."
935   (interactive "P")
936   (cond
937    (uninstall
938     (ti::assoc-replace-maybe-add
939      'auto-mode-alist tinydiff-:auto-mode-alist 'remove))
940    (t
941     (ti::assoc-replace-maybe-add
942      'auto-mode-alist tinydiff-:auto-mode-alist)
943     (if (interactive-p)
944         (message "TinyDiff installed")))))
945
946 ;;}}}
947 ;;{{{ code: keymap
948
949 ;;; ----------------------------------------------------------------------
950 ;;;
951 (defun tinydiff-mode-define-keys-minibuffer-default ()
952   "This function defines some extra bindings to minibuffer.
953 Eg. TAB that completes current filename."
954   (setq tinydiff-:minibuffer-map (copy-keymap minibuffer-local-map))
955   ;;
956   ;;  Here we define nice tab filename completion inside minibuffer
957   ;;  This may be superflous, but what the heck :-)
958   ;;
959   (define-key tinydiff-:minibuffer-map "\t"
960     'tinydiff-minibuffer--complete-filename)
961   (define-key tinydiff-:minibuffer-map [(kp-tab)]
962     'tinydiff-minibuffer--complete-filename)
963   ;;
964   ;;  More command line handling
965   ;;
966   ;;  There is no logic in naming the key settings other than
967   ;;  simple rule: they must me as lower left as possible
968   ;;  to be reached quickly.
969   ;;
970   ;;  You won't need any of these in echo area prompt
971   ;;
972   (define-key tinydiff-:minibuffer-map "\C-z"
973     'tinydiff-minibuffer--change-diff-command)
974   (define-key tinydiff-:minibuffer-map "\C-r"
975     'tinydiff-minibuffer--rev-add-command)
976   (define-key tinydiff-:minibuffer-map "\C-c"
977     'tinydiff-minibuffer--insert-file-autosave)
978   (define-key tinydiff-:minibuffer-map "\C-v"
979     'tinydiff-minibuffer--insert-file-backup)
980   (define-key tinydiff-:minibuffer-map "\C-s"
981     'tinydiff-minibuffer--toggle-diff-type)
982   (define-key tinydiff-:minibuffer-map "\C-f"
983     'tinydiff-minibuffer--user-option)
984   (define-key tinydiff-:minibuffer-map "\C-p"
985     'tinydiff-minibuffer--insert-previous-word)
986   (define-key tinydiff-:minibuffer-map "?"
987     'tinydiff-minibuffer--minibuffer-help))
988
989 ;;}}}
990 ;;{{{ code: misc
991
992 ;;; ............................................................ &misc ...
993
994 ;;;###autoload (autoload 'tinydiff-debug-toggle "tinydiff" "" t)
995
996 (eval-and-compile (ti::macrof-debug-standard "tinydiff" "-:"))
997
998 ;;; ----------------------------------------------------------------------
999 ;;;
1000 (defsubst tinydiff-kill-revision-list ()
1001   "Deletes private version lists."
1002   (setq tinydiff-:version-list nil
1003         tinydiff-:version-branch-list nil))
1004
1005 ;;; ----------------------------------------------------------------------
1006 ;;;
1007 (defun tinydiff-splice-command  (string)
1008   "Splice off directory from string and return '(DIR CMD REST)"
1009   (when (string-match
1010          (concat
1011           "cd[ \t]+\\([^;]+\\);"    ;; dir
1012           "[ \t]*\\([^ \t\r\n]+\\)" ;; cmd
1013           "[ \t]+\\(.+\\)")         ;; rest
1014          string)
1015     (list
1016      ;;  Delete trailing whitespace
1017      (replace-regexp-in-string "[ \t\r\n]+$" ""
1018                                (match-string 1 string))
1019      (match-string 2 string)
1020      (match-string 3 string))))
1021
1022 ;;; ----------------------------------------------------------------------
1023 ;;;
1024 (defun tinydiff-shell-command (cmd buffer)
1025   "Run CMD and output to BUFFER.
1026 The passed CMD must be in the format:
1027
1028    cd DIRECTORY; BINARY option option option ...."
1029   (let* ((fid                "tinydiff-shell-command"))
1030     (multiple-value-bind (dir cmd rest)
1031         (tinydiff-splice-command cmd)
1032       (when (or (not dir)
1033                 (not (file-directory-p dir)))
1034         (error "Tinydiff: No directory found `%s'" dir))
1035       (let ((default-directory (file-name-as-directory dir)))
1036         (tinydiff-debug fid
1037                         'default-directory default-directory
1038                         'cmd    cmd
1039                         'rest   rest
1040                         'buffer buffer)
1041         (setq buffer (get-buffer-create buffer))
1042         (let* (args
1043                (rargs (reverse (split-string rest)))
1044                (file2 (expand-file-name (pop rargs)))
1045                (file1 (expand-file-name (pop rargs))))
1046           (push file1 rargs)
1047           (push file2 rargs)
1048           (setq args (reverse rargs))
1049           (tinydiff-debug fid 'CMD cmd 'FILE1 file1 'FILE2 file2)
1050           (apply 'call-process
1051                  cmd
1052                  nil
1053                  buffer
1054                  nil
1055                  args))
1056         (when (or (null buffer)
1057                   (null (get-buffer buffer))
1058                   (not (buffer-live-p buffer)))
1059           (error "TinyDiff: Shell dind't return results [ %s ]" cmd))))))
1060
1061 ;;; ----------------------------------------------------------------------
1062 ;;;
1063 (defun tinydiff-update-revision-list (file &optional version)
1064   "Read all revision numbers for FILE starting from VERSION."
1065   (let* ((fid   "tinydiff-update-revision-list: ")
1066          (ver   (or version "1.1"))
1067          (dots  (count-char-in-string ?. ver)))
1068     (unless fid ;; XEmacs byte compiler silencer
1069       (setq fid nil))
1070     (when (null tinydiff-:version-list)
1071       (tinydiff-debug fid ver dots)
1072       (setq tinydiff-:version-list  (ti::vc-rcs-all-versions file)))
1073     (when (or (null tinydiff-:version-branch-list)
1074               ;; Chck that there is right brach list
1075               ;; "1.1"  -- "1.1.1.1" ?
1076               (not (eq dots
1077                        (count-char-in-string
1078                         ?.
1079                         (car tinydiff-:version-branch-list)))))
1080       (setq tinydiff-:version-branch-list
1081             (ti::vc-rcs-get-all-branches
1082              ver tinydiff-:version-list))
1083       (tinydiff-debug fid "ver" version))
1084     tinydiff-:version-list))
1085
1086 ;;; ----------------------------------------------------------------------
1087 ;;; - Doing the version getting is SLOW with lisp. Use some external
1088 ;;;   shell program to do the job for you. It is MUCH quicker.
1089 ;;;
1090 (defun tinydiff-rcs-diff-between-versions (dir file)
1091   "Return VC diff command that diffs current version and previous version.
1092 DIR and FILE is passed to function."
1093   (interactive)
1094   (let* ((ver      (ti::vc-rcs-buffer-version))
1095          v-list
1096          ret
1097          prev)
1098     (if (null ver)
1099         (error "TinyDiff: Cannot find version number."))
1100     (setq v-list   (tinydiff-update-revision-list (concat dir file)))
1101     (setq prev     (ti::vc-rcs-previous-version ver v-list))
1102     (if (null prev)
1103         (error "TinyDiff: Cannot find previous version number."))
1104     (setq ret
1105           (format "cd %s; %s -r%s -r%s %s %s "
1106                   dir
1107                   (if (ti::vc-rcs-file-exists-p file)
1108                       tinydiff-:rcsdiff-program
1109                     tinydiff-:cvsdiff-program)
1110                   prev ver
1111                   (or (eval tinydiff-:diff-option)
1112                       "")
1113                   file))
1114     ret))
1115
1116 ;;; ----------------------------------------------------------------------
1117 ;;;
1118 (defsubst tinydiff-source ()
1119   "Return source buffer of diff."
1120   (if (and tinydiff-:diff-source-buffer
1121            (buffer-live-p (get-buffer tinydiff-:diff-source-buffer)))
1122       tinydiff-:diff-source-buffer
1123     (setq tinydiff-:diff-source-buffer
1124           (funcall tinydiff-:source-buffer-function))))
1125
1126 ;;; ----------------------------------------------------------------------
1127 ;;;
1128 (defun tinydiff-set-source-buffer (buffer)
1129   "Set source BUFFER for current diff."
1130   (interactive "bSource buffer: ")
1131   (if (not (get-buffer buffer))
1132       (error "TinyDiff: Buffer does not exist")
1133     (setq tinydiff-:diff-source-buffer buffer)))
1134
1135 ;;; ----------------------------------------------------------------------
1136 ;;;
1137 (defun tinydiff-function-name-store (line)
1138   "Store found function name from LINE into `tinydiff-:register-function-name'.
1139 Currently works well only Lisp functions."
1140   (let* ((reg  tinydiff-:register-function-name)
1141          (mode (symbol-name major-mode))
1142          txt)
1143     (when (string-match "lisp" mode)
1144       ;;  try to extract symbol-name
1145       ;;  see tinydiff-get-function-name, because point sits on DEFUN already
1146       ;;  and the ti::buffer-defun-function-name searches it again..
1147       (forward-line 1))
1148     (setq txt (ti::buffer-defun-function-name))
1149     (when reg
1150       (if txt
1151           (set-register reg txt)
1152         ;; empty it
1153         (set-register reg "")))))
1154
1155 ;;; ----------------------------------------------------------------------
1156 ;;;
1157 (defun tinydiff-get-function-name (buffer line)
1158   "Return function or variable name at current point.
1159 Switches to BUFFER and go to LINE and calls `beginning-of-defun'"
1160   (let* ((func          tinydiff-:function-name-handle-function)
1161          (find-func     tinydiff-:find-ref-function)
1162          (max-leap      100)            ;lines
1163          point
1164          ret)
1165     (with-current-buffer buffer
1166       (ti::widen-safe
1167         (goto-line line)
1168         (ignore-errors
1169           (setq point (point))
1170           (funcall find-func)           ;exit , if error generated
1171           ;;  Let's be little intelligent
1172           (if (< (count-lines (point) point) max-leap)
1173               ;;  Okay, we believe that function was found
1174               (setq ret (ti::read-current-line))
1175             ;; Can't be that far away... reset the pointer
1176             (goto-char point))
1177           (if func
1178               (setq ret (funcall func (point))))) ;; ignore-errors
1179         ret))))
1180
1181 ;;; ----------------------------------------------------------------------
1182 ;;;
1183 (defun tinydiff-show-function-name (&rest args)
1184   "Show possible function name in mode line on the current diff point. ARGS."
1185   (interactive)
1186   (let* ((line          (tinydiff-get-line-number))
1187          (buffer        (tinydiff-source))
1188          desc)
1189     (cond
1190      ((not (and line buffer))
1191       (message "Tinydiff: Sorry, No line and buffer info."))
1192      (t
1193       (setq desc  (tinydiff-get-function-name buffer line))
1194       (if (stringp desc)
1195           ;;  Moving mouse wipes this away...
1196           (message desc)
1197         (message "Tinydiff: Can't find reference."))))))
1198
1199 ;;; ----------------------------------------------------------------------
1200 ;;;
1201 (defun tinydiff-turn-on-view-mode ()
1202   "Turn on view mode and read-only status."
1203   (view-mode 1)
1204   (unless buffer-read-only
1205     (setq buffer-read-only t)))
1206
1207 ;;}}}
1208 ;;{{{ code: command line
1209
1210 ;;; ..................................................... &commandLine ...
1211
1212 ;;; ----------------------------------------------------------------------
1213 ;;;
1214 (defun tinydiff-minibuffer--replace-text (beg end text)
1215   "Command line. Replace region between BEG and END with TEXT."
1216   (ti::save-line-column-macro
1217       nil nil
1218     (delete-region beg end)
1219     (goto-char beg)
1220     (insert text)))
1221
1222 ;;; ----------------------------------------------------------------------
1223 ;;;
1224 (defun tinydiff-minibuffer--read-revision ()
1225   "Command line. Read revision.
1226
1227 Return:
1228  (nbr . pos)    ,where pos is point at line.
1229  nil"
1230   (interactive)
1231   (let* ((fid "tinydiff-minibuffer--read-revision:")
1232          nbr
1233          pos)
1234     (unless fid ;; XEmacs byte compiler silencer
1235       (setq fid nil))
1236     (save-excursion
1237       ;;  a) in the top of REV number
1238       ;;  b) pick REV forward
1239       (or (eq 0 (skip-chars-backward "0-9."))
1240           (eq 0 (skip-chars-forward "^0-9.")))
1241       ;; cd /users/foo/elisp/; rcsdiff -c -r1.25 -r1.8 tinydiff.el
1242       (cond
1243        ((setq nbr (ti::buffer-match "[0-9.]+" 0))
1244         (setq pos (match-beginning 0)))
1245        (t
1246         (beginning-of-line)
1247         (setq nbr (ti::buffer-match ".*-[rul]\\([0-9.]+\\)" 1))
1248         (setq pos (match-beginning 1)))))
1249     (tinydiff-debug fid "col"(current-column) "nbr" nbr "pos" pos )
1250     (if nbr
1251         (cons nbr pos))))
1252
1253 ;;; ----------------------------------------------------------------------
1254 ;;;
1255 (defun tinydiff-minibuffer--directory  ()
1256   "Return directory name from 'cd' command."
1257   (save-excursion
1258     (beginning-of-line)
1259     (ti::buffer-match "cd +\\([^;]+\\)" 1)))
1260
1261 ;;; ----------------------------------------------------------------------
1262 ;;;
1263 (defun tinydiff-minibuffer--read-rcs-file-name (&optional line or-diff-name)
1264   "Read RCS filename from LINE.
1265 If OR-DIFF-NAME is non-nil, look for 'diff' command instead."
1266   (let* ((fid       "tinydiff-minibuffer--read-rcs-file-name:")
1267          (line      (or line (ti::read-current-line)))
1268          (rcsdiff   tinydiff-:rcsdiff-program)
1269          (diff      tinydiff-:diff-program)
1270          (re        (concat
1271                      "cd[ \t]+\\([^;]+\\);[ \t]*"
1272                      (if or-diff-name diff rcsdiff)
1273                      ".* \\([^ \t]+\\)"))
1274          dir
1275          file
1276          ret)
1277     (unless fid ;; XEmacs byte compiler silencer
1278       (setq fid nil))
1279     ;;   The directory name is gotten after 'cd' command
1280     (tinydiff-debug fid line re)
1281     (when (and (string-match re line)
1282                (setq dir  (match-string 1 line))
1283                (setq file (match-string 2 line)))
1284       (setq ret (concat dir file)))
1285     ret))
1286
1287 ;;; ----------------------------------------------------------------------
1288 ;;; Don't ask: This function should be rewritten someday, someday...
1289 ;;;
1290 (defun tinydiff-minibuffer--rev-add-command  ()
1291   "Adding two -rX.x string to the command line.
1292 This is only done if there is rcsdiff command and less the 2 -rX.x
1293 switches
1294
1295 Eg.
1296         rcsdiff -r1.3 file.cc
1297 -->     rcsdiff -r1.2 -r1.3 file.cc
1298
1299         rcsdiff -r1.1 -r1.1 file.cc
1300 -->     Do nothing, since there is already two -r switches."
1301   (interactive)
1302   (let* ((fid       "tinydiff-minibuffer--rev-add-command:")
1303          (line      (ti::remove-properties (ti::read-current-line)))
1304          ;; (rcsdiff   tinydiff-:rcsdiff-program)
1305          (re        (concat "^.*;[ \t]*"
1306                             "\\("
1307                             tinydiff-:rcsdiff-program
1308                             "\\|cvs[ \t]+diff"
1309                             "\\|"
1310                             tinydiff-:cvsdiff-program
1311                             "\\)"
1312                             "\\(.*\\)"))
1313          (i         0)
1314          prev-list elt
1315          r-list
1316          args
1317          list
1318          revision
1319          nbr
1320          tmp
1321          copy)
1322     (unless fid ;; XEmacs byte compiler silencer
1323       (setq fid nil))
1324     (tinydiff-debug "IN:\n\n" fid line re)
1325     (when (string-match re line)
1326       (setq args (match-string 1 line)
1327             list (split-string line)
1328             copy (copy-list list))
1329       (tinydiff-debug fid "ARGS" args "LIST" list)
1330       (setq args list)
1331       (while (setq elt (pop copy))
1332         (if (not (string-match "^-r" elt)) ;Find this
1333             (ti::nconc prev-list elt)
1334           ;;   Here we make list
1335           ;;   '((NTH-POS CAR-LIST CDR-LIST) (N CA CD) ..)
1336           ;;   -- all-the-elements before
1337           ;;   -- and the rest
1338           (push (list i
1339                       (copy-list prev-list)
1340                       (nthcdr i list))
1341                 r-list)
1342           (tinydiff-debug fid "R-LIST>>" r-list))
1343         (incf  i))
1344       (tinydiff-debug fid "r-list NOW:" (length r-list)  r-list)
1345       (cond
1346        ((eq (length r-list) 0)          ;no -r --> add it
1347         ;;  looks complicated? The idea is to
1348         ;;  -- get last element, but only if it's NOT an option
1349         ;;  -- get all, but not the last element.
1350         (setq r-list (nreverse args))
1351         (setq elt (car r-list))         ;This is the added -rN.N
1352         (tinydiff-debug fid "ELT" elt r-list)
1353         (if (not (string-match "^-" elt))
1354             (setq args (reverse (cdr (reverse args))))
1355           (setq elt nil))               ;it was an option
1356         (ti::nconc args "-r" )
1357         (if elt                         ;file name set ?
1358             (ti::nconc args elt )))
1359        ((eq (length r-list) 2)          ;-r -r  remove first
1360         (setq elt  (car r-list))
1361         (setq args (nth 2 elt))         ;Revision and rest of the args
1362         (setq args                      ;car-list to the beginning
1363               (reverse (union (reverse (nth 1 elt)) args))))
1364        ((eq (length r-list) 1)          ;Only one -r
1365         (setq elt (car r-list))
1366         (setq args (nth 2 elt))         ;Revision and rest of the args
1367         ;; elt = (POS LIST-UNTIL-R LIST-INClUDING-R-AND-AFTER)
1368         (setq tmp (car (nth 2 elt))              ;copy the -rN.N
1369               i   (count-char-in-string ?. tmp)) ;; How many dots?
1370         (tinydiff-debug fid "1>" elt "ARGS" args "I=" i "TMP" tmp)
1371         ;; ........................................... Change revision ...
1372         (when (string-match "\\.\\([0-9]+\\)$" tmp)
1373           (setq revision (string-to-int (match-string 1 tmp))
1374                 nbr      (string-to-int
1375                           (ti::string-right (match-string 1 tmp) 1)))
1376           (tinydiff-debug fid "1>REV"   revision "last NBR"  nbr)
1377           (cond
1378            ((or (eq i 1)                ; Like 2.2, one dot
1379                 (and (> i 1)          ; Can't make 1.1.1.1 --> 1.1.1.0
1380                      (> nbr 1)))
1381             ;;  2.2 --> 2.1
1382             (setq revision (1- revision))
1383             (setq tmp (ti::replace-match 1 (int-to-string revision) tmp))
1384             (tinydiff-debug fid "1>CHANGED" revision tmp))
1385            ((and (> i 1)
1386                  (= nbr 1))
1387             ;;   Remove last 1.1.N.N --> 1.1
1388             (if (string-match "\\(.*\\)\\.[0-9]+\\.[0-9]+$" tmp)
1389                 (setq tmp (match-string 1 tmp)))
1390             (tinydiff-debug fid "2>CHANGED" tmp))))
1391         (push tmp args)                 ;--> '(-rN.N -rN.N FILE)
1392         (setq args                      ;car-list to the beginning
1393               (reverse (union (reverse (nth 1 elt)) args)))))
1394       (setq args (ti::list-to-string args))
1395       (tinydiff-debug fid "args" args  )
1396       (delete-region (line-beginning-position) (line-end-position))
1397       (insert args))
1398     (end-of-line)
1399     ;;  Go after last revision number, so that user can change it easily
1400     (beginning-of-line)
1401     (if (re-search-forward "-r" nil t)
1402         (skip-chars-forward "^ \t")
1403       (end-of-line))))
1404
1405 ;;; ----------------------------------------------------------------------
1406 ;;;
1407 (defun tinydiff-minibuffer--toggle-diff-type  ()
1408   "Toggle -c context or -u unified diff option in command line."
1409   (interactive)
1410   (let* ((line (ti::remove-properties (ti::read-current-line)))
1411          ret)
1412     (cond
1413      ((string-match "^\\(.+ -[^ \t]*\\)c\\(.+\\)" line)
1414       (setq ret (format "%su%s"
1415                         (match-string 1 line)
1416                         (match-string 2 line))))
1417      ((string-match "^\\(.+ -[^ \t]*\\)u\\(.+\\)" line)
1418       (setq ret (format "%sc%s"
1419                         (match-string 1 line)
1420                         (match-string 2 line)))))
1421     (when ret
1422       (beginning-of-line)
1423       (kill-line)
1424       (insert ret)
1425       ;; Preserve approx point.
1426       (goto-char (min (point-max) (point))))))
1427
1428 ;;; ----------------------------------------------------------------------
1429 ;;;
1430 (defun tinydiff-minibuffer--user-option  ()
1431   "Add or remove tinydiff-:cl-user-option from the line."
1432   (interactive)
1433   (let* ((line      (ti::remove-properties (ti::read-current-line)))
1434          (rcsdiff   tinydiff-:rcsdiff-program)
1435          (diff      tinydiff-:diff-program)
1436          (opt       tinydiff-:cl-user-option)
1437          ret)
1438     (when (not (ti::nil-p opt))
1439       (cond
1440        ((string-match (regexp-quote opt) line)
1441         (setq ret (ti::replace-match 0 "" line)))
1442
1443        ((or (string-match (concat rcsdiff "\\( +\\)") line)
1444             (string-match (concat diff "\\( +\\)") line))
1445         (setq ret (ti::replace-match 1 (concat " " opt " ") line)))))
1446     (when ret
1447       (beginning-of-line) (kill-line)
1448       (insert ret))))
1449
1450 ;;; ----------------------------------------------------------------------
1451 ;;;
1452 (defun tinydiff-minibuffer--change-diff-command  ()
1453   "Change diff command in minibuffer."
1454   (interactive)
1455   (let* ((fid      "tinydiff-minibuffer--change-diff-command: ")
1456          (rcsdiff  tinydiff-:rcsdiff-program)
1457          (diff     tinydiff-:diff-program)
1458          word
1459          line
1460          list
1461          done)
1462     (unless fid ;; XEmacs byte compiler silencer
1463       (setq fid nil))
1464     (tinydiff-debug fid "in")
1465     (save-excursion
1466       (beginning-of-line)
1467       (cond
1468        ;;  Make sure it's our command.
1469        ((and (re-search-forward "; *" nil t)
1470              (setq word (ti::buffer-read-word)))
1471         (setq line (buffer-substring-no-properties
1472                     (point) (line-end-position)))
1473         (setq list (split-string line))
1474         (tinydiff-debug fid
1475                         "\n\nLINE " line "\n"
1476                         "LIST " list "\n"
1477                         "1 string= word rcsdiff " word rcsdiff "\n"
1478                         "2 string= word diff "    word diff "\n"
1479                         "2 tinydiff-:last-data " tinydiff-:last-data "\n"
1480                         "3 " (and (string-match word diff)
1481                                   list
1482                                   (not (stringp tinydiff-:last-data))))
1483
1484         (cond
1485          ;; ................................................... case-1 ...
1486          ((or (string= word rcsdiff)
1487               (string= word "cvs"))
1488           (tinydiff-debug fid "rcsdiff INPUT:" list)
1489           (setq tinydiff-:last-data line)
1490           ;;  Remove some elements from list
1491           (setq list (ti::list-find
1492                       list
1493                       (concat "^-r\\|" rcsdiff "\\|cvs\\|diff" )
1494                       (function
1495                        (lambda (arg elt)
1496                          (not (string-match arg elt))))
1497                       'all))
1498           (tinydiff-debug fid "1 FILTERED:" list)
1499           (kill-line)
1500           (insert  diff " " (ti::list-to-string list))
1501           (setq done t))
1502          ;; ................................................... case-2 ...
1503          ;; Did the last command have the same filename ? --> if not
1504          ;; then we cannot use C-z
1505          ((and (string-match word diff)
1506                list
1507                (stringp tinydiff-:last-data)
1508                (ti::string-match-case
1509                 (regexp-quote (nth (1- (length list)) list) )
1510                 tinydiff-:last-data))
1511           (kill-line)
1512           (insert tinydiff-:last-data)
1513           (setq done t))
1514          ;; ................................................... case-3 ...
1515          ;; (3) diff --> rcs diff thing
1516          ((and (string-match word diff)
1517                list
1518                (not (stringp tinydiff-:last-data)))
1519           (kill-line)
1520           (insert (concat
1521                    rcsdiff
1522                    " -r "
1523                    (ti::list-to-string (cdr list))))
1524           (setq done t))))))
1525     (when done
1526       ;;  Move after the 'cd' and 'diff commands to add/change options easily
1527       (beginning-of-line)
1528       (re-search-forward "; *" nil t)
1529       (forward-word 2))))
1530
1531 ;;; ----------------------------------------------------------------------
1532 ;;;
1533 (defun tinydiff-minibuffer--sweep-unix () ;; win32
1534   "Change all backslashes to forward slashes."
1535   (let* ((line (ti::remove-properties (ti::read-current-line))))
1536     (when (string-match "[\\]" line)
1537       (setq line (ti::file-name-forward-slashes line))
1538       (beginning-of-line) (kill-line)
1539       (insert line)
1540       ;; Preserve approx point.
1541       (goto-char (min (point-max) (point))))))
1542
1543 ;;; ----------------------------------------------------------------------
1544 ;;;
1545 (defun tinydiff-minibuffer--complete-filename ()
1546   "Complete filename."
1547   (interactive)
1548   (tinydiff-minibuffer--sweep-unix)
1549   (let* ((fid   "tinydiff-minibuffer--complete-filename")
1550          (word  (save-excursion
1551                   (forward-char -1)
1552                   (ti::buffer-read-space-word)))
1553          (dir   default-directory))
1554     (unless fid ;; XEmacs byte compiler silencer
1555       (setq fid nil))
1556     (tinydiff-debug fid "BEGIN" word dir)
1557     (unless (ti::nil-p word)
1558       ;;   The directory name we get from the 'CD' prompt if the
1559       ;;   filename lacks dir part.
1560       (let ((cddir (tinydiff-minibuffer--directory)))
1561         (cond
1562          ((not (string-match "/" word))
1563           (setq dir cddir))
1564          ((string-match "^\\.\\." word) ;; relative path
1565           (setq dir (expand-file-name (concat
1566                                        (file-name-as-directory cddir)
1567                                        (file-name-directory word)))
1568                 word (file-name-nondirectory word)))))
1569       (tinydiff-debug fid "END" word dir)
1570       (let ((default-directory (or dir
1571                                    default-directory)))
1572         (ti::file-complete-file-name-word word)))))
1573
1574 ;;; ----------------------------------------------------------------------
1575 ;;;
1576 (defun tinydiff-minibuffer--insert-file-autosave  (&optional backup)
1577   "Insert auto-save filename into current point if it exists.
1578 Prefix arg says to insert BACKUP filename instead."
1579   (interactive)
1580   (let* ((line      (ti::read-current-line))
1581          (rcsdiff   tinydiff-:rcsdiff-program)
1582          (fid       "tinydiff-minibuffer--insert-file-autosave: ") ;function id
1583          file
1584          file2
1585          stat)
1586     (unless fid ;; XEmacs byte compiler silencer
1587       (setq fid nil))
1588     (setq file (tinydiff-minibuffer--read-rcs-file-name line 'diff))
1589     (tinydiff-debug fid "arg" backup "file" file)
1590     (cond
1591      ((string-match rcsdiff line)
1592       (message
1593        (substitute-command-keys
1594         (concat
1595          "Tinydiff: Don't use rcsdiff command; Change command with "
1596          "\\[tinydiff-minibuffer--change-diff-command] "))))
1597      ((not file)
1598       (message "Tinydiff: Can't read file from prompt. Include 'cd'"))
1599      ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. . process file ..
1600      (t
1601       (cond
1602        (backup
1603         (setq file2 (make-backup-file-name file))
1604         (setq stat (file-exists-p file2)))
1605        (t
1606         ;;  The autosave fiel must be surrounded with '' because
1607         ;;  # is shell comment
1608         (setq file2 (format
1609                      "#%s#"
1610                      (file-name-nondirectory file)))
1611         (setq stat
1612               (file-exists-p (concat (file-name-directory file) file2)))))
1613       (if (null stat)
1614           (message "Tinydiff: Not found %s " file2 )
1615         (setq file2 (concat "'" file2 "'"))
1616         ;; put spaces around filename if needed
1617         (setq file2
1618               (concat
1619                (if (ti::space-p (preceding-char))
1620                    ""
1621                  " ")
1622                file2
1623                (if (ti::space-p (following-char))
1624                    ""
1625                  " ")))
1626         (insert file2))))))
1627
1628 ;;; ----------------------------------------------------------------------
1629 ;;;
1630 (defun tinydiff-minibuffer--insert-file-backup  ()
1631   "Command line. Inset backup filename if it exists."
1632   (interactive)
1633   (tinydiff-minibuffer--insert-file-autosave 'backup))
1634
1635 ;;; ----------------------------------------------------------------------
1636 ;;;
1637 (defun tinydiff-minibuffer--minibuffer-help ()
1638   "Show brief help."
1639   (interactive)
1640   (with-output-to-temp-buffer "*Help*"
1641     (princ
1642      (substitute-command-keys
1643       "TinyDiff minibuffer command line keys\n\n \\{tinydiff-:minibuffer-map}")))
1644   (with-current-buffer "*Help*"
1645     (ti::pmin)
1646     (forward-line 4)
1647     (delete-non-matching-lines "tinydiff-minibuffer-")))
1648
1649 ;;; ----------------------------------------------------------------------
1650 ;;;
1651 (defun tinydiff-minibuffer--insert-previous-word ()
1652   "Insert previous word: like filename or rcs switch."
1653   (interactive)
1654   (let* (
1655 ;;;         (char (if (line-end-position)
1656 ;;;                   (preceding-char)
1657 ;;;                 (or (following-char)
1658 ;;;                     (preceding-char))))
1659
1660          (word (save-excursion
1661                  (when (re-search-backward "[^ \t]" nil t)
1662                    (ti::buffer-read-space-word)))))
1663     (when word
1664       (insert word))))
1665
1666 ;;}}}
1667
1668 ;;{{{ code: diff type, patch
1669
1670 ;;; ........................................................... &patch ...
1671
1672 ;;; ----------------------------------------------------------------------
1673 ;;;
1674 (defsubst tinydiff-patch-get-dir-from-cmd  (cmd)
1675   "Return FILE from CMD."
1676   (when cmd
1677     (setq cmd (ti::string-match "cd +\\([^ \t;]+\\)" 1 cmd))
1678     (ti::file-make-path cmd)))
1679
1680 ;;; ----------------------------------------------------------------------
1681 ;;;
1682 (defsubst tinydiff-patch-get-file-from-cmd  (cmd)
1683   "Return FILE from CMD."
1684   (when cmd
1685     (setq cmd (ti::string-match
1686                (format "cd .*%s +\\([^ \t;]+\\)" tinydiff-:patch-program)
1687                1
1688                cmd))))
1689
1690 ;;; ----------------------------------------------------------------------
1691 ;;;
1692 (defun tinydiff-patch-minibuffer-cleanup ()
1693   "Check if we can use the patch as is, PGP change must be restored."
1694   (save-excursion
1695     ;; PGP breaks the dashed lines:
1696     ;; - --- 1.2.1.1  1996/06/11 11:36:03
1697     ;; - --- 212,219 ----
1698     ;;
1699     ;; Correct the lines back.
1700     (let (done)
1701       (ti::pmin)
1702       (while (re-search-forward "^\\(- \\)\\(--- [0-9]+.*\\)" nil t)
1703         (message "Tinydiff: Correcting patch: PGP's broken lines...")
1704         (setq done t)
1705         (ti::replace-match 1 (match-string 2)))
1706       (ti::pmin)
1707       (when (re-search-forward "^--+BEGIN +PGP +SIGNATURE" nil t)
1708         (message "Tinydiff: Correcting patch: removing PGP tags...")
1709         (setq done t)
1710         (delete-region (line-beginning-position) (point-max)))
1711       (when done
1712         (message "Tinydiff: Correcting patch: done.")))))
1713
1714 ;;; ----------------------------------------------------------------------
1715 ;;;
1716 (defun tinydiff-get-file-name (&optional arg)
1717   "Try to get file name for the diff.
1718 Optionally read line \"RCS file: xxx.el\"
1719
1720 ARG says which to look:
1721
1722   1 pick line *** file.xx
1723   2 pick line --- file.xx
1724   3 pick line +++ file.xx      GNU unified diff.
1725
1726 Return list:
1727   '(file (type pos))
1728   nil"
1729   (let* ((stat (ti::buffer-diff-type-p))
1730          (re1   "^[ \t]*[*][*][*] +\\([^ \t\n]+\\)")
1731          (re2   "^[ \t]*--- +\\([^ \t\n]+\\)")
1732          (re3   "^[ \t]*[+][+][+] +\\([^ \t\n]+\\)")
1733          (re    re1)                    ;default
1734          list
1735          file)
1736     (when stat                          ;only if diff found
1737       (if arg                           ;set search regexp
1738           (cond
1739            ((eq 1 arg) (setq re re1))
1740            ((eq 2 arg) (setq re re2))
1741            ((eq 3 arg) (setq re re3))))
1742
1743       (save-excursion
1744         (goto-char (cdr stat))
1745         (cond
1746          ((save-excursion
1747             ;;  Ignore path
1748             (and (or (re-search-backward
1749                       "RCS +file: +\\([^/]+\\),v" nil t)
1750                      (re-search-backward
1751                       "RCS +file: +.*/\\\(.*\\),v" nil t))
1752                  (setq file (match-string 1)))))
1753          ((save-excursion
1754             (when (re-search-backward "diff -c.*" nil t)
1755               (setq list (split-string (ti::read-current-line)))
1756               ;; get last element
1757               (setq file (nth (length list) list)))))
1758          ((save-excursion
1759             (when (or (re-search-backward re nil t)
1760                       (re-search-forward  re nil t))
1761               (setq file (match-string 1))))))))
1762
1763     (if file                            ;pick the return values
1764         (list (ti::remove-properties file) stat)
1765       nil)))
1766
1767 ;;; ----------------------------------------------------------------------
1768 ;;;
1769 (defun tinydiff-patch-check-if-load  (file buffer &optional flag)
1770   "See if we want to load FILE by looking results in BUFFER.
1771 If file was RCS controlled and not in Emacs, ask to load it.
1772 If file is active in Emacs ad to do `load-file' to refresh current Emacs.
1773
1774 Input:
1775
1776   FILE      File name
1777   BUFFER    The patch(1) command output buffer
1778   FLAG      If 'hunk, this is only partial diff."
1779   (let* (case-fold-search               ;case sensitive matches
1780          (fid       "tinydiff-patch-check-if-load")
1781          (fbuffer   (find-buffer-visiting file))
1782          (modified  (if fbuffer  (ti::buffer-modified-p fbuffer)))
1783          (was-rcs   (ti::re-search-check "RCS/"))
1784
1785          ;; Is package active in Emacs? Was patched file .el?
1786          el-file
1787          sym
1788          feature
1789          no-ask)
1790     (unless fid ;; XEmacs byte compiler silencer
1791       (setq fid nil))
1792     (and file
1793          (setq el-file  (ti::string-match "\\(.*\\)\\.el$" 1 file))
1794          (setq sym      (intern-soft (file-name-nondirectory el-file)))
1795          (setq feature  (featurep sym)))
1796
1797     (tinydiff-debug fid "in:" file buffer el-file sym feature
1798                     "FBUFFER" fbuffer "MODIFIED" modified
1799                     "WAS-RCS" was-rcs)
1800     ;;  Be sure about success
1801     (when (with-current-buffer buffer (ti::re-search-check "[Hh]unk.*succeed"))
1802       (cond
1803        ((and fbuffer
1804              (not modified)
1805              ;;  If autorevert.el is turned on with
1806              ;;  M-x global-auto-revert-mode, do not ask from user
1807              (or (not (boundp 'global-auto-revert-mode))
1808                  (null (symbol-value 'global-auto-revert-mode)))
1809              (or (y-or-n-p "Buffer for the file exist, revert? ")
1810                  (progn
1811                    (setq no-ask t)
1812                    ;; Set flag and Stop case
1813                    nil)))
1814         (pop-to-buffer fbuffer)
1815         (revert-buffer))
1816        ((and fbuffer modified)
1817         (display-buffer buffer)
1818         (message "Tinydiff: file's buffer in Emacs is modified.."))
1819        ((and was-rcs
1820              (null no-ask)              ;Maybe set in 1st cond case
1821              (y-or-n-p (format "RCS file patched, find-file %s " file "? ")))
1822         (find-file file))
1823        ((and feature
1824              (not (eq flag 'hunk))
1825              (y-or-n-p (format "%s is running in your Emacs, reload it?  "
1826                                file)))
1827         (load file)
1828         (message
1829          "Tinydiff: %s reloaded. Your Emacs is now running the latest patch."
1830          file))))))
1831
1832 ;;; ----------------------------------------------------------------------
1833 ;;;
1834 (defun tinydiff-patch-check-failure (&optional buffer)
1835   "Check patch failure messages from BUFFER.
1836 Return:
1837   filename      rejection file if failure happened.
1838   '(\"\")       some unknown failure happened; rejection file not available
1839                 String holds matched failure condition.
1840   nil           Patch succeeded ok."
1841   (interactive)
1842   (let* ((fid "tinydiff-patch-check-failure")
1843          (re  "failed.*saving +rejects +to +\\([^ \t\n]+\\)")
1844          (case-fold-search t)
1845          file)
1846     (unless fid ;; XEmacs byte compiler silencer
1847       (setq fid nil))
1848     (with-current-buffer (or buffer (current-buffer))
1849       (or
1850        ;;   1 out of 2 hunks failed--saving rejects to test.el.rej
1851        (setq file (ti::re-search-check re 1 nil 'get-matched))
1852        ;;  patch: **** this file doesn't appear to be the 1.7 version--aborting
1853        (and (setq file
1854                   (ti::re-search-check
1855                    "^patch:.*--aborting" 1 nil 'get-matched-text))
1856             (setq file (list file)))
1857
1858        ;;  In SunOS it simply prints the following on success.
1859        ;;
1860        ;;       Looks like a new-style context diff.
1861        ;;       done
1862        (and (save-excursion             ;Check second line first
1863               (ti::pmin)             ;If we care to dig deeper then...
1864               (forward-line 2)
1865               (looking-at ".*done\\.?"))
1866             (save-excursion
1867               (let (stat1 stat2 stat3 str)
1868                 (ti::pmin)
1869                 (setq stat1 (looking-at ".*Looks like.*"))
1870                 (forward-line 1)
1871                 (setq stat2 (looking-at ".*done\\.?"))
1872                 (forward-line 1)
1873                 (setq str (buffer-substring-no-properties
1874                            (point) (point-max)))
1875                 (setq stat3 (string-match "^[\n\r]*\\'"  str))
1876                 (or (and stat1 stat2 stat3)
1877                     (setq file
1878                           (list
1879                            "TinyDiff: Hm. Can't tell if patch succeeded."))))))
1880        ;;  Maybe shell error has terminated the command
1881        ;;  "Unrecognized switch: -t"
1882        (and (not (ti::re-search-check "hunk.*succeed"))
1883             (prog1 nil
1884               (setq file
1885                     (list
1886                      "TinyDiff: Hm. No 'hunk succeed' message found."))))))
1887     (tinydiff-debug fid file)
1888     file))
1889
1890 ;;; ----------------------------------------------------------------------
1891 ;;;
1892 (defun tinydiff-patch-check-rejections  (cmd buffer)
1893   "After CMD, check rejections from BUFFER.
1894 If the the patch command says in this buffer:
1895
1896   1 out of 1 hunks failed--saving rejects to file.rej
1897
1898 Then loading the rejection file.
1899
1900 CMD is the original patch command used.
1901
1902 References:
1903
1904   `tinydiff-:patch-reject-buffer'"
1905   (let* ((fid      "tinydiff-patch-check-rejections: ")
1906          file
1907          stat
1908          tmp
1909          dir
1910          file-load)
1911     (unless fid ;; XEmacs byte compiler silencer
1912       (setq fid nil))
1913     (tinydiff-debug fid "in:" cmd buffer)
1914     (setq stat (tinydiff-patch-check-failure buffer))
1915 ;;;    (pop-to-buffer (current-buffer)) (ti::d! "rejections" stat cmd)
1916     (cond
1917      ((ti::listp stat)
1918       (message (car stat)))
1919      ((stringp stat)
1920       (setq file stat)
1921       (if cmd
1922           (setq dir (tinydiff-patch-get-dir-from-cmd cmd))
1923         (setq dir default-directory))
1924       (setq file-load (concat dir file))
1925       (tinydiff-debug fid file-load)
1926       (cond
1927        ((file-exists-p file-load)
1928         (setq tmp (ti::temp-buffer tinydiff-:patch-reject-buffer 'clear))
1929         (with-current-buffer tmp (insert-file-contents file-load))
1930         (display-buffer tmp)
1931         t)
1932        (t
1933         (message "TinyDiff: Can't find DIR for file...") (sit-for 1)
1934         (call-interactively 'find-file)
1935         t))))))
1936
1937 ;;; ----------------------------------------------------------------------
1938 ;;;
1939 (defun tinydiff-patch-with-diff-1 (file beg end &optional interactive type)
1940   "Apply diff to file i.e. patch a FILE.
1941
1942 Input:
1943
1944   FILE          absolute file name where to store diff.
1945   BEG           diff start point in buffer
1946   END           diff end point
1947   INTERACTIVE   User interaction, allow editing the patch command etc.
1948   TYPE          Type of patch: 'hunk means partial diff."
1949   (let* ((fid           "tinydiff-patch-with-diff-1")
1950          (file          (expand-file-name file))
1951          (opt-global    (or tinydiff-:patch-global-option
1952                             ""))
1953          (diff-tmp      (if (ti::win32-shell-p)
1954                             ;;  Must use DOS paths
1955                             (expand-file-name tinydiff-:diff-tmp-file)
1956                           ;;  Otherwise take it as it is
1957                           tinydiff-:diff-tmp-file))
1958          (prg           (eval tinydiff-:patch-program))
1959          (map           tinydiff-:minibuffer-map)
1960          (source-buffer (current-buffer))
1961          (file-buffer   (find-buffer-visiting file))
1962          buffer                         ;shell messages
1963          data-buffer
1964          ff
1965          dir
1966          cmd)
1967     (unless fid ;; XEmacs byte compiler silencer
1968       (setq fid nil))
1969     (tinydiff-debug fid "in:" file beg end interactive)
1970     (tinydiff-debug fid "vars:" diff-tmp prg source-buffer file-buffer)
1971     (when (and
1972            file-buffer
1973            (ti::buffer-modified-p file-buffer)
1974            interactive
1975            (not (y-or-n-p
1976                  (format
1977                   "TinyDiff: buffer %s not saved, continue ?" file-buffer))))
1978       (error "TinyDiff: Aborted."))
1979     (if (not (file-exists-p file))
1980         (error "Tinydiff: file not found: %s" file ))
1981     (setq dir         (file-name-directory file)
1982           ff          (file-name-nondirectory file)
1983           buffer      (ti::temp-buffer tinydiff-:diff-tmp-buffer  'clear)
1984           data-buffer (ti::temp-buffer tinydiff-:patch-tmp-buffer 'clear))
1985     ;;  Sometimes user's UMASK is not ok. PErhaps the file is
1986     ;;  not readable after the write.
1987     (when (and (file-exists-p diff-tmp)
1988                (not (file-writable-p diff-tmp)))
1989       (error "TinyDiff: [ERROR] Not writable. Check UMASK or permissions %s"
1990              diff-tmp))
1991     (ti::write-file-as-is-macro
1992      (write-region (point-min) (point-max) diff-tmp))
1993     (unless (file-readable-p diff-tmp)
1994       (set-file-modes diff-tmp 384)) ;; -rw-------
1995     (with-current-buffer data-buffer
1996       (insert-buffer-substring source-buffer beg end)
1997       (tinydiff-patch-minibuffer-cleanup)
1998 ;;;      (pop-to-buffer (current-buffer))      (ti::d! "ok")
1999       (setq cmd (format "cd %s ; %s %s %s %s" dir prg opt-global ff diff-tmp))
2000       (tinydiff-debug fid "cmd:" cmd)
2001       (when interactive
2002         (let* (tinyef-mode              ;Electric file mode OFF
2003                tinycompile-mode)        ;Make sure this is off too
2004           (if tinyef-mode
2005               (setq tinyef-mode nil))   ;No-op, bytecomp silencer
2006           (if tinycompile-mode
2007               (setq tinycompile-mode nil)) ;No-op
2008           (setq cmd (ti::remove-properties
2009                      (read-from-minibuffer "> " cmd map)))
2010           ;;  Record command line prompt to *Messages* buffer
2011           (message (concat "TinyDiff: RUN " cmd))))
2012       (if (ti::nil-p cmd)               ;user cleared the line ?
2013           (message "Tinydiff: Patching cancelled.")
2014         (tinydiff-shell-command cmd buffer)
2015         (when interactive
2016           (display-buffer buffer)
2017           (or (tinydiff-patch-check-rejections cmd buffer)
2018               (and (stringp file)
2019                    (tinydiff-patch-check-if-load
2020                     file
2021                     buffer
2022                     type))))))))
2023
2024 ;;; ----------------------------------------------------------------------
2025 ;;;
2026 (defun tinydiff-file-to-patch ()
2027   "Suggest possible filename to patch.
2028
2029 References:
2030
2031   `tinydiff-:patch-list'
2032
2033 Return:
2034
2035   string     suggested file to patch
2036   list       list of filenames read from diff."
2037   (let* ((fid   "tinydiff-get-file-name-list")
2038          (list  tinydiff-:patch-list)
2039          stat
2040          file
2041          file-list
2042          re
2043          search-path
2044          dest-file)
2045     (unless fid ;; XEmacs byte compiler silencer
2046       (setq fid nil))
2047     (ti::dotimes counter 1 4
2048       (setq stat (tinydiff-get-file-name counter))
2049       (when stat
2050         (setq file (nth 0 stat)
2051               stat (nth 1 stat))
2052         ;;  Try to find also gzipped files
2053         (push file file-list)
2054         (pushnew (concat file ".gz") file-list :test 'string=)
2055         (if (string-match "/" file)
2056             (push (file-name-nondirectory file)  file-list))))
2057     ;;  Preserve the order. Try to find the original file first.
2058     (setq file-list (nreverse file-list))
2059     (tinydiff-debug fid "file-list:" file-list)
2060     (when file-list
2061       ;; Normally the directory part cannot be used, because people have
2062       ;; files in different places, search it anyway in case the
2063       ;; file structure is the same..
2064       ;;
2065       ;; 1. "/dir/dir/file.el"
2066       ;; 2. "file.el"
2067       ;;
2068       ;; ClearCase diff:
2069       ;; *** /work/jackr/Emacs/folding.el@@/main/5  Wed Mar 13 09:39:36 1996
2070       ;; --- /work/jackr/Emacs/folding.el           Wed Mar 13 13:50:26 1996
2071       (catch 'done
2072         (dolist (file file-list)
2073           (cond
2074            ((file-exists-p file)
2075             (setq dest-file file)
2076             (throw 'done t))
2077            ((stringp file)
2078             (setq file (file-name-nondirectory file))
2079             (dolist (elt list) ;; User '((REGEXP DIR) ..)
2080               ;;  Get next element from patch table
2081               (setq re              (nth 0 elt)
2082                     search-path     (eval  (nth 1 elt)))
2083               (when (and (string-match re file)
2084                          (or (setq dest-file
2085                                    (ti::file-get-load-path file search-path))
2086                              (and (string-match "\\.el$" file)
2087                                   (setq
2088                                    dest-file
2089                                    (ti::file-get-load-path
2090                                     file
2091                                     search-path)))))
2092                 (throw 'done t))))))))
2093     (if dest-file
2094         dest-file)))
2095
2096 ;;; ----------------------------------------------------------- &patch ---
2097 ;;;
2098 (defun tinydiff-patch
2099   (arg &optional beg end dest-file verb type orig-buffer)
2100   "Try to guess diff type and region in the buffer.
2101 If automatic detection fails user must select diff region by hand.
2102
2103 Input:
2104
2105   PREFIX ARG    lets user to edit the diff command before executing.
2106                 This is enabled by default for interactive calls.
2107   BEG END       diff region; defaults to whole buffer if nil.
2108   DEST-FILE     file to patch
2109   VERB          Be verbose.
2110   TYPE          Type of diff: 'hunk or nil (whole diff)
2111   ORIG-BUFFER   Original buffer where the whole patch is."
2112   (interactive
2113    (progn
2114 ;;; This is not a good idea if you get many patches; to
2115 ;;; ask every time...
2116 ;;;
2117 ;;;     (if (and tinydiff-:package-exist-tinymy
2118 ;;;           (y-or-n-p "Do you want to make a safe copy? "))
2119 ;;;      (call-interactively 'tinymy-copy-file))
2120      (cond
2121       ((region-active-p)
2122        (list
2123         current-prefix-arg
2124         (region-beginning)              ;avoids region active check
2125         (region-end)
2126         (tinydiff-file-to-patch)))
2127       (t
2128        (list
2129         current-prefix-arg
2130         nil
2131         nil
2132         (tinydiff-file-to-patch))))))
2133   (let* ((fid           "tinydiff-patch")
2134          (go-status     t)              ;should we proceed patching?
2135          buffer
2136          rej-flag
2137          char
2138          ZIP)
2139     (or orig-buffer
2140         (setq orig-buffer (current-buffer)))
2141     (let* ((patch-buffer-file-name
2142             (with-current-buffer orig-buffer
2143               buffer-file-name))
2144            (guess-dir  (if patch-buffer-file-name
2145                            (file-name-directory
2146                             patch-buffer-file-name)))
2147            (guess-file (if patch-buffer-file-name
2148                            (file-name-nondirectory
2149                             patch-buffer-file-name)))
2150            (guess-path patch-buffer-file-name))
2151       (if (and guess-file
2152                (string-match "\\.rej$" guess-file))
2153           (setq guess-file (file-name-sans-extension guess-file)
2154                 guess-path (concat guess-dir guess-file)))
2155       (unless fid ;; XEmacs byte compiler silencer
2156         (setq fid nil))
2157       (setq verb (or arg verb (interactive-p)))
2158       (tinydiff-debug fid "in:" arg beg end)
2159       (or orig-buffer
2160           (setq orig-buffer (current-buffer)))
2161       (or dest-file
2162           (with-current-buffer orig-buffer
2163             (setq dest-file tinydiff-:patch-to-file)))
2164       (tinydiff-debug fid "dest-file:" dest-file)
2165       ;; ......................................................... patch ...
2166       ;;
2167       (unless (stringp dest-file)
2168         (message
2169          "Tinydiff: Can't detect file along paths in tinydiff-:patch-list.")
2170         (sit-for 0.7)
2171         (let ((default-directory default-directory))
2172           ;;  The ask prompt will start from HOME in that case.
2173           (unless patch-buffer-file-name
2174             (setq default-directory "~"))
2175           (setq dest-file (read-file-name
2176                            "Apply diff to: "
2177                            guess-dir
2178                            nil
2179                            t
2180                            guess-file))))
2181       (if (or (not (stringp dest-file))
2182               (not (file-exists-p dest-file)))
2183           (error "TinyDiff: Cannot patch non-existing file. Aborted."))
2184       (with-current-buffer orig-buffer
2185         (set (make-local-variable 'tinydiff-:patch-to-file) dest-file))
2186       ;; ........................................................... zip ...
2187       (when (string-match "\\(.*\\)\\.gz$" dest-file)
2188         (setq dest-file (match-string 1 dest-file))
2189         (setq ZIP t)
2190         (message "TinyDiff: Uncompressing gzip file...")
2191         (ti::temp-buffer tinydiff-:diff-tmp-buffer 'clear)
2192         (call-process
2193          "gzip"
2194          nil                        ;; Input
2195          tinydiff-:patch-tmp-buffer ;; Output buffer
2196          nil                        ;; display
2197          "-d"
2198          (format "%s.gz" dest-file))
2199         (message "TinyDiff: Uncompressing %s file...done." dest-file))
2200       ;; ....................................................... rejects ...
2201       (when (file-exists-p (concat dest-file ".rej"))
2202         (setq rej-flag t))
2203       (when (and (not (or (null tinydiff-:patch-hunk-count)
2204                           (eq 0 tinydiff-:patch-hunk-count))))
2205         (if (not
2206              (and
2207               (file-exists-p (concat dest-file ".orig"))
2208               (prog1 t
2209                 (setq
2210                  char
2211                  (ti::read-char-safe-until
2212                   (format "%s\
2213 .orig found: r = retry patch, o = back to .orig, g = go and patch"
2214                           (if rej-flag
2215                               "[.rej]"
2216                             ""))
2217                   '(?o ?r ?g    ?\e ?q ?\b ?\C-g))))))
2218             (when rej-flag
2219               (message "Tinydiff: Hm... rejection file found.")
2220               (sit-for 1))
2221           ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .orig exist ..
2222           (cond
2223            ((char= char ?r)             ;Retry patch to original
2224             (delete-file dest-file)     ;copy-file won't work otw
2225             (copy-file  (concat dest-file ".orig") dest-file))
2226            ((char= char ?g)             ;Go ahead
2227             nil)
2228            ((char= char ?o)
2229             (delete-file dest-file)
2230             (copy-file  (concat dest-file ".orig") dest-file)
2231             (delete-file (concat dest-file ".orig"))
2232             (setq go-status nil)
2233             (message
2234              "Tinydiff: Original file restored: .orig copied over %s"
2235              dest-file))
2236            (t
2237             (setq go-status nil)
2238             (message "Tinydiff: Cancelled patching.")))))
2239       (when go-status
2240         (with-current-buffer orig-buffer
2241           (set (make-local-variable 'tinydiff-:patch-hunk-count)
2242                (1+ (or tinydiff-:patch-hunk-count 0))))
2243         (tinydiff-patch-with-diff-1
2244          dest-file (point-min) (point-max) verb type))
2245       (when ZIP
2246         (message "TinyDiff: Compressing gzip file...")
2247         (call-process "gzip"
2248                       nil
2249                       nil               ; (current-buffer)
2250                       nil
2251                       "-9"
2252                       (format "%s.gz" dest-file))
2253         (message "TinyDiff: Compressing %s file...done" dest-file)))))
2254
2255 ;;}}}
2256 ;;{{{ code: command generate
2257
2258 ;;; ----------------------------------------------------------------------
2259 ;;;
2260 (defun tinydiff-diff-command-generate  (&optional no-ask)
2261   "Return diff command as string; optionally NO-ASK."
2262   (if (null tinydiff-:minibuffer-map)
2263       (run-hooks 'tinydiff-:mode-define-keys-minibuffer-hook))
2264   (let* ((map           tinydiff-:minibuffer-map)
2265          (tmp-file      (expand-file-name
2266                          tinydiff-:diff-tmp-file))
2267          (diff-prg      (or (eval tinydiff-:diff-program)
2268                             (error "TinyDiff: tinydiff-:diff-program is nil")))
2269          (rcsdiff-prg   (eval tinydiff-:rcsdiff-program))
2270          (cvsdiff-prg   (eval tinydiff-:cvsdiff-program))
2271          (options       (or (eval tinydiff-:diff-option) ""))
2272          (ange          (and (stringp default-directory)
2273                              (string-match "@" default-directory)))
2274          (dired         (and (null ange)
2275                              (string-match "dired" (symbol-name major-mode))))
2276          (bf            (cond
2277                          (dired
2278
2279                           ;;  If point is out of file listing dired flags
2280                           ;;  error, don't mind it.
2281
2282                           (ignore-errors (dired-get-filename)))
2283                          ((buffer-file-name))))
2284          (file          "")
2285          (file2         "")
2286          (dir           "")
2287          rev
2288          prompt
2289          ans
2290          cvs-info)
2291     ;;  This flag is used to to signal that buffer should not be
2292     ;;  saved, because the underlying file has changed.
2293     (put 'tinydiff-diff-command-generate 'buffer-not-modified nil)
2294     (tinydiff-debug default-directory bf "DIRED-ANG" dired ange "OPT" options)
2295     (tinydiff-kill-revision-list)
2296     (when bf
2297       (setq dir  (file-name-directory bf)
2298             file (file-name-nondirectory bf)))
2299     (cond
2300      ;; .............................................. buffer change ...
2301      ;; - The buffer has changed. Or file has changed.
2302      ((and bf
2303            (null dired)
2304            (file-exists-p bf)
2305            (or (buffer-modified-p)
2306                ;;  someone else edited or changed the same file
2307                (null (verify-visited-file-modtime (current-buffer))))
2308            (y-or-n-p "Diff between buffer and file on disk? "))
2309       (put 'tinydiff-diff-command-generate 'buffer-not-modified bf)
2310       (ti::widen-safe                 ;make sure whole buffer is saved
2311         (write-region (point-min) (point-max) tmp-file))
2312       (setq file2 tmp-file))
2313      ;; ........................................................ rcs ...
2314      ;; - Buffer is RCS controlled.
2315      ((and bf
2316            (null dired)
2317            (ti::vc-rcs-file-exists-p bf))
2318       ;; - if the revision information cannot be found, then the
2319       ;;   '-rX.x' switch is not used.
2320       (setq rev (ti::vc-rcs-buffer-version))
2321       (unless rev
2322         (message "Tinydiff: RCS Revision not detected.") (sit-for 1))
2323       (setq options
2324             (format "%s %s"
2325                     options
2326                     (if rev
2327                         (concat "-r" rev " ")
2328                       "")))
2329       (setq diff-prg
2330             (or rcsdiff-prg
2331                 (error "TinyDiff: tinydiff-:rcsdiff-program is nil"))))
2332      ;; .......................................................... cvs ...
2333      ((and bf
2334            (null dired)
2335            (or (boundp 'tinydiff-cvs-flagged)
2336                (setq cvs-info (ti::vc-cvs-file-exists-p bf))))
2337       ;; It's too expensive to call `ti::vc-cvs-file-exists-p' every time,
2338       ;; so we create intermediate variable to flag this buffer as CVS
2339       ;; controlled.
2340       (make-local-variable 'tinydiff-cvs-flagged)
2341       ;; (setq rev (ti::vc-rcs-buffer-version))
2342       (setq rev (car (ti::vc-cvs-entry-split-info
2343                       (ti::vc-cvs-entry-split cvs-info)
2344                       'revision)))
2345       (unless rev
2346         (message "Tinydiff: CVS Revision not detected.") (sit-for 1))
2347       (setq options
2348             (format "%s %s"
2349                     options
2350                     (if rev
2351                         (concat "-r" rev " ")
2352                       "")))
2353       (setq diff-prg
2354             (or cvsdiff-prg
2355                 (error "TinyDiff: tinydiff-:cvsdiff-program is nil"))))
2356      ;; .................................................... default ...
2357      ;; General diff prompt
2358      ((null ange)
2359       (setq dir default-directory))
2360      (t
2361       (setq dir (expand-file-name "~"))))
2362     ;; ... ... ... ... ... ... ... ... ... ... command generated . .
2363     (setq prompt (format
2364                   "cd %s; %s %s %s %s "
2365                   dir
2366                   (or diff-prg
2367                       (error
2368                        "TinyDiff: No diff program available. Check PATH."))
2369                   options
2370                   file
2371                   file2))
2372     (if no-ask
2373         (setq ans prompt)
2374       (let* (tinyef-:mode)              ;Electric file mode OFF
2375         (if tinyef-:mode
2376             (setq tief-mode nil))       ;No-op, bytecompier silencer
2377         ;;  Record command line prompt to *Messages* buffer
2378         (message (concat "TinyDiff: " prompt))
2379         (setq ans (read-from-minibuffer ">" prompt map))))
2380     (if (ti::nil-p ans)
2381         (setq ans nil))
2382     (ti::remove-properties ans)))
2383
2384 ;;}}}
2385 ;;{{{ code: generating, parsing diff
2386
2387 ;;; .................................................... &diff-parsing ...
2388
2389 ;;; ----------------------------------------------------------------------
2390 ;;;
2391 (defun tinydiff-parse-buffer (&optional verb)
2392   "Prepare diff buffer for `tinydiff-mode'.  VERB.
2393 Mark diff lines for special handling."
2394   (interactive)
2395   (let* ((diff-type      (car-safe (ti::buffer-diff-type-p)))
2396
2397          ;;  In GNU diff , there is option --initial-tab
2398          ;;  which adds tab before each diff line to make the
2399          ;;  text look "as it was originally"
2400          ;;
2401          ;;  That's why allowed whitespace at the beginning.
2402
2403          (re-c2         "^[ \t]*[*][*][*] \\([0-9]+\\)")
2404          (re-c3         "^[ \t]*[-][-][-] \\([0-9]+\\)")
2405          (re-normal     "^\\([0-9]+\\)\\(,[0-9]+\\)?+[acd][0-9]")
2406
2407          (re-gnu-u      "^@@[ \t]+[-+][0-9]+,[0-9]+[ \t]+[-+]+\\([0-9]+\\)")
2408          (re-gnu-n      "^[dac]\\([0-9]+\\) [0-9]+$")
2409
2410          (prop-list     '(mouse-face    highlight
2411                                         owner         tinydiff)))
2412     (ti::verb)
2413     (let ((sym 'font-lock-keywords))
2414       (set sym tinydiff-:font-lock-keywords))
2415     (ti::text-clear-region-properties
2416      (point-min) (point-max) '(owner tinydiff))
2417     (save-excursion
2418       (ti::pmin)
2419       (cond
2420        ((eq diff-type 'context)
2421         (ti::text-re-search re-c2 nil 1 nil prop-list)
2422         (ti::pmin)
2423         (ti::text-re-search re-c3 nil 1 nil prop-list))
2424        ((eq diff-type 'normal)
2425         (ti::text-re-search re-normal nil 1 nil prop-list))
2426        ((eq diff-type 'gnu-u)
2427         (ti::text-re-search re-gnu-u nil 1 nil prop-list))
2428        ((eq diff-type 'gnu-n)
2429         (ti::text-re-search re-gnu-n nil 1 nil prop-list))))
2430     (if diff-type
2431         (if verb
2432             (message (concat "Tinydiff: Diff parsed, type: "
2433                              (prin1-to-string diff-type))))
2434       (if verb
2435           (message "Tinydiff: Diff not recognized.")))
2436     (run-hooks 'tinydiff-:parse-buffer-hook)))
2437
2438 ;;; ......................................................... &diff-do ...
2439
2440 ;;; ----------------------------------------------------------------------
2441 ;;;
2442 ;;;###autoload
2443 (defun tinydiff-diff-show (cmd)
2444   "Generate diff CMD for the buffer and show it in the other window.
2445 Lets user to edit option in the command line."
2446   (interactive
2447    (progn
2448      (when (and
2449             (buffer-file-name)
2450             (file-exists-p
2451              (format
2452               "%s#%s#"
2453               (file-name-directory (buffer-file-name))
2454               (file-name-nondirectory (buffer-file-name)))))
2455        (message
2456         "Tinydiff: There is autosave file, use minibuffer %s binding"
2457         (ti::keymap-function-bind-info
2458          'tinydiff-minibuffer--insert-file-autosave
2459          tinydiff-:minibuffer-map)
2460         (sit-for 1)))
2461      (list
2462       (tinydiff-diff-command-generate))))
2463   (if (and (stringp cmd)
2464            (buffer-modified-p)
2465            (buffer-file-name)
2466            ;;  If this is in the commend, we're diffing buffer against
2467            ;;  file on disk
2468            (not (or (string-match (regexp-quote tinydiff-:diff-tmp-file) cmd)
2469                     (string-match
2470                      (regexp-quote (expand-file-name tinydiff-:diff-tmp-file))
2471                      cmd)))
2472            (y-or-n-p "Save buffer before running diff? "))
2473       (save-buffer))
2474   (when (stringp cmd)
2475     (tinydiff-diff cmd 'show)))
2476
2477 ;;; ----------------------------------------------------------------------
2478 ;;;
2479 ;;;###autoload
2480 (defun tinydiff-diff-show-noask (cmd)
2481   "Generate diff CMD for the buffer. Guess all parameters."
2482   (interactive
2483    (list
2484     ;;  - The first one runs fine for rcsdiff (ie. buffer is in RCS)
2485     ;;    but if it fails, we have to ask paramters from user.
2486     (or (tinydiff-diff-command-generate 'no-ask)
2487         (tinydiff-diff-command-generate))))
2488   ;;  the `get' is set in `tinydiff-diff-command-generate'
2489   ;;  to indicate that buffer and file  content are no in synch, user
2490   ;;  does not want to save modified buffer, but check against the copy
2491   ;;  at disk.
2492   (if (and (null (get 'tinydiff-diff-command-generate 'buffer-not-modified))
2493            (buffer-modified-p)
2494            (y-or-n-p "Save buffer before running diff? "))
2495       (save-buffer))
2496   (if (not (ti::nil-p cmd))
2497       (tinydiff-diff cmd 'show)))
2498
2499 ;;; ------------------------------------------------------------- &cmd ---
2500 ;;; - The diff data is inserted into register automatically, because
2501 ;;;   many time the diff data is pasted to somewhere else. Eg. by sending it
2502 ;;;   via mail to someone else in projects.
2503 ;;;
2504 ;;;
2505 ;;;###autoload
2506 (defun tinydiff-diff (cmd &optional show verb)
2507   "Run diff on buffer, possibly using rcsdiff if file is version controlled.
2508 Inserts contents into register.
2509
2510 The version control is determined by searching RCS strings 'Id' or 'Log'
2511
2512 Input:
2513
2514   CMD           diff command
2515   SHOW          show the results
2516   NO-ASK        run diff without asking any questions.
2517   VERB          enable verbose messages
2518
2519 References:
2520
2521   `tinydiff-:extra-diff-program'
2522   `tinydiff-:diff-buffer'
2523   `tinydiff-:diff-options'
2524
2525 Return:
2526
2527  nil            ,the no-ask parameter could not determine right diff.
2528  buffer         ,the shell output buffer. Note, that the diff may have
2529                  failed, in that case the buffer does not hold valid output."
2530   (let* ((fid      "tinydiff-diff")
2531          (bf       (buffer-file-name))
2532          (buffer   (ti::temp-buffer tinydiff-:diff-buffer 'clear))
2533          (reg      (eval tinydiff-:register-diff)) ;where to put the diff content
2534          prereq
2535          tmp)
2536     (tinydiff-debug fid 'cmd cmd 'show show 'verb verb)
2537     (ti::verb)
2538     ;;  It the command is rcsdiff, then we must include
2539     ;;  Prereq: tag to the beginning of diff. Thhat tells patch
2540     ;;  that when applying the diff, it must find that string
2541     ;;  from the file before it can prosess applying the patch.
2542     ;;
2543     ;;  It prevents patching wrong versions.
2544     ;;
2545     ;;      Prereq: 1.10
2546     ;;
2547     ;;  Or we use this which is more stricter. It supposes you have
2548     ;;  rcs 'id' string in a file.
2549     ;;
2550     ;;      Prereq: tinylib.el,v 1.10
2551     ;;
2552     ;;  Nov 8 1996: Hm the latter isn't supported, if picks only
2553     ;;  "tinylib.el,v" and not whole string in the linew
2554     ;;  I'm going to request imprevement to GNU patch....
2555     (cond
2556      ((setq tmp (ti::string-match "-r\\([0-9.]+\\)" 1 cmd))
2557       ;; Add file name too
2558       (if (or t
2559               (null (tinydiff-minibuffer--read-rcs-file-name cmd)))
2560           (setq prereq (format "Prereq: %s\n" tmp))
2561 ;;; disabled now.
2562 ;;;     (setq tmp2    (file-name-nondirectory tmp2))
2563 ;;;     (setq prereq  (format "Prereq: %s,v %s"  tmp2 tmp))
2564         nil)))
2565     (if (and (null cmd)
2566              (null bf))
2567         (message "Tinydiff: Sorry, this is not a file buffer.")
2568       (if (null cmd)
2569           ;;  if the NO-ASK parameter is set, then we can't ask anything
2570           ;;  from the user. What if the file is not RCS file? Then what we
2571           ;;  diff against? --> give up and return nil pointer
2572           (setq buffer nil)
2573         ;; ... ... ... ... ... ... ... ... ... ... ... ... shell command . .
2574         ;; ELSE
2575         (tinydiff-shell-command cmd buffer)
2576         (with-current-buffer buffer
2577           (ti::pmin)
2578           (when prereq
2579             (insert prereq "\n"))
2580           (when show
2581             (setq tmp (current-buffer))
2582             ;;  Unless it's already visible in some frame.
2583             (if (setq tmp (get-buffer-window buffer t))
2584                 (raise-frame (window-frame tmp))
2585               (display-buffer buffer)))
2586           (when reg
2587             (set-register reg (buffer-string))
2588             (if verb
2589                 (message
2590                  (concat "Tinydiff: Diff stored in register ..."
2591                          (char-to-string reg)))))
2592           (run-hooks 'tinydiff-:diff-hook))))
2593     buffer))
2594
2595 ;;}}}
2596 ;;{{{ code: Misc; mime write
2597
2598 ;;; ----------------------------------------------------------------------
2599 ;;;
2600 (defun tinydiff-compose-diff-filename ()
2601   "Compose filename by reading the original filename from diff buffer.
2602 Filename is composed like this: ~/tmp + FILE + .diff suffix."
2603   (let* ((dir
2604           (cond
2605            ((file-directory-p "~/tmp/") "~/tmp/")
2606            ((file-directory-p "/tmp/") "/tmp/")
2607            (t
2608             default-directory)))
2609          file
2610          stat
2611          ret)
2612     (ti::dotimes counter 1 4 ;;  #todo: remove ti::dotimes
2613       (setq stat (tinydiff-get-file-name counter))
2614       (when stat
2615         (setq file (nth 0 stat)
2616               stat (nth 1 stat))
2617         ;;   If the name is not .diff or .patch, then it will do
2618         (when (not (string-match "\\.diff\\|\\.patch" file))
2619           (setq ret file  counter 6))))
2620     (when ret
2621       (ti::file-make-path
2622        (if (ti::win32-shell-p)
2623            (expand-file-name dir)
2624          dir)
2625        (concat file ".patch")))))
2626
2627 ;;; ----------------------------------------------------------------------
2628 ;;;
2629 (defun tinydiff-write-file (file)
2630   "Write current diff to temporary file.
2631 This is purely an interactive function.
2632 The suggested to be written is named like this: ~/tmp + FILE + .diff suffix."
2633   (interactive
2634    (let* ((file-name            (tinydiff-compose-diff-filename))
2635           (file                 (and file-name
2636                                      (file-name-nondirectory file-name)))
2637           (default-directory    (if file-name
2638                                     (file-name-as-directory
2639                                      (file-name-directory file-name))
2640                                   default-directory)))
2641      (list (read-file-name "Write diff to: " nil nil nil file))))
2642   (unless (ti::nil-p file)
2643     (write-region (point-min) (point-max) file)))
2644
2645 ;;; ----------------------------------------------------------------------
2646 ;;;
2647 (defun tinydiff-mime-compose  (&optional insert-to-mail verb)
2648   "Read current buffer and make TM MIME attachement.
2649 Save attachement to `tinydiff-:register-diff' or to a mail buffer,
2650 which must have MIME-edit mode active.
2651
2652 The TM MIME spec is used: 7bit, type=patch.
2653 If you need other specifications, insert diff via TM's insert file.
2654
2655 Input:
2656
2657   INSERT-TO-MAIL    Flag, if non-nil, add MIME block to the end of
2658                     buffer pointed by `tinydiff-:mail-buffer-function'.
2659   VERB              Be verbose."
2660   (interactive "P")
2661   (let* ((file  (file-name-nondirectory (tinydiff-compose-diff-filename)))
2662          (mime-tag
2663           (format
2664            (concat
2665             "--[[application/octet-stream; type=patch\n"
2666             "Content-Disposition: attachment; "
2667             "filename=\"%s\"][7bit]]\n")
2668            file))
2669          (obuffer  (current-buffer))
2670          (mime-p   t)
2671          mail-buffer)
2672     (ti::verb)
2673     (cond
2674      (insert-to-mail
2675       (unless (setq mail-buffer (funcall tinydiff-:mail-buffer-function))
2676         (error "Tinydiff: Can't find mail buffer where to insert to"))
2677       (with-current-buffer mail-buffer
2678         (when (not (or (ti::mail-mime-tm-edit-p)
2679                        (ti::mail-mime-semi-edit-p)))
2680           (setq mime-p nil)))
2681       ;;  Do not add the mime tag, if there is noTM mime edit mode
2682       (if mime-p
2683           (ti::append-to-buffer mail-buffer mime-tag))
2684       (append-to-buffer mail-buffer (point-min) (point-max))
2685       (when verb
2686         (message "Tinydiff: %sdiff appended to buffer: %s"
2687                  (if mime-p "Mime " "")
2688                  (buffer-name mail-buffer))))
2689      (t
2690       (with-temp-buffer
2691         (insert mime-tag)
2692         (insert-buffer obuffer)
2693         (set-register tinydiff-:register-diff (buffer-string))
2694         (when verb
2695           (message "TinyDiff: MIME diff in register `%c'"
2696                    tinydiff-:register-diff)))))))
2697
2698 ;;}}}
2699
2700 ;;{{{ code: Line functions
2701
2702 ;;; ----------------------------------------------------------------------
2703 ;;;
2704 (defun tinydiff-get-buffer-name ()
2705   "Return buffer name of the current diff."
2706   (let* (file
2707          ret
2708          list)
2709     (save-excursion
2710       (cond
2711        ((progn (ti::pmin)
2712 ;;;                    (ti::d! "RCS file")
2713                (re-search-forward "^RCS file: .*/\\(.*\\),v" nil t))
2714
2715         ;; RCS file: RCS/folding.el,v
2716         ;; retrieving revision 1.18
2717         ;; retrieving revision 1.19
2718         (setq file (match-string 1)))
2719        ((progn (ti::pmin)
2720 ;;;           (ti::d! "---")
2721                (re-search-forward "^---[ \t]+\\([^\t ]+\\)" nil t))
2722         ;;  --- copy/tinydiff.el Wed Jan 24 16:08:05 1996
2723         ;;  +++ tinydiff.el      Wed Jan 24 16:29:07 1996
2724         ;;  @@ -1,6 +1,6 @@
2725         (setq file (match-string 1))
2726         (or (or (setq ret (and (string-match "/" file)
2727                                (find-buffer-visiting file)))
2728                 (setq ret (get-buffer (file-name-nondirectory file))))
2729             ;;  Hmm; the "---" file was not found; try "***" file
2730             (and
2731              (progn
2732                (ti::pmin)
2733 ;;;           (ti::d! "***")
2734                (re-search-forward "^\\*\\*\\*[ \t]+\\([^\t ]+\\)" nil t)
2735                ;;  *** copy/tinydiff.el Wed Jan 24 16:08:05 1996
2736                ;;  --- /tmp/tdi.diff    Wed Jan 24 16:29:07 1996
2737                (setq file (match-string 1)))
2738              (or (setq ret (and (string-match "/" file)
2739                                 (find-buffer-visiting file)))
2740                  (setq ret (get-buffer (file-name-nondirectory file)))))))
2741        ((progn (ti::pmin)
2742                (re-search-forward "^filename: \\([^\t ]+\\)$" nil t))
2743         ;;  User tag, e.g. output from shell script that generates the
2744         ;;  'filename' tag + runs the diff program.
2745         (setq file (match-string 1)))
2746        (t
2747         ;;  other diff formats .. Still open
2748         nil))
2749       ;; ............................................. examine results ...
2750       (when (and (not ret)
2751                  file)
2752         (if file                        ;remove directory part
2753             (setq file                  ;; returns nil if no "/" found
2754                   (file-name-nondirectory file)))
2755         (cond
2756          ((get-buffer file)
2757           (setq ret file)) ;;  - buffer name == file name
2758          ((setq list (ti::dolist-buffer-list (string-match file (buffer-name))))
2759           ;;  - Eg name tinydiff.el may be in name tinydiff.el<2>
2760           ;;  - just pick the first from list
2761           (setq ret (car list)))))
2762
2763       ret)))
2764
2765 ;;; ----------------------------------------------------------------------
2766 ;;;
2767 (defun tinydiff-get-line-number ()
2768   "Return diff line number if line has one."
2769   (let* ((diff-type     (car-safe (ti::buffer-diff-type-p)))
2770          (re-c2         "^[*][*][*] \\([0-9]+\\)")
2771          (re-c3         "^[-][-][-] \\([0-9]+\\)")
2772          (re-normal     "^\\([0-9]+\\)\\(,[0-9]+\\)?+[acd][0-9]")
2773          ;; Wrong: this returned left hand number
2774 ;;;         (re-gnu-u      "^@@ [-+]\\([0-9]+\\),[0-9]+[ \t]+[-+]+")
2775          (re-gnu-u      "^@@ [-+][0-9]+,[0-9]+[ \t]+[-+]+\\([0-9]+\\)")
2776          (re-gnu-n      "^[dac]\\([0-9]+\\) [0-9]+$")
2777          ret)
2778     (save-excursion
2779       (beginning-of-line)
2780       (cond
2781        ((eq diff-type 'context)
2782         (or (setq ret (ti::buffer-match re-c2 1))
2783             (setq ret (ti::buffer-match re-c3 1))))
2784        ((eq diff-type 'normal)
2785         (setq ret (ti::buffer-match re-normal 1)))
2786        ((eq diff-type 'gnu-n)
2787         (setq ret (ti::buffer-match re-gnu-n 1)))
2788        ((eq diff-type 'gnu-u)
2789         (setq ret (ti::buffer-match re-gnu-u 1)))))
2790 ;;;    (ti::d! (match-string 0) diff-type)
2791     (when ret
2792       (setq ret (string-to-int ret)))
2793     ret))
2794
2795 ;;; ----------------------------------------------------------------------
2796 ;;;
2797 (defun tinydiff-goto (buffer line)
2798   "Show BUFFER and put cursor at LINE in other window."
2799   (let* ((ob     (current-buffer))      ;original buffer
2800          (delay  0.1))
2801     (switch-to-buffer-other-window buffer)
2802     (goto-line line)
2803     ;; Flash the cursor and go back to diff buffer
2804     (and delay
2805          (sit-for delay))
2806     (pop-to-buffer ob)))
2807
2808 ;;; ----------------------------------------------------------------------
2809 ;;;
2810 (defun tinydiff-goto-next (&optional back verb no-update)
2811   "Search next position, or  BACKWARD.
2812
2813 Input:
2814
2815   BACK          if non-nil then search backward
2816   VERB          enable verbose messages
2817   NO-UPDATE     do not update diff source buffer
2818
2819 Return:
2820
2821   t             if successful
2822   nil           no more hits"
2823   (interactive)
2824   (let* ((diff-type     (car-safe (ti::buffer-diff-type-p)))
2825          (re-c2         "^[*][*][*] \\([0-9]+\\)")
2826 ;;;         (re-c3         "^[-][-][-] \\([0-9]+\\)")
2827          (re-normal     "^[0-9]+\\(,[0-9]+\\)?+[acd][0-9]")
2828
2829 ;;;         (re-gnu-u      "^@@ [-+]\\([0-9]+\\),[0-9]+[ \t]+[-+]+")
2830          (re-gnu-u      "^@@ [-+][0-9]+,[0-9]+[ \t]+[-+]+\\([0-9]+\\)")
2831          (re-gnu-n      "^[dac]\\([0-9]+\\) [0-9]+$")
2832          (type-list
2833           (list
2834            (cons 'context
2835                  re-c2)
2836            (cons 'normal
2837                  re-normal)
2838            (cons 'gnu-u
2839                  re-gnu-u)
2840            (cons 'gnu-n
2841                  re-gnu-n)))
2842          (func          (if back 're-search-backward 're-search-forward))
2843          buffer
2844          ret
2845          re)
2846     (ti::verb)
2847     (setq re (cdr-safe (assoc diff-type type-list)))
2848     (ignore-errors
2849       (when (setq buffer (funcall tinydiff-:source-buffer-function))
2850         (setq tinydiff-:diff-source-buffer buffer) ))
2851     (when (stringp re)
2852       (if back
2853           (beginning-of-line)
2854         (end-of-line))
2855       (if (null (funcall func re nil t))
2856           (if verb (message "Tinydiff: No more hits."))
2857         ;;   Adjust the display to middle of the screen
2858         (unless no-update
2859           (goto-char (match-beginning 0))
2860           (recenter '(4))               ;show it
2861           (tinydiff-goto-kbd 'verb))
2862         (setq ret t)))
2863     ret))
2864
2865 ;;; ----------------------------------------------------------------------
2866 ;;;
2867 (defun tinydiff-goto-prev ()
2868   "Search diff position backward."
2869   (interactive)
2870   (beginning-of-line)
2871   (tinydiff-goto-next 'back 'verb))
2872
2873 ;;; ----------------------------------------------------------------------
2874 ;;;
2875 (defun tinydiff-goto-prev-no-update ()
2876   "Search diff position backward."
2877   (interactive)
2878   (beginning-of-line)
2879   (tinydiff-goto-next 'back 'verb 'no-update))
2880
2881 ;;; ----------------------------------------------------------------------
2882 ;;;
2883 (defun tinydiff-goto-next-no-update ()
2884   "Search next position."
2885   (interactive)
2886   (end-of-line)
2887   (tinydiff-goto-next nil 'verb 'no-update))
2888
2889 ;;; ----------------------------------------------------------------------
2890 ;;;
2891 (defun tinydiff-goto-kbd (&optional verb)
2892   "Show the diff source in another window. VERB."
2893   (interactive)
2894   (let* ((line          (tinydiff-get-line-number))
2895          (buffer        (tinydiff-source)))
2896     (ti::verb)
2897     (if (null line)
2898         ;; nothing to do, not valid line
2899         (if verb (message
2900                   "Tinydiff: Can't find source line reference."))
2901       (if (null buffer)
2902           (if verb (message "Tinydiff: Cannot find buffer name for diff."))
2903         (tinydiff-goto buffer line)))))
2904
2905 ;;; ----------------------------------------------------------------------
2906 ;;;
2907 (defun tinydiff-goto-mouse (event)
2908   "Show current line in other window. Use mouse EVENT.
2909 Activate only if point underneath has 'mouse-property."
2910   (interactive "e")
2911   (let* ((buffer        (tinydiff-source))
2912          line)
2913     (when (ti::text-get-mouse-property)
2914       (setq line (ti::remove-properties (ti::buffer-read-word "[0-9]+")))
2915       (if (and buffer
2916                (not (ti::nil-p line)))
2917           (tinydiff-goto buffer (string-to-int line))
2918         (message "Tinydiff: Sorry, missing Line Number or filenname.")))))
2919
2920 ;;}}}
2921 ;;{{{ code: patch block handling
2922
2923 ;;; ----------------------------------------------------------------------
2924 ;;;
2925 (defun tinydiff-header ()
2926   "Return the diff header."
2927   (save-excursion
2928     (ti::pmin)
2929     ;; --- foo-2.4.orig/config.guess
2930     ;; +++ foo-2.4/config.guess
2931     (when (looking-at "^--- ")
2932       (forward-line 2)
2933       (buffer-substring (point-min) (point)))))
2934
2935 ;;; ----------------------------------------------------------------------
2936 ;;;
2937 (defun tinydiff-block-region  ()
2938   "Return (beg . end) of diff block around current point or nil."
2939   (let* ( ;; (type   (ti::buffer-diff-type-p))
2940          beg
2941          end)
2942     (end-of-line)
2943     (setq end (point))
2944     (or (tinydiff-goto-next nil nil 'no-update) ;; *** 2720,2727 ****
2945         (goto-char (point-max)))
2946     ;; If the point moved to somewhere.
2947     (if (eq end (point))
2948         (setq end nil)
2949       ;;  We need the previous line too
2950       ;;
2951       ;;  ***************
2952       ;;  *** 4687,4692 ****
2953       ;;
2954       (forward-line -1)
2955       (setq end (line-beginning-position)))
2956     (setq beg (point))
2957     (tinydiff-goto-next 'back nil 'no-update)
2958     (if (eq beg (point))
2959         (setq beg nil)
2960       ;;  Take the whole hunk
2961       (if (looking-at "^[*][*][*] ")
2962           (forward-line -1))
2963       (setq beg (line-beginning-position)))
2964     (if (and beg end)
2965         (cons beg end))))
2966
2967 ;;; ----------------------------------------------------------------------
2968 ;;;
2969 (defun tinydiff-block-kill  ()
2970   "Kill Diff block around point."
2971   (interactive)
2972   (let* ((region (tinydiff-block-region))
2973          buffer-read-only)
2974     (if (and (interactive-p)
2975              (null region))
2976         (message "TinyDiff: can't determine diff block bounds.")
2977       (delete-region (car region) (cdr region)))))
2978
2979 ;;; ----------------------------------------------------------------------
2980 ;;;
2981 (defun tinydiff-block-apply-patch  ()
2982   "Apply diff block around point.
2983 References:
2984   `tinydiff-patch-set-option'."
2985   (interactive)
2986   (let* ((add-opt tinydiff-:patch-global-option)
2987          (region  (save-excursion
2988                     (or (tinydiff-block-region)
2989                         (error "TinyDiff: Hunk region not found."))))
2990          (header  (or (tinydiff-header)
2991                       (progn
2992                         (message "TinyDiff: [WARN] Patch header not found.")
2993                         "")))
2994          (buffer  (current-buffer))
2995          file)
2996     (if (and (interactive-p)
2997              (null region))
2998         (message "TinyDiff: can't determine diff block bounds.")
2999       (setq file (tinydiff-file-to-patch))
3000       (with-temp-buffer
3001         (insert header)
3002         (insert-buffer-substring buffer (car region) (cdr region))
3003         (tinydiff-patch nil nil nil  file 'verb 'hunk buffer)))))
3004
3005 ;;; ----------------------------------------------------------------------
3006 ;;;
3007 (defun tinydiff-patch-set-option (opt-string)
3008   "Set `tinydiff-:patch-global-option' to OPT-STRING.
3009 E.g. to apply revese diff, you may want to set the option to: -R"
3010   (interactive
3011    (list
3012     (read-string
3013      (format
3014       "Tinydiff patch option%s: "
3015       (if (and (stringp tinydiff-:patch-global-option)
3016                (not (string-match "^[ \t]*$"
3017                                   tinydiff-:patch-global-option)))
3018           (format " [%s]"
3019                   tinydiff-:patch-global-option)
3020         "")))))
3021   (setq tinydiff-:patch-global-option opt-string))
3022
3023 ;;}}}
3024 ;;{{{ setup: Install
3025
3026 (defadvice cvs-mode-diff (after tinydiff-turn-on-diff-mode act)
3027   "Call `turn-on-tinydiff-mode'."
3028   (when (boundp 'cvs-diff-buffer-name)
3029     (with-current-buffer (symbol-value 'cvs-diff-buffer-name)
3030       (unless (fboundp 'diff-mode)
3031         (turn-on-tinydiff-mode)))))
3032
3033 (add-hook 'tinydiff-:mode-define-keys-hook  'tinydiff-mode-define-keys)
3034 (add-hook 'tinydiff-:parse-buffer-hook      'turn-on-font-lock-mode)
3035
3036 ;; These have to be here, because when someone says
3037 ;;   (add-hook 'tinydiff-:diff-hook 'my-tinydiff-:diff-hook)
3038 ;;
3039 ;; The variable gets defined immediately. --> following does nothing...
3040 ;;   (defvar tinydiff-:diff-hook '(tinydiff-parse-buffer tinydiff-mode))
3041
3042 (ti::add-hooks 'tinydiff-:diff-hook
3043                '(tinydiff-parse-buffer
3044                  turn-on-tinydiff-mode
3045                  tinydiff-turn-on-view-mode))
3046
3047 (tinydiff-install)
3048
3049 ;;}}}
3050
3051 (provide   'tinydiff)
3052 (run-hooks 'tinydiff-:load-hook)
3053
3054 ;;; tinydiff.el ends here